openssl stream
转载涉及的源码
- statem.c : openssl/ssl/statem/statem.c
- statem.h : openssl/ssl/statem/statem.h
- ssl.h : openssl/nclude/openssl/ssl.h
- statem_lib.c : openssl/ssl/statem/statem_lib.c
- statem_clnt.c : openssl/ssl/statem/statem_clnt.c
- statem_srvr.c : openssl/ssl/statem/statem_srvr.c
- methods.c : openssl/ssl/methods.c
示例代码可见 :openssl/zsk ,包含双向认证的两种方式。
四、Openssl 库中状态机简述
Openssl 库中主要有两个状态机:消息流状态机、握手状态机组成。消息流状态机 控制消息的读取和发送,包括处理 非阻塞的IO事件、刷写BIO、处理意外消息等。同时他本身被独立分解成两个独立的子状态机,用来分别控制读取和发送消息。握手状态机 会跟踪当前的 SSL/TLS 握手状态。握手状态的转换是随消息流状态机的事件处理而变化。这些状态机保存着握手需要的一些消息处理函数和算法函数用来解析消息和执行加解密操作。因此正常情况下,状态机遵循 “接收消息-处理消息-切换状态-发送消息” 这个流程,一旦消息流状态机不按正常的流程走,就会导致状态机的异常。
这里先来看一下状态机情况总览:
* --------------------------------------------- -------------------
* | | | |
* | Message flow state machine | | |
* | | | |
* | -------------------- -------------------- | Transition | Handshake state |
* | | MSG_FLOW_READING | | MSG_FLOW_WRITING | | Event | machine |
* | | sub-state | | sub-state | |----------->| |
* | | machine for | | machine for | | | |
* | | reading messages | | writing messages | | | |
* | -------------------- -------------------- | | |
* | | | |
* --------------------------------------------- -------------------
4.1 消息流状态机
消息流状态机,共有5个状态用于表明消息的状态。摘取 Openssl 库对此状态机的描述如下:
* The main message flow state machine. We start in the MSG_FLOW_UNINITED or
* MSG_FLOW_FINISHED state and finish in MSG_FLOW_FINISHED. Valid states and
* transitions are as follows:
*
* MSG_FLOW_UNINITED MSG_FLOW_FINISHED
* | |
* +-----------------------+
* v
* MSG_FLOW_WRITING <---> MSG_FLOW_READING
* |
* V
* MSG_FLOW_FINISHED
* |
* V
* [SUCCESS]
*
* We may exit at any point due to an error or NBIO event. If an NBIO event
* occurs then we restart at the point we left off when we are recalled.
* MSG_FLOW_WRITING and MSG_FLOW_READING have sub-state machines associated with them.
*
* In addition to the above there is also the MSG_FLOW_ERROR state. We can move
* into that state at any point in the event that an irrecoverable error occurs.
*
* Valid return values are:
* 1: Success
* <=0: NBIO or error
可以明显看到消息流状态机的几个状态的转移过程,对于几个状态在此做详细释义:
- MSG_FLOW_UNINITED:握手尚未开始
- MSG_FLOW_ERROR:当前连接发生错误
- MSG_FLOW_READING:当前正读取消息
- MSG_FLOW_WRITING:当前正写入消息
- MSG_FLOW_FINISHED:握手结束
4.2 读状态机
读状态机是属于消息流状态机的子状态机,这里我们单独拿出来分析
* This function implements the sub-state machine when the message flow is in
* MSG_FLOW_READING. The valid sub-states and transitions are:
*
* READ_STATE_HEADER <--+<-------------+
* | | |
* v | |
* READ_STATE_BODY -----+-->READ_STATE_POST_PROCESS
* | |
* +----------------------------+
* v
* [SUB_STATE_FINISHED]
*
* READ_STATE_HEADER has the responsibility for reading in the message header
* and transitioning the state of the handshake state machine.
*
* READ_STATE_BODY reads in the rest of the message and then subsequently
* processes it.
*
* READ_STATE_POST_PROCESS is an optional step that may occur if some post
* processing activity performed on the message may block.
*
* Any of the above states could result in an NBIO event occurring in which case
* control returns to the calling application. When this function is recalled we
* will resume in the same state where we left off.
读状态机的状态释义如下:
- READ_STATE_HEADER:负责读取消息头并转换握手状态机的状态
- READ_STATE_BODY:读取消息的其余部分,然后随后对其进行处理
- READ_STATE_POST_PROCESS:是一个可选的步骤,如果对消息执行的某些后处理活动可能会被阻塞,则可能会发生该步骤
由上图中状态机转移图可知 读状态机 完整操作之后会转移到 SUB_STATE_FINISHED 这个状态。这个状态是定义在 statem.c 中的枚举类,用来表明子状态机的返回值:
typedef enum {
SUB_STATE_ERROR, //发生错误
SUB_STATE_FINISHED, //子状态完成后,将转到下一个子状态
SUB_STATE_END_HANDSHAKE //子状态已完成,握手也已完成
} SUB_STATE_RETURN;
4.3 写状态机
写状态机同样也属于消息流状态机的子状态机,摘录 Openssl 中的说明如下:
* This function implements the sub-state machine when the message flow is in
* MSG_FLOW_WRITING. The valid sub-states and transitions are:
*
* +-> WRITE_STATE_TRANSITION ------> [SUB_STATE_FINISHED]
* | |
* | v
* | WRITE_STATE_PRE_WORK -----> [SUB_STATE_END_HANDSHAKE]
* | |
* | v
* | WRITE_STATE_SEND
* | |
* | v
* | WRITE_STATE_POST_WORK
* | |
* +-------------+
*
* WRITE_STATE_TRANSITION transitions the state of the handshake state machine
* WRITE_STATE_PRE_WORK performs any work necessary to prepare the later
* sending of the message. This could result in an NBIO event occurring in
* which case control returns to the calling application. When this function
* is recalled we will resume in the same state where we left off.
*
* WRITE_STATE_SEND sends the message and performs any work to be done after
* sending.
*
* WRITE_STATE_POST_WORK performs any work necessary after the sending of the
* message has been completed. As for WRITE_STATE_PRE_WORK this could also
* result in an NBIO event.
写状态机的状态释义如下:
- WRITE_STATE_TRANSITION:转换握手状态机的状态
- WRITE_STATE_PRE_WORK:发送消息之前执行其他的准备工作
- WRITE_STATE_SEND:发送消息,并执行发送后要完成的任何工作
- WRITE_STATE_POST_WORK:在消息发送完成后执行其他必要的工作
4.4 握手状态机
握手状态机的状态太多,此处不一一说明,因为其中定义了不同协议的状态比较繁琐,具体可参见 第三章 ssl.h 中定义
五、状态机工作过程
下面将从总体的角度来分析状态机的状态转移,首先说明三个变量的含义:
- s->statem.state : 消息流状态
- s->statem.hand_state : 握手状态
- st->write_state:写状态
- st->read_state:读状态
1: 初始化 SSL_CTX_new , 代入参数 TLS_client_method 指定使用的协议,其具体的函数的定义在 methods.c :IMPLEMENT_tls_meth_func(...)
其中指定了连接函数是 ossl_statem_connect
2:初始化消息流状态机的状态 SSL_connect -> SSL_set_connect_state -> ossl_statem_clear
👉️s->statem.state = MSG_FLOW_UNINITED;👈️
👉️s->statem.hand_state = TLS_ST_BEFORE;👈️ //握手状态机
3:开始连接 SSL_do_handshake -> ossl_statem_connect -> state_machine ->
👉️s->statem.state = MSG_FLOW_UNINITED;👈️
👉️s->statem.hand_state = TLS_ST_BEFORE;👈️ //握手状态机
👉️st->request_state = TLS_ST_BEFORE;👈️
4:SSL_do_handshake 函数中在判断一些列条件和初始化一些参数之后开始切换 消息流状态机的状态
👉️st->state = MSG_FLOW_WRITING;👈️
👉️init_write_state_machine(s);👈️
👉️st->write_state = WRITE_STATE_TRANSITION;👈️
5:SSL_do_handshake 函数中 消息流状态机切换写状态之后,启动写状态机
👉️ssret = write_state_machine(s);👈️
❤️ ssret 如果状态为 SUB_STATE_FINISHED 则消息流状态机 切换读状态,
👉️st->state = MSG_FLOW_READING;👈️
👉️ init_read_state_machine(s);👈️
👉️st->read_state = READ_STATE_HEADER;👈️
6:SSL_do_handshake 函数中 消息流状态机切换读状态之后,启动读状态机
👉️ ssret = read_state_machine(s);👈️
❤️ ssret 如果状态为 SUB_STATE_FINISHED 则消息流状态机 切换写状态,
👉️st->state = MSG_FLOW_WRITING;👈️
👉️init_write_state_machine(s);👈️
👉️st->write_state = WRITE_STATE_TRANSITION;👈️
至此 第 5,6 步骤 处于不断的循环中 消息流的状态切换 启动对应的读写状态机去收发消息, 这是 消息流状态机的 工作原理 ,那么握手状态机是怎么工作的呢?
握手状态机:
7、消息流状态机和握手状态机产生关系是在 第5,6步骤:
深入 write_state_machine(s) 来看
write_state_machine -> 分别设定客户端和服务端的一些处理函数,这里以 clent 为例:
transition = ossl_statem_client_write_transition;
pre_work = ossl_statem_client_pre_work;
post_work = ossl_statem_client_post_work;
get_construct_message_f = ossl_statem_client_construct_message;
-> transition(st->write_state = WRITE_STATE_TRANSITION) -> ossl_statem_client_write_transition (statem_cLnt.c)
-> 📌️st->hand_state=TLS_ST_BEFORE -> 切换握手状态机的状态 :
👉️st->hand_state = TLS_ST_CW_CLNT_HELLO;👈️
transition 返回值为 WRITE_TRAN_CONTINUE: 此时返回到 write_state_machine 函数
👉️st->write_state = WRITE_STATE_PRE_WORK;👈️
👉️st->write_state_work = WORK_MORE_A;👈️
write_state_machine : st->write_state = WRITE_STATE_PRE_WORK -> pre_work -> ossl_statem_client_pre_work (statem_cLnt.c)
-> 📌️st->hand_state=TLS_ST_CW_CLNT_HELLO
😄️😄️😄️[重要] -> get_construct_message_f -> ossl_statem_client_construct_message (statem_cLnt.c) 此处构建客户端的消息
pre_work 返回 WORK_FINISHED_CONTINUE: 此时返回到 write_state_machine 函数
👉️st->write_state = WRITE_STATE_SEND;👈️
-> 📌️st->write_state=WRITE_STATE_SEND -> 切换写状态机的状态:
👉️statem_do_write👈️ 这个函数没弄清楚
👉️st->write_state = WRITE_STATE_POST_WORK;👈️
write_state_machine : st->write_state=WRITE_STATE_POST_WORK -> post_work -> ossl_statem_client_post_work (statem_cLnt.c)
-> 📌️st->hand_state=TLS_ST_CW_CLNT_HELLO :
post_work 返回 WORK_FINISHED_CONTINUE : 此时返回到 write_state_machine 函数
👉️st->write_state = WRITE_STATE_TRANSITION;👈️
-> transition(st->write_state = WRITE_STATE_TRANSITION) -> ossl_statem_client_write_transition (statem_cLnt.c)
-> 📌️st->hand_state=TLS_ST_CW_CLNT_HELLO -> 切换握手状态机的状态 :
transition 返回 WRITE_TRAN_FINISHED : 此时返回到 write_state_machine 函数
write_state_machine 返回 SUB_STATE_FINISHED 表示写入状态机完成 ,此时返回到 上述第5步骤 ,以此进入消息循环 读状态机相同
#生成根证书私钥(pem文件) openssl genrsa -out cakey.pem 2048 #生成根证书签发申请文件(csr文件) openssl req -new -key cakey.pem -out ca.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=myCA" #自签发根证书(cer文件) openssl x509 -req -days 365 -sha1 -extensions v3_ca -signkey cakey.pem -in ca.csr -out cacert.pem
client:
#生成客户端私钥 openssl genrsa -out key.pem 2048 #生成证书请求文件 openssl req -new -key key.pem -out client.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=myClient" #使用根证书签发客户端证书 openssl x509 -req -days 365 -sha1 -extensions v3_req -CA ../rootca/cacert.pem -CAkey ../rootca/cakey.pem -CAserial ../server/ca.srl -in client.csr -out cert.pem #使用CA证书验证客户端证书 openssl verify -CAfile ../rootca/cacert.pem cert.pem gcc -o client client.c -O0 -ggdb -I/usr/local/ssl/include -L/usr/local/ssl/lib -lssl -lcrypto
#include <stdio.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <resolv.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/ssl.h> #include <openssl/err.h> #define MAXBUF 1024 #define CA_FILE "/home/admin/testcert/rootca/cacert.pem" #define CLIENT_KEY "/home/admin/testcert/client/key.pem" #define CLIENT_CERT "/home/admin/testcert/client/cert.pem" void ShowCerts(SSL * ssl) { X509 *cert; char *line; cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { printf("数字证书信息:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("证书: %s\n", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("颁发者: %s\n", line); free(line); X509_free(cert); } else { printf("无证书信息!\n"); } } int main(int argc, char **argv) { int sockfd, len; struct sockaddr_in dest; char buffer[MAXBUF + 1]; SSL_CTX *ctx; SSL *ssl; const SSL_METHOD *method; if (argc != 3) { printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个" "IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息", argv[0], argv[0]); exit(0); } SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); method = TLSv1_2_client_method(); ctx = SSL_CTX_new(method); if (!ctx) { printf("create ctx is failed.\n"); } #if 0 const char * cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH"; if (SSL_CTX_set_cipher_list(ctx, cipher_list) == 0) { SSL_CTX_free(ctx); printf("Failed to set cipher list: %s", cipher_list); } #endif /*设置会话的握手方式*/ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0); /*加载CA FILE*/ if (SSL_CTX_load_verify_locations(ctx, CA_FILE, 0) != 1) { SSL_CTX_free(ctx); printf("Failed to load CA file %s", CA_FILE); } if (SSL_CTX_set_default_verify_paths(ctx) != 1) { SSL_CTX_free(ctx); printf("Call to SSL_CTX_set_default_verify_paths failed"); } /*加载客户端证书*/ if (SSL_CTX_use_certificate_file(ctx, CLIENT_CERT, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(ctx); printf("Failed to load client certificate from %s", CLIENT_KEY); } /*加载客户端私钥*/ if (SSL_CTX_use_PrivateKey_file(ctx, CLIENT_KEY, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(ctx); printf("Failed to load client private key from %s", CLIENT_KEY); } /*验证私钥*/ if (SSL_CTX_check_private_key(ctx) != 1) { SSL_CTX_free(ctx); printf("SSL_CTX_check_private_key failed"); } /*处理握手多次*/ SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket"); exit(errno); } 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); } if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) { perror("Connect "); exit(errno); } /*创建SSL*/ ssl = SSL_new(ctx); if (ssl == NULL) { printf("SSL_new error.\n"); } /*将fd添加到ssl层*/ SSL_set_fd(ssl, sockfd); if (SSL_connect(ssl) == -1) { printf("SSL_connect fail.\n"); 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("接收消息成功:'%s',共%d个字节的数据\n", buffer, len); } else { printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno)); goto finish; } bzero(buffer, MAXBUF + 1); strcpy(buffer, "context:from client->server"); len = SSL_write(ssl, buffer, strlen(buffer)); if (len < 0) { printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno)); } else { printf("消息'%s'发送成功,共发送了%d个字节!\n", buffer, len); } finish: SSL_shutdown(ssl); SSL_free(ssl); close(sockfd); SSL_CTX_free(ctx); return 0; }
server
#生成服务端私钥 openssl genrsa -out key.pem 2048 #生成证书请求文件 openssl req -new -key key.pem -out server.csr -subj "/C=CN/ST=myprovince/L=mycity/O=myorganization/OU=mygroup/CN=myServer" #使用根证书签发服务端证书 openssl x509 -req -days 365 -sha1 -extensions v3_req -CA ../rootca/cacert.pem -CAkey ../rootca/cakey.pem -CAserial ca.srl -CAcreateserial -in server.csr -out cert.pem #使用CA证书验证server端证书 openssl verify -CAfile ../rootca/cacert.pem cert.pem gcc -o server server.c -O0 -ggdb -I/usr/local/ssl/include -L/usr/local/ssl/lib -lssl -lcrypto
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #include <openssl/ssl.h> #include <openssl/err.h> #define MAXBUF 1024 #define CA_FILE "/home/admin/testcert/rootca/cacert.pem" #define SERVER_KEY "/home/admin/testcert/server/key.pem" #define SERVER_CERT "/home/admin/testcert/server/cert.pem" int main(int argc, char **argv) { int sockfd, new_fd; int reuse = 0; socklen_t len; struct sockaddr_in my_addr, their_addr; unsigned int myport, lisnum; char buf[MAXBUF + 1]; SSL_CTX *ctx; const SSL_METHOD *method; if (argv[1]) { myport = atoi(argv[1]); } else { myport = 7838; } if (argv[2]) { lisnum = atoi(argv[2]); } else { lisnum = 2; } SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); method = TLSv1_2_server_method(); ctx = SSL_CTX_new(method); if (ctx == NULL) { ERR_print_errors_fp(stdout); exit(1); } #if 0 const char *cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"; if (SSL_CTX_set_cipher_list(ctx, cipher_list) == 0) { SSL_CTX_free(ctx); printf("Failed to set cipher list %s", cipher_list); } #endif SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, 0); /*加载CA FILE*/ if (SSL_CTX_load_verify_locations(ctx, CA_FILE, 0) != 1) { SSL_CTX_free(ctx); printf("Failed to load CA file %s", CA_FILE); } /*加载服务端证书*/ if (SSL_CTX_use_certificate_file(ctx, SERVER_CERT, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stdout); exit(1); } /*加载服务端私钥*/ if (SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY, SSL_FILETYPE_PEM) <= 0) { printf("use private key fail.\n"); ERR_print_errors_fp(stdout); exit(1); } /*验证私钥*/ if (!SSL_CTX_check_private_key(ctx)) { ERR_print_errors_fp(stdout); exit(1); } //处理握手多次 SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } else { printf("socket created\n"); } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){ printf("setsockopet error\n"); return -1; } bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = PF_INET; my_addr.sin_port = htons(myport); my_addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } printf("Server bind success.\n"); if (listen(sockfd, lisnum) == -1) { perror("listen"); exit(1); } printf("Server begin to listen\n"); while (1) { SSL *ssl; len = sizeof(struct sockaddr); if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len)) == -1) { perror("accept"); exit(errno); } printf("Server: receive a connection from %s, port %d, socket %d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd); ssl = SSL_new(ctx); if (ssl == NULL) { printf("SSL_new error.\n"); } SSL_set_fd(ssl, new_fd); if (SSL_accept(ssl) == -1) { perror("accept"); ERR_print_errors_fp(stderr); close(new_fd); break; } printf("Server with %s encryption\n", SSL_get_cipher(ssl)); bzero(buf, MAXBUF + 1); strcpy(buf, "server->client"); len = SSL_write(ssl, buf, strlen(buf)); if (len <= 0) { printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno, strerror(errno)); goto finish; } else { printf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len); } bzero(buf, MAXBUF + 1); len = SSL_read(ssl, buf, MAXBUF); if (len > 0) { printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len); } else { printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno)); } finish: SSL_shutdown(ssl); SSL_free(ssl); close(new_fd); } close(sockfd); SSL_CTX_free(ctx); return 0; }
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
2021-06-14 tcp ip 三次握手时数据结构-