重要
自签证书和合法证书最核心的区别不是加密能力,而是客户端是否默认信任签发 CA。
合法 CA 的根证书通常已预装在操作系统或浏览器中;自建 CA 需要手动导入并信任。信任建立后,证书链校验逻辑一致。
本文按三层证书链说明自签发流程:
根 CA ↓ 签发 中间 CA ↓ 签发 服务端证书
| 类型 | 信任来源 | 说明 |
|---|---|---|
| 根 CA 证书 | 手动导入客户端信任库 | 是整条自建证书链的信任锚 |
| 中间 CA 证书 | 由根 CA 签发 | 用于签发服务端证书,降低根 CA 私钥暴露风险 |
| 服务端证书 | 由中间 CA 签发 | 证书必须包含 SAN(Subject Alternative Name),否则域名/IP 校验可能失败 |
| CA 私钥 | 签发证书的根权限 | 根 CA / 中间 CA 私钥必须严格保护,不能放进 K8s Secret、Pod 或镜像 |
1.简介
内网环境、测试环境、私有域名,经常需要自己签发 TLS 证书。
常见场景:
- 本地开发环境开启 HTTPS;
- 内网服务通过 Ingress 暴露;
- 私有域名没有公网 DNS 或不方便申请公网证书;
- 需要验证 TLS、mTLS、证书链等配置。
2.原理
2.1 前置理论:签名原语
签名原语只有一种操作:
| |
验签通过,同时证明:原文未被篡改 + 签名方持有对应私钥。
证书体系中每一次签名,都是这个原语的调用。差异只在:谁签、签什么数据、验签发生在哪个环节。
2.2 签发流水线
CSR(Certificate Signing Request,证书签名请求)不是运行时证书链的一部分,而是申请证书时交给 CA 的材料。
证书链中,每一轮签发都调用签名原语两次:
| 步骤 | 签名方 | 私钥 | 签什么 | 验签方 | 验签时机 |
|---|---|---|---|---|---|
| CSR 自签名 | 申请者 | subject.key | CSR 正文 | CA | 签发流水线内 |
| CA 签发 | CA | issuer.key | 证书正文 | 客户端 | TLS 握手时 |
两次签名机制完全一样,差异只在参数和验签环节的归属。
CSR 自签名时,公钥在 CSR 正文内,所以公钥无法被替换——篡改公钥会导致正文哈希变化,验签失败。因此验签通过就意味着:CSR 未被篡改,且申请人持有对应私钥。

