OPENSSL源码阅读(3)

Clienthello读取和Serverhello发送

Posted by 大狗 on September 7, 2020

OPENSSL源码阅读(3)

前言

今天来分析分析自动机,自动机这东西基本只要是服务器都得用,上学那会看别人写的http自动机,现在工作了自己写tls自动机,如果对自动机有疑问可以看看字符串匹配的几个算法。本质上没啥变化。但是在TLS1.3中有个很大的问题:传统的自动机驱动是由数据驱动的,在TLS1.3当中由于存在early_data存在了一个early_data驱动和缓存的问题;对于客户端而言如何自发的从early_data的发送阶段切换到发送end_of_early_data阶段;对于服务端而言,early_data是缓存还是直接发送出去?我不会讲我司的解决方法(虽然这几个都是我设计的),我们只看看openssl的做法。

state_machine的解析

自动机的状态初始化:

openssl当中,state_machine包含两个部分,分别是读自动机(read_state_machine)和写自动机(write_state_machine),读自动机收取报文,写自动机负责发送报文,自动机就在读和写里面两个蹦来蹦去。openssl在进入自动机之前先把自动机状态,握手状态和early_data状态都初始化。然后才进入了state_machine中。下面的代码时初始化的代码,改了statem的状态。

	void SSL_set_accept_state(SSL *s)
	{
		s->server = 1;
		s->shutdown = 0;
		ossl_statem_clear(s);
		s->handshake_func = s->method->ssl_accept;
		clear_ciphers(s);
	}
	
	void ossl_statem_clear(SSL *s)
	{
		s->statem.state = MSG_FLOW_UNINITED;
		s->statem.hand_state = TLS_ST_BEFORE;
		s->statem.in_init = 1;
		s->statem.no_cert_verify = 0;
	}

自动机的大状态切换

