OpenSSL TLS、证书链与密钥验证工具手册
连接成功不等于证书可信
curl 报证书错误时,常见的第一反应是加 -k。这会让请求继续,却把最重要的判断一起抹掉:服务端究竟发了哪张证书,客户端是否拥有正确的信任锚,URL 中的主机名能否匹配 SAN。OpenSSL 适合做这类首诊,因为它能把 TLS 握手、证书对象和离线构链拆开观察。
先确认当前终端调用的是哪一个构建。OpenSSL 可能来自系统、包管理器、Git for Windows、语言运行时或容器镜像;相同命令名不代表相同配置目录和 provider。
command -v openssl
openssl version -a
openssl list -providersversion -a 会给出版本、平台、编译选项、OPENSSLDIR 和模块目录。OpenSSL 4.0 是常规发行线,3.5 是 LTS 发行线;项目应根据操作系统供应商、FIPS 要求和依赖兼容选择分支,而不是在脚本里假定所有机器都是最新版。官方发行生命周期和下载页是判断支持状态的入口。
用严格握手同时验证 SNI、链和主机名
下面这条命令刻意同时给出 TCP 目标、SNI、信任锚和期望主机名:
openssl s_client \
-connect 127.0.0.1:8443 \
-servername dev.local.test \
-CAfile certs/dev-root.pem \
-verify_return_error \
-verify_hostname dev.local.test \
-brief </dev/null-connect 解决“连到哪里”,-servername 决定 ClientHello 中的 SNI,虚拟主机据此选择证书。-CAfile 明确信任锚,-verify_hostname 检查服务身份,-verify_return_error 让验证错误真正中止握手。没有最后一个参数时,s_client 会尽量继续,以便展示更多诊断信息;这对人工排查有用,却不适合作为自动门禁的成功条件。s_client 官方手册明确区分了这两种行为。
成功输出会出现验证通过和协商协议。Connection refused 发生在 TLS 之前,应该回到监听端口和路由;issuer 相关错误说明构链缺材料或信任锚不对;hostname mismatch 说明证书已经可以构链,但 SAN 不包含当前访问名。这三类故障不能靠重复导入同一张根证书互相修复。
-showcerts 只打印服务端实际发送的证书列表,而且保持服务端发送顺序。它不是经过验证的证书链。把这条命令的输出截图当“证书正常”是很常见的误判:服务端可能漏发中间证书,列表中也可能存在无关证书,客户端仍需从自己的信任锚构造有效路径。
openssl s_client \
-connect service.example.test:443 \
-servername service.example.test \
-showcerts </dev/null需要经过 HTTP CONNECT 代理时,显式增加 -proxy proxy.example.test:3128。代理认证如果使用 Basic,凭据在 TLS 隧道建立前只经过可逆编码;口令应通过 OpenSSL 支持的受控 password source 提供,不能直接写入共享命令历史。
x509 读取证书,verify 构造路径
openssl x509 处理的是单张证书。它适合确认 subject、issuer、序列号、SHA-256 指纹、SAN、用途和有效窗口,不负责证明服务端正在使用这张文件。
openssl x509 -in certs/server.pem -noout \
-subject -issuer -serial -fingerprint -sha256 \
-ext subjectAltName -ext extendedKeyUsage -dates如果文件是 DER 编码,增加 -inform DER;不要仅凭 .cer、.crt 或 .pem 扩展名推断实际编码。证书更新时至少对比指纹、SAN、issuer、序列号和公钥,避免把“文件改了”误当成“身份按预期轮换”。
离线链验证由 openssl verify 完成。根证书放在 -CAfile,中间证书作为不受信的构链材料交给 -untrusted,叶子证书放在最后:
openssl verify -show_chain -purpose sslserver \
-CAfile certs/root-ca.pem \
-no-CApath -no-CAstore \
-untrusted certs/intermediate-ca.pem \
-verify_hostname service.example.test \
certs/server.pemverify 手册把 -untrusted 定义为只参与构链、并不自动成为信任锚的证书来源。删除它后若稳定出现 issuer 错误,就证明服务或证据包缺少中间证书。反过来,把中间 CA 直接塞进 -CAfile 可能让实验通过,却改变了组织原本要求的信任边界。
用 s_server 做一个可丢弃的正反实验
证书问题不必先接入完整框架。准备一张包含 dev.local.test SAN 的叶子证书和对应私钥后,可以在隔离目录启动最小 TLS 服务:
openssl s_server -accept 8443 -www \
-cert certs/dev.local.test.pem \
-key certs/dev.local.test-key.pem另一个终端先执行严格握手,再把期望名称改成证书中不存在的 api.local.test:
openssl s_client -connect 127.0.0.1:8443 \
-servername dev.local.test \
-CAfile certs/dev-root.pem \
-verify_return_error \
-verify_hostname api.local.test </dev/null如果结果是 hostname mismatch,说明信任锚和签名路径没有被破坏,失败只来自服务身份。再去掉 -CAfile 并在没有安装开发根证书的容器内运行,预期才应变成不受信错误。两个反例分别控制一个变量,比“浏览器能不能打开”更容易定位问题。
s_server 是测试工具,不是生产入口。它适合验证证书文件、TLS 参数和客户端行为,不承担热更新、访问日志、限流、进程守护或密钥托管。
证书和私钥必须证明是一对
文件名相同不构成密钥配对证据。可以分别导出叶子证书和私钥的公钥,转换成同一种 DER 表示后计算摘要:
openssl x509 -in certs/server.pem -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256
openssl pkey -in certs/server-key.pem -pubout -outform DER \
| openssl dgst -sha256
openssl pkey -in certs/server-key.pem -check -noout前两个摘要必须一致,第三条检查私钥内部一致性。加密私钥默认让命令交互询问口令,或者使用权限受控的文件、环境或 secret provider;-passin pass:... 会把口令暴露在命令历史、进程参数和日志中。
Java、Windows 和部分代理软件常使用 PKCS12 身份包。OpenSSL 可以把私钥、叶子证书和中间链装进同一个 .p12,但根证书通常不应作为服务身份的一部分发送:
openssl pkcs12 -export \
-inkey certs/server-key.pem \
-in certs/server.pem \
-certfile certs/intermediate-ca.pem \
-name server-tls \
-out .local/server.p12
openssl pkcs12 -in .local/server.p12 -info -noout-export 表示创建 PKCS12;没有它时,命令按解析输入包处理。pkcs12 手册还说明 -certfile 只是附加证书材料。旧系统如果只支持遗留算法,-legacy 可以帮助读取或导出,但这应被视为迁移信号,而不是给新制品的默认配置。
项目里保存验证方法,不保存密钥
仓库适合提交证书生成说明、测试域名、校验脚本和示例配置,不适合提交真实私钥、个人开发 CA 或生产证书包。脚本应从受控位置接收路径,并让失败保留非零退出码:
openssl x509 -in "$CERT_FILE" -noout -ext subjectAltName
openssl verify -CAfile "$CA_FILE" "$CERT_FILE"
openssl x509 -in "$CERT_FILE" -checkend "$RENEW_WINDOW_SECONDS" -noout
openssl pkey -in "$KEY_FILE" -check -noout续期窗口来自证书生命周期、变更审批和发布提前量,不能全公司共享一个没有容量依据的固定数字。对外服务还要从真实客户端网络执行严格 s_client,因为离线文件验证无法证明负载均衡器已经加载新证书,也无法发现某个节点仍在发送旧链。
本地开发证书可以交给 mkcert 管理;Java truststore、keystore 和 alias 生命周期应进入 keytool。OpenSSL 在这条链路中的职责是观察、转换和验证,不是替代组织 CA、自动续期平台或 Java 信任库管理器。
退出时清掉进程、文件和临时信任
先停止 s_server 和所有读取测试私钥的进程,再删除临时叶子证书、私钥、PKCS12 文件、导出的握手日志和证书列表。调试输出可能包含内部域名、证书主体、代理地址和会话信息,不能当普通构建日志长期保存。
如果实验曾把 CA 加进系统或运行时信任库,删除证书文件不等于撤销信任。必须回到真正持有状态的工具执行删除,并在新的客户端进程中重新握手,确认结果从成功变成明确的不受信错误。只有这个反证成立,清理才算完成。
