一、环境
需要提前准备好服务端和客户端的证书和私钥,以及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
等空了维护。