SSL 多线程通信 linux openSSL C API编程

一、环境

需要提前准备好服务端和客户端的证书和私钥,以及CA的证书。

OpenSSL 1.1.1f  31 Mar 2020
built on: Wed Nov 24 13:20:48 2021 UTC
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(int) blowfish(ptr)

Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
 

二、实现

2.1 简要步骤

SSL客户端和服务端的实现流程大体一致,只是在多线程处理时不一样,服务器端多了线程管理,从而保证线程安全。

客户端:

SSL_library_init();
OpenSSL_add_all_algorithms(); 
ERR_load_BIO_strings();
ERR_load_crypto_strings();
SSL_load_error_strings();                  
ctx = SSL_CTX_new(SSLv23_client_method()); 
SSL_CTX_use_certificate_file();
SSL_CTX_use_PrivateKey_file();
SSL_CTX_check_private_key(ctx);
SSL_CTX_load_verify_locations();
server = connect_server(ADDR, PORT);
ssl    = SSL_new(ctx);
SSL_set_fd(ssl, server); 
SSL_connect(ssl);
SSL_write(ssl, req, strlen(req));
SSL_free(ssl);
close(server); 
SSL_CTX_free(ctx);

服务端:

SSL_library_init();
OpenSSL_add_all_algorithms(); 
ERR_load_BIO_strings();
ERR_load_crypto_strings();
SSL_load_error_strings();                  
ctx = SSL_CTX_new(SSLv23_client_method()); 
SSL_CTX_use_certificate_file();
SSL_CTX_use_PrivateKey_file();
SSL_CTX_check_private_key(ctx);
SSL_CTX_load_verify_locations();
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); /* verify client cert */
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CA_CERT));
server = port_listen(PORT); /* create server socket */
ssl    = SSL_new(ctx);
SSL_set_fd(ssl, server); 
SSL_accept(ssl);
SSL_write(ssl, req, strlen(req));
SSL_read(ssl, buf, sizeof(buf) - 1);
SSL_free(ssl);
close(server); 
SSL_CTX_free(ctx);

2.1 客户端实现

OpenSSL 1.1.1f版本的ssl接口是支持多线程的,因此直接使用pthread编程即可。

以下源码,ssl读写是两个线程,客户端不验证服务端的证书,增加了失败重连机制。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  /* sigaction */
#define FAIL                -1
#define PORT                7383
#define SSL_RETRY_DELAY_SEC 300
#define ADDR                "127.0.0.1"
#define CA_CERT             "./cert/ca.crt"
#define CLIENT_CA           "./cert/cert.crt"
#define CLIENT_KEY          "./cert/pri.pem"

