V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hxndg
V2EX  ›  SSL

TLS1.3 OPENSSL 的 EARLY DATA 处理流程

  •  
  •   hxndg · 2019-01-26 01:53:25 +08:00 · 3532 次点击
    这是一个创建于 1910 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 条附言  ·  2019-01-31 14:08:14 +08:00
    忽然想到好多公众号都会干这种事情:随便写点垃圾,然后宣传自己。
    那我宣传一下我司好了( LUL ),ArrayNetworks 公司,负载均衡专家,F 5不敢比,PK   A10 还是没问题的,联通集采中标公司。。。。。哈哈哈
    2 条回复    2019-01-30 11:01:40 +08:00
    isCyan
        1
    isCyan  
       2019-01-29 23:12:36 +08:00 via Android
    可能是干货,虽然看不懂,先挽尊
    hxndg
        2
    hxndg  
    OP
       2019-01-30 11:01:40 +08:00
    @isCyan
    简单的代码分析而已,主要的新东西都在 1.3 的 rfc 里面。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   950 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 21:16 · PVG 05:16 · LAX 14:16 · JFK 17:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.