OPENSSL源码阅读(6)

非复用状态下的发送CertificateVerify

Posted by 大狗 on September 12, 2020

OPENSSL源码阅读(6)

前言

上一回说了非复用状态/完整握手状态下发送EncryptedExtension消息时自动机的变化,这次我们来说说完整握手情况下发送CertificateVerify时发生了什么,这里我们跳过了证书消息发送阶段。

从EncryptedExtension发送完毕说起

我们上次讲了,发送EncryptedExtension之后post_work啥都没干,然后Certificate消息发送阶段也没做啥有趣的事情。这次就要发送CertificateVerify消息了,因为上次最后修改写自动机状态为写初始状态st->write_state = WRITE_STATE_TRANSITION,所以还要从while循环说起,啊,需要记住这个时候的握手状态是写证书阶段,即st->hand_state = TLS_ST_SW_CERT

写自动机transition函数流程

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

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

进入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;

构建CertificateVerify消息的流程就比较有趣了,ossl_statem_server_construct_message指定调用的函数tls_construct_cert_verify函数之后,调用的函数还是tls_construct_cert_verify,Certificate Verify消息构建完成就执行发送和post_work函数。我们先来看看tls_construct_cert_verify函数到底干了些什么。

  • 首先调用get_cert_verify_tbs_data函数获取要签名的内容,根据RFC8446 SECTION4.4.3,我们清楚要签名的内容为‘64字节0x20+字符串”TLS 1.3, server CertificateVerify”+一字节0x00+Transcript-Hash(Handshake Context, Certificate)’,这里的Handshake Context就是Certificate消息之前的所有握手消息。而get_cert_verify_tbs_data函数就是按这个过程写的,比较简单,不赘述了。
  • 获取证书的私钥pkey = s->s3->tmp.cert->privatekey;,调用WPACKET_put_bytes_u16(pkt, lu->sigalg)写入签名算法,接着调用EVP_DigestSignInit(mctx, &pctx, md, NULL, pkey)初始化签名ctx,即ctx中已经获得了证书的私钥了。
  • 如果签名算法是RSA_PSS算法,那么需要加上RSA_PSS的padding,然后加盐,RSA_PSS的流程和RSA_PKCS的计算过程的区别我下回单独开一篇文章写。
  • 如果签名算法是ECDSA签名算法,那么不需要加什么多余步骤
  • 最后调用EVP_DigestSign(mctx, sig, &siglen, hdata, hdatalen)函数,获得Certificate Verify内容,返回1。
	int tls_construct_cert_verify(SSL *s, WPACKET *pkt)
{
    EVP_PKEY *pkey = NULL;
    const EVP_MD *md = NULL;
    EVP_MD_CTX *mctx = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    size_t hdatalen = 0, siglen = 0;
    void *hdata;
    unsigned char *sig = NULL;
    unsigned char tls13tbs[TLS13_TBS_PREAMBLE_SIZE + EVP_MAX_MD_SIZE];
    const SIGALG_LOOKUP *lu = s->s3->tmp.sigalg;

    if (lu == NULL || s->s3->tmp.cert == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_INTERNAL_ERROR);
        goto err;
    }
    pkey = s->s3->tmp.cert->privatekey;

    if (pkey == NULL || !tls1_lookup_md(lu, &md)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_INTERNAL_ERROR);
        goto err;
    }

    mctx = EVP_MD_CTX_new();
    if (mctx == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_MALLOC_FAILURE);
        goto err;
    }

    /* Get the data to be signed */
    if (!get_cert_verify_tbs_data(s, tls13tbs, &hdata, &hdatalen)) {
        /* SSLfatal() already called */
        goto err;
    }

    if (SSL_USE_SIGALGS(s) && !WPACKET_put_bytes_u16(pkt, lu->sigalg)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_INTERNAL_ERROR);
        goto err;
    }
    siglen = EVP_PKEY_size(pkey);
    sig = OPENSSL_malloc(siglen);
    if (sig == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_MALLOC_FAILURE);
        goto err;
    }

    if (EVP_DigestSignInit(mctx, &pctx, md, NULL, pkey) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_EVP_LIB);
        goto err;
    }

    if (lu->sig == EVP_PKEY_RSA_PSS) {
        if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) <= 0
            || EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx,
                                                RSA_PSS_SALTLEN_DIGEST) <= 0) {
            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                     ERR_R_EVP_LIB);
            goto err;
        }
    }
    if (s->version == SSL3_VERSION) {
        if (EVP_DigestSignUpdate(mctx, hdata, hdatalen) <= 0
            || !EVP_MD_CTX_ctrl(mctx, EVP_CTRL_SSL3_MASTER_SECRET,
                                (int)s->session->master_key_length,
                                s->session->master_key)
            || EVP_DigestSignFinal(mctx, sig, &siglen) <= 0) {

            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                     ERR_R_EVP_LIB);
            goto err;
        }
    } else if (EVP_DigestSign(mctx, sig, &siglen, hdata, hdatalen) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_CERT_VERIFY,
                 ERR_R_EVP_LIB);
        goto err;
    }
}

写自动机发送阶段

没啥好说的,写CTX把消息发送出去。

写自动机post_work阶段

post_work函数实际上也啥都没做,直接返回,并将写自动机状态改为写初始状态st->write_state = WRITE_STATE_TRANSITION;。直接蹦到下个阶段。

结语

这次实际上没咋写,写CTX初始化好了,CertificateVerify构建好了直接发出去,pre_work和post_work啥都没干,construct_func函数构建CertificateVerify消息。

结尾的闲言碎语

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