[原]OpenSSL SSL連接初始化部分解析



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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM