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

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

このコミットは、Go言語の crypto/x509 パッケージにおいて、親証明書(CA証明書など)の RawSubject フィールドが存在する場合に、その生のSubject情報を利用するように変更を加えるものです。これにより、Go以外のツールで生成された証明書を親として使用する際に発生する、ASN.1エンコーディングの詳細(特に文字列型)が失われる問題を回避し、証明書の互換性と正確性を向上させます。

コミット

commit 02d1dae1069f881ea6b53ecc3cbf3bbe3ac40a72
Author: Adam Langley <agl@golang.org>
Date:   Tue Dec 6 16:42:48 2011 -0500

    crypto/x509: if a parent cert has a raw subject, use it.
    
    This avoids a problem when creating certificates with parents that
    were produce by other code: the Go structures don't contain all the
    information about the various ASN.1 string types etc and so that
    information would otherwise be lost.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5453067

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

https://github.com/golang/go/commit/02d1dae1069f881ea6b53ecc3cbf3bbe3ac40a72

元コミット内容

crypto/x509: if a parent cert has a raw subject, use it.

This avoids a problem when creating certificates with parents that
were produce by other code: the Go structures don't contain all the
information about the various ASN.1 string types etc and so that
information would otherwise be lost.

変更の背景

X.509証明書は、公開鍵基盤(PKI)においてエンティティの身元を検証するために広く使用されています。これらの証明書は、ASN.1(Abstract Syntax Notation One)という標準化されたデータ記述言語と、DER(Distinguished Encoding Rules)というエンコーディングルールに基づいて構造化されています。証明書には、発行者(Issuer)と主体者(Subject)の識別名(Distinguished Name, DN)が含まれており、これらは通常、複数の属性(例: Common Name, Organization, Countryなど)から構成されます。

Go言語の crypto/x509 パッケージは、X.509証明書の生成、解析、検証を扱うための標準ライブラリです。しかし、Goの内部構造(pkix.Nameなど)は、ASN.1のすべての詳細、特にSubjectやIssuerのDNを構成する各属性の「文字列型」(例: PrintableString, UTF8String, BMPStringなど)を完全に保持するわけではありませんでした。

このコミット以前は、CreateCertificate 関数が親証明書の発行者情報を取得する際、常に親証明書の Subject フィールドから ToRDNSequence() メソッドを呼び出し、その結果をASN.1で再エンコードしていました。このプロセスは、Goの内部構造が元のASN.1文字列型の情報を保持していないため、Go以外のツールで生成された親証明書を使用した場合に問題を引き起こしました。具体的には、元の証明書が特定のASN.1文字列型(例えば PrintableString)を使用していたとしても、Goがそれを UTF8String として再エンコードしてしまうなど、元のエンコーディング情報が失われる可能性がありました。

このようなエンコーディングの不一致は、特に厳格なX.509パーサーや、特定の文字列型に依存するシステムとの相互運用性において問題となることがありました。このコミットは、この「情報損失」の問題を解決し、Goが生成する証明書が、元の親証明書のSubject情報をより忠実に再現できるようにすることを目的としています。

前提知識の解説

X.509 証明書

X.509は、公開鍵証明書のフォーマットを定義するITU-Tの標準です。PKIにおいて、公開鍵とそれに対応するエンティティ(人、組織、デバイスなど)の身元を結びつけるために使用されます。証明書には、公開鍵、エンティティの識別名(Subject DN)、証明書の発行者(Issuer DN)、有効期間、署名アルゴリズム、発行者のデジタル署名などが含まれます。

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための国際標準です。異なるシステム間でデータを交換する際に、データの表現方法を抽象的に定義するために使用されます。X.509証明書は、ASN.1で定義された構造を持ち、DER(Distinguished Encoding Rules)という特定のエンコーディングルールに従ってバイト列に変換されます。

DER (Distinguished Encoding Rules)

DERは、ASN.1で定義されたデータ構造を一意のバイト列にエンコードするためのルールセットです。これにより、同じASN.1データ構造が常に同じバイト列にエンコードされることが保証され、データの整合性と互換性が維持されます。

Subject と Issuer の識別名 (Distinguished Name, DN)

