构造Server Hello消息
int tls_construct_server_hello(SSL *s)
{
unsigned char *buf;
unsigned char *p, *d;
int i, sl;
int al = 0;
unsigned long l;
/*我们知道数据包包含了头部和数据两个部分,在构造的时候是分别构造的,一般的头部部分构造比较复杂,数据部分通常就是一个拷贝操作。这里的 ssl_handshake_start就是一个区分头部和数据部分的指针。# define ssl_handshake_start(s) (((unsigned char *)s->init_buf->data) + s->method->ssl3_enc->hhlen)*/
buf = (unsigned char *)s->init_buf->data;
d = p = ssl_handshake_start(s);
//拿到了头部的指针之后,就可以开始往里顺序的填充头部了。首先填充的是版本号
*(p++) = s->version >> 8;
*(p++) = s->version & 0xff;
/*
* 填充服务端产生的随机数
*/
memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE);
p += SSL3_RANDOM_SIZE;
/*-
接下来是处理Session ID。用户来了Session ID的请求,有两种方式,一种是Session Cache,一种是Session Ticket,如果Session Ticket出现,就会跳过Session Cache而优先采用Session Ticket。处理用户发来的Session ID的请求也有好几种情况,一种是找到了用户Session ID对应的Session,这时就会回复这个Session ID。还有一种是没有找到,这时就会创建一个新的Session ID,发送回去的是新的Session ID。由于Session Ticket是比Session ID有更高的优先级。如果是服务器决定使用Session Ticket,就会回复生成的Session Ticket。如果想要一个Session ID不能被复用,就会回复一个0长度的Session ID
*/
if (s->session->not_resumable ||
(!(s->ctx->session_cache_mode & SSL_SESS_CACHE_SERVER)
&& !s->hit))
s->session->session_id_length = 0;
sl = s->session->session_id_length;
if (sl > (int)sizeof(s->session->session_id)) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_HELLO, ERR_R_INTERNAL_ERROR);
ossl_statem_set_error(s);
return 0;
}
*(p++) = sl;
memcpy(p, s->session->session_id, sl);
p += sl;
//向Server Hello消息中写入服务器选择的加密套件
/* put the cipher */
i = ssl3_put_cipher_by_char(s->s3->tmp.new_cipher, p);
p += i;
/* put the compression method */
#ifdef OPENSSL_NO_COMP
*(p++) = 0;
#else
if (s->s3->tmp.new_compression == NULL)
*(p++) = 0;
else
*(p++) = s->s3->tmp.new_compression->id;
#endif
//写入服务器支持的Server Hello的扩展头部
if (ssl_prepare_serverhello_tlsext(s) <= 0) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_HELLO, SSL_R_SERVERHELLO_TLSEXT);
ossl_statem_set_error(s);
return 0;
}
if ((p =
ssl_add_serverhello_tlsext(s, p, buf + SSL3_RT_MAX_PLAIN_LENGTH,
&al)) == NULL) {
ssl3_send_alert(s, SSL3_AL_FATAL, al);
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_HELLO, ERR_R_INTERNAL_ERROR);
ossl_statem_set_error(s);
return 0;
}
/* 填充完数据之后就是设置头部的收尾工作了,比如长度信息*/
l = (p - d);
if (!ssl_set_handshake_header(s, SSL3_MT_SERVER_HELLO, l)) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_HELLO, ERR_R_INTERNAL_ERROR);
ossl_statem_set_error(s);
return 0;
}
return 1;
}
Server Hello就没有后续的步骤了。一个函数就可以完成填充,填充完成之后就可以发出去了。发出去的过程也会改变整个状态机的状态。使得SSL进入接收状态。
在TLS 1.2中,Server在发送了Server Hello之后是不会等待客户端的回复的。而是选择是否继续发送其他的消息,例如上面看到过是否会发送一个New Ticket消息的判断。这个判断是建立在对Session的一整套逻辑的判断的基础上的。
如果决定了复用,接下来Server就不会发送Server Key Exchange,否则Server就该发送该消息沟通密码学上下文。
//这里省略了PSK相关的逻辑。PSK是Pre Shared Key,就是双方互相提前知道了约定密码的沟通方法,一般不使用。SRP也不讨论。
int tls_construct_server_key_exchange(SSL *s)
{
#ifndef OPENSSL_NO_DH
EVP_PKEY *pkdh = NULL;
int j;
#endif
#ifndef OPENSSL_NO_EC
unsigned char *encodedPoint = NULL;
int encodedlen = 0;
int curve_id = 0;
#endif
EVP_PKEY *pkey;
const EVP_MD *md = NULL;
unsigned char *p, *d;
int al, i;
unsigned long type;
int n;
const BIGNUM *r[4];
int nr[4], kn;
BUF_MEM *buf;
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
if (md_ctx == NULL) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_R_MALLOC_FAILURE);
al = SSL_AD_INTERNAL_ERROR;
goto f_err;
}
//在处理Client Hello的时候,服务端已经选择了对应的密码学套件。这个套件里面就包含了密码学的选择。这一步是沟通密码学套件的细节,自然信息要基于选择的密码学套件
type = s->s3->tmp.new_cipher->algorithm_mkey;
//前面也有看到init_buf是用来构造待发送数据包的缓存
buf = s->init_buf;
//r是四个大数,OpenSSL密码学运算的核心是大数系统。大数是指位数远超过CPU能一个指令处理的数据长度。例如1024位长度的大数
r[0] = r[1] = r[2] = r[3] = NULL;
n = 0;
//处理DH算法的密码学参数计算,从判断中可以看到,这里包含了DHE和DHEPSK两种
#ifndef OPENSSL_NO_DH
if (type & (SSL_kDHE | SSL_kDHEPSK)) {
CERT *cert = s->cert;
EVP_PKEY *pkdhp = NULL;
//DH结构体是用于存放DH算法的核心结构体。里面包含了DH算法用的p,g,q等参数,还有算法计算过程的中间结果
DH *dh;
//由于DH算法使用的数字要求是一个大素数,OpenSSL提供了一种动态产生大素数的方法,就是下文的ssl_get_auto_dh函数。但是也提供了允许用户直接提供这个大素数的方法,也就是在dh_tmp_auto判断不通过的情况下。我们要理解清楚的是,DH结构体代表的是一个数学层面的DH算法的计算,而EVP_PKEY代表的是一个非对称加密的密码学计算的上下文。DH是EVP_PKEY的一个参数。所以后续有一步EVP_PKEY_assign_DH(pkdh, dhp);的操作来完成这个密码学计算上下文的设置。
if (s->cert->dh_tmp_auto) {
DH *dhp = ssl_get_auto_dh(s);
pkdh = EVP_PKEY_new();
if (pkdh == NULL || dhp == NULL) {
DH_free(dhp);
al = SSL_AD_INTERNAL_ERROR;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
ERR_R_INTERNAL_ERROR);
goto f_err;
}
EVP_PKEY_assign_DH(pkdh, dhp);
pkdhp = pkdh;
} else {
//如果不是动态生成,这个DH上下文就可以直接使用证书结构体里面的dh_tmp。虽然是存储在证书结构体里面,但是这个参数并不是证书的一部分。而是使用者调用OpenSSL的API在创建上下文的时候动态创建的。使用的是SSL_CTRL_SET_TMP_DH的ctrl选项。
pkdhp = cert->dh_tmp;
}
//还有一种情况是既不是动态生成,用户又没有提供,而是用户提供了用于生成大素数的回调函数。就调用这个回调函数来生成大素数。
if ((pkdhp == NULL) && (s->cert->dh_tmp_cb != NULL)) {
DH *dhp = s->cert->dh_tmp_cb(s, 0, 1024);
pkdh = ssl_dh_to_pkey(dhp);
if (pkdh == NULL) {
al = SSL_AD_INTERNAL_ERROR;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
ERR_R_INTERNAL_ERROR);
goto f_err;
}
pkdhp = pkdh;
}
if (pkdhp == NULL) {
al = SSL_AD_HANDSHAKE_FAILURE;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
SSL_R_MISSING_TMP_DH_KEY);
goto f_err;
}
//ssl_security是用来检查当前的密码学套件的安全等级的。任何的加密算法都有一个安全等级的概念。典型的就是私钥的长度太小等。在OpenSSL中,不同的加密算法的不同参数都被赋予了不同的安全等级,一个OpenSSL运行的时候是不允许低于约定等级的密码学算法被使用的。
if (!ssl_security(s, SSL_SECOP_TMP_DH,
EVP_PKEY_security_bits(pkdhp), 0, pkdhp)) {
al = SSL_AD_HANDSHAKE_FAILURE;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
SSL_R_DH_KEY_TOO_SMALL);
goto f_err;
}
if (s->s3->tmp.pkey != NULL) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
ERR_R_INTERNAL_ERROR);
goto err;
}
//pkey域是用来存放每次握手动态生成的DH/ECDH的公钥私钥对的。是通过这里生成的DH参数来生成对应的公钥私钥对。这个逻辑不是程序规定的,而是DH算法就是这样运行的。
s->s3->tmp.pkey = ssl_generate_pkey(pkdhp);
if (s->s3->tmp.pkey == NULL) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_R_EVP_LIB);
goto err;
}
//最后就是把DH算法中的p,q,g和生成的公钥私钥提取到函数的局部变量中,以待后续的使用
dh = EVP_PKEY_get0_DH(s->s3->tmp.pkey);
EVP_PKEY_free(pkdh);
pkdh = NULL;
DH_get0_pqg(dh, &r[0], NULL, &r[1]);
DH_get0_key(dh, &r[2], NULL);
} else
#endif
#ifndef OPENSSL_NO_EC
//椭圆曲线与DH并不冲突。DH是一种密钥交换算法,椭圆曲线是一种计算方法。他们可以共同组合成ECDHE这种密码交换算法。单独的DHE就是用的DH的计算算法,DH的密钥交换方法来完成密钥交换。
if (type & (SSL_kECDHE | SSL_kECDHEPSK)) {
int nid;
if (s->s3->tmp.pkey != NULL) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
ERR_R_INTERNAL_ERROR);
goto err;
}
/*nid是OpenSSL中用来组织各种各样密码学参数甚至密码学细节选项的一种身份标记,是一个非常庞大的数据库。一个OpenSSL上下文运行之后,当使用EC算法的时候,所使用的椭圆曲线的参数就是固定的了(运行之前可以配置)。例如是使用哪一条曲线,例如p-256曲线*/
nid = tls1_shared_curve(s, -2);
curve_id = tls1_ec_nid2curve_id(nid);
if (curve_id == 0) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
SSL_R_UNSUPPORTED_ELLIPTIC_CURVE);
goto err;
}
//选择了一条曲线之后,就是像DH一样开始生成临时的算法所需要的临时参数
s->s3->tmp.pkey = ssl_generate_pkey_curve(curve_id);
if (s->s3->tmp.pkey == NULL) {
al = SSL_AD_INTERNAL_ERROR;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_R_EVP_LIB);
goto f_err;
}
encodedlen = EVP_PKEY_get1_tls_encodedpoint(s->s3->tmp.pkey,
&encodedPoint);
if (encodedlen == 0) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_R_EC_LIB);
goto err;
}
n += 4 + encodedlen;
/*
* 在DH的代码模块最终提取了r对应的四个大数,但是在椭圆曲线,这四大大数会暂时置空
*/
r[0] = NULL;
r[1] = NULL;
r[2] = NULL;
r[3] = NULL;
} else
#endif /* !OPENSSL_NO_EC */
{
al = SSL_AD_HANDSHAKE_FAILURE;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE);
goto f_err;
}
for (i = 0; i < 4 && r[i] != NULL; i++) {
nr[i] = BN_num_bytes(r[i]);
#ifndef OPENSSL_NO_SRP
if ((i == 2) && (type & SSL_kSRP))
n += 1 + nr[i];
else
#endif
#ifndef OPENSSL_NO_DH
/*-
* 这一步是为了兼容windows的一些特点
*/
if ((i == 2) && (type & (SSL_kDHE | SSL_kDHEPSK)))
n += 2 + nr[0];
else
#endif
n += 2 + nr[i];
}
//加密都会有签名算法。无论是非对称还是对称的工程使用过程,签名算法都是不会缺少的
if (!(s->s3->tmp.new_cipher->algorithm_auth & (SSL_aNULL | SSL_aSRP))
&& !(s->s3->tmp.new_cipher->algorithm_mkey & SSL_PSK)) {
if ((pkey = ssl_get_sign_pkey(s, s->s3->tmp.new_cipher, &md))
== NULL) {
al = SSL_AD_DECODE_ERROR;
goto f_err;
}
kn = EVP_PKEY_size(pkey);
/* Allow space for signature algorithm */
if (SSL_USE_SIGALGS(s))
kn += 2;
/* Allow space for signature length */
kn += 2;
} else {
pkey = NULL;
kn = 0;
}
if (!BUF_MEM_grow_clean(buf, n + SSL_HM_HEADER_LENGTH(s) + kn)) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_LIB_BUF);
goto err;
}
d = p = ssl_handshake_start(s);
//之前有把各种算法计算得到的结果提取到r,这里就开始处理,主要是将这几个数据转化为网络数据用于发送出去。这几个数据就是服务器要使用Server Key Exchange发送给客户端的数据
for (i = 0; i < 4 && r[i] != NULL; i++) {
#ifndef OPENSSL_NO_DH
/*-
* for interoperability with some versions of the Microsoft TLS
* stack, we need to zero pad the DHE pub key to the same length
* as the prime
*/
if ((i == 2) && (type & (SSL_kDHE | SSL_kDHEPSK))) {
s2n(nr[0], p);
for (j = 0; j < (nr[0] - nr[2]); ++j) {
*p = 0;
++p;
}
} else
#endif
s2n(nr[i], p);
BN_bn2bin(r[i], p);
p += nr[i];
}
#ifndef OPENSSL_NO_EC
if (type & (SSL_kECDHE | SSL_kECDHEPSK)) {
/*
如果使用了椭圆曲线,OpenSSL是只支持命名曲线的。例如目前应用最广泛的p-256曲线,该曲线由于NIST的背景,不太被很多机构信任。谷歌目前在推广X25519。所以这里写入的密码学参数的方法也是写入了命名的曲线,OpenSSL也支持使用命名曲线。曲线类型,曲线数据长度和曲线的参数详细信息被顺序的写入数据包
*/
*p = NAMED_CURVE_TYPE;
p += 1;
*p = 0;
p += 1;
*p = curve_id;
p += 1;
*p = encodedlen;
p += 1;
memcpy(p, encodedPoint, encodedlen);
OPENSSL_free(encodedPoint);
encodedPoint = NULL;
p += encodedlen;
}
#endif
/* pkey如果存在就证明服务器是有一个证书的,这在大部分情况下都是成立的。服务器一般会配置一个证书*/
if (pkey != NULL) {
//哈希算法无处不在,完整性校验,确保不被改动,是安全的协议的前提。OpenSSL里面经常看到SIGALG这种简写,就是指的签名算法。服务器会在这里选择使用什么哈希算法来进行哈希计算。计算的内容包含了客户端,服务端生成的随机数还有服务端生成的密码学参数
if (md) {
if (SSL_USE_SIGALGS(s)) {
if (!tls12_get_sigandhash(p, pkey, md)) {
al = SSL_AD_INTERNAL_ERROR;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
ERR_R_INTERNAL_ERROR);
goto f_err;
}
p += 2;
}
#ifdef SSL_DEBUG
fprintf(stderr, "Using hash %s\n", EVP_MD_name(md));
#endif
if (EVP_SignInit_ex(md_ctx, md, NULL) <= 0
|| EVP_SignUpdate(md_ctx, &(s->s3->client_random[0]),
SSL3_RANDOM_SIZE) <= 0
|| EVP_SignUpdate(md_ctx, &(s->s3->server_random[0]),
SSL3_RANDOM_SIZE) <= 0
|| EVP_SignUpdate(md_ctx, d, n) <= 0
|| EVP_SignFinal(md_ctx, &(p[2]),
(unsigned int *)&i, pkey) <= 0) {
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_LIB_EVP);
al = SSL_AD_INTERNAL_ERROR;
goto f_err;
}
s2n(i, p);
n += i + 2;
if (SSL_USE_SIGALGS(s))
n += 2;
} else {
/* Is this error check actually needed? */
al = SSL_AD_HANDSHAKE_FAILURE;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE,
SSL_R_UNKNOWN_PKEY_TYPE);
goto f_err;
}
}
if (!ssl_set_handshake_header(s, SSL3_MT_SERVER_KEY_EXCHANGE, n)) {
al = SSL_AD_HANDSHAKE_FAILURE;
SSLerr(SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE, ERR_R_INTERNAL_ERROR);
goto f_err;
}
EVP_MD_CTX_free(md_ctx);
return 1;
f_err:
ssl3_send_alert(s, SSL3_AL_FATAL, al);
err:
#ifndef OPENSSL_NO_DH
EVP_PKEY_free(pkdh);
#endif
#ifndef OPENSSL_NO_EC
OPENSSL_free(encodedPoint);
#endif
EVP_MD_CTX_free(md_ctx);
ossl_statem_set_error(s);
return 0;
}
OpenSSL在发送Servert Key Exchange的过程就是一个按照协商的密码学套件,生成密码学参数,然后写入数据包的过程。这里有一个很重要的哈希操作,将本次通信用到的密码学上下文涉及到的关键密码学参数都进行了哈希。确保信道不会被篡改。
如果抓包的话也会看到这段哈希的结果。单纯的看抓包的结果很难理解这段哈希的意义。实际上,这段哈希就是对整个信道描述的哈希。如果不是哈希,而是直接传输参与哈希的几个参数,就能直接还原出这个信道。
TLS 1.2里面最神奇的一步就是Change Cipher。这一步不属于握手的流程数据包,但是代表了一个程序内部的状态。我们看到OpenSSL虽然产生了密码学的上下文,但是至此也并没有在本地进行一个密码学上下文的构造。而按照标准,这个时候服务端就应该要做这件事情了。
OpenSSL里有两个文件是根握手的过程相关性很高的。一个是s3_lib.c,一个是t1_lib.c。从名字上看,s3和t1分别指的是不同的TLS版本的库函数的实现,但是实际上并不是如此。双方提供的功能更多的是互补的。用来区别SSL和TLS不同版本的实现是在s3_enc.c和t1_enc.c中的。
在t1_lib.c中,我们能看到刚才的构造Server Hello扩展的时候使用的ssl_add_serverhello_tlsext函数。在处理客户端Client Hello时候用到的解析扩展的ssl_parse_clienthello_tlsext函数。在生成密码学参数的哈希键值的时候,我们要用到一个被提前设置的哈希,这个设置哈希的函数是tls1_set_server_sigalgs,也位于t1_lib.c。
这些扩展处理位于t1_lib.c,而通用的功能的处理就位于s3_lib.c了。这下就可以想明白这样命名的具体含义了。t1_lib.c中存放的是与s3_lib.c中互补的握手用到的库函数,但是这些函数所对应的功能都是在TLS 1.0之后才有的。
一个握手过程的库函数大部分在SSLv3时代就已经成型了。例如对密码学套件列表进行排序的函数ssl_sort_cipher_list,往数据包写入数据的函数ssl3_handshake_write,从客户端提供的密码学套件列表中选择密码学套件的函数ssl3_choose_cipher,填充随机数的函数ssl_fill_hello_random等等。虽然在新的版本与SSLv3的内容会不一样,但是新版本的新内容在已有函数的情况下,都会选择在s3_lib.c中直接修改添加。所以你也会在s3_lib.c中看到很多TLS 1.2相关的内容。OpenSSL的历史包袱可见一斑。
握手的过程中,最重要的函数大类是密码学相关的函数。如何通过非对称加密协商得到的数据来生成对称加密的信道,才是最麻烦的事情。这一部分的函数都是位于s3_enc.c和t1_enc.c中。这两个文件的函数名都几乎是一样的,就是不同协议版本的同一个协议过程的不同的实现方法。
可以看到两个文件的最大的一个区别就是TLS是拥有PRF算法的,而SSL3没有。可以看到这两个文件的核心思想是几个密码学的关键字:key block,finish mac, master secret。
ssl3_generate_key_block的代码比较长,而TLS的代码很简单。
static int tls1_generate_key_block(SSL *s, unsigned char *km, int num)
{
int ret;
ret = tls1_PRF(s,
TLS_MD_KEY_EXPANSION_CONST,
TLS_MD_KEY_EXPANSION_CONST_SIZE, s->s3->server_random,
SSL3_RANDOM_SIZE, s->s3->client_random, SSL3_RANDOM_SIZE,
NULL, 0, NULL, 0, s->session->master_key,
s->session->master_key_length, km, num);
return ret;
}
因为TLS用了PRF算法。我们看PRF算法的输入就知道如何产生这个Key Block。有客户端和服务器的随机数,然后是Master Key(Master Key和Master Secret指的是一个东西)。km是结果的存储地址,num是结果的长度。
所以问题的关键就变成Master Key的意义是什么。同一个文件下的tls1_generate_master_secret函数就是用来生成Master Key的。因为服务端只有在收到了Client 的Key Exchange消息之后才有可能进行对称加密上下文的生成。所以虽然服务端会先发送Change Cipher的消息到客户端,但是实际的对称加密上下文也还是要等到Client的消息发送完才会有。
OpenSSL中在握手的过程中收到的和接收的所有的数据包都会调用ssl3_finish_mac函数,这个函数如下:
int ssl3_finish_mac(SSL *s, const unsigned char *buf, int len)
{
if (s->s3->handshake_dgst == NULL)
return BIO_write(s->s3->handshake_buffer, (void *)buf, len) == len;
else
return EVP_DigestUpdate(s->s3->handshake_dgst, buf, len);
}
这个函数的作用是将握手过程的数据包写到s->s3->handshake_buffer中。每一个读写的数据包最后都要参加最终的哈希计算,要确保数据包没有被修改。这个写入的数据包还有一个非常重要的功能就是在TLS版本的握手的时候用来生成key(SSLv3的时候不一样)。
这里TLS是直接调用了SSLv3时代的函数ssl3_digest_cached_records来生成Master Key,而SSLv3的时候却是有另外的计算方案,这个函数只是用来做整个握手的完整性校验。TLS采用这种方案肯定是在发展的过程中发现了什么。无论是是SSLv3还是TLS,产生这个最重要的中间参数的函数入口都是ssl_generate_master_secret函数,这个函数位于ssl3_lib.c中。从这个函数与文件的从属关系中,可以慢慢地体会到OpenSSL发展的过程中的一系列的变化。所以看OpenSSL要用发展的眼光去看,而不是用一个静态的架构层面的概念去审视。从发展的层面看,作为一个发展了二十年的软件,OpenSSL能够如此,已经是非常的不容易。这个生成Master Key的函数由于在不同的版本中是不同的选择,所以也就肯定存在一个方法表。
typedef struct ssl3_enc_method {
int (*enc) (SSL *, SSL3_RECORD *, unsigned int, int);
int (*mac) (SSL *, SSL3_RECORD *, unsigned char *, int);
int (*setup_key_block) (SSL *);
int (*generate_master_secret) (SSL *, unsigned char *, unsigned char *, int);
int (*change_cipher_state) (SSL *, int);
int (*final_finish_mac) (SSL *, const char *, int, unsigned char *);
int finish_mac_length;
const char *client_finished_label;
int client_finished_label_len;
const char *server_finished_label;
int server_finished_label_len;
int (*alert_value) (int);
int (*export_keying_material) (SSL *, unsigned char *, size_t,
const char *, size_t,
const unsigned char *, size_t,
int use_context);
uint32_t enc_flags;
unsigned int hhlen;
int (*set_handshake_header) (SSL *s, int type, unsigned long len);
int (*do_write) (SSL *s);
} SSL3_ENC_METHOD;
这个方法表中的大部分函数我们都看到过了。有的是通用的外层函数,大部分的都是分别位于s3_enc.c和t1_enc.c中的版本相关的函数。这就是不同协议的握手过程的方法表的封装。从中,我们看到的目前遇到的两个关键的值的函数,一个是Master Key,一个是Key Block,Key Block又是从Master Key中生成的。接下来Master Key的生成方法会非常让人吃惊。