OPENSSL源码阅读(7)

非复用状态下的发送Finished报文

Posted by 大狗 on September 13, 2020

OPENSSL源码阅读(7)

前言

上一回说了非复用状态/完整握手状态下发送CertificateVerify消息时自动机的变化,这次我们来说说完整握手情况下发送Finished时发生了什么

从CertificateVerify发送完毕说起

我们上次讲了,发送CertificateVerify之后post_work啥都没干。这次就要发送Finished消息了,此时状态是服务端写CertificateVerify阶段,即st->hand_state == TLS_ST_SW_CERT_VRFY;。这里有一点值得注意,finished key已经计算完成,

写自动机transition函数流程

此次进入transition函数,当前的握手状态为st->hand_state == TLS_ST_SW_CERT_VRFY;,因此,新的状态是”服务端写Finished”阶段,即st->hand_state = TLS_ST_SW_FINISHED。返回之后,修改写自动机的写状态st->write_state = WRITE_STATE_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_CERT_VRFY:
        st->hand_state = TLS_ST_SW_FINISHED;
        return WRITE_TRAN_CONTINUE;

写自动机pre_work函数流程和Finished构建流程

进入pre_work函数之后实际上啥都没做就返回了,直接返回,继续执行。pre_work函数的代码为:

	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;

...
	}
	return WORK_FINISHED_CONTINUE;

构建Finished消息的流程就比较有趣了,ossl_statem_server_construct_message指定调用的函数tls_construct_finished函数之后。返回,然后调用tls_construct_finished,Finished消息构建完成就执行发送和post_work函数。首先要简单说说服务端的finished如何计算,首先需要从服务端握手秘钥计算出finished_key,计算方法为 finished_key =HKDF-Expand-Label(BaseKey, "finished", "", Hash.length),之后对握手报文做HMAC,计算方法为verify_data =HMAC(finished_key,Transcript-Hash(ClientHello+ServerHello+EncryptedExtension+Certificate+CertificateVerify)),注意我写的Transcript-Hash是对我假设的双方已经发送过的数据做hash。下面看tls_construct_finished函数的代码流程:

  • 首先判断是不是pha,然后做个检查:如果是客户端,算下自己的finished_key。因为对于客户端而言,不像服务端已经将客户端的finished_key算出来,是客户端没算自己的finished_key。
  • 根据角色选择不同的label,这里用的是 s->method->ssl3_enc->server_finished_label,值是”server finished”,长度为15
  • 开始计算finished的mac,代码为s->method->ssl3_enc->final_finish_mac(s,sender, slen,s->s3->tmp.finish_md);final_finish_mac函数实际上是tls13_final_finish_mac。等下我们看tls13_final_finish_mac函数。
  • 将计算出来的finished消息写到数据包中
  • 调用memcpy(s->s3->previous_server_finished, s->s3->tmp.finish_md, finish_md_len);保存此次finished消息,如果计算pha,下次可能会用到。
	int tls_construct_finished(SSL *s, WPACKET *pkt)
{
    size_t finish_md_len;
    const char *sender;
    size_t slen;

    /* This is a real handshake so make sure we clean it up at the end */
    if (!s->server && s->post_handshake_auth != SSL_PHA_REQUESTED)
        s->statem.cleanuphand = 1;

    /*
     * We only change the keys if we didn't already do this when we sent the
     * client certificate
     */
    if (SSL_IS_TLS13(s)
            && !s->server
            && s->s3->tmp.cert_req == 0
            && (!s->method->ssl3_enc->change_cipher_state(s,
                    SSL3_CC_HANDSHAKE | SSL3_CHANGE_CIPHER_CLIENT_WRITE))) {;
        /* SSLfatal() already called */
        return 0;
    }

    if (s->server) {
        sender = s->method->ssl3_enc->server_finished_label;
        slen = s->method->ssl3_enc->server_finished_label_len;
    } else {
        sender = s->method->ssl3_enc->client_finished_label;
        slen = s->method->ssl3_enc->client_finished_label_len;
    }

    finish_md_len = s->method->ssl3_enc->final_finish_mac(s,
                                                          sender, slen,
                                                          s->s3->tmp.finish_md);
    if (finish_md_len == 0) {
        /* SSLfatal() already called */
        return 0;
    }

    s->s3->tmp.finish_md_len = finish_md_len;

    if (!WPACKET_memcpy(pkt, s->s3->tmp.finish_md, finish_md_len)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_FINISHED,
                 ERR_R_INTERNAL_ERROR);
        return 0;
    }

    /*
     * Log the master secret, if logging is enabled. We don't log it for
     * TLSv1.3: there's a different key schedule for that.
     */
    if (!SSL_IS_TLS13(s) && !ssl_log_secret(s, MASTER_SECRET_LABEL,
                                            s->session->master_key,
                                            s->session->master_key_length)) {
        /* SSLfatal() already called */
        return 0;
    }

    /*
     * Copy the finished so we can use it for renegotiation checks
     */
    if (!ossl_assert(finish_md_len <= EVP_MAX_MD_SIZE)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_FINISHED,
                 ERR_R_INTERNAL_ERROR);
        return 0;
    }
    if (!s->server) {
        memcpy(s->s3->previous_client_finished, s->s3->tmp.finish_md,
               finish_md_len);
        s->s3->previous_client_finished_len = finish_md_len;
    } else {
        memcpy(s->s3->previous_server_finished, s->s3->tmp.finish_md,
               finish_md_len);
        s->s3->previous_server_finished_len = finish_md_len;
    }

    return 1;
}
}