static bool isServerDisconnect = true;
int connect_server(const char* hostname, int port)
{
    int sd;
    struct hostent* host;
    struct sockaddr_in addr;

    if ((host = gethostbyname(hostname)) == NULL) {
        perror(hostname);
        abort();
    }

    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_port        = htons(port);
    addr.sin_addr.s_addr = *(long*)(host->h_addr);

    if (connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
        close(sd);
        perror(hostname);
        abort();
    }

    return sd;
}
SSL_CTX* init_ctx(void)
{
    SSL_CTX* ctx;
    OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */
    ERR_load_BIO_strings();
    ERR_load_crypto_strings();
    SSL_load_error_strings();                  /* Bring in and register error messages */
    ctx = SSL_CTX_new(SSLv23_client_method()); /* Create new context */
    //ctx = SSL_CTX_new(TLSv1_client_method()); /* Create new context */
    if (ctx == NULL) {
        ERR_print_errors_fp(stderr);
        abort();
    }

    return ctx;
}
void show_cert_file(const char* certFile)
{
    BIO* certBio = NULL;
    X509* cert   = NULL;
    char* line;

    //create BIO object to read certificate
    certBio = BIO_new(BIO_s_file());

    //Read certificate into BIO
    if (!(BIO_read_filename(certBio, certFile))) {
        printf("reading certificate error in %s\r\n", certFile);
        BIO_free_all(certBio);
        exit(EXIT_FAILURE);
    }

    if (!(cert = PEM_read_bio_X509(certBio, NULL, 0, NULL))) {
        fprintf(stderr, "loading certificate error in %s\r\n", certFile);
        BIO_free_all(certBio);
        exit(EXIT_FAILURE);
    }

    line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
    printf("%s\n", line);
    free(line);      /* free the malloc'ed string */
    X509_free(cert); /* free the malloc'ed certificate copy */
    BIO_free_all(certBio);
}
void load_cert_key_ca_file(SSL_CTX* ctx, const char* certFile, const char* keyFile, const char* caFile)
{
    if (!ctx) {
        abort();
    }

    // load cert
    if (certFile && SSL_CTX_use_certificate_file(ctx, certFile, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        abort();
    }

    // load private key
    if (keyFile && SSL_CTX_use_PrivateKey_file(ctx, keyFile, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        abort();
    }

    // check private key
    if (certFile && keyFile && !SSL_CTX_check_private_key(ctx)) {
        ERR_print_errors_fp(stderr);
        abort();
    }

    // load ca file
    if (caFile && !SSL_CTX_load_verify_locations(ctx, caFile, 0)) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return;
}

/*! signal handling variables */
volatile bool exit_sig = false;
volatile bool quit_sig = false;
void sig_handler(int sigio)
{
    if (sigio == SIGQUIT) {
        quit_sig = true;
    } else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
        exit_sig = true;
    }

    return;
}
void thread_recv(void* arg)
{
    char buf[1024];
    int err;
    SSL* ssl = (SSL*)arg;
    while (1) {
        memset(buf, 0, 1024);
        err = SSL_read(ssl, buf, sizeof(buf) - 1);
        if (err < 0) {
            printf("SSL read error\r\n");
        } else if (err == 0) {
            printf("SSL disconnect\r\n");
            isServerDisconnect = true;
            return;
        }
        printf("SSL recv: %s\r\n", buf);
    }
}
int main(int count, char* strings[])
{
    SSL_CTX* ctx;
    int server;
    SSL* ssl;
    char buf[1024];
    pthread_t pid;
    int retryTimeSec;

    struct sigaction sigact;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags   = 0;
    sigact.sa_handler = sig_handler;
    sigaction(SIGQUIT, &sigact, NULL); /* Ctrl-\ */
    sigaction(SIGINT, &sigact, NULL);  /* Ctrl-C 8*/
    sigaction(SIGTERM, &sigact, NULL); /* default "kill" command */
    sigaction(SIGPIPE, &sigact, NULL); /* ignore  SIGPIPE */

    SSL_library_init();
    ctx = init_ctx();
    //SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); /* if need verify server cert */
    load_cert_key_ca_file(ctx, CLIENT_CA, CLIENT_KEY, CA_CERT);
    show_cert_file(CLIENT_CA);

RETRY:
    server = connect_server(ADDR, PORT);
    ssl    = SSL_new(ctx);   /* create new SSL connection state */
    SSL_set_fd(ssl, server); /* attach the socket descriptor */

    if (SSL_connect(ssl) == FAIL) { /* perform the connection */
        printf("SSL handshake error, rerey timeout %ds\r\n", SSL_RETRY_DELAY_SEC);
        close(server); /* close socket */
        retryTimeSec = SSL_RETRY_DELAY_SEC;
        while (retryTimeSec--) {
            if (!quit_sig && !exit_sig) {
                SSL_CTX_free(ctx); /* release context */
                printf("exit client\r\n");
            }
            sleep(1);
        }
        goto RETRY;
    } else {
        printf("SSL handshake success\r\n");
        const char* req = "jackwang client request";
        printf("connected with %s encryption\n", SSL_get_cipher(ssl));
        isServerDisconnect = false;
        pthread_create(&pid, NULL, (void* (*)(void*))thread_recv, (void*)ssl);
        while (!quit_sig && !exit_sig && !isServerDisconnect) {
            /* get any certs */
            memset(buf, 0, 1024);
            SSL_write(ssl, req, strlen(req)); /* encrypt & send message */
            sleep(1);
        }
        SSL_free(ssl); /* release connection state */
    }
    pthread_cancel(pid);
    pthread_join(pid, NULL);

    close(server);     /* close socket */
    SSL_CTX_free(ctx); /* release context */
    printf("exit client\r\n");
    return 0;
}

2.2 服务端

服务端主要多了线程管理和证书校验。以下贴出主程序和线程代码。

void thread_main(void* arg)
{
    char buf[1024];
    int clientPort;
    char clientAddr[PEER_IP_LENGTH];

    SSL* ssl    = (SSL*)arg;
    int sock_fd = SSL_get_fd(ssl);
    int err     = SSL_accept(ssl);
    clientPort  = getpeer_information(sock_fd, clientAddr);

    if (err < 0) {
        printf("%s:%d SSL handshake error\r\n", clientAddr, clientPort);
        close(sock_fd);
        SSL_free(ssl);
        pthread_exit((void*)-1);
    } else {
        printf("%s:%d SSL handshake success\r\n", clientAddr, clientPort);
        threadpool_add_one(pthread_self(), ssl);
        //printf("total client num:%d\r\n", threadpool_num());
    }

    printf("%s:%d SSL connection using %s\n", clientAddr, clientPort, SSL_get_cipher(ssl));
    show_cert_ssl(ssl);

    while (1) {
        memset(buf, 0, 1024);
        err = SSL_read(ssl, buf, sizeof(buf) - 1);
        if (err < 0) {
            printf("%s:%d SSL read error\r\n", clientAddr, clientPort);
            goto FINISH;
        } else if (err == 0) {
            printf("%s:%d SSL disconnect\r\n", clientAddr, clientPort);
            goto FINISH;
        }
        printf("%s:%d SSL recv: %s\n", clientAddr, clientPort, buf);
    }

FINISH:
    threadpool_remove_one(pthread_self(), ssl);
}

int main(int count, char* Argc[])
{
    SSL_CTX* ctx;
    int server;
    pthread_t pid;
    int ret;
    //Only root user have the permsion to run the server
    if (!is_root()) {
        printf("This program must be run as root/sudo user!!\r\n");
        exit(0);
    }

    struct sigaction sigact;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags   = 0;
    sigact.sa_handler = sig_handler;
    sigaction(SIGQUIT, &sigact, NULL); /* Ctrl-\ */
    sigaction(SIGINT, &sigact, NULL);  /* Ctrl-C 8*/
    sigaction(SIGTERM, &sigact, NULL); /* default "kill" command */
    sigaction(SIGPIPE, &sigact, NULL); /* ignore  SIGPIPE */

    threadpool_init(); /* init threadpool */

    // Initialize the SSL library
    SSL_library_init();
    ctx = init_ctx(); /* initialize SSL */
    load_cert_key_ca_file(ctx, SERVER_CA, SERVER_KEY, CA_CERT);

    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); /* verify client cert */
    SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CA_CERT));

    server = port_listen(PORT); /* create server socket */
    printf("server listening (%d)\r\n", PORT);

    while (!sigQuit && !sigExit) {
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        SSL* ssl;
        int client = accept(server, (struct sockaddr*)&addr, &len); /* accept connection as usual */
        if (client > 0) {                                           //printf("connection: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
            ssl = SSL_new(ctx);                                     /* get new SSL state with context */
            ret = SSL_set_fd(ssl, client);                          /* set connection socket to SSL state */
            if (ret > 0) {
                pthread_create(&pid, NULL, (void* (*)(void*))thread_main, (void*)ssl);
            } else {
                continue;
            }
        }
    }
    threadpool_remove_all();
    close(server);     /* close server socket */
    SSL_CTX_free(ctx); /* release context */
    ERR_free_strings();
}

三、编译

客户端:gcc -Wall -o client ssl_client.c -L/usr/lib -lssl -lcrypto -lpthread

服务端:gcc -Wall -o server ssl_server.c -L/usr/lib -lssl -lcrypto -lpthread

若没有ssl和crypto库,则需要安装:apt-get install libssl-dev

SSL 多线程通信 linux openSSL C API编程_第1张图片

 四、公开代码仓库

EiRi_jackmaster/pthread_ssl

等空了维护。

你可能感兴趣的:(网络安全,Linux,linux,ssl)