본문 바로가기
SWE/네트워크

SSL TLS 소켓 프로그래밍 | openssl self signed certificate 생성하여 서버 테스트

by S나라라2 2022. 10. 4.
반응형

 

ssl handshake 과정

 

Secure Socket Programming 코드는 구글링을 하면 쉽게 찾을 수 있다.

이 코드를 테스트해보자!

 

테스트를 위해 openssl을 이용해 self signed certificate을 먼저 만든다.

 

 

Self Signed Certificate 생성 과정

1. 개인키 생성
2. 공개키 생성
3. CSR 생성
4. CRT 생성

 

위의 과정을 디테일하게 설명하자면

먼저 ssl 통신을 위해 서버에서는 개인키, 공개키 쌍으로 필요하다. 

먼저 생성한 개인키를 기반으로 공개키를 추출할 것이다.

 

그리고 Certificate Sigining Request(인증요청서)를 생성한다.

 인증 요청서란 SSL 인증의 정보를 암호화하여 인증기관에 보내 인증서를 발급받게 하는 신청서이다.

이 CSR도 앞서 생성한 개인키를 이용해서 만들 수 있ㄷ.

 

CRT(인증서)를 생성한다.

root CA를 통해서 인증서를 만들어야 맞지만, 우리는 테스트를 위한 것이니까

CSR을 명시적으로 넣어서 직접 인증서를 만들 것이다.

 

*참고로, 서버 개인키는 암호화되어 저장되어서는 안된다. 그렇지 않으면 관리자가 패스워드를 너무 자주 입력해야 한다.

 

 

 

1. 개인키 생성

$openssl genrsa -out private.key 2048

 

2. 공개키 생성

$ openssl rsa -in private.key -pubout -out public.key

 

3. CSR 생성

$ openssl req -new -key private.key -out cert.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:Gyeonggi
Locality Name (eg, city) []:Pangyo
Organization Name (eg, company) [Internet Widgits Pty Ltd]:TempCor
Organizational Unit Name (eg, section) []:TempSec
Common Name (e.g. server FQDN or YOUR name) []:  # [ip address] or [url]  
Email Address []:temp@temp.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

 

4. crt 생성

$ openssl x509 -req -days 365 -in cert.csr -signkey private.key -out cert.crt

 

결과

인증서, 인증요청서, 개인키와 공개키를 확인할 수 있다.

 


 

Secure Socket Programming 예제 코드

 

// server의 main.cpp

더보기
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

 

int create_socket(int port)
{
    int s;
    struct sockaddr_in addr;

 

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

 

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
        perror("Unable to create socket");
        exit(EXIT_FAILURE);
    }

 

    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("Unable to bind");
        exit(EXIT_FAILURE);
    }

 

    if (listen(s, 1) < 0) {
        perror("Unable to listen");
        exit(EXIT_FAILURE);
    }

 

    return s;
}

 

