crypto/x509: add support for Relative OIDs
要約
概要
crypto/x509 パッケージが RELATIVE-OID(相対オブジェクト識別子)を含む証明書を正しくパースできない問題を解決するためのプロポーザルです。新興のTLS仕様(Merkle Tree Certificates等)がこの ASN.1 型を使用しており、Go の X.509 パーサーの仕様準拠性を高めることが目的です。
ステータス変更
(新規) → active
2026年4月22日のProposal Review Meetingにおいて、@FiloSottile が事前に投稿した具体的な設計方針(オプション6)が議論の俎上に上がり、週次レビューの対象として active 列に追加されました。
技術的背景
現状の問題点
ASN.1(Abstract Syntax Notation One)の型の一つである RELATIVE-OID(タグ番号13)が、encoding/asn1 および x/crypto/cryptobyte パッケージでサポートされていません。x509.ParseCertificate で RELATIVE-OID を属性値に含む証明書を読み込もうとすると、以下のエラーが発生します。
x509: invalid RDNSequence: invalid attribute value: unsupported string type: 13
RFC 5280 の定義上、X.509 の Name 構造における AttributeValue は ANY 型であり、パーサーは任意の ASN.1 値を受け入れなければなりません。しかし現在の crypto/x509 のパーサーは属性値を PrintableString、UTF8String 等の文字列型のみに限定しており、これは仕様非準拠の状態です。
提案された解決策
@FiloSottile の提案(オプション6)により、pkix.AttributeTypeAndValue の Value フィールドに格納される型を、crypto/x509 がパースした場合について明文化します。
具体的には以下の型マッピングをドキュメントとして定義し、パーサーを対応させます:
- 文字列型(PrintableString, IA5String, NumericString, BMPString, T61String, UTF8String)→
string INTEGER→int64BIT STRING→asn1.BitStringOCTET STRING→[]byteOBJECT IDENTIFIER→asn1.ObjectIdentifierUTCTIME/GENERALIZEDTIME→time.TimeBOOLEAN→boolNULL→nil- それ以外(RELATIVE-OID 等) →
asn1.RawValue
これによって何ができるようになるか
Merkle Tree Certificates(MTC)や TLS Trust Anchor IDs といった新しい PKI 仕様が使用する RELATIVE-OID を含む証明書を、x509.ParseCertificate でエラーなく読み込めるようになります。また、OCTET STRING や SEQUENCE など、従来はパースエラーとなっていたあらゆる ASN.1 型の属性値も asn1.RawValue として受け取れるようになり、仕様準拠性と相互運用性が大幅に向上します。
コード例
// Before: RELATIVE-OID を属性値に含む証明書のパースが失敗する
cert, err := x509.ParseCertificate(derBytes)
// => エラー: "x509: invalid RDNSequence: invalid attribute value: unsupported string type: 13"
// After: 未知の ASN.1 型は asn1.RawValue として格納される
cert, err := x509.ParseCertificate(derBytes)
if err != nil {
log.Fatal(err)
}
for _, attr := range cert.Subject.Names {
switch v := attr.Value.(type) {
case string:
fmt.Printf("文字列属性: %s\n", v)
case asn1.RawValue:
// RELATIVE-OID や OCTET STRING など未知の型
fmt.Printf("その他の属性 (tag=%d): %x\n", v.Tag, v.Bytes)
}
}
議論のハイライト
- 問題の根本原因: パーサーの問題は
cryptobyteではなくparseASN1Stringにあり、属性値を文字列型のみと仮定している点にあると @davidben が指摘。RFC 5280 上AttributeValueはANY型であるため仕様違反の動作です。 - Go 1.17 以前の動作との比較:
encoding/asn1を使用していた Go 1.16 以前は偶然にも多くの型を受け入れていましたが、RELATIVE-OID はnilにバインドされるという意図しない動作でした。Go 1.17 のcryptobyteへの移行(CL #274234)でこの挙動が壊れました。 - 型選択の検討:
AttributeTypeAndValue.Valueの型をasn1.RawValueに変更する案も検討されましたが、既存の API との互換性から、既知の型は従来通りにデコードし、未知の型のみRawValueにフォールバックする設計が選ばれました。 - OBJECT IDENTIFIER の型の扱い: OBJECT IDENTIFIER 属性値には
x509.OID(大きな OID 成分をサポートする新しい型)ではなく、既存のasn1.ObjectIdentifierを使うことが決定されました。crypto/x509/pkixパッケージがcrypto/x509を参照するのは構造上好ましくないためです。 - 実装範囲:
encoding/asn1パッケージ自体の変更は最小限に留め、主にcrypto/x509のパーサー(x/crypto/cryptobyteを使用するパス)を修正する方針が示されました。