crypto/x509: accept non-string pkix.Name attributes
要約
概要
crypto/x509 パッケージが X.509 証明書を解析する際に、Subject/Issuer の識別名(DN)フィールドの属性値として文字列型以外の ASN.1 型を受け入れられるよう、パーサの挙動とドキュメントを改善するproposalです。
ステータス変更
active → likely_accept
RFC 5280 では属性値(AttributeValue)は ANY 型として定義されており、文字列以外の ASN.1 型を含んでも仕様準拠であるにもかかわらず、Go の crypto/x509 パーサがこれらを拒否していました。Merkle Tree Certificate のような新しいTLS関連ドラフト仕様への対応必要性と、設計の妥当性についてコアチームで合意が得られたため、2026年5月6日の週次提案レビューミーティングで likely_accept と判断されました。
技術的背景
現状の問題点
RFC 5280 の X.509 証明書仕様では、RDN(Relative Distinguished Name)の属性値(AttributeValue)は ANY 型として定義されています。しかし現在の crypto/x509 の parseName 関数(parseASN1String 経由)は、属性値として以下の文字列型しか受け入れていません: T61String、PrintableString、UTF8String、BMPString、IA5String、NumericString。
TLS のドラフト仕様 draft-ietf-tls-trust-anchor-ids および Merkle Tree Certificates で使用される RELATIVE-OID を属性値に含む証明書を x509.ParseCertificate で解析しようとすると、以下のエラーが発生します:
x509: invalid RDNSequence: invalid attribute value: unsupported string type: 13
歴史的背景として、Go 1.16 以前は encoding/asn1 を使った汎用的な Unmarshal により解析できていましたが、Go 1.17 で x/crypto/cryptobyte を使った専用パーサへの移行(CL #274234)に伴い、この問題が生じました。
提案された解決策
crypto/x509 の parseName 関数が、文字列型以外の ASN.1 型も pkix.AttributeTypeAndValue.Value に格納できるよう拡張します。具体的には以下の型マッピングを採用します。
| ASN.1 型 | Go の型 |
|---|---|
| PrintableString, IA5String, NumericString, BMPString, T61String, UTF8String | string |
| INTEGER | int64 |
| BIT STRING | asn1.BitString |
| OCTET STRING | []byte |
| OBJECT IDENTIFIER | asn1.ObjectIdentifier |
| UTCTIME, GENERALIZEDTIME | time.Time |
| BOOLEAN | bool |
| NULL | nil |
| その他すべて | asn1.RawValue |
また、pkix.AttributeTypeAndValue のドキュメントに、Value フィールドが取り得る Go の型を明記します。 |
これによって何ができるようになるか
Merkle Tree Certificate や TLS Trust Anchor IDs などの新しいTLS仕様に準拠した証明書を Go でエラーなく解析できるようになります。また、OCTET STRING、INTEGER、OBJECT IDENTIFIER など文字列以外の属性値を含む独自 X.509 証明書の処理も可能になります。
コード例
// Before: RELATIVE-OID を含む証明書の解析が失敗する
cert, err := x509.ParseCertificate(derBytes)
// => error: x509: invalid RDNSequence: invalid attribute value: unsupported string type: 13
// After: 非文字列属性も含む証明書を正常に解析できる
cert, err := x509.ParseCertificate(derBytes)
if err != nil {
panic(err)
}
for _, attr := range cert.Subject.Names {
switch v := attr.Value.(type) {
case string:
fmt.Printf("文字列属性: %s = %s\n", attr.Type, v)
case []byte:
fmt.Printf("OCTET STRING属性: %s = %x\n", attr.Type, v)
case asn1.RawValue:
// RELATIVE-OID などの未知の型はRawValueとして取得可能
fmt.Printf("その他ASN.1属性: %s, タグ=%d\n", attr.Type, v.Tag)
}
}
議論のハイライト
- 複数の解決案の検討: FiloSottile は6つの選択肢を列挙しました。(5) 旧
encoding/asn1の挙動への回帰、(6) 全 ASN.1 型を認識し未知型はasn1.RawValueにフォールバック、という2案が最終候補となり、より情報量の多い (6) が採用されました。 - 後方互換性:
pkix.AttributeTypeAndValue.Valueはもともとany型のため、型定義の変更は不要です。現在は解析エラーで失敗していたコードがエラーなく動くようになるため、後方非互換ではありません。ただし、将来型を変更する機会は失われます(FiloSottile 指摘)。 - Go 1.16 からの退行の確認: davidben が詳細な調査を行い、Go 1.16 では
encoding/asn1経由で一部の型(OCTET STRING、OBJECT IDENTIFIERなど)が解析できていたことを証明しました。RELATIVE-OID や NULL などはnilにバインドされていたという興味深い挙動も明らかになりました。 - ドキュメントの場所: rolandshoemaker は「
pkix.AttributeTypeAndValueはany型であり冗長では」と疑問を呈しましたが、aclements が「利用者の最も見つけやすい場所にドキュメントを置くべき」と結論付けました。 - 将来の X.509 DN API の議論: rolandshoemaker は
pkix.NameのAPI全体の刷新(新 DN 型の導入)を示唆しましたが、現在の提案の妨げにならないよう、別途の議論とすることになりました。