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的握手部分了.