下面简单介绍下SSL的连接。SSL连接的数据流图大致描述如下:
第一步:客户端发送client_hello请求SSL连接;
服务器端回应server_hello。
这一步主要协商,包括协议版本,session ID,加密算法、以及初始化的随机数。
第二步:服务端发送证书,进行证书用来计算连接共享密钥。
服务端发送密钥,数据加密密钥的交换操作,RSA密钥交换。
服务端发送证书请求,要求获取客户端证书的请求。
第三步:服务端接收客户端发送的证书、以及客户端密钥、进行认证。
第四步:发送修改信息,修改加密算法信息。
在介绍SSL连接的具体实现之前,先来介绍下openssl的几个至关重要的方法集,所谓的方法集,也就是一个结构体,里面全是函数指针,设置的一些回调函数,这些回调函数,贯穿整个连接以及认证。
在介绍这个函数集之前,又需要先来介绍结构体和一个宏,用来描述这个方法集(参考文档:include/openssl/ssl<结构体>和ssl/ssl_locl.h<宏>):
这个结构体就是描述了这个方法集,描述的是这个方法集的数据类型。
而接下来要介绍的这个宏,则是创建一个SSL_METHOD的变量,并附上一定的初始化步骤。
从以上代码可以看出,这个宏里面是定义了一个函数,这个函数返回的是一个SSL_METHOD类型的结构体指针。这个函数的工作是就是创建一个static的
SSL_METHOD类型的结构体。定义的函数的名字是宏的参数func_name所传入的值。
举个例子:参考mod_ssl中的modules/ssl/ssl_engine_init.c中的ssl_init_ctx_protocol(359行)初始化函数。
文中是对modssl_ctx_t中的ssl_ctx的初始化部分。modthod就是这个函数集的结构体。赋的值是函数SSLV23_server_method的返回值,而函数SSLV23_server_method定义的地方是在openssl中ssl/S23_srvr.c中的135行处。
文中用上面介绍的宏IMPLEMENT_ssl23_meth_func定义了一个函数SSLv23_server_method。该函数将返回SSL_METHOD的结构体。
三、ssl连接分析ssl_accept关于证书认证,调用的几个主要函数是:
ssl_hook_Access函数 ->
SSL_do_handshake函数 ->
ssl23_accept函数.
其实整个证书认证的过程,就是一个重新握手的过程。在ssl23_accept这个函数中,主要描述了整个握手过程,以及这个握手过程中所经历的状态变化和每一步所做的工作。
关于证书认证,在mod_ssl调用的关键步骤如下(modules/ssl/Ssl_engine_kernel.c中的ssl_hook_Access函数,大致位置614行):
文中调用了两遍SSL_do_handshake函数,第一遍调用ssl_do_handshake仅是向客户端发送hello的答复信息;第二遍SSL_do_handshake是接收处理,以及整个认证过程,核心工作在第二个SSL_do_handshake。
mod_ssl通过向客户端发送一个hello req的回复请求信息来强制SSL进行重新协商。Mod_ssl做了这么一个处理:在服务端发送hello req回复请求之后,openssl会立即相应,虽然说在SSL/TLS协议中并没有指定客户端必须响应服务器端的hello req回复请求,但是服务器端为了容错,会强制要求回复,之后服务器端会改成ACCEPT状态。在这里不能使用SSL_set_accept_state()函数,因为SSL_set_accept_state()会重设连接的很多信息,所以只能自己设置状态进行手动的握手。
对上面代码的分析:
1、首先调用SSL_renegotiate函数,会设置一个标志位renegotiate(ssl->s3->renegotiate),一个表示重新协商的标志位,SSL_do_handshake的函数中,会调用一个回调函数ssl_renegotiate_check来检测这个标志位,如果这个标志位被设成1,则会将ssl的状态设置SSL_ST_RENEGOTIATE,并将renegotiate标记位设置成0。这个状态位贯穿以及连接整个ssl_accept的过程,以及控制其握手流程。
2、第二行的SSL_do_handshake函数,也就是调用的第一遍SSL_do_handshake,会完成上述的检测renegotiate状态之后,调用另外一个回调函数handshake_func,这个函数实际上就是SSL_accept或者是SSL_connect函数(对应的是服务器端或者客户端)。这一步在SSL_accept中,所经历的状态变化是:
状态值 对应的宏
0x3004 SSL_ST_RENEGOTIATE
0x2120 SSL3_ST_SW_HELLO_REQ_A
0x2121 SSL3_ST_SW_HELLO_REQ_B
0x2100 SSL3_ST_SW_FLUSH
0x2122 SSL3_ST_SW_HELLO_REQ_C
0x3 SSL_ST_OK
先对这些宏的字面解释做个简单介绍,openssl的这几个宏,从字面上看就能看出大概所做的处理:以SSL_ST_SW_HELLO_REQ_A为例,SSL、SSL3是指协议版本,ST是指状态类型,SW中的S是指Server,W是指写Write(相应的就有对应的Client和Read),HELLO_REQ是指工作的内容,最后面的后缀A、B、C是指这步活动的各个小步骤,SSL很多步骤都分A、B两种,通常,A状态表示刚进入该状态,还没有收发数据,B状态表示进行的收发数据处理后,但还没有完成善后工作。初步可以看出,这一步完成的工作是服务端写入hello回复请求。
在这步中所经历的其他几个状态,在下一次SSL_do_handshake中将还会出现,将在下一次SSL_do_handshake中做统一介绍。
在这次SSL_do_handshake调用中最核心的一步就是处于SSL3_ST_SW_HELLO_REQ_A状态中调用的ssl3_send_hello_request函数,向客户端发送一个hello的回复请求。
3、中间的几行是容错处理,比较核心的最后两步,倒数第二步比较简单,SSL_set_state(ssl, SSL_ST_ACCEPT);就是设置一个状态,将状态设置成SSL_ST_ACCEPT。
4、最后一步,也就是第二次调用SSL_do_handshake,也是所有的核心处理。
下面将粗略下分析第四步每一步所做的工作:
这一步经历的所有状态如下:
步骤 状态数值 对应的宏
1 0x2000 SSL_ST_ACCEPT
2 0x2110 SSL3_ST_SR_CLNT_HELLO_A
3 0x2130 SSL3_ST_SW_SRVR_HELLO_A
4 0x2131 SSL3_ST_SW_SRVR_HELLO_B
5 0x2140 SSL3_ST_SW_CERT_A
6 0x2141 SSL3_ST_SW_CERT_B
7 0x2150 SSL3_ST_SW_KEY_EXCH_A
8 0x2160 SSL3_ST_SW_CERT_REQ_A
9 0x2161 SSL3_ST_SW_CERT_REQ_B
10 0x2100 SSL3_ST_SW_FLUSH
11 0x2180 SSL3_ST_SR_CERT_A
12 0x2181 SSL3_ST_SR_CERT_B
13 0x2190 SSL3_ST_SR_KEY_EXCH_A
14 0x21a0 SSL3_ST_SR_CERT_VRFY_A
15 0x21c0 SSL3_ST_SR_FINISHED_A
16 0x21c1 SSL3_ST_SR_FINISHED_B
17 0x21d0 SSL3_ST_SW_CHANGE_A
18 0x21d1 SSL3_ST_SW_CHANGE_B
19 0x21e0 SSL3_ST_SW_FINISHED_A
20 0x21e1 SSL3_ST_SW_FINISHED_B
21 0x2100 SSL3_ST_SW_FLUSH
22 0x3 SSL_ST_OK
由于这个函数比较庞大,在这里将不附上源码(代码penssl中的ssl/S3_srve.c中的ssl3_accept函数,164行),仅粗略描述下状态变化,以及每步的主要功能和所调用的主要函数。
第1步是之前在ssk_hook_Access(mod_ssl中的)中设置的状态位,也是这次的入口状态,
第2步读取客户端的数据,函数自己构造的,而不是实际收到的,主要调用的函数
ssl3_get_client_hello。
第3、4步是写服务器端的回应的HELLO请求信息,主要调用2个函数:ssl3_send_hello_request,写服务端的回应请求信息;ssl3_init_finished_mac,初始化认证码MAC。
在函数ssl3_send_hello_request里面会将状态改为SSL3_ST_SW_SRVR_HELLO_B。其中有个比较重要的判断,s->hit用来标记该ssl会话是否是重用的,在ssl3_get_client()函数中检查客户端的hello信息后设置的,如果会话是重用的,将状态改为SSL3_ST_SW_CHANGE_A,否则为新的SSL会话,进入证书处理A状态,将状态改为SSL_ST_SW_CERT_A。
第5、6步SSL3_ST_SW_CERT_A,进行证书交换,用来计算连接共享密钥。主要调用ssl3_send_server_certificate函数,在非NULL加密的话调用,发送服务器端的证书。
第7步SSL3_ST_SW_KEY_EXCH_A,数据加密的密钥交换操作。算法类型由一个常数表述l=s->s3->tmp.new_cipher->algorithms。主要调用的函数ssl3_send_server_key_exchange,进行RSA密钥交换。
第8、9步SSL3_ST_SW_CERT_REQ_A,进入证书请求阶段。
大多数情况下是不需要客户端的证书的,如果要验证对方,只要以下条件之一不满足就可以认证对方:
file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image003.gif
其中CTX的verify_mode通过SSL_CTX_set_verify()来修改,s->verify_mode可通过函数SSL_get_verify()来修改,s->verify_mode的初始值是ctx->verify_mode赋予的。
这一步主要调用的函数ssl3_send_certificate_request,发送要获取对方证书的请求。其中s->s3->tmp.next_state=SSL3_ST_SR_CERT_A会在清除写缓冲状态的处理中会用到这个数据,记录清除写缓冲之后下一步的操作。
第10步SSL3_ST_SW_FLUSH。清除写缓冲。
第11、12步SSL3_ST_SR_CERT_A,接收客户端证书。这步主要调用了2个函数ssl3_check_client_hello,检测客户端的hello信息;ssl3_get_client_certificate,获取客户端证书。整个证书认证、以及crl检测就是在ssl3_get_client_certificate这个函数中完成的。
第13步SSL3_ST_SR_KEY_EXCH_A,处理密钥交换。主要调用了2个函数,ssl3_get_client_key_exchange获取客户端密钥;以及回调函数method->ssl3_enc->cert_verify_mac,用来认证证书MAC码。
第14步SSL3_ST_SR_CERT_VRFY_A,验证证书。主要调用函数ssl3_get_cert_verify,这个函数的主要目的是验证证书的签名。
第15、16步SSL3_ST_SR_FINISHED_A,服务器端接收结束状态。主要调用函数ssl3_get_finished,获取结束信息。这里里面,也有判断重用标记,s->hit,如果是重用的,连接已建立,改状态为SSL_ST_OK,否则,改状态为SSL3_ST_SW_CHANGE_A。
第17、18步SSL3_ST_SW_CHANGE_A,服务器发送修改信息状态。这步主要有s->session->cipher=s->s3->tmp.new_cipher设置SSL加密算法;以及函数调用ssl3_send_change_cipher_spec,发送修改加密算法信息。
第19、20步SSL3_ST_SW_FINISHED_A,服务端发送结束,握手完成。调用函数ssl3_send_finished。这一步会将状态转成SSL3_ST_SW_FLUSH,同时指定下下步的状态。在指定下下步状态的时候,也会判断s->hit标记位,如过为1,将下下步的状态改成SSL3_ST_SR_FINISHED_A,否则将下下步状态改成SSL_ST_OK。
第21步SSL3_ST_SW_FLUSH,清除SSL写缓冲。并指定下一步状态。
第22步SSL_ST_OK,连接成功完成,清除连接过程中所申请的资源。
整个认证的流程以及执行的顺序如上述描述。
补充说明:openssl对于ssl_accept这一系列的函数,总共有3套,分别如ssl23_accept、ssl2_accept、ssl3_accept以23、2、3来区分(表示的版本信息的区别)。实际上第一连接会走ssl23_accept()函数,这个函数里面会有协商过程,协商客户端、服务器端的ssl版本,协商的过程是在ssl23_get_client_hello()函数中完成的,这也是个比较复杂的函数(这个函数主要是要分析ssl的协议)。在协商玩完了之后会转到SSL_accept函数,实际上就是转到ssl2_accept或者ssl3_accept函数。在ssl2_accept和ssl3_accept函数中就没有协商的过程了。
四、总结