[インデックス 18066] ファイルの概要
このコミットは、Go言語の encoding/asn1
パッケージにおける、ASN.1シーケンス内の非表示文字列(non-printable strings)のパースに関するバグ修正です。具体的には、シーケンス内で文字列をパースする際に、裸の文字列フィールドをパースする場合と同じ基準でタグタイプを変更するように修正されています。これにより、IA5String
、GeneralString
、T61String
、UTF8String
などの様々な文字列タイプが 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
以外にも IA5String
、T61String
、UTF8String
など、Goの string
型にマッピング可能な様々な文字列型が存在します。これらの文字列型がシーケンス内に現れた場合、以前の実装では tagPrintableString
への変換が行われず、結果としてパースエラーや不正なデコードが発生する可能性がありました。
今回の修正では、この tagGeneralString
のみを特別扱いするロジックを拡張し、IA5String
、GeneralString
、T61String
、UTF8String
のいずれかのタグを持つ文字列がシーケンス内で検出された場合、それらを一律に tagPrintableString
として扱うように変更されました。これにより、これらの非表示文字列も []string
型に正しくデコードできるようになります。
この変更は、ASN.1の柔軟な文字列エンコーディングとGoの厳密な型システムとの間のギャップを埋めるための重要な修正であり、より堅牢なASN.1パース処理を実現します。
コアとなるコードの変更箇所
変更は src/pkg/encoding/asn1/asn1.go
と src/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.tag
が tagGeneralString
であった場合にのみ、そのタグを 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
だけでなく、tagIA5String
、tagT61String
、tagUTF8String
も同様に 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
関数は、以下の手順でテストを行います。
stringSliceTestData
の各要素([]string
)をasn1.Marshal
を使ってASN.1バイト列にエンコードします。- エンコードされたバイト列を
asn1.Unmarshal
を使って[]string
型の変数res
にデコードします。 - 元のデータ
test
とデコードされたデータres
が一致するかどうかをfmt.Sprintf("%v", ...)
を使って比較します。
このテストの追加により、parseSequenceOf
関数の修正が、様々な種類の文字列を含む []string
のエンコードとデコードにおいて正しく機能することを確認できます。特に、非ASCII文字を含む文字列が正しく処理されるかどうかの検証は重要です。
関連リンク
- Go言語の
encoding/asn1
パッケージのドキュメント: https://pkg.go.dev/encoding/asn1 - ASN.1の概要 (Wikipedia): https://ja.wikipedia.org/wiki/ASN.1
参考にした情報源リンク
- コミットメッセージと差分情報: 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プロジェクトの内部イシュートラッカーに存在すると推測されます。