Xerath(제라스) 2022. 6. 8. 09:09
728x90
반응형

 - HTTP와 HTTPS의 차이 : TLS가 존재한다

- 대칭키 암호 알고리즘(Symmetric cipher)

: 암호화하는 송신자와 복호화하는 수신자는 동일한 키를 가지고 있어야 함.

 - 공개키 암호 알고리즘(Asymmetric cipher or Public Key encryption Algorithm)

: 공개키와 개인키가 한쌍으로 존재하고 같지 않음.(수학적인 관계는 존재함.), 대칭키와 비교했을 때 속도는 느리나 키 교환 분야에서 활용가능.

공개키 crypto system

1) 암호화 하는 기술

2) 전자서명하는 기술

 

- 전자 서명

: 공개키 암호 알고리즘의 사용방법 중 하나임.

송신자의 개인키로 서명 : MSG -> MSG+Sign Value

수신자는 송신자의 공개키로 복호화하여 Sign Value확인.

이때 이 송신자의 공개키를 신뢰되게 알려주는 것이 PKI 이다.

 

- OpenSSL

: 오픈소스 라이브러리로서 Application에게 SSL/TLS서비스를 제공함.

 

OpenSSL을 하기 위해서 3가지 operation이 필요함.

SSL_library_init() : OpenSSL 라이브러리를 쓸 수 있도록 초기화 함. 

OpenSSL_add_all_algorithm() : 사용할 알고리즘이 무엇인지 명시하기 힘들때 모든 가능한 알고리즘을 로드함.

SSL_load_error_strings() : 에러와 관련된 것들을 출력해 줌.(디버깅이 편해짐.)

 

일단, 위 3 operation들로 OpenSSL이 초기화되면 SSL context를 생성할 준비가 된 것임.

=> SSL_CTX_new() : SSL_CTX object를 만듦. 여기에 initial settings가 저장됨.

이건 connection동안 유지됨. ∴ connection 종료 시 메모리 할당 해제해야 함.

이 함수는 TLS_client_method()를 매개변수로 갖는데 이 함수는 TLS 메소드를 사용하기 위해서 쓰임.

이렇게 실행하면 ctx에 클라이언트가 자동적으로 가장 필요한 알고리즘과 그에 필요한  key를 세팅함.

=> 

SSL_new() : SSL 객체를 만드는 함수. 만들어진 SSL 객체를 가지고서 SSL/TLS 통신을 트랙킹 가능함.

SSL_set_tlsext_host_name() : 접속하고자 하는 서버의 domain name 세팅하는 함수

SSL_set_fd() , SSL_connect() : 만든 TCP 소켓에 ssl 변수를 세팅하는 함수, 만든 SSL 객체를 이용해서 해당 서버에 커넥션을 만드는 함수

 

SSL_write(), SSL_read() : 데이터를 송수신할 때 쓰는 함수들

SSL_shutdown() : SSL 관련된 커넥션 변수들, ctx 변수들이 메모리에 계속 할당되어 있으면 메모리 누수 발생

=> SSL_free(), SSLCTX_free()를 해줘서 할당된 메모리를 해지함.

SSL_get_cipher() : ssl변수를 통해서 세팅했던 알고리즘 리스트를 모두 출력할 수 있음.

SSL_get_peer_certificate() : 접속한 서버의 인증서를 가져올 수 있고 이를 이용하여 만든 인증서 객체(ex) X509)에서 필요한 정보를 함수들을 이용하여 가져올 수 있음.

