1. 详解NodeJS Https HSM双向认证实现

     更新时间:2019年03月12日 14:17:05   作者:githoniel   我要评论

    这篇文章主要介绍了详解NodeJS Https HSM双向认证实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    工作中需要建立一套HSM的HTTPS双向认证通道,即通过硬件?#29992;?#26426;(Ukey)进行本地?#29992;?#36816;算的HTTPS双向认证,和银行的UKEY认证类似。

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

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

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

    概述

    本次TLS双向认证支持以下?#29992;?#22871;件(*为建议使用套件):

    • 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)

    四种?#29992;?#22871;件流程完全一致,只是部分算法细节与报文略有差异,体现在

    • 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>; // 客户端支持的所有?#29992;?#22871;件,上述四种
      CompressionMethod compression_methods<1..2^8-1>; // 压缩算法
      select (extensions_present) { // 额外插件,为空
        case false:
          struct {};
        case true:
          Extension extensions<0..2^16-1>;
      };
    } ClientHello; // 客户端发送支持的TLS版本、客户端随机数、支持的?#29992;?#22871;件等信息
    
    

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

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

    struct {
      ProtocolVersion server_version; // 服务端最后决定使用的TLS版本
      Random random; // 与客户端随机数算法相同,但是必须是独立生成,与客户端毫无关联
      SessionID session_id; // ?#33539;?#30340;会话ID
      CipherSuite cipher_suite; // 最?#31449;?#23450;的?#29992;?#22871;件
      CompressionMethod compression_method; // 最终使用的压缩算法
      select (extensions_present) { // 额外插件,为空
        case false:
          struct {};
        case true:
          Extension extensions<0..2^16-1>;
      };
    } ServerHello; // 服务器端返回最?#31449;?#23450;的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算法,用服务器端证书的公钥,?#29992;?#23458;户端生成的46字节随机数(premaster secret)
        case dhe_dss:
        case dhe_rsa:
        case dh_dss:
        case dh_rsa:
        case dh_anon:
          ClientDiffieHellmanPublic;
      } exchange_keys;
    } ClientKeyExchange; // 用于返回?#29992;?#30340;客户端生成的随机密钥(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 发送数据。

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

    相关文章

    最新评论

    山东群英会开奖查询