OpenSSL之SSL_connect函数分析

本系列OpenSSL使用的代码版本为:1.0.2o。


前言

本篇文章纯属个人学习的一点经验分享,若有不对之处烦请各位大神现身指点,希望能和大家一起共同进步。


一、SSL_connect是什么?

SSL_connect函数是ssl客户端发起ssl连接的一个函数,函数原型:int SSL_connect(SSL *ssl)。这个函数的调用实现了ssl密钥协商的所有流程,从client_hello开始到最后的密钥协商结束。

二、代码分析

首先我们来看看SSL_connect的实现:

int SSL_connect(SSL *s)
{
    if (s->handshake_func == 0)
        /* Not properly initialized yet */
        SSL_set_connect_state(s);

    return (s->method->ssl_connect(s));
}

首先判断handshake_func握手方法指针是否被赋值,如果没有则调用SSL_set_connect_state进行设置,然后调用s->method->ssl_connect(s)进行连接,那么method结构是从哪里来的呢,它是从ctx = SSL_CTX_new (SSLv23_client_method())获取得来的,SSLv23_client_method()就是一个method结构,然后设置到ctx上下文中,最后调用SSL_new(ctx)时拷贝到SSL结构中,所以我们想知道这里调用的ssl_connect到底是那个方法,就看你SSL_CTX_new()时传的是什么了。我这里调用了SSLv23_client_method(),我们就可以去跟着这个方法找到ssl_connect的实现,它在哪呢?在s23_clnt.c文件的:IMPLEMENT_ssl23_meth_func(SSLv23_client_method,
                          ssl_undefined_function,
                          ssl23_connect, ssl23_get_client_method)

中,在这个宏定义中将ssl23_connect赋值给了method结构中的ssl_connect,所以s->method->ssl_connect(s)其实调用的就是ssl23_connect(s)函数,接下来我们来继续追踪ssl23_connect方法的实现。

int ssl23_connect(SSL *s)
{
    BUF_MEM *buf = NULL;
    unsigned long Time = (unsigned long)time(NULL);
    void (*cb) (const SSL *ssl, int type, int val) = NULL;
    int ret = -1;
    int new_state, state;

    RAND_add(&Time, sizeof(Time), 0);
    ERR_clear_error();
    clear_sys_error();

    if (s->info_callback != NULL)
        cb = s->info_callback;
    else if (s->ctx->info_callback != NULL)
        cb = s->ctx->info_callback;

    s->in_handshake++;
    if (!SSL_in_init(s) || SSL_in_before(s))
        SSL_clear(s);

    for (;;) {
        state = s->state;

        switch (s->state) {
        case SSL_ST_BEFORE:
        case SSL_ST_CONNECT:
        case SSL_ST_BEFORE | SSL_ST_CONNECT:
        case SSL_ST_OK | SSL_ST_CONNECT:

            if (s->session != NULL) {
                SSLerr(SSL_F_SSL23_CONNECT,
                       SSL_R_SSL23_DOING_SESSION_ID_REUSE);
                ret = -1;
                goto end;
            }
            s->server = 0;
            if (cb != NULL)
                cb(s, SSL_CB_HANDSHAKE_START, 1);

            /* s->version=TLS1_VERSION; */
            s->type = SSL_ST_CONNECT;

            if (s->init_buf == NULL) {
                if ((buf = BUF_MEM_new()) == NULL) {
                    ret = -1;
                    goto end;
                }
                if (!BUF_MEM_grow(buf, SSL3_RT_MAX_PLAIN_LENGTH)) {
                    ret = -1;
                    goto end;
                }
                s->init_buf = buf;
                buf = NULL;
            }

            if (!ssl3_setup_buffers(s)) {
                ret = -1;
                goto end;
            }

            if (!ssl3_init_finished_mac(s)) {
                ret = -1;
                goto end;
            }

            s->state = SSL23_ST_CW_CLNT_HELLO_A;
            s->ctx->stats.sess_connect++;
            s->init_num = 0;
            break;

        case SSL23_ST_CW_CLNT_HELLO_A:
        case SSL23_ST_CW_CLNT_HELLO_B:

            s->shutdown = 0;
            ret = ssl23_client_hello(s);
            if (ret <= 0)
                goto end;
            s->state = SSL23_ST_CR_SRVR_HELLO_A;
            s->init_num = 0;

            break;

        case SSL23_ST_CR_SRVR_HELLO_A:
        case SSL23_ST_CR_SRVR_HELLO_B:
            ret = ssl23_get_server_hello(s);
            if (ret >= 0)
                cb = NULL;
            goto end;
            /* break; */

        default:
            SSLerr(SSL_F_SSL23_CONNECT, SSL_R_UNKNOWN_STATE);
            ret = -1;
            goto end;
            /* break; */
        }

        if (s->debug) {
            (void)BIO_flush(s->wbio);
        }

        if ((cb != NULL) && (s->state != state)) {
            new_state = s->state;
            s->state = state;
            cb(s, SSL_CB_CONNECT_LOOP, 1);
            s->state = new_state;
        }
    }
 end:
    s->in_handshake--;
    if (buf != NULL)
        BUF_MEM_free(buf);
    if (cb != NULL)
        cb(s, SSL_CB_CONNECT_EXIT, ret);
    return (ret);
}

