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_secret
与tls13_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,所以接下来会进入if
if (!(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这块还有啥不明白的直接告诉我就成了