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

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

このコミットは、Go言語の encoding/asn1 パッケージにおける、ASN.1シーケンス内の非表示文字列(non-printable strings)のパースに関するバグ修正です。具体的には、シーケンス内で文字列をパースする際に、裸の文字列フィールドをパースする場合と同じ基準でタグタイプを変更するように修正されています。これにより、IA5StringGeneralStringT61StringUTF8String などの様々な文字列タイプが PrintableString として扱われ、[]string へのパースが正しく行われるようになります。

コミット

commit 2b693b7c19e2f14c75b01b89bd2df31592e67d1e
Author: Jakob Borg <jakob@nym.se>
Date:   Wed Dec 18 17:06:17 2013 -0500

    encoding/asn1: Fix parsing of non-printable strings in
    sequences.
    
    Use the same criteria for when to modify the tag type when
    parsing a string in a sequence as when parsing a bare string
    field.
    
    Fixes #6726.
    
    R=golang-dev, bradfitz, gobot, agl
    CC=golang-dev
    https://golang.org/cl/22460043

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

https://github.com/golang/go/commit/2b693b7c19e2f14c75b01b89bd2df31592e67d1e

元コミット内容

encoding/asn1: Fix parsing of non-printable strings in
sequences.

Use the same criteria for when to modify the tag type when
parsing a string in a sequence as when parsing a bare string
field.

Fixes #6726.

変更の背景

このコミットは、Go言語の encoding/asn1 パッケージにおける既存のバグを修正するために導入されました。具体的には、ASN.1シーケンス(SEQUENCE OF 構造など)内に含まれる文字列のパースにおいて、非表示文字列(例: IA5String, GeneralString, T61String, UTF8String)が正しく処理されない問題がありました。

以前の実装では、シーケンス内の文字列をパースする際に、GeneralString のみが PrintableString として扱われる特別なケースがありました。しかし、他の非表示文字列タイプは考慮されておらず、これにより []string のようなGoのスライス型にデコードしようとすると、パースエラーが発生するか、意図しない結果になる可能性がありました。

コミットメッセージにある Fixes #6726 は、この問題がGoプロジェクトの内部イシュートラッカーで報告されたバグであることを示唆しています。一般的なWeb検索ではこの特定のイシューの詳細は見つかりませんでしたが、コミット内容から、ASN.1の文字列エンコーディングとGoの型システム間のミスマッチが原因であったと推測されます。

前提知識の解説

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法であり、通信プロトコルやデータストレージにおいて、異なるシステム間でデータを交換する際に使用されます。ASN.1は、データの抽象的な構文を定義し、そのデータをバイト列にエンコード(符号化)およびデコード(復号化)するためのエンコーディングルール(例: DER, BER, PER)と組み合わせて使用されます。

ASN.1のデータ型には、整数、ブール値、文字列、シーケンス(構造体のようなもの)、セット、選択(共用体のようなもの)など、多岐にわたるものがあります。

ASN.1の文字列型

ASN.1には様々な文字列型が存在し、それぞれ異なる文字セットやエンコーディングをサポートしています。

  • PrintableString: ASCII文字の一部(英数字、一部の記号)のみを許容する文字列。
  • IA5String: ASCII文字(0-127)を許容する文字列。
  • GeneralString: 任意の文字を許容する汎用的な文字列。
  • T61String (TeletexString): テレテックス文字セットを許容する文字列。
  • UTF8String: UTF-8エンコーディングされたUnicode文字を許容する文字列。

これらの文字列型は、ASN.1のタグ(Tag)によって識別されます。タグは、データの型とクラスを示す識別子です。

Go言語の encoding/asn1 パッケージ

Go言語の encoding/asn1 パッケージは、ASN.1で定義されたデータ構造をGoの構造体やスライスにエンコード/デコードするための機能を提供します。このパッケージは、X.509証明書やPKCS#10証明書署名要求など、暗号化やセキュリティ関連の標準で広く使用されています。

encoding/asn1 パッケージは、Goの reflect パッケージを利用して、Goの型とASN.1のタグや構造をマッピングします。これにより、開発者はGoの構造体タグを使ってASN.1のエンコーディングルールを定義できます。

reflect パッケージ

Go言語の reflect パッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。これにより、型情報(reflect.Type)や値情報(reflect.Value)を動的に取得し、それらを操作することができます。encoding/asn1 パッケージでは、この reflect パッケージを使用して、Goの構造体のフィールドがどのようなASN.1型に対応するかを判断し、エンコード/デコード処理を行います。

技術的詳細

このコミットの技術的な核心は、encoding/asn1 パッケージ内の parseSequenceOf 関数における文字列タグの扱いを改善することにあります。

parseSequenceOf 関数は、ASN.1の SEQUENCE OF 型、つまり同じ型の要素が連続して並ぶ構造をパースする際に使用されます。Goでは、これは通常 []Type のようなスライス型にマッピングされます。

以前の実装では、parseSequenceOf 関数内で、パース対象の要素が tagGeneralString であった場合にのみ、そのタグを tagPrintableString に変更していました。これは、Goの []string 型にデコードする際に、PrintableString がデフォルトで期待されるためと考えられます。しかし、ASN.1には GeneralString 以外にも IA5StringT61StringUTF8String など、Goの string 型にマッピング可能な様々な文字列型が存在します。これらの文字列型がシーケンス内に現れた場合、以前の実装では tagPrintableString への変換が行われず、結果としてパースエラーや不正なデコードが発生する可能性がありました。

今回の修正では、この tagGeneralString のみを特別扱いするロジックを拡張し、IA5StringGeneralStringT61StringUTF8String のいずれかのタグを持つ文字列がシーケンス内で検出された場合、それらを一律に tagPrintableString として扱うように変更されました。これにより、これらの非表示文字列も []string 型に正しくデコードできるようになります。