SSL_CTX *create_context()
{
    const SSL_METHOD *method;
    method = TLS_server_method();
   
    SSL_CTX *ctx;
    ctx = SSL_CTX_new(method);
    if (!ctx) {
        perror("Unable to create SSL context");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

 

    return ctx;
}

 

void configure_context(SSL_CTX *ctx)
{
    /* Set the key and cert */
    if (SSL_CTX_use_certificate_file(ctx, "[path]/cert.crt", SSL_FILETYPE_PEM) <= 0) {  
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }

 

    if (SSL_CTX_use_PrivateKey_file(ctx, "[path]/private.key", SSL_FILETYPE_PEM) <= 0 ) {  
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
}

 

int main(int argc, char **argv)
{
    printf("welcom ssl server\n");
    int sock;
    SSL_CTX *ctx;

 

    ctx = create_context();

 

    configure_context(ctx);

 

    sock = create_socket(4433);

 

    /* Handle connections */
    while (1) {
        printf("in while\n");
        struct sockaddr_in addr;
        unsigned int len = sizeof(addr);
        SSL *ssl;
        const char reply[] = "test\n";

 

        int client = accept(sock, (struct sockaddr*)&addr, &len);
        if (client < 0) {
            perror("Unable to accept");
            exit(EXIT_FAILURE);
        }

 

        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client);

 

        if (SSL_accept(ssl) <= 0) {
            ERR_print_errors_fp(stderr);
        } else {
            SSL_write(ssl, reply, strlen(reply));
        }

 

        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(client);
    }

 

    close(sock);
    SSL_CTX_free(ctx);

 

    return 0;
}



 

// server의 CMakeLists.txt

*참고로 cmake를 사용하지 않고 바로 gcc빌드해도 된다 $gcc -o ssl_server -lssl -lcrypto

더보기
# required cmake minimum version
CMAKE_MINIMUM_REQUIRED ( VERSION 3.2.1 )

 

# name of project
PROJECT( "ssl_server" )
SET(TARGET_EXE ssl_server)

 

# executable program
add_executable(${TARGET_EXE} main.cpp)

 

TARGET_LINK_LIBRARIES( ${TARGET_EXE} /usr/lib/x86_64-linux-gnu/libssl.a )
TARGET_LINK_LIBRARIES( ${TARGET_EXE} /usr/lib/x86_64-linux-gnu/libcrypto.so )
 
 
add_compile_options(-lssl -lcrypto -w)  # openssl

 

// client의 main.cpp

더보기
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <resolv.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
 
const int ERROR_STATUS = -1;

 

SSL_CTX *InitSSL_CTX(void)
{
    const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */
    SSL_CTX *ctx = SSL_CTX_new(::TLS_client_method());

 

    if (ctx == nullptr)
    {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    return ctx;
}

 

int OpenConnection(const char *hostname, const char *port)
{
    struct hostent *host;
    if ((host = gethostbyname(hostname)) == nullptr)
    {
        perror(hostname);
        exit(EXIT_FAILURE);
    }

 

    struct addrinfo hints = {0}, *addrs;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

 

    const int status = getaddrinfo(hostname, port, &hints, &addrs);
    if (status != 0)
    {
        fprintf(stderr, "%s: %s\n", hostname, gai_strerror(status));
        exit(EXIT_FAILURE);
    }

 

    int sfd, err;
    for (struct addrinfo *addr = addrs; addr != nullptr; addr = addr->ai_next)
    {
        sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
        if (sfd == ERROR_STATUS)
        {
            err = errno;
            continue;
        }

 

        if (connect(sfd, addr->ai_addr, addr->ai_addrlen) == 0)
        {
            break;
        }

 

        err = errno;
        sfd = ERROR_STATUS;
        close(sfd);
    }

 

    freeaddrinfo(addrs);

 

    if (sfd == ERROR_STATUS)
    {
        fprintf(stderr, "%s: %s\n", hostname, strerror(err));
        exit(EXIT_FAILURE);
    }
    return sfd;
}

 

/*
delete에서 에러나와서 주석처리함.
현재 테스트를 위해선 없어도 됨
void DisplayCerts(SSL *ssl)
{
    X509 *cert = SSL_get_peer_certificate(ssl); /* get the server's certificate
    if (cert != nullptr)
    {
        printf("Server certificates:\n");
        char *line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("Subject: %s\n", line);
        delete line;
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("Issuer: %s\n", line);
        delete line;
        X509_free(cert);
    }
    else
    {
        printf("Info: No client certificates configured.\n");
    }
}
*/

 

int main(int argc, char const *argv[])
{
    SSL_CTX *ctx = InitSSL_CTX();
    SSL *ssl = SSL_new(ctx);
    if (ssl == nullptr)
    {
        fprintf(stderr, "SSL_new() failed\n");
        exit(EXIT_FAILURE);
    }

 

    //Host is hardcoded to localhost for testing purposes
    const int sfd = OpenConnection("[ip address or server url]", "4433");  
    SSL_set_fd(ssl, sfd);

 

    const int status = SSL_connect(ssl);
    if (status != 1)
    {
        SSL_get_error(ssl, status);
        ERR_print_errors_fp(stderr); //High probability this doesn't do anything
        fprintf(stderr, "SSL_connect failed with SSL_get_error code %d\n", status);
        exit(EXIT_FAILURE);
    }

 

    printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
    //DisplayCerts(ssl);
    const char *chars = "Hello World, 123!";
    SSL_write(ssl, chars, strlen(chars));
    SSL_free(ssl);
    close(sfd);
    SSL_CTX_free(ctx);
    return 0;
}

 

// client의 CMakeLists.txt

더보기

# required cmake minimum version
CMAKE_MINIMUM_REQUIRED ( VERSION 3.2.1 )

# name of project
PROJECT( "ssl_client" )
SET(TARGET_EXE ssl_client)

# executable program
add_executable(${TARGET_EXE} main.cpp)

TARGET_LINK_LIBRARIES( ${TARGET_EXE} /usr/lib/x86_64-linux-gnu/libssl.a )
TARGET_LINK_LIBRARIES( ${TARGET_EXE} /usr/lib/x86_64-linux-gnu/libcrypto.so )


add_compile_options(-lssl -lcrypto -w)  # openssl

 


 

이제 ssl_server, ssl_client 프로그램을 실행하여 확인해보자

 

실행 결과

client가 어떤 사이퍼로 연결을 성공했는지 출력한다.

 

client,server 코드 둘 다 패킷을 전송만 하고 있다.

SSL_read로 코드를 약간만 수정하면 서로 데이터 패킷 주고받는 것을 확인해 볼 수 있다.

 


 

출처

https://www.lesstif.com/system-admin/openssl-root-ca-ssl-6979614.html

https://blog.hangadac.com/2017/07/31/%ED%99%88%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%EA%B8%B0-ssl-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%97%B0%EC%8A%B5/OpenSSL API를 이용한 보안 프로그래밍, Part 3: 보안 서비스 제공하기 (한글) :: 인디노트 (tistory.com)

HTTPS와 SSL 인증서 - 생활코딩 (opentutorials.org)

A C++ Client That Sends Data Over TLS Using OpenSSL · GitHub

https://wiki.openssl.org/index.php/Simple_TLS_Server

반응형