详解NodeJS Https HSM双向认证实现


Posted in NodeJs onMarch 12, 2019

工作中需要建立一套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 完整握手流程图

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

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

NodeJs 相关文章推荐
nodejs npm install全局安装和本地安装的区别
Jun 05 NodeJs
NodeJS学习笔记之Connect中间件模块(一)
Jan 27 NodeJs
快速掌握Node.js之Window下配置NodeJs环境
Mar 21 NodeJs
初探nodeJS
Jan 24 NodeJs
windows 下安装nodejs 环境变量设置
Feb 02 NodeJs
nodejs个人博客开发第二步 入口文件
Apr 12 NodeJs
Nodejs读取文件时相对路径的正确写法(使用fs模块)
Apr 27 NodeJs
Nodejs中Express 常用中间件 body-parser 实现解析
May 22 NodeJs
对mac下nodejs 更新到最新版本的最新方法(推荐)
May 17 NodeJs
typescript nodejs 依赖注入实现方法代码详解
Jul 21 NodeJs
Nodejs中使用puppeteer控制浏览器中视频播放功能
Aug 26 NodeJs
nodejs利用readline提示输入内容实例代码
Jul 15 NodeJs
NodeJs入门教程之定时器和队列
Mar 08 #NodeJs
nodejs npm错误Error:UNKNOWN:unknown error,mkdir 'D:\Develop\nodejs\node_global'at Error
Mar 02 #NodeJs
nodejs同步调用获取mysql数据时遇到的大坑
Mar 02 #NodeJs
Nodejs中怎么实现函数的串行执行
Mar 02 #NodeJs
Nodejs让异步变成同步的方法
Mar 02 #NodeJs
nodejs使用async模块同步执行的方法
Mar 02 #NodeJs
NodeJS实现同步的方法
Mar 02 #NodeJs
You might like
php数组合并array_merge()函数使用注意事项
2014/06/19 PHP
php中隐形字符65279(utf-8的BOM头)问题
2014/08/16 PHP
php+redis实现商城秒杀功能
2020/11/19 PHP
js中将多个语句写成一个语句的两种方法小结
2007/12/08 Javascript
js post方式传递提交的实现代码
2010/05/31 Javascript
jquery 获取标签名(tagName)示例代码
2013/07/11 Javascript
把字符串按照特定的字母顺序进行排序的js代码
2014/01/28 Javascript
document.addEventListener使用介绍
2014/03/07 Javascript
jQuery遍历json中多个map的方法
2015/02/12 Javascript
详解AngularJS中module模块的导入导出
2015/12/10 Javascript
深入浅析AngularJS中的module(模块)
2016/01/04 Javascript
javascript禁止超链接跳转的方法
2016/02/02 Javascript
深入浅析JavaScript中with语句的理解
2016/05/12 Javascript
js实现当鼠标移到表格上时显示这一格全部内容的代码
2016/06/12 Javascript
canvas实现流星雨的背景效果
2017/01/13 Javascript
原生js实现鼠标跟随效果
2017/02/28 Javascript
JS+html5 canvas实现的简单绘制折线图效果示例
2017/03/13 Javascript
vue中的非父子间的通讯问题简单的实例代码
2017/07/19 Javascript
Vue使用lodop实现打印小结
2019/07/06 Javascript
vue-devtools的安装和使用步骤详解
2019/10/17 Javascript
[06:16]《DAC最前线》之地区预选赛全面回顾
2015/01/19 DOTA
python在windows下创建隐藏窗口子进程的方法
2015/06/04 Python
django反向解析和正向解析的方式
2018/06/05 Python
python mysql自增字段AUTO_INCREMENT值的修改方式
2020/05/18 Python
Python如何转换字符串大小写
2020/06/04 Python
Windows下PyCharm配置Anaconda环境(超详细教程)
2020/07/31 Python
CSS3按钮鼠标悬浮实现光圈效果源码
2016/09/11 HTML / CSS
改变生活的男士内衣:SAXX Underwear
2019/08/28 全球购物
英国哈罗德园艺:Harrod Horticultural
2020/03/31 全球购物
房地产员工找工作的自我评价
2013/11/15 职场文书
土木工程专业个人求职信
2013/12/30 职场文书
合作协议书模板
2014/10/10 职场文书
2014年党的群众路线活动个人整改措施
2014/10/28 职场文书
企业整改报告范文
2014/11/08 职场文书
爱国主义教育基地观后感
2015/06/18 职场文书
理想国读书笔记
2015/06/25 职场文书