工做中須要創建一套HSM的HTTPS雙向認證通道,即經過硬件加密機(Ukey)進行本地加密運算的HTTPS雙向認證,和銀行的UKEY認證相似。node
NodeJS能夠利用openSSL的HSM plugin方式實現,可是須要編譯C++,太麻煩,做者採用了利用Node Socket接口,純JS自行實現Https/Http協議的方式實現git
具體實現能夠參考以下node-https-hsmgithub
TLS規範天然是參考RFC文檔The Transport Layer Security (TLS) Protocol Version 1.2算法
本次TLS雙向認證支持如下加密套件(*爲建議使用套件):bash
四種加密套件流程徹底一致,只是部分算法細節與報文略有差別,體如今服務器
目前業界推薦使用TLS v1.2, TLS v1.1不建議使用。session
如下爲 TLS 完整握手流程圖dom
* =======================FULL HANDSHAKE======================
* Client Server
*
* ClientHello -------->
* ServerHello
* Certificate
* CertificateRequest
* <-------- ServerHelloDone
* Certificate
* ClientKeyExchange
* CertificateVerify
* Finished -------->
* change_cipher_spec
* <-------- Finished
* Application Data <-------> Application Data
複製代碼
TLS握手始於客戶端發起 ClientHello 請求。ui
struct {
uint32 gmt_unix_time; // UNIX 32-bit format, UTC時間
opaque random_bytes[28]; // 28位長度隨機數
} Random; //隨機數
struct {
ProtocolVersion client_version; // 支持的最高版本的TLS版本
Random random; // 上述隨機數
SessionID session_id; // 會話ID,新會話爲空
CipherSuite cipher_suites<2..2^16-2>; // 客戶端支持的全部加密套件,上述四種
CompressionMethod compression_methods<1..2^8-1>; // 壓縮算法
select (extensions_present) { // 額外插件,爲空
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello; // 客戶端發送支持的TLS版本、客戶端隨機數、支持的加密套件等信息
複製代碼
服務器端收到 ClientHello 後,若是支持客戶端的TLS版本和算法要求,則返回 ServerHello, Certificate, CertificateRequest, ServerHelloDone 報文加密
struct {
ProtocolVersion server_version; // 服務端最後決定使用的TLS版本
Random random; // 與客戶端隨機數算法相同,可是必須是獨立生成,與客戶端毫無關聯
SessionID session_id; // 肯定的會話ID
CipherSuite cipher_suite; // 最終決定的加密套件
CompressionMethod compression_method; // 最終使用的壓縮算法
select (extensions_present) { // 額外插件,爲空
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ServerHello; // 服務器端返回最終決定的TLS版本,算法,會話ID和服務器隨機數等信息
struct {
ASN.1Cert certificate_list<0..2^24-1>; // 服務器證書信息
} Certificate; // 向客戶端發送服務器證書
struct {
ClientCertificateType certificate_types<1..2^8-1>; // 證書類型,本次握手爲 值固定爲rsa_sign
SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>; // 支持的HASH 簽名算法
DistinguishedName certificate_authorities<0..2^16-1>; // 服務器能承認的CA證書的Subject列表
} CertificateRequest; // 本次握手爲雙向認證,此報文表示請求客戶端發送客戶端證書
struct {
} ServerHelloDone // 標記服務器數據末尾,無內容
複製代碼
客戶端應校驗服務器端證書,一般用當用本地存儲的可信任CA證書校驗,若是校驗經過,客戶端將返回 Certificate, ClientKeyExchange, CertificateVerify, change_cipher_spec, Finished 報文。
CertificateVerify 報文中的簽名爲Ukey硬件簽名
, 此外客戶端證書也是從Ukey讀取。
struct {
ASN.1Cert certificate_list<0..2^24-1>; // 服務器證書信息
} Certificate; // 向服務器端發送客戶端證書
struct {
select (KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret; // 服務器採用RSA算法,用服務器端證書的公鑰,加密客戶端生成的46字節隨機數(premaster secret)
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;
} exchange_keys;
} ClientKeyExchange; // 用於返回加密的客戶端生成的隨機密鑰(premaster secret)
struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length]; // 採用客戶端RSA私鑰,對以前全部的握手報文數據,HASH後進行RSA簽名
}
} CertificateVerify; // 用於服務器端校驗客戶端對客戶端證書的全部權
struct {
enum { change_cipher_spec(1), (255) } type; // 固定值0x01
} ChangeCipherSpec; // 通知服務器後續報文爲密文
struct {
opaque verify_data[verify_data_length]; // 校驗密文,算法PRF(master_secret, 'client finished', Hash(handshake_messages))
} Finished; // 密文信息,計算以前全部收到和發送的信息(handshake_messages)的摘要,加上`client finished`, 執行PRF算法
複製代碼
Finished 報文生成過程當中,將產生會話密鑰 master secret,而後生成Finish報文內容。
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)
verify_data = PRF(master_secret, 'client finished', Hash(handshake_messages))
複製代碼
PRF爲TLS v1.2規定的僞隨機算法, 此例子中,HMAC算法爲 SHA256
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...
// A(0) = seed
// A(i) = HMAC_hash(secret, A(i-1))
複製代碼
服務收到請求後,首先校驗客戶端證書的合法性,而且驗證客戶端證書籤名是否合法。根據服務器端證書私鑰,解密 ClientKeyExchange,得到pre_master_secret, 用相同的PRF算法便可獲取會話密鑰,校驗客戶端 Finish 信息是否正確。若是正確,則服務器端與客戶端完成密鑰交換。 返回 change_cipher_spec, Finished 報文。
struct {
enum { change_cipher_spec(1), (255) } type; // 固定值0x01
} ChangeCipherSpec; // 通知服務器後續報文爲密文
struct {
opaque verify_data[verify_data_length]; // 校驗密文,算法PRF(master_secret, 'server finished', Hash(handshake_messages))
} Finished; // 密文信息,計算以前全部收到和發送的信息(handshake_messages)的摘要,加上`server finished`, 執行PRF算法
複製代碼
客戶端校驗服務器的Finished報文合法後,握手完成,後續用 master_secret 發送數據。