Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16402] ファイルの概要

このコミットは、Go言語の encoding/asn1 パッケージにおけるDER (Distinguished Encoding Rules) エンコードされたブーリアン値のパース処理をより厳密にするための変更です。具体的には、X.690標準に準拠し、ブーリアン値としてエンコードが許容されるのは 0 (FALSE) と 255 (TRUE) のオクテットのみであることを強制するよう修正されました。また、この変更を検証するための新しいテストケースが追加されています。

コミット

commit af48543c5458086435c92f63677723217900c1b5
Author: Gerasimos Dimitriadis <gedimitr@gmail.com>
Date:   Fri May 24 12:37:42 2013 -0400

    asn1: Stricter checks for DER encoded booleans
    
    According to X.690, only 0 and 255 are allowed as values
    for encoded booleans. Also added some test for parsing
    booleans
    
    R=golang-dev, agl, r
    CC=golang-dev
    https://golang.org/cl/9692043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/af48543c5458086435c92f63677723217900c1b5

元コミット内容

asn1: Stricter checks for DER encoded booleans

According to X.690, only 0 and 255 are allowed as values
for encoded booleans. Also added some test for parsing
booleans

変更の背景

この変更の背景には、ASN.1 (Abstract Syntax Notation One) のエンコーディングルールの一つであるDER (Distinguished Encoding Rules) の厳密な要件があります。以前の実装では、ブーリアン値のエンコードにおいて、単一オクテットであれば 0 以外は全て true と解釈されていました。しかし、X.690標準(ASN.1エンコーディング仕様)のDERセクションでは、ブーリアン値のエンコードは非常に厳密に定義されており、FALSE0x00TRUE0xFF (全てのビットが1) の単一オクテットでなければならないと規定されています。

このコミットは、Goの encoding/asn1 パッケージがこのDERの厳密な要件に準拠するように修正することを目的としています。これにより、不正な形式のブーリアンエンコーディングがパースされた場合にエラーを返すようになり、より堅牢で標準に準拠したASN.1パーサーが提供されます。

前提知識の解説

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法です。異なるシステム間でデータを交換する際に、データの表現方法を抽象的に定義するために使用されます。これにより、プラットフォームやプログラミング言語に依存しないデータ交換が可能になります。ASN.1自体はデータの「構造」を定義するものであり、その構造をバイト列に変換する具体的な「エンコーディングルール」は別途定義されます。

DER (Distinguished Encoding Rules)

DERは、ASN.1で定義されたデータ構造をバイト列にエンコードするためのエンコーディングルールの一つです。DERの最大の特徴は、一意性 (uniqueness) です。つまり、特定のASN.1データ構造に対して、DERエンコーディングは常にただ一つのバイト列に決定されます。これは、デジタル署名やハッシュ計算など、データの表現が一意であることが求められるセキュリティ関連のプロトコルで特に重要です。

DERは、BER (Basic Encoding Rules) のサブセットであり、BERが許容する複数のエンコーディング形式の中から、特定の形式のみを許可することで一意性を保証します。

ASN.1におけるブーリアン値のエンコーディング (DER)

X.690標準のDERセクションでは、ブーリアン値のエンコーディングについて以下のように規定されています。

  • タグ: ブーリアン型はユニバーサルタグ BOOLEAN (1) を持ちます。
  • 長さ: ブーリアン値のエンコードは常に1オクテットの長さです。
  • :
    • FALSE の場合、値オクテットは 0x00 (全てのビットが0) でなければなりません。
    • TRUE の場合、値オクテットは 0xFF (全てのビットが1) でなければなりません。

これ以外の値(例えば 0x010x7F など)は、DERエンコードされたブーリアンとしては不正と見なされます。

技術的詳細

このコミットは、主に src/pkg/encoding/asn1/asn1.go ファイル内の parseBool 関数と、それに対応するテストファイル src/pkg/encoding/asn1/asn1_test.go に変更を加えています。

parseBool 関数の変更

変更前は、parseBool 関数はブーリアン値のバイト列の長さが1オクテットであることを確認した後、そのオクテットが 0 でなければ true と解釈していました。これはBERの一般的な解釈には合致しますが、DERの厳密な要件には準拠していませんでした。

変更後、parseBool 関数は以下のチェックを追加しています。

  1. 長さのチェック: 引き続き、入力バイト列の長さが1オクテットであることを確認します。これに違反する場合は SyntaxError を返します。
  2. 値の厳密なチェック: switch ステートメントを使用して、単一のオクテットが 0x00 または 0xFF であるかを厳密にチェックします。
    • 0x00 の場合は false を返します。
    • 0xFF の場合は true を返します。
    • これら以外の値(例: 0x01)の場合は、SyntaxError{"encoding/asn1: invalid boolean"} を返してエラーとします。

