详解NodeJS Https HSM双向认证实现
工作中需要建立一套HSM的HTTPS双向认证通道,即通过硬件加密机(Ukey)进行本地加密运算的HTTPS双向认证,和银行的UKEY认证类似。
NodeJS可以利用openSSL的HSMplugin方式实现,但是需要编译C++,太麻烦,作者采用了利用NodeSocket接口,纯JS自行实现Https/Http协议的方式实现
具体实现可以参考如下node-https-hsm
TLS规范自然是参考RFC文档TheTransportLayerSecurity(TLS)ProtocolVersion1.2
概述
本次TLS双向认证支持以下加密套件(*为建议使用套件):
- TLS_RSA_WITH_AES_128_CBC_SHA256(TLSv1.2)*
- TLS_RSA_WITH_AES_256_CBC_SHA256(TLSv1.2)*
- TLS_RSA_WITH_AES_128_CBC_SHA(TLSv1.1)
- TLS_RSA_WITH_AES_256_CBC_SHA(TLSv1.1)
四种加密套件流程完全一致,只是部分算法细节与报文略有差异,体现在
- AES_128/AES_256的会话AES密钥长度分别为16/32字节。
- TLS1.1在计算finish报文数据时,进行的是MD5+SHA1的HASH算法,而在TLSv1.2下,HASH算法变成了单次SHA256。
- TLS1.1处理finish报文时的伪随机算法(PRF)需要将种子数据为分两块,分别用MD5/SHA1取HASH后异或,TLS1.2为单次SHA256。
- TLS1.2的CertificateVerify/ServerKeyExchange报文末尾新增2个字节的SignatureHashAlgorithm,表示hash_alg和sign_alg。
目前业界推荐使用TLSv1.2,TLSv1.1不建议使用。
流程图
以下为TLS完整握手流程图
*=======================FULLHANDSHAKE====================== *ClientServer * *ClientHello--------> *ServerHello *Certificate *CertificateRequest *<--------ServerHelloDone *Certificate *ClientKeyExchange *CertificateVerify *Finished--------> *change_cipher_spec *<--------Finished *ApplicationData<------->ApplicationData
流程详解
客户端发起握手请求
TLS握手始于客户端发起ClientHello请求。
struct{ uint32gmt_unix_time;//UNIX32-bitformat,UTC时间 opaquerandom_bytes[28];//28位长度随机数 }Random;//随机数 struct{ ProtocolVersionclient_version;//支持的最高版本的TLS版本 Randomrandom;//上述随机数 SessionIDsession_id;//会话ID,新会话为空 CipherSuitecipher_suites<2..2^16-2>;//客户端支持的所有加密套件,上述四种 CompressionMethodcompression_methods<1..2^8-1>;//压缩算法 select(extensions_present){//额外插件,为空 casefalse: struct{}; casetrue: Extensionextensions<0..2^16-1>; }; }ClientHello;//客户端发送支持的TLS版本、客户端随机数、支持的加密套件等信息
服务器端回应客户端握手请求
服务器端收到ClientHello后,如果支持客户端的TLS版本和算法要求,则返回ServerHello,Certificate,CertificateRequest,ServerHelloDone报文
struct{ ProtocolVersionserver_version;//服务端最后决定使用的TLS版本 Randomrandom;//与客户端随机数算法相同,但是必须是独立生成,与客户端毫无关联 SessionIDsession_id;//确定的会话ID CipherSuitecipher_suite;//最终决定的加密套件 CompressionMethodcompression_method;//最终使用的压缩算法 select(extensions_present){//额外插件,为空 casefalse: struct{}; casetrue: Extensionextensions<0..2^16-1>; }; }ServerHello;//服务器端返回最终决定的TLS版本,算法,会话ID和服务器随机数等信息 struct{ ASN.1Certcertificate_list<0..2^24-1>;//服务器证书信息 }Certificate;//向客户端发送服务器证书 struct{ ClientCertificateTypecertificate_types<1..2^8-1>;//证书类型,本次握手为值固定为rsa_sign SignatureAndHashAlgorithmsupported_signature_algorithms<2^16-1>;//支持的HASH签名算法 DistinguishedNamecertificate_authorities<0..2^16-1>;//服务器能认可的CA证书的Subject列表 }CertificateRequest;//本次握手为双向认证,此报文表示请求客户端发送客户端证书 struct{ }ServerHelloDone//标记服务器数据末尾,无内容
客户端收到服务器后响应
客户端应校验服务器端证书,通常用当用本地存储的可信任CA证书校验,如果校验通过,客户端将返回Certificate,ClientKeyExchange,CertificateVerify,change_cipher_spec,Finished报文。
CertificateVerify报文中的签名为Ukey硬件签名,此外客户端证书也是从Ukey读取。
struct{ ASN.1Certcertificate_list<0..2^24-1>;//服务器证书信息 }Certificate;//向服务器端发送客户端证书 struct{ select(KeyExchangeAlgorithm){ casersa: EncryptedPreMasterSecret;//服务器采用RSA算法,用服务器端证书的公钥,加密客户端生成的46字节随机数(premastersecret) casedhe_dss: casedhe_rsa: casedh_dss: casedh_rsa: casedh_anon: ClientDiffieHellmanPublic; }exchange_keys; }ClientKeyExchange;//用于返回加密的客户端生成的随机密钥(premastersecret) struct{ digitally-signedstruct{ opaquehandshake_messages[handshake_messages_length];//采用客户端RSA私钥,对之前所有的握手报文数据,HASH后进行RSA签名 } }CertificateVerify;//用于服务器端校验客户端对客户端证书的所有权 struct{ enum{change_cipher_spec(1),(255)}type;//固定值0x01 }ChangeCipherSpec;//通知服务器后续报文为密文 struct{ opaqueverify_data[verify_data_length];//校验密文,算法PRF(master_secret,'clientfinished',Hash(handshake_messages)) }Finished;//密文信息,计算之前所有收到和发送的信息(handshake_messages)的摘要,加上`clientfinished`,执行PRF算法
Finished报文生成过程中,将产生会话密钥mastersecret,然后生成Finish报文内容。
master_secret=PRF(pre_master_secret,"mastersecret",ClientHello.random+ServerHello.random) verify_data=PRF(master_secret,'clientfinished',Hash(handshake_messages))
PRF为TLSv1.2规定的伪随机算法,此例子中,HMAC算法为SHA256
PRF(secret,label,seed)=P_(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{ opaqueverify_data[verify_data_length];//校验密文,算法PRF(master_secret,'serverfinished',Hash(handshake_messages)) }Finished;//密文信息,计算之前所有收到和发送的信息(handshake_messages)的摘要,加上`serverfinished`,执行PRF算法
客户端会话开始
客户端校验服务器的Finished报文合法后,握手完成,后续用master_secret发送数据。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。