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

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

このコミットは、Go言語の標準ライブラリである encoding/asn1 パッケージにおける、ASN.1 の SET 型のアンマーシャリング(デコード)サポートの追加と、既存のコメントの改善を目的としています。具体的には、SET タグが指定された場合に、そのタグを正しく認識し、対応するGoの構造体にデコードできるようにする変更が含まれています。

変更されたファイルは以下の通りです。

  • src/pkg/encoding/asn1/asn1.go: ASN.1のパースロジックとタグ処理のコア部分。
  • src/pkg/encoding/asn1/asn1_test.go: encoding/asn1 パッケージのテストファイル。SET 型のアンマーシャリングに関する新しいテストケースが追加されています。
  • src/pkg/encoding/asn1/marshal.go: ASN.1のマーシャリング(エンコード)ロジック。コメントの追加が行われています。

コミット

commit 90c066da52f1f8f74e2f5cb2a9b65415140c5b57
Author: Adam Langley <agl@golang.org>
Date:   Tue Jan 28 14:12:25 2014 -0500

    encoding/asn1: support set tag when unmarshaling.
    
    This change also addresses some places where the comments were lacking.
    
    Fixes #7087.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/56700043

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

https://github.com/golang/go/commit/90c066da52f1f8f74e2f5cb2a9b65415140c5b57

元コミット内容

encoding/asn1: support set tag when unmarshaling.

This change also addresses some places where the comments were lacking.

Fixes #7087.

変更の背景

