1 简介 我国密码行业中有两个主要的通信协议相关的技术标准:
《SSL VPN技术规范》作为行业标准,部分内容已上升为《信息安全技术 传输层密码协议(TLCP)》国密标准。
 
国密TLS,又称GMTLS1.1,GMTLS1.1中的1.1代表国密TLS的版本号: _0x0101_。众所周知,目前TLS协议支持的版本:
1 2 3 4 VersionTLS10 = 0x0301 VersionTLS11 = 0x0302 VersionTLS12 = 0x0303 VersionTLS13 = 0x0304 
目前主流的TLS软件程序实现中,通信实体在建立TLS连接时,会优先协商使用最大版本号TLS协议 _(版本号越大意味着安全性越高、性能越好)_,因此VersionGMSSL = 0x0101 版本号的设定增加了软件支持的复杂度,这也是阻碍GMTLS1.1广泛应用的一个重要原因。
TLCP 显著的特点是将 TLS 协议中使用的数字证书拆分成了加密和签名两种用途的证书,加密证书和签名证书以及对应私钥均需要进行配置使用,所以 TLCP 也俗称“国密双证书”协议。
1.1 密钥种类 1.1.1 概述 采用非对称密码算法进行身份鉴别和密钥交换,身份鉴别通过后协商预主密钥,双方各自计算主密钥,进而推导出工作密钥。使用工作密钥进行加解密和完整性校验。
1.1.2 服务端密钥 服务端密钥为非对称密码算法的密钥对,包括签名密钥对和加密密钥对,其中签名密钥用于握手过程中服务端身份鉴别,加密密钥对用于预主密钥的协商。
1.1.3 客户端密钥 客户端密钥为非对称密码算法的密钥对,包括签名密钥对和加密密钥对,其中签名密钥用于握手过程中客户端身份鉴别,加密密钥对用于预主密钥的协商。
1.1.4 预主密钥 预主密钥(pre_master_secret)是双方协商生成的密钥素材,用于生成主密钥
1.1.5 主密钥 主密钥(master_secret)由预主密钥、客户端随机数、服务端随机数、常量字符串,经计算生成的 48 字节密钥素材,用于生成工作密钥。
1.1.6 工作密钥 工作密钥包括数据加密密钥和校验密钥。其中数据加密密钥用于数据的加密和解密,校验密钥用于数据的完整性计算和校验。发送方使用的工作密钥称为写密钥,接收方使用的工作密钥称为读密钥。
1.2 密码套件 GMTLS1.1 中相关的密码套件(图片来自GB/T 38636-2020 ):
从图中可以了解到GMTLS1.1:
密钥交换算法包含ECDHE、ECC、IBSDH、IBC 以及 RSA,其中 IBC 和 IBSDH 主要涉及到 SM9 标识密码算法_。加密算法只有SM4,加密模式涉及两种方式:CBC和GCM。 
校验算法/哈希算法包含:SM3 和 SHA256 
 
注:从上图中我们还可以了解到,在GMTLS1.1支持的套件中,包含了国际算法RSA和SHA256,这里从公开文献中未找到具体原因,大胆猜测主要是为了:
支持国际知名CA机构颁发的数字证书 
作为一种切换到全国密TLS过度 
 
1.3 协议原理 GMTLS1.1 协议主要参考了TLS1.1,并借鉴了TLS1.2的部分内容。在关键流程上基本一致,只是在密码算法使用上用国密算法对国际算法进行了替换。
1.4 国际和商密(SM)对比 
TLCP协议与TLS协议对比: 
1.5 证书对比 
2 基于铜锁(Tongsuo)部署国密服务端 铜锁(Tongsuo)对国密双证书协议进行了支持,并统称为 NTLS。NTLS 并不是指某一种具体的符合商用密码相关技术标准要求的网络协议,而是多个协议的统称。在 铜锁(Tongsuo) 中代指 TLCP 和 0024 国密双证书协议,因为 NTLS 和标准 TLS 协议存在工作方式的不同,因此 铜锁(Tongsuo) 中增加了一些新的 API 来对其进行支持。而应用程序若想使用 NTLS 功能,就需要调用这些新增 API,给现有基于 OpenSSL API 进行适配的应用程序带来了额外的开发工作量。
下载 Tongsuo 
 
