简介:ZXCA自信数字证书制作工具面向个人用户与小型组织,提供一套可自行搭建证书签发与管理流程的实用方案,适合希望理解数字证书原理、练习公钥私钥配对与证书生命周期管理的初学者及运维人员。该工具集覆盖证书申请、签发、撤销、导入导出等环节,支持 PEM、DER 等常见格式转换,并提供文件加密与代码签名能力;配套的“文件小保镖”组件侧重文件的加密存储、安全分享、权限控制与备份恢复,可满足日常敏感数据的保护需求。压缩包共 8 个文件,以 4 个 txt 说明文档、2 个 exe 可执行程序、1 个 chm 帮助手册和 1 个 html 页面为主,整体约 4.84MB,体积轻便、解压即用。目前已有 628 人学习下载。读者可从中获得一套较完整的证书制作与文件保护实践路径,借助说明文档与帮助手册快速上手,理解 CA 角色模拟、证书格式转换以及加密分享的实现思路,为后续在真实网络环境中部署身份认证与数据加密打下基础。
1. 内网服务的证书从哪来:ZXCA 解决的是一段没人愿意手敲的命令
某公司内网一套管理后台要改成 HTTPS,域名只在内网 DNS 里解析,公有 CA 签不了,只能自签。第一次照着博客手敲 openssl,参数里漏了 SAN,浏览器照旧红锁;第二次补上 SAN,Java 客户端又报PKIX path building failed。ZXCA 这类自签名数字证书制作工具,处理的就是这条链路上散落的十几个参数:生成根 CA、签发服务端与客户端证书、按目标平台导出 PEM、PFX、JKS,并且把 SAN、KeyUsage 这些真正决定证书能不能用的字段提到台面上。它适合做私有化部署、内网联调、设备预置证书、mTLS 服务间鉴权的团队,不适合面向公网 C 端用户的站点——那类场景必须走公有 CA。
2. X.509 与信任链:自签名证书到底"签"了什么
很多人对自签名的第一反应是"不安全",其实这个判断只对了一半。自签名证书在密码学强度上和公有 CA 签出来的证书没有区别,用的是同一套 RSA、ECDSA 和 SHA-256;差别只在于——它的签发者不在任何系统内置的信任库里。理解这一点,后面所有的参数配置才有落脚点。
2.1 一张证书的物理结构
X.509 v3 证书在 DER 编码下就是三段:tbsCertificate(待签名数据)、signatureAlgorithm(签名算法标识)、signatureValue(签名值)。真正承载信息的是第一段,里面装着版本号、序列号、签发者 DN、有效期、主体 DN、主体公钥,以及一串扩展字段。
签名的生成过程是:把tbsCertificate做 DER 编码,对这个字节流求哈希,再用签发者私钥对哈希值做签名运算,结果放进signatureValue。这意味着任何一个字段改动一个比特,签名校验就会失败。序列号重复、有效期被改、SAN 被塞进新域名,都会立刻暴露。这也是为什么证书一旦签发就不该手改,要改就重新签——我见过有人用文本编辑器直接往 PEM 里加域名,最后排查了两天才发现是这么回事。
issuer和subject字段在根 CA 证书里是相同的,这就是"自签名"这个说法的字面来源:自己给自己签。
2.2 信任链:信任来自"被装进信任库"这件事
验证方拿到一张叶子证书时,做的事情是:取出issuer,去本地信任库或随请求带来的证书集合里找同名subject的证书,用后者的公钥验证前者的签名;验证通过就继续往上找,直到某一环的证书本身就在信任库里——这个证书叫信任锚(trust anchor)。
自签名根 CA 能成为信任锚,靠的不是数学证明,而是它被显式导入到了目标机器的信任库。根 CA 的签名是它自己验自己,这在逻辑上是循环的,所以必须由外部给它一个"起点"身份。这也解释了为什么自签证书在你自己机器上配好之后一切正常,换一台没导入根证书的机器立刻报错——不是证书坏了,是链条断在了信任锚这一步。
一个常见的误判是"我把根证书和服务端证书合成一个文件就行了"。合并不等于导入。合并出来的链文件只是方便服务端在握手时把中间证书一起发出去,减少客户端去别处找的开销;信任锚仍然需要单独装进客户端的信任库。
2.3 决定证书能不能用的五个字段
真正让证书"能用"或"翻车"的,不是 CN,而是下面这几个扩展。ZXCA 的签发界面里这些字段都是显式的,这也是它比手敲命令省事的地方。
| 字段 | 典型取值 | 填错会怎样 |
|---|---|---|
basicConstraints | 根/中间 CA:critical, CA:TRUE;叶子证书:critical, CA:FALSE | 叶子证书标成 CA:TRUE 会被部分客户端拒绝;CA 证书漏了 CA:TRUE 则签出的下级全部验不过 |
keyUsage | 根 CA:critical, keyCertSign, cRLSign;服务端:digitalSignature, keyEncipherment | 根 CA 少了keyCertSign,用它签的所有证书链验证直接失败 |
extendedKeyUsage | 服务端serverAuth;客户端clientAuth;两者都要就都写 | 做 mTLS 时客户端证书只写了 serverAuth,服务端会拒绝握手 |
subjectAltName | DNS:app.internal, IP:10.20.30.40 | 现代浏览器和主流 HTTP 客户端完全忽略 CN,只认 SAN;漏了就是ERR_CERT_COMMON_NAME_INVALID |
validity | 内部服务建议 397 天以内,根 CA 可以 5~10 年 | 有效期过长会导致轮换无人推动,到期那天集中爆炸 |
keyUsage里keyEncipherment这个位,在 ECDSA 证书上是没有意义的(ECDSA 不做密钥加密),只在 RSA 密钥交换场景需要。用 EC 密钥还硬写keyEncipherment,个别严格的校验实现会报不一致。ZXCA 在选算法时会联动这个字段,手敲命令的话得自己记得。
2.4 什么时候自签够用,什么时候必须走公有 CA
判断标准其实只有一条:谁来做信任锚。
如果证书的消费方是你能控制的——内网服务器、公司配发的笔记本、出厂预置的设备、Kubernetes 集群内部的 sidecar——那么把自签根 CA 装进这些机器的信任库是完全可行的,运维成本可控,也没有中间人风险,因为根私钥就在你自己手里。
如果消费方是不可控的——公网用户的浏览器、应用商店的审核、第三方支付回调、任何你没法要求对方装根证书的场景——那就必须公有 CA。自签在这些地方一定会被拦,而且拦得毫无商量余地。
还有一类灰色地带:内部系统但用户量大、设备杂。这种情况我一般会先算一笔账——把根证书推到几百台设备上并保证不被用户手动删掉,成本往往比申请一张公有 CA 证书高得多。
3. ZXCA 落地:从根 CA 到服务端证书的完整签发流程
工具本身不难,难的是把一整套文件的命名、权限、有效期、扩展字段一次定对。下面这套流程是我在几个私有化项目里磨出来的,ZXCA 的操作路径和它一一对应;如果你手上没有工具,同样的参数用 openssl 也能复刻,我会把等价命令一并给出,方便对照参数含义。
3.1 先定目录和命名规范
这一步听起来琐碎,但它是后面所有问题的根源。证书文件一旦混在一起,三个月后你根本分不清哪个 key 配哪个 crt。我一般的结构是这样:
mkdir -p pki/{ca,issued,export} chmod 700 pki pki/ca下面这些约定值得硬性执行:
- 根 CA 私钥
pki/ca/ca.key权限设600,只有签发机上的特定账号能读,绝不进代码仓库; - 每张叶子证书一个子目录,目录名就是 CN,里面放
xxx.key、xxx.crt、xxx.fullchain.crt、xxx.ext; .ext文件必须保留。半年后要重新签发同一张证书时,看这个文件比看证书内容快得多;- 导出给业务方的格式统一放
pki/export/,按"目标平台/证书名"两级分。
提示:私钥权限这一步不要省。证书本身是公开信息,泄露无所谓;私钥泄露等于身份被冒用,而且是悄无声息的那种。
3.2 建根 CA:自签那一步的四个必填项
根 CA 只签一次,签完之后私钥就应该离线保存。ZXCA 里对应的是"新建根证书"这一项,需要填主体 DN、密钥算法、有效期,以及是否允许签发下级 CA。
等价的 openssl 命令如下,用 EC P-384 是因为密钥短、握手快,现代客户端支持度也已经足够:
# 生成根 CA 私钥,EC P-384 曲线 openssl genpkey -algorithm EC \ -pkeyopt ec_paramgen_curve:P-384 \ -out pki/ca/ca.key chmod 600 pki/ca/ca.key # 自签根证书,有效期 10 年 openssl req -x509 -new -key pki/ca/ca.key -sha256 -days 3650 \ -subj "/C=CN/O=Demo Lab/CN=Demo Lab Root CA" \ -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ -addext "subjectKeyIdentifier=hash" \ -out pki/ca/ca.crt参数逐条解释:
-x509表示直接输出自签证书而不是 CSR,这是根 CA 和普通证书最本质的区别;-days 3650给根 CA 十年有效期。根 CA 到期意味着所有下级证书要重签,尽量往后放;basicConstraints=critical,CA:TRUE,pathlen:0里的pathlen:0限定它下面只能挂叶子证书,不能再签中间 CA。如果你规划了中间 CA 层,这里要改成pathlen:1;keyUsage里的keyCertSign是根 CA 的命门,少了它签出来的证书链验证必失败;cRLSign是将来发 CRL 用的,可以先留着;critical关键字让不认识该扩展的客户端必须拒绝这张证书,属于安全默认值。
签完立刻验一下:
openssl x509 -in pki/ca/ca.crt -noout -subject -issuer -dates openssl x509 -in pki/ca/ca.crt -noout -text | grep -A1 "Basic Constraints"第二条命令应该输出CA:TRUE,如果显示CA:FALSE,说明扩展没生效,多半是命令里-addext拼写或者 OpenSSL 版本太老不支持该参数。
3.3 签发服务端证书:SAN 是硬指标
从这一步开始就是日常操作了。给一个内网服务签发证书,需要三样东西:私钥、CSR、带扩展的签发配置。
CN=app.internal # 服务端私钥 openssl genpkey -algorithm RSA \ -pkeyopt rsa_keygen_bits:2048 \ -out pki/issued/$CN/$CN.key # 生成 CSR openssl req -new -key pki/issued/$CN/$CN.key \ -subj "/C=CN/O=Demo Lab/CN=$CN" \ -out pki/issued/$CN/$CN.csrCSR 里的 CN 现在只是个标识,真正决定客户端认不认的是 SAN。下面这个扩展文件是核心:
cat > pki/issued/$CN/$CN.ext <<'EOF' basicConstraints=critical,CA:FALSE keyUsage=critical,digitalSignature,keyEncipherment extendedKeyUsage=serverAuth subjectAltName=DNS:app.internal,DNS:*.app.internal,IP:10.20.30.40 subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer EOFsubjectAltName这一行要按实际访问方式写全:域名、通配域名、IP 三类都可能有。某公司内网就遇到过一次,证书只写了DNS:app.internal,但监控系统是通过 IP 直连的,于是监控采集全部报证书错误——补IP:条目才解决。
authorityKeyIdentifier=keyid,issuer这行容易被忽略,它的作用是把签发者的密钥标识写进证书,让验证方在链里快速定位上一级。链一长,缺了这个字段的证书在部分 Java 版本上会报"找不到签发者"。
签发:
openssl x509 -req -in pki/issued/$CN/$CN.csr \ -CA pki/ca/ca.crt -CAkey pki/ca/ca.key -CAcreateserial \ -days 397 -sha256 \ -extfile pki/issued/$CN/$CN.ext \ -out pki/issued/$CN/$CN.crt # 拼出完整链,顺序必须是 叶子 → 中间 → 根 cat pki/issued/$CN/$CN.crt pki/ca/ca.crt > pki/issued/$CN/$CN.fullchain.crt-CAcreateserial会自动维护一个ca.srl序列号文件,同一个 CA 多次签发时序列号递增,不会重复。如果你把这个文件删了,序列号会从头开始,理论上可能出现重复序列号——虽然概率低,但在有 CRL 的场景下会引发吊销混乱,所以ca.srl要和 CA 私钥一起备份。
3.4 签发客户端证书:mTLS 的另一半
双向 TLS 的场景下,服务端也要验客户端。客户端证书和服务端证书的唯一实质差别在extendedKeyUsage:
openssl x509 -req -in pki/issued/client-a/client-a.csr \ -CA pki/ca/ca.crt -CAkey pki/ca/ca.key -CAcreateserial \ -days 397 -sha256 \ -extfile <(printf '%s\n' \ "basicConstraints=critical,CA:FALSE" \ "keyUsage=critical,digitalSignature" \ "extendedKeyUsage=clientAuth" \ "subjectKeyIdentifier=hash" \ "authorityKeyIdentifier=keyid,issuer") \ -out pki/issued/client-a/client-a.crt注意keyUsage只留了digitalSignature。客户端证书在 TLS 握手里只做签名证明,不参与密钥加密,所以keyEncipherment不需要。ZXCA 在签发客户端证书时会自动切换这组扩展,手敲命令的话这里是高频错点——写成 serverAuth 的客户端证书,服务端会直接拒绝握手,日志里通常只有一句含糊的certificate verify failed。
3.5 签发完立刻跑三条验证
证书生成成功和证书可用是两件事。签完我固定跑这三条:
# 1. 链验证 + 主机名校验 openssl verify -CAfile pki/ca/ca.crt \ -verify_hostname app.internal \ pki/issued/app.internal/app.internal.crt # 2. 看扩展是否落到位 openssl x509 -in pki/issued/app.internal/app.internal.crt -noout -text \ | grep -E "Subject Alternative Name|Extended Key Usage|Key Usage" -A1 # 3. 实际握手(服务起来之后) openssl s_client -connect app.internal:443 \ -servername app.internal \ -CAfile pki/ca/ca.crt </dev/null第一条输出OK才算过。第二条要能看到 SAN 里有你填的全部条目。第三条如果最后一行是Verify return code: 0 (ok),说明从 TCP 到证书链全线通;如果出现unable to get local issuer certificate,多半是服务端没配链文件,只发了叶子证书。
4. 分发与部署:格式转换和服务端挂载
证书签出来只是半成品,能塞进目标服务里才算完成。这一步最容易出问题的不是配置本身,而是格式——PEM、PFX、JKS 三种装的是同一套密钥材料,但不同平台的偏好完全不同,给错了对方连导入都失败。
4.1 四种格式分别给谁
| 格式 | 扩展名 | 内容 | 典型消费方 |
|---|---|---|---|
| PEM | .crt/.key | Base64 文本,证书与私钥分开 | Nginx、Apache、Go、Node、绝大多数 Linux 服务 |
| DER | .der/.cer | 二进制编码,通常是单张证书 | 部分设备固件、嵌入式模块 |
| PKCS#12 | .pfx/.p12 | 证书 + 私钥 + 链,单个加密文件 | Windows 证书管理器、.NET、浏览器导入 |
| JKS | .jks | Java 私有的密钥库格式 | Tomcat、Spring Boot、传统 Java 客户端 |
导出 PFX,注意要把完整链一起打进去,否则导入方看到的还是单张证书:
openssl pkcs12 -export \ -in pki/issued/app.internal/app.internal.fullchain.crt \ -inkey pki/issued/app.internal/app.internal.key \ -certfile pki/ca/ca.crt \ -name app-internal \ -out pki/export/app.internal.p12-name是给这个条目起的别名,Windows 导入后会显示成"友好名称",Java 侧则用它来索引。别名起得含糊,导入十个证书之后就分不清了,建议统一用主机名。
再转 JKS(需要 JDK 里的 keytool):
keytool -importkeystore \ -srckeystore pki/export/app.internal.p12 -srcstoretype PKCS12 \ -destkeystore pki/export/app.internal.jks -deststoretype JKS \ -deststorepass changeit -destkeypass changeitchangeit只是个占位口令,生产环境必须换成强口令,并且通过环境变量或密钥管理服务注入,不要写死在启动脚本里。
4.2 Nginx 挂载:链文件顺序是死规矩
Nginx 的ssl_certificate指向的文件里,证书顺序必须是"叶子在最前,上级依次往后"。顺序反了,部分客户端在握手时会拿到一张对不上号的证书,报错信息还极其含糊。
server { listen 443 ssl; server_name app.internal; ssl_certificate /etc/nginx/certs/app.internal.fullchain.crt; ssl_certificate_key /etc/nginx/certs/app.internal.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # 双向 TLS:要求客户端出示证书 ssl_client_certificate /etc/nginx/certs/ca.crt; ssl_verify_client on; ssl_verify_depth 2; }几个参数值得单独说:
ssl_verify_depth 2表示允许两级链(叶子 → 中间 → 根)。如果你的结构只有根 CA 直接签叶子,写1就够;写太小会把合法证书挡在外面,写太大则放宽了链长度限制;ssl_client_certificate填的是签发客户端证书的那张 CA 证书,不是客户端证书本身,这里填错是最常见的配置事故;- 私钥文件如果带口令,Nginx 启动时会卡在交互输入,容器里直接起不来。要么用无口令私钥并把文件权限收紧,要么用
ssl_password_file指定口令文件。
4.3 Java 侧:keystore 和 truststore 不是一回事
这两个概念混淆导致的排查时间,比证书本身所有问题加起来还多。
- keystore:装自己的私钥和证书,服务端用来向别人证明"我是谁";
- truststore:装你愿意信任的 CA 证书,用来验证"对方是谁"。
双向 TLS 场景下两个都要配,缺一个就握不上手。
# 把根 CA 导入 truststore keytool -importcert \ -alias demo-root-ca \ -file pki/ca/ca.crt \ -keystore pki/export/truststore.jks \ -storepass changeit -noprompt # 确认导入成功 keytool -list -keystore pki/export/truststore.jks -storepass changeitJVM 层面的配置:
java -jar app.jar \ -Djavax.net.ssl.keyStore=/opt/app/app.internal.jks \ -Djavax.net.ssl.keyStorePassword=changeit \ -Djavax.net.ssl.trustStore=/opt/app/truststore.jks \ -Djavax.net.ssl.trustStorePassword=changeit如果不想动全局 JVM 参数,Spring Boot 里也可以按连接器单独配server.ssl.key-store和server.ssl.trust-store,效果一样但作用域更小。我一般倾向后者——全局参数会影响所有出站连接,包括调用第三方接口,容易引发意料之外的信任变化。
4.4 把根证书装进系统信任库
自签证书要在客户端机器上不报警告,根 CA 必须进系统信任库。各平台命令不同,但思路一致:
# Debian / Ubuntu 系 sudo cp pki/ca/ca.crt /usr/local/share/ca-certificates/demo-lab-root-ca.crt sudo update-ca-certificates # RHEL / CentOS 系 sudo cp pki/ca/ca.crt /etc/pki/ca-trust/source/anchors/demo-lab-root-ca.crt sudo update-ca-trust extract # macOS(需管理员权限) sudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain pki/ca/ca.crtWindows 上通过certutil -addstore -f Root ca.crt导入到"受信任的根证书颁发机构"。这里有个细节:Linux 的update-ca-certificates只影响使用系统证书库的程序(curl、wget、大部分 OpenSSL 应用),Java 有自己独立的cacerts,Python 的 requests 在部分发行版上也有独立路径。装完系统库不等于所有程序都认,这一点必须有心理准备。
5. 排查:自签名证书五类高频翻车现场
下面这五条基本覆盖了我在实际项目里遇到的八成问题。每一条都按"看到什么 → 为什么 → 怎么改"写,方便对号入座。
5.1 浏览器红锁,提示名称不匹配
现象:证书明明装好了,浏览器地址栏还是红锁,展开详情写着证书对当前域名无效。
原因:证书里只有 CN,没有subjectAltName扩展,或者 SAN 里写的是app.internal而实际访问用的是 IP 或者另一个别名。
解决:重新签发,把访问路径上所有可能的名称都写进 SAN。这里不要图省事用通配符*.internal一把梭——通配符只匹配一级子域,且部分场景(比如多级域名)会失效。宁可多写几条具体条目。
5.2 Java 客户端报 PKIX path building failed
现象:同一个接口,curl 调通,Java 程序调用报sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target。
原因:Java 用的是自己的cacerts信任库,系统信任库里装了根 CA 对它没有任何影响。
解决:用 keytool 把根 CA 导入 Java 的 truststore,然后通过-Djavax.net.ssl.trustStore指过去,或者导入 JDK 自带的$JAVA_HOME/lib/security/cacerts。后者会影响这台机器上所有 Java 程序,改动前要评估。
5.3 Nginx 起不来或握手直接失败
现象:nginx -t通过,systemctl start失败,日志里SSL_CTX_use_PrivateKey相关报错。
原因:三种可能——私钥和证书不配对、私钥带口令、私钥文件权限不对导致 Nginx worker 读不了。
解决:先比对公私钥模数:
openssl x509 -in server.crt -noout -pubkey | openssl md5 openssl pkey -in server.key -pubout | openssl md5两个哈希值必须一致,不一致说明文件和证书根本不是一对,多半是签发时搞混了目录。一致的话再检查私钥是否加密:
head -3 server.key | grep -i "ENCRYPTED"有输出就是带口令的,需要去掉:openssl pkey -in server.key -out server-nopass.key。
5.4 移动端或小程序请求失败,服务端日志里没有记录
现象:PC 浏览器正常,移动端 App 或小程序请求直接超时,服务端日志干净得像什么都没发生。
原因:证书链不完整。服务端只发了叶子证书,没有带中间证书或根证书,PC 端浏览器会自动去下载缺失的中间证书补上,移动端不做这个兜底,直接断连。
解决:确认ssl_certificate指向的是fullchain.crt而不是单张server.crt,并用openssl s_client -connect host:443 -showcerts检查服务端实际发出的证书张数,正常应该是两张以上。
5.5 证书导入后仍提示吊销状态未知
现象:Windows 或部分浏览器提示"无法检查服务器证书的吊销状态",业务其实能用,但每台机器都要点一次继续,用户体验极差。
原因:证书里带了 CRL 分发点或 OCSP 地址,但自签场景下这些地址根本不存在,客户端查不到结果就报警告。
解决:签发时不要在扩展里写crlDistributionPoints和authorityInfoAccess。ZXCA 默认不会加这两项,但如果是从别的配置模板复制过来的扩展文件,很容易带进去。检查方法:
openssl x509 -in server.crt -noout -text | grep -E "CRL Distribution|Authority Information"没有输出就是干净的。
6. 进阶:把签发脚本化,以及上线前的一次性验证
单张证书用 ZXCA 点几下就出来了,但一个微服务集群动辄几十个域名,逐张点会疯。我的做法是:用工具把根 CA 和第一个服务端证书走通,确认参数组合正确,然后把这套参数固化成脚本批量跑,新服务上线只改一行。
#!/usr/bin/env bash set -euo pipefail CA_CRT="pki/ca/ca.crt" CA_KEY="pki/ca/ca.key" OUT="pki/issued" DAYS=397 issue_server_cert() { local cn="$1" local san="$2" local dir="$OUT/$cn" mkdir -p "$dir" openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \ -out "$dir/$cn.key" 2>/dev/null chmod 600 "$dir/$cn.key" openssl req -new -key "$dir/$cn.key" \ -subj "/C=CN/O=Demo Lab/CN=$cn" -out "$dir/$cn.csr" { echo "basicConstraints=critical,CA:FALSE" echo "keyUsage=critical,digitalSignature,keyEncipherment" echo "extendedKeyUsage=serverAuth" echo "subjectAltName=$san" echo "subjectKeyIdentifier=hash" echo "authorityKeyIdentifier=keyid,issuer" } > "$dir/$cn.ext" openssl x509 -req -in "$dir/$cn.csr" \ -CA "$CA_CRT" -CAkey "$CA_KEY" -CAcreateserial \ -days "$DAYS" -sha256 -extfile "$dir/$cn.ext" \ -out "$dir/$cn.crt" cat "$dir/$cn.crt" "$CA_CRT" > "$dir/$cn.fullchain.crt" openssl verify -CAfile "$CA_CRT" -verify_hostname "$cn" "$dir/$cn.crt" } issue_server_cert "app.internal" "DNS:app.internal,DNS:*.app.internal,IP:10.20.30.40" issue_server_cert "api.internal" "DNS:api.internal,IP:10.20.30.41"这个脚本的关键点是末尾那句openssl verify——签发失败必须立刻让脚本退出。set -euo pipefail配合它,能保证流水线里不会悄悄产出一张坏证书然后被打包进镜像。
上线前我固定过一遍这张检查表:
| 检查项 | 命令 / 方法 | 期望结果 |
|---|---|---|
| 链完整性 | openssl verify -CAfile ca.crt -verify_hostname <host> <crt> | 输出OK |
| SAN 覆盖 | openssl x509 -noout -ext subjectAltName -in <crt> | 包含所有实际访问方式 |
| 有效期 | openssl x509 -noout -dates -in <crt> | 到期日与轮换计划对得上 |
| 公私钥配对 | 比对-pubkey的 md5 | 两个哈希一致 |
| 服务端实发链 | openssl s_client -showcerts -connect host:443 | 至少两张证书,顺序正确 |
| 信任锚导入 | 目标机跑curl -v https://host | 无证书警告 |
最后说个血泪教训。早些年我把一张自签证书的有效期设成了十年,心想省事。结果两年后要换密钥算法,发现所有客户端都已经把这张证书硬编码进信任配置,换证书等于全线停机改造。从那以后我每次签发前都强制走一遍有效期检查,内部服务一律不超过 397 天,配合自动轮换脚本,宁可多跑几次流水线,也不给未来埋雷。希望帮到你。
本文还有配套的精品资源,点击获取