下面我们看state_machine的代码。首先初始化自动机状态,SSL结构体里面的OSSL_STATEM即对应于自动机,OSSL_STATEM中存有各种自动机状态(这个东西等会再说)。OSSL_STATEM的state用来标记大的状态而hand_state用来表示握手状态。两块初始完成之后要对s->init_buf初始化。实际上这块buf初始化了就用来存储客户端的clienthello信息了。s->init_num存储读取的字节数。初始化完成之后就可以进行自动机的运转了。这里注意大自动机已经被初始化为写状态MSG_FLOW_WRITING,为什么是写状态?因为state_machine通用客户端和服务端,客户端需要先写,如果启动的是服务器,那么写状态会在写自动机返回以后直接切换成读,所以不影响。大自动机实际上就是写和读,即 MSG_FLOW_WRITING <—> MSG_FLOW_READING。

	static int state_machine(SSL *s, int server)
{
    BUF_MEM *buf = NULL;
    void (*cb) (const SSL *ssl, int type, int val) = NULL;
    OSSL_STATEM *st = &s->statem;
    int ret = -1;
    int ssret;
...
    /* Initialise state machine */
    if (st->state == MSG_FLOW_UNINITED
            || st->state == MSG_FLOW_FINISHED) {
        if (st->state == MSG_FLOW_UNINITED) {
            st->hand_state = TLS_ST_BEFORE;
            st->request_state = TLS_ST_BEFORE;
        }

        s->server = server;
...
        if (s->init_buf == NULL) {
            if ((buf = BUF_MEM_new()) == NULL) {
                SSLfatal(s, SSL_AD_NO_ALERT, SSL_F_STATE_MACHINE,
                         ERR_R_INTERNAL_ERROR);
                goto end;
            }
            if (!BUF_MEM_grow(buf, SSL3_RT_MAX_PLAIN_LENGTH)) {
                SSLfatal(s, SSL_AD_NO_ALERT, SSL_F_STATE_MACHINE,
                         ERR_R_INTERNAL_ERROR);
                goto end;
            }
            s->init_buf = buf;
            buf = NULL;
        }

        s->init_num = 0;

        /*
         * Should have been reset by tls_process_finished, too.
         */
        s->s3.change_cipher_spec = 0;
...
		st->state = MSG_FLOW_WRITING;
        init_write_state_machine(s);
...
    while (st->state != MSG_FLOW_FINISHED) {
        if (st->state == MSG_FLOW_READING) {
            ssret = read_state_machine(s);
            if (ssret == SUB_STATE_FINISHED) {
                st->state = MSG_FLOW_WRITING;
                init_write_state_machine(s);
            } else {
                /* NBIO or error */
                goto end;
            }
        } else if (st->state == MSG_FLOW_WRITING) {
            ssret = write_state_machine(s);
            if (ssret == SUB_STATE_FINISHED) {
                st->state = MSG_FLOW_READING;
                init_read_state_machine(s);
            } else if (ssret == SUB_STATE_END_HANDSHAKE) {
                st->state = MSG_FLOW_FINISHED;
            } else {
                /* NBIO or error */
                goto end;
            }
        } else {
            /* Error */
            check_fatal(s, SSL_F_STATE_MACHINE);
            SSLerr(SSL_F_STATE_MACHINE, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
            goto end;
        }
    }

    ret = 1;

 end:
    st->in_handshake--;
...
    BUF_MEM_free(buf);
    if (cb != NULL) {
        if (server)
            cb(s, SSL_CB_ACCEPT_EXIT, ret);
        else
            cb(s, SSL_CB_CONNECT_EXIT, ret);
    }
    return ret;
}

读自动机的切换

我们下面接着细看读写自动机的内容,先看读自动机read_state_machine。读自动机就是在“读取消息头部READ_STATE_HEADER”,“读取消息体READ_STATE_BODY”和“读取后操作READ_STATE_POST_PROCESS”之间来回转换。看下面的代码,用于判断状态转变,处理流程和处理后流程的代码就是这个:

		transition = ossl_statem_server_read_transition;
        process_message = ossl_statem_server_process_message;
        max_message_size = ossl_statem_server_max_message_size;
        post_process_message = ossl_statem_server_post_process_message;

有一点值得注意哈,这个时候的报文可不是明文,也就是说,这里是密文的操作。我本身实际上是非常反感这种操作的,因为这里没有明确体现出来分层的理念。但是这种操作的好处是可以很简单的区分出来当前该做的是加密还是解密。我司的代码里面我们明确将自动机不针对读写进行拆分,而是针对record layer和handshake layer进行区分。因此,在写TLS1.3的early data的时候,我面对的主要问题是怎么判断当前消息的加解密。当然这种先读取头部再拆分消息体的做法是非常直接的写法。

读自动机先获取消息的种类,之后调用transition判断状态的转换。比方说收到了clienthello之后就将自动机的握手状态转换为处理clienthello,即st->hand_state = TLS_ST_SR_CLNT_HELLO;,clienthello是明文,可以直接返回了。返回之后就需要读取clienthello消息体tls_get_message_body,读取完成之后再调用process_message来处理客户端的clienthello,代码为tls_process_client_hello(s, pkt)这里进行的实际上是一大堆的保存和校验操作。这些操作完成了,就继续返回读自动机。这里有一点值得注意,处理完客户端消息不一定需要继续操作。如果这个时候需要做操作了,再继续进行post_process。反映在代码上就到了st->read_state_work = post_process_message(s, st->read_state_work);,对于处理完clienthello而言,这时候服务端做的操作就是选择通信的cipher,计算服务端的server key。这四个读自动机用的函数我会在本文后面详细讲讲,想理解清楚这个,需要对TLS1.3的状态变化比较熟悉。

	static SUB_STATE_RETURN read_state_machine(SSL *s)
{
    OSSL_STATEM *st = &s->statem;
    int ret, mt;
    size_t len = 0;
    int (*transition) (SSL *s, int mt);
    PACKET pkt;
    MSG_PROCESS_RETURN(*process_message) (SSL *s, PACKET *pkt);
    WORK_STATE(*post_process_message) (SSL *s, WORK_STATE wst);
    size_t (*max_message_size) (SSL *s);
    void (*cb) (const SSL *ssl, int type, int val) = NULL;

    cb = get_callback(s);

    if (s->server) {
        transition = ossl_statem_server_read_transition;
        process_message = ossl_statem_server_process_message;
        max_message_size = ossl_statem_server_max_message_size;
        post_process_message = ossl_statem_server_post_process_message;
    } else {
...
    }

    if (st->read_state_first_init) {
        s->first_packet = 1;
        st->read_state_first_init = 0;
    }

    while (1) {
        switch (st->read_state) {
        case READ_STATE_HEADER:
...
                ret = tls_get_message_header(s, &mt);
            }

            if (ret == 0) {
                /* Could be non-blocking IO */
                return SUB_STATE_ERROR;
            }

...
            /*
             * Validate that we are allowed to move to the new state and move
             * to that state if so
             */
            if (!transition(s, mt))
                return SUB_STATE_ERROR;

...

            st->read_state = READ_STATE_BODY;
            /* Fall through */

        case READ_STATE_BODY:
            if (!SSL_IS_DTLS(s)) {
                /* We already got this above for DTLS */
                ret = tls_get_message_body(s, &len);
                if (ret == 0) {
                    /* Could be non-blocking IO */
                    return SUB_STATE_ERROR;
                }
            }

            s->first_packet = 0;
            if (!PACKET_buf_init(&pkt, s->init_msg, len)) {
                SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_READ_STATE_MACHINE,
                         ERR_R_INTERNAL_ERROR);
                return SUB_STATE_ERROR;
            }
            ret = process_message(s, &pkt);

            /* Discard the packet data */
            s->init_num = 0;

            switch (ret) {
            case MSG_PROCESS_ERROR:
                check_fatal(s, SSL_F_READ_STATE_MACHINE);
                return SUB_STATE_ERROR;

            case MSG_PROCESS_FINISHED_READING:
                if (SSL_IS_DTLS(s)) {
                    dtls1_stop_timer(s);
                }
                return SUB_STATE_FINISHED;

            case MSG_PROCESS_CONTINUE_PROCESSING:
                st->read_state = READ_STATE_POST_PROCESS;
                st->read_state_work = WORK_MORE_A;
                break;

            default:
                st->read_state = READ_STATE_HEADER;
                break;
            }
            break;

        case READ_STATE_POST_PROCESS:
            st->read_state_work = post_process_message(s, st->read_state_work);
            switch (st->read_state_work) {
            case WORK_ERROR:
                check_fatal(s, SSL_F_READ_STATE_MACHINE);
                /* Fall through */
            case WORK_MORE_A:
            case WORK_MORE_B:
            case WORK_MORE_C:
                return SUB_STATE_ERROR;

            case WORK_FINISHED_CONTINUE:
                st->read_state = READ_STATE_HEADER;
                break;

            case WORK_FINISHED_STOP:
                if (SSL_IS_DTLS(s)) {
                    dtls1_stop_timer(s);
                }
                return SUB_STATE_FINISHED;
            }
            break;

        default:
            /* Shouldn't happen */
            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_READ_STATE_MACHINE,
                     ERR_R_INTERNAL_ERROR);
            return SUB_STATE_ERROR;
        }
    }
}

