实现原理:多层 PKI 与 pathLenConstraint
对应代码:
pkg/caissuer.go(GenerateIntermediateCA/SignCertificate)+pkg/generator.go(GenerateSelfSignedCert)
pathLenConstraint 是多层 PKI 里最容易被忽略、踩坑最深的字段。本页讲它怎么约束信任链深度,以及 Go x509 库的一个表达陷阱。
多层 PKI 结构
| 角色 | IsCA | KeyUsage | pathLen 语义 |
|---|---|---|---|
| 根 CA | true | CertSign+CRLSign | 不限制 |
| 中间 CA | true | CertSign+CRLSign+DigitalSignature | 限制下方还能嵌套几层 |
| 叶子 | false | digitalSignature/keyEncipherment | 不适用 |
Request 字段表
IntermediateCARequest(caissuer.go L21-39,15 字段):CommonName / Organization / Country / Province / Locality / DNSNames / IPAddresses / ValidityDays / KeySize / KeyType / PathLenConstraint / OutputCertPath / OutputKeyPath / ParentCertPath / ParentKeyPath
SignCertRequest(L42-60,15 字段):同上但 PathLenConstraint 换成 KeyUsage string(server/client/both)
GenerateCertRequest(generator.go L22-36,13 字段):少了 PathLenConstraint 和 KeyUsage,多了 IsCA bool
默认值对比表
| 参数 | SelfSigned(generator) | IntermediateCA(caissuer) | SignCert(caissuer) |
|---|---|---|---|
| KeyType 默认 | rsa | rsa | rsa |
| RSA KeySize 默认 | 2048 | 4096 | 2048 |
| ECDSA KeySize 默认 | 256 (P-256) | 384 (P-384) | 256 (P-256) |
| ValidityDays 默认 | 365 | 1825(5年) | 365 |
| CommonName 默认 | "localhost" | "Intermediate CA" | "localhost" |
| KeyUsage 默认 | server (DS+KE+ServerAuth) | CertSign+CRLSign+DS | "server" |
| PathLen 默认 | — | 0 (MaxPathLenZero=true) | — |
| OutputCert 默认 | <CN>.pem | <sanitized CN>.pem | <sanitized CN>.pem |
注意:中间 CA 与签发用 sanitizeFilename 处理 CN 做文件名,自签用原始 CN。
pathLenConstraint 三种取值
# 企业常见配置:中间 CA 只能签叶子,不能签下级中间
cert-skills generate-intermediate-ca --path-len 0 ...
# 需要多级中间(如大型组织):
cert-skills generate-intermediate-ca --path-len 1 ... # 下面还能再签一层中间
# 无限制:
cert-skills generate-intermediate-ca --path-len -1 ...Go x509 的表达陷阱
这是 cert-skills 内部处理的坑——Go 的 x509 库里,表达"0"必须同时设两个字段:
// ❌ 错误:只设 MaxPathLen=0,Go 会当作"未设置",等于无限制
template.MaxPathLen = 0
// ✅ 正确:同时设 MaxPathLen=0 和 MaxPathLenZero=true
template.MaxPathLen = 0
template.MaxPathLenZero = truecert-skills 的处理(caissuer.go L143):
MaxPathLen: req.PathLenConstraint,
MaxPathLenZero: req.PathLenConstraint == 0,-1 与 0 的区别
- 传
-1:MaxPathLen=-1, MaxPathLenZero=false(Go 的 -1 表示无限制,可继续签中间 CA) - 传
0:MaxPathLen=0, MaxPathLenZero=true(严格 0,只能签叶子)
中间 CA 模板
template := x509.Certificate{
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
MaxPathLen: req.PathLenConstraint,
MaxPathLenZero: req.PathLenConstraint == 0,
}校验父证书须 IsCA==true;Subject 字段未指定时从父 CA 继承(nonEmptySlice)。
ECDSA 曲线映射
256→P256, 384→P384, 521→P521, default→P256。Ed25519 keySize 固定 256(由 ed25519.GenerateKey 决定)。
ExtKeyUsage 映射表(SignCertificate)
| KeyUsage 输入 | KeyUsage (x509) | ExtKeyUsage |
|---|---|---|
"server" | DigitalSignature | KeyEncipherment | [ServerAuth] |
"client" | DigitalSignature | [ClientAuth] |
"both" | DigitalSignature | KeyEncipherment | [ServerAuth, ClientAuth] |
| 其它 | error "unsupported key_usage" | — |
Ed25519 特例
Ed25519 不支持密钥封装,所以 cert-skills 强制 KeyUsage = DigitalSignature(去掉 KeyEncipherment)。给 Ed25519 证书传 --key-usage server 实际只会得到 DigitalSignature。
签发流程
序列号与随机性
generateRandomSerial(L312-319)用 Lsh(1, 128) 生成 128 位随机 serial(rand.Int),远超 CA/B BR 的 64 位最低要求。
生产 CA 绝不能用固定序列号
GenerateSelfSignedCert(generator.go L130)刻意用 SerialNumber: big.NewInt(1),因为自签证书是测试用途。生产 CA 必须用 generateRandomSerial(128 位随机,远超 CA/B BR 的 64 位最低要求)——固定或可预测的序列号会被视为不合规。
叶子证书的约束
叶子证书不带 CertSign——只有 CA 能签证书,这是 PKI 的根本约束。
ReadSignerFromFile 多格式
ReadSignerFromFile(L490-521)依次尝试:
下一步
- PKI 操作手册 → PKI 与证书签发
- 完整生命周期编排 → 多层 PKI 工作流
- 链验证怎么用 pathLen → 链验证