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

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

このコミットは、Go言語の標準ライブラリ crypto/x509 パッケージにおける、ECDSA公開鍵のシリアライズ(マーシャリング)機能の改善に関するものです。具体的には、ECDSA公開鍵をPKIX(Public Key Infrastructure X.509)形式でDERエンコードしてシリアライズする機能が追加され、既存の CreateCertificate 関数から公開鍵のシリアライズロジックが MarshalPKIXPublicKey 関数にファクタリング(分離・再利用可能化)されました。

コミット

commit 4874bc9b76da2362cdec0a8c6b56cd740d45c5ad
Author: Nicholas Sullivan <nicholas.sullivan@gmail.com>
Date:   Thu Sep 12 12:23:34 2013 -0400

    crypto/x509: allow ECDSA public keys to be marshaled.
    
    The public key serialization from CreateCertificate is factored out to be
    used in MarshalPKIXPublicKey.
    Testcode with one P224 ECDSA keypair has been added.
    
    R=golang-dev, agl
    CC=agl, golang-dev
    https://golang.org/cl/13427044

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

https://github.com/golang/go/commit/4874bc9b76da2362cdec0a8c6b56cd740d45c5ad

元コミット内容

crypto/x509: allow ECDSA public keys to be marshaled.

The public key serialization from CreateCertificate is factored out to be
used in MarshalPKIXPublicKey.
Testcode with one P224 ECDSA keypair has been added.

R=golang-dev, agl
CC=agl, golang-dev
https://golang.org/cl/13427044

変更の背景

Go言語の crypto/x509 パッケージは、X.509証明書とPKI(公開鍵基盤)の操作を扱うための重要なライブラリです。このパッケージには、証明書の作成 (CreateCertificate) や解析 (ParseCertificate) といった機能が含まれています。

このコミット以前は、CreateCertificate 関数内でRSA公開鍵とECDSA公開鍵のシリアライズロジックが直接実装されていました。しかし、公開鍵をPKIX形式でシリアライズする機能は、証明書作成だけでなく、他の様々な用途(例えば、公開鍵を単独でファイルに保存したり、他のプロトコルで交換したりする場合)でも必要とされます。

このコミットの背景には、以下の課題と目的があったと考えられます。

  1. ECDSA公開鍵の直接マーシャリングの欠如: CreateCertificate 関数はECDSA公開鍵を内部的に処理できましたが、ECDSA公開鍵オブジェクトを直接PKIX形式のバイト列に変換する汎用的な公開API (MarshalPKIXPublicKey) が存在しませんでした。これにより、開発者はECDSA公開鍵を外部形式で扱う際に不便を強いられる可能性がありました。
  2. コードの重複と保守性の向上: CreateCertificate 内に公開鍵シリアライズロジックが埋め込まれていると、同じロジックが他の場所で必要になった場合にコードの重複が発生し、保守が困難になります。共通のシリアライズロジックを独立した関数として切り出すことで、コードの再利用性を高め、保守性を向上させることが目的でした。
  3. テストカバレッジの向上: 新しい機能が追加される際には、その機能が正しく動作することを保証するためのテストコードが不可欠です。特に暗号関連の機能では、正確性が極めて重要であるため、P224 ECDSA鍵ペアを用いたテストケースの追加は、変更の信頼性を高める上で重要な要素でした。

