继续分析SSL协议,上次写的 ClientHello 过程只是整个 SSL 协议握手过程的一部分而已。
下面是我画的一个简单的客户端握手过程,协议状态机简图:
根据上篇文章,可以知道客户端握手过程调用的函数是 SSL_connect 。在SSL_connect 函数内部调用中,
实现了客户端的协议状态机。协议状态机的上层代码在:..\ssl\statem\statem.c 文件中的 state_machine 函数
中实现。下面是 state_machine 函数的部分代码,这部分代码就是上面图示的状态机。根据代码和状态机简图,
应该很容易就明白这个客户端握手协议状态机的原理了吧。
因为 write_state_machine() 是SSL_connect 的第一个状态,一步一个脚印,依次分析。
我又画了一个丑陋的 “写状态机” 简图: 帮助你大致了解这个子状态机是怎么运转的。
So,现在分析 “写状态机” 是怎么实现的。
进入“写状态机”前,要进行一些简单的 状态标志 初始化。有: state , write_state , hand_state ,
request_state 。初始化的代码为:
//在 statem.c 的 state_machine 函数中初始化
st->hand_state = TLS_ST_BEFORE;
st->request_state = TLS_ST_BEFORE;
st->state = MSG_FLOW_WRITING;
//在 statem.c 的 init_write_state_machine 函数中初始化
st->write_state = WRITE_START_TRANSITION;
//*****************************************************************************
OK,正式进入 write_state_machine() ,分析“写状态机”的代码。
从下图可以看到,根据当前执行的是客户端代码还是服务端代码,对一些关键 函数指针 初始化。
因为本文是基于客户端分析HandShake,所以我暂时只分析客户端的HandShake函数。
经过上面的 函数指针 初始化,开始新的 “写状态机” 循环,如下图所示。
根据 write_state ,执行不同基本块。根据前面的初始化,我们知道先执行的是
WRITE_STATE_TRANSITION 基本块。
先判断是否注册了回调函数,有则调用回调函数;接着调用 transition() 指针函数。
根据前面初始化 transition 函数指针的代码,分析 ossl_statem_client_write_transition()
函数。
//*****************************************************************************
根据官方注释可以知道,这个函数判断握手状态应该迁移到哪个状态。函数根据 hand_state
执行不同基本块。前面 hand_state 被赋值为 TLS_ST_BEFORE 。让我们看看这个基本块做了啥。
就是修改了 hand_state ,并返回 WRITE_TRAN_CONTINUE 。 Good , 现在我们重返分析调用
指针函数 transition() 处后面的 switch 代码。根据指针函数的返回值,执行:
case WRITE_TRAN_CONTINUE:
st->write_state = WRITE_STATE_PRE_WORK;
st->write_state_work = WORK_MORE_A;
break;
//*****************************************************************************
OK,返回到 while 循环层的 switch 语句,执行 WRITE_STATE_PRE_WORK 基本块。
调用 pre_wrok() 指针函数,并根据返回值做别的操作……分析 pre_work() 函数。
官方注释提示说这是客户端发送服务端消息前的一些必要的预处理。根据 hand_state 执行。
前面 hand_state 被赋值为 TLS_ST_CW_CLNT_HELLO 。
哇,这个基本块也太简单了,就不说是干啥的了。不过要注意了,我分析的是TLS,不说DTLS,
所以赋值完成后就直接返回 WORK_FINISHED_CONTINUE 。返回到 write_state_machine() 继续分析:
修改 write_state 为 WRITE_STATE_SEND ,接着调用 get_construct_message_f() 指针函数,
即 ossl_statem_client_construct_message() 函数。
//*****************************************************************************
官方says:获取 消息构造函数 以及 消息的类型。 我已经高亮了根据前面的分析,应该
执行的代码块。OK返回到 write_state_machine() 继续分析:
//*****************************************************************************
调用 confunc() 函数指针,即 tls_construct_client_hello() 函数。
这个函数略长,我就不把整个函数截图放出来了,如下所示是一些关键的基本块,根据注释就知道在这里写入
会话ID,客户端支持的密码套件,压缩方式,以及TLS扩展信息。 正如我上篇文章中写的
如出一辙。
执行完 ssl_close_construct_packet() 后,就返回到循环语句,执行 switch(st->write_state) ,
根据最新的 write_state 标志,我们执行:
调用 statem_do_write() 函数,即 发送预构造的消息给伙伴(服务器)。
因为 hand_state 的值为 TLS_ST_CW_CLNT_SEND ,所以调用 ssl_do_write() 函数。
这个函数最后到底调用哪个函数是根据客户端初始的 TLS版本决定的 ,暂时就不分析了。
回到上面,分析 statem_do_write() 函数返回后执行的代码,修改了 write_state 和 write_state_work。
进入 WRITE_STATE_POST_WORK 基本块,调用 post_work() 指针函数。
//*****************************************************************************
post_work() 即 ossl_statem_client_post_work() 函数:
执行一些发送消息的善后函数。这里我就不展开说明了。函数返回 WORK_FINISHED_CONTINUE。
write_state 又回到了 WRITE_STATE_TRANSITION 状态?这是个死循环?不,因为 hand_state
不同了,所以 transition() 指针函数 将返回 WRITE_TRAN_FINISHED ,结束 write_state_machine()
函数的死循环,回到 state_machine() 函数!
因为返回值是 SUB_STATE_FINISHED ,所以将调用 init_read_state_machine() 函数,
并修改 state 为 MSG_FLOW_READING 进入“读取状态机”。
好了,以上就是 握手状态机 的 “写状态机”的分析了。写了好几个小时……不过之前也分析了好久,
诶,还好,虽然慢,但至少我还是往前走的吧!