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

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

このコミットでは、doc/go1.tmplsrc/pkg/crypto/x509/x509.go の2つのファイルが変更されています。

  • doc/go1.tmpl: Go 1のリリースノートまたはドキュメントテンプレートの一部であり、crypto/x509 パッケージのAPI変更に関する記述が追加されています。
  • src/pkg/crypto/x509/x509.go: Go言語の標準ライブラリである crypto/x509 パッケージのソースコードです。X.509証明書およびCRL(証明書失効リスト)の生成に関する主要なロジックが含まれています。

コミット

commit a99e35b625cd5ec4b33c7c07377d6a65e142641d
Author: Adam Langley <agl@golang.org>
Date:   Thu Jan 19 08:49:52 2012 -0500

    crypto/x509: remove explicit uses of rsa.

    (Sending to r because of the API change.)

    Over time we might want to add support for other key types.

    While I was in the code, I also made the use of RawSubject the same
    between Subject and Issuer when creating certificates.

    R=r, rsc
    CC=golang-dev
    https://golang.org/cl/5554049

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

https://github.com/golang/go/commit/a99e35b625cd5ec4b33c7c07377d6a65e142641d

元コミット内容

このコミットの主な目的は、Go言語の crypto/x509 パッケージにおいて、RSA鍵型への明示的な依存を排除することです。具体的には、CreateCertificate および CreateCRL 関数が、これまで *rsa.PublicKey*rsa.PrivateKey といった具体的なRSA鍵型を引数として受け取っていたのを、より汎用的な interface{} 型に変更しています。これにより、将来的にはRSA以外の公開鍵アルゴリズム(例: ECDSA)のサポートを容易に拡張できるようになります。

また、コードの変更作業中に、証明書を作成する際に SubjectIssuer の両方で RawSubject の使用方法を統一し、コードの整合性と保守性を向上させています。

この変更はAPIに影響を与えるため、レビュー担当者 r に送付されたことがコミットメッセージに記されています。

変更の背景

このコミットが行われた2012年1月は、Go言語がバージョン1.0のリリースに向けて準備を進めていた時期にあたります。Go 1は、Go言語の安定したAPIと互換性を保証する最初のメジャーリリースとなるため、将来的な拡張性を見据えたAPI設計が非常に重要でした。

crypto/x509 パッケージは、TLS/SSL通信やコード署名など、現代のインターネットセキュリティにおいて不可欠なX.509証明書を扱うための基盤を提供します。当初の実装では、公開鍵暗号方式として広く普及しているRSAに特化していましたが、セキュリティ要件やパフォーマンスの観点から、楕円曲線暗号(ECC)などの他の鍵アルゴリズムのサポートが求められることは明らかでした。

このコミットは、将来的にRSA以外の鍵タイプ(特にECDSA)を crypto/x509 パッケージでサポートするための第一歩として、APIの柔軟性を高めることを目的としています。具体的な鍵タイプに依存しない interface{} を使用することで、新しい鍵タイプが追加された際に、既存の関数シグネチャを変更することなく対応できるようになります。

また、RawSubject の使用を統一する変更は、コードの重複を排除し、証明書のサブジェクト(主体者)とイシューアー(発行者)の処理ロジックを簡素化することで、コードの品質と保守性を向上させるための改善です。

前提知識の解説

X.509証明書

X.509は、公開鍵基盤(PKI)において公開鍵の所有者を識別するための標準フォーマットです。デジタル証明書として広く利用されており、ウェブサイトのTLS/SSL証明書、コード署名証明書、電子メールのS/MIME証明書などに使われています。X.509証明書には、公開鍵、所有者の識別情報(Subject)、発行者の識別情報(Issuer)、有効期間、署名などが含まれます。

RSA (Rivest–Shamir–Adleman)

RSAは、公開鍵暗号方式の一つで、デジタル署名と鍵交換に広く利用されています。大きな素数の積を基にした数学的な困難性(素因数分解問題)を利用しており、公開鍵と秘密鍵のペアで構成されます。公開鍵で暗号化されたデータは対応する秘密鍵でのみ復号でき、秘密鍵で署名されたデータは公開鍵で検証できます。