消息头的处理与tls_get_message_header函数

好了,我们再回头看读取消息报文头部的部分,首先调用tls_get_message_header读取客户端消息。一开始read_state是READ_STATE_HEADER这个不多解析,主要就是调用tls_get_message_header函数,读取报文,并且获取报文的种类。我们知道TLS1.3中,自动机的驱动方式只有三种:

  • 网络协议栈驱动,即收到了新消息需要处理。
  • 计算驱动,即需要先计算出来一些密钥等信息,之后使用这些密钥参与计算。
  • 自发的驱动,比方说发送early_data结束了,需要发送end_of_early_data报文,而end_of_early_data报文的加解密key并不和发送的early_data保持一致。服务端只存在前两种驱动方式。而tls_get_message_header就是网络协议栈驱动,tls_get_message_header将报文的消息种类存储于mt中。
       while (1) {
          switch (st->read_state) {
          case READ_STATE_HEADER:
          ...
                  ret = tls_get_message_header(s, &mt);
              }
    

现在我们来看看这个通用的函数,tls_get_message_header函数内部调用了ssl_read_bytes。握手TLS1.3的时候,这个函数包裹的是ssl3_read_bytes函数,该函数需要能够解密被加密的报文,此外还必须能够处理各种收到的报文,比方说alert报文,close_notify报文,重协商报文等等。该函数会等待报文达到可以独写的数量再返回,ssl3_read_bytes函数我们以后再看,单独写一篇看。好,我们接着看tls_get_message_header函数,读取到足够的函数以后,报文包括record layer的数据被放到s->init_buf->data中,如果报文的种类是change_cipher_spec(change_cipher_spec函数用于表示加密key的变化),就需要判断该change_cipher_spec报文来的对不对,比方说已经读取的个数对不对?读取到的handshake layer和record layer是不是种类都是change_cipher_spec?否则就得报错返回了。

如果没有错误,报文就继续向下进行了。到了n2l3那里,我司写的代码是INT2TOLEN3等方式,我觉得比n2l3看起来好理解不少。接着往下看s->s3.tmp.message_size = l;这里tmp.message_size置为一,后面把client_hello的消息体指给了init_msg,init_num再次赋值为0,再返回就是判断状态是否需要变化了。

	int tls_get_message_header(SSL *s, int *mt)
{
    /* s->init_num < SSL3_HM_HEADER_LENGTH */
    int skip_message, i, recvd_type;
    unsigned char *p;
    size_t l, readbytes;

    p = (unsigned char *)s->init_buf->data;

    do {
        while (s->init_num < SSL3_HM_HEADER_LENGTH) {
            i = s->method->ssl_read_bytes(s, SSL3_RT_HANDSHAKE, &recvd_type,
                                          &p[s->init_num],
                                          SSL3_HM_HEADER_LENGTH - s->init_num,
                                          0, &readbytes);
            if (i <= 0) {
                s->rwstate = SSL_READING;
                return 0;
            }
            if (recvd_type == SSL3_RT_CHANGE_CIPHER_SPEC) {
                /*
                 * A ChangeCipherSpec must be a single byte and may not occur
                 * in the middle of a handshake message.
                 */
                if (s->init_num != 0 || readbytes != 1 || p[0] != SSL3_MT_CCS) {
                    SSLfatal(s, SSL_AD_UNEXPECTED_MESSAGE,
                             SSL_F_TLS_GET_MESSAGE_HEADER,
                             SSL_R_BAD_CHANGE_CIPHER_SPEC);
                    return 0;
                }
                if (s->statem.hand_state == TLS_ST_BEFORE
                        && (s->s3.flags & TLS1_FLAGS_STATELESS) != 0) {
                    /*
                     * We are stateless and we received a CCS. Probably this is
                     * from a client between the first and second ClientHellos.
                     * We should ignore this, but return an error because we do
                     * not return success until we see the second ClientHello
                     * with a valid cookie.
                     */
                    return 0;
                }
                s->s3.tmp.message_type = *mt = SSL3_MT_CHANGE_CIPHER_SPEC;
                s->init_num = readbytes - 1;
                s->init_msg = s->init_buf->data;
                s->s3.tmp.message_size = readbytes;
                return 1;
            } else if (recvd_type != SSL3_RT_HANDSHAKE) {
                SSLfatal(s, SSL_AD_UNEXPECTED_MESSAGE,
                         SSL_F_TLS_GET_MESSAGE_HEADER,
                         SSL_R_CCS_RECEIVED_EARLY);
                return 0;
            }
            s->init_num += readbytes;
        }

        skip_message = 0;
        if (!s->server)
           ... //这个也跳过吧,这个是客户端,我们这里是服务端
    } while (skip_message);
    /* s->init_num == SSL3_HM_HEADER_LENGTH */

    *mt = *p;
    s->s3.tmp.message_type = *(p++);//*(p++)和*p++是一样的,都是先取值然后p后移一位

    if (RECORD_LAYER_is_sslv2_record(&s->rlayer)) {
        ...   //不分析了,不是sslv2的报文
    } else {
        n2l3(p, l);
        /* BUF_MEM_grow takes an 'int' parameter */
        if (l > (INT_MAX - SSL3_HM_HEADER_LENGTH)) {
            SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_GET_MESSAGE_HEADER,
                     SSL_R_EXCESSIVE_MESSAGE_SIZE);
            return 0;
        }
        s->s3.tmp.message_size = l;

        s->init_msg = s->init_buf->data + SSL3_HM_HEADER_LENGTH;
        s->init_num = 0;
    }

    return 1;
}