X.509証明書において、Subjectは証明書が発行されるエンティティの識別名であり、Issuerは証明書を発行した認証局(CA)の識別名です。これらのDNは、複数の属性(例: CN=Common Name, O=Organization, C=Countryなど)から構成され、これらはASN.1の「相対識別名シーケンス(Relative Distinguished Name Sequence, RDNSequence)」としてエンコードされます。

ASN.1 文字列型

ASN.1には、様々な文字列型が定義されています。これらは、文字列がどのような文字セットやエンコーディングで表現されるかを示します。例えば:

  • PrintableString: ASCII文字、数字、一部の記号(スペース、アポストロフィ、括弧など)に限定されます。
  • UTF8String: UTF-8エンコーディングされた任意のUnicode文字を表現できます。
  • BMPString: UCS-2(Basic Multilingual Plane)エンコーディングされたUnicode文字を表現できます。
  • TeletexString, IA5String など、他にも多くの型があります。

これらの文字列型は、ASN.1エンコーディングにおいて、タグと長さの後に実際の文字列データが続く形で表現されます。Goの pkix.Name 構造体は、これらの文字列型の情報を直接保持せず、内部的にはGoの文字列として扱います。そのため、元のASN.1エンコーディングが持っていた特定の文字列型の情報が失われる可能性がありました。

crypto/x509 パッケージの Certificate 構造体と RawSubject フィールド

Goの crypto/x509 パッケージの Certificate 構造体は、X.509証明書の情報をGoのデータ構造として表現します。この構造体には、Subject フィールド(pkix.Name 型)と RawSubject フィールド([]byte 型)があります。

  • Subject: 証明書のSubject DNをGoの pkix.Name 構造体として解析したものです。これは、人間が読みやすい形式でDNの各属性にアクセスできるようにします。
  • RawSubject: 証明書から直接読み取られた、Subject DNの生のASN.1 DERエンコードされたバイト列です。このフィールドは、元の証明書が持っていたASN.1エンコーディングの詳細(例えば、各属性の文字列型)を、Goの内部構造がそれらを完全に表現できない場合でも、そのまま保持します。

技術的詳細

このコミットの核心は、crypto/x509 パッケージの CreateCertificate 関数における、親証明書の発行者(Issuer)情報の取り扱いを変更することです。

以前の実装では、CreateCertificate 関数は常に親証明書の parent.Subject フィールドから ToRDNSequence() メソッドを呼び出し、その結果を asn1.Marshal を使ってASN.1 DER形式にエンコードしていました。ToRDNSequence() メソッドは、pkix.Name 構造体の内容をASN.1のRDNSequenceとして表現しますが、この変換の過程で、元のASN.1エンコーディングが持っていた各属性の具体的な文字列型(例: PrintableString vs UTF8String)の情報は失われ、Goのデフォルトのエンコーディングルール(通常は UTF8String)が適用されていました。

この問題は、特にGo以外のツール(例: OpenSSL)で生成された証明書を親証明書として使用する場合に顕著でした。これらのツールは、特定のASN.1文字列型を使用してSubject DNをエンコードすることがあり、Goがそれを再エンコードする際に異なる文字列型を使用してしまうと、結果として生成される証明書のIssuer DNが元の親証明書のSubject DNとバイト列レベルで一致しなくなる可能性がありました。これは、証明書のパス検証や、厳格な証明書比較を行うシステムにおいて互換性の問題を引き起こす可能性があります。

このコミットでは、この問題を解決するために、親証明書の RawSubject フィールドの利用を優先するように変更しました。RawSubject フィールドは、証明書がパースされた際に、Subject DNの生のASN.1 DERエンコードされたバイト列をそのまま保持します。これにより、元の証明書が使用していたASN.1文字列型やその他のエンコーディングの詳細が完全に保持されます。

変更後のロジックは以下のようになります:

  1. CreateCertificate 関数内で、親証明書の発行者情報をエンコードする前に、まず parent.RawSubject の長さが0より大きいかどうかを確認します。
  2. もし parent.RawSubject が存在する場合(つまり、元の証明書から生のSubject情報が利用可能である場合)、その RawSubject のバイト列を直接 asn1Issuer として使用します。これにより、元のASN.1エンコーディングが完全に保持されます。
  3. parent.RawSubject が存在しない場合(例えば、Go自身が生成した証明書で RawSubject が設定されていない場合や、何らかの理由でパース時に RawSubject が取得できなかった場合)、以前と同様に parent.Subject.ToRDNSequence() を呼び出し、その結果を asn1.Marshal でエンコードして asn1Issuer とします。これはフォールバックメカニズムとして機能します。