Go言語の interface{} と型アサーション

Go言語の interface{} は、任意の型の値を保持できる空のインターフェースです。Goのインターフェースは、メソッドのセットを定義する型であり、特定のメソッドセットを実装する任意の型がそのインターフェースを満たします。interface{} はメソッドを一つも持たないため、Goのすべての型が interface{} を満たします。

このコミットでは、interface{} を使用して、関数が特定の具体的な型(例: *rsa.PublicKey)ではなく、より抽象的な型を受け入れるようにしています。これにより、将来的に異なる鍵タイプ(例: *ecdsa.PublicKey)が追加された場合でも、関数シグネチャを変更することなく対応できるようになります。

型アサーションは、インターフェース型の変数が実際に特定の具象型であるかどうかをチェックし、その具象型の値を取り出すためのGoの機能です。構文は value, ok := interfaceValue.(ConcreteType) のようになります。ok はアサーションが成功したかどうかを示すブール値です。このコミットでは、interface{} で受け取った鍵が実際に *rsa.PublicKey または *rsa.PrivateKey であることを確認するために型アサーションが使用されています。

crypto/x509 パッケージ

Go言語の標準ライブラリの一部であり、X.509証明書とCRL(証明書失効リスト)の解析、検証、生成を行うための機能を提供します。PKIアプリケーションをGoで開発する際の基盤となります。

pkix パッケージ

crypto/x509 パッケージと密接に関連しており、X.509証明書やCRLの構造を定義する際に使用されるPKIX(Public Key Infrastructure X.509)関連のデータ構造(例: pkix.Name, pkix.AlgorithmIdentifier)を提供します。

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法です。X.509証明書やCRLは、ASN.1で定義された構造に従ってエンコードされます。Goの encoding/asn1 パッケージは、ASN.1データのマーシャリング(Goの構造体からASN.1バイト列への変換)とアンマーシャリング(ASN.1バイト列からGoの構造体への変換)をサポートします。

RDNSequence (Relative Distinguished Name Sequence)

X.509証明書の Subject(主体者)や Issuer(発行者)のフィールドは、RDNSequenceという構造で表現されます。これは、国(C)、組織(O)、共通名(CN)などの属性と値のペアのシーケンスです。

RawSubjectSubject

crypto/x509.Certificate 構造体には、RawSubjectSubject の2つのフィールドがあります。

  • RawSubject: 証明書のSubjectフィールドのDERエンコードされたバイト列を直接保持します。これは、証明書がパースされた際に元のバイト列を保持するために使用されます。
  • Subject: pkix.Name 型で、RawSubject をGoの構造体にパースしたものです。国、組織、共通名などの個々の属性にアクセスしやすくなります。

証明書を生成する際には、Subject 構造体から RDNSequence を構築し、それをASN.1エンコードして RawSubject 形式にするか、既存の RawSubject を直接使用するかの選択肢があります。このコミットでは、この処理を統一しています。

技術的詳細

interface{} の導入によるAPIの汎用化

このコミットの最も重要な変更は、CreateCertificate および CreateCRL 関数の鍵引数を *rsa.PublicKey / *rsa.PrivateKey から interface{} に変更した点です。

変更前:

func CreateCertificate(rand io.Reader, template, parent *Certificate, pub *rsa.PublicKey, priv *rsa.PrivateKey) (cert []byte, err error)
func (c *Certificate) CreateCRL(rand io.Reader, priv *rsa.PrivateKey, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error)

変更後:

func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interface{}, priv interface{}) (cert []byte, err error)
func (c *Certificate) CreateCRL(rand io.Reader, priv interface{}, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error)

この変更により、これらの関数は任意の型の公開鍵/秘密鍵を受け入れることができるようになります。しかし、コミットメッセージにもあるように、この時点ではRSA鍵のみがサポートされています。そのため、関数内部では受け取った interface{} 型の引数に対して型アサーションを行い、それが *rsa.PublicKey または *rsa.PrivateKey であることを確認しています。

rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
    return nil, errors.New("x509: non-RSA public keys not supported")
}

rsaPriv, ok := priv.(*rsa.PrivateKey)
if !ok {
    return nil, errors.New("x509: non-RSA private keys not supported")
}

