OPENSSL 的代码写的很乱,流程不是十分清楚,TLS1.3 的 EARLY DATA 又是一个穿插错乱的流程,所以写个东西来简单介绍。针对的代码是 S_SERVER 的流程。
如果启动 S_SERVER 时,支持 early_data,那么 OPENSSL 会调用 SSL_read_early_data 函数:
int SSL_read_early_data(SSL *s, void *buf, size_t num, size_t *readbytes) { int ret; if (!s->server) { SSLerr(SSL_F_SSL_READ_EARLY_DATA, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); return SSL_READ_EARLY_DATA_ERROR; } switch (s->early_data_state) { case SSL_EARLY_DATA_NONE: if (!SSL_in_before(s)) { SSLerr(SSL_F_SSL_READ_EARLY_DATA, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); return SSL_READ_EARLY_DATA_ERROR; } /* fall through */ case SSL_EARLY_DATA_ACCEPT_RETRY: s->early_data_state = SSL_EARLY_DATA_ACCEPTING; ret = SSL_accept(s); if (ret <= 0) { /* NBIO or error */ s->early_data_state = SSL_EARLY_DATA_ACCEPT_RETRY; return SSL_READ_EARLY_DATA_ERROR; } /* fall through */ case SSL_EARLY_DATA_READ_RETRY: if (s->ext.early_data == SSL_EARLY_DATA_ACCEPTED) { s->early_data_state = SSL_EARLY_DATA_READING; ret = SSL_read_ex(s, buf, num, readbytes); /* * State machine will update early_data_state to * SSL_EARLY_DATA_FINISHED_READING if we get an EndOfEarlyData * message */
很有趣,openssl 先是在 SSL_read_early_data 中调用了 SSL_accept 函数,这个函数本质上就是 state_machine,也就是 read_state_machine 和 write_state_machine,read_state_machine 负责处理读取到的 handshake 握手消息,比方说 client_hello。server 转完了 client_hello 的分析过程,并且把参数都保存下来就转入 write_state_machine,write_state_machine 要做的事情比较多,write_state_machine 分别要 construct_server_hello,construct_change_spec(处于兼容性),construct_encrypted_extension。并且调用 ssl->method->do_write 函数,这个函数模板真正指向 ssl3_do_write 函数。这一大段过程就是 SSL_accept 的过程。也就是说 SSL_accept 函数就是服务端的整个握手流程,请参看 TLS1.3 RFC 的 SERVER STATE MACHINE 流程图 visit https://tools.ietf.org/html/rfc8446#page-121
SSL_accept 握手完成之后,服务端调用 SSL_read_ex(s, buf, num, readbytes)函数读取 early_data 的内容,SSL_read_ex 内部包裹 ssl_read_internal 函数,ssl_read_internal 包裹 s->method->ssl_read,这个函数指针就是 ssl3_read,包裹 ssl3_read_internal 函数,而 ssl3_read_internal 又包裹了 s->method->ssl_read_bytes 也就是 ssl3_read_bytes 函数
int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf, size_t len, int peek, size_t *readbytes)
根据 type 的类型读取 payload,存储长度到 len,只有三种 type:SSL3_RT_HANDSHAKE (when ssl3_get_message calls us),SSL3_RT_APPLICATION_DATA (when ssl3_read calls us),0 (during a shutdown, no data has to be returned)。也就是说这个函数只负责读取,更多的内容会由上层负责。值得注意的是,为了能在本层分析报文的 payload,也就是报文的种类( alert,handshake protocol ),该函数会把 payload 的头四个字节存储到 s->rlayer.handshake_fragment 中,然后根据预先假定的 type 类型来分析读到的报文。
回到刚才的场景,SSL_read_ex 的报文类型是 SSL3_RT_APPLICATION_DATA,下面的代码时调用 SSL3_read_bytes 的函数:
static int ssl3_read_internal(SSL *s, void *buf, size_t len, int peek, size_t *readbytes) { int ret; clear_sys_error(); if (s->s3->renegotiate) ssl3_renegotiate_check(s, 0); s->s3->in_read_app_data = 1; ret = s->method->ssl_read_bytes(s, SSL3_RT_APPLICATION_DATA, NULL, buf, len, peek, readbytes); ...
ssl3_read_bytes 函数会不断的读取 payload 到传进去的参数 buf 中,如果这个过程读不到数据,该函数会再次尝试读取出来 pay_load,下面代码在 SSL3_read_bytes 内部:
do { if (len - totalbytes > SSL3_RECORD_get_length(rr)) n = SSL3_RECORD_get_length(rr); else n = len - totalbytes; memcpy(buf, &(rr->data[rr->off]), n); buf += n; if (peek) { /* Mark any zero length record as consumed CVE-2016-6305 */ if (SSL3_RECORD_get_length(rr) == 0) SSL3_RECORD_set_read(rr); } else { SSL3_RECORD_sub_length(rr, n); SSL3_RECORD_add_off(rr, n); if (SSL3_RECORD_get_length(rr) == 0) { s->rlayer.rstate = SSL_ST_READ_HEADER; SSL3_RECORD_set_off(rr, 0); SSL3_RECORD_set_read(rr); } } if (SSL3_RECORD_get_length(rr) == 0 || (peek && n == SSL3_RECORD_get_length(rr))) { curr_rec++; rr++; } totalbytes += n; } while (type == SSL3_RT_APPLICATION_DATA && curr_rec < num_recs && totalbytes < len); if (totalbytes == 0) { /* We must have read empty records. Get more data */ goto start; } if (!peek && curr_rec == num_recs && (s->mode & SSL_MODE_RELEASE_BUFFERS) && SSL3_BUFFER_get_left(rbuf) == 0) ssl3_release_read_buffer(s); *readbytes = totalbytes; return 1;
如果服务端收到一个 SSL3_RT_APPLICATION_DATA,该消息可能史哥 ALERT 消息,或者 HANDSHAKE 消息,如果是 ALERT 消息,ssl3_read_bytes 会利用 PACKET_get_1 来获取具体的 alert number,alert content。非当服务端收到 End of Early Data 报文时,该报文类型为 SSL3_RT_HANDSHAKE 而不是 SSL3_RT_APPLICATION_DATA 报文,说明我们收到了一个握手报文。服务端会尝试收取到足够长度的握手报文,并在此进入握手函数 s->handshake_func 恢复握手过程,下面的代码在 SSL3_read_bytes 内部。
if ((s->rlayer.handshake_fragment_len >= 4) && !ossl_statem_get_in_handshake(s)) { int ined = (s->early_data_state == SSL_EARLY_DATA_READING); /* We found handshake data, so we're going back into init */ ossl_statem_set_in_init(s, 1); i = s->handshake_func(s); /* SSLfatal() already called if appropriate */ if (i < 0) return i; if (i == 0) { return -1; }
进入 handshake_func 也就是 state_machine 的后续流程不再赘述,参照流程图即可。
握手的函数流程,和一些函数的 API 写不写看心情吧,加上一些链接可以用来学习。 https://www.cnblogs.com/hrhguanli/p/3834585.html https://blog.csdn.net/cwg2552298/article/details/83045448 https://security.stackexchange.com/questions/55667/tls-sequence-number https://tools.ietf.org/html/rfc8446
![]() | 1 isCyan 2019-01-29 23:12:36 +08:00 via Android 可能是干货,虽然看不懂,先挽尊 |