一、前言
在 有穷自动机 (或叫状态机) 一文中,简单介绍了自动机的原理,这是人们在面对复杂问题时为了建立数学模型而对问题进行抽象的描述。那么回归到现实问题时,这种抽象的描述又应该怎么落地生根呢?本文借助分析 Openssl 库的握手过程来探讨状态机是如何在程序中发生作用的,因此本文的重点分析状态机的工作过程,对于Openssl库中握手过程的细节不做过多深入。
二、SSL连接流程图
注:上述图片中橙色是普通 Socket 操作,其余为 SSL 操作,此处为单向认证的流程,双向认证是在此基础上 client 添加证书和密钥校验等过程,详见 参考文献[2]
三、涉及的源码
- 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步骤 ,以此进入消息循环 读状态机相同
笔者本意是想使用状态机的转移图或者其他的方式来清晰说明握手协议过程中状态机的工作原理,但分析下来后发觉这部分错综复杂,难以用简单的图示说明,所以只能采用文本简述的方式。后续或可随着对这部分的理解加深之后,重构本篇。
参考
[ 1 ] OpenSSL之SSL用法
[ 2 ] 基于openssl的单向和双向认证的深入分析
[ 3 ] Openssl状态机的实现