返回之后再次回到服务端,注意这里只是将clienthello的头部读了进来,并不清楚到底是不是复用,有没有early data。 tansition函数此时会判断需要将握手自动机的握手状态修改为服务端读clientheloo,即st->hand_state = TLS_ST_SR_CLNT_HELLO;。之后就获取消息体,调用相关的处理函数。

消息体的处理与tls_process_client_hello函数

接着看消息体的处理,消息体获取函数tls_get_message_body功能是不断读取解密好的握手消息报文并储存起来,将报文存储起来的目的有两个:

  • 报文存储起来之后进行处理,比方说读取clienthello的random等等
  • 报文存储起来以后,finish的时候要进行mac计算verify,需要对之前所有的握手信息进行transcript-hash。 这次先不讲tls_get_message_body函数了,下回和get_message_header一起,单开一个说。

好,我们继续说,存储好clienthello之后就需要对clienthello进行处理,此时的握手状态是服务端读clienthello(TLS_ST_SR_CLNT_HELLO),所以调用的函数是tls_process_client_hello

	case READ_STATE_BODY:
            if (!SSL_IS_DTLS(s)) {
                /* We already got this above for DTLS */
                ret = tls_get_message_body(s, &len);
                if (ret == 0) {
                    /* Could be non-blocking IO */
                    return SUB_STATE_ERROR;
                }
            }

            s->first_packet = 0;
            if (!PACKET_buf_init(&pkt, s->init_msg, len)) {
                SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_READ_STATE_MACHINE,
                         ERR_R_INTERNAL_ERROR);
                return SUB_STATE_ERROR;
            }
            ret = process_message(s, &pkt);

            /* Discard the packet data */
            s->init_num = 0;

            switch (ret) {
            case MSG_PROCESS_ERROR:
                check_fatal(s, SSL_F_READ_STATE_MACHINE);
                return SUB_STATE_ERROR;

            case MSG_PROCESS_FINISHED_READING:
                if (SSL_IS_DTLS(s)) {
                    dtls1_stop_timer(s);
                }
                return SUB_STATE_FINISHED;

            case MSG_PROCESS_CONTINUE_PROCESSING:
                st->read_state = READ_STATE_POST_PROCESS;
                st->read_state_work = WORK_MORE_A;
                break;

            default:
                st->read_state = READ_STATE_HEADER;
                break;
            }
            break;

tls_process_client_hello负责处理clienthello报文,首先需要判断该clienthello是不是来的是个正确的时间,如果是个错误的重协商报文,那就直接判错。因为代码比较长,我就不贴了,只简单写写做了点什么。之后就需要根据clienthello报文初始化以下信息:

  • 是否是sslv2报文
  • 获取clienthello中的legacy_version,也就是版本号,tls1.3中对于版本号有兼容性方面的考校
  • 读取客户端的random
  • 读取客户端的session_id,需要注意tls1.3中的session_id本质上已经丧失了复用的功能,复用使用的是psk
  • 读取客户端的支持的ciphersuite
  • 调用函数tls_collect_extensions读取并保存客户端的拓展信息,注意这里只是保存还没有分析的流程,也就是说这部分都是原始数据的拓展,即raw_extensions,我们这里重点关注四种类型的拓展,signature_algorithm,supported_group,key_share和pre_share_key拓展。这四种拓展分别用于协商签名算法(证书),支持的曲线,协商使用的临时密钥,复用时的身份。 上述操作完成了,就返回MSG_PROCESS_CONTINUE_PROCESSING让流程继续进行。

消息体读取后的处理

