OPENSSL源码阅读(4)

非复用状态下的发送Change_Cipher_Spec

Posted by 大狗 on September 10, 2020

OPENSSL源码阅读(4)

前言

从这里开始,最好对于TLS1.3的密钥衍生流程比较熟悉,很多名词都是密钥衍生流程图里的,至少得清楚一些名词的含义。 源码阅读(3)里,我们说到了自动机处理完了CLIENTHELLO,发送了SERVERHELLO报文,这次我们来聊聊接下来的流程,我们首先假设这次没有接受客户端的PSK,走一次完整的ECDHE握手,处于方便考虑,我们假设没有双向认证,也不吧NST报文扯进来。下面先把TLS1.3完整握手流程图列出来。

			ClientHello
          + key_share               -------->
                                                          ServerHello
                                                          + key_share
												   {ChangeCipherSpec}
                                                {EncryptedExtensions}
                                                       {Certificate*}
                                                 {CertificateVerify*}
                                                           {Finished}
                                    <--------     [Application Data*]
          {Finished}                -------->
          [Application Data]        <------->      [Application Data]

从发送完SERVERHELLO说起

上次我们发送完SERVERHELLO,又将写自动机的状态置为初始状态st->write_state = WRITE_STATE_TRANSITION;,此时会继续回到写自动机的while循环里。看上面的自动机,接下来服务器需要发送ChangeCipherSpec,EncryptedExtensions,Certificate,CertificateVerify和Finished消息,之所以发送ChangeCipherSpec是因为我们这里考虑中间机器兼容性的问题。这里的ChangeCipherSpec发送阶段是个非常重要的阶段,我们可以知道EncryptedExtensions需要由服务端写密钥(server_write_key)加密保护,那么如何从握手密钥(handshake secret)衍生出来握手密钥便是主要的问题。

写自动机状态转换流程

写自动机继续在while循环里转,大状态还是写初始状态,st->write_state = WRITE_STATE_TRANSITION;首先还是进入transition(s)函数。

	 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:

