[インデックス 18917] ファイルの概要
このコミットは、Go言語のencoding/asn1
パッケージにおける時間型(time.Time
)のエンコーディングに関する問題を修正するものです。具体的には、UTCTime
の表現範囲外の時刻を適切に処理するために、GeneralizedTime
の使用を導入しています。また、関連してcrypto/tls
パッケージ内の証明書生成ロジックも更新されています。
変更されたファイルは以下の通りです。
src/pkg/crypto/tls/generate_cert.go
: TLS証明書生成に関するコード。src/pkg/encoding/asn1/asn1.go
: ASN.1の基本的な型定義とパースロジック。src/pkg/encoding/asn1/marshal.go
: Goの型をASN.1形式にマーシャリング(エンコード)するロジック。src/pkg/encoding/asn1/marshal_test.go
:encoding/asn1
パッケージのマーシャリング機能のテストコード。
コミット
- コミットハッシュ:
050b60a36975a805921de71d6bfda310cd50e7ca
- 作者: Adam Langley agl@golang.org
- 日付: Fri Mar 21 11:14:38 2014 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/050b60a36975a805921de71d6bfda310cd50e7ca
元コミット内容
encoding/asn1: use GeneralizedTime for times outside the range of UTCTime.
Fixes issue #6976.
LGTM=r
R=golang-codereviews, r
CC=golang-codereviews
https://golang.org/cl/72080044
変更の背景
この変更は、Go言語のencoding/asn1
パッケージが、ASN.1のUTCTime
型で表現できる範囲(通常、1950年から2049年まで)を超える日付を適切にマーシャリングできないという問題(Issue #6976)を解決するために導入されました。
UTCTime
は、年を2桁で表現するため、2050年以降の日付を正確に表現することができません。これは、いわゆる「Y2K38問題」に似た「Y2K50問題」として知られる潜在的な問題を引き起こします。具体的には、2050年以降の日付をUTCTime
としてエンコードしようとすると、誤った年が表現されたり、エンコードに失敗したりする可能性がありました。
このコミット以前は、crypto/tls/generate_cert.go
のようなコードでは、証明書の有効期限が2049年12月31日を超える場合に、強制的にその日付に制限するような回避策が取られていました。しかし、これは将来の日付を扱うアプリケーションにとって不便であり、正しい解決策ではありませんでした。
この問題に対処するため、ASN.1標準で定義されているもう一つの時間型であるGeneralizedTime
を使用することが決定されました。GeneralizedTime
は年を4桁で表現するため、より広い範囲の日付を扱うことができます。このコミットの目的は、UTCTime
の範囲外の時刻に対して自動的にGeneralizedTime
を使用するようにencoding/asn1
パッケージを修正することです。
前提知識の解説
ASN.1 (Abstract Syntax Notation One)
ASN.1は、データ構造を記述するための標準的な記法であり、異なるシステム間でデータを交換する際に、そのデータの表現方法を明確に定義するために使用されます。通信プロトコル、特にX.509証明書、LDAP、SNMPなどで広く利用されています。ASN.1自体はデータの抽象的な構文を定義するものであり、具体的なバイト列へのエンコーディング方法はBER (Basic Encoding Rules) やDER (Distinguished Encoding Rules) などのエンコーディングルールによって定められます。
BER (Basic Encoding Rules) と DER (Distinguished Encoding Rules)
- BER (Basic Encoding Rules): ASN.1で定義されたデータ構造をバイト列にエンコードするための基本的なルールセットです。BERは柔軟性が高く、同じデータでも複数のエンコーディング方法が存在する場合があります。
- DER (Distinguished Encoding Rules): BERのサブセットであり、特定のデータ構造に対して常に一意のバイト列エンコーディングを保証するルールセットです。セキュリティ関連のアプリケーション(例: X.509証明書)では、データの改ざんを防ぎ、一貫性を保つためにDERが厳密に要求されることがよくあります。
ASN.1における時間型: UTCTime と GeneralizedTime
ASN.1には、日付と時刻を表現するためのいくつかの組み込み型があります。このコミットに関連するのはUTCTime
とGeneralizedTime
です。
-
UTCTime:
- 形式:
YYMMDDhhmmssZ
またはYYMMDDhhmmss+hhmm
/YYMMDDhhmmss-hhmm
- 年を2桁で表現します(例:
99
は1999年、00
は2000年)。 - この2桁の年の解釈には、通常、1950年から2049年までの範囲が使用されます。つまり、
50
から99
は1950年から1999年、00
から49
は2000年から2049年と解釈されます。 - このため、2049年12月31日23時59分59秒以降の時刻を正確に表現することができません。
- 末尾の
Z
はUTC(協定世界時)であることを示します。
- 形式:
-
GeneralizedTime:
- 形式:
YYYYMMDDhhmmssZ
またはYYYYMMDDhhmmss.fffZ
など、より柔軟な形式。 - 年を4桁で表現します(例:
1999
,2050
)。 - これにより、
UTCTime
の制限を超える広い範囲の日付を表現できます。 - 秒の小数部(ミリ秒、マイクロ秒など)も表現可能です。
- 末尾の
Z
はUTCであることを示します。
- 形式:
Go言語の time.Time
型
Go言語の標準ライブラリには、日付と時刻を扱うためのtime.Time
型が提供されています。この型は、ナノ秒の精度で時刻を表現でき、タイムゾーン情報も保持できます。encoding/asn1
パッケージは、このtime.Time
型とASN.1の時間型との間のマーシャリング(Goのtime.Time
からASN.1バイト列へ)およびアンマーシャリング(ASN.1バイト列からGoのtime.Time
へ)を処理します。
encoding/asn1
パッケージ
Go言語のencoding/asn1
パッケージは、ASN.1構造体のエンコードとデコードをサポートします。Goの構造体フィールドにタグを付けることで、ASN.1のタグ、クラス、型、オプションなどを指定できます。このパッケージは、X.509証明書やその他のASN.1ベースのプロトコルを扱う際に不可欠です。
技術的詳細
このコミットの主要な目的は、Goのtime.Time
オブジェクトをASN.1形式にマーシャリングする際に、その時刻がUTCTime
の表現範囲(1950年〜2049年)外である場合に、自動的にGeneralizedTime
を使用するようにすることです。
具体的な変更点は以下の通りです。
-
UTCTime
とGeneralizedTime
の切り替えロジックの導入:marshal.go
にoutsideUTCRange(t time.Time) bool
関数が追加されました。この関数は、与えられたtime.Time
オブジェクトの年が1950年未満または2050年以上である場合にtrue
を返します。marshalBody
関数内でtime.Time
型を処理する際に、このoutsideUTCRange
関数を呼び出し、結果に基づいてmarshalUTCTime
または新しく追加されたmarshalGeneralizedTime
を呼び出すように変更されました。- 同様に、
marshalField
関数でも、タグがtagUTCTime
である場合にoutsideUTCRange
をチェックし、必要に応じてタグをtagGeneralizedTime
に切り替えるロジックが追加されました。
-
GeneralizedTime
のマーシャリング実装:marshal.go
にmarshalGeneralizedTime(out *forkableWriter, t time.Time) (err error)
関数が追加されました。この関数は、time.Time
オブジェクトをYYYYMMDDhhmmssZ
形式のGeneralizedTime
としてエンコードします。- 年の部分を4桁で出力するために、
marshalFourDigits
ヘルパー関数が新しく導入されました。 marshalUTCTime
とmarshalGeneralizedTime
で共通する日付・時刻部分のマーシャリングロジックをmarshalTimeCommon
関数に抽出し、コードの重複を排除しました。
-
UTCTime
とGeneralizedTime
のパースロジックの統一:asn1.go
のparseSequenceOf
関数内で、tagGeneralizedTime
とtagUTCTime
の両方が同じように扱われるように変更されました。これにより、どちらの形式でエンコードされた時間も、Goのtime.Time
オブジェクトとして適切にデコードできるようになります。
-
crypto/tls
の証明書生成ロジックの簡素化:src/pkg/crypto/tls/generate_cert.go
から、証明書のnotAfter
(有効期限終了日時)が2049年12月31日を超える場合に、強制的にその日付に制限していたコードが削除されました。これは、encoding/asn1
パッケージがGeneralizedTime
を適切に扱えるようになったため、もはや不要になったためです。
-
テストケースの追加:
marshal_test.go
にfarFuture()
ヘルパー関数が追加され、2100年の日付を生成します。- この
farFuture()
を使用して、GeneralizedTime
として正しくマーシャリングされることを確認する新しいテストケースが追加されました。
これらの変更により、Goのencoding/asn1
パッケージは、UTCTime
の範囲外の時刻を透過的にGeneralizedTime
として扱い、より堅牢な日付処理を提供できるようになりました。
コアとなるコードの変更箇所
src/pkg/crypto/tls/generate_cert.go
--- a/src/pkg/crypto/tls/generate_cert.go
+++ b/src/pkg/crypto/tls/generate_cert.go
@@ -58,12 +58,6 @@ func main() {
notAfter := notBefore.Add(*validFor)
- // end of ASN.1 time
- endOfTime := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
- if notAfter.After(endOfTime) {
- notAfter = endOfTime
- }
-
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
src/pkg/encoding/asn1/asn1.go
--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -465,11 +465,15 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
if err != nil {
return
}
- // 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:
+ // We pretend that various other string types are
+ // PRINTABLE STRINGs so that a sequence of them can be
+ // parsed into a []string.
t.tag = tagPrintableString
+ case tagGeneralizedTime, tagUTCTime:
+ // Likewise, both time types are treated the same.
+ t.tag = tagUTCTime
}
if t.class != classUniversal || t.isCompound != compoundType || t.tag != expectedTag {
src/pkg/encoding/asn1/marshal.go
--- a/src/pkg/encoding/asn1/marshal.go
+++ b/src/pkg/encoding/asn1/marshal.go
@@ -295,8 +295,23 @@ func marshalTwoDigits(out *forkableWriter, v int) (err error) {
return out.WriteByte(byte('0' + v%10))
}
+func marshalFourDigits(out *forkableWriter, v int) (err error) {
+ var bytes [4]byte
+ for i := range bytes {
+ bytes[3-i] = '0' + byte(v%10)
+ v /= 10
+ }
+ _, err = out.Write(bytes[:])
+ return
+}
+
+func outsideUTCRange(t time.Time) bool {
+ year := t.Year()
+ return year < 1950 || year >= 2050
+}
+
func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {
- year, month, day := t.Date()
+ year := t.Year()
switch {
case 1950 <= year && year < 2000:
@@ -310,6 +325,24 @@ func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {
return
}
+ return marshalTimeCommon(out, t)
+}
+
+func marshalGeneralizedTime(out *forkableWriter, t time.Time) (err error) {
+ year := t.Year()
+ if year < 0 || year > 9999 {
+ return StructuralError{"cannot represent time as GeneralizedTime"}
+ }
+ if err = marshalFourDigits(out, year); err != nil {
+ return
+ }
+
+ return marshalTimeCommon(out, t)
+}
+
+func marshalTimeCommon(out *forkableWriter, t time.Time) (err error) {
+ _, month, day := t.Date()
+
err = marshalTwoDigits(out, int(month))
if err != nil {
return
@@ -378,7 +411,12 @@ func stripTagAndLength(in []byte) []byte {
func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameters) (err error) {
switch value.Type() {
case timeType:
- return marshalUTCTime(out, value.Interface().(time.Time))
+ t := value.Interface().(time.Time)
+ if outsideUTCRange(t) {
+ return marshalGeneralizedTime(t)
+ } else {
+ return marshalUTCTime(t)
+ }
case bitStringType:
return marshalBitString(out, value.Interface().(BitString))
case objectIdentifierType:
@@ -504,7 +542,8 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters)
return StructuralError{"explicit string type given to non-string member"}
}
- if tag == tagPrintableString {
+ switch tag {
+ case tagPrintableString:
if params.stringType == 0 {
// This is a string without an explicit string type. We'll use
// a PrintableString if the character set in the string is
@@ -521,6 +560,10 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters)
} else {
tag = params.stringType
}
+ case tagUTCTime:
+ if outsideUTCRange(v.Interface().(time.Time)) {
+ tag = tagGeneralizedTime
+ }
}
if params.set {
src/pkg/encoding/asn1/marshal_test.go
--- a/src/pkg/encoding/asn1/marshal_test.go
+++ b/src/pkg/encoding/asn1/marshal_test.go
@@ -67,6 +67,14 @@ type marshalTest struct {
out string // hex encoded
}
+func farFuture() time.Time {
+ t, err := time.Parse(time.RFC3339, "2100-04-05T12:01:01Z")
+ if err != nil {
+ panic(err)
+ }
+ return t
+}
+
var marshalTests = []marshalTest{
{10, "02010a"},
{127, "02017f"},
@@ -83,6 +91,7 @@ var marshalTests = []marshalTest{
{time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"},
{time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"},
{time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"},
+ {farFuture(), "180f32313030303430353132303130315a"},
{BitString{[]byte{0x80}, 1}, "03020780"},
{BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"},
{ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"},
コアとなるコードの解説
src/pkg/crypto/tls/generate_cert.go
このファイルでは、TLS証明書を生成する際のnotAfter
(有効期限終了日時)の制限が削除されました。以前は、UTCTime
の2049年までの制限に対応するため、notAfter
が2049年12月31日を超える場合に強制的にその日付に設定されていました。encoding/asn1
パッケージがGeneralizedTime
を適切に扱えるようになったため、この回避策は不要となり、コードが簡素化されました。これにより、GoのTLS証明書は2050年以降の有効期限を持つことができるようになります。
src/pkg/encoding/asn1/asn1.go
parseSequenceOf
関数内の変更は、ASN.1シーケンスをパースする際に、GeneralizedTime
とUTCTime
の両方を同じtagUTCTime
として扱うようにしています。これは、デコード時に両方の時間型を透過的に処理し、Goのtime.Time
型に変換できるようにするためです。これにより、エンコード時にどちらの時間型が使用されたかに関わらず、一貫したデコード動作が保証されます。
src/pkg/encoding/asn1/marshal.go
このファイルは、time.Time
オブジェクトをASN.1形式にマーシャリングするロジックの核心部分です。
-
marshalFourDigits(out *forkableWriter, v int) (err error)
:- この新しいヘルパー関数は、整数
v
を4桁のASCII数字としてout
ライターに書き込みます。これはGeneralizedTime
の年(YYYY)部分をエンコードするために使用されます。
- この新しいヘルパー関数は、整数
-
outsideUTCRange(t time.Time) bool
:- この新しい関数は、与えられた
time.Time
オブジェクトt
の年が1950年未満または2050年以上である場合にtrue
を返します。この関数が、UTCTime
の表現範囲外の時刻を検出するための主要なロジックを提供します。
- この新しい関数は、与えられた
-
marshalUTCTime(out *forkableWriter, t time.Time) (err error)
:- この関数は、
UTCTime
形式で時刻をマーシャリングします。変更点として、年の部分の処理が簡素化され、共通のマーシャリングロジックがmarshalTimeCommon
に抽出されました。
- この関数は、
-
marshalGeneralizedTime(out *forkableWriter, t time.Time) (err error)
:- この新しい関数は、
GeneralizedTime
形式で時刻をマーシャリングします。年の部分をmarshalFourDigits
を使って4桁で出力し、残りの日付・時刻部分はmarshalTimeCommon
を呼び出して処理します。これにより、2050年以降の時刻も正確にエンコードできるようになります。
- この新しい関数は、
-
marshalTimeCommon(out *forkableWriter, t time.Time) (err error)
:marshalUTCTime
とmarshalGeneralizedTime
の両方で共通して使用される、月、日、時、分、秒、タイムゾーン(UTCを示す'Z')のマーシャリングロジックがこの関数にまとめられました。これにより、コードの重複が解消され、保守性が向上しています。
-
marshalBody
関数内の変更:timeType
(time.Time
型)を処理するcase
ブロックが変更されました。outsideUTCRange(t)
をチェックし、結果に応じてmarshalGeneralizedTime
またはmarshalUTCTime
を呼び出すようになりました。これにより、適切な時間型が自動的に選択されます。
-
marshalField
関数内の変更:tagUTCTime
を処理するcase
ブロックが追加されました。ここでもoutsideUTCRange(v.Interface().(time.Time))
をチェックし、必要に応じてタグをtagGeneralizedTime
に切り替えることで、最終的なASN.1エンコーディングのタグが正しく設定されるようにします。
src/pkg/encoding/asn1/marshal_test.go
-
farFuture() time.Time
:- このヘルパー関数は、2100年4月5日12時01分01秒UTCという、
UTCTime
の範囲を大きく超える未来の時刻を生成します。
- このヘルパー関数は、2100年4月5日12時01分01秒UTCという、
-
marshalTests
への追加:{farFuture(), "180f32313030303430353132303130315a"}
という新しいテストケースが追加されました。これは、farFuture()
で生成された時刻が、GeneralizedTime
として正しくエンコードされること(タグ18
、長さ0f
、値21000405120101Z
)を検証します。このテストケースの追加により、GeneralizedTime
のマーシャリング機能が期待通りに動作することが保証されます。
関連リンク
- Go Issue #6976: https://github.com/golang/go/issues/6976
- Go CL 72080044: https://golang.org/cl/72080044
参考にした情報源リンク
- ASN.1 (Wikipedia): https://ja.wikipedia.org/wiki/ASN.1
- Basic Encoding Rules (BER) (Wikipedia): https://en.wikipedia.org/wiki/Basic_Encoding_Rules
- Distinguished Encoding Rules (DER) (Wikipedia): https://en.wikipedia.org/wiki/Distinguished_Encoding_Rules
- UTCTime (Wikipedia): https://en.wikipedia.org/wiki/X.690#UTCTime
- GeneralizedTime (Wikipedia): https://en.wikipedia.org/wiki/X.690#GeneralizedTime
- Go
time
package documentation: https://pkg.go.dev/time - Go
encoding/asn1
package documentation: https://pkg.go.dev/encoding/asn1 - Web search for "Go issue 6976 encoding/asn1 GeneralizedTime" (used for background information on the issue).