Skip to content

实现原理:多层 PKI 与 pathLenConstraint

对应代码:pkg/caissuer.goGenerateIntermediateCA / SignCertificate)+ pkg/generator.goGenerateSelfSignedCert

pathLenConstraint 是多层 PKI 里最容易被忽略、踩坑最深的字段。本页讲它怎么约束信任链深度,以及 Go x509 库的一个表达陷阱。

多层 PKI 结构

角色IsCAKeyUsagepathLen 语义
根 CAtrueCertSign+CRLSign不限制
中间 CAtrueCertSign+CRLSign+DigitalSignature限制下方还能嵌套几层
叶子falsedigitalSignature/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 字段):少了 PathLenConstraintKeyUsage,多了 IsCA bool

默认值对比表

参数SelfSigned(generator)IntermediateCA(caissuer)SignCert(caissuer)
KeyType 默认rsarsarsa
RSA KeySize 默认204840962048
ECDSA KeySize 默认256 (P-256)384 (P-384)256 (P-256)
ValidityDays 默认3651825(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 三种取值

bash
# 企业常见配置:中间 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"必须同时设两个字段:

go
// ❌ 错误:只设 MaxPathLen=0,Go 会当作"未设置",等于无限制
template.MaxPathLen = 0

// ✅ 正确:同时设 MaxPathLen=0 和 MaxPathLenZero=true
template.MaxPathLen = 0
template.MaxPathLenZero = true

cert-skills 的处理(caissuer.go L143):

go
MaxPathLen:     req.PathLenConstraint,
MaxPathLenZero: req.PathLenConstraint == 0,

-1 与 0 的区别

  • -1MaxPathLen=-1, MaxPathLenZero=false(Go 的 -1 表示无限制,可继续签中间 CA)
  • 0MaxPathLen=0, MaxPathLenZero=true严格 0,只能签叶子)

中间 CA 模板

go
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)依次尝试:

下一步