これにより、DERの仕様に厳密に準拠したブーリアンパースが実現されます。

テストケースの追加と修正

src/pkg/encoding/asn1/asn1_test.go には、TestParseBool という新しいテスト関数が追加されました。このテストは、boolTestData という構造化されたテストデータを使用して、parseBool 関数の新しい厳密な挙動を検証します。

boolTestData には、以下のケースが含まれています。

  • {[]byte{0x00}, true, false}: 正しい FALSE エンコーディング。
  • {[]byte{0xff}, true, true}: 正しい TRUE エンコーディング。
  • {[]byte{0x00, 0x00}, false, false}: 長さが不正なケース(エラーが期待される)。
  • {[]byte{0xff, 0xff}, false, false}: 長さが不正なケース(エラーが期待される)。
  • {[]byte{0x01}, false, false}: DERでは不正な TRUE エンコーディング(エラーが期待される)。

また、既存の unmarshalTestData の一部も修正されています。特に、{[]byte{0x01, 0x01, 0x01}, newBool(true)} というテストケースが {[]byte{0x01, 0x01, 0xff}, newBool(true)} に変更され、TRUE のエンコーディングが 0x01 から 0xff に修正されています。これは、Unmarshal 関数が内部で parseBool を呼び出すため、その挙動がDERに準拠するように整合性を保つための変更です。

コアとなるコードの変更箇所

diff --git a/src/pkg/encoding/asn1/asn1.go b/src/pkg/encoding/asn1/asn1.go
old mode 100644
new mode 100755
index cac9d64b5e..a14df04eff
--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -47,11 +47,23 @@ func (e SyntaxError) Error() string { return "ASN.1 syntax error: " + e.Msg }
 
 func parseBool(bytes []byte) (ret bool, err error) {
 	if len(bytes) != 1 {
-		err = SyntaxError{"invalid boolean"}
+		err = SyntaxError{"encoding/asn1: invalid boolean"}
 		return
 	}
 
-	return bytes[0] != 0, nil
+	// DER demands that "If the encoding represents the boolean value TRUE,
+	// its single contents octet shall have all eight bits set to one."
+	// Thus only 0 and 255 are valid encoded values.
+	switch bytes[0] {
+	case 0:
+		ret = false
+	case 0xff:
+		ret = true
+	default:
+		err = SyntaxError{"encoding/asn1: invalid boolean"}
+	}
+
+	return
 }
 
 // INTEGER
diff --git a/src/pkg/encoding/asn1/asn1_test.go b/src/pkg/encoding/asn1/asn1_test.go
old mode 100644
new mode 100755
index 6e98dcf0b9..fb82937b7e
--- a/src/pkg/encoding/asn1/asn1_test.go
+++ b/src/pkg/encoding/asn1/asn1_test.go
@@ -12,6 +12,32 @@ import (
 	"time"
 )
 
