详解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框架Express的模板视图机制分析
Jul 19 NodeJs
windows系统下简单nodejs安装及环境配置
Jan 08 NodeJs
Nodejs实现的一个简单udp广播服务器、客户端
Sep 25 NodeJs
Nodejs抓取html页面内容(推荐)
Aug 11 NodeJs
NodeJs读取JSON文件格式化时的注意事项
Sep 25 NodeJs
进阶之初探nodeJS
Jan 24 NodeJs
nodejs 终端打印进度条实例代码
Apr 22 NodeJs
NodeJS自定义模块写法(详解)
Jun 27 NodeJs
NodeJS安装图文教程
Apr 19 NodeJs
搭建一个nodejs脚手架的方法步骤
Jun 28 NodeJs
nodejs语言实现验证码生成功能的示例代码
Oct 13 NodeJs
NodeJs使用webpack打包项目的方法详解
Feb 28 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破解防盗链图片的一个简单方法
2014/06/07 PHP
CodeIgniter框架过滤HTML危险代码
2014/06/12 PHP
让您的菜单不离网站
2006/10/03 Javascript
javascript 简单高效判断数据类型 系列函数 By shawl.qiu
2007/03/06 Javascript
动态加载图片路径 保持JavaScript控件的相对独立性
2010/09/06 Javascript
将查询条件的input、select清空
2014/01/14 Javascript
Jquery 过滤器(first,last,not,even,odd)的使用
2014/01/22 Javascript
jQuery入门介绍之基础知识
2015/01/13 Javascript
纯js代码实现简单计算器
2015/12/02 Javascript
js控制文本框只能输入中文、英文、数字与指定特殊符号的实现代码
2016/09/09 Javascript
JS常用算法实现代码
2016/11/14 Javascript
Mongoose学习全面理解(推荐)
2017/01/21 Javascript
原生js实现打字动画游戏
2017/02/04 Javascript
激动人心的 Angular HttpClient的源码解析
2017/07/10 Javascript
Nodejs+angularjs结合multiparty实现多图片上传的示例代码
2017/09/29 NodeJs
JQuery事件委托原理与用法实例分析
2019/05/13 jQuery
js函数柯里化的方法和作用实例分析
2020/04/11 Javascript
js瀑布流布局的实现
2020/06/28 Javascript
[10:07]2014DOTA2国际邀请赛 实拍选手现场观战DK对阵Titan
2014/07/12 DOTA
Python高效编程技巧
2013/01/07 Python
python 魔法函数实例及解析
2019/09/25 Python
Python如何实现小程序 无限求和平均
2020/02/18 Python
python中导入 train_test_split提示错误的解决
2020/06/19 Python
Django-simple-captcha验证码包使用方法详解
2020/11/28 Python
HTML5 新标签全部总汇(推荐)
2016/06/13 HTML / CSS
世界上最好的威士忌和烈性酒购买网站:The Whisky Exchange
2016/11/20 全球购物
受外贸欢迎的美国主机:BlueHost
2017/05/16 全球购物
路政管理专业个人自荐信范文
2013/11/30 职场文书
复核员上岗演讲稿
2014/01/05 职场文书
2015毕业生实习工作总结
2014/12/12 职场文书
幼儿园大班个人总结
2015/02/28 职场文书
公司出纳岗位职责
2015/03/31 职场文书
安全教育片观后感
2015/06/17 职场文书
导游词之鲁迅祖居
2019/10/17 职场文书
使用kubeadm命令行工具创建kubernetes集群
2022/03/31 Servers
前端框架ECharts dataset对数据可视化的高级管理
2022/12/24 Javascript