Openssl库握手过程中的状态机分析

一、前言

有穷自动机 (或叫状态机) 一文中,简单介绍了自动机的原理,这是人们在面对复杂问题时为了建立数学模型而对问题进行抽象的描述。那么回归到现实问题时,这种抽象的描述又应该怎么落地生根呢?本文借助分析 Openssl 库的握手过程来探讨状态机是如何在程序中发生作用的,因此本文的重点分析状态机的工作过程,对于Openssl库中握手过程的细节不做过多深入。

二、SSL连接流程图

单向认证方式

注:上述图片中橙色是普通 Socket 操作,其余为 SSL 操作,此处为单向认证的流程,双向认证是在此基础上 client 添加证书和密钥校验等过程,详见 参考文献[2]

三、涉及的源码

示例代码可见 :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

可以明显看到消息流状态机的几个状态的转移过程,对于几个状态在此做详细释义:

  1. MSG_FLOW_UNINITED:握手尚未开始
  2. MSG_FLOW_ERROR:当前连接发生错误
  3. MSG_FLOW_READING:当前正读取消息
  4. MSG_FLOW_WRITING:当前正写入消息
  5. 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.

读状态机的状态释义如下:

  1. READ_STATE_HEADER:负责读取消息头并转换握手状态机的状态
  2. READ_STATE_BODY:读取消息的其余部分,然后随后对其进行处理
  3. 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.

写状态机的状态释义如下:

  1. WRITE_STATE_TRANSITION:转换握手状态机的状态
  2. WRITE_STATE_PRE_WORK:发送消息之前执行其他的准备工作
  3. WRITE_STATE_SEND:发送消息,并执行发送后要完成的任何工作
  4. 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状态机的实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355