前言
本篇文章纯属个人学习的一点经验分享,若有不对之处烦请各位大神现身指点,希望能和大家一起共同进步。
一、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流程基本走完,连接建立成功。
总结
以上代码分析只是大致将流程走了一遍,许多的细节因为篇幅的原因无法详细,有需要的同学可以留言,我会根据留言来做下一步的详细分析。