麒麟V10部署Angie与铜锁国密双栈验证
记录时间:2026-11-05
环境:Kylin V10 SP3 (Halberd) / aarch64(全新环境,离线)/ Angie 1.12.4 / Tongsuo 8.5.0(OpenSSL 3.5.4 基线)/ gcc 7.3.0
一、目标与背景
给 nginx 侧加国密能力:同一个入口既服务国密浏览器(TLCP 双证书),也保留 RSA 给国际浏览器,满足国密改造与等保要求。先在测试环境从零跑通整套流程,再谈生产切换。
Web 服务器选型为 Angie 1.12.4:sticky 会话保持和 NTLS 国密都是内置能力,不需要第三方模块;维护由 nginx 核心团队负责,GA 版本正常发布。
二、环境与准备
2.1 环境信息
| 节点 | IP | 系统 | 角色 |
|---|---|---|---|
| localhost | 192.168.120.79 | Kylin V10 SP3 / aarch64 | 测试机,离线(ISO 挂载 /mnt) |
| 组件 | 版本 | 说明 |
|---|---|---|
| Angie | 1.12.4 | Web 服务器,内置 sticky 与 NTLS |
| Tongsuo | 8.5.0 | 国密密码库,enable-ntls 编译 |
| gcc | 7.3.0 | Kylin 自带 |
| OpenSSL(系统) | 1.1.1f(麒麟国密补丁版) | 仅作客户端交叉验证,不能用于编译 Angie |
2.2 本地 yum 源(离线环境)
ISO 挂载后配本地源。测试机若已有 kylin10-local-repo 可跳过本节。
mount /root/Kylin-Server-V10-SP3-2403-Release-20240426-arm64.iso /mnt
cat > /etc/yum.repos.d/kylin10-local.repo <<'EOF'
[kylin10-local-repo]
name=Kylin Linux Advanced Server V10 Local Repo
baseurl=file:///mnt
enabled=1
gpgcheck=0
EOF
yum clean all && yum makecache
yum repolist
2.3 安装编译依赖
# yum install -y gcc gcc-c++ make perl perl-Text-Template pcre-devel zlib-devel tar
yum install -y gcc gcc-c++ make perl pcre-devel zlib-devel tar
rpm -q gcc make perl pcre-devel zlib-devel | sed 's/^/ /'
不要把
perl-Text-Template写进这条命令。Kylin 本地源没有这个包,带上它会整条yum install报No match并中断,其他包也不会安装。它单独在 2.4 处理。
2.4 离线安装 Text::Template
Tongsuo 的 Configure 第 2109 行是 use Text::Template 1.46;,这是硬依赖,不是测试依赖。它也不是 Perl 核心模块,装了 perl 包也没有。
联网机器下载:
https://cpan.metacpan.org/modules/by-module/Text/Text-Template-1.61.tar.gz
传到测试机后:
cd /usr/local/src
tar xf Text-Template-1.61.tar.gz
cd Text-Template-1.61
perl Makefile.PL && make && make install
perl -MText::Template -e 'print $Text::Template::VERSION, "\n"' # 输出 1.61
Makefile.PL阶段提示的Test::More::UTF8、Test::Warnings只是测试依赖,忽略即可。装完后模块在/usr/local/share/perl5,属于@INC路径。
2.5 获取源码包
两个项目 release 页都不提供源码 asset,用 tag 归档。在联网机器上下载:
# Tongsuo 8.5.0
curl -L -o Tongsuo-8.5.0.tar.gz \
https://github.com/Tongsuo-Project/Tongsuo/archive/refs/tags/8.5.0.tar.gz
# Angie 1.12.4
curl -L -o angie-Angie-1.12.4.tar.gz \
https://github.com/webserver-llc/angie/archive/refs/tags/Angie-1.12.4.tar.gz
传到测试机后解压:
cd /usr/local/src
tar xf Tongsuo-8.5.0.tar.gz
tar xf angie-Angie-1.12.4.tar.gz
ls -d Tongsuo-8.5.0 angie-Angie-1.12.4
ls Tongsuo-8.5.0/test/certs/sm2/ | head # 只用其中的客户端双证书,第七节 ECDHE 握手要用
预期:两个源码目录就位。
三、编译 Tongsuo 8.5.0
cd /usr/local/src/Tongsuo-8.5.0
./config enable-ntls --prefix=/opt/tongsuo no-shared
make -j"$(nproc)"
make install
验证:
/opt/tongsuo/bin/openssl version
/opt/tongsuo/bin/openssl ciphers -v | grep -iE 'SM2|SM4'
预期输出:
- 版本:
Tongsuo: Tongsuo 8.5.0加OpenSSL 3.5.4(8.5.0 已改用 3.5.4 基线,不再是 1.1.1); - 套件:
ECC-SM2-SM4-GCM-SM3、ECDHE-SM2-SM4-GCM-SM3、ECC-SM2-SM4-CBC-SM3、ECDHE-SM2-SM4-CBC-SM3,以及 TLS1.3 的TLS_SM4_GCM_SM3、TLS_SM4_CCM_SM3。
Tongsuo 编译耗时几分钟属正常。如果
make因告警中断,把最后 20 行贴出来再判断,不要自己改编译参数。
四、证书准备
国密证书必须用 CA 签发的标准编码证书,Tongsuo 源码树里那套自签测试证书浏览器不认(原因见 7.4),不写进配置示例。下面只准备两样:线上签发的国密双证书,和一张 RSA 自签证书。
4.1 RSA 自签证书
mkdir -p /AppHome/nginx/cert
openssl req -x509 -newkey rsa:2048 -nodes -days 30 \
-subj "/CN=sm2-test.local" \
-keyout /AppHome/nginx/cert/rsa.key -out /AppHome/nginx/cert/rsa.crt
4.2 线上签发国密双证书(gmcert.org)
浏览器验收和生产都用 CA 签发的证书。这里记录 https://www.gmcert.org/subForm# 的签发过程。
一共签两次,签名证书和加密证书各一次,除”密钥用途”外其余完全一致。
两次都相同的项:
| 表单项 | 填写 | 说明 |
|---|---|---|
| 设置证书算法 | 国密SM2算法 | |
| 选择CA | 签名证书选签名 CA,加密证书选加密 CA(下拉有区分就按类型选) | |
| C / S / L / O / OU | 按实际填写(如 CN、广东、广州、gzeport、yw) | |
| 主题名称/CN | sm2-test.local |
必须和浏览器访问的域名一致 |
| 基本约束 | 普通证书 | CA 证书是给签发机构用的,不要选 |
| 主题备用名称 | DNS=sm2-test.local,IP=192.168.120.79 |
域名必填,IP 加上方便直接用 IP 测 |
| 扩展密钥用途 | 服务器身份验证(TLS Web Server Authentication) | 国密浏览器会检查 |
| 选择证书签名算法 | SM3 | |
| 证书有效天数 | 365 | 测试够用 |
| 证书链选项 | 有”包含完整链”就选它 | |
| 输出格式 | PEM证书+密钥 | Angie 只认 PEM,PKCS12 不能直接用 |
| 是否发送到邮箱 | 仅下载 | 私钥不要走邮箱 |
两次不同的项:
| 证书 | 密钥用途(勾选) | 下载后命名 |
|---|---|---|
| 第一次:签名证书 | 数字签名 + 不可否认(digitalSignature、nonRepudiation) | sign.crt / sign.key |
| 第二次:加密证书 | 密钥加密 + 数据加密(keyEncipherment、dataEncipherment) | enc.crt / enc.key |
不要选”兼顾签名加密”。TLCP 要求签名和加密是两套独立密钥,Angie 的
ssl_certificate双参数就是按这个设计的。
下载后归档:
mkdir -p /opt/gmcert/sign /opt/gmcert/enc
cd /opt/gmcert/sign
cp sm2-test.local.cert.pem sign.crt
cp sm2-test.local.key.pem sign.key
cd /opt/gmcert/enc
cp sm2-test.local.cert.pem enc.crt
cp sm2-test.local.key.pem enc.key
本次签发结果:签名与加密两张证书签发者是 GMCert GM Root CA - 01,根直签没有中间证书;下载的证书里已经带了根,不需要再 cat 拼链。证书内容核对放在 7.1。
五、编译 Angie
5.1 用户与目录
id gzapps || useradd -r -s /sbin/nologin gzapps
mkdir -p /AppHome/nginx/logs
chown -R gzapps:gzapps /AppHome/nginx/logs
5.2 配置与编译
cd /usr/local/src/angie-Angie-1.12.4
./configure \
--prefix=/AppHome/nginx --sbin-path=sbin/nginx \
--user=gzapps --group=gzapps \
--with-http_ssl_module --with-http_stub_status_module --with-pcre \
--with-http_gzip_static_module --with-http_v2_module \
--with-http_sub_module --with-http_realip_module \
--with-stream --with-stream_ssl_module --with-stream_realip_module \
--with-debug --with-ntls \
--with-openssl=/usr/local/src/Tongsuo-8.5.0 \
--with-openssl-opt="enable-ntls"
make -j"$(nproc)"
| 参数 | 作用 | 不加会怎样 |
|---|---|---|
--with-ntls |
开启 NTLS 支持(USE_NTLS 默认 NO) |
ssl_ntls 报 unknown directive |
--sbin-path=sbin/nginx |
二进制装成 sbin/nginx(Angie 默认 sbin/angie) |
启动脚本要改路径 |
--with-openssl=/usr/local/src/Tongsuo-8.5.0 |
用 Tongsuo 源码静态编译 TLS 库 | 用系统 OpenSSL,无国密 |
--with-openssl-opt="enable-ntls" |
Tongsuo 侧开启 NTLS | TLCP 双证书握手失败 |
--prefix=/AppHome/nginx |
与生产一致,相对 pid logs/... 按它解析 |
pid/日志路径错位 |
--user/--group=gzapps |
运行用户按现网规范 | 默认用户不是 gzapps |
5.3 安装与校验
make install
/AppHome/nginx/sbin/nginx -V
chown -R gzapps:gzapps /AppHome/nginx/logs
检查四点:Angie/1.12.4、nginx/1.31.2、built with OpenSSL 3.5.4、configure 参数里有 --with-ntls。
Angie 的安装目标默认把二进制命名成
sbin/angie,这里用--sbin-path=sbin/nginx保持原名。安装脚本会把同名旧文件改名为.old,回滚时直接用。
5.4 关于 sticky 的编译参数
不需要任何参数。Angie 的 auto/options 里 HTTP_UPSTREAM_STICKY=YES 是默认值,只有加 --without-http_upstream_sticky_module 才会去掉,所以 configure 里没有、也不需要 --with-...sticky。stream 层的 sticky 同理(STREAM_UPSTREAM_STICKY=YES)。
六、双栈配置
/AppHome/nginx/conf/sm2-test.conf(浏览器验收通过的那一版):
worker_processes 1;
pid logs/angie-sm2-test.pid;
error_log logs/error.log info;
events { worker_connections 256; }
http {
log_format sm2_probe '$remote_addr:$server_port $ssl_protocol $ssl_cipher $ssl_server_cert_type';
server {
listen 443 ssl;
server_name sm2-test.local;
ssl_ntls on;
# 国密双证书:一条指令两个参数(签名证书 + 加密证书)
ssl_certificate /opt/gmcert/sign/sign.crt /opt/gmcert/enc/enc.crt;
ssl_certificate_key /opt/gmcert/sign/sign.key /opt/gmcert/enc/enc.key;
# 国际 RSA 证书,与国密证书并存
ssl_certificate /AppHome/nginx/cert/rsa.crt;
ssl_certificate_key /AppHome/nginx/cert/rsa.key;
access_log logs/sm2-access.log sm2_probe;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers "ECC-SM2-SM4-CBC-SM3:ECDHE-SM2-SM4-CBC-SM3:ECC-SM2-SM4-GCM-SM3:ECDHE-SM2-SM4-GCM-SM3:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256";
location / { return 200 "proto=$ssl_protocol cipher=$ssl_cipher\n"; }
}
}
| 指令 | 说明 |
|---|---|
ssl_ntls on |
启用国密双证书,Angie 内置能力 |
ssl_certificate(两个参数) |
顺序是签名证书在前、加密证书在后 |
第二组 ssl_certificate |
国际 RSA 证书,与国密证书同 server 共存 |
ssl_ciphers |
ECC 与 ECDHE 两类国密套件都要列,部分国密客户端只支持 ECC 静态套件 |
ssl_protocols |
只约束国际 TLS;国密走 NTLSv1.1,不受这里控制 |
log_format sm2_probe |
四列分别记录客户端、端口、协议、套件、服务端证书类型,用来一眼区分两条路径 |
access_log ... sm2_probe |
只在测试 server 上开,不能把自定义格式塞进生产 aka_logs |
error_log ... debug |
本次编译没加 --with-debug,debug 级别不产生调试行,等同 info;保留是为了以后重编时直接可用 |
/AppHome/nginx/sbin/nginx -t -c /AppHome/nginx/conf/sm2-test.conf
/AppHome/nginx/sbin/nginx -c /AppHome/nginx/conf/sm2-test.conf
ss -lntp | grep :443
国密那组证书固定用
/opt/gmcert下线上签发的两张,不要换成 Tongsuo 源码树里的自签测试证书——那套浏览器不认。
七、握手验证
7.1 证书核对
握手前先确认两件事:证书内容符合要求、证书与私钥配对。
openssl x509 -in /opt/gmcert/sign/sign.crt -noout -subject -issuer -dates \
-ext subjectAltName,keyUsage,extendedKeyUsage
openssl x509 -in /opt/gmcert/sign/sign.crt -noout -text | grep -A3 "Public Key Algorithm"
openssl x509 -in /opt/gmcert/sign/sign.crt -noout -text | grep -A1 "ASN1 OID"
cd /opt/gmcert
for p in sign/sign enc/enc; do
echo "========== $p =========="
openssl x509 -in $p.crt -noout -subject -issuer -dates \
-ext subjectAltName,keyUsage,extendedKeyUsage
openssl x509 -in $p.crt -noout -text | grep -A3 "Public Key Algorithm"
echo -n "证书公钥指纹: "; openssl x509 -in $p.crt -noout -pubkey | openssl md5
echo -n "私钥公钥指纹: "; openssl pkey -in $p.key -pubout 2>/dev/null | openssl md5
done
四项必须对:
| 检查项 | 期望 |
|---|---|
| Public Key Algorithm | id-ecPublicKey + ASN1 OID: SM2 |
| 签名证书 keyUsage | digitalSignature、nonRepudiation |
| 加密证书 keyUsage | keyEncipherment、dataEncipherment(实测还带 Key Agreement) |
| SAN | DNS:sm2-test.local、IP Address:192.168.120.79 |
第一项不对就别上服务,否则浏览器还是 alert 50(原因见 7.4)。本次签发结果:两张都是 id-ecPublicKey 加 ASN1 OID: SM2,签名证书 keyUsage 是 Digital Signature,加密证书是 Key Encipherment, Data Encipherment, Key Agreement,SAN 为 DNS:sm2-test.local, IP:192.168.120.79,证书与私钥的公钥指纹一致。
7.2 四格握手
| 客户端 | 预期协议 | 预期套件 | 结果 |
|---|---|---|---|
| Tongsuo ECC(无客户端证书) | NTLSv1.1 | ECC-SM2-SM4-GCM-SM3 | 通过 |
| Tongsuo ECDHE(带客户端双证书) | NTLSv1.1 | ECDHE-SM2-SM4-GCM-SM3 | 通过 |
| 360 国密浏览器 | NTLSv1.1 | ECC-SM2-SM4-GCM-SM3 | 通过 |
| Chrome / Edge | TLSv1.3 | TLS_AES_128_GCM_SHA256 | 通过 |
| Tongsuo TLS1.2 | TLSv1.2 | ECDHE-RSA-AES256-GCM-SHA384 | 通过 |
# 国密 ECC,客户端不需要证书
printf 'GET / HTTP/1.0\r\n\r\n' | /opt/tongsuo/bin/openssl s_client \
-connect 127.0.0.1:443 -cipher ECC-SM2-SM4-GCM-SM3 \
-enable_ntls -ntls
# 国密 ECDHE,必须带客户端双证书
printf 'GET / HTTP/1.0\r\n\r\n' | /opt/tongsuo/bin/openssl s_client \
-connect 127.0.0.1:443 -cipher ECDHE-SM2-SM4-GCM-SM3 \
-enable_ntls -ntls \
-sign_cert /usr/local/src/Tongsuo-8.5.0/test/certs/sm2/client_sign.crt \
-sign_key /usr/local/src/Tongsuo-8.5.0/test/certs/sm2/client_sign.key \
-enc_cert /usr/local/src/Tongsuo-8.5.0/test/certs/sm2/client_enc.crt \
-enc_key /usr/local/src/Tongsuo-8.5.0/test/certs/sm2/client_enc.key
# 国际 TLS1.3
curl -k -s https://127.0.0.1:443/
# 国际 TLS1.2 精确套件
printf 'GET / HTTP/1.0\r\n\r\n' | /opt/tongsuo/bin/openssl s_client \
-connect 127.0.0.1:443 -tls1_2 -cipher ECDHE-RSA-AES256-GCM-SHA384
判据:
- 国密两条看
Protocol: NTLSv1.1,套件名里有没有 SM2 不作数; - 国际两条看
TLSv1.3/TLSv1.2; - 客户端没导入 GMCert 根证书时会报
verify error:num=19 self-signed certificate in certificate chain,握手本身不受影响; - 自签 RSA 证书会报
verify error:num=18 self-signed certificate,测试环境属正常; -cipher只对 TLS1.2 及以下生效,不加-tls1_2时客户端会协商 TLS1.3、显示TLS_AES_*,不是配置错误。
7.3 双栈同时生效(最终验证)
恢复双栈配置后,同一台客户端上两种浏览器分别访问,访问日志为:
tail -5 /AppHome/nginx/logs/sm2-access.log
192.168.122.109:443 TLSv1.3 TLS_AES_128_GCM_SHA256 RSA ← Chrome / Edge
192.168.122.109:443 NTLSv1.1 ECC-SM2-SM4-GCM-SM3 SM2 ← 360 国密浏览器
error.log 无新增记录,说明没有握手失败、没有国际路径缺套件。四列分别是客户端、协议、套件、服务端证书类型。

