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 curl+铜锁: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