欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解NodeJS Https HSM双向认证实现

程序员文章站 2022-10-29 09:15:47
工作中需要建立一套hsm的https双向认证通道,即通过硬件加密机(ukey)进行本地加密运算的https双向认证,和银行的ukey认证类似。 nodejs可以利用ope...

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

nodejs可以利用openssl的hsm plugin方式实现,但是需要编译c++,太麻烦,作者采用了利用node socket接口,纯js自行实现https/http协议的方式实现

具体实现可以参考如下

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 完整握手流程图

* =======================full handshake======================
 * client                        server
 *
 * clienthello         -------->
 *                         serverhello
 *                         certificate
 *                     certificaterequest
 *               <--------   serverhellodone
 * certificate
 * clientkeyexchange
 * certificateverify
 * finished           -------->
 *                     change_cipher_spec
 *               <--------       finished
 * application data       <------->   application data

流程详解

客户端发起握手请求

tls握手始于客户端发起 clienthello 请求。

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 发送数据。

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