この変更の主な背景は、Goの encoding/asn1 パッケージがASN.1の SET 型を正しくアンマーシャリングできないという既存の課題(Issue #7087)を解決することにあります。

ASN.1 (Abstract Syntax Notation One) は、データ構造を記述するための標準的な記法であり、様々なプロトコル(例: X.509証明書、LDAP、SNMPなど)で広く利用されています。ASN.1には SEQUENCESET という2つの主要な複合型があります。

  • SEQUENCE: 要素の順序が重要であり、エンコードおよびデコード時にその順序が維持される必要があります。
  • SET: 要素の順序は重要ではなく、エンコードされた値では任意の順序で出現する可能性があります。

これまでの encoding/asn1 パッケージは SEQUENCE 型の処理には対応していましたが、SET 型のアンマーシャリングには明示的なサポートが不足していました。そのため、SET 型を含むASN.1データをGoの構造体にデコードしようとすると、正しく処理できない問題が発生していました。

このコミットは、SET 型のデータをGoの構造体にデコードする際に、asn1:"set" タグをGoの構造体フィールドに指定することで、encoding/asn1 パッケージが SET 型を正しく認識し、処理できるようにするためのものです。また、既存のコードのコメントが不足している箇所を補完し、可読性と理解度を向上させることも目的としています。

前提知識の解説

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造をプラットフォームやプログラミング言語に依存しない形で記述するための国際標準です。通信プロトコルやデータ交換フォーマットの定義に広く用いられます。ASN.1で定義されたデータ構造は、BER (Basic Encoding Rules) や DER (Distinguished Encoding Rules) などのエンコーディングルールに従ってバイト列に変換されます。

DER (Distinguished Encoding Rules)

DERはBERのサブセットであり、特定のASN.1値に対して常に一意のバイト列表現を生成することを保証するエンコーディングルールです。これにより、デジタル署名やハッシュ計算など、データの同一性が厳密に求められる場面で利用されます。DERでは、SET 型の要素も特定の順序(通常はタグと値の辞書順)でエンコードされることが義務付けられています。

encoding/asn1 パッケージ

Go言語の encoding/asn1 パッケージは、DERエンコードされたASN.1データをGoの構造体との間でマーシャリング(エンコード)およびアンマーシャリング(デコード)するための機能を提供します。Goの構造体フィールドにタグ(例: asn1:"optional,explicit,tag:x")を付与することで、ASN.1の型やエンコーディングの特性をGoの構造体にマッピングします。

ASN.1 SETSEQUENCE の違い

  • SEQUENCE: 要素の順序が定義されており、エンコードされたデータもその順序に従います。Goの構造体では、フィールドの定義順がそのままASN.1の SEQUENCE の要素順に対応します。
  • SET: 要素の順序は定義されていません。エンコードされたデータでは、要素は任意の順序で出現できます。しかし、DERエンコーディングでは一意性を保証するために、要素を特定の順序(通常はタグと値の辞書順)で並べ替えてエンコードします。Goの encoding/asn1 パッケージで SET を扱う場合、アンマーシャリング時に要素の順序が保証されないため、Goのslice型にデコードする際に注意が必要です。

Goの構造体タグ

Goでは、構造体フィールドにタグを付与することで、リフレクションを通じて追加のメタデータを提供できます。encoding/asn1 パッケージでは、asn1:"key:value" の形式でタグを使用し、ASN.1のエンコーディング/デコーディング動作を制御します。

  • optional: フィールドがASN.1の OPTIONAL であることを示します。
  • explicit: 明示的なタグ付け(EXPLICIT)を使用することを示します。
  • tag:x: ASN.1のタグ番号を指定します。
  • set: このコミットで追加されたタグで、フィールドがASN.1の SET 型であることを示します。

技術的詳細

このコミットの技術的な核心は、encoding/asn1 パッケージがASN.1の SET 型をアンマーシャリングする際の挙動を修正・拡張することにあります。

  1. parseField 関数の変更: src/pkg/encoding/asn1/asn1.go 内の parseField 関数は、Goの構造体フィールドのタグを解析し、対応するASN.1のタグとクラスを決定する役割を担っています。このコミットでは、params.set (構造体タグに set が含まれるかを示すフラグ) が true の場合、universalTagtagSet (ASN.1の SET 型のユニバーサルタグ) に設定するロジックが追加されました。これにより、アンマーシャリング時に SET 型のデータが正しく識別されるようになります。

    // src/pkg/encoding/asn1/asn1.go
    if params.set {
        universalTag = tagSet
    }
    
  2. Unmarshal 関数のコメント更新: src/pkg/encoding/asn1/asn1.go 内の Unmarshal 関数のドキュメンテーションコメントが更新され、新しい set タグの説明が追加されました。これにより、開発者が SET 型を扱う際にどのように構造体タグを使用すればよいかが明確になります。

    // src/pkg/encoding/asn1/asn1.go
    //	set		causes a SET, rather than a SEQUENCE type to be expected
    

    さらに、スライス要素の型名が "SET" で終わる場合に、set タグが設定されたかのように扱われるという新しい動作もコメントで説明されています。これは、ネストされたスライスなど、構造体タグを直接指定できない場合に特に有用です。

    // src/pkg/encoding/asn1/asn1.go
    // If the type name of a slice element ends with "SET" then it's treated as if
    // the "set" tag was set on it. This can be used with nested slices where a
    // struct tag cannot be given.
    
  3. Marshal 関数のコメント追加: src/pkg/encoding/asn1/marshal.go 内の Marshal 関数のドキュメンテーションコメントに、マーシャリング時に使用できる追加の構造体タグ(ia5, omitempty, printable, utf8)に関する説明が追加されました。これは直接 SET 型のアンマーシャリングとは関係ありませんが、コメントの不足を補うというコミットの目的の一部です。

  4. テストケースの追加: src/pkg/encoding/asn1/asn1_test.goTestSet という新しい構造体と、それに対応するアンマーシャリングのテストデータが追加されました。このテストケースは、asn1:"set" タグが付けられた []int スライスが正しくデコードされることを検証します。

    // src/pkg/encoding/asn1/asn1_test.go
    type TestSet struct {
        Ints []int `asn1:"set"`
    }
    // ...
    {[]byte{0x30, 0x0b, 0x31, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, &TestSet{Ints: []int{1, 2, 3}}},
    

    ここで、0x30SEQUENCE のタグ、0x0b は長さ、0x31SET のタグ、0x09 は長さ、そしてその後に INTEGER 型の 1, 2, 3 が続くASN.1のバイト列が定義されています。このテストは、SET 型のデータが正しくGoの []int にデコードされることを確認します。

これらの変更により、encoding/asn1 パッケージはASN.1の SET 型をより堅牢に処理できるようになり、関連するプロトコルを扱うGoアプリケーションの信頼性が向上します。

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

src/pkg/encoding/asn1/asn1.go

--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -634,6 +634,10 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
 		universalTag = tagGeneralizedTime
 	}
 
+	if params.set {
+		universalTag = tagSet
+	}
+
 	expectedClass := classUniversal
 	expectedTag := universalTag
 
@@ -854,13 +858,20 @@ func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) {
 //
 // The following tags on struct fields have special meaning to Unmarshal:
 //
-//	optional		marks the field as ASN.1 OPTIONAL
-//	[explicit] tag:x	specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC
-//	default:x		sets the default value for optional integer fields
+//	application	specifies that a APPLICATION tag is used
+//	default:x	sets the default value for optional integer fields
+//	explicit	specifies that an additional, explicit tag wraps the implicit one
+//	optional	marks the field as ASN.1 OPTIONAL
+//	set		causes a SET, rather than a SEQUENCE type to be expected
+//	tag:x		specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC
 //
 // If the type of the first field of a structure is RawContent then the raw
 // ASN1 contents of the struct will be stored in it.
 //
+// If the type name of a slice element ends with "SET" then it's treated as if
+// the "set" tag was set on it. This can be used with nested slices where a
+// struct tag cannot be given.
+//
 // Other ASN.1 types are not supported; if it encounters them,
 // Unmarshal returns a parse error.
 func Unmarshal(b []byte, val interface{}) (rest []byte, err error) {

src/pkg/encoding/asn1/asn1_test.go

--- a/src/pkg/encoding/asn1/asn1_test.go
+++ b/src/pkg/encoding/asn1/asn1_test.go
@@ -397,6 +397,10 @@ type TestBigInt struct {
 	X *big.Int
 }
 
+type TestSet struct {
+	Ints []int `asn1:"set"`
+}
+
 var unmarshalTestData = []struct {
 	in  []byte
 	out interface{}
@@ -416,6 +420,7 @@ var unmarshalTestData = []struct {
 	{[]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)}},
+	{[]byte{0x30, 0x0b, 0x31, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, &TestSet{Ints: []int{1, 2, 3}}},
 }
 
 func TestUnmarshal(t *testing.T) {

src/pkg/encoding/asn1/marshal.go

--- a/src/pkg/encoding/asn1/marshal.go
+++ b/src/pkg/encoding/asn1/marshal.go
@@ -568,6 +568,14 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters)
 }
 
 // Marshal returns the ASN.1 encoding of val.
+//
+// In addition to the struct tags recognised by Unmarshal, the following can be
+// used:
+//
+//	ia5:		causes strings to be marshaled as ASN.1, IA5 strings
+//	omitempty:	causes empty slices to be skipped
+//	printable:	causes strings to be marshaled as ASN.1, PrintableString strings.
+//	utf8:		causes strings to be marshaled as ASN.1, UTF8 strings
 func Marshal(val interface{}) ([]byte, error) {
 	var out bytes.Buffer
 	v := reflect.ValueOf(val)

コアとなるコードの解説

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

  1. parseField 関数内の params.set 処理の追加: parseField 関数は、Goの構造体フィールドのタグを解析し、対応するASN.1のタグとクラスを決定する重要な役割を担っています。 追加された以下のコードブロックは、fieldParameters 構造体に含まれる set フラグが true の場合に、universalTagtagSet (ASN.1の SET 型のユニバーサルタグ、値は 0x11 または17) に設定します。 これにより、asn1:"set" タグが指定されたGoの構造体フィールドに対して、アンマーシャリング時にASN.1の SET 型として正しく処理されるようになります。

    if params.set {
        universalTag = tagSet
    }
    
  2. Unmarshal 関数のコメント更新: Unmarshal 関数のドキュメンテーションコメントが大幅に更新され、Goの構造体フィールドに指定できる新しいタグとその意味が追加されました。 特に重要なのは、set タグの説明です。

    //	set		causes a SET, rather than a SEQUENCE type to be expected
    

    この説明により、開発者はGoの構造体フィールドに asn1:"set" を追加することで、ASN.1の SET 型のデータをデコードできることを理解できます。

    さらに、スライス要素の型名が "SET" で終わる場合に、明示的に set タグを指定しなくても set タグが設定されたかのように扱われるという、便利な機能が追加されたこともコメントで説明されています。これは、ネストされたスライスなど、構造体タグを直接指定できない場合に特に役立ちます。

    // If the type name of a slice element ends with "SET" then it's treated as if
    // the "set" tag was set on it. This can be used with nested slices where a
    // struct tag cannot be given.
    

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

  1. TestSet 構造体の追加: TestSet という新しいGoの構造体が定義されました。この構造体は、asn1:"set" タグが付けられた Ints という []int 型のフィールドを持っています。これは、SET OF INTEGER のようなASN.1構造をGoで表現する典型的な方法です。

    type TestSet struct {
        Ints []int `asn1:"set"`
    }
    
  2. unmarshalTestData へのテストケースの追加: unmarshalTestData スライスに、TestSet 構造体と対応するASN.1のバイト列を含む新しいテストケースが追加されました。 {[]byte{0x30, 0x0b, 0x31, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, &TestSet{Ints: []int{1, 2, 3}}}, このバイト列は、ASN.1の SEQUENCE (タグ 0x30) の中に SET (タグ 0x31) が含まれ、その SET の中に3つの INTEGER (値 1, 2, 3) が含まれる構造を表しています。このテストケースは、encoding/asn1 パッケージがこのバイト列を正しくパースし、TestSet 構造体の Ints フィールドに [1, 2, 3] をデコードできることを検証します。

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

  1. Marshal 関数のコメント追加: Marshal 関数のドキュメンテーションコメントに、マーシャリング時に使用できる追加の構造体タグ(ia5, omitempty, printable, utf8)に関する説明が追加されました。これは、Unmarshal 関数のコメント更新と同様に、ドキュメンテーションの改善を目的としています。

これらの変更により、encoding/asn1 パッケージはASN.1の SET 型のアンマーシャリングをサポートし、より多くのASN.1データ構造をGoで扱うことが可能になりました。

関連リンク

  • Go CL 56700043: https://golang.org/cl/56700043
  • Go Issue #7087: (Web検索では直接的なIssue #7087は見つかりませんでしたが、コミットメッセージに記載されているため、このコミットが解決した問題として参照されます。)

参考にした情報源リンク