最后我们看看tls13_final_finish_mac消息,捋捋这个流程:

  • 首先对finished消息之前所有的消息计算transcript-hash,代码为ssl_handshake_hash(s, hash, sizeof(hash), &hashlen)
  • 因为str是server_finished_label,所以先要设置HMAC ctx的私钥,这里可不是衍生秘钥,是设置HMAC的私钥key = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, s->server_finished_secret, hashlen);,注意这里我们已经算出来了finished_key了,就是s->server_finished_secret
  • 使用衍生秘钥对第一步的trnascript-hash结果做一次hmac,结果即为finished消息。
	size_t tls13_final_finish_mac(SSL *s, const char *str, size_t slen,
                             unsigned char *out)
{
    const EVP_MD *md = ssl_handshake_md(s);
    unsigned char hash[EVP_MAX_MD_SIZE];
    size_t hashlen, ret = 0;
    EVP_PKEY *key = NULL;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();

    if (!ssl_handshake_hash(s, hash, sizeof(hash), &hashlen)) {
        /* SSLfatal() already called */
        goto err;
    }

    if (str == s->method->ssl3_enc->server_finished_label) {
        key = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL,
                                           s->server_finished_secret, hashlen);
    } else if (SSL_IS_FIRST_HANDSHAKE(s)) {
        key = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL,
                                           s->client_finished_secret, hashlen);
    } else {
        unsigned char finsecret[EVP_MAX_MD_SIZE];

        if (!tls13_derive_finishedkey(s, ssl_handshake_md(s),
                                      s->client_app_traffic_secret,
                                      finsecret, hashlen))
            goto err;

        key = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, finsecret,
                                           hashlen);
        OPENSSL_cleanse(finsecret, sizeof(finsecret));
    }

    if (key == NULL
            || ctx == NULL
            || EVP_DigestSignInit(ctx, NULL, md, NULL, key) <= 0
            || EVP_DigestSignUpdate(ctx, hash, hashlen) <= 0
            || EVP_DigestSignFinal(ctx, out, &hashlen) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS13_FINAL_FINISH_MAC,
                 ERR_R_INTERNAL_ERROR);
        goto err;
    }

    ret = hashlen;
 err:
    EVP_PKEY_free(key);
    EVP_MD_CTX_free(ctx);
    return ret;
}

写自动机发送阶段

没啥好说的,写CTX把消息发送出去。Finished消息依然是有服务端写秘钥保护,即由server_handshake_traffic_secret保护。

写自动机post_work阶段

服务端发送完finished消息之后,就可以发送应用数据了。因此在post_work函数中要调用s->method->ssl3_enc->generate_master_secret(s, s->master_secret, s->handshake_secret, 0, &s->session->master_key_length)s->method->ssl3_enc->change_cipher_state(s, SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_SERVER_WRITE)函数,这两个通用函数分别指向tls13_generate_master_secrettls13_change_cipher_state函数。调用完成之后返回WORK_FINISHED_CONTINUE。写自动机再次初始化为写初始状态,写自动机再循环st->write_state = WRITE_STATE_TRANSITION;。我们先看看tls13_generate_master_secret函数。等下我们看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:
...
    case TLS_ST_SW_FINISHED:
        if (statem_flush(s) != 1)
            return WORK_MORE_A;
