SSL_CTX_new: 分配并初始化SSL_CTX结构,
1 很重要的就是load cipher_list
2 设置ssl_session timeout时间默认为7200秒
3 初始化client_CA STACK
4 初始化EVP_MD, rsa_md5, md5, sha1
5 初始化ex_data.
6 初始化comp_methods
重点解析load cipher_list, 因为在替换engine时, 要指定我们支持的算法;
关键函数: ssl_create_cipher_list
预设EVP_CIPHER算法
ssl_cipher_methods - 全局空数组, 元素类型: EVP_CIPHER *, 容量:9
0, 1, 2 3 4, 7, 8 - 只用到了7个
7 - SN_aes_128_cbc
8 - SN_aes_256_cbc
预设EVP_MD算法
ssl_digest_methods - 全局空数组, 元素类型: EVP_MD *, 容量:2
0 - sm_md5
1 - SN_sha1(fmcpc supported)
所有evp_cipher和evp_md是放在一个hash列表中, 根据名称检索得到的
根据一个static int init_ciphers全局标志, 来决定是否load_cipher_list
其过程用CRYPTO_w_lock进行lock, 线程安全
完了后, 会调用: ssl_cipher_get_disabled
首先是硬设置一些被disable的算法:
默认编译下:
10000000101000
SSL_kFZA - 0x8 | SSL_kKRB5 - 0x20 |SSL_aKRB5 - 0x2000
然后检测ssl_cipher_methods和ssl_digest_methods表中, 哪些指针为空, 如果为
空, 将该算法对应的disable mask位设置为1
SSL_eFZA - 0x100000
最终mask == 100000010000000101000 - 0x00102028
如果我们强行改ssl代码, 应该可以首先disable其他算法, 只保留
AES-SHA1, 但这种方法视乎不妥.
然后获取num_of_ciphers, 这个num_of_ciphers非常简单, 就是一个全局的
sizeof(ssl3_ciphers)/sizeof(SSL_CIPHER)
ssl3_ciphers在s3_lib.c中定义, 全局, 定义时初始化. 原属为SSL_CIPHER
SSL_CIPHER 的每个算法指定了:
密钥交换算法, 非对称算法, 对称算法及模式, hash算法, 如:
TLS1_TXT_RSA_WITH_AES_128_SHA/TLS1_TXT_DH_RSA_WITH_AES_128_SHA
加密强度, used(前面是disable)掩码, 算法位数等;
这里得到的num_of_cipher长度为0x48(72)个.
然后将预定义的SSL_CIPHERS从全局数组中取出来, 和disable_mask按位比较, 去掉不适用的算法.
算法被放到一个CIPHER_ORDER结构中, 有个active标志, 应该特别注意, 这个标志初始化被置为0
后面还要根据rulestr, strength, mask来决定, 哪些算法应该保留, 保留的被放到cipherstack中
这个过程看起来比较复杂, 没有仔细解读.
这里还应该注意一个宏: KSSL_DEBUG, 如果在nt_dll.mak中定义该宏, 应该所有算法会被打印出来.
这是OpenSSL作者自己定义的宏.
实际上, 被CIPHER_ORDER数组的算法有0x3c(60)个(很多了). CIPHER_ORDER内部有个prev和next
在加入完成后, 按照顺序排成一个双向链表, 并返回头和尾的指针.
最后有1B(27)个算法被选中, 与我们dump出来的server_cipher_list的个数相同. 算法列表:
DHE-RSA-AES256-SHA
DHE-DSS-AES256-SHA
AES256-SHA <<<<<<<<<<< 调试后发现使用的这个算法.
EDH-RSA-DES-CBC3-SHA
EDH-DSS-DES-CBC3-SHA
DES-CBC3-SHA
DHE-RSA-AES128-SHA
DHE-DSS-AES128-SHA
AES128-SHA >>>>>>>>>>>>> 因为加密卡只支持这两种算法, 所以, 这里应该用这个.
IDEA-CBC-SHA
DHE-DSS-RC4-SHA
RC4-SHA
RC4-MD5
EXP1024-DHE-DSS-DES-CBC-SHA
EXP1024-DES-CBC-SHA
EXP1024-RC2-CBC-MD5
EDH-RSA-DES-CBC-SHA
EDH-DSS-DES-CBC-SHA
DES-CBC-SHA
EXP1024-DHE-DSS-RC4-SHA
EXP1024-RC4-SHA
EXP1024-RC4-MD5
EXP-EDH-RSA-DES-CBC-SHA
EXP-EDH-DSS-DES-CBC-SHA
EXP-DES-CBC-SHA
EXP-RC2-CBC-MD5
EXP-RC4-MD5
SHA - 是指用SHA1
AES128 - 使用的模式是CBC
这里有两种算法必须支持:
NID_md5 和 NID_sha1必须支持, 不能屏蔽掉, 否则会报错.
/////////////////////////////////////////////
接下来是使用client证书
//int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type)
SSL_CTX_use_certificate_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM)
返回值, <= 0表示错误.
过程:
1 读取client证书, 证书存放格式: pem (der编码的BASE64表示)
PEM_read_bio_X509, 解析为X509格式
2 SSL_CTX_use_certificate
2.1 ssl_set_cert
证书中, pub_key存放位置: X509::cert_info::key, 格式:X509_PUBKEY
可以使用函数: X509_PUBKEY_get获得, 返回EVP_KEY, 比较关键了.
(key = x->cert_info->key)
type=OBJ_obj2nid(key->algor->algorithm); type:6, 对应NID_rsaEncryption
key->algor->algorithm是NID_rsaEncryption对应的OBJECT DER编码
然后调用EVP_PKEY_new, 这些都调不到engine中. go on -->
der = key->public_key->data存放的是rsa public key的der编码.
length = key->public_key->length
调用: d2i_PublicKey(type, &ret, &p, (long)j), ret为EVP_KEY类型
ret->save_type=type; // ret类型EVP_KEY
ret->type=EVP_PKEY_type(type);
这里直接ret->pkey.rsa=d2i_RSAPublicKey(NULL, der,length);
// 从这里看, 已经得到EVP_KEY了, 但并没有调用与Engine相关的函数
然后将得到EVP_KEY指针存放在x->cert_info->key->pkey中, 并返回该指针
这里我们记录下整个x509结构的内容:
- x 0x019e8cb8
- cert_info 0x019e8d30
+ version 0x019e7b30
+ serialNumber 0x019e8d70
+ signature 0x007c9798
+ issuer 0x019e8d98
+ validity 0x007c4070
+ subject 0x019e7980
- key 0x019e79d0
+ algor 0x019e79f8
+ public_key 0x019e7a18
- pkey 0x019e8ee8
type 0x00000006
save_type 0x00000006
references 0x00000002
- pkey {...}
+ ptr 0x019e8f18 ""
- rsa 0x019e8f18
pad 0x00000000
version 0x00000000
+ meth 0x0063e958 rsa_pkcs1_eay_meth
engine 0x00000000
+ n 0x019e9008
+ e 0x019e90d8
+ d 0x00000000
+ p 0x00000000
+ q 0x00000000
+ dmp1 0x00000000
+ dmq1 0x00000000
+ iqmp 0x00000000
- ex_data {...}
+ sk 0x00000000
dummy 0xbaadf00d
references 0x00000001
flags 0x00000006
+ _method_mod_n 0x00000000
+ _method_mod_p 0x00000000
+ _method_mod_q 0x00000000
+ bignum_data 0x00000000 ""
blinding 0x00000000
mt_blinding 0x00000000
+ dsa 0x019e8f18
+ dh 0x019e8f18
ec 0x019e8f18
save_parameters 0x00000001
+ attributes 0x00000000
+ issuerUID 0x00000000
+ subjectUID 0x00000000
+ extensions 0x019ea208
+ sig_alg 0x019e7a40
+ signature 0x019e7a60
valid 0x00000000
references 0x00000001
+ name 0x019ea868 "/C=CN/ST=Chongqing/O=YZ/OU=YZ/CN=sslsocketclient/emailAddress=sslsocketclient@yunzhen.com"
+ ex_data {...}
ex_pathlen 0xffffffff
ex_pcpathlen 0x00000000
ex_flags 0x00000000
ex_kusage 0x00000000
ex_xkusage 0x00000000
ex_nscert 0x00000000
+ skid 0x00000000
+ akid 0x00000000
policy_cache 0x00000000
+ sha1_hash 0x019e8cfc ""
+ aux 0x00000000
可以看到, evp_pkey中的rsa的结构, 只有n, e, 没有私钥的部分. 合理, 构造只具有公钥部分的rsa完全没问题.
然后会检测证书中是否包含有私钥, 如果有私钥, 会将私钥部分拷贝到x->cert_info->key->pkey中.
在使用硬件加密卡时, 证书存放在加密卡的存储器中, 证书一般不包含私钥, key pair另外存放在保密区域.
所以, 这里不会出现这种情况. 这里说一下https通信, 我们在生成测试证书来搭建https通信时, 会
将私钥捆绑在个人证书上, 然后导入人证书存放区域, 靠密码保护, 与此不同. (没有研究使用usb key的情况)
因为ms的证书管理器没有单独导入key pair的地方(可以运行certmgr来看一下, 确实没有单独管理私钥的地方).
https通信时, 一般需要用到P12格式的个人证书, 这个P12个人证书是将私钥与X509证书捆绑在一起的.
openssl参考命令: openssl pkcs12 -export -clcerts -in crunch.cer -inkey crunchkey.pem -out crunch.p12
然后会执行EVP_KEY_free, 把我吓一跳, 怎么会就free了呢, 才想起前面在返回EVP_PKEY前, EVP_PKEY
的引用计数被加了一次. 原来这里只是减少引用计数而已;
然后将cert->pkeys[i == 0].x509 = 传入的x509证书
最后将cert->key指向&pkeys[i == 0].
对了, 这个i是ssl_cert_type获得的结果, 如果是rsa证书, i为0, dsa对应2, ecc对应5, 其他的有错误
////////////////////////////////////////////
接下来是需要调用到SSL_CTX_use_PrivateKey, 前面看到因为在加载证书时, 因为证书上没有捆绑私钥, 所以
这里要单独执行SSL_CTX_use_PrivateKey.
首先是调用PEM_read_bio_PrivateKey读取私钥证书. 然后再调用SSL_CTX_use_PrivateKey.
key可能是PEM格式的, 也可能是直接存为DER格式的, 都可以, 只是读取key文件的函数要变一下;
SSL_CTX_use_PrivateKey(SSL_CTX*, EVP_KEY*);
SSL_CTX_use_PrivateKey内部主要调用ssl_set_pkey(CERT*, EVP_KEY*);
在调用X509_get_pubkey, 获得的是前面提到的cert->pkes[i== 0].x509->cert_info->key->pkey
然后调用EVP_PKEY_copy_parameters, 这个函数误导我了, 以为这里就是拷贝私钥部分, 只有当私钥type类型
为EVP_PKEY_EC或EVP_PKEY_DSA时, 这个函数才拷贝私钥部分; RSA类型的在后面.
读取的privatekey实际上是一个keypair, 直接将其赋值给了cert->pkeys[i == 0].privatekey.
而在校验pubkey和privatekey时, 实际上是比较keypair中的公钥部分与pubkey中的公钥部分是否相等.
OK, SSL连接的初始化工作已经做完了. 下面开始socket连接, 接下来就是SSL的握手部分了.