NTLS (TLCP and GM/T 0024) 基于 Tongsuo。
1 git clone  https://github.com/Tongsuo-Project/Tongsuo.git 
编译:
1 ./config --prefix=/opt/tongsuo enable-ntls 
下载 Tengine 
 
1 git clone  https://github.com/alibaba/tengine.git 
编译 Tengine 
 
1 2 3 4 5 ./configure --add-module=modules/ngx_tongsuo_ntls \     --with-openssl=../Tongsuo \     --with-openssl-opt="--strict-warnings --api=1.1.1 enable-ntls" \     --with-http_ssl_module --with-stream \     --with-stream_ssl_module --with-stream_sni 
配置 Tengine 开启 NTLS 
 
可以直接使用Tongsuo项目中已经签发的SM2双证书,仅用于测试;
根CA > 中间CA > 客户端/服务端证书,根CA证书签发中间CA证书,中间CA证书签发客户端和服务端的证书,包括签名证书和加密证书,这些证书的公钥算法都是SM2。
服务端签名证书和私钥:https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/server_sign.crt https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/server_sign.key 
服务端加密证书和私钥:https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/server_enc.crt https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/server_enc.key 
CA证书,这里面包含根CA和中间CA证书:https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/chain-ca.crt https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/client_sign.crt https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/client_sign.key 
客户端加密证书和私钥:https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/client_enc.crt https://github.com/Tongsuo-Project/Tongsuo/blob/master/test/certs/sm2/client_enc.key 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 worker_processes  1; events {     	worker_connections  1024; } http { 	include       mime.types; 	default_type  application/octet-stream; 	server { 			listen       443 ssl; 			server_name  localhost; 			enable_ntls  on; 			ssl_sign_certificate        server_sign.crt; 			ssl_sign_certificate_key    server_sign.key;          			ssl_enc_certificate         server_enc.crt; 			ssl_enc_certificate_key     server_enc.key; 			location / { 				return  200 "body $ssl_protocol :$ssl_cipher " ; 			} 	} } stream {      server {         listen       8443 ssl;         enable_ntls  on;         ssl_sign_certificate        server_sign.crt;         ssl_sign_certificate_key    server_sign.key;             		ssl_enc_certificate         server_enc.crt;         		ssl_enc_certificate_key     server_enc.key;         return  "body $ssl_protocol :$ssl_cipher " ;     } } 
3 国密证书生成 3.1 证书制作相关命令 
生成一个 EC 密钥对(私钥和公钥),使用 SM2 曲线参数,保存私钥为 server_sign.key 文件: 
 
1 openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out server_sign.key 
使用私钥生成一个证书签名请求(CSR),并将其保存为 server_sign.csr 文件。该请求使用 SM3 散列算法,并且采用给定的主题信息: 
 
1 openssl req -config $CONF_DIR /subca.cnf -key server_sign.key -new -out server_sign.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=server sign"  
-config $CONF_DIR/subca.cnf: 指定配置文件,包含了生成 CSR 所需的详细信息和设置。-key server_sign.key: 指定用于生成 CSR 的私钥文件路径。-new: 创建新的证书签名请求。-out server_sign.csr: 指定生成的 CSR 文件名。-sm3: 使用 SM3 散列算法。-nodes: 生成的私钥不加密。-subj "/C=AA/ST=BB/O=CC/OU=DD/CN=server sign": 指定证书主题信息。 
使用 CA 配置文件(假设为 $CONF_DIR/subca.cnf)中定义的设置和参数,通过 CA 签发证书,将上一步生成的 CSR 文件 server_sign.csr 作为输入。签发的证书输出到 server_sign.crt 文件中: 
 