transition函数最终包裹的是ossl_statem_server13_write_transition(SSL *s)函数,因为刚才的握手状态是发送SERVERHELLO,即st->hand_state == TLS_ST_SW_SRVR_HELLO,因为我们默认是考虑中间机器兼容性SSL_OP_ENABLE_MIDDLEBOX_COMPAT,所以握手状态改为发送Change_Cipher_Spec阶段,即st->hand_state = TLS_ST_SW_CHANGE;。之后返回WRITE_TRAN_CONTINUEtransition阶段结束,进入到pre_work函数。

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

    /*
     * No case for TLS_ST_BEFORE, because at that stage we have not negotiated
     * TLSv1.3 yet, so that is handled by ossl_statem_server_write_transition()
     */

    switch (st->hand_state) {
...

    case TLS_ST_SW_SRVR_HELLO:
        if ((s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0
                && s->hello_retry_request != SSL_HRR_COMPLETE)
            st->hand_state = TLS_ST_SW_CHANGE;
        else if (s->hello_retry_request == SSL_HRR_PENDING)
            st->hand_state = TLS_ST_EARLY_DATA;
        else
            st->hand_state = TLS_ST_SW_ENCRYPTED_EXTENSIONS;
        return WRITE_TRAN_CONTINUE;

pre_work阶段和构建CHANGE_CIPHER_SPEC消息

进入pre_work函数,实际上没做啥事情,可以看到直接返回WORK_FINISHED_CONTINUE了。构建CHANGE_CIPHER_SPEC函数实际上也非常简单,就是写了一个字节的CHANGE_CIPHER_SPEC数据,不多赘述了。返回之后写状态改为写发送 st->write_state = WRITE_STATE_SEND;

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

    switch (st->hand_state) {
    default:
···

    case TLS_ST_SW_CHANGE:
        if (SSL_IS_TLS13(s))
            break;
···
        }
        return WORK_FINISHED_CONTINUE;

写发送阶段

也没啥好说的,毕竟CHANGE_CIPHER_SPEC报文太简单了。

写发送后阶段

写发送后的状态转换

写发送后的阶段就得好好说说了,调用函数post_work包裹ossl_statem_server_post_work函数,此时握手状态为CHANG_CIPHER_SPEC阶段,且没发送hello_retry_request,所以会调用s->method->ssl3_enc->setup_key_block(s)s->method->ssl3_enc->change_cipher_state函数,运算结束后返回WORK_FINISHED_CONTINUE,写自动机又重置为写初始化状态st->write_state = WRITE_STATE_TRANSITION;,写自动机等待发送EncryptedExtension拓展。继续说状态之前我们看看ssl3_enc这个通用的函数,对于TLS1.3而言,这个函数是TLSv1_3_enc_data函数,定义也贴在了下面,我们接下来看看TLSv1_3_enc_data函数的tls13_change_cipher_state到底干了些什么,计算了哪些数据。

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

    s->init_num = 0;

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

    case TLS_ST_SW_CHANGE:
        if (s->hello_retry_request == SSL_HRR_PENDING) {
            if (!statem_flush(s))
                return WORK_MORE_A;
            break;
        }

        if (SSL_IS_TLS13(s)) {
            if (!s->method->ssl3_enc->setup_key_block(s)
                || !s->method->ssl3_enc->change_cipher_state(s,
                        SSL3_CC_HANDSHAKE | SSL3_CHANGE_CIPHER_SERVER_WRITE)) {
                /* SSLfatal() already called */
                return WORK_ERROR;
            }

            if (s->ext.early_data != SSL_EARLY_DATA_ACCEPTED
                && !s->method->ssl3_enc->change_cipher_state(s,
                        SSL3_CC_HANDSHAKE |SSL3_CHANGE_CIPHER_SERVER_READ)) {
                /* SSLfatal() already called */
                return WORK_ERROR;
            }
            /*
             * We don't yet know whether the next record we are going to receive
             * is an unencrypted alert, an encrypted alert, or an encrypted
             * handshake message. We temporarily tolerate unencrypted alerts.
             */
            s->statem.enc_read_state = ENC_READ_STATE_ALLOW_PLAIN_ALERTS;
            break;
        }
		
	SSL3_ENC_METHOD const TLSv1_3_enc_data = {
		tls13_enc,
		tls1_mac,
		tls13_setup_key_block,
		tls13_generate_master_secret,
		tls13_change_cipher_state,
		tls13_final_finish_mac,
		TLS_MD_CLIENT_FINISH_CONST, TLS_MD_CLIENT_FINISH_CONST_SIZE,
		TLS_MD_SERVER_FINISH_CONST, TLS_MD_SERVER_FINISH_CONST_SIZE,
		tls13_alert_code,
		tls13_export_keying_material,
		SSL_ENC_FLAG_SIGALGS | SSL_ENC_FLAG_SHA256_PRF,
		ssl3_set_handshake_header,
		tls_close_construct_packet,
		ssl3_handshake_write
};

tls13_change_cipher_state的功能

进入tls13_change_cipher_state函数之前,我们先看看我们知道了哪些东西。解析ClientHello的KeyShare的时候,我们计算出来了握手密钥,保存了从Clienthello到ServerHello的握手报文,此时进入了tls13_change_cipher_state函数,调用tls13_change_cipher_state函数时,可以看到两次调用的时候which分别为SSL3_CC_HANDSHAKE|SSL3_CHANGE_CIPHER_SERVER_WRITESSL3_CC_HANDSHAKE|SSL3_CHANGE_CIPHER_SERVER_READ,而#define SSL3_CHANGE_CIPHER_SERVER_READ (SSL3_CC_SERVER|SSL3_CC_READ)#define SSL3_CHANGE_CIPHER_SERVER_WRITE (SSL3_CC_SERVER|SSL3_CC_WRITE)。所以这两次运算实际上计算的就是服务端写key,服务端写iv,服务端读key和服务端读iv。我们拆开看看如何体现在代码上:

	int tls13_change_cipher_state(SSL *s, int which)
{
...
    static const unsigned char client_early_traffic[] = "c e traffic";
    static const unsigned char client_handshake_traffic[] = "c hs traffic";
    static const unsigned char client_application_traffic[] = "c ap traffic";
    static const unsigned char server_handshake_traffic[] = "s hs traffic";
    static const unsigned char server_application_traffic[] = "s ap traffic";
    static const unsigned char exporter_master_secret[] = "exp master";
    static const unsigned char resumption_master_secret[] = "res master";
    static const unsigned char early_exporter_master_secret[] = "e exp master";

    unsigned char *iv;
    unsigned char secret[EVP_MAX_MD_SIZE];
    unsigned char hashval[EVP_MAX_MD_SIZE];
    unsigned char *hash = hashval;
    unsigned char *insecret;
    unsigned char *finsecret = NULL;
    const char *log_label = NULL;
    EVP_CIPHER_CTX *ciph_ctx;
    size_t finsecretlen = 0;
    const unsigned char *label;
    size_t labellen, hashlen = 0;
    int ret = 0;
    const EVP_MD *md = NULL;
    const EVP_CIPHER *cipher = NULL;

    if (which & SSL3_CC_READ) {
        if (s->enc_read_ctx != NULL) {
            EVP_CIPHER_CTX_reset(s->enc_read_ctx);
        } else {
            s->enc_read_ctx = EVP_CIPHER_CTX_new();
            if (s->enc_read_ctx == NULL) {
                SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                         SSL_F_TLS13_CHANGE_CIPHER_STATE, ERR_R_MALLOC_FAILURE);
                goto err;
            }
        }
        ciph_ctx = s->enc_read_ctx;
        iv = s->read_iv;

        RECORD_LAYER_reset_read_sequence(&s->rlayer);
    } else {
       ...
    }

    if (((which & SSL3_CC_CLIENT) && (which & SSL3_CC_WRITE))
            || ((which & SSL3_CC_SERVER) && (which & SSL3_CC_READ))) {
			if (which & SSL3_CC_EARLY) {
            ...
        } else if (which & SSL3_CC_HANDSHAKE) {
            insecret = s->handshake_secret;
            finsecret = s->client_finished_secret;
            finsecretlen = EVP_MD_size(ssl_handshake_md(s));
            label = client_handshake_traffic;
            labellen = sizeof(client_handshake_traffic) - 1;
            log_label = CLIENT_HANDSHAKE_LABEL;
            /*
             * The handshake hash used for the server read/client write handshake
             * traffic secret is the same as the hash for the server
             * write/client read handshake traffic secret. However, if we
             * processed early data then we delay changing the server
             * read/client write cipher state until later, and the handshake
             * hashes have moved on. Therefore we use the value saved earlier
             * when we did the server write/client read change cipher state.
             */
            hash = s->handshake_traffic_hash;
        } else {
            insecret = s->master_secret;
            label = client_application_traffic;
            labellen = sizeof(client_application_traffic) - 1;
            log_label = CLIENT_APPLICATION_LABEL;
            /*
             * For this we only use the handshake hashes up until the server
             * Finished hash. We do not include the client's Finished, which is
             * what ssl_handshake_hash() would give us. Instead we use the
             * previously saved value.
             */
            hash = s->server_finished_hash;
        }
    } else {
        /* Early data never applies to client-read/server-write */
        if (which & SSL3_CC_HANDSHAKE) {
            insecret = s->handshake_secret;
            finsecret = s->server_finished_secret;
            finsecretlen = EVP_MD_size(ssl_handshake_md(s));
            label = server_handshake_traffic;
            labellen = sizeof(server_handshake_traffic) - 1;
            log_label = SERVER_HANDSHAKE_LABEL;
        } else {
            insecret = s->master_secret;
            label = server_application_traffic;
            labellen = sizeof(server_application_traffic) - 1;
            log_label = SERVER_APPLICATION_LABEL;
        }
    }

    if (!(which & SSL3_CC_EARLY)) {
        md = ssl_handshake_md(s);
        cipher = s->s3.tmp.new_sym_enc;
        if (!ssl3_digest_cached_records(s, 1)
                || !ssl_handshake_hash(s, hashval, sizeof(hashval), &hashlen)) {
            /* SSLfatal() already called */;
            goto err;
        }
    }

    /*
     * Save the hash of handshakes up to now for use when we calculate the
     * client application traffic secret
     */
    if (label == server_application_traffic)
        memcpy(s->server_finished_hash, hashval, hashlen);

    if (label == server_handshake_traffic)
        memcpy(s->handshake_traffic_hash, hashval, hashlen);

    if (label == client_application_traffic) {
        /*
         * We also create the resumption master secret, but this time use the
         * hash for the whole handshake including the Client Finished
         */
        if (!tls13_hkdf_expand(s, ssl_handshake_md(s), insecret,
                               resumption_master_secret,
                               sizeof(resumption_master_secret) - 1,
                               hashval, hashlen, s->resumption_master_secret,
                               hashlen, 1)) {
            /* SSLfatal() already called */
            goto err;
        }
    }

    if (!derive_secret_key_and_iv(s, which & SSL3_CC_WRITE, md, cipher,
                                  insecret, hash, label, labellen, secret, iv,
                                  ciph_ctx)) {
        /* SSLfatal() already called */
        goto err;
    }

    if (label == server_application_traffic) {
        memcpy(s->server_app_traffic_secret, secret, hashlen);
        /* Now we create the exporter master secret */
        if (!tls13_hkdf_expand(s, ssl_handshake_md(s), insecret,
                               exporter_master_secret,
                               sizeof(exporter_master_secret) - 1,
                               hash, hashlen, s->exporter_master_secret,
                               hashlen, 1)) {
            /* SSLfatal() already called */
            goto err;
        }

        if (!ssl_log_secret(s, EXPORTER_SECRET_LABEL, s->exporter_master_secret,
                            hashlen)) {
            /* SSLfatal() already called */
            goto err;
        }
    } else if (label == client_application_traffic)
        memcpy(s->client_app_traffic_secret, secret, hashlen);

    if (!ssl_log_secret(s, log_label, secret, hashlen)) {
        /* SSLfatal() already called */
        goto err;
    }

    if (finsecret != NULL
            && !tls13_derive_finishedkey(s, ssl_handshake_md(s), secret,
                                         finsecret, finsecretlen)) {
        /* SSLfatal() already called */
        goto err;
    }

    if (!s->server && label == client_early_traffic)
        s->statem.enc_write_state = ENC_WRITE_STATE_WRITE_PLAIN_ALERTS;
    else
        s->statem.enc_write_state = ENC_WRITE_STATE_VALID;
    ret = 1;
 err:
    if ((which & SSL3_CC_EARLY) != 0) {
        /* We up-refed this so now we need to down ref */
        ssl_evp_cipher_free(cipher);
    }
    OPENSSL_cleanse(secret, sizeof(secret));
    return ret;
}

第一次计算服务端写KEY & IV

第一次计算时,which为SSL3_CC_HANDSHAKE|SSL3_CHANGE_CIPHER_SERVER_WRITE,所以函数里一开始的几个IF根本就不会进入。 函数首先判断有没有读flagif (which & SSL3_CC_READ),不存在所以不会进入,后面判断是否为客户端写((which & SSL3_CC_CLIENT) && (which & SSL3_CC_WRITE)和服务端读((which & SSL3_CC_CLIENT) && (which & SSL3_CC_WRITE),也不进入,转而进入else分支,首先需要计算服务端写密钥(TLS1.3密钥衍生流程的server_handshake_traffic_secret)。执行的代码是:

	if (which & SSL3_CC_HANDSHAKE) {
            insecret = s->handshake_secret;
            finsecret = s->server_finished_secret;
            finsecretlen = EVP_MD_size(ssl_handshake_md(s));
            label = server_handshake_traffic;
            labellen = sizeof(server_handshake_traffic) - 1;
            log_label = SERVER_HANDSHAKE_LABEL;

确定好计算的对象,因为不需要接受EARLY_DATA,所以直接计算握手消息(ClientHello+ServerHello)的transcript hash,代码为:

	if (!(which & SSL3_CC_EARLY)) {
        md = ssl_handshake_md(s);
        cipher = s->s3.tmp.new_sym_enc;
        if (!ssl3_digest_cached_records(s, 1)
                || !ssl_handshake_hash(s, hashval, sizeof(hashval), &hashlen)) {
            /* SSLfatal() already called */;
            goto err;
        }
    }

接下来,就是利用hash消息,做一次derive secret得到服务端握手secret(TLS1.3密钥衍生的”server_handshake_traffic_secret”),将之保存到secret变量里,除了下面计算finished key要用,其他地方都不用,所以secret变量是个局部变量。derive_secret函数内部会调用write_key = HKDF-Expand-Label(Secret, "key", "", key_length)write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)产生加密时的握手key和iv,并保存到CTX中, 代码如下(记录密钥那部分就不写了):

	if (label == server_handshake_traffic)
        memcpy(s->handshake_traffic_hash, hashval, hashlen);
	
	if (!derive_secret_key_and_iv(s, which & SSL3_CC_WRITE, md, cipher,
                                  insecret, hash, label, labellen, secret, iv,
                                  ciph_ctx)) {
        /* SSLfatal() already called */
        goto err;
    }

最终,利用服务端写密钥(TLS1.3密钥衍生的”server_handshake_traffic_secret”),计算服务端的finishdkey,到此第一次tls13_change_cipher_state调用结束。计算finished_key时利用的规则和代码为:

	server_finished_key = HKDF-Expand-Label(server_handshake_traffic_secret, "finished", "", Hash.length)
	   
	if (finsecret != NULL
            && !tls13_derive_finishedkey(s, ssl_handshake_md(s), secret,
                                         finsecret, finsecretlen)) {
        /* SSLfatal() already called */
        goto err;
    }

第二次计算服务端读KEY & IV

第一次计算时,which为SSL3_CC_HANDSHAKE|SSL3_CHANGE_CIPHER_SERVER_READ。 函数首先判断有没有读flagif (which & SSL3_CC_READ),初始化读ctx和读iv。之后判断是否为客户端写((which & SSL3_CC_CLIENT) && (which & SSL3_CC_WRITE)和服务端读((which & SSL3_CC_CLIENT) && (which & SSL3_CC_WRITE),进入该分支,计算服务端读密钥也就是客户端写密钥(TLS1.3密钥衍生流程的client_handshake_traffic_secret):

	insecret = s->handshake_secret;
    finsecret = s->client_finished_secret;
    finsecretlen = EVP_MD_size(ssl_handshake_md(s));
    label = client_handshake_traffic;
    labellen = sizeof(client_handshake_traffic) - 1;
    log_label = CLIENT_HANDSHAKE_LABEL;

确定好计算的对象,因为不需要接受EARLY_DATA,所以直接计算握手消息(ClientHello+ServerHello)的transcript hash,代码为:

	if (!(which & SSL3_CC_EARLY)) {
        md = ssl_handshake_md(s);
        cipher = s->s3.tmp.new_sym_enc;
        if (!ssl3_digest_cached_records(s, 1)
                || !ssl_handshake_hash(s, hashval, sizeof(hashval), &hashlen)) {
            /* SSLfatal() already called */;
            goto err;
        }
    }

接下来,就是计算服务端读key和服务端读iv的流程,因为已经计算过hash了,所以直接利用s->handshake_traffic_hash即可。对s->handshake_secret做一次derive secret得到客户端握手secret(TLS1.3密钥衍生的”client_handshake_traffic_secret”),将之保存到secret变量里,除了下面计算finished key要用,其他地方都不用,所以secret变量是个局部变量。derive_secret函数内部会调用write_key = HKDF-Expand-Label(Secret, "key", "", key_length)write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)产生加密时的握手key和iv,并保存到读CTX中, 代码如下(记录密钥那部分就不写了):

	if (!derive_secret_key_and_iv(s, which & SSL3_CC_WRITE, md, cipher,
                                  insecret, hash, label, labellen, secret, iv,
                                  ciph_ctx)) {
        /* SSLfatal() already called */
        goto err;
    }

最终,利用服务端读密钥(TLS1.3密钥衍生的”client_handshake_traffic_secret”),计算客户端的finishdkey,到此第二次tls13_change_cipher_state调用结束。计算finished_key时利用的规则和代码为:

	client_finished_key = HKDF-Expand-Label(client_handshake_traffic_secret, "finished", "", Hash.length)
	   
	if (finsecret != NULL
            && !tls13_derive_finishedkey(s, ssl_handshake_md(s), secret,
                                         finsecret, finsecretlen)) {
        /* SSLfatal() already called */
        goto err;
    }

结束

我个人很反感这段代码,工业级代码应该简单才对,为了毕竟tls1.3之前就个tls1.2,通用性没那么夸张。现在这样子看的太费劲了。唉,有点不想写了,好累啊,没人看,估计也没人对OPENSSL的自动机感兴趣。

结尾的闲言碎语

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