国密 TLCP 实战:GmSSL / OCL / Nginx 版本选型与全部调试修改说明
本文面向发布到 CSDN,汇总本人在 Windows + WSL2 编译、Docker 部署、CentOS 生产环境跑通Nginx 国密 HTTPS(TLCP)时使用的源码版本、目录布局,以及为调通而做的全部修改(含配置、脚本、证书处理;不含对 Nginx 核心 C 源码的 patch)。
参考博文:基于 GmSSL 搭建 Nginx 国密反代(流程可参考,但部分写法如
ssl_protocols GMTLS与当前 OCL/Nginx 行为不完全一致,下文会说明)。
一、环境与目录布局
| 环境 | 说明 |
|---|---|
| 开发机 | Windows 10/11 +WSL2 Ubuntu 24.04 |
| 生产机 | CentOS 7 系(10.131.111.250),Docker 运行 |
| 源码根目录 | /mnt/d/codes/(Windows 对应D:\codes\) |
推荐三仓库并列:
/mnt/d/codes/ ├── GmSSL/ # 国密算法库 ├── OpenSSL-Compatibility-Layer/ # OpenSSL 兼容层(OCL) └── nginx/ # Nginx 1.25.3 源码 + Docker 工程证书调试目录(与仓库分离,便于拷到生产机):
/mnt/d/nginx_gm/certs/ # 自建 CA + 服务器 TLCP 证书 /opt/nginx/certs/ # 生产机挂载进容器 /opt/nginx/conf/nginx.conf # 生产机自定义主配置二、引用的三个项目及版本(务必对齐)
2.1 版本总表
| 项目 | 仓库 | 推荐 Tag / 版本 | 用途 |
|---|---|---|---|
| GmSSL | https://github.com/guanzhi/GmSSL | v3.1.1 | OCL 依赖;gmssl命令行生成证书 |
| OpenSSL-Compatibility-Layer | https://github.com/GmSSL/OpenSSL-Compatibility-Layer | v0.8.1 | 提供libssl.so/libcrypto.so,伪装 OpenSSL API |
| Nginx | https://nginx.org 或本仓库release-1.25.3 | 1.25.3 | OCL README 明确测试过的版本 |
| 运行环境 | 版本 |
|---|---|
| WSL 编译 | Ubuntu24.04,gcc13.3.0 |
| Docker 基础镜像 | Ubuntu24.04 |
| Docker 镜像标签 | nginx-gmssl:1.25.3 |
2.2 版本不对齐时的典型现象
| 错配 | 现象 |
|---|---|
| GmSSL 用master,Nginx/OCL 用 v3.1.1 编译 | WSL 上gmssl tlcp_client报tls.c:2829:tls_init(): error(见下文 §5.3) |
| Nginx 1.31+ 未在 OCL 列表中 | 编译或握手阶段未知问题,不建议生产首选用 |
| 系统 OpenSSL 3.x 未被子替换 | nginx -V显示 SSL 但握手仍是国际算法 |
gmssl与链接进 Nginx 的libgmssl不是同一次编译 | 证书工具能生成,运行时库行为不一致 |
2.3 编译安装顺序(固定)
① GmSSL (v3.1.1) → sudo make install → /usr/local/lib/libgmssl.so ② OCL (v0.8.1) → sudo make install → /usr/local/lib/libssl.so, libcrypto.so ③ Nginx (1.25.3) → ./configure && make → objs/nginx安装 OCL 后必须确认默认头文件/库来自 OCL,而不是系统 OpenSSL:
grep-iOPENSSL /usr/local/include/openssl/opensslv.h ldd /usr/local/lib/libcrypto.so|grepgmssl三、各组件编译参数(最终调通版)
3.1 GmSSL v3.1.1
cd/mnt/d/codes/GmSSLgitfetch--tagsgitcheckout v3.1.1mkdirbuild&&cdbuild cmake..make-j$(nproc)sudomakeinstall# 命令行工具whichgmssl# 期望 /usr/local/bin/gmssl说明:证书在/mnt/d/nginx_gm/certs用gmssl sm2keygen/certgen/reqgen/reqsign生成;口令示例Ilove@cn123,签名私钥与加密私钥必须相同(OCL 硬性要求)。
对 GmSSL 源码的修改:无(仅git checkout v3.1.1,避免使用 master 做客户端测试)。
3.2 OpenSSL-Compatibility-Layer v0.8.1
cd/mnt/d/codes/OpenSSL-Compatibility-Layergitfetch--tagsgitcheckout v0.8.1mkdirbuild&&cdbuild cmake..make-j$(nproc)sudomakeinstall安装结果:
- 头文件:
/usr/local/include/openssl/(OCL 头文件) - 库:
/usr/local/lib/libssl.so、libcrypto.so(底层依赖libgmssl)
对 OCL 源码的修改(生产调通路径):无 patch。
理解其行为即可,不必改代码(见 §5.1pem.c逻辑)。
社区可选 patch(浏览器访问报错时):
OCL Issue #13 提到在 GmSSL 的src/tlcp.c的tlcp_do_accept中注释客户端扩展校验,以兼容部分国密浏览器。本人生产调通未改此文件;若奇安信/360 握手失败可再评估。
3.3 Nginx 1.25.3
第一次 configure(踩坑,缺 PCRE)
./configure\--prefix=/usr/local/nginx\--with-http_ssl_module\--with-stream\--with-stream_ssl_module\--without-http_rewrite_moduleobjs/ngx_auto_config.h中无NGX_PCRE,配置里写location ~* \.(js|css|...)$会报:
nginx: [emerg] using regex "..." requires PCRE library最终 configure(调通版)
sudoaptinstall-ylibpcre3-dev ./configure\--prefix=/usr/local/nginx\--with-http_ssl_module\--with-stream\--with-stream_ssl_module\--with-pcre\--with-pcre-jitmake-j$(nproc)当前工程内记录的编译参数(objs/ngx_auto_config.h):
#defineNGX_CONFIGURE" --prefix=/usr/local/nginx --with-http_ssl_module --with-stream --with-stream_ssl_module --without-http_rewrite_module --with-pcre --with-pcre-jit"验证:
objs/nginx-V# 应含 openssl 相关信息,且 ldd objs/nginx | grep -E 'ssl|crypto|gmssl'对 Nginx 官方源码src/的修改:无。国密能力完全来自链接 OCL 的 libssl,不是 nginx 补丁模块。
四、Docker 工程修改(本仓库nginx/docker/)
采用「WSL 编好二进制 + 拷贝 .so → 镜像只打包」策略,不在镜像内重编三件套。
4.1docker/prepare.sh(打包动态库)
将 sibling 目录编译产物拷入vendor/lib:
../GmSSL/build/bin/libgmssl.so* ../OpenSSL-Compatibility-Layer/build/libssl.so* ../OpenSSL-Compatibility-Layer/build/libcrypto.so*4.2Dockerfile要点
- 基础镜像:
ubuntu:24.04 - 安装运行时依赖:
libpcre3、zlib1g(不在镜像里装开发包) COPY objs/nginx→/usr/local/nginx/sbin/nginxENV LD_LIBRARY_PATH=/usr/local/libEXPOSE 80 4443(生产映射-p 443:443时由宿主机nginx.conf决定 listen)
4.3docker-entrypoint.sh修改(重要)
修改前(旧镜像逻辑,导致容器秒退):
if[!-f/certs/tlcp_server_certs.pem]||[!-f/certs/tlcp_server_keys.pem];thenecho"error: TLCP certs required under /certs (see docker/README.md)"exit1fi生产挂载为/etc/nginx/certs/,文件名也可能是sign-fullchain.pem等,与/certs/tlcp_server_*.pem不一致 →entrypoint 直接 exit 1。
修改后(当前文件):
#!/bin/shset-eNGINX=/usr/local/nginx/sbin/nginx ldconfig2>/dev/null||trueif["$1"="$NGINX"]||["$(basename"$1"2>/dev/null)"="nginx"];then"$NGINX"-tfiexec"$@"仅做nginx -t,不再硬编码证书路径。
4.4docker/default.conf修改
修改前:
ssl_certificate /certs/tlcp_server_certs.pem; ssl_certificate_key /certs/tlcp_server_keys.pem; ssl_password_file /certs/tlcp_server_password.txt;修改后:
ssl_certificate /etc/nginx/certs/tlcp_server_certs.pem; ssl_certificate_key /etc/nginx/certs/tlcp_server_keys.pem; ssl_password_file /etc/nginx/certs/tlcp_server_password.txt;与docker-compose.yml挂载./certs:/etc/nginx/certs:ro一致。
4.5 生产docker run(相对最初命令的修正)
dockerrun-d\--namenginx-gm\-p443:443\-v/opt/nginx/certs:/etc/nginx/certs:ro\-v/opt/nginx/conf/nginx.conf:/usr/local/nginx/conf/nginx.conf:ro\-v/opt/nginx/html:/etc/nginx/html:ro\-v/opt/nginx/logs:/usr/local/nginx/logs\nginx-gmssl:1.25.3| 最初写法 | 问题 |
|---|---|
--name nginx-gm重复 | 命令非法或覆盖 |
logs→/var/log/nginx:ro | 前缀应为/usr/local/nginx/logs,且不能只读 |
cache无关只读挂载 | 与 nginx 无关,易误导 |
| 旧 entrypoint + 旧镜像 | 证书未放到/certs/tlcp_server_*.pem即退出 |
五、生产nginx.conf与证书(运维侧修改)
5.1 证书文件(OCL 要求,非 Nginx 官方格式)
服务器侧三个文件(/opt/nginx/certs/):
| 文件 | 内容顺序 |
|---|---|
tlcp_server_certs.pem | 终端签名证→ 终端加密证→中间 CA(不要根证) |
tlcp_server_keys.pem | 签名加密私钥→加密加密私钥(两段ENCRYPTED PRIVATE KEY) |
password.txt | 一行口令,无\r |
生成示例(与 OCL README 一致):
catsigncert.pem enccert.pem cacert.pem>tlcp_server_certs.pemcatsignkey.pem enckey.pem>tlcp_server_keys.pemprintf'%s\n''Ilove@cn123'>password.txt证书处理上的“修改”(非改 C 代码):
- 厂商未加密私钥(
BEGIN PRIVATE KEY)→ 用gmssl pkcs8 -topk8加密后再合并。 - 厂商分体 fullchain(
sign-fullchain.pem+enc-fullchain.pem)→ 改为 OCL 推荐的单证链 + 双钥文件。 password.txt与密钥口令不一致(曾用12345678而密钥为Ilove@cn123)→ 统一口令并cat -A检查无^M。
5.2 生产server块(最终)
server { listen 443 ssl; server_name 10.131.111.250; ssl_certificate /etc/nginx/certs/tlcp_server_certs.pem; ssl_certificate_key /etc/nginx/certs/tlcp_server_keys.pem; ssl_password_file /etc/nginx/certs/password.txt; ssl_ecdh_curve sm2p256v1; ssl_ciphers ECDHE-SM2-WITH-SMS4-SM3:ECDHE-SM2-WITH-SMS4-GCM-SM3; ssl_prefer_server_ciphers on; location / { root /etc/nginx/html; index index.html index.htm; } }相对 CSDN 参考文/初稿删除或禁止的项:
# 错误 — Nginx 配置解析不认 GMTLS ssl_protocols GMTLS TLSv1.2; # 错误 — OCL 双钥加密场景不要删 # ssl_password_file ...说明:TLCP 由 GmSSL 在libssl内完成,不需要也不能在ssl_protocols里写GMTLS。
5.3 OCL 读私钥逻辑(理解即可,解释为何必须加密+口令)
OCLsrc/pem.c中PEM_read_bio_PrivateKey核心逻辑(v0.8.1 / main 同类):
// 必须有 password 回调if(!bio||!cb||!u){error_print();returnNULL;}cb(pass,sizeof(pass),0,u);// 同一 BIO 连续读两把 SM2 私钥sm2_private_key_info_decrypt_from_pem(&pkey->signkey,pass,bio);sm2_private_key_info_decrypt_from_pem(&pkey->kenckey,pass,bio);因此:
- 必须配置
ssl_password_file; tlcp_server_keys.pem必须是PKCS#8 加密格式(BEGIN ENCRYPTED PRIVATE KEY);- 两把钥同一口令。
六、GmSSL 命令行版本问题(WSL 调试)
6.1 现象
gmssl tlcp_client-get/-host10.131.111.250-port443-cacertrootcacert.pem# /mnt/d/codes/GmSSL/src/tls.c:2829:tls_init():# tlcp_client: error此时服务器docker exec nginx-gm nginx -t已successful,nc443 通。
6.2 原因
WSL 中/usr/local/bin/gmssl若来自GmSSL master,tls_init()会检查key_exchange_modes != 0,而tlcp_client未设置 supported_groups / signature_algorithms,导致客户端在握手前就失败。
v3.1.1的tls_init无此检查,且会正确设置conn->is_client = ctx->is_client。
6.3 处理(版本切换,非改 tls.c)
cd/mnt/d/codes/GmSSLgitcheckout v3.1.1rm-rfbuild&&mkdirbuild&&cdbuild&&cmake..&&make-j$(nproc)sudomakeinstall6.4 不要用这些方式判断服务是否存活
| 工具 | 结果 | 原因 |
|---|---|---|
curl https://IP:443 | PR_END_OF_FILE_ERROR | 国际 TLS vs TLCP |
openssl pkey -in tlcp_server_keys.pem | unable to load key | 系统 OpenSSL 不支持 SM2 |
七、修改项汇总表(便于 CSDN 读者对照)
| 序号 | 对象 | 是否改上游 C 源码 | 修改内容 |
|---|---|---|---|
| 1 | GmSSL | 否 | 固定tag v3.1.1,不用 master 跑tlcp_client |
| 2 | OCL | 否(可选 tlcp.c 见 Issue #13) | 固定tag v0.8.1;理解pem.c双钥+解密 |
| 3 | Nginx | 否 | configure 增加--with-pcre --with-pcre-jit |
| 4 | docker-entrypoint.sh | 项目脚本 | 去掉/certs/tlcp_server_*.pem强校验,仅nginx -t |
| 5 | docker/default.conf | 项目配置 | 证书路径/certs→/etc/nginx/certs |
| 6 | 生产nginx.conf | 运维配置 | listen 443;删GMTLS;OCL 三件套路径 |
| 7 | 证书文件 | 运维 | 合并证链/双钥;加密私钥;password.txt去 CRLF |
| 8 | docker run | 运维 | 日志可写、去掉错误 volume、更新镜像 |
八、验证通过标准(本环境)
# 1. 编译链ldd /usr/local/nginx/sbin/nginx|grepgmssl objs/nginx-V# 2. 容器dockerexecnginx-gm /usr/local/nginx/sbin/nginx-t# configuration file ... test is successful# 3. 监听(镜像可能无 ss)dockerexecnginx-gmsh-c'cat /proc/net/tcp'|grep01BB# 443# 4. 客户端(GmSSL v3.1.1)gmssl tlcp_client-get/-host10.131.111.250-port443-cacertrootcacert.pem# 5. 浏览器# 国密浏览器 + 导入 rootcacert.pem → https://10.131.111.250/九、发布 CSDN 时的建议标签
Nginx国密GmSSLTLCPOpenSSL-Compatibility-LayerSM2DockerHTTPS
十、参考链接
- GmSSL/OpenSSL-Compatibility-Layer README
- guanzhi/GmSSL(Tagv3.1.1)
- GmSSL Issue #1156 - invalid value GMTLS
- OCL Issue #13 - 浏览器访问国密 nginx
- 本人部署踩坑合集:同目录
nginx-gmssl-部署总结.md
版权声明:版本号与修改记录基于 2026 年 5 月实际调试环境整理;若上游 tag 更新,请以各项目 Release 说明为准,并重新做nginx -t与tlcp_client回归。
