工做中须要创建一套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 发送数据。