このパターンは、Go言語で将来的な拡張性を考慮したAPIを設計する際の一般的な手法です。現時点では特定の型に限定しつつも、将来的に新しい型が追加された際に、既存のAPIシグネチャを変更することなく、内部の実装を拡張するだけで対応できるようになります。例えば、ECDSA鍵のサポートを追加する際には、この型アサーションのロジックに *ecdsa.PublicKey*ecdsa.PrivateKey のチェックを追加し、それぞれの鍵タイプに応じた処理を分岐させることで対応可能です。

subjectBytes ヘルパー関数の導入と RawSubject の統一

以前のコードでは、証明書の SubjectIssuer のフィールドをASN.1エンコードする際に、RawSubject が存在するかどうかで処理を分岐させていました。このコミットでは、このロジックを subjectBytes という新しいヘルパー関数に抽出し、コードの重複を排除しています。

変更前 (CreateCertificate 内のIssuer処理の例):

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

変更後 (subjectBytes 関数の導入):

func subjectBytes(cert *Certificate) ([]byte, error) {
    if len(cert.RawSubject) > 0 {
        return cert.RawSubject, nil
    }

    return asn1.Marshal(cert.Subject.ToRDNSequence())
}

そして、CreateCertificate 関数内でこの subjectBytes 関数を使用するように変更されています。

asn1Issuer, err := subjectBytes(parent)
if err != nil {
    return
}

asn1Subject, err := subjectBytes(template)
if err != nil {
    return
}

この変更により、SubjectIssuer の両方で RawSubject の有無に応じた処理が統一され、コードの可読性と保守性が向上しました。

doc/go1.tmpl の更新

APIの変更はGo 1の重要な変更点としてドキュメントに反映されています。doc/go1.tmpl には、crypto/x509 パッケージの CreateCertificate および CreateCRL 関数が interface{} を受け取るようになったことが明記されており、これにより将来的に他の公開鍵アルゴリズムが実装可能になることが説明されています。また、既存のユーザーは変更を必要としないことも強調されています。これは、Go 1の互換性保証の原則に則った記述です。

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