+type boolTest struct {
+	in  []byte
+	ok  bool
+	out bool
+}
+
+var boolTestData = []boolTest{
+	{[]byte{0x00}, true, false},
+	{[]byte{0xff}, true, true},
+	{[]byte{0x00, 0x00}, false, false},
+	{[]byte{0xff, 0xff}, false, false},
+	{[]byte{0x01}, false, false},
+}
+
+func TestParseBool(t *testing.T) {
+	for i, test := range boolTestData {
+		ret, err := parseBool(test.in)
+		if (err == nil) != test.ok {
+			t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok)
+		}
+		if test.ok && ret != test.out {
+			t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out)
+		}
+	}
+}
+
 type int64Test struct {
 	in  []byte
 	ok  bool
@@ -378,7 +404,7 @@ var unmarshalTestData = []struct {
 	{[]byte{0x30, 0x03, 0x81, 0x01, 0x01}, &TestContextSpecificTags{1}},
 	{[]byte{0x30, 0x08, 0xa1, 0x03, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02}, &TestContextSpecificTags2{1, 2}},
 	{[]byte{0x01, 0x01, 0x00}, newBool(false)},
-	{[]byte{0x01, 0x01, 0x01}, newBool(true)},
+	{[]byte{0x01, 0x01, 0xff}, newBool(true)},
 	{[]byte{0x30, 0x0b, 0x13, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x22, 0x02, 0x01, 0x33}, &TestElementsAfterString{"foo", 0x22, 0x33}},
 	{[]byte{0x30, 0x05, 0x02, 0x03, 0x12, 0x34, 0x56}, &TestBigInt{big.NewInt(0x123456)}},
 }

コアとなるコードの解説

src/pkg/encoding/asn1/asn1.goparseBool 関数

func parseBool(bytes []byte) (ret bool, err error) {
	if len(bytes) != 1 {
		err = SyntaxError{"encoding/asn1: invalid boolean"}
		return
	}

	// DER demands that "If the encoding represents the boolean value TRUE,
	// its single contents octet shall have all eight bits set to one."
	// Thus only 0 and 255 are valid encoded values.
	switch bytes[0] {
	case 0:
		ret = false
	case 0xff:
		ret = true
	default:
		err = SyntaxError{"encoding/asn1: invalid boolean"}
	}

	return
}

この関数は、ASN.1のブーリアン値を表すバイトスライス bytes を受け取り、Goの bool 型とエラーを返します。

  1. if len(bytes) != 1: まず、入力されたバイトスライスの長さが1オクテットであるかをチェックします。DERではブーリアン値は常に1オクテットでエンコードされるため、これ以外の長さは不正です。不正な場合は SyntaxError を設定して関数を終了します。エラーメッセージもより具体的になりました ("encoding/asn1: invalid boolean")。
  2. コメント: DERの仕様に関する重要なコメントが追加されています。「エンコーディングがブーリアン値TRUEを表す場合、その単一のコンテンツオクテットは8ビット全てが1に設定されていなければならない。」と明記し、0255 (0xFF) のみが有効なエンコード値であることを強調しています。
  3. switch bytes[0]: 入力バイトスライスの最初の(唯一の)オクテットの値に基づいて処理を分岐します。
    • case 0:: オクテットが 0x00 の場合、retfalse に設定します。
    • case 0xff:: オクテットが 0xFF の場合、rettrue に設定します。
    • default:: 上記のいずれでもない場合(例: 0x01 など)、DERの仕様に違反するため、SyntaxError を設定してエラーを返します。

この変更により、parseBool 関数はDERの厳密なブーリアンエンコーディングルールに準拠し、不正な形式のブーリアン値に対しては明確にエラーを返すようになりました。

src/pkg/encoding/asn1/asn1_test.go の変更

type boolTest struct {
	in  []byte
	ok  bool
	out bool
}

var boolTestData = []boolTest{
	{[]byte{0x00}, true, false},
	{[]byte{0xff}, true, true},
	{[]byte{0x00, 0x00}, false, false},
	{[]byte{0xff, 0xff}, false, false},
	{[]byte{0x01}, false, false},
}

func TestParseBool(t *testing.T) {
	for i, test := range boolTestData {
		ret, err := parseBool(test.in)
		if (err == nil) != test.ok {
			t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok)
		}
		if test.ok && ret != test.out {
			t.Errorf("#%d: Bad result: %v (expected %v)", i, ret, test.out)
		}
	}
}
  • boolTest 構造体: テストケースの入力 (in バイトスライス)、期待されるエラーの有無 (ok ブーリアン)、期待されるパース結果 (out ブーリアン) を定義するヘルパ構造体です。
  • boolTestData 変数: boolTest 型のスライスで、parseBool 関数の様々な入力と期待される出力を定義しています。特に、長さが不正なケース (0x00, 0x000xff, 0xff) や、DERでは不正な値 (0x01) がエラーとなることを検証するケースが含まれています。
  • TestParseBool 関数:
    • for ループで boolTestData の各テストケースを反復処理します。
    • parseBool を呼び出し、返された ret (結果のブーリアン) と err (エラー) を取得します。
    • if (err == nil) != test.ok: エラーの有無が期待通りであるかをチェックします。test.oktrue ならエラーがないことを、false ならエラーがあることを期待します。
    • if test.ok && ret != test.out: エラーが発生しなかった場合 (test.oktrue)、パース結果 ret が期待される test.out と一致するかをチェックします。

この新しいテストスイートは、parseBool 関数の厳密なDER準拠の挙動を包括的に検証します。

また、unmarshalTestData の修正は、{[]byte{0x01, 0x01, 0x01}, newBool(true)} から {[]byte{0x01, 0x01, 0xff}, newBool(true)} への変更です。これは、Unmarshal 関数が内部で parseBool を使用するため、TRUE のエンコーディングが 0x01 ではなく 0xff であるべきというDERの要件に合わせて、テストデータ自体を修正したものです。

関連リンク

参考にした情報源リンク