7.4 浏览器验收卡住的原因:证书公钥编码
这轮验证在浏览器侧耗时最多,先后出现过 alert 46(证书不受信)和 alert 50(decode error),最后定位到证书公钥编码:
| 证书来源 | 公钥算法编码 | 360 国密浏览器表现 |
|---|---|---|
Tongsuo openssl 自签 |
算法 OID 直接是 SM2(1.2.156.10197.1.301) | 握手到 ServerKeyExchange 后发 alert 50 |
| gmcert.org 签发 | id-ecPublicKey + 曲线参数 SM2 |
握手成功,NTLSv1.1 |
结论:国密服务端证书不能只用 Tongsuo 自签,必须用标准编码的证书。向 CA 申请时明确四项要求:签名证书与加密证书两张、id-ecPublicKey + SM2 编码、SAN 含业务域名、PEM 私钥无口令。
八、内置 sticky 验证
在同一个 sm2-test.conf 的 http {} 内追加:
upstream demo_backend {
server 127.0.0.1:19001;
server 127.0.0.1:19002;
sticky cookie route;
}
server {
listen 18444;
server_name sticky-test.local;
location / { proxy_pass http://demo_backend; }
}
Angie 的
sticky必须带模式参数。现网 nginx 的裸sticky;不能直接搬过来;要沿用现有 cookie 名,写sticky cookie route;。cookie 值格式与 nginx-sticky-module 不同,切换时用户会一次性重新粘滞。
mkdir -p /tmp/b1 /tmp/b2
echo BACKEND-19001 > /tmp/b1/index.html
echo BACKEND-19002 > /tmp/b2/index.html
(cd /tmp/b1 && timeout 900 python3 -m http.server 19001 --bind 127.0.0.1 &)
(cd /tmp/b2 && timeout 900 python3 -m http.server 19002 --bind 127.0.0.1 &)
/AppHome/nginx/sbin/nginx -t -c /AppHome/nginx/conf/sm2-test.conf
/AppHome/nginx/sbin/nginx -s reload -c /AppHome/nginx/conf/sm2-test.conf
curl -sk -c /tmp/cookie.jar -o - http://127.0.0.1:18444/
for i in 1 2 3; do curl -sk -b /tmp/cookie.jar http://127.0.0.1:18444/; done
预期:首次响应带 Set-Cookie: route=<sid>,随后三次请求都落在同一个 BACKEND。
九、回滚与常见问题
| 现象 | 处理 |
|---|---|
ssl_ntls 报 unknown directive |
编译时漏了 --with-ntls,重新 configure |
ssl_certificate 报参数个数错误 |
双参数写法只在 ssl_ntls on 时成立,检查开关位置 |
sticky 报 unknown directive |
检查是否误加了 --without-http_upstream_sticky_module |
| 国密握手失败但 RSA 正常 | 先查签名/加密证书是否颠倒、私钥是否配对;浏览器报 alert 50 查证书公钥编码(7.4) |
启动报 getpwnam("gzapps") failed |
没有该用户,useradd -r -s /sbin/nologin gzapps |
| 大响应或大上传报权限错误 | logs、*_temp 目录属主不对,chown 给 gzapps |
make 报 Can't locate Text/Template.pm |
2.4 没装,或装完没在 @INC 路径 |
回滚:安装脚本留下的 sbin/nginx.old 是安装前的文件,覆盖回去后 -t 通过再 reload。