需要提前准备好服务端和客户端的证书和私钥,以及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
四、公开代码仓库
EiRi_jackmaster/pthread_ssl
等空了维护。