最近在分析某个PC端程序的登录过程,发现它用的是openssl进行https通讯的。
由于以前没有openssl的使用经验,遂开始学习这个库,在这里记录一些TLS协议的原理,
以及openssl实现TLS协议的代码分析。(TLS 相当于 SSL 协议的后续版本。)
画了一个丑陋的简图:
图的右边代表时间的方向。按照时间顺序,显然是客户端先发起 TLS 请求, 即ClientHello消息;
然后服务器响应客户端请求,返回 ServerHello 消息以及服务器的证书;客户端根据服务器返回的
证书验证服务器是否合法,并确定使用的TLS版本;客户端发送交换密钥请求;服务器响应密钥交换
请求;接着就进行加密的通讯了。
OpenSSL 已经高度抽象了这些过程,根据网上的一些例子得知客户端的基本代码:
sslctx_init() // 初始化 SSL_Ctx 对象相关
SSL_new(g_sslctx) //创建 SSL 对象
SSL_set_fd(ssl,fd) //用创建的 socket 初始化 SSL 对象
SSL_connect(ssl) //连接 SSL服务器,这里实现了 ClientHello
//---- 接着就是与服务器的其他通讯过程调用了
SSL_shutdown(ssl) //关闭SSL连接
//---- 善后 ……
得知了是 SSL_connect 函数对服务器发送 ClientHello 就可以开始分析源码了。
SSL_connect :
函数仅仅是简单的调用 SSL_do_handshake 函数,分析 SSL_do_handshake 函数。
SSL_do_handshake:
由于可以使用不同版本的TLS协议,所以 SSL_do_handshake 最终调用 s->handshake_func(s);
使用函数指针可以根据不同版本TLS协议调用不同的握手函数。
我用 strings 搜索了对 s->handshake_func 的赋值语句,发现了赋值为 ossl_statem_connect
ossl_statem_connect:
简单的对 state_machine(s, 0); 函数进行调用
state_machine:
根据函数名,知道这是个协议状态机函数。根据 st->state 以及 st->hand_state 等协议状态,
进行不同的操作。由于当前是 ClientHello 协议,所以我只分析关于 ClientHello 相关代码。
根据协议跳转,最终调用 write_state_machine 函数。
write_state_machine:
初始化一些函数指针,根据当前握手状态(ClientHello),调用 tls_construct_client_hello 函数。
tls_construct_client_hello:
这个函数就是最底层的组包函数了。(在 statem_clnt.c 文件中)
写入 2 字节的客户端版本信息
WPACKET_put_bytes_u16(pkt, s->client_version)
写入客户端随机数
WPACKET_memcpy(pkt, s->s3->client_random, SSL3_RANDOM_SIZE)
如果会话ID不为0,则写入会话ID
WPACKET_memcpy(pkt, session_id,sess_id_len)
写入和密码套件相关的信息
ssl_cipher_list_to_bytes(s, SSL_get_ciphers(s), pkt)
具体调用 s->method->put_cipher_by_char(c, pkt, &len) 写入密码套件信息
如果有压缩信息,则写入压缩信息
ssl_allow_compression
最后添加 扩展信息
tls_construct_extensions(s, pkt, SSL_EXT_CLIENT_HELLO, NULL, 0)
看看WireShark 抓的 ClientHello 包结构:
包括了:版本号、客户端随机数、密码套件信息、压缩方式、扩展信息等成员。
Good , 分析了一天,总是一点点收获……