经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Node.js » 查看文章
详解NodeJS Https HSM双向认证实现
来源:jb51  时间:2019/3/13 8:38:11  对本文有异议

工作中需要建立一套HSM的HTTPS双向认证通道,即通过硬件加密机(Ukey)进行本地加密运算的HTTPS双向认证,和银行的UKEY认证类似。

NodeJS可以利用openSSL的HSM plugin方式实现,但是需要编译C++,太麻烦,作者采用了利用Node Socket接口,纯JS自行实现Https/Http协议的方式实现

具体实现可以参考如下 node-https-hsm

TLS规范自然是参考RFC文档 The Transport Layer Security (TLS) Protocol Version 1.2

概述

本次TLS双向认证支持以下加密套件(*为建议使用套件):

  • TLS_RSA_WITH_AES_128_CBC_SHA256(TLS v1.2) *
  • TLS_RSA_WITH_AES_256_CBC_SHA256(TLS v1.2) *
  • TLS_RSA_WITH_AES_128_CBC_SHA(TLS v1.1)
  • TLS_RSA_WITH_AES_256_CBC_SHA(TLS v1.1)

四种加密套件流程完全一致,只是部分算法细节与报文略有差异,体现在

  • AES_128/AES_256的会话AES密钥长度分别为16/32字节。
  • TLS 1.1 在计算finish报文数据时,进行的是MD5 + SHA1的HASH算法,而在TLS v1.2下,HASH算法变成了单次SHA256。
  • TLS 1.1 处理finish报文时的伪随机算法(PRF)需要将种子数据为分两块,分别用 MD5 / SHA1 取HASH后异或,TLS 1.2 为单次 SHA256。
  • TLS 1.2 的 CertificateVerify / ServerKeyExchange 报文末尾新增2个字节的 Signature Hash Algorithm,表示 hash_alg 和 sign_alg。

目前业界推荐使用TLS v1.2, TLS v1.1不建议使用。

流程图

以下为 TLS 完整握手流程图

  1. * =======================FULL HANDSHAKE======================
  2. * Client Server
  3. *
  4. * ClientHello -------->
  5. * ServerHello
  6. * Certificate
  7. * CertificateRequest
  8. * <-------- ServerHelloDone
  9. * Certificate
  10. * ClientKeyExchange
  11. * CertificateVerify
  12. * Finished -------->
  13. * change_cipher_spec
  14. * <-------- Finished
  15. * Application Data <-------> Application Data
  16.  

流程详解

客户端发起握手请求

TLS握手始于客户端发起 ClientHello 请求。

  1. struct {
  2. uint32 gmt_unix_time; // UNIX 32-bit format, UTC时间
  3. opaque random_bytes[28]; // 28位长度随机数
  4. } Random; //随机数
  5.  
  6. struct {
  7. ProtocolVersion client_version; // 支持的最高版本的TLS版本
  8. Random random; // 上述随机数
  9. SessionID session_id; // 会话ID,新会话为空
  10. CipherSuite cipher_suites<2..2^16-2>; // 客户端支持的所有加密套件,上述四种
  11. CompressionMethod compression_methods<1..2^8-1>; // 压缩算法
  12. select (extensions_present) { // 额外插件,为空
  13. case false:
  14. struct {};
  15. case true:
  16. Extension extensions<0..2^16-1>;
  17. };
  18. } ClientHello; // 客户端发送支持的TLS版本、客户端随机数、支持的加密套件等信息
  19.  

服务器端回应客户端握手请求

服务器端收到 ClientHello 后,如果支持客户端的TLS版本和算法要求,则返回 ServerHello, Certificate, CertificateRequest, ServerHelloDone 报文

  1. struct {
  2. ProtocolVersion server_version; // 服务端最后决定使用的TLS版本
  3. Random random; // 与客户端随机数算法相同,但是必须是独立生成,与客户端毫无关联
  4. SessionID session_id; // 确定的会话ID
  5. CipherSuite cipher_suite; // 最终决定的加密套件
  6. CompressionMethod compression_method; // 最终使用的压缩算法
  7. select (extensions_present) { // 额外插件,为空
  8. case false:
  9. struct {};
  10. case true:
  11. Extension extensions<0..2^16-1>;
  12. };
  13. } ServerHello; // 服务器端返回最终决定的TLS版本,算法,会话ID和服务器随机数等信息
  14.  
  15. struct {
  16. ASN.1Cert certificate_list<0..2^24-1>; // 服务器证书信息
  17. } Certificate; // 向客户端发送服务器证书
  18.  
  19. struct {
  20. ClientCertificateType certificate_types<1..2^8-1>; // 证书类型,本次握手为 值固定为rsa_sign
  21. SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>; // 支持的HASH 签名算法
  22. DistinguishedName certificate_authorities<0..2^16-1>; // 服务器能认可的CA证书的Subject列表
  23. } CertificateRequest; // 本次握手为双向认证,此报文表示请求客户端发送客户端证书
  24.  
  25. struct {
  26.  
  27. } ServerHelloDone // 标记服务器数据末尾,无内容
  28.  

