[インデックス 12910] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/asn1
パッケージにおける、ASN.1 (Abstract Syntax Notation One) の長さエンコーディングの処理に関する重要な改善を含んでいます。具体的には、ASN.1構造体の要素の長さを表現する際に、Goの int
型が扱える最大値まで対応できるように修正されました。これにより、以前の2^24バイトという制限が緩和され、より大きなデータ構造をASN.1形式で正確にデコードできるようになります。また、DER (Distinguished Encoding Rules) の厳格な要件に従い、長さフィールドにおける冗長な先行ゼロをエラーとして検出するようになりました。
コミット
- Author: Adam Langley agl@golang.org
- Date: Wed Apr 18 13:41:11 2012 -0400
- Commit Hash:
6742d0a085021d9638a4d59dd8fb562aec8d51a8
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6742d0a085021d9638a4d59dd8fb562aec8d51a8
元コミット内容
encoding/asn1: allow lengths up to the maximum int value.
Previously we capped lengths at 2**24 rather than 2**31.
R=golang-dev, r, r
CC=golang-dev
https://golang.org/cl/6050050
変更の背景
この変更の主な背景は、encoding/asn1
パッケージがASN.1データの長さをデコードする際に、不必要に厳しい制限を設けていた点にあります。以前の実装では、ASN.1の長さフィールドが2^24バイト(約16MB)を超える場合にエラーとして扱っていました。しかし、Goの int
型は通常32ビットシステムで2^31-1、64ビットシステムで2^63-1までの値を表現できます。この2^24という制限は、int
型の最大値よりもはるかに小さく、正当なASN.1データであってもデコードできないケースが発生する可能性がありました。
このコミットは、この不整合を解消し、encoding/asn1
がGoの int
型で表現可能な最大長まで適切に処理できるようにすることを目的としています。これにより、より大きなASN.1構造体や、特に証明書や暗号化関連のデータなど、大きなバイナリデータを扱う際に、より堅牢な処理が可能になります。
また、DER (Distinguished Encoding Rules) は、ASN.1データのエンコーディングに関する厳格なルールセットであり、特定のエンコーディングが唯一の正しい形式であることを保証します。DERのルールの一つに、長さフィールドは「最小限のバイト数」でエンコードされなければならないというものがあります。つまり、冗長な先行ゼロは許されません。以前の実装ではこのチェックが不十分であったため、このコミットではその点も修正し、DERの厳格な要件に準拠するようにしています。
前提知識の解説
ASN.1 (Abstract Syntax Notation One)
ASN.1は、データ構造を記述するための標準的な記法です。通信プロトコルやデータストレージにおいて、異なるシステム間でデータを交換する際に、そのデータの構造と型を明確に定義するために使用されます。ASN.1自体は抽象的な記法であり、具体的なバイト列への変換ルールはエンコーディングルールによって定義されます。
DER (Distinguished Encoding Rules)
DERは、ASN.1のエンコーディングルールの一つであり、特に厳格なルールセットを持つバイナリエンコーディングです。DERの主な特徴は、特定のASN.1値に対して常に一意のバイト列を生成することです。これは、デジタル署名やハッシュ計算など、データの同一性が保証される必要がある場面で非常に重要です。X.509証明書やPKCS#10証明書署名要求など、多くのセキュリティ関連の標準でDERが使用されています。
ASN.1の長さエンコーディング
ASN.1では、各データ要素の「タグ」と「長さ」と「値」の3つの部分で構成されます。長さフィールドは、その要素の「値」部分が何バイト続くかを示します。長さのエンコーディングには、主に以下の3つの形式があります。
- 短形式 (Short Form): 長さが0から127バイトの場合。1バイトで表現され、最上位ビットは0、残りの7ビットで長さを表します。
- 長形式 (Long Form): 長さが128バイト以上の場合。最初の1バイトの最上位ビットは1、残りの7ビットは後続の長さバイト数を示します。後続のバイト列が実際の長さをビッグエンディアンで表現します。例えば、最初のバイトが
0x82
であれば、続く2バイトが長さを表します。 - 不定長形式 (Indefinite Form): 長さが不明な場合。最初のバイトが
0x80
で、値の終わりに0x00 0x00
の終端マーカーが置かれます。DERでは不定長形式は許可されていません。
このコミットで問題となっているのは、主に長形式の処理です。
Go言語の int
型
Go言語の int
型は、プラットフォームに依存する符号付き整数型です。32ビットシステムでは32ビット幅(-2,147,483,648 から 2,147,483,647)、64ビットシステムでは64ビット幅(-9,223,372,036,854,775,808 から 9,223,372,036,854,775,807)を持ちます。このコミットの文脈では、特に32ビットシステムにおける int
の最大値(約2 * 10^9)が重要になります。2^31は、32ビット符号付き整数の最大値に非常に近い値です。
技術的詳細
このコミットの技術的詳細は、encoding/asn1
パッケージの parseTagAndLength
関数における長さのデコードロジックの変更に集約されます。
以前の制限 (2^24) の理由と問題点
元のコードでは、長形式のエンコーディングにおいて、長さを示すバイト数が3バイトを超える場合(つまり、長さが2^24を超える場合)にエラーとしていました。これは、numBytes > 3
という条件でチェックされていました。
numBytes
は、長さを示すために後続するバイトの数です。
numBytes = 1
の場合、長さは1バイトで表現され、最大2^8 - 1 = 255。numBytes = 2
の場合、長さは2バイトで表現され、最大2^16 - 1 = 65535。numBytes = 3
の場合、長さは3バイトで表現され、最大2^24 - 1 = 16,777,215。numBytes = 4
の場合、長さは4バイトで表現され、最大2^32 - 1。
したがって、numBytes > 3
の制限は、長さを2^24で上限設定していたことを意味します。これは、Goの int
型が32ビットシステムで2^31-1まで扱えるにもかかわらず、不必要に低い上限を設けていたことになります。この制限は、大きなASN.1構造体を扱う際に問題を引き起こす可能性がありました。
新しい長さの処理と int
の最大値への対応
コミットでは、numBytes > 3
のチェックが削除されました。代わりに、長さの計算中に ret.length
が 1<<23
(2^23) を超えるかどうかをチェックするようになりました。
if ret.length >= 1<<23 {
// We can't shift ret.length up without
// overflowing.
err = StructuralError{"length too large"}
return
}
ret.length <<= 8
ret.length |= int(b)
この変更の意図は、ret.length
が次のバイトをシフトして追加する前に、int
型のオーバーフローを防ぐことです。int
型が32ビットの場合、最大値は約2^31です。ret.length <<= 8
は ret.length
を8ビット左にシフトする操作です。もし ret.length
が既に 2^23
(約800万) を超えている場合、さらに8ビット左にシフトすると 2^31
(約20億) を超え、32ビット符号付き int
の範囲を逸脱する可能性があります。このチェックにより、int
型の最大値を超えるような非常に大きな長さが検出され、エラーとして処理されるようになります。これにより、Goの int
型が扱える最大値まで長さを許容しつつ、オーバーフローを適切に防ぐことができます。
冗長な先行ゼロの検出
DERの厳格なルールに従うため、長さフィールドに冗長な先行ゼロが含まれている場合にエラーを返すようになりました。これは、長形式のエンコーディングにおいて、長さが0でないにもかかわらず、その表現に不要なゼロが先頭に付加されているケースを指します。
if ret.length == 0 {
// DER requires that lengths be minimal.
err = StructuralError{"superfluous leading zeros in length"}
return
}
このチェックは、長さがデコードされた後に ret.length
が0であるにもかかわらず、それが長形式でエンコードされていた場合に発動します。例えば、長さが1バイトであるにもかかわらず、0x82 0x00 0x01
のように2バイトで 0x0001
と表現されている場合、これは冗長な先行ゼロとみなされ、DERの要件に違反します。この修正により、encoding/asn1
はよりDERに準拠したパーサーとなります。
コアとなるコードの変更箇所
src/pkg/encoding/asn1/asn1.go
ファイルの parseTagAndLength
関数が変更されています。
--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -377,11 +377,6 @@ func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset i
} else {
// Bottom 7 bits give the number of length bytes to follow.
numBytes := int(b & 0x7f)
- // We risk overflowing a signed 32-bit number if we accept more than 3 bytes.
- if numBytes > 3 {
- err = StructuralError{"length too large"}
- return
- }
if numBytes == 0 {
err = SyntaxError{"indefinite length found (not DER)"}
return
@@ -394,8 +389,19 @@ func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset i
}
b = bytes[offset]
offset++
+ if ret.length >= 1<<23 {
+ // We can't shift ret.length up without
+ // overflowing.
+ err = StructuralError{"length too large"}
+ return
+ }
ret.length <<= 8
ret.length |= int(b)
+ if ret.length == 0 {
+ // DER requires that lengths be minimal.
+ err = StructuralError{"superfluous leading zeros in length"}
+ return
+ }
}
}
また、src/pkg/encoding/asn1/asn1_test.go
にもテストケースが追加されています。
--- a/src/pkg/encoding/asn1/asn1_test.go
+++ b/src/pkg/encoding/asn1/asn1_test.go
@@ -283,6 +283,12 @@ var tagAndLengthData = []tagAndLengthTest{\n {[]byte{0x00, 0x83, 0x01, 0x00}, false, tagAndLength{}},\n {[]byte{0x1f, 0x85}, false, tagAndLength{}},\n {[]byte{0x30, 0x80}, false, tagAndLength{}},\n+\t// Superfluous zeros in the length should be an error.\n+\t{[]byte{0xa0, 0x82, 0x00, 0x01}, false, tagAndLength{}},\n+\t// Lengths up to the maximum size of an int should work.\n+\t{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}},\n+\t// Lengths that would overflow an int should be rejected.\n+\t{[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}},\n }\n
func TestParseTagAndLength(t *testing.T) {
コアとなるコードの解説
src/pkg/encoding/asn1/asn1.go
の変更点
-
numBytes > 3
の削除:- 以前のコードでは、長さを示すバイト数
numBytes
が3を超える場合にStructuralError{"length too large"}
を返していました。これは、長さを2^24で制限していたことに相当します。 - この行が削除されたことで、長さの表現に4バイト以上を使用することが可能になり、Goの
int
型が扱える最大値まで長さを許容するようになりました。
- 以前のコードでは、長さを示すバイト数
-
if ret.length >= 1<<23
の追加:- これは、長さのデコード中に
int
型のオーバーフローを防ぐためのチェックです。 ret.length
は現在までにデコードされた長さの値です。次のバイトb
をret.length <<= 8
で左に8ビットシフトして結合する前に、ret.length
が1<<23
(2^23) 以上であるかをチェックします。- もし
ret.length
が1<<23
以上であれば、さらに8ビット左シフトすると、32ビット符号付きint
の最大値である2^31 - 1
を超える可能性が高くなります。この場合、StructuralError{"length too large"}
を返してエラーとします。これにより、int
型の範囲内で安全に長さを処理できるようになります。
- これは、長さのデコード中に
-
if ret.length == 0
の追加:- これは、DERの厳格なルールである「長さは最小限のバイト数で表現されなければならない」という要件を満たすためのチェックです。
- 長さがデコードされた後、もし
ret.length
が0
であるにもかかわらず、それが長形式でエンコードされていた場合(つまり、長さを示すバイト列が0x00
などの冗長なゼロを含んでいた場合)、StructuralError{"superfluous leading zeros in length"}
を返します。 - 例えば、長さが
0x01
であるべきなのに0x82 0x00 0x01
のようにエンコードされている場合、ret.length
は最終的に1
になりますが、このチェックはret.length
が0
の場合にのみ発動するため、この特定の例では直接エラーにはなりません。しかし、例えば0x81 0x00
のように長さが0なのに長形式でエンコードされているようなケースを捕捉します。テストケース// Superfluous zeros in the length should be an error. {[]byte{0xa0, 0x82, 0x00, 0x01}, false, tagAndLength{}},
は、このチェックがどのように機能するかを示唆しています。このテストケースでは、0x0001
という長さが0x82
でエンコードされているため、ret.length
が0
になることはありませんが、このテストはparseTagAndLength
が0x0001
のような冗長なゼロを含む長さを正しくエラーとして処理することを確認しています。
src/pkg/encoding/asn1/asn1_test.go
の変更点
追加されたテストケースは、上記の変更が正しく機能することを確認しています。
-
{[]byte{0xa0, 0x82, 0x00, 0x01}, false, tagAndLength{}},
- これは、長さフィールドに冗長な先行ゼロ (
0x00
) が含まれている場合に、エラーとなることをテストしています。0x82
は続く2バイトが長さであることを示し、0x00 0x01
は長さが1であることを意味しますが、これは0x01
と短形式で表現できるため、DERでは冗長とみなされます。
- これは、長さフィールドに冗長な先行ゼロ (
-
{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}},
- これは、Goの32ビット
int
型の最大値 (0x7fffffff
、つまり2^31-1) までの長さが正しくデコードされることをテストしています。0x84
は続く4バイトが長さであることを示します。
- これは、Goの32ビット
-
{[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}},
- これは、Goの32ビット
int
型の最大値を超える長さ(0x80000000
、つまり2^31)がエラーとして拒否されることをテストしています。これは、if ret.length >= 1<<23
のチェックによって捕捉されます。
- これは、Goの32ビット
これらの変更とテストケースにより、encoding/asn1
パッケージは、より大きなASN.1データの長さを正確に処理し、DERの厳格なエンコーディングルールに準拠するようになりました。
関連リンク
- Go CL 6050050: https://golang.org/cl/6050050
参考にした情報源リンク
- ITU-T Recommendation X.690 (08/2015) - ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER): https://www.itu.int/rec/T-REC-X.690-201508-I/en
- Go言語の
int
型に関するドキュメント (Go言語の公式ドキュメントや仕様を参照) - ASN.1 Length Field Encoding: https://www.oss.com/asn1/resources/asn1-made-simple/asn1-length-field-encoding.html
- DER Encoding of Length: https://www.obj-sys.com/asn1tutorial/node13.html
- Stack Overflow: "What is the maximum value of an int in Go?" (一般的なGoの
int
型に関する情報源)