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_CONTINUE
。transition
阶段结束,进入到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_WRITE
与SSL3_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这块还有啥不明白的直接告诉我就成了