관리 메뉴

사과하는 제라스

16. Basic OpenSSL 본문

대학 전공 공부/네트워크 프로그래밍

16. Basic OpenSSL

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
    반응형

    '대학 전공 공부 > 네트워크 프로그래밍' 카테고리의 다른 글

    17. OpenSSL(TLS 서버 구축)  (0) 2022.06.09
    15. 네트워크 보안  (0) 2022.06.08
    14강. 보안 기본 개념  (0) 2022.05.21
    13강. HTTP and Web Client  (0) 2022.05.13
    12강. DNS  (0) 2022.05.13