接基于openssl实现tls+socket的安全通信-1,这篇主要是讲解生成秘钥、证书,并且是使用socket api和openssl api来实现基于tls的安全通信。由于证书链并没有新东西,该篇主要还是以单证书为例,后续可以再整一个基于证书链的通信。
时隔一年,来补第二篇了。
一、SSL/TLS建立连接的过程
上一篇文章简单的说明下tls的过程,接下来分别从理论上、秘钥证书生成以及代码的角度分析建立连接的过程。下图既是单边TLS校验的全过程(server校验client)。
在建立好tcp连接之后,client首先会发送client hello,如下图所示,client hello主要包括随机字字符串;session id用于识别对话(有局限性,所以后面又有session ticket);Extension里面也是包含很多东西的,比如有支持的版本有哪些,椭圆曲线参数,session ticket等,针对不同的场景包含的内容也不同,大多时候我们是不怎么关心,需要的时候可以具体再查看一下;一些密码信息,比如支持哪些加密算法(cipher suites);还有就是client端的公钥,在key_share字段里面。这篇文章,对tls握手阶段的字段解释蛮清楚的:tls hello解析。不过这篇文章对ack的解释是错误的,跟加密无关,对端回ack既是证明对端收到这个报文,这是tcp的特性。
client hello之后,sever也会发server hello,server hello总结下来既是对client发过的一些信息进行确认(协商)。如下图,上个过程中,client把自己的公钥给了server,接下来server也会把自己的公钥给到client,同样是在key_share上面,并且选择跟client一样的椭圆曲线算法参数;server 也会从client cipher suite中选择一个client支持的加密算法,比如下面server选择的事TLS_AES_256_GCM_SHA384.
在client端收到server hello后,会根据两个公钥计算出用于对称加密的秘钥,并通过change cipher spec的报文通知到server,如下图所示,至此后续通信即可使用对称秘钥进行加密通信了。
所以总结下来,tls 握手过程是协商一些关键信息,比如版本、加密算法、椭圆算法的参数等,同时利用非对称加密使双方拿到对称加密的秘钥。这就避免了其前面文章,基于openssl实现tls+socket的安全通信-1,对称秘钥被其他窃取的可能,而不使用非对称的秘钥原因是因为对称秘钥加解密的效率更加高效,因为对称秘钥本身是加密传输的,所以也基本不可能被窃取。非对称加密和对称加密组合使用,既保证了安全性也保证了效率。
1. 证书和秘钥生成
证书是由秘钥生成的。openssl的介绍和使用通过这篇文章的介绍,我们可以生成最简单的秘钥和证书。如果想要使用更多参数,一定要多使用--help
。格式如:openssl req --help, openssl genrsa --help
,而不是openssl --help
。
秘钥生成:openssl genrsa -out test.key 2048
证书生成:openssl req -new -x509 -key test.key -out test.dvc -days 1095
可以生成一个秘钥test.key和证书test.dvc。
2. tls socket编程
TLS server代码本身没有什么很大难度,但是我们可以借助代码进一步的理解握手和整个通信本身。
//tls_server.c #include <arpa/inet.h> #include <errno.h> #include <netinet/in.h> #include <openssl/err.h> #include <openssl/ssl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #define MAXBUF 1024 int main(int argc, char **argv) { int sockfd, new_fd; socklen_t len; struct sockaddr_in server_addr, client_addr; char buf[MAXBUF + 1]; SSL_CTX *ctx = NULL; SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); ctx = SSL_CTX_new(SSLv23_server_method()); if (ctx == NULL) { ERR_print_errors_fp(stdout); exit(1); } printf("certificate file: '%s'.\n", argv[1]); if (SSL_CTX_use_certificate_file(ctx, argv[1], SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(1); } printf("privatekey file: '%s'.\n", argv[1]); if (SSL_CTX_use_PrivateKey_file(ctx, argv[2], SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(1); } if (!SSL_CTX_check_private_key(ctx)) { ERR_print_errors_fp(stdout); exit(1); } if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } else printf("socket created\n"); bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = PF_INET; server_addr.sin_port = htons(7838); if (argv[3]) server_addr.sin_addr.s_addr = inet_addr(argv[3]); else server_addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } else printf("binded\n"); if (listen(sockfd, 3) == -1) { perror("listen"); exit(1); } else printf("begin listen\n"); while (1) { SSL *ssl; len = sizeof(struct sockaddr); if ((new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &len)) == -1) { perror("accept"); exit(errno); } else printf("server: got connection from '%s', port %d, socket %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), new_fd); ssl = SSL_new(ctx); SSL_set_fd(ssl, new_fd); if (SSL_accept(ssl) == -1) { perror("accept"); close(new_fd); break; } bzero(buf, MAXBUF + 1); strcpy(buf, "server->client"); len = SSL_write(ssl, buf, strlen(buf)); if (len <= 0) { printf("send '%s' failed, errno: %d, error infomation: '%s' \n", buf, errno, strerror(errno)); goto finish; } else{ printf("send %d bytes '%s' successfuly.\n", len, buf); } bzero(buf, MAXBUF + 1); len = SSL_read(ssl, buf, MAXBUF); if (len > 0) printf("recv %d bytes with '%s'.", len, buf); else printf("recvive error. errno: %d, error infomation:'%s'.\n", errno, strerror(errno)); finish: SSL_shutdown(ssl); SSL_free(ssl); close(new_fd); } close(sockfd); SSL_CTX_free(ctx); return 0; }
TLS client
// tls_client.c #include <arpa/inet.h> #include <errno.h> #include <netinet/in.h> #include <openssl/err.h> #include <openssl/ssl.h> #include <resolv.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #define MAXBUF 1024 void ShowCerts(SSL *ssl) { X509 *cert; char *line; cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { printf("certificate info:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("subject: %s\n", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("issuer: %s\n", line); free(line); X509_free(cert); } else{ printf("no certificate infomation.\n"); } } int main(int argc, char **argv) { int sockfd, len; struct sockaddr_in dest; char buffer[MAXBUF + 1]; SSL_CTX *ctx; SSL *ssl; SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); ctx = SSL_CTX_new(SSLv23_client_method()); if (ctx == NULL) { ERR_print_errors_fp(stdout); exit(1); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket"); exit(errno); } printf("socket created\n"); bzero(&dest, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(atoi(argv[2])); if (inet_aton(argv[1], (struct in_addr *)&dest.sin_addr.s_addr) == 0) { perror(argv[1]); exit(errno); } printf("address created\n"); if (connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) != 0) { perror("Connect "); exit(errno); } printf("server connected\n"); ssl = SSL_new(ctx); SSL_set_fd(ssl, sockfd); //implement handshake if (SSL_connect(ssl) == -1) ERR_print_errors_fp(stderr); else { printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); ShowCerts(ssl); } bzero(buffer, MAXBUF + 1); len = SSL_read(ssl, buffer, MAXBUF); if (len > 0){ printf("recv %d bytes with '%s'.\n", len, buffer); } else{ printf("recvive error. errno: %d, error infomation:%s.\n", errno, strerror(errno)); goto finish; } bzero(buffer, MAXBUF + 1); strcpy(buffer, "from client->server"); len = SSL_write(ssl, buffer, strlen(buffer)); if (len <= 0) { printf("send '%s' failed, errno: %d, error infomation: '%s'.\n", buffer, errno, strerror(errno)); goto finish; } else{ printf("send %d bytes '%s' successfuly.\n", len, buffer); } finish: SSL_shutdown(ssl); SSL_free(ssl); close(sockfd); SSL_CTX_free(ctx); return 0; }
to write again!!!