...
        if (SSL_IS_TLS13(s)) {
            if (!s->method->ssl3_enc->generate_master_secret(s,
                        s->master_secret, s->handshake_secret, 0,
                        &s->session->master_key_length)
                || !s->method->ssl3_enc->change_cipher_state(s,
                        SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_SERVER_WRITE))
            /* SSLfatal() already called */
            return WORK_ERROR;
        }
        break;
			
	 return WORK_FINISHED_CONTINUE;

tls13_generate_master_secret函数

生成主密钥很简单,我们已经生成了握手秘钥s->handshake_secret,对应于TLS1.3秘钥衍生中的”Handshake Secret”,对s->handshake_secret做一次Derived-Secret再做一次HKDF-EXTRACT就得到主密钥master_secret,存储到s->master_secret里。tls13_generate_secret == Derived-Secret + HKDF-EXTRACT就不多讲解了。

	int tls13_generate_master_secret(SSL *s, unsigned char *out,
                                 unsigned char *prev, size_t prevlen,
                                 size_t *secret_size)
{
    const EVP_MD *md = ssl_handshake_md(s);

    *secret_size = EVP_MD_size(md);
    /* Calls SSLfatal() if required */
    return tls13_generate_secret(s, md, prev, NULL, 0, out);
}

tls13_change_cipher_state函数

此次我们调用tls13_change_cipher_state时,which为SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_SERVER_WRITE。我们看看流程:

  • tls13_change_cipher_state的第一个ifif (which & SSL3_CC_READ)不会进,只会进入else分支,初始化写ctx。
  • tls13_change_cipher_state的第一个if客户端写和服务端读也不会进入,只会进入else分支,因为是握手秘钥,所以不会进入if (which & SSL3_CC_HANDSHAKE),输入秘钥为insecret = s->master_secret;,指定标签为static const unsigned char server_application_traffic[] = "s ap traffic";
  • 因为不是接受EARLY_DATA,所以接下来会进入ifif (!(which & SSL3_CC_EARLY)),对ClientHello,ServerHello…server Finished所有的消息做hash
  • 因为需要计算客户端应用秘钥(TLS1.3的秘钥衍生流程里的client_application_traffic_secret_0),所以保存上一步的hash结果
  • 根据主密钥insecret = s->master_secret;和hash的结果,计算服务端应用数据写Key和服务端应用数据写Iv,代码为derive_secret_key_and_iv(s, which & SSL3_CC_WRITE, md, cipher,insecret, hash, label, labellen, secret, iv, ciph_ctx)
  • 进入if (label == server_application_traffic)的代码块,计算导出主密钥,我司的代码没写这个,因为没用到。
  • 最后一个if不会进入,因为finsecret == NULL
	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) {
        ...
    } else {
        s->statem.enc_write_state = ENC_WRITE_STATE_INVALID;
        if (s->enc_write_ctx != NULL) {
            EVP_CIPHER_CTX_reset(s->enc_write_ctx);
        } else {
            s->enc_write_ctx = EVP_CIPHER_CTX_new();
            if (s->enc_write_ctx == NULL) {
                SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                         SSL_F_TLS13_CHANGE_CIPHER_STATE, ERR_R_MALLOC_FAILURE);
                goto err;
            }
        }
        ciph_ctx = s->enc_write_ctx;
        iv = s->write_iv;

        RECORD_LAYER_reset_write_sequence(&s->rlayer);
    }

    if (((which & SSL3_CC_CLIENT) && (which & SSL3_CC_WRITE))
            || ((which & SSL3_CC_SERVER) && (which & SSL3_CC_READ))) {
       ...
    } else {
        /* Early data never applies to client-read/server-write */
        if (which & SSL3_CC_HANDSHAKE) {
            ...
        } 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 (!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;
        }

        ...
    } else if (label == client_application_traffic)
        ...

...

    if (finsecret != NULL
            && !tls13_derive_finishedkey(s, ssl_handshake_md(s), secret,
                                         finsecret, finsecretlen)) {
     ...
    }

    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:
    OPENSSL_cleanse(secret, sizeof(secret));
    return ret;
}

结语

服务端finished消息发送结束之后,计算服务端应用数据写密钥,流程非常简单。下回就可以说到服务端如何处理客户端的FINSIEHD消息了。

结尾的闲言碎语

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