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 <stdio.h> #include <errno.h> #include <unistd.h> #include <malloc.h> #include <string.h> #include <sys/socket.h> #include <resolv.h> #include <netdb.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/x509.h> #include <openssl/x509v3.h> #include <openssl/bio.h> #include <stdbool.h> #include <signal.h> /* 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 certificatecertBio = BIO_new(BIO_s_file());//Read certificate into BIOif (!(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 certif (certFile && SSL_CTX_use_certificate_file(ctx, certFile, SSL_FILETYPE_PEM) <= 0) {ERR_print_errors_fp(stderr);abort();}// load private keyif (keyFile && SSL_CTX_use_PrivateKey_file(ctx, keyFile, SSL_FILETYPE_PEM) <= 0) {ERR_print_errors_fp(stderr);abort();}// check private keyif (certFile && keyFile && !SSL_CTX_check_private_key(ctx)) {ERR_print_errors_fp(stderr);abort();}// load ca fileif (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 serverif (!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 librarySSL_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
?四、公開代碼倉庫
EiRi_jackmaster/pthread_ssl
等空了維護。
總結
以上是生活随笔為你收集整理的SSL 多线程通信 linux openSSL C API编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 熊猫烧香源码
- 下一篇: java if else过多_Sprin