一、前言
在 有窮自動(dòng)機(jī) (或叫狀態(tài)機(jī)) 一文中,簡單介紹了自動(dòng)機(jī)的原理,這是人們在面對復(fù)雜問題時(shí)為了建立數(shù)學(xué)模型而對問題進(jìn)行抽象的描述。那么回歸到現(xiàn)實(shí)問題時(shí),這種抽象的描述又應(yīng)該怎么落地生根呢?本文借助分析 Openssl 庫的握手過程來探討狀態(tài)機(jī)是如何在程序中發(fā)生作用的,因此本文的重點(diǎn)分析狀態(tài)機(jī)的工作過程,對于Openssl庫中握手過程的細(xì)節(jié)不做過多深入。
二、SSL連接流程圖

注:上述圖片中橙色是普通 Socket 操作,其余為 SSL 操作,此處為單向認(rèn)證的流程,雙向認(rèn)證是在此基礎(chǔ)上 client 添加證書和密鑰校驗(yàn)等過程,詳見 參考文獻(xiàn)[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 ,包含雙向認(rèn)證的兩種方式。
四、Openssl 庫中狀態(tài)機(jī)簡述
Openssl 庫中主要有兩個(gè)狀態(tài)機(jī):消息流狀態(tài)機(jī)、握手狀態(tài)機(jī)組成。消息流狀態(tài)機(jī) 控制消息的讀取和發(fā)送,包括處理 非阻塞的IO事件、刷寫B(tài)IO、處理意外消息等。同時(shí)他本身被獨(dú)立分解成兩個(gè)獨(dú)立的子狀態(tài)機(jī),用來分別控制讀取和發(fā)送消息。握手狀態(tài)機(jī) 會(huì)跟蹤當(dāng)前的 SSL/TLS 握手狀態(tài)。握手狀態(tài)的轉(zhuǎn)換是隨消息流狀態(tài)機(jī)的事件處理而變化。這些狀態(tài)機(jī)保存著握手需要的一些消息處理函數(shù)和算法函數(shù)用來解析消息和執(zhí)行加解密操作。因此正常情況下,狀態(tài)機(jī)遵循 “接收消息-處理消息-切換狀態(tài)-發(fā)送消息” 這個(gè)流程,一旦消息流狀態(tài)機(jī)不按正常的流程走,就會(huì)導(dǎo)致狀態(tài)機(jī)的異常。
這里先來看一下狀態(tài)機(jī)情況總覽:
* --------------------------------------------- -------------------
* | | | |
* | 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 消息流狀態(tài)機(jī)
消息流狀態(tài)機(jī),共有5個(gè)狀態(tài)用于表明消息的狀態(tài)。摘取 Openssl 庫對此狀態(tài)機(jī)的描述如下:
* 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
可以明顯看到消息流狀態(tài)機(jī)的幾個(gè)狀態(tài)的轉(zhuǎn)移過程,對于幾個(gè)狀態(tài)在此做詳細(xì)釋義:
- MSG_FLOW_UNINITED:握手尚未開始
- MSG_FLOW_ERROR:當(dāng)前連接發(fā)生錯(cuò)誤
- MSG_FLOW_READING:當(dāng)前正讀取消息
- MSG_FLOW_WRITING:當(dāng)前正寫入消息
- MSG_FLOW_FINISHED:握手結(jié)束
4.2 讀狀態(tài)機(jī)
讀狀態(tài)機(jī)是屬于消息流狀態(tài)機(jī)的子狀態(tài)機(jī),這里我們單獨(dú)拿出來分析
* 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.
讀狀態(tài)機(jī)的狀態(tài)釋義如下:
- READ_STATE_HEADER:負(fù)責(zé)讀取消息頭并轉(zhuǎn)換握手狀態(tài)機(jī)的狀態(tài)
- READ_STATE_BODY:讀取消息的其余部分,然后隨后對其進(jìn)行處理
- READ_STATE_POST_PROCESS:是一個(gè)可選的步驟,如果對消息執(zhí)行的某些后處理活動(dòng)可能會(huì)被阻塞,則可能會(huì)發(fā)生該步驟
由上圖中狀態(tài)機(jī)轉(zhuǎn)移圖可知 讀狀態(tài)機(jī) 完整操作之后會(huì)轉(zhuǎn)移到 SUB_STATE_FINISHED 這個(gè)狀態(tài)。這個(gè)狀態(tài)是定義在 statem.c 中的枚舉類,用來表明子狀態(tài)機(jī)的返回值:
typedef enum {
SUB_STATE_ERROR, //發(fā)生錯(cuò)誤
SUB_STATE_FINISHED, //子狀態(tài)完成后,將轉(zhuǎn)到下一個(gè)子狀態(tài)
SUB_STATE_END_HANDSHAKE //子狀態(tài)已完成,握手也已完成
} SUB_STATE_RETURN;
4.3 寫狀態(tài)機(jī)
寫狀態(tài)機(jī)同樣也屬于消息流狀態(tài)機(jī)的子狀態(tài)機(jī),摘錄 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.
寫狀態(tài)機(jī)的狀態(tài)釋義如下:
- WRITE_STATE_TRANSITION:轉(zhuǎn)換握手狀態(tài)機(jī)的狀態(tài)
- WRITE_STATE_PRE_WORK:發(fā)送消息之前執(zhí)行其他的準(zhǔn)備工作
- WRITE_STATE_SEND:發(fā)送消息,并執(zhí)行發(fā)送后要完成的任何工作
- WRITE_STATE_POST_WORK:在消息發(fā)送完成后執(zhí)行其他必要的工作
4.4 握手狀態(tài)機(jī)
握手狀態(tài)機(jī)的狀態(tài)太多,此處不一一說明,因?yàn)槠渲卸x了不同協(xié)議的狀態(tài)比較繁瑣,具體可參見 第三章 ssl.h 中定義
五、狀態(tài)機(jī)工作過程
下面將從總體的角度來分析狀態(tài)機(jī)的狀態(tài)轉(zhuǎn)移,首先說明三個(gè)變量的含義:
- s->statem.state : 消息流狀態(tài)
- s->statem.hand_state : 握手狀態(tài)
- st->write_state:寫狀態(tài)
- st->read_state:讀狀態(tài)
1: 初始化 SSL_CTX_new , 代入?yún)?shù) TLS_client_method 指定使用的協(xié)議,其具體的函數(shù)的定義在 methods.c :IMPLEMENT_tls_meth_func(...)
其中指定了連接函數(shù)是 ossl_statem_connect
2:初始化消息流狀態(tài)機(jī)的狀態(tài) SSL_connect -> SSL_set_connect_state -> ossl_statem_clear
???s->statem.state = MSG_FLOW_UNINITED;???
???s->statem.hand_state = TLS_ST_BEFORE;??? //握手狀態(tài)機(jī)
3:開始連接 SSL_do_handshake -> ossl_statem_connect -> state_machine ->
???s->statem.state = MSG_FLOW_UNINITED;???
???s->statem.hand_state = TLS_ST_BEFORE;??? //握手狀態(tài)機(jī)
???st->request_state = TLS_ST_BEFORE;???
4:SSL_do_handshake 函數(shù)中在判斷一些列條件和初始化一些參數(shù)之后開始切換 消息流狀態(tài)機(jī)的狀態(tài)
???st->state = MSG_FLOW_WRITING;???
???init_write_state_machine(s);???
???st->write_state = WRITE_STATE_TRANSITION;???
5:SSL_do_handshake 函數(shù)中 消息流狀態(tài)機(jī)切換寫狀態(tài)之后,啟動(dòng)寫狀態(tài)機(jī)
???ssret = write_state_machine(s);???
?? ssret 如果狀態(tài)為 SUB_STATE_FINISHED 則消息流狀態(tài)機(jī) 切換讀狀態(tài),
???st->state = MSG_FLOW_READING;???
??? init_read_state_machine(s);???
???st->read_state = READ_STATE_HEADER;???
6:SSL_do_handshake 函數(shù)中 消息流狀態(tài)機(jī)切換讀狀態(tài)之后,啟動(dòng)讀狀態(tài)機(jī)
??? ssret = read_state_machine(s);???
?? ssret 如果狀態(tài)為 SUB_STATE_FINISHED 則消息流狀態(tài)機(jī) 切換寫狀態(tài),
???st->state = MSG_FLOW_WRITING;???
???init_write_state_machine(s);???
???st->write_state = WRITE_STATE_TRANSITION;???
至此 第 5,6 步驟 處于不斷的循環(huán)中 消息流的狀態(tài)切換 啟動(dòng)對應(yīng)的讀寫狀態(tài)機(jī)去收發(fā)消息, 這是 消息流狀態(tài)機(jī)的 工作原理 ,那么握手狀態(tài)機(jī)是怎么工作的呢?
握手狀態(tài)機(jī):
7、消息流狀態(tài)機(jī)和握手狀態(tài)機(jī)產(chǎn)生關(guān)系是在 第5,6步驟:
深入 write_state_machine(s) 來看
write_state_machine -> 分別設(shè)定客戶端和服務(wù)端的一些處理函數(shù),這里以 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 -> 切換握手狀態(tài)機(jī)的狀態(tài) :
???st->hand_state = TLS_ST_CW_CLNT_HELLO;???
transition 返回值為 WRITE_TRAN_CONTINUE: 此時(shí)返回到 write_state_machine 函數(shù)
???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) 此處構(gòu)建客戶端的消息
pre_work 返回 WORK_FINISHED_CONTINUE: 此時(shí)返回到 write_state_machine 函數(shù)
???st->write_state = WRITE_STATE_SEND;???
-> ???st->write_state=WRITE_STATE_SEND -> 切換寫狀態(tài)機(jī)的狀態(tài):
???statem_do_write??? 這個(gè)函數(shù)沒弄清楚
???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 : 此時(shí)返回到 write_state_machine 函數(shù)
???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 -> 切換握手狀態(tài)機(jī)的狀態(tài) :
transition 返回 WRITE_TRAN_FINISHED : 此時(shí)返回到 write_state_machine 函數(shù)
write_state_machine 返回 SUB_STATE_FINISHED 表示寫入狀態(tài)機(jī)完成 ,此時(shí)返回到 上述第5步驟 ,以此進(jìn)入消息循環(huán) 讀狀態(tài)機(jī)相同
筆者本意是想使用狀態(tài)機(jī)的轉(zhuǎn)移圖或者其他的方式來清晰說明握手協(xié)議過程中狀態(tài)機(jī)的工作原理,但分析下來后發(fā)覺這部分錯(cuò)綜復(fù)雜,難以用簡單的圖示說明,所以只能采用文本簡述的方式。后續(xù)或可隨著對這部分的理解加深之后,重構(gòu)本篇。
參考
[ 1 ] OpenSSL之SSL用法
[ 2 ] 基于openssl的單向和雙向認(rèn)證的深入分析
[ 3 ] Openssl狀態(tài)機(jī)的實(shí)現(xiàn)