この変更により、Goは、Go以外のツールで生成された証明書を親として使用する際に、そのIssuer DNをより忠実に再現できるようになり、相互運用性と証明書の正確性が向上しました。

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

変更は src/pkg/crypto/x509/x509.go ファイルの CreateCertificate 関数内で行われました。

--- a/src/pkg/crypto/x509/x509.go
+++ b/src/pkg/crypto/x509/x509.go
@@ -927,10 +927,15 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub *rsa.P
  		return
  	}\n
-\tasn1Issuer, err := asn1.Marshal(parent.Subject.ToRDNSequence())\n-\tif err != nil {\n-\t\treturn\n+\tvar asn1Issuer []byte\n+\tif len(parent.RawSubject) > 0 {\n+\t\tasn1Issuer = parent.RawSubject
+\t} else {\n+\t\tif asn1Issuer, err = asn1.Marshal(parent.Subject.ToRDNSequence()); err != nil {\n+\t\t\treturn
+\t\t}\n \t}\n+\n  \tasn1Subject, err := asn1.Marshal(template.Subject.ToRDNSequence())\n  \tif err != nil {\n  \t\treturn

具体的には、以下の部分が変更されました。

変更前:

	asn1Issuer, err := asn1.Marshal(parent.Subject.ToRDNSequence())
	if err != nil {
		return
	}

変更後:

	var asn1Issuer []byte
	if len(parent.RawSubject) > 0 {
		asn1Issuer = parent.RawSubject
	} else {
		if asn1Issuer, err = asn1.Marshal(parent.Subject.ToRDNSequence()); err != nil {
			return
		}
	}

コアとなるコードの解説

変更されたコードブロックは、新しく生成される証明書の Issuer フィールド(発行者情報)をASN.1形式で準備する部分です。

  1. var asn1Issuer []byte: まず、asn1Issuer という名前のバイトスライスを宣言します。これは、最終的に証明書のIssuerフィールドに格納されるASN.1エンコードされたバイト列を保持します。

  2. if len(parent.RawSubject) > 0 { ... }: ここがこのコミットの主要な変更点です。

    • parent.RawSubject は、親証明書がパースされた際に、そのSubject DNの生のASN.1 DERエンコードされたバイト列を保持するフィールドです。
    • この条件は、RawSubject フィールドにデータが格納されているかどうか(つまり、元のASN.1エンコーディング情報が利用可能かどうか)を確認します。
    • もし RawSubject が存在し、かつ空でなければ、その生のバイト列を直接 asn1Issuer に代入します (asn1Issuer = parent.RawSubject)。これにより、元の証明書が使用していたASN.1文字列型やその他のエンコーディングの詳細が完全に保持され、Goが再エンコードする際に情報が失われることを防ぎます。
  3. else { ... }: parent.RawSubject が空であるか、存在しない場合のフォールバックロジックです。

    • if asn1Issuer, err = asn1.Marshal(parent.Subject.ToRDNSequence()); err != nil { ... }: このブロックは、変更前のコードと同じ動作をします。
      • parent.Subject.ToRDNSequence() は、親証明書の Subject フィールド(pkix.Name 型)から、ASN.1のRDNSequence表現を生成します。
      • asn1.Marshal() は、このRDNSequenceをASN.1 DER形式のバイト列にエンコードします。
      • この結果が asn1Issuer に代入されます。この場合、Goの内部的な文字列型変換が適用されるため、元のASN.1文字列型の情報は失われる可能性があります。
      • エンコード中にエラーが発生した場合は、関数から早期にリターンします。

このロジックにより、Goは、親証明書が持つ生のSubject情報(RawSubject)を優先的に利用することで、Go以外のツールで生成された証明書との互換性を高め、より正確な証明書チェーンの構築を可能にしています。

関連リンク

参考にした情報源リンク