代码在底下,最好自己手头有一份代码可以直接看

        WORK_STATE tls_post_process_client_hello(SSL *s, WORK_STATE wst)
{
    const SSL_CIPHER *cipher;

    if (wst == WORK_MORE_A) {
        int rv = tls_early_post_process_client_hello(s);
        if (rv == 0) {
            /* SSLfatal() was already called */
            goto err;
        }
        if (rv < 0)
            return WORK_MORE_A;
        wst = WORK_MORE_B;
    }
    if (wst == WORK_MORE_B) {
        if (!s->hit || SSL_IS_TLS13(s)) {
            /* Let cert callback update server certificates if required */
            if (!s->hit && s->cert->cert_cb != NULL) {
                int rv = s->cert->cert_cb(s, s->cert->cert_cb_arg);
                if (rv == 0) {
                    SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                             SSL_F_TLS_POST_PROCESS_CLIENT_HELLO,
                             SSL_R_CERT_CB_ERROR);
                    goto err;
                }
                if (rv < 0) {
                    s->rwstate = SSL_X509_LOOKUP;
                    return WORK_MORE_B;
                }
                s->rwstate = SSL_NOTHING;
            }

            /* In TLSv1.3 we selected the ciphersuite before resumption */
            if (!SSL_IS_TLS13(s)) {
                cipher =
                    ssl3_choose_cipher(s, s->peer_ciphers, SSL_get_ciphers(s));

                if (cipher == NULL) {
                    SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE,
                             SSL_F_TLS_POST_PROCESS_CLIENT_HELLO,
                             SSL_R_NO_SHARED_CIPHER);
                    goto err;
                }
                s->s3.tmp.new_cipher = cipher;
            }
            if (!s->hit) {
                if (!tls_choose_sigalg(s, 1)) {
                    /* SSLfatal already called */
                    goto err;
                }
                /* check whether we should disable session resumption */
                if (s->not_resumable_session_cb != NULL)
                    s->session->not_resumable =
                        s->not_resumable_session_cb(s,
                            ((s->s3.tmp.new_cipher->algorithm_mkey
                              & (SSL_kDHE | SSL_kECDHE)) != 0));
                if (s->session->not_resumable)
                    /* do not send a session ticket */
                    s->ext.ticket_expected = 0;
            }
        } else {
            /* Session-id reuse */
            s->s3.tmp.new_cipher = s->session->cipher;
        }

        /*-
         * we now have the following setup.
         * client_random
         * cipher_list          - our preferred list of ciphers
         * ciphers              - the clients preferred list of ciphers
         * compression          - basically ignored right now
         * ssl version is set   - sslv3
         * s->session           - The ssl session has been setup.
         * s->hit               - session reuse flag
         * s->s3.tmp.new_cipher - the new cipher to use.
         */

        /*
         * Call status_request callback if needed. Has to be done after the
         * certificate callbacks etc above.
         */
        if (!tls_handle_status_request(s)) {
            /* SSLfatal() already called */
            goto err;
        }
        /*
         * Call alpn_select callback if needed.  Has to be done after SNI and
         * cipher negotiation (HTTP/2 restricts permitted ciphers). In TLSv1.3
         * we already did this because cipher negotiation happens earlier, and
         * we must handle ALPN before we decide whether to accept early_data.
         */
        if (!SSL_IS_TLS13(s) && !tls_handle_alpn(s)) {
            /* SSLfatal() already called */
            goto err;
        }

        wst = WORK_MORE_C;
    }
#ifndef OPENSSL_NO_SRP
    if (wst == WORK_MORE_C) {
        int ret;
        if ((ret = ssl_check_srp_ext_ClientHello(s)) == 0) {
            /*
             * callback indicates further work to be done
             */
            s->rwstate = SSL_X509_LOOKUP;
            return WORK_MORE_C;
        }
        if (ret < 0) {
            /* SSLfatal() already called */
            goto err;
        }
    }
#endif

    return WORK_FINISHED_STOP;
 err:
    return WORK_ERROR;
}
消息体处理后WORK_MORE_A的部分

消息读取完成了,关键数据也存储好了。就到了消息后处理的流程。我们直接看post_process_client_hello的代码。函数首先要进入tls_early_post_client_hello函数处理,该函数功能很多

  • 内部调用ssl_choose_server_version来选择tls版本,相对应的,如果是客户端调用的函数就是ssl_choose_client_versionssl_choose_server_version函数内部有一个表,叫做tls_version_table,会根据选定的的版本挑选特定的握手函数,比方说tlsv1_3_server_method。
  • 检查tls1.3的clienthello是不是处于一个sslrecord的边界内(这个如果没看懂的话建议去看看rfc原话)
  • 检查cipher是否兼容
  • 检查压缩算法等等
  • 检查clienthello.random是否合法
  • 分析各种拓展信息,首先分析extended_master_secret类型的拓展,之后调用函数tls_parse_all_extensions分析SSL_EXT_CLIENT_HELLO的所有拓展,这个分析的函数实际上也是个回调函数,具体每种拓展的函数体在extensions.c里的static const EXTENSION_DEFINITION ext_defs里,上面说了四个我们需要重点关注的拓展,签名,supported_group,key_share和psk拓展,对应于下面三条,这三条都是在tls_parse_all_extensions中对不同的函数进行调用的。
  • 如果你看了tls1.3的rfc,那你必然知道clienthello里可以包含两种和签名算法有关的拓展,signature_algorithm和signature_algorithm_cert。这两种拓展会分别调用函数tls_parse_ctos_sig_algstls_parse_ctos_sig_algs_cert被保存到s->s3.tmp.peer_sigalgss->s3.tmp.peer_cert_sigalgs中。注意,这里只是保存!
  • 对于supported_groups,调用函数tls_parse_ctos_supported_groups,将客户端支持的曲线保存到s->ext.peer_supportedgroups中。
  • 对于key_share拓展,调用函数tls_parse_ctos_key_share,如果客户端不支持ECDHE的协商,只支持PSK_ONLY,那就什么也不做。反之,遍历客户端的Key_share拓展,如果有客户端不支持的曲线,报错。找到第一个服务端客户端共有的曲线,将该曲线的key_share,也就是客户端的临时公钥保存到s->s3.peer_tmp,曲线id保存到s->s3.group_id。接下来继续遍历Key_share拓展,但是不保存也不分析其他的key_share了,只是检查key_share拓展的有效性。
  • 对于psk拓展,调用函数tls_parse_ctos_psk,先检查支不支持PSK交换方式,不支持报错。反之继续,分析的流程不多说了。在TICKET IDENTITY的设计那里会把这部分补上。
  • 检查psk是否为带外数据建立,server端的random是不是需要特定的填充数据。
  • 调用函数tls1_set_server_sigalgs设定服务端可以使用的签名算法,openssl有一个s->s3.tmp.valid_flags[SSL_PKEY_NUM]数组,这个数组用来标识哪个证书能用,哪个签名算法可以用。步骤为:先置空s->s3.tmp.valid_flags[SSL_PKEY_NUM],如果客户端发了signature_algorithm和signature_algorithm_cert拓展,那么调用tls1_process_sigalgstls1_process_sigalgs中调用tls1_set_shared_sigalgs先将s->shared_sigalgs结构清空,然后调用tls12_shared_sigalgs函数计算出来客户端服务端共用的签名算法再写入到s->shared_sigalgs里(也就是说shared_sigalgs里都是可用的签名算法)。共用的签名算法保持好了,返回tls1_process_sigalgs函数中,对s->shared_sigalgs做for循环分析,看证书是否支持,如果支持就将标记打到s->s3.tmp.valid_flags[idx]上。注意这里还没有决定选择哪一个。如果没找到共存的signature algorithm,那就直接报错终止连接了。