これらの背景から、ECDSA公開鍵のマーシャリングを可能にし、コードの構造を改善することがこのコミットの主な動機となりました。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念について基本的な知識が必要です。

  1. 公開鍵暗号 (Public-Key Cryptography):

    • 公開鍵と秘密鍵のペアを使用する暗号方式。公開鍵で暗号化されたデータは対応する秘密鍵でのみ復号でき、秘密鍵で署名されたデータは公開鍵で検証できます。
    • RSA (Rivest–Shamir–Adleman): 最も広く使われている公開鍵暗号アルゴリズムの一つ。大きな素数の積を基盤としています。
    • ECDSA (Elliptic Curve Digital Signature Algorithm): 楕円曲線暗号に基づくデジタル署名アルゴリズム。RSAよりも短い鍵長で同等のセキュリティ強度を提供できるため、モバイルデバイスやリソースが限られた環境で特に有用です。
  2. X.509 証明書と PKIX (Public Key Infrastructure X.509):

    • X.509: 公開鍵証明書の標準フォーマットを定義するITU-Tの標準規格。WebサイトのSSL/TLS通信などで広く利用されています。
    • PKIX: X.509証明書を基盤とした公開鍵基盤(PKI)のフレームワークを定義するIETFの標準規格群。証明書の構造、発行、失効、検証などのプロセスを規定します。
    • X.509証明書には、公開鍵、所有者情報、発行者情報、有効期間、署名などが含まれます。公開鍵は、証明書の重要な構成要素の一つです。
  3. ASN.1 (Abstract Syntax Notation One):

    • データ構造を記述するための標準的な記法。通信プロトコルやデータストレージにおいて、異なるシステム間でデータを交換する際に、データの形式を厳密に定義するために使用されます。
    • DER (Distinguished Encoding Rules): ASN.1で定義されたデータ構造をバイト列にエンコードするための規則の一つ。DERは、特定のASN.1値に対して一意のバイト列表現を保証するため、暗号化やデジタル署名などのセキュリティ関連の用途で広く用いられます。X.509証明書や公開鍵のシリアライズにはDERがよく使われます。
    • pkix.AlgorithmIdentifier: ASN.1で定義される構造体で、暗号アルゴリズムとそのパラメータを識別するために使用されます。公開鍵のアルゴリズム(例: RSA, ECDSA)と、そのアルゴリズムに固有のパラメータ(例: 楕円曲線のOID)を含みます。
    • asn1.BitString: ASN.1で定義されるビット列を表す型。公開鍵のビット列を格納するために使用されます。
  4. Go言語の crypto/x509 パッケージ:

    • Go言語の標準ライブラリの一部で、X.509証明書の解析、生成、検証、および関連する公開鍵操作を提供します。
    • rsa.PublicKey: RSA公開鍵を表すGoの構造体。
    • ecdsa.PublicKey: ECDSA公開鍵を表すGoの構造体。
    • elliptic.Curve: 楕円曲線のパラメータを定義するインターフェース。ECDSA鍵は特定の楕円曲線に関連付けられます。
    • elliptic.Marshal: 楕円曲線上の点をバイト列にマーシャリングする関数。

技術的詳細

このコミットの主要な技術的変更点は、以下の通りです。

  1. marshalPublicKey 関数の導入:

    • このコミットの核となる変更は、marshalPublicKey という新しい内部ヘルパー関数の導入です。
    • この関数は、interface{} 型の公開鍵(*rsa.PublicKey または *ecdsa.PublicKey)を受け取り、その公開鍵のDERエンコードされたバイト列 (publicKeyBytes)、対応するPKIXアルゴリズム識別子 (publicKeyAlgorithm)、およびエラーを返します。
    • これにより、RSAとECDSAの両方の公開鍵のシリアライズロジックがこの関数に集約され、再利用性が向上しました。
  2. ECDSA公開鍵のマーシャリングロジックの実装:

    • marshalPublicKey 関数内で、*ecdsa.PublicKey 型の公開鍵が渡された場合の処理が追加されました。
    • ECDSA公開鍵のバイト列は、elliptic.Marshal(pub.Curve, pub.X, pub.Y) を使用して生成されます。これは、楕円曲線の種類 (pub.Curve) と、公開鍵のX座標 (pub.X) およびY座標 (pub.Y) を基に、非圧縮形式の楕円曲線点をバイト列に変換するものです。
    • ECDSA公開鍵のアルゴリズム識別子 (pkix.AlgorithmIdentifier) は、oidPublicKeyECDSA (ECDSAのオブジェクト識別子) と、使用されている楕円曲線のOID(オブジェクト識別子)をパラメータとして設定することで構築されます。oidFromNamedCurve 関数は、Goの elliptic.Curve オブジェクトから対応するOIDを取得するために使用されます。
  3. MarshalPKIXPublicKey 関数の改善:

    • 既存の MarshalPKIXPublicKey 関数は、CreateCertificate 関数から切り出された marshalPublicKey ヘルパー関数を利用するように変更されました。
    • これにより、MarshalPKIXPublicKey は、RSA公開鍵だけでなく、ECDSA公開鍵もPKIX形式でDERエンコードしてシリアライズできるようになりました。
    • PKIX形式の公開鍵構造体 (pkixPublicKey) は、アルゴリズム識別子 (Algo) と公開鍵のビット列 (BitString) を含みます。marshalPublicKey から返された publicKeyAlgorithmpublicKeyBytes がそれぞれこれらのフィールドに設定されます。
  4. CreateCertificate 関数からのロジックのファクタリング:

    • CreateCertificate 関数内の公開鍵シリアライズロジックが削除され、新しく導入された marshalPublicKey 関数を呼び出すように変更されました。
    • これにより、CreateCertificate は公開鍵のシリアライズの詳細から解放され、その主な責務である証明書作成に集中できるようになりました。コードの凝集度が高まり、可読性と保守性が向上します。
  5. テストコードの追加:

    • P224楕円曲線を用いたECDSA鍵ペアのテストコードが追加されました。これは、新しいECDSA公開鍵マーシャリング機能が正しく動作することを検証するためのものです。
    • テストでは、ECDSA鍵ペアを生成し、その公開鍵を MarshalPKIXPublicKey でマーシャリングし、その後 ParsePKIXPublicKey でパースし直して、元の公開鍵と一致するかどうかを確認する、といった検証が行われたと考えられます。

