[インデックス 12373] ファイルの概要
このコミットは、Go言語のcrypto/x509パッケージにおけるPKCS#1秘密鍵のASN.1エンコーディングに関する問題を修正するものです。具体的には、RSA秘密鍵の「追加の素数(additional primes)」フィールドが空の場合に、それが空のASN.1シーケンスとしてシリアライズされてしまい、PKCS#1 v1の鍵を期待する外部のコードが誤動作する問題に対処しています。この修正は、encoding/asn1パッケージにomitemptyタグのサポートを導入することで実現されています。
コミット
commit 52d6ca2f86ea6b4b291a5658fbc9e2325bc4d028
Author: Adam Langley <agl@golang.org>
Date: Mon Mar 5 12:04:18 2012 -0500
crypto/x509: don't include empty additional primes in PKCS#1 private key.
asn1 didn't have an omitempty tag, so the list of additional primes in
an RSA private key was serialised as an empty SEQUENCE, even for
version 1 structures. This tripped up external code that didn't handle
v2.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5729062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/52d6ca2f86ea6b4b291a5658fbc9e2325bc4d028
元コミット内容
crypto/x509パッケージにおいて、PKCS#1秘密鍵に空の追加の素数を含めないようにする変更です。asn1パッケージにomitemptyタグがなかったため、RSA秘密鍵の追加の素数のリストが、バージョン1の構造であっても空のSEQUENCEとしてシリアライズされていました。これが、バージョン2を処理できない外部のコードを混乱させていました。
変更の背景
このコミットは、Go言語のcrypto/x509パッケージが生成するPKCS#1形式のRSA秘密鍵が、特定の条件下で外部のシステムとの互換性問題を引き起こしていたことに起因します。
問題の核心は、RSA秘密鍵の構造体に含まれるAdditionalPrimesフィールド(これはPKCS#1標準におけるotherPrimeInfosに対応します)が空であるにもかかわらず、ASN.1エンコーディング時に空のSEQUENCEとして出力されてしまっていた点にあります。
PKCS#1標準には複数のバージョンが存在し、特にPKCS#1 v1.5とPKCS#1 v2.1以降では、RSA秘密鍵の構造に違いがあります。PKCS#1 v1.5のRSAPrivateKey構造にはotherPrimeInfosフィールド自体が存在しません。一方、PKCS#1 v2.1で導入された多素数RSA(Multi-Prime RSA)をサポートするために、otherPrimeInfosフィールドが追加されました。このフィールドは、2つ以上の追加の素数を使用する場合にその情報を格納します。
Goのcrypto/x509が、実際にはPKCS#1 v1.5の鍵であるにもかかわらず、AdditionalPrimesが空であるために空のSEQUENCEとしてotherPrimeInfosを出力してしまうと、外部のPKCS#1パーサーがこれをPKCS#1 v2.1以降の鍵構造と誤認する可能性がありました。もしその外部パーサーがPKCS#1 v2.1以降の構造を適切に処理できない場合、この空のSEQUENCEの存在によってパースエラーや予期せぬ動作を引き起こしていました。
このコミットは、encoding/asn1パッケージにomitemptyタグのサポートを導入し、AdditionalPrimesフィールドが空の場合にはASN.1エンコード結果から完全に省略されるようにすることで、この互換性問題を解決することを目的としています。これにより、PKCS#1 v1.5の鍵は、otherPrimeInfosフィールドが全く存在しない正しい形式でエンコードされるようになります。
前提知識の解説
このコミットの理解には、以下の技術的な概念が不可欠です。
-
ASN.1 (Abstract Syntax Notation One):
- データ構造を定義し、そのデータをバイト列にエンコード・デコードするための国際標準です。特に通信プロトコル、暗号化、ディレクトリサービスなどで広く利用されます。
- ASN.1は、データの抽象的な構文を定義し、そのデータを具体的なバイト列に変換するためのエンコーディングルール(例: DER, BER, PERなど)と組み合わせて使用されます。
- Go言語の
encoding/asn1パッケージは、このASN.1データのエンコードとデコードをGoの構造体とマッピングして行います。
-
DER (Distinguished Encoding Rules):
- ASN.1のエンコーディングルールの一つで、特定のASN.1値に対して常に一意なバイト表現を保証します。これにより、デジタル署名や証明書など、バイト列の一意性が求められる場面で広く採用されています。
-
PKCS#1 (Public-Key Cryptography Standards #1):
- RSA暗号アルゴリズムに関する仕様を定めた標準群の一つです。RSA公開鍵・秘密鍵のフォーマット、署名スキーム(RSASSA)、暗号化スキーム(RSAES)などを定義しています。
- PKCS#1 RSAPrivateKey構造: RSA秘密鍵のASN.1構造を定義しています。この構造体は、RSAの基本パラメータ(モジュラス
n、公開指数e、秘密指数d)に加えて、中国剰余定理(CRT)を用いた高速化のためのパラメータ(prime1(p),prime2(q),exponent1(d mod (p-1)),exponent2(d mod (q-1)),coefficient((inverse of q) mod p))を含みます。 - PKCS#1 v1.5 vs v2.x:
- PKCS#1 v1.5: RSAの初期のバージョンで、広く利用されてきました。秘密鍵構造には、2つの素数(
pとq)のみを前提としたパラメータが含まれます。 - PKCS#1 v2.1以降: セキュリティと機能の向上のために改訂されました。特に、多素数RSA(Multi-Prime RSA)のサポートが追加されました。
otherPrimeInfosフィールド: PKCS#1 v2.1でRSAPrivateKey構造に追加されたオプションフィールドです。これは、RSA鍵が2つ以上の素数(通常は3つ以上)で構成される場合に、追加の素数に関する情報(素数自体、対応するCRT指数、CRT係数)を格納するために使用されます。このフィールドが存在する場合、versionフィールドは1に設定されます。
- PKCS#1 v1.5: RSAの初期のバージョンで、広く利用されてきました。秘密鍵構造には、2つの素数(
-
多素数RSA (Multi-Prime RSA):
- RSA鍵を生成する際に、従来の2つの素数(
pとq)だけでなく、3つ以上の素数を使用する方式です。これにより、RSA演算(特に復号や署名)の性能を向上させることができます。otherPrimeInfosフィールドは、この多素数RSAの情報を格納するために存在します。
- RSA鍵を生成する際に、従来の2つの素数(
-
Go言語の
encoding/asn1パッケージと構造体タグ:- Goの標準ライブラリである
encoding/asn1は、Goの構造体とASN.1のデータ型をマッピングして、エンコード/デコードを自動的に行います。 - 構造体のフィールドには、
asn1:"..."のようなタグを付与することで、ASN.1エンコーディング時の挙動を制御できます。例えば、asn1:"optional"は、そのフィールドがASN.1データに存在しなくてもエラーにならないことを示します。 - このコミット以前の
encoding/asn1パッケージには、フィールドが空の場合にそのフィールドをASN.1出力から完全に省略するomitemptyのようなタグがありませんでした。これが、空のスライスが空のSEQUENCEとしてシリアライズされる原因となっていました。
- Goの標準ライブラリである
技術的詳細
このコミットが解決しようとしている技術的な問題は、Goのencoding/asn1パッケージの挙動とPKCS#1秘密鍵のASN.1構造の間のミスマッチにありました。
具体的には、crypto/x509パッケージ内のpkcs1PrivateKey構造体には、RSA秘密鍵の追加の素数を表すAdditionalPrimesというスライスフィールドが存在します。このフィールドは、PKCS#1標準のRSAPrivateKey構造におけるotherPrimeInfosに対応します。
問題は、AdditionalPrimesスライスが空(つまり、RSA鍵が従来の2つの素数のみで構成されている場合)であっても、encoding/asn1パッケージがこれをASN.1の空のSEQUENCEとしてエンコードしてしまっていた点です。
-- 誤ったエンコーディングの例 (空のAdditionalPrimesが空のSEQUENCEとして出力される)
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER,
publicExponent INTEGER,
privateExponent INTEGER,
prime1 INTEGER,
prime2 INTEGER,
exponent1 INTEGER,
exponent2 INTEGER,
coefficient INTEGER,
otherPrimeInfos SEQUENCE OF OtherPrimeInfo OPTIONAL -- ここが空のSEQUENCEとして出力される
}
PKCS#1 v1.5のRSAPrivateKey構造には、otherPrimeInfosフィールド自体が存在しません。したがって、PKCS#1 v1.5の鍵を期待する外部のパーサーは、このフィールドが存在しないことを前提としています。しかし、Goが生成した鍵が空のSEQUENCEとしてotherPrimeInfosを含んでいると、外部パーサーはこれをPKCS#1 v2.1以降の構造と誤認し、かつそのotherPrimeInfosフィールドの処理に対応していない場合に、パースエラーやセキュリティ上の問題を引き起こす可能性がありました。
このコミットは、encoding/asn1パッケージにomitemptyタグのサポートを導入することで、この問題を根本的に解決します。
omitemptyタグの導入:encoding/asn1の内部で、構造体フィールドのタグにomitemptyが指定されている場合、そのフィールドがGoのゼロ値(スライスやマップの場合は空)であれば、ASN.1エンコード結果からそのフィールドを完全に省略するように変更されます。pkcs1.goの変更:pkcs1PrivateKey構造体のAdditionalPrimesフィールドにasn1:"optional,omitempty"タグが追加されます。これにより、AdditionalPrimesスライスが空の場合、対応するotherPrimeInfosフィールドはASN.1出力から完全に削除され、PKCS#1 v1.5の鍵の正しい形式に準拠するようになります。
-- 正しいエンコーディングの例 (空のAdditionalPrimesが完全に省略される)
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER,
publicExponent INTEGER,
privateExponent INTEGER,
prime1 INTEGER,
prime2 INTEGER,
exponent1 INTEGER,
exponent2 INTEGER,
coefficient INTEGER
-- otherPrimeInfos は完全に省略される
}
この変更により、Goで生成されたPKCS#1秘密鍵は、PKCS#1 v1.5の鍵であればotherPrimeInfosフィールドを含まず、PKCS#1 v2.1以降の鍵で追加の素数が存在する場合にのみotherPrimeInfosフィールドが適切にエンコードされるようになり、外部システムとの互換性が大幅に向上します。
コアとなるコードの変更箇所
このコミットでは、主に以下の5つのファイルが変更されています。
-
src/pkg/crypto/x509/pkcs1.gopkcs1PrivateKey構造体のAdditionalPrimesフィールドのASN.1タグが変更されました。- 変更前:
AdditionalPrimes []pkcs1AdditionalRSAPrimeasn1:"optional"` - 変更後:
AdditionalPrimes []pkcs1AdditionalRSAPrimeasn1:"optional,omitempty"`
- 変更前:
-
src/pkg/encoding/asn1/asn1_test.goparseFieldParametersTestData内のテストケースが更新され、omitemptyの新しいフィールドがfalseとして追加されました。
-
src/pkg/encoding/asn1/common.gofieldParameters構造体にomitEmpty boolフィールドが追加されました。parseFieldParameters関数内で、構造体タグの解析ロジックにomitemptyという文字列が検出された場合にret.omitEmpty = trueを設定する処理が追加されました。
-
src/pkg/encoding/asn1/marshal.gomarshalField関数内に、スライスが空でomitEmptyが指定されている場合にエンコードをスキップするロジックが追加されました。if v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty { return }
-
src/pkg/encoding/asn1/marshal_test.goomitemptyの動作を検証するための新しいテスト構造体omitEmptyTestが追加されました。marshalTests変数に、空のスライスを持つomitEmptyTestと、要素を持つomitEmptyTestの2つの新しいテストケースが追加されました。これにより、omitemptyが正しく機能し、空のスライスがエンコードされないこと、および空でないスライスが正しくエンコードされることが検証されます。
コアとなるコードの解説
このコミットの主要な変更は、Goのencoding/asn1パッケージにomitemptyタグの機能を追加し、それをcrypto/x509パッケージのPKCS#1秘密鍵構造に適用することです。
-
src/pkg/encoding/asn1/common.goの変更:fieldParameters構造体は、Goの構造体フィールドに付与されたasn1タグの解析結果を保持します。ここにomitEmpty boolフィールドが追加されたことで、タグにomitemptyが含まれているかどうかの情報が保持できるようになりました。parseFieldParameters関数は、asn1:"..."タグの文字列を解析し、optional,explicit,defaultなどのパラメータを抽出します。この変更により、omitemptyという文字列がタグ内に見つかった場合、fieldParametersのomitEmptyフラグがtrueに設定されるようになります。これは、Goのencoding/jsonパッケージなどと同様のタグ解析メカニズムです。
-
src/pkg/encoding/asn1/marshal.goの変更:marshalField関数は、Goの構造体の個々のフィールドをASN.1形式にエンコードする主要なロジックを含んでいます。- 追加された以下のコードブロックがこのコミットの核心です。
このコードは、エンコードしようとしているGoのif v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty { return }reflect.Value(v) が以下の3つの条件をすべて満たす場合に、そのフィールドのエンコード処理を即座に終了(return)させます。v.Kind() == reflect.Slice: フィールドがスライス型であること。v.Len() == 0: そのスライスが空であること。params.omitEmpty: そのフィールドに対応するfieldParametersにomitemptyタグが指定されていること。 このロジックにより、空のスライスでomitemptyが指定されているフィールドは、ASN.1出力から完全に省略されるようになります。
-
src/pkg/crypto/x509/pkcs1.goの変更:pkcs1PrivateKey構造体は、PKCS#1形式のRSA秘密鍵のGo言語での表現です。AdditionalPrimes []pkcs1AdditionalRSAPrimeフィールドは、PKCS#1 v2.1以降で導入された多素数RSAの「追加の素数」情報を保持するスライスです。- このフィールドのタグが
asn1:"optional"からasn1:"optional,omitempty"に変更されたことで、上記のmarshal.goの新しいロジックが適用されるようになります。つまり、AdditionalPrimesスライスが空の場合(通常の2素数RSA鍵の場合など)、このフィールドはASN.1エンコード結果から完全に省略され、PKCS#1 v1.5の鍵構造との互換性が確保されます。
-
src/pkg/encoding/asn1/marshal_test.goの変更:- 新しいテストケース
omitEmptyTestが追加され、omitemptyタグが期待通りに動作するかを検証します。 {omitEmptyTest{[]string{}}, "3000"}: このテストケースは、空のスライスを持つomitEmptyTest構造体をマーシャリングした結果が"3000"(ASN.1の空のSEQUENCEのDERエンコーディング)になることを期待しています。これは、omitemptyが適用される前の挙動を示しており、このコミットの変更によってこのテストは失敗するはずです。注: このテストケースは、コミット後のコードでは"3000"ではなく空のバイト列を期待するように変更されるべきですが、元のコミットではこのようになっています。これは、omitemptyが導入される前の挙動をテストしているか、あるいはテストコードの意図が少し異なる可能性があります。しかし、コミットメッセージと他のコード変更から判断すると、空のシーケンスが出力されないことが目的です。{omitEmptyTest{[]string{"1"}}, "30053003130131"}: このテストケースは、要素を持つスライスが正しくエンコードされることを確認します。
- 新しいテストケース
これらの変更により、Goのencoding/asn1パッケージはより柔軟になり、特に暗号関連の標準で求められる厳密なASN.1エンコーディング要件(特定のフィールドが空の場合に完全に省略されるべきか否か)に対応できるようになりました。
関連リンク
- Go CL (Code Review): https://golang.org/cl/5729062
参考にした情報源リンク
- PKCS#1 private key additional primes:
- ietf.org: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEthCgtCyCRAJX1YVxeH1HZ_VFrixQ66I_1JZ_ZIJI8UaoiQAB7LpP06czXQHg-iX2QQHQMFtMetQ15Hizj9cGjG0CmktCvzuMLr7ODvO6f3v4sw-t-KS-eSDRW2SSQy6XtGK78kas7
- nop.hu: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF1r1G-GRFpZpANQBaKvSn-zdEQwlSXTZDXRSXhjtnYqZT32ZQ9DpWPuMVi9C8eDfJxPUOTQk7Lqp3PJMOx14YL_K8yz4txuBadnlSJQ8m7HOgIdkP6QngM2PUxyOOhGh9aCw_m_BfFwg==
- docs.rs: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGME7YRHCZFiHwWkldghiU7V3QmTxEHlfiYN0T4Ew9sTjOFdUENjaiu9Hj5vbkIV-YmhvWaJT6Ih7uDwYgMyBwTyMr-_aVgRQqH2fTGYTwqv-OcG3WwD4lYStnt2Gs6vTfkeCicaIz9BK7RVZaIqb0MvOBrsymU
- ASN.1 omitempty Go:
- ubc.ca: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHzldNM9hNIhRsW6iBeY33oyvkrxgT_msOhgns_q4tF3NHRbvFTk4_5RufattU6rb6P2wg4E__mwSYYixRhDy9JdtNxa0sPFVh2maZPqqEnxraserk7Q0hqbUqYtBVY55rPL6iotbh6KzPF5PpTTfponHGL3FiCLFY24g3GrsqA-sW0s4l8lsBSfuO3k5n4ZxCxmIzuMZEUvpfB
- go.dev: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGihdvf_izAgW_6QiPwGP52tEXG5PqJob5k5B6LTV6E9qo5hp_aEdAlioLJz6qHeW9mA8tQWr25vd6CwOyYkrphqOVQxF7h0Fe9RkvWE-ifj1zB6K4Y6kugpQhW
- PKCS#1 v1 vs v2:
- wikipedia.org: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHL2xyxF_hioIJQp1St_qmwlpE7gIen0z5SnqchoY36iG_KmDaD_rpToV43bKtX9-Fp0mMqnv5Kf-ghFewnKIa65XTCY3HVZv2z1WKGUN9GzWg8WKhd1rYm5P9hWBImEQ==
- stackexchange.com: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEq_QCkaloqAWz8QWUfNaZfv63BZFaVhN6s9sMQFSt1vxFi2sPDylihgzFMXE-IbM7lmdq5OHXI9rfxo90Tp3pm_3IgfaZgdh5qAiRfs6VbeAACqRx4yUPmF9DGYnr9gtWr9M49kMLLOD59MEzav2NbR0LONxce2GZSpMLrCR1pUy93CU_EPagTJApLE_o=