客户端收到服务器后响应

客户端应校验服务器端证书,通常用当用本地存储的可信任CA证书校验,如果校验通过,客户端将返回 Certificate, ClientKeyExchange, CertificateVerify, change_cipher_spec, Finished 报文。

CertificateVerify 报文中的签名为 Ukey硬件签名 , 此外客户端证书也是从Ukey读取。

  1. struct {
  2. ASN.1Cert certificate_list<0..2^24-1>; // 服务器证书信息
  3. } Certificate; // 向服务器端发送客户端证书
  4.  
  5. struct {
  6. select (KeyExchangeAlgorithm) {
  7. case rsa:
  8. EncryptedPreMasterSecret; // 服务器采用RSA算法,用服务器端证书的公钥,加密客户端生成的46字节随机数(premaster secret)
  9. case dhe_dss:
  10. case dhe_rsa:
  11. case dh_dss:
  12. case dh_rsa:
  13. case dh_anon:
  14. ClientDiffieHellmanPublic;
  15. } exchange_keys;
  16. } ClientKeyExchange; // 用于返回加密的客户端生成的随机密钥(premaster secret)
  17.  
  18. struct {
  19. digitally-signed struct {
  20. opaque handshake_messages[handshake_messages_length]; // 采用客户端RSA私钥,对之前所有的握手报文数据,HASH后进行RSA签名
  21. }
  22. } CertificateVerify; // 用于服务器端校验客户端对客户端证书的所有权
  23.  
  24. struct {
  25. enum { change_cipher_spec(1), (255) } type; // 固定值0x01
  26. } ChangeCipherSpec; // 通知服务器后续报文为密文
  27.  
  28. struct {
  29. opaque verify_data[verify_data_length]; // 校验密文,算法PRF(master_secret, 'client finished', Hash(handshake_messages))
  30. } Finished; // 密文信息,计算之前所有收到和发送的信息(handshake_messages)的摘要,加上`client finished`, 执行PRF算法
  31.  

Finished 报文生成过程中,将产生会话密钥 master secret,然后生成Finish报文内容。

  1. master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)
  2. verify_data = PRF(master_secret, 'client finished', Hash(handshake_messages))
  3.  

PRF为TLS v1.2规定的伪随机算法, 此例子中,HMAC算法为 SHA256

  1. PRF(secret, label, seed) = P_<hash>(secret, label + seed)
  2.  
  3. P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
  4. HMAC_hash(secret, A(2) + seed) +
  5. HMAC_hash(secret, A(3) + seed) + ...
  6. // A(0) = seed
  7. // A(i) = HMAC_hash(secret, A(i-1))

服务器完成握手

服务收到请求后,首先校验客户端证书的合法性,并且验证客户端证书签名是否合法。根据服务器端证书私钥,解密 ClientKeyExchange,获得pre_master_secret, 用相同的PRF算法即可获取会话密钥,校验客户端 Finish 信息是否正确。如果正确,则服务器端与客户端完成密钥交换。 返回 change_cipher_spec, Finished 报文。

  1. struct {
  2. enum { change_cipher_spec(1), (255) } type; // 固定值0x01
  3. } ChangeCipherSpec; // 通知服务器后续报文为密文
  4.  
  5. struct {
  6. opaque verify_data[verify_data_length]; // 校验密文,算法PRF(master_secret, 'server finished', Hash(handshake_messages))
  7. } Finished; // 密文信息,计算之前所有收到和发送的信息(handshake_messages)的摘要,加上`server finished`, 执行PRF算法

客户端会话开始

客户端校验服务器的Finished报文合法后,握手完成,后续用 master_secret 发送数据。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号