crypto/x509: accept non-string pkix.Name attributes
要約
概要
crypto/x509 パッケージのX.509証明書パーサーが、Subject/Issuer名の属性値(pkix.AttributeTypeAndValue.Value)として文字列型以外のASN.1型を含む証明書を拒否してしまう問題を修正するproposalです。文字列型以外の属性値を持つ証明書を正常にパースできるよう、パーサーの動作を改善し、許容される型の対応関係をドキュメント化します。
ステータス変更
likely_accept → accepted
2026年5月6日に likely_accept となり、2026年5月13日のプロポーザルレビューミーティングで合意に変更なしとして正式に accepted となりました。提案内容は明確で後方互換性を損なわず、RFC 5280の仕様に準拠する方向への変更であることが支持されました。
技術的背景
現状の問題点
RFC 5280において、X.509のDistinguished Name(DN)内の属性値(AttributeValue)は ANY 型として定義されており、あらゆるASN.1型を取りうることが仕様上の要件です。
AttributeTypeAndValue ::= SEQUENCE {
type AttributeType, -- OBJECT IDENTIFIER
value AttributeValue } -- ANY
しかし現在の crypto/x509 の内部関数 parseName(ParseCertificate などの公開APIから呼ばれる)は、属性値として PrintableString、IA5String、NumericString、BMPString、T61String、UTF8String の6つの文字列型しか受け付けず、それ以外の型が含まれると即座にエラーを返します。
たとえば、Merkle Tree Certificate(MTC)の仕様(draft-davidben-tls-merkle-tree-certs)では RELATIVE-OID 型の属性値が使われており、現在のGoで x509.ParseCertificate を呼ぶと次のエラーが発生します。
x509: invalid RDNSequence: invalid attribute value: unsupported string type: 13
OCTET STRING や INTEGER、NULL などを属性値に持つ証明書も同様にパース不能でした。なお Go 1.17以前は encoding/asn1 を使った実装で既知の型はパースできていましたが、その後の実装変更(CL 274234)で現在の厳格な挙動になりました。
提案された解決策
6つの選択肢が議論された末、選択肢 (6) が採用されました。
crypto/x509の内部パーサー(parseName)を拡張し、文字列型以外のASN.1型もpkix.AttributeTypeAndValue.Valueとして格納できるようにする。pkix.AttributeTypeAndValueのドキュメントに、crypto/x509でパースした場合にValueフィールドが取りうる型を明示する。
これによって何ができるようになるか
文字列型以外のASN.1属性値を含むX.509証明書を、エラーなくパースできるようになります。
コード例
// Before: 文字列以外の属性値(OCTET STRING, INTEGER, RELATIVE-OID 等)を持つ証明書は
// ParseCertificate がエラーを返し、証明書を取得できない
cert, err := x509.ParseCertificate(block.Bytes)
// err: "x509: invalid RDNSequence: invalid attribute value: unsupported string type: 4"
// After: 同じ証明書が正常にパースされ、各属性値は対応するGoの型で格納される
cert, err := x509.ParseCertificate(block.Bytes)
// err == nil
for _, attr := range cert.Subject.Names {
switch v := attr.Value.(type) {
case string:
// PrintableString, IA5String, UTF8String 等
case []byte:
// OCTET STRING
case int64:
// INTEGER
case asn1.ObjectIdentifier:
// OBJECT IDENTIFIER
case bool:
// BOOLEAN
case time.Time:
// UTCTime, GeneralizedTime
case asn1.BitString:
// BIT STRING
case nil:
// NULL
case asn1.RawValue:
// 上記以外の未知のASN.1型
}
}
ドキュメントに明記される型の対応関係(crypto/x509 でのパース時):
| 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 |
議論のハイライト
- 仕様適合性が主な動機: RFC 5280でも
AttributeValueはANYとして定義されており、現在の実装は仕様上許可されている証明書を拒否していた。MTC(Merkle Tree Certificate)の普及に向けて、RELATIVE-OIDを属性値に持つ証明書を受け付けられるようにすることが直接の契機となった。 - 歴史的な経緯: Go 1.17以前は
encoding/asn1を経由して既知の型(OCTET STRING、OBJECT IDENTIFIER 等)はパースできていたが、CL 274234 によるパーサーの書き換えで文字列型のみに制限される動作へと変化した。この変更が意図的かどうかは不明。 asn1.RawValueの採用: 未知の型にはasn1.RawValueを使う方針が @FiloSottile によって提案・採用された。encoding/asn1がany型にアンマーシャルした場合nilを返していた既存の問題を踏まえ、情報を失わない表現として選択された。pkix.Name全体の設計課題: X.509のDNはSEQUENCE OF SET OF AttributeTypeAndValueという2層構造だがpkix.Nameはこれを平坦化しており、文字列型も元のASN.1型情報を失う等の問題が指摘された。@rolandshoemaker からは全面的に新しいDN型を導入すべきとの意見も出たが、今回はまず既存の問題修正に絞ることになった。- ドキュメント化の場所: @aclements は
pkix.AttributeTypeAndValueが最も発見しやすい場所であるとして、同型のドキュメントに型対応表を記載することを支持した。