src/pkg/crypto/x509/x509.go

  1. subjectBytes 関数の追加:

    --- a/src/pkg/crypto/x509/x509.go
    +++ b/src/pkg/crypto/x509/x509.go
    @@ -899,6 +899,14 @@ var (
     	oidRSA         = []int{1, 2, 840, 113549, 1, 1, 1}
     )
    
    +func subjectBytes(cert *Certificate) ([]byte, error) {
    +\tif len(cert.RawSubject) > 0 {
    +\t\treturn cert.RawSubject, nil
    +\t}
    +\n+\treturn asn1.Marshal(cert.Subject.ToRDNSequence())
    +}
    +\n // CreateCertificate creates a new certificate based on a template. The
     // following members of template are used: SerialNumber, Subject, NotBefore,
     // NotAfter, KeyUsage, BasicConstraintsValid, IsCA, MaxPathLen, SubjectKeyId,
    
  2. CreateCertificate 関数のシグネチャ変更と型アサーションの追加:

    --- a/src/pkg/crypto/x509/x509.go
    +++ b/src/pkg/crypto/x509/x509.go
    @@ -909,10 +917,23 @@ var (
     // signee and priv is the private key of the signer.
     //
     // The returned slice is the certificate in DER encoding.
    -func CreateCertificate(rand io.Reader, template, parent *Certificate, pub *rsa.PublicKey, priv *rsa.PrivateKey) (cert []byte, err error) {
    +//
    +// The only supported key type is RSA (*rsa.PublicKey for pub, *rsa.PrivateKey
    +// for priv).
    +func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interface{}, priv interface{}) (cert []byte, err error) {
    +\trsaPub, ok := pub.(*rsa.PublicKey)
    +\tif !ok {
    +\t\treturn nil, errors.New("x509: non-RSA public keys not supported")
    +\t}\n+\n+\trsaPriv, ok := priv.(*rsa.PrivateKey)
    +\tif !ok {
    +\t\treturn nil, errors.New("x509: non-RSA private keys not supported")
    +\t}\n+\n     asn1PublicKey, err := asn1.Marshal(rsaPublicKey{
    -\t\tN: pub.N,\n-\t\tE: pub.E,\n+\t\tN: rsaPub.N,\n+\t\tE: rsaPub.E,\n     })
     	if err != nil {
     		return
     	}
    
  3. CreateCertificate 内での subjectBytes の利用と rsa.SignPKCS1v15 の引数変更:

    --- a/src/pkg/crypto/x509/x509.go
    +++ b/src/pkg/crypto/x509/x509.go
    @@ -927,16 +948,12 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub *rsa.P
     		return
     	}
    
    -	var asn1Issuer []byte
    -	if len(parent.RawSubject) > 0 {
    -		asn1Issuer = parent.RawSubject
    -	} else {
    -		if asn1Issuer, err = asn1.Marshal(parent.Subject.ToRDNSequence()); err != nil {
    -			return
    -		}
    -	}
    -
    -	asn1Subject, err := asn1.Marshal(template.Subject.ToRDNSequence())
    +	asn1Issuer, err := subjectBytes(parent)
    +	if err != nil {
    +		return
    +	}
    +
    +	asn1Subject, err := subjectBytes(template)
     	if err != nil {
     		return
     	}
    @@ -964,7 +981,7 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub *rsa.P
     	h.Write(tbsCertContents)
     	digest := h.Sum(nil)
    
    -\tsignature, err := rsa.SignPKCS1v15(rand, priv, crypto.SHA1, digest)\n+\tsignature, err := rsa.SignPKCS1v15(rand, rsaPriv, crypto.SHA1, digest)
     	if err != nil {
     		return
     	}
    
  4. CreateCRL 関数のシグネチャ変更と型アサーションの追加:

    --- a/src/pkg/crypto/x509/x509.go
    +++ b/src/pkg/crypto/x509/x509.go
    @@ -1011,7 +1028,13 @@ func ParseDERCRL(derBytes []byte) (certList *pkix.CertificateList, err error) {\n
     // CreateCRL returns a DER encoded CRL, signed by this Certificate, that
     // contains the given list of revoked certificates.
    -func (c *Certificate) CreateCRL(rand io.Reader, priv *rsa.PrivateKey, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) {
    +//
    +// The only supported key type is RSA (*rsa.PrivateKey for priv).
    +func (c *Certificate) CreateCRL(rand io.Reader, priv interface{}, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) {
    +\trsaPriv, ok := priv.(*rsa.PrivateKey)
    +\tif !ok {
    +\t\treturn nil, errors.New("x509: non-RSA private keys not supported")
    +\t}\n     	tbsCertList := pkix.TBSCertificateList{
     \t\tVersion: 2,\n     \t\tSignature: pkix.AlgorithmIdentifier{
    @@ -1032,7 +1055,7 @@ func (c *Certificate) CreateCRL(rand io.Reader, priv *rsa.PrivateKey, revokedCer
     	h.Write(tbsCertListContents)
     	digest := h.Sum(nil)
    
    -\tsignature, err := rsa.SignPKCS1v15(rand, priv, crypto.SHA1, digest)\n+\tsignature, err := rsa.SignPKCS1v15(rand, rsaPriv, crypto.SHA1, digest)
     	if err != nil {
     		return
     	}
    

doc/go1.tmpl

  1. crypto/x509 パッケージに関する新しいセクションの追加:

    --- a/doc/go1.tmpl
    +++ b/doc/go1.tmpl
    @@ -607,10 +607,28 @@ structure.
     Existing users of <code>*elliptic.Curve</code> will need to change to
     simply <code>elliptic.Curve</code>. Calls to <code>Marshal</code>,
     <code>Unmarshal</code> and <code>GenerateKey</code> are now functions
    -in <code>crypto.elliptic</code> that take an <code>elliptic.Curve</code>
    +in <code>crypto/elliptic</code> that take an <code>elliptic.Curve</code>
     as their first argument.
     </p>
    
    +<h3 id=\"crypto/x509\">The crypto/x509 package</h3>
    +
    +<p>
    +In Go 1, the
    +<a href=\"/pkg/crypto/x509/#CreateCertificate\"><code>CreateCertificate</code></a>
    +and
    +<a href=\"/pkg/crypto/x509/#CreateCRL\"><code>CreateCRL</code></a>
    +functions in <code>crypto/x509</code> have been altered to take an
    +<code>interface{}</code> where they previously took a <code>*rsa.PublicKey</code>
    +or <code>*rsa.PrivateKey</code>. This will allow other public key algorithms
    +to be implemented in the future.
    +</p>
    +
    +<p>
    +<em>Updating</em>:
    +No changes will be needed.
    +</p>
    +
     <h3 id=\"hash\">The hash package</h3>
    
     <p>
    
  2. crypto/elliptic パッケージのヘッダーIDの修正:

    --- a/doc/go1.tmpl
    +++ b/doc/go1.tmpl
    @@ -592,7 +592,7 @@ the correct function or method for the old functionality, but
     may have the wrong type or require further analysis.
     </p>
    
    -<h3 id=\"hash\">The crypto/elliptic package</h3>
    +<h3 id=\"crypto/elliptic\">The crypto/elliptic package</h3>
    
     <p>
     In Go 1, <a href=\"/pkg/crypto/elliptic/#Curve\"><code>elliptic.Curve</code></a>
    

コアとなるコードの解説

CreateCertificate および CreateCRL の鍵引数の変更

最も顕著な変更は、CreateCertificateCreateCRL 関数の pub および priv 引数の型が、具体的な *rsa.PublicKey*rsa.PrivateKey から interface{} に変更されたことです。

変更前: CreateCertificatepub *rsa.PublicKey, priv *rsa.PrivateKey を受け取っていました。 CreateCRLpriv *rsa.PrivateKey を受け取っていました。

変更後: 両関数とも pub interface{}, priv interface{} を受け取るようになりました。

この変更の意図は、将来的にRSA以外の鍵タイプ(例: ECDSA)をサポートするための拡張性を持たせることです。Goのインターフェースの特性を利用することで、関数シグネチャを変更することなく、内部ロジックで異なる鍵タイプを処理できるようになります。

関数内部では、受け取った interface{} 型の引数が実際に *rsa.PublicKey または *rsa.PrivateKey であるかを型アサーション value, ok := interfaceValue.(ConcreteType) を用いて確認しています。okfalse の場合(つまり、RSA鍵ではない場合)、"x509: non-RSA public keys not supported" のようなエラーを返します。これは、APIは汎用化されたものの、このコミット時点ではRSA鍵のみがサポートされていることを明確に示しています。

また、rsa.SignPKCS1v15 の呼び出しでは、型アサーションによって取得した rsaPriv 変数を使用するように変更されています。これにより、rsa パッケージの関数が期待する具体的な *rsa.PrivateKey 型の引数が渡されることが保証されます。

subjectBytes ヘルパー関数の導入

subjectBytes 関数は、*Certificate 型の引数を受け取り、その証明書のサブジェクト(またはイシューアー)のDERエンコードされたバイト列を返します。この関数は、cert.RawSubject が存在する場合はそれを直接返し、存在しない場合は cert.Subject.ToRDNSequence() をASN.1エンコードして返します。

このヘルパー関数が導入される前は、CreateCertificate 関数内で parent.RawSubjecttemplate.Subject の両方に対して、同様の if-else ロジックが重複して記述されていました。subjectBytes を導入することで、この重複が解消され、コードがよりDRY(Don't Repeat Yourself)になり、可読性と保守性が向上しました。

この変更は、APIの汎用化とは直接関係ありませんが、コードベースの品質向上に貢献しています。

ドキュメントの更新

doc/go1.tmpl の変更は、Go 1のリリースノートにこのAPI変更が記載されることを意味します。これにより、ユーザーは crypto/x509 パッケージの CreateCertificate および CreateCRL 関数が interface{} を受け取るようになったこと、そしてこれが将来の鍵タイプサポートのための変更であることを認識できます。また、「Updating: No changes will be needed.」という記述は、既存のRSA鍵を使用しているユーザーにとってはコードの変更が不要であることを示しており、Go 1の互換性保証の原則を反映しています。

関連リンク

参考にした情報源リンク