首先判断连接是否处于初始化状态SSL_in_init(s),如果不是调用SSL_clear(s)清除状态和数据,然后进入for循环根据s->state状态来状态机操作,客户端第一个状态是初始化状态,然后进入SSL23_ST_CW_CLNT_HELLO_A,调用ssl23_client_hello(s)发送client_hello报文,然后将状态置为SSL23_ST_CR_SRVR_HELLO_A,进入获取server_hello状态,调用ssl23_get_server_hello(s)方法,在ssl23_get_server_hello方法中根据服务端返回的ssl版本信息重置method结构体,代码如下:

if ((p[2] == TLS1_VERSION_MINOR) && !(s->options & SSL_OP_NO_TLSv1)) {
            s->version = TLS1_VERSION;
            s->method = TLSv1_client_method();
        } else if ((p[2] == TLS1_1_VERSION_MINOR) &&
                   !(s->options & SSL_OP_NO_TLSv1_1)) {
            s->version = TLS1_1_VERSION;
            s->method = TLSv1_1_client_method();
        } else if ((p[2] == TLS1_2_VERSION_MINOR) &&
                   !(s->options & SSL_OP_NO_TLSv1_2)) {
            s->version = TLS1_2_VERSION;
            s->method = TLSv1_2_client_method();
        } else {
            /*
             * Unrecognised version, we'll send a protocol version alert using
             * our preferred version.
             */
            switch(s->client_version) {
            default:
                /*
                 * Shouldn't happen
                 * Fall through
                 */
            case TLS1_2_VERSION:
                s->version = TLS1_2_VERSION;
                s->method = TLSv1_2_client_method();
                break;
            case TLS1_1_VERSION:
                s->version = TLS1_1_VERSION;
                s->method = TLSv1_1_client_method();
                break;
            case TLS1_VERSION:
                s->version = TLS1_VERSION;
                s->method = TLSv1_client_method();
                break;
#ifndef OPENSSL_NO_SSL3
            case SSL3_VERSION:
                s->version = SSL3_VERSION;
                s->method = SSLv3_client_method();
                break;
#endif
            }
            SSLerr(SSL_F_SSL23_GET_SERVER_HELLO, SSL_R_UNSUPPORTED_PROTOCOL);
            ssl3_send_alert(s, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
            goto err;
        }

然后在调用SSL_connect(s)重新进入新method的处理流程,如服务器版本为SSL3_VERSION,则进入s3_clnt.c文件中的 ssl3_connect()方法进行处理,当前的s->state状态依然是SSL23_ST_CR_SRVR_HELLO_A,进入ssl3_connect之后才真正的开始处理服务端发送过来的协议报文,从server_hello开始,调用ssl3_get_server_hello(s)方法处理server_hello报文,详细的处理流程可以自己仔细看,由于篇幅太多就不在这里说了。

处理完server_hello报文之后,根据结果将状态置为SSL3_ST_CR_CERT_A、SSL3_ST_CR_SESSION_TICKET_A或者SSL3_ST_CR_FINISHED_A,不考虑其他流程,走正常的ssl密钥协商,接下来就是SSL3_ST_CR_CERT_A,调用ssl3_get_server_certificate(s)来处理certificate报文,处理完certificate报文之后状态置为SSL3_ST_CR_KEY_EXCH_A,调用ssl3_get_key_exchange(s)来处理server key exchange报文,之后状态置为SSL3_ST_CR_CERT_REQ_A,调用ssl3_get_certificate_request(s)来处理certificate request报文,当然ssl单向认证是不存在certificate request报文的,所以在ssl3_get_certificate_request(s)方法中,第一步读取报文数据,第二步就是判断报文数据内容,如果报文类型为SSL3_MT_SERVER_DONE,则返回1退出方法,代码如下:

   n = s->method->ssl_get_message(s,
                                   SSL3_ST_CR_CERT_REQ_A,
                                   SSL3_ST_CR_CERT_REQ_B,
                                   -1, s->max_cert_list, &ok);

    if (!ok)
        return ((int)n);

    s->s3->tmp.cert_req = 0;

    if (s->s3->tmp.message_type == SSL3_MT_SERVER_DONE) {
        s->s3->tmp.reuse_message = 1;
        /*
         * If we get here we don't need any cached handshake records as we
         * wont be doing client auth.
         */
        if (s->s3->handshake_buffer) {
            if (!ssl3_digest_cached_records(s))
                goto err;
        }
        return (1);
    }

然后将s->state状态置为SSL3_ST_CR_SRVR_DONE_A,进入ssl3_get_server_done(s)方法,处理server hello done报文处理,

处理完server hello done报文之后将状态置为SSL3_ST_CW_CERT_A或者SSL3_ST_CW_KEY_EXCH_A,这是单项认证和双向认证报文区别了,如果是双向认证,则状态置为SSL3_ST_CW_CERT_A,进入ssl3_send_client_certificate(s)方法,发送client certificate报文,然后将状态置为SSL3_ST_CW_KEY_EXCH_A,进入ssl3_send_client_key_exchange(s)发送client key exchange报文,然后将状态置为SSL3_ST_CW_CERT_VRFY_A或者SSL3_ST_CW_CHANGE_A,双向认证置为SSL3_ST_CW_CERT_VRFY_A调用ssl3_send_client_verify(s)发送证书验签报文,之后将状态置为SSL3_ST_CW_CHANGE_A,调用ssl3_send_change_cipher_spec(s,SSL3_ST_CW_CHANGE_A,SSL3_ST_CW_CHANGE_B)发送change cipher spec报文,将状态置为SSL3_ST_CW_FINISHED_A,调用ssl3_send_finished发送加密的finished报文,最后将状态置为SSL3_ST_CR_SESSION_TICKET_A或者SSL3_ST_CR_FINISHED_A,是否置为SSL3_ST_CR_SESSION_TICKET_A需要看客户端在发送client hello时是否发送了session ticket,如果没有则进入SSL3_ST_CR_FINISHED_A状态,调用ssl3_get_finished等待获取change cipher spec和服务端finished报文,如果ssl3_get_finished处理正常,则将状态置为SSL_ST_OK,完成ssl密钥协商,退出ssl3_connect()方法返回结果。如此SSL_connect流程基本走完,连接建立成功。


总结

以上代码分析只是大致将流程走了一遍,许多的细节因为篇幅的原因无法详细,有需要的同学可以留言,我会根据留言来做下一步的详细分析。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值