3. X.509标准详解:V3证书结构、证书扩展、基本字段说明

X.509是个老家伙了。最早1988年出来的时候,证书结构很简单,就几个字段。后来大家发现不够用,V2加了点东西,还是不够。到V3,干脆搞了一套扩展机制,才算真正灵活了。

我个人习惯把V3证书看成两部分:基本字段是骨架,扩展是血肉。骨架决定了证书是谁发的、发给谁的、什么时候有效;血肉决定了证书能干什么、不能干什么、信任链怎么走。你想想看,如果只有骨架,那数字证书和一张电子名片有什么区别?

好,我们直接拆开看。

3.1 基本字段:骨架不能错

每个X.509 V3证书都包含下面这些必选字段。我在项目里见过不少因为字段填错导致的握手失败,比如序列号重复——嗯,这是一个非常隐蔽的坑,后面会讲。

字段 说明 避坑要点
版本号 现在基本是V3(值为2),V1和V2已被淘汰 如果用V2,部分扩展无法解析
序列号 CA为每个证书分配的唯一整数 长度建议≥8字节,不能重复!
签名算法标识 CA签发该证书所用的算法(如sha256WithRSAEncryption) 必须与证书末尾的签名值算法一致
颁发者 签发该证书的CA的DN(Distinguished Name) DN的编码顺序有讲究,RFC 4514有规定
有效期 notBefore 和 notAfter,UTC时间 注意2038年问题?RFC 5280允许GeneralizedTime
主体 证书持有者的DN 如果是SSL服务器证书,Common Name(CN)需要匹配域名
主体公钥信息 公钥算法 + 公钥值 RSA密钥长度2048位起步,现在4096更安全
签名值 CA对证书tbsCertificate部分的数字签名 验证时用CA的公钥
一点经验:序列号重复的问题我碰过一次。某内部CA的数据库被回滚了,结果又签发了相同序列号的证书,导致一端缓存了旧证书,服务端换了新证书,TLS握手死活验证不过。从那以后,我要求所有CA系统必须保证序列号全局唯一,哪怕时间戳也得加进去。

3.2 扩展机制:V3 最聪明的设计

V3证书的扩展区是个SEQUENCE OF Extension。每个Extension由三部分组成:extnID(OID)、critical(布尔值)、extnValue(ASN.1 DER编码)。

critical字段特别重要。如果某个扩展标记为critical,但客户端不认识它,客户端必须拒绝该证书。为什么?因为CA说“这个字段很重要,你不理解就不能用”。反之,non-critical的扩展,不认识可以跳过。

我曾经在审计日志里看到过一个诡异的现象:某根证书扩展把未知OID标记为critical,导致所有依赖它的中间证书都被客户端拒绝。查了半天,原来是生成工具的一个bug。所以,非必要不critical,这是铁律。

3.3 常用扩展详解:血肉里藏着门道

RFC 5280定义了一大堆扩展,但日常开发你记住下面这几个就够用了。

  • 基本约束 (Basic Constraints):标识证书是否为CA。critical一般。如果ca=FALSE,证书不能签发子证书。
  • 密钥用法 (Key Usage):定义公钥用途,如digitalSignature、keyEncipherment。ca证书必须包含keyCertSign和cRLSign。
  • 扩展密钥用法 (Extended Key Usage):更细粒度的用途,如serverAuth(1.3.6.1.5.5.7.3.1)、clientAuth。如果存在,客户端必须检查。
  • 主体备用名称 (Subject Alternative Name, SAN):SSL证书的核心。域名写在SAN里,而不是CN。现在浏览器已经不再看CN了。
  • CRL分发点 (CRL Distribution Points):告诉客户端去哪里获取CRL。包含HTTP或LDAP URL。
  • 机构信息访问 (Authority Information Access, AIA):包含CA Issuers(获取上级证书)和OCSP响应地址。
核心区别:Key Usage vs. Extended Key Usage。Key Usage是“生物特征”,说你能做什么动作;EKU是“岗位职责”,说你能扮演什么角色。比如一个SSL服务器证书,Key Usage必须有digitalSignature、keyEncipherment,EKU必须有serverAuth。缺任何一个,浏览器都可能报错。

3.4 扩展验证的实战教训

有一次,客户反馈他们自建PKI签发的服务器证书在Chrome上提示“ERR_CERT_WEAK_SIGNATURE_ALGORITHM”。我一看,签名算法用的sha1WithRSAEncryption——这确实弱,但CA证书也是同算法。理论上既然CA都能认,为啥服务器证书不行?

后来查了扩展发现:CA证书的Basic Constraints里pathLenConstraint被设置成0,意味着它只能签发终端实体证书,不能再签发中间CA。这个本身没问题。但是CA证书的Signature Algorithm是sha1,而服务器证书也是sha1,Chrome对sha1的终端证书有特殊处理——除非是2016年之前签发的,否则直接拒绝。这就不是扩展的问题了,是算法策略。但如果你不看扩展,你会先把锅甩给CA配置。

所以我的建议是:做PKI审计日志时,必须同时检查签名算法、基本约束、密钥用法和EKU。任何一个不匹配,都可能导致链验证失败。

X.509 V3 证书结构示意 X.509 V3 证书结构 基本字段(必须) 版本号 + 序列号 签名算法标识 颁发者 + 有效期 主体 + 主体公钥信息 颁发者唯一ID / 主体唯一ID (V2/V3) 扩展区(V3 可选的) 基本约束 (ca/pathLen) OID: 2.5.29.19 密钥用法 (KeyUsage) OID: 2.5.29.15 扩展密钥用法 (EKU) OID: 2.5.29.37 主体备用名称 (SAN) OID: 2.5.29.17 签名值(必须) CA 对 tbsCertificate 的数字签名 说明:基本字段和签名值对每个证书都必不可少;扩展区是V3的关键特性,通过OID识别。 红色标记表示critical字段需要客户端强制理解,否则必须拒绝证书。

3.5 扩展的编码与审计日志

在做PKI审计日志时,扩展的编码细节往往被忽略,但这里刚好是问题重灾区。举个例子:SAN扩展的GeneralName有多种选择,dNSName、iPAddress、uniformResourceIdentifier等。如果编码时把dNSName的tag写成了0(应该是2),那证书解析出来的SAN就会乱掉。审计日志里如果只记录OID而不记录值的正确性,这种问题根本发现不了。

我建议审计日志至少捕获以下扩展信息:

  • 每个扩展的OID、critical标志
  • 对于SAN,列出所有名字(包括类型)
  • 对于Basic Constraints,记录ca和pathLenConstraint
  • 对于Key Usage,清晰列出enabled位
  • 对于AIA/CRL DP,记录所有URL
警告:有些扩展的值可能包含敏感信息(如CA的私钥ID?不会,但Subject Key Identifier是一般信息)。日志在采集时要脱敏吗?个人倾向于不脱敏,因为PKI审计本身就是安全审计,需要全量信息。但需要确保日志存储本身的访问控制足够严格。

3.6 小结

X.509 V3证书的设计是非常精巧的。基本字段提供了信任的锚点,扩展机制赋予了无限的可能。你在审计日志中看到的每一个证书,背后都有一套严格的结构规则。理解这些字段和扩展,你才能从日志里读出真正的业务含义,而不是只看一堆十六进制。

嗯,这一节内容不少。下节课我们会深入CRL和OCSP的格式,但那又是另一个话题了。先消化好证书结构,这是后续一切的基础。