1 openssl ca -config $CONF_DIR /subca.cnf -extensions server_sign_req -days 3650 -in  server_sign.csr -notext -out server_sign.crt -md sm3 -batch 
-config $CONF_DIR/subca.cnf: 指定用于签发证书的 CA 配置文件。-extensions server_sign_req: 指定要应用的扩展名。-days 3650: 设置证书有效期为 3650 天(约合 10 年)。-in server_sign.csr: 指定输入的证书签名请求文件。-notext: 生成的证书不包含文本。-out server_sign.crt: 指定生成的证书文件名。-md sm3: 使用 SM3 消息摘要算法进行签名。-batch: 在执行证书签发过程中避免交互式操作。 
3.2 证书制作配置文件 上边的 subca.cnf 指定了生成证书的相关配置信息和扩展配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [ ca ] default_ca = CA_default [ CA_default ] dir                = ./certs             = $dir /certs crl_dir           = $dir /crl new_certs_dir     = $dir /newcerts database          = $dir /db/index unique_subject    = no serial            = $dir /db/serial RANDFILE          = $dir /private/random private_key       = $dir /subca.key certificate       = $dir /subca.crt crlnumber         = $dir /crlnumber crl               = $dir /crl/ca.crl.pem crl_extensions    = crl_ext default_crl_days  = 30 default_md        = sm3 name_opt          = ca_default cert_opt          = ca_default default_days      = 365 preserve          = no policy            = policy_strict [ policy_strict ] countryName             = optional stateOrProvinceName     = optional organizationName        = optional organizationalUnitName  = optional commonName              = supplied emailAddress            = optional [ server_sign_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature subjectAltName = @alt_names [ server_enc_req ] basicConstraints = CA:FALSE keyUsage = keyAgreement, keyEncipherment, dataEncipherment subjectAltName = @alt_names [ client_sign_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature [ client_enc_req ] basicConstraints = CA:FALSE keyUsage = keyAgreement, keyEncipherment, dataEncipherment 
3.3 脚本工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #!/bin/env bash set  -x export  PATH=/opt/tongsuo/bin:$PATH WORK_DIR=$(cd  $(dirname  $0 );pwd ) CONF_DIR=$WORK_DIR /conf function  my_clean     rm  -f *.key *.csr *.crt     rm  -rf {newcerts,db,private,crl} } function  begin     mkdir  {newcerts,db,private,crl}     touch  db/{index,serial}     echo  00 > db/serial } function  gen_ca_certs          openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out ca.key     openssl req -config $CONF_DIR /ca.cnf -new -key ca.key -out ca.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=root ca"      openssl ca -selfsign -config $CONF_DIR /ca.cnf -in  ca.csr -keyfile ca.key -extensions v3_ca -days 3650 -notext -out ca.crt -md sm3 -batch          openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out subca.key     openssl req -config $CONF_DIR /ca.cnf -new -key subca.key -out subca.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=sub ca"      openssl ca -config $CONF_DIR /ca.cnf -extensions v3_intermediate_ca -days 3650 -in  subca.csr -notext -out subca.crt -md sm3 -batch     cat  ca.crt subca.crt > chain-ca.crt } function  gen_server_certs          openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out server_sign.key          openssl req -config $CONF_DIR /subca.cnf -key server_sign.key -new -out server_sign.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=server sign"                     openssl ca -config $CONF_DIR /subca.cnf -extensions server_sign_req -days 3650 -in  server_sign.csr -notext -out server_sign.crt -md sm3 -batch     openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out server_enc.key     openssl req -config $CONF_DIR /subca.cnf -key server_enc.key -new -out server_enc.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=server enc"      openssl ca -config $CONF_DIR /subca.cnf -extensions server_enc_req -days 3650 -in  server_enc.csr -notext -out server_enc.crt -md sm3 -batch } function  gen_client_certs          openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out client_sign.key     openssl req -config $CONF_DIR /subca.cnf -key client_sign.key -new -out client_sign.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=client sign"      openssl ca -config $CONF_DIR /subca.cnf -extensions client_sign_req -days 3650 -in  client_sign.csr -notext -out client_sign.crt -md sm3 -batch     openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:sm2 -out client_enc.key     openssl req -config $CONF_DIR /subca.cnf -key client_enc.key -new -out client_enc.csr -sm3 -nodes -subj "/C=AA/ST=BB/O=CC/OU=DD/CN=client enc"      openssl ca -config $CONF_DIR /subca.cnf -extensions client_enc_req -days 3650 -in  client_enc.csr -notext -out client_enc.crt -md sm3 -batch } function  end     rm  -f *.csr     rm  -rf {newcerts,db,private,crl} } function  main     begin     case  $1  in          gen_server_cert)         gen_ca_certs         gen_server_certs         ;;         gen_client_cert)         gen_ca_certs         gen_client_certs         ;;         gen_cert)         gen_ca_certs         gen_server_certs         gen_client_certs         ;;         clean)         my_clean         ;;         *)         echo  "usage: $0  {gen_server_cert|gen_client_cert|gen_cert|clean}"      esac      end } main "$@ "  
4 客户端工具 wrk+铜锁:https://www.yuque.com/tsdoc/ts/kd16l0 https://www.yuque.com/tsdoc/ts/xuxk18ckbtpgvfdi 
5 测试 5.1 国密双证书和双向认证 5.1.1 客户端证书生成 生成客户端证书和密钥:
1 2 3 4 5 6 7 openssl ecparam -genkey -name SM2 -out client_sign.key openssl ecparam -genkey -name SM2 -out client_enc.key openssl req -x509 -new -key client_sign.key -out client_sign.crt -subj /CN=client_sign/ openssl req -x509 -new -key client_enc.key -out client_enc.crt -subj /CN=client_enc/ cat  client_sign.crt client_enc.crt > client_sign_enc.crt
以上的是生成自签名证书,如果需要使用已有的 CA证书进行签发,可以通过参数 -CA 和 -CAkey 选项指定签发证书所需的 CA 证书和对应的私钥。
5.1.2 服务端配置 生成的 client_sign_enc.crt 对客户端的两个证书进行合并,因为是自签名证书,可以直接把client_sign_enc.crt 配置到服务端,如下以 Nginx的配置为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 server {     listen       8443 ssl;     server_name  _;     ssl_session_cache shared:SSL:1m;     ssl_session_timeout 1m;     ssl_session_tickets off;     ssl_protocols TLSv1 TLSv1.1 TLSv1.2;     ssl_ciphers HIGH:!aNULL:!MD5:!ADH:!RC4;     ssl_prefer_server_ciphers on;     enable_ntls  on;     # 国密双证书     ssl_sign_certificate        certs/gmtls/server_sign.crt;     ssl_sign_certificate_key    certs/gmtls/server_sign.key;     ssl_enc_certificate         certs/gmtls/server_enc.crt;     ssl_enc_certificate_key     certs/gmtls/server_enc.key;     #ssl_trusted_certificate certs/gmtls/chain-ca.crt;     # 指定 验证客户端的证书     ssl_client_certificate certs/gmtls/client_sign_enc.crt;     # 开启客户端认证     ssl_verify_client on;     ssl_verify_depth 2;     #ssl_certificate certs/server.crt;     #ssl_certificate_key certs/server.key;     location / {         proxy_http_version 1.1;         proxy_set_header Upgrade $http_upgrade;         proxy_set_header Connection "Upgrade";         proxy_set_header host $host;         proxy_pass http://127.1.1.1:9090;         #return 200 "test hello\n";     } } 
5.1.3 测试 通过 openssl 命令行指定客户端证书,进行双向认证,发送HTTPS请求:
1 /bin/echo -e "GET / HTTP/1.0\r\n\r\n"  |  openssl s_client -connect 192.168.170.137:8443 -CAfile chain-ca.crt -servername optional -quiet -sign_cert client_sign.crt -sign_key client_sign.key -enc_cert client_enc.crt -enc_key client_enc.key  -enable_ntls -ntls 
可以通过 -cipher ECDHE-SM2-SM4-GCM-SM3 指定密码套件,其他使用方式如:
-cipher 'ECDHE-RSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256' :按照优先级指定多个加密套件。如果第一个不可用,将尝试使用下一个。-cipher 'AES256-*' :使用模式匹配,AES256-* 表示选择所有以 AES256- 开头的加密套件。-cipher 'AES256:!aNULL':! 表示排除指定类型的加密套件,例如 !aNULL 表示排除匿名加密套件。 
参考资料:# TLS原理与实践(四)国密TLS