消息体处理后WORK_MORE_B的部分

操作做完之后就需要继续进行WORK_MORE_B的部分了。如果当前不是复用连接,那么需要选择合适的证书和签名的算法。调用的函数是tls_choose_sigalg,实际上就是包裹的find_sig_alg的函数。该函数负责的功能就是签名算法的选择,也就是检查签名算法和证书的匹配性,最终将挑选好的曲线扔到s->s3.tmp.sigalg中。之所以这么检查可以看看ECDSA的签名流程,我同样写了。检查的流程分为三块

  • 直接跳过sha1,sha224,DSA等,这几种直接就不支持结束了。
  • 检查ctx是否支持某具体的签名算法
  • 检查证书是否支持该签名算法。如果签名算法是椭圆曲线,那么检查该签名算法的椭圆曲线和证书的私钥的椭圆曲线是否一致。如果是RSA_PSS类型,那么检查私钥是否足够大做签名。
WORK_MORE_C的部分

这部分没啥好说的,srp拓展等等没啥好说的,跳了。 这里运行结束READ_STATE_MACHINE函数子状态机就运行结束了,返回子状态结束(SUB_STATE_FINISHED)。之后初始化大的自动机状态为写状态st->state = MSG_FLOW_WRITING;,并初始化写自动机状态st->write_state = WRITE_STATE_TRANSITION。下面就进入写自动机的状态了。

写自动机的流程切换

写自动机实际上做的就是计算和发送消息的流程,我们看代码,这里要重点注意那几个通用函数,包括pre_work,post_work等等。他们是构建的主体。

	static SUB_STATE_RETURN write_state_machine(SSL *s)
{
    OSSL_STATEM *st = &s->statem;
    int ret;
    WRITE_TRAN(*transition) (SSL *s);
    WORK_STATE(*pre_work) (SSL *s, WORK_STATE wst);
    WORK_STATE(*post_work) (SSL *s, WORK_STATE wst);
    int (*get_construct_message_f) (SSL *s, WPACKET *pkt,
                                    int (**confunc) (SSL *s, WPACKET *pkt),
                                    int *mt);
    void (*cb) (const SSL *ssl, int type, int val) = NULL;
    int (*confunc) (SSL *s, WPACKET *pkt);
    int mt;
    WPACKET pkt;

    cb = get_callback(s);

    if (s->server) {
        transition = ossl_statem_server_write_transition;
        pre_work = ossl_statem_server_pre_work;
        post_work = ossl_statem_server_post_work;
        get_construct_message_f = ossl_statem_server_construct_message;
    } else {
        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;
    }

    while (1) {
        switch (st->write_state) {
        case WRITE_STATE_TRANSITION:
            if (cb != NULL) {
                /* Notify callback of an impending state change */
                if (s->server)
                    cb(s, SSL_CB_ACCEPT_LOOP, 1);
                else
                    cb(s, SSL_CB_CONNECT_LOOP, 1);
            }
            switch (transition(s)) {
            case WRITE_TRAN_CONTINUE:
                st->write_state = WRITE_STATE_PRE_WORK;
                st->write_state_work = WORK_MORE_A;
                break;

            case WRITE_TRAN_FINISHED:
                return SUB_STATE_FINISHED;
                break;

            case WRITE_TRAN_ERROR:
                check_fatal(s, SSL_F_WRITE_STATE_MACHINE);
                return SUB_STATE_ERROR;
            }
            break;

        case WRITE_STATE_PRE_WORK:
            switch (st->write_state_work = pre_work(s, st->write_state_work)) {
            case WORK_ERROR:
                check_fatal(s, SSL_F_WRITE_STATE_MACHINE);
                /* Fall through */
            case WORK_MORE_A:
            case WORK_MORE_B:
            case WORK_MORE_C:
                return SUB_STATE_ERROR;

            case WORK_FINISHED_CONTINUE:
                st->write_state = WRITE_STATE_SEND;
                break;

            case WORK_FINISHED_STOP:
                return SUB_STATE_END_HANDSHAKE;
            }
            if (!get_construct_message_f(s, &pkt, &confunc, &mt)) {
                /* SSLfatal() already called */
                return SUB_STATE_ERROR;
            }
            if (mt == SSL3_MT_DUMMY) {
                /* Skip construction and sending. This isn't a "real" state */
                st->write_state = WRITE_STATE_POST_WORK;
                st->write_state_work = WORK_MORE_A;
                break;
            }
            if (!WPACKET_init(&pkt, s->init_buf)
                    || !ssl_set_handshake_header(s, &pkt, mt)) {
                WPACKET_cleanup(&pkt);
                SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_WRITE_STATE_MACHINE,
                         ERR_R_INTERNAL_ERROR);
                return SUB_STATE_ERROR;
            }
            if (confunc != NULL && !confunc(s, &pkt)) {
                WPACKET_cleanup(&pkt);
                check_fatal(s, SSL_F_WRITE_STATE_MACHINE);
                return SUB_STATE_ERROR;
            }
            if (!ssl_close_construct_packet(s, &pkt, mt)
                    || !WPACKET_finish(&pkt)) {
                WPACKET_cleanup(&pkt);
                SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_WRITE_STATE_MACHINE,
                         ERR_R_INTERNAL_ERROR);
                return SUB_STATE_ERROR;
            }

            /* Fall through */

        case WRITE_STATE_SEND:
            if (SSL_IS_DTLS(s) && st->use_timer) {
                dtls1_start_timer(s);
            }
            ret = statem_do_write(s);
            if (ret <= 0) {
                return SUB_STATE_ERROR;
            }
            st->write_state = WRITE_STATE_POST_WORK;
            st->write_state_work = WORK_MORE_A;
            /* Fall through */

        case WRITE_STATE_POST_WORK:
            switch (st->write_state_work = post_work(s, st->write_state_work)) {
            case WORK_ERROR:
                check_fatal(s, SSL_F_WRITE_STATE_MACHINE);
                /* Fall through */
            case WORK_MORE_A:
            case WORK_MORE_B:
            case WORK_MORE_C:
                return SUB_STATE_ERROR;

            case WORK_FINISHED_CONTINUE:
                st->write_state = WRITE_STATE_TRANSITION;
                break;

            case WORK_FINISHED_STOP:
                return SUB_STATE_END_HANDSHAKE;
            }
            break;

        default:
            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_WRITE_STATE_MACHINE,
                     ERR_R_INTERNAL_ERROR);
            return SUB_STATE_ERROR;
        }
    }
}