上图以"签发服务端证书"为例。抽象到证书链中,每一轮都可以看成:签发者 issuer 给 申请者 subject 签发一张证书。
| |
展开:CSR 与证书签发数据流
[生成服务端密钥对]
|
+--> [server.pub 公钥]
| |
| | 进入 CSR 主体信息
| v
| [CSR 主体信息]
| [申请者信息 Subject/SAN + server.pub]
| |
| | SHA256
| v
| [CSR 主体信息摘要] ----+
| |
+--> [server.key 私钥] --------+-- 签名 --> [CSR signatureValue]
|
| 组装
v
[CSR = 申请者信息 Subject/SAN + server.pub + CSR signatureValue]
|
| 提交给上级 CA
v
[上级 CA 验证 CSR]
|
| 1. 读取 CSR 主体信息、server.pub、CSR signatureValue
| 2. 重新计算 CSR 主体信息摘要
| 3. 使用 server.pub 验证 CSR signatureValue
v
[验证通过:CSR 未被篡改,且申请人持有 server.key]
|
| CA 继续校验域名 / SAN / 用途 / 有效期 / 策略
v
[生成证书主体信息]
[证书主体信息 = server.pub + Subject/SAN + Issuer/Validity + KeyUsage]
|
| SHA256
v
[证书主体信息摘要] -------------+
|
[issuer.key 上级 CA 私钥] ------+-- 签名 --> [CA signatureValue]
|
| 组装
v
[server.crt = 证书主体信息 + CA signatureValue]2.3 证书链如何组成
证书链本质上是一条信任路径:客户端从服务端证书一路向上验证,直到找到自己已经信任的根 CA。
常见结构:
| |
自建 CA 的简单场景通常只有两层:
| |
服务端通常需要返回:
| |
客户端本地需要提前信任:
| |
本例使用三层链:客户端只需要信任 root-ca.crt;服务端需要提供 example.domain.crt + intermediate-ca.crt 组成的完整证书链(fullchain)。
2.4 证书链如何校验
签发是"从上往下签"——每层 CA 用自己私钥签发下级证书。校验是"从下往上验"——客户端用上级公钥逐层验证下级证书签名。
校验每一步都是在执行 2.1 中的验签:重新计算证书主体摘要,用上级证书中的公钥验证签名值。
| 步骤 | 操作 | 使用的公钥 |
|---|---|---|
| 1 | 验证 server.crt:SHA256(server.crt 正文) → 用公钥验签名 | intermediate-ca.crt 中的公钥 |
| 2 | 验证 intermediate-ca.crt:SHA256(intermediate-ca.crt 正文) → 用公钥验签名 | root-ca.crt 中的公钥 |
| 3 | 检查 root-ca.crt 是否在本地信任库 | — |
| 4 | 检查服务端证书 SAN / 有效期 / 吊销状态 | — |
注意:步骤 3 不是验签,是本地查找——根 CA 的信任来自"是否已导入",不是通过更上级证书证明出来的。
3.实操
3.1 目标与文件关系
本例会生成以下文件:
| 文件 | 可以理解成 | 用途 | 是否需要保密 |
|---|---|---|---|
root-ca.key | 根 CA 私钥 | 签发中间 CA | 必须保密 |
root-ca.crt | 根 CA 证书 | 导入客户端信任库 | 可公开 |
intermediate-ca.key | 中间 CA 私钥 | 签发服务端证书 | 必须保密 |
intermediate-ca.csr | 中间 CA 证书申请 | 交给根 CA 签发 | 可公开 |
intermediate-ca.crt | 中间 CA 证书 | 组成服务端完整证书链(fullchain) | 可公开 |
example.domain.key | 服务端私钥 | Nginx / Ingress 握手时使用 | 必须保密 |
example.domain.csr | 服务端证书申请 | 交给中间 CA 签发 | 可公开 |
example.domain.crt | 服务端证书 | 服务端 HTTPS 证书 | 可公开 |
example.domain.fullchain.crt | 服务端证书链 | 服务端证书 + 中间 CA 证书 | 可公开 |
example.domain.ext | 服务端证书扩展 | 写 SAN、用途等扩展字段 | 可公开 |
3.2 生成根 CA
生成根 CA 私钥:
| |
生成根 CA 根证书:
| |
查看根 CA 证书:
| |
3.3 生成中间 CA 并签发其证书
生成中间 CA 私钥:
| |
生成中间 CA CSR:
| |
创建中间 CA 扩展文件:
| |
保存为:
| |
使用根 CA 签发中间 CA 证书:
| |
验证中间 CA 证书:
| |
预期输出:
| |
3.4 生成服务端证书
生成服务端私钥:
| |
生成服务端 CSR:
| |
查看 CSR:
| |
创建服务端 SAN 扩展文件:
SAN(Subject Alternative Name,主体备用名称)用于声明证书可以匹配哪些域名或 IP。现代 TLS 客户端校验证书时,主要看 SAN,只写 CN 不够。
| |
保存为:
| |
| 字段 | 说明 |
|---|---|
basicConstraints=CA:FALSE | 表示该证书不是 CA 证书 |
keyUsage | 指定密钥用途 |
extendedKeyUsage=serverAuth | 表示用于服务端 TLS 认证 |
subjectAltName | 证书可匹配的 DNS / IP 列表 |
使用中间 CA 签发服务端证书:
| |
-CA intermediate-ca.crt:指定中间 CA 证书;-CAkey intermediate-ca.key:指定中间 CA 私钥;-CAcreateserial:生成序列号文件;-extfile example.domain.ext:写入 SAN 等扩展;-days 825:服务端证书有效期,建议不要过长。
查看服务端证书:
| |
重点确认 SAN:
| |
生成服务端完整证书链(fullchain):
| |
3.5 验证证书链
使用根 CA 和中间 CA 验证服务端证书:
| |
预期输出:
| |
本地启动临时 TLS 服务测试:
| |
另一个终端验证:
| |
4.运维
4.1 配置客户端信任根 CA
Linux 系统需要把 root-ca.crt 加入系统信任列表。
Debian / Ubuntu:
| |
RHEL / CentOS:
| |
验证 curl:
| |
如果已加入系统信任:
| |
4.2 证书吊销
证书吊销要先判断泄露的是哪个私钥。
| 泄露对象 | 影响范围 | 处理方式 |
|---|---|---|
服务端私钥 example.domain.key | 只影响该服务域名 | 吊销旧服务端证书,重新生成服务端私钥和证书 |
| 中间 CA 私钥 | 影响该中间 CA 签发的所有证书 | 吊销中间 CA,重新签发下级证书 |
根 CA 私钥 root-ca.key | 整个自建 CA 体系失效 | 移除旧 CA 信任,重建 CA,重新签发所有证书 |
吊销机制主要有两类:
| 机制 | 说明 |
|---|---|
| CRL(Certificate Revocation List) | CA 定期发布吊销列表,客户端下载后检查证书序列号 |
| OCSP(Online Certificate Status Protocol) | 客户端在线查询某张证书是否有效 |
内网自建 CA 常见处理:
- 记录要吊销证书的 serial;
- 在 CA 侧吊销证书;
- 生成新的 CRL;
- 让客户端或网关加载新的 CRL;
- 重新签发并替换服务端证书。
如果使用 openssl ca 管理 CA,可以执行:
| |
查看 CRL:
| |
需要注意:
- 只生成 CRL 不够,客户端必须实际检查 CRL 才有意义;
- 很多内网系统默认不检查 CRL/OCSP,需要在网关、客户端或运行时显式配置;
- 如果是根 CA 私钥泄露,吊销单张证书没有意义,必须更换根 CA,并从所有客户端信任列表中移除旧 CA。
K8s 场景中,如果服务端私钥泄露,通常处理流程:
| |
4.3 Kubernetes TLS Secret
Ingress 使用服务端证书链和服务端私钥:
| |
查看 Secret:
| |
Ingress 示例:
| |
需要注意:
- Secret 中不要放 CA 私钥;
- Ingress Controller 需要能读取该 namespace 下的 Secret;
- 浏览器是否信任取决于客户端是否导入
root-ca.crt,不是 K8s Secret 决定。
4.4 常见问题
| 问题 | 原因 | 处理 |
|---|---|---|
| 浏览器提示不安全 | 客户端不信任自建 CA | 导入 root-ca.crt 到系统或浏览器信任列表 |
x509: certificate signed by unknown authority | 程序不信任 CA | 配置 CA 文件或加入系统信任 |
certificate is valid for ... not ... | SAN 中没有访问域名/IP | 重新签发证书并补充 SAN |
| Ingress 仍使用默认证书 | Secret 名称/namespace 不匹配 | 检查 Ingress tls.secretName 和 namespace |
tls: private key does not match public key | 证书和私钥不是一对 | 重新确认 crt/key 匹配 |
验证证书和私钥是否匹配:
| |
两条命令输出一致,说明证书和私钥匹配。
5.总结
- 自签证书和合法证书最核心的区别是:客户端默认不信任自建 CA;
- 服务端证书必须配置 SAN,不能只依赖 CN;
- CA 私钥必须离线或严格保护,不能放入业务 Secret、Pod、镜像;
- K8s Ingress Secret 只需要服务端证书链和服务端私钥;
- 排查证书问题优先看:证书链、SAN、Secret namespace、证书和私钥是否匹配。