[インデックス 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.go
pkcs1PrivateKey
構造体のAdditionalPrimes
フィールドのASN.1タグが変更されました。- 変更前:
AdditionalPrimes []pkcs1AdditionalRSAPrime
asn1:"optional"` - 変更後:
AdditionalPrimes []pkcs1AdditionalRSAPrime
asn1:"optional,omitempty"`
- 変更前:
-
src/pkg/encoding/asn1/asn1_test.go
parseFieldParametersTestData
内のテストケースが更新され、omitempty
の新しいフィールドがfalse
として追加されました。
-
src/pkg/encoding/asn1/common.go
fieldParameters
構造体にomitEmpty bool
フィールドが追加されました。parseFieldParameters
関数内で、構造体タグの解析ロジックにomitempty
という文字列が検出された場合にret.omitEmpty = true
を設定する処理が追加されました。
-
src/pkg/encoding/asn1/marshal.go
marshalField
関数内に、スライスが空でomitEmpty
が指定されている場合にエンコードをスキップするロジックが追加されました。if v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty { return }
-
src/pkg/encoding/asn1/marshal_test.go
omitempty
の動作を検証するための新しいテスト構造体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=