写自动机初始部分流程

写自动机一开始调用transition(s)函数,这里实际上非常简单,对TLS1.3而言就是单纯的把握手状态st->hand_state从”服务端读clienthello状态”(TLS_ST_SR_CLNT_HELLO)变成”服务端写serverhello状态”(TLS_ST_SW_SRVR_HELLO)。然后让代码返回WRITE_TRAN_CONTINUE。返回之后,代码继续向下执行进入PRE_WORK阶段。

	static WRITE_TRAN ossl_statem_server13_write_transition(SSL *s)
{
    OSSL_STATEM *st = &s->statem;



    switch (st->hand_state) {
   ....
    case TLS_ST_SR_CLNT_HELLO:
        st->hand_state = TLS_ST_SW_SRVR_HELLO;
        return WRITE_TRAN_CONTINUE;

写自动机WRITE_STATE_PRE_WORK阶段流程

WRITE_STATE_PRE_WORK阶段,先调用pre_work函数,因为当前是“服务端写serverhello”状态,所以代码实际上啥都没干,直接返回WORK_FINISHED_CONTINUE,将握手状态机子状态修改st->write_state = WRITE_STATE_SEND;。再调用get_construct_message_f函数构建发送的信息。get_construct_message_f做的也是个包工头工作,调用不同的函数来计算服务端参数,构建发送的数据包,这里我们能看到调用的是tls_construct_server_hello函数,这个函数里面得缕一缕。

	WORK_STATE ossl_statem_server_pre_work(SSL *s, WORK_STATE wst)
{
    OSSL_STATEM *st = &s->statem;

    switch (st->hand_state) {
    default:
        /* No pre work to be done */
        break;

    case TLS_ST_SW_SRVR_HELLO:
        if (SSL_IS_DTLS(s)) {
            /*
             * Messages we write from now on should be buffered and
             * retransmitted if necessary, so we need to use the timer now
             */
            st->use_timer = 1;
        }
        break;
...
    return WORK_FINISHED_CONTINUE;
}

	int ossl_statem_server_construct_message(SSL *s, WPACKET *pkt,
                                         confunc_f *confunc, int *mt)
{
    OSSL_STATEM *st = &s->statem;

    switch (st->hand_state) {
    default:
        /* Shouldn't happen */
        SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                 SSL_F_OSSL_STATEM_SERVER_CONSTRUCT_MESSAGE,
                 SSL_R_BAD_HANDSHAKE_STATE);
        return 0;

    case TLS_ST_SW_SRVR_HELLO:
        *confunc = tls_construct_server_hello;
        *mt = SSL3_MT_SERVER_HELLO;
        break;
...

    return 1;
}

tls_construct_server_hello函数

tls_construct_server_hello函数将版本号,random,session_id写好以后,会调用tls_construct_extensions,这个函数层层包裹到每个拓展的的construct函数上去,我们前面说了四种重要拓展。一个一个拿过来看。

  • signature_algorithm和signature_algorithm_cert,这两种拓展服务端不会发给客户端,除非是需要双向认证。
  • supported_group拓展,该拓展调用函数tls_construct_stoc_supported_groups,如果上面我们已经选好了Key_share,那么这里s->s3.group_id就不应当等于零了,此时的操作就是取出我们支持的曲线,
  • psk拓展,这个调用函数tls_construct_stoc_psk,发送选择的identity index即可
  • (重要!)key_share拓展,会调用函数tls_construct_stoc_key_share,我们假设客户端发送了可用的key_share,也保存到了s->s3.peer_tmp中,那么首先,先将选定的group也就是选定的椭圆曲线id写到key_share拓展起始部分。之后调用函数skey = ssl_generate_pkey(s, ckey);生成服务端的临时密钥对skey,将skey按格式写入到key_share拓展里。之后就要调用ssl_derive(s, skey, ckey, 1)生成我们的ECDHE secret和握手密钥,也就是说,服务端(也就是我们)已经利用客户端公钥,服务端私钥生成了ECDHE私钥了,对应于RFC8446的SECTION7.4和SECTION7.1的上部。

我们看看ssl_derive函数,调用的代码为ssl_derive(s, skey, ckey, 1),skey即为私钥,ckey即为公钥,1为gensecret。阅读代码可知,先后先使用私钥和公钥计算产生(EC)DHE secret,并将(EC)DHE secret存储到pms结构里,对应于TLS1.3密钥衍生流程图”(EC)DHE”。接下来进入if(gen_secret)的判断里,gen_secret为1,我们假设没有复用,则s->hit == 0,所以先调用tls13_generate_secret函数将early_secret计算出来并存储到s->early_secret中,s->early_secret对应于TLS1.3密钥衍生流程图”Early Secret”。之后调用tls13_generate_handshake_secret,并将刚才的pms即(EC)DHE secret参与到计算流程,得出handshake secret存储到s->handshake_secret中,s->handshake_secret对应于TLS1.3密钥衍生流程图”Handshake Secret”。这里还有两点需要注意:1这里并没有计算出来服务端写/读密钥2服务端读/写密钥并不直接参与到读和写里,还要在再做一次衍生才能从secret发展到key&iv

	/* Derive secrets for ECDH/DH */
int ssl_derive(SSL *s, EVP_PKEY *privkey, EVP_PKEY *pubkey, int gensecret)
{
    int rv = 0;
    unsigned char *pms = NULL;
    size_t pmslen = 0;
    EVP_PKEY_CTX *pctx;

    if (privkey == NULL || pubkey == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_SSL_DERIVE,
                 ERR_R_INTERNAL_ERROR);
        return 0;
    }

    pctx = EVP_PKEY_CTX_new_from_pkey(s->ctx->libctx, privkey, s->ctx->propq);

    if (EVP_PKEY_derive_init(pctx) <= 0
        || EVP_PKEY_derive_set_peer(pctx, pubkey) <= 0
        || EVP_PKEY_derive(pctx, NULL, &pmslen) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_SSL_DERIVE,
                 ERR_R_INTERNAL_ERROR);
        goto err;
    }

#ifndef OPENSSL_NO_DH
    if (SSL_IS_TLS13(s) &&  EVP_PKEY_id(privkey) == EVP_PKEY_DH)
        EVP_PKEY_CTX_set_dh_pad(pctx, 1);
#endif

    pms = OPENSSL_malloc(pmslen);
    if (pms == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_SSL_DERIVE,
                 ERR_R_MALLOC_FAILURE);
        goto err;
    }

    if (EVP_PKEY_derive(pctx, pms, &pmslen) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_SSL_DERIVE,
                 ERR_R_INTERNAL_ERROR);
        goto err;
    }

    if (gensecret) {
        /* SSLfatal() called as appropriate in the below functions */
        if (SSL_IS_TLS13(s)) {
            /*
             * If we are resuming then we already generated the early secret
             * when we created the ClientHello, so don't recreate it.
             */
            if (!s->hit)
                rv = tls13_generate_secret(s, ssl_handshake_md(s), NULL, NULL,
                                           0,
                                           (unsigned char *)&s->early_secret);
            else
                rv = 1;

            rv = rv && tls13_generate_handshake_secret(s, pms, pmslen);
        } else {
            rv = ssl_generate_master_secret(s, pms, pmslen, 0);
        }
    } else {
        /* Save premaster secret */
        s->s3.tmp.pms = pms;
        s->s3.tmp.pmslen = pmslen;
        pms = NULL;
        rv = 1;
    }

 err:
    OPENSSL_clear_free(pms, pmslen);
    EVP_PKEY_CTX_free(pctx);
    return rv;
}