この変更は、ASN.1の柔軟な文字列エンコーディングとGoの厳密な型システムとの間のギャップを埋めるための重要な修正であり、より堅牢なASN.1パース処理を実現します。

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

変更は src/pkg/encoding/asn1/asn1.gosrc/pkg/encoding/asn1/asn1_test.go の2つのファイルにわたります。

src/pkg/encoding/asn1/asn1.go

--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -451,11 +451,13 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
 		if err != nil {
 			return
 		}
-		// We pretend that GENERAL STRINGs are PRINTABLE STRINGs so
-		// that a sequence of them can be parsed into a []string.
-		if t.tag == tagGeneralString {
+		// We pretend that various other string types are PRINTABLE STRINGs
+		// so that a sequence of them can be parsed into a []string.
+		switch t.tag {
+		case tagIA5String, tagGeneralString, tagT61String, tagUTF8String:
 			t.tag = tagPrintableString
 		}
+
 		if t.class != classUniversal || t.isCompound != compoundType || t.tag != expectedTag {
 			err = StructuralError{"sequence tag mismatch"}
 			return

src/pkg/encoding/asn1/asn1_test.go

--- a/src/pkg/encoding/asn1/asn1_test.go
+++ b/src/pkg/encoding/asn1/asn1_test.go
@@ -6,6 +6,7 @@ package asn1
 
 import (
 	"bytes"
+	"fmt"
 	"math/big"
 	"reflect"
 	"testing"
@@ -776,3 +777,29 @@ var derEncodedPaypalNULCertBytes = []byte{
 	0xc8, 0x64, 0x8c, 0xb5, 0x50, 0x23, 0x82, 0x6f, 0xdb, 0xb8, 0x22, 0x1c, 0x43,
 	0x96, 0x07, 0xa8, 0xbb,
 }
+
+var stringSliceTestData = [][]string{
+	{"foo", "bar"},
+	{"foo", "\\\\bar"},
+	{"foo", "\\\"bar\\\""},
+	{"foo", "åäö"},
+}
+
+func TestStringSlice(t *testing.T) {
+	for _, test := range stringSliceTestData {
+		bs, err := Marshal(test)
+		if err != nil {
+			t.Error(err)
+		}
+
+		var res []string
+		_, err = Unmarshal(bs, &res)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if fmt.Sprintf("%v", res) != fmt.Sprintf("%v", test) {
+			t.Errorf("incorrect marshal/unmarshal; %v != %v", res, test)
+		}
+	}
+}

コアとなるコードの解説

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

parseSequenceOf 関数内の変更がこのコミットの核心です。

元のコード:

		// We pretend that GENERAL STRINGs are PRINTABLE STRINGs so
		// that a sequence of them can be parsed into a []string.
		if t.tag == tagGeneralString {
			t.tag = tagPrintableString
		}

この部分では、パース中のASN.1要素のタグ t.tagtagGeneralString であった場合にのみ、そのタグを tagPrintableString に上書きしていました。これは、Goの []string 型へのデコードを可能にするためのヒューリスティックな処理でした。

変更後のコード:

		// We pretend that various other string types are PRINTABLE STRINGs
		// so that a sequence of them can be parsed into a []string.
		switch t.tag {
		case tagIA5String, tagGeneralString, tagT61String, tagUTF8String:
			t.tag = tagPrintableString
		}

この if 文が switch 文に置き換えられ、tagGeneralString だけでなく、tagIA5StringtagT61StringtagUTF8String も同様に tagPrintableString として扱われるようになりました。これにより、これらの文字列型が SEQUENCE OF 構造内で出現した場合でも、Goの []string 型に正しくデコードされるようになります。この修正は、encoding/asn1 パッケージがより広範なASN.1文字列エンコーディングに対応し、より堅牢なパース処理を提供することを意味します。

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

テストファイルには、stringSliceTestData という新しいテストデータと TestStringSlice というテスト関数が追加されました。

stringSliceTestData は、様々な種類の文字列を含む [][]string 型のデータです。これには、通常の文字列、バックスラッシュを含む文字列、引用符を含む文字列、そして非ASCII文字(日本語の「åäö」に相当する文字)を含む文字列が含まれています。これらの文字列は、ASN.1の異なる文字列型(例えば、UTF8String など)としてエンコードされる可能性があります。

TestStringSlice 関数は、以下の手順でテストを行います。

  1. stringSliceTestData の各要素([]string)を asn1.Marshal を使ってASN.1バイト列にエンコードします。
  2. エンコードされたバイト列を asn1.Unmarshal を使って []string 型の変数 res にデコードします。
  3. 元のデータ test とデコードされたデータ res が一致するかどうかを fmt.Sprintf("%v", ...) を使って比較します。

このテストの追加により、parseSequenceOf 関数の修正が、様々な種類の文字列を含む []string のエンコードとデコードにおいて正しく機能することを確認できます。特に、非ASCII文字を含む文字列が正しく処理されるかどうかの検証は重要です。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分情報: https://github.com/golang/go/commit/2b693b7c19e2f14c75b01b89bd2df31592e67d1e
  • Go言語の公式ドキュメント
  • ASN.1に関する一般的な情報源 (例: Wikipedia)
  • Go言語の reflect パッケージのドキュメント: https://pkg.go.dev/reflect
  • Go CL 22460043: https://golang.org/cl/22460043 (これはコミットメッセージに記載されているGoのコードレビューシステムへのリンクであり、詳細な議論が含まれている可能性がありますが、直接アクセスして内容を確認することはできませんでした。)
  • Go issue #6726: 一般的なWeb検索では詳細が見つかりませんでしたが、Goプロジェクトの内部イシュートラッカーに存在すると推測されます。