/*
 * MIT License
 *
 * Copyright (c) 2018 Lewis Van Winkle
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <openssl/crypto.h> //SSL 관련 헤더들 선언됨.
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define ISVALIDSOCKET(s) ((s) >= 0)
#define CLOSESOCKET(s) close(s)
#define SOCKET int
#define GETSOCKETERRNO() (errno)


int main(int argc, char *argv[]) {

	// SSL라이브러리 초기화하는 코드.
    SSL_library_init(); 
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    
    //SSL object를 관리할 수 있는 ctx.
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
    if (!ctx) {
        fprintf(stderr, "SSL_CTX_new() failed.\n");
        return 1;
    }

	//입력받은 argument로 hostname, port번호 받아옴.
    if (argc < 3) {
        fprintf(stderr, "usage: https_simple hostname port\n");
        return 1;
    }

    char *hostname = argv[1];
    char *port = argv[2];

	//입력한 hostname을 통해 getaddrinfo로 ip주소 받아옴.
    printf("Configuring remote address...\n");
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    struct addrinfo *peer_address;
    if (getaddrinfo(hostname, port, &hints, &peer_address)) {
        fprintf(stderr, "getaddrinfo() failed. (%d)\n", GETSOCKETERRNO());
        exit(1);
    }
	
    //접속하고자 하는 서버에 대한 IP주소 출력.
    printf("Remote address is: ");
    char address_buffer[100];
    char service_buffer[100];
    getnameinfo(peer_address->ai_addr, peer_address->ai_addrlen,
            address_buffer, sizeof(address_buffer),
            service_buffer, sizeof(service_buffer),
            NI_NUMERICHOST);
    printf("%s %s\n", address_buffer, service_buffer);

	//TCP 소켓 server 만듦
    printf("Creating socket...\n");
    SOCKET server;
    server = socket(peer_address->ai_family,
            peer_address->ai_socktype, peer_address->ai_protocol);
    if (!ISVALIDSOCKET(server)) {
        fprintf(stderr, "socket() failed. (%d)\n", GETSOCKETERRNO());
        exit(1);
    }
    
	//TCP connection 진행.
    printf("Connecting...\n");
    if (connect(server,
                peer_address->ai_addr, peer_address->ai_addrlen)) {
        fprintf(stderr, "connect() failed. (%d)\n", GETSOCKETERRNO());
        exit(1);
    }
    freeaddrinfo(peer_address);

    printf("Connected.\n\n");

	//SSL object 만들어서 초기화.
    SSL *ssl = SSL_new(ctx);
    if (!ctx) {
        fprintf(stderr, "SSL_new() failed.\n");
        return 1;
    }
	
    //접속하고자 하는 hostname을 설정해줌.
    if (!SSL_set_tlsext_host_name(ssl, hostname)) {
        fprintf(stderr, "SSL_set_tlsext_host_name() failed.\n");
        ERR_print_errors_fp(stderr);
        return 1;
    }
	
    //ssl 변수를 server 소켓에 세팅하고 SSL_connect()를 이용하여 ssl에 접근함.
    SSL_set_fd(ssl, server);
    if (SSL_connect(ssl) == -1) {
        fprintf(stderr, "SSL_connect() failed.\n");
        ERR_print_errors_fp(stderr);
        return 1;
    }
	
    //어떤 알고리즘들이 쓰이는지 출력.
    printf ("SSL/TLS using %s\n", SSL_get_cipher(ssl));

	//cert라는 포인터에 원하는 것들을 세팅해서 출력가능.
    X509 *cert = SSL_get_peer_certificate(ssl);
    if (!cert) {
        fprintf(stderr, "SSL_get_peer_certificate() failed.\n");
        return 1;
    }

    char *tmp;
    if ((tmp = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0))) {
        printf("subject: %s\n", tmp);
        OPENSSL_free(tmp);
    }

    if ((tmp = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0))) {
        printf("issuer: %s\n", tmp);
        OPENSSL_free(tmp);
    }

    X509_free(cert);

	//HTTPS로 데이터를 보내기 위해서 데이터를 세팅함. 
    char buffer[2048];

    sprintf(buffer, "GET / HTTP/1.1\r\n");
    sprintf(buffer + strlen(buffer), "Host: %s:%s\r\n", hostname, port);
    sprintf(buffer + strlen(buffer), "Connection: close\r\n");
    sprintf(buffer + strlen(buffer), "User-Agent: https_simple\r\n");
    sprintf(buffer + strlen(buffer), "\r\n");

	//ssl 변수를 이용해서 SSL_write를 이용한 buffer값을 써주면 TCP 커넥션 위의 ssl 커넥션 위로 데이터가 오감.
    //buffer가 모두 암호화되어서 전송됨.
    SSL_write(ssl, buffer, strlen(buffer));
    printf("Sent Headers:\n%s", buffer);
	
    while(1) { //데이터를 받는 부분.
        int bytes_received = SSL_read(ssl, buffer, sizeof(buffer));
            if (bytes_received < 1) {
                printf("\nConnection closed by peer.\n");
                break;
            }
			//데이터를 받는 부분.
            printf("Received (%d bytes): '%.*s'\n",
                    bytes_received, bytes_received, buffer);

    } //end while(1)

    printf("\nClosing socket...\n");
    SSL_shutdown(ssl);
    CLOSESOCKET(server);
    SSL_free(ssl);
    SSL_CTX_free(ctx);

    printf("Finished.\n");
    return 0;
}

----------------------------------------------------------------------------------------------------------------------

- TLS 서버를 만드려면 항상 인증서(Certificate)를 세팅해야 함.

 

- 인증서와 전자서명을 통해서 우리가 접속하고자 하는 서버를 인증함.

 

728x90
반응형