详解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教程之制作一个简单的文章发布系统
Nov 21 NodeJs
nodejs实现HTTPS发起POST请求
Apr 23 NodeJs
初探nodeJS
Jan 24 NodeJs
NodeJS学习笔记之Module的简介
Mar 24 NodeJs
nodejs学习笔记之路由
Mar 27 NodeJs
nodejs 终端打印进度条实例代码
Apr 22 NodeJs
nodejs结合socket.io实现websocket通信功能的方法
Jan 12 NodeJs
Nodejs中的require函数的具体使用方法
Apr 02 NodeJs
独立部署小程序基于nodejs的服务器过程详解
Jun 24 NodeJs
浅谈使用nodejs搭建web服务器的过程
Jul 20 NodeJs
nodejs利用readline提示输入内容实例代码
Jul 15 NodeJs
分享五个Node.js开发的优秀实践 
Apr 07 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制作简单的内容采集器的代码
2007/11/28 PHP
PHP中余数、取余的妙用
2015/06/29 PHP
PHP命名空间和自动加载类
2016/04/03 PHP
Laravel 6.2 中添加了可调用容器对象的方法
2019/10/22 PHP
javascript FormatNumber函数实现方法
2008/12/30 Javascript
如何将JS的变量值传递给ASP变量
2012/12/10 Javascript
jQuery的显示和隐藏方法与css隐藏的样式对比
2013/10/18 Javascript
jquery 漂亮的删除确认和提交无刷新删除示例
2013/11/13 Javascript
让table变成exls的示例代码
2014/03/24 Javascript
使用JavaScript实现网页版Pongo设计思路及源代码分享
2014/06/16 Javascript
jQuery中:radio选择器用法实例
2015/01/03 Javascript
javascript定时器完整实例
2015/02/10 Javascript
写给小白的JavaScript引擎指南
2015/12/04 Javascript
js将json格式的对象拼接成复杂的url参数方法
2016/05/25 Javascript
javascript监听页面刷新和页面关闭事件方法详解
2017/01/09 Javascript
详解关于react-redux中的connect用法介绍及原理解析
2017/09/11 Javascript
详解让sublime text3支持Vue语法高亮显示的示例
2017/09/29 Javascript
[02:43]DOTA2英雄基础教程 德鲁伊
2014/01/13 DOTA
在Python中使用判断语句和循环的教程
2015/04/25 Python
Python实现Windows上气泡提醒效果的方法
2015/06/03 Python
基于python中theano库的线性回归
2018/08/31 Python
python使用正则表达式来获取文件名的前缀方法
2018/10/21 Python
python 自动重连wifi windows的方法
2018/12/18 Python
python返回数组的索引实例
2019/11/28 Python
Virtualenv 搭建 Py项目运行环境的教程详解
2020/06/22 Python
Python 保存加载mat格式文件的示例代码
2020/08/04 Python
html5 冒号分隔符对齐的实现
2019/07/31 HTML / CSS
英国最大的汽车交易网站:Auto Trader UK
2016/09/23 全球购物
简历上的自我评价怎么写
2014/01/28 职场文书
三八红旗集体先进事迹材料
2014/05/22 职场文书
校园学雷锋广播稿
2014/10/08 职场文书
幼儿园小班开学寄语(2016秋季)
2015/12/03 职场文书
诉讼和解协议书
2016/03/23 职场文书
2019森林防火宣传标语大全!
2019/07/03 职场文书
idea搭建可运行Servlet的Web项目
2021/06/26 Java/Android
Moment的feature导致线上bug解决分析
2022/09/23 Javascript