[インデックス 11262] ファイルの概要
このコミットでは、doc/go1.tmpl
と src/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)のサポートを容易に拡張できるようになります。
また、コードの変更作業中に、証明書を作成する際に Subject
と Issuer
の両方で 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)などの属性と値のペアのシーケンスです。
RawSubject
と Subject
crypto/x509.Certificate
構造体には、RawSubject
と Subject
の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
の統一
以前のコードでは、証明書の Subject
と Issuer
のフィールドを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
}
この変更により、Subject
と Issuer
の両方で RawSubject
の有無に応じた処理が統一され、コードの可読性と保守性が向上しました。
doc/go1.tmpl
の更新
APIの変更はGo 1の重要な変更点としてドキュメントに反映されています。doc/go1.tmpl
には、crypto/x509
パッケージの CreateCertificate
および CreateCRL
関数が interface{}
を受け取るようになったことが明記されており、これにより将来的に他の公開鍵アルゴリズムが実装可能になることが説明されています。また、既存のユーザーは変更を必要としないことも強調されています。これは、Go 1の互換性保証の原則に則った記述です。
コアとなるコードの変更箇所
src/pkg/crypto/x509/x509.go
-
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,
-
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 }
-
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 }
-
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
-
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>
-
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
の鍵引数の変更
最も顕著な変更は、CreateCertificate
と CreateCRL
関数の pub
および priv
引数の型が、具体的な *rsa.PublicKey
や *rsa.PrivateKey
から interface{}
に変更されたことです。
変更前:
CreateCertificate
は pub *rsa.PublicKey, priv *rsa.PrivateKey
を受け取っていました。
CreateCRL
は priv *rsa.PrivateKey
を受け取っていました。
変更後:
両関数とも pub interface{}, priv interface{}
を受け取るようになりました。
この変更の意図は、将来的にRSA以外の鍵タイプ(例: ECDSA)をサポートするための拡張性を持たせることです。Goのインターフェースの特性を利用することで、関数シグネチャを変更することなく、内部ロジックで異なる鍵タイプを処理できるようになります。
関数内部では、受け取った interface{}
型の引数が実際に *rsa.PublicKey
または *rsa.PrivateKey
であるかを型アサーション value, ok := interfaceValue.(ConcreteType)
を用いて確認しています。ok
が false
の場合(つまり、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.RawSubject
と template.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の互換性保証の原則を反映しています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
crypto/x509
パッケージドキュメント: https://pkg.go.dev/crypto/x509crypto/rsa
パッケージドキュメント: https://pkg.go.dev/crypto/rsaencoding/asn1
パッケージドキュメント: https://pkg.go.dev/encoding/asn1- Go言語のインターフェースに関する公式ブログ記事 (英語): https://go.dev/blog/interfaces
参考にした情報源リンク
- X.509 - Wikipedia: https://ja.wikipedia.org/wiki/X.509
- RSA暗号 - Wikipedia: https://ja.wikipedia.org/wiki/RSA%E6%9A%97%E5%8F%B7
- Go言語のインターフェースと型アサーションに関する一般的な情報源 (例: Go by Example, A Tour of Goなど)
- Go 1 Release Notes (当時の情報): https://go.dev/doc/go1 (このコミットが反映されたGo 1のドキュメント)
- Go CL 5554049: https://golang.org/cl/5554049 (コミットメッセージに記載されているGo Code Reviewのリンク)