key schedule process.png

几个拓展构建好了,ECHED secret计算完成了,现在就需要发送了SERVERHELLO了,进入到下一个状态

写自动机发送的流程

写自动机需要发送了,ServerHello还是明文的,要明确告诉客户端选择的key_share,psk是哪个,只能用明文。消息写完了进入post阶段。

	ret = statem_do_write(s);
            if (ret <= 0) {
                return SUB_STATE_ERROR;
            }
            st->write_state = WRITE_STATE_POST_WORK;
            st->write_state_work = WORK_MORE_A;

写自动机发送后的流程

调用函数post_work,实际上包裹的是ossl_statem_server_post_work函数,上面几步我们发送了serverhello到客户端,也计算出来ECDHE secret。此时实际上没啥实际要做的东西了,该函式只是将要发送的数据发出去(我觉得叫做冲flush出去更合适),返回WORK_FINISHED_CONTINUE。此时看写自动机函数就会发现,返回之后会将写状态置为写的初始化状态st->write_state = WRITE_STATE_TRANSITION;,此时再次进入写自动机的循环,下面具体的步骤下次再说。

结语

哇,总算写完了clienthello的分析和serverhello的发送了,好累。

结尾的闲言碎语

写到这里差不多就可以结束了,就不多说了。TLS这块还有啥不明白的直接告诉我就成了 狗头的赞赏码.jpg