これらの変更により、Goの crypto/x509 パッケージは、ECDSA公開鍵のより柔軟で標準に準拠した取り扱いをサポートするようになりました。

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

変更は src/pkg/crypto/x509/x509.go ファイルに集中しています。

--- a/src/pkg/crypto/x509/x509.go
+++ b/src/pkg/crypto/x509/x509.go
@@ -45,33 +45,55 @@ func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error) {
 	return parsePublicKey(algo, &pki)
 }
 
-// MarshalPKIXPublicKey serialises a public key to DER-encoded PKIX format.
-func MarshalPKIXPublicKey(pub interface{}) ([]byte, error) {
-	var pubBytes []byte
-
+func marshalPublicKey(pub interface{}) (publicKeyBytes []byte, publicKeyAlgorithm pkix.AlgorithmIdentifier, err error) {
 	switch pub := pub.(type) {
 	case *rsa.PublicKey:
-		pubBytes, _ = asn1.Marshal(rsaPublicKey{
+		publicKeyBytes, err = asn1.Marshal(rsaPublicKey{
 			N: pub.N,
 			E: pub.E,
 		})
+		publicKeyAlgorithm.Algorithm = oidPublicKeyRSA
+		// This is a NULL parameters value which is technically
+		// superfluous, but most other code includes it and, by
+		// doing this, we match their public key hashes.
+		publicKeyAlgorithm.Parameters = asn1.RawValue{
+			Tag: 5,
+		}
+	case *ecdsa.PublicKey:
+		publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
+		oid, ok := oidFromNamedCurve(pub.Curve)
+		if !ok {
+			return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve")
+		}
+		publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA
+		var paramBytes []byte
+		paramBytes, err = asn1.Marshal(oid)
+		if err != nil {
+			return
+		}
+		publicKeyAlgorithm.Parameters.FullBytes = paramBytes
 	default:
-		return nil, errors.New("x509: unknown public key type")
+		return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: only RSA and ECDSA public keys supported")
 	}
 
+	return publicKeyBytes, publicKeyAlgorithm, nil
+}
+
+// MarshalPKIXPublicKey serialises a public key to DER-encoded PKIX format.
+func MarshalPKIXPublicKey(pub interface{}) ([]byte, error) {
+	var publicKeyBytes [][]byte
+	var publicKeyAlgorithm pkix.AlgorithmIdentifier
+	var err error
+
+	if publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(pub); err != nil {
+		return nil, err
+	}
+
 	pkix := pkixPublicKey{
-		Algo: pkix.AlgorithmIdentifier{
-			Algorithm: []int{1, 2, 840, 113549, 1, 1, 1},
-			// This is a NULL parameters value which is technically
-			// superfluous, but most other code includes it and, by
-			// doing this, we match their public key hashes.
-			Parameters: asn1.RawValue{
-				Tag: 5,
-			},
-		},
+		Algo: publicKeyAlgorithm,
 		BitString: asn1.BitString{
-			Bytes:     pubBytes,
-			BitLength: 8 * len(pubBytes),
+			Bytes:     publicKeyBytes,
+			BitLength: 8 * len(publicKeyBytes),
 		},
 	}
 
@@ -1338,28 +1360,8 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interf
 	var publicKeyBytes []byte
 	var publicKeyAlgorithm pkix.AlgorithmIdentifier
 
-	switch pub := pub.(type) {
-	case *rsa.PublicKey:
-		publicKeyBytes, err = asn1.Marshal(rsaPublicKey{
-			N: pub.N,
-			E: pub.E,
-		})
-		publicKeyAlgorithm.Algorithm = oidPublicKeyRSA
-	case *ecdsa.PublicKey:
-		oid, ok := oidFromNamedCurve(pub.Curve)
-		if !ok {
-			return nil, errors.New("x509: unknown elliptic curve")
-		}
-		publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA
-		var paramBytes []byte
-		paramBytes, err = asn1.Marshal(oid)
-		if err != nil {
-			return
-		}
-		publicKeyAlgorithm.Parameters.FullBytes = paramBytes
-		publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
-	default:
-		return nil, errors.New("x509: only RSA and ECDSA public keys supported")
+	if publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(pub); err != nil {
+		return nil, err
 	}
 
 	var signatureAlgorithm pkix.AlgorithmIdentifier

コアとなるコードの解説

  1. func marshalPublicKey(...) の追加:

    • この関数は、公開鍵の型(RSAまたはECDSA)に応じて、公開鍵のバイト列 (publicKeyBytes) とアルゴリズム識別子 (publicKeyAlgorithm) を生成する責任を負います。
    • RSAの場合: 既存のRSA公開鍵のマーシャリングロジックがここに移動しました。asn1.Marshal を使用して rsaPublicKey 構造体をDERエンコードし、アルゴリズムは oidPublicKeyRSA に設定されます。パラメータとしてNULL値の asn1.RawValue が追加されています。これは、他の実装との互換性を保ち、公開鍵のハッシュ値が一致するようにするためです。
    • ECDSAの場合:
      • elliptic.Marshal(pub.Curve, pub.X, pub.Y) を呼び出して、ECDSA公開鍵のX座標とY座標をバイト列に変換します。これは、楕円曲線上の点を表現する標準的な方法です。
      • oidFromNamedCurve(pub.Curve) を使用して、使用されている楕円曲線のOIDを取得します。このOIDは、ECDSAアルゴリズムのパラメータとして必要です。
      • publicKeyAlgorithm.AlgorithmoidPublicKeyECDSA に設定され、publicKeyAlgorithm.Parameters には楕円曲線のOIDがDERエンコードされたバイト列として設定されます。
      • サポートされていない楕円曲線が使用された場合はエラーを返します。
    • サポートされていない公開鍵タイプが渡された場合は、エラーを返します。
  2. MarshalPKIXPublicKey の変更:

    • この関数は、marshalPublicKey を呼び出して公開鍵のバイト列とアルゴリズム識別子を取得するように変更されました。
    • 取得した情報を使用して、PKIX形式の公開鍵構造体 (pkixPublicKey) を構築し、最終的にDERエンコードして返します。これにより、MarshalPKIXPublicKey はRSAとECDSAの両方の公開鍵を統一的に処理できるようになりました。
  3. CreateCertificate の変更:

    • CreateCertificate 関数内の公開鍵のシリアライズに関する switch ステートメントが削除され、代わりに marshalPublicKey(pub) の呼び出しに置き換えられました。
    • これにより、CreateCertificate は公開鍵の具体的なシリアライズ方法の詳細を知る必要がなくなり、コードが簡潔になりました。エラーハンドリングも marshalPublicKey からのエラーをそのまま返す形に変更されています。

これらの変更により、公開鍵のシリアライズロジックが一箇所に集約され、crypto/x509 パッケージの内部構造がよりモジュール化され、拡張性と保守性が向上しました。特に、ECDSA公開鍵の標準的なマーシャリングがサポートされたことで、Go言語でECDSA証明書や鍵を扱う際の柔軟性が大幅に向上しました。

関連リンク

参考にした情報源リンク