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

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

このコミットは、Go言語の標準ライブラリである crypto/x509 パッケージに楕円曲線デジタル署名アルゴリズム (ECDSA) のサポートを追加するものです。具体的には、X.509証明書におけるECDSA署名の検証と、ECDSA公開鍵のパース機能が拡張されています。

コミット

commit 5c6162cdd16d7d706342946c0f2a8a944f2796fe
Author: Benjamin Black <b@b3k.us>
Date:   Tue May 22 11:03:59 2012 -0400

    crypto/x509: Add ECDSA support
    
    R=golang-dev, agl, rsc
    CC=golang-dev
    https://golang.org/cl/6208087
---
 src/pkg/crypto/x509/x509.go      | 144 +++++++++++++++++++++++++++++++++++----
 src/pkg/crypto/x509/x509_test.go | 111 ++++++++++++++++++++++++++++++
 2 files changed, 241 insertions(+), 14 deletions(-)

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

https://github.com/golang/go/commit/5c6162cdd16d7d706342946c0f2a8a944f2796fe

元コミット内容

crypto/x509: Add ECDSA support

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

変更の背景

この変更の背景には、デジタル署名技術の進化と、より効率的でセキュアな暗号アルゴリズムへの需要があります。ECDSAは、従来のRSAやDSAといったデジタル署名アルゴリズムと比較して、同等のセキュリティレベルをより短い鍵長で実現できるという利点があります。これにより、証明書のサイズを小さくし、署名生成・検証の計算コストを削減できるため、特にリソースが限られた環境や、大量のトランザクションを処理するシステムにおいて有利です。

Go言語の crypto/x509 パッケージは、X.509証明書のパース、検証、生成など、公開鍵基盤 (PKI) の中心的な機能を提供します。ECDSAのサポートを追加することで、GoアプリケーションがECDSAベースの証明書を扱うことが可能になり、より広範なPKIインフラストラクチャとの相互運用性が向上します。これは、Webサーバー(HTTPS)、コード署名、VPNなど、X.509証明書が広く利用される様々な分野において、Go言語の適用範囲を広げる上で重要なステップとなります。

前提知識の解説

X.509 証明書

X.509は、公開鍵証明書の標準フォーマットを定義するITU-Tの標準規格です。公開鍵と、その公開鍵の所有者に関する情報(識別名、有効期間など)を紐付け、認証局(CA)によってデジタル署名されたものです。これにより、公開鍵の正当性を検証し、通信相手の身元を確認することができます。X.509証明書は、TLS/SSL、VPN、コード署名など、様々なセキュリティプロトコルやアプリケーションで広く利用されています。

デジタル署名アルゴリズム (DSA, RSA, ECDSA)

デジタル署名アルゴリズムは、メッセージの完全性と送信者の認証を保証するために使用されます。

  • RSA (Rivest–Shamir–Adleman): 広く使われている公開鍵暗号アルゴリズムで、デジタル署名にも利用されます。大きな素数の積を因数分解することの困難性に基づいています。
  • DSA (Digital Signature Algorithm): 米国政府によって標準化されたデジタル署名アルゴリズムです。離散対数問題の困難性に基づいています。
  • ECDSA (Elliptic Curve Digital Signature Algorithm): 楕円曲線暗号 (ECC) をベースにしたデジタル署名アルゴリズムです。DSAと同様に離散対数問題に基づきますが、楕円曲線上の点を利用することで、同等のセキュリティレベルをより短い鍵長で実現できます。これにより、計算効率が向上し、署名サイズも小さくなります。

Object Identifier (OID)

OIDは、ASN.1 (Abstract Syntax Notation One) で定義されたオブジェクトを一意に識別するための数値の階層構造です。X.509証明書では、署名アルゴリズム、公開鍵アルゴリズム、拡張フィールドなど、様々な要素を識別するためにOIDが使用されます。例えば、1.2.840.113549.1.1.5sha1WithRSAEncryption を表すOIDです。

楕円曲線 (Elliptic Curve)

楕円曲線暗号 (ECC) は、楕円曲線上の点の加算とスカラー倍算という数学的操作に基づいています。特定の楕円曲線とベースポイントが定義され、その曲線上の点の離散対数問題の困難性が暗号の安全性に利用されます。このコミットで言及されている secp224r1, secp256r1, secp384r1, secp521r1 は、NIST (National Institute of Standards and Technology) によって標準化された、広く利用されている楕円曲線です。

  • secp256r1 (P-256): 最も広く使われている楕円曲線の一つで、TLS/SSLなどで頻繁に利用されます。
  • secp384r1 (P-384): P-256よりも高いセキュリティレベルを提供します。
  • secp521r1 (P-521): さらに高いセキュリティレベルを提供します。
  • secp224r1 (P-224): 比較的小さな曲線で、リソースが限られた環境で利用されることがあります。

技術的詳細

このコミットは、Go言語の crypto/x509 パッケージにECDSAのサポートを統合するために、以下の主要な変更を加えています。

  1. ECDSA関連の定数とOIDの追加:

    • SignatureAlgorithm 型に ECDSAWithSHA1, ECDSAWithSHA256, ECDSAWithSHA384, ECDSAWithSHA512 を追加。これらは、ECDSA署名に使用されるハッシュアルゴリズムを示します。
    • PublicKeyAlgorithm 型に ECDSA を追加。
    • 対応するOID (oidSignatureECDSAWithSHA1, oidSignatureECDSAWithSHA256, oidSignatureECDSAWithSHA384, oidSignatureECDSAWithSHA512, oidPublicKeyEcdsa) を定義。これらのOIDは、X.509証明書内でECDSA関連のアルゴリズムを識別するために使用されます。
    • RFC 3279 (ECDSA Signature Algorithm) および RFC 5758 (ECDSA Signature Algorithm) に基づくOIDのコメントが追加されています。
  2. 楕円曲線のOIDとマッピングの追加:

    • secp224r1, secp256r1, secp384r1, secp521r1 の各楕円曲線に対応するOID (oidNamedCurveP224, oidNamedCurveP256, oidNamedCurveP384, oidNamedCurveP521) を定義。
    • getNamedCurveFromOID 関数を追加し、これらのOIDをGoの crypto/elliptic パッケージで提供される具体的な楕円曲線 (elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521()) にマッピングします。これは、X.509証明書内の公開鍵情報から使用されている楕円曲線を特定するために不可欠です。
  3. ECDSA公開鍵のパース機能の追加:

    • parsePublicKey 関数に ECDSA アルゴリズムのケースを追加。
    • X.509証明書からECDSA公開鍵をパースするロジックが実装されています。これには、公開鍵情報から楕円曲線のOIDを抽出し、対応する楕円曲線を取得し、その後、公開鍵のX座標とY座標をアンマーシャルして ecdsa.PublicKey 構造体を構築する処理が含まれます。
  4. ECDSA署名検証機能の追加:

    • Certificate 型の CheckSignature メソッドにECDSA署名の検証ロジックを追加。
    • 署名アルゴリズムがECDSAの場合、署名データがASN.1でエンコードされたECDSA署名 (ecdsaSignature 構造体) としてデコードされます。
    • デコードされたRとSの値が正であることを確認した後、ecdsa.Verify 関数を使用して署名の検証が行われます。
  5. テストケースの追加:

    • x509_test.go にECDSA署名された自己署名証明書のPEMエンコードされたデータが複数追加されています。これらは、SHA1, SHA256, SHA384ハッシュアルゴリズムと、secp256r1, secp384r1, secp521r1楕円曲線を使用した証明書です。
    • TestECDSA 関数が追加され、これらのテスト証明書をパースし、署名アルゴリズム、公開鍵アルゴリズム、および署名検証が正しく行われることを確認しています。

これらの変更により、Goの crypto/x509 パッケージは、ECDSAベースのX.509証明書を完全にサポートできるようになり、Goアプリケーションがより現代的な暗号技術を利用できるようになります。

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

src/pkg/crypto/x509/x509.go

  • import 文に crypto/ecdsacrypto/elliptic を追加。
  • ecdsaSignature 型を dsaSignature と同様に定義。
  • SignatureAlgorithm および PublicKeyAlgorithm 列挙型にECDSA関連の定数を追加。
  • ECDSA関連のOID定数 (oidSignatureECDSAWithSHA1 など) を追加。
  • getSignatureAlgorithmFromOID 関数にECDSA関連のOIDから署名アルゴリズムを返すケースを追加。
  • oidPublicKeyEcdsa 定数を追加。
  • getPublicKeyAlgorithmFromOID 関数に oidPublicKeyEcdsa から ECDSA を返すケースを追加。
  • 楕円曲線 (secp224r1, secp256r1, secp384r1, secp521r1) のOID定数を追加。
  • getNamedCurveFromOID 関数を追加し、OIDから対応する elliptic.Curve オブジェクトを返すように実装。
  • Certificate.CheckSignature メソッドの switch algo 文に ECDSAWithSHA1, ECDSAWithSHA256, ECDSAWithSHA384, ECDSAWithSHA512 のケースを追加し、対応するハッシュタイプを設定。
  • Certificate.CheckSignature メソッドの switch pub := c.PublicKey.(type) 文に *ecdsa.PublicKey のケースを追加し、ECDSA署名検証ロジックを実装。
  • parsePublicKey 関数に case ECDSA: を追加し、ECDSA公開鍵のパースロジックを実装。

src/pkg/crypto/x509/x509_test.go

  • import 文に crypto/ecdsa を追加。
  • _ "crypto/sha256"_ "crypto/sha512" を追加(ハッシュアルゴリズムの登録のため)。
  • ECDSA署名された自己署名証明書のPEMエンコードされた文字列変数 (ecdsaSHA1CertPem, ecdsaSHA256p256CertPem, ecdsaSHA256p384CertPem, ecdsaSHA384p521CertPem) を追加。
  • ecdsaTests スライスを定義し、各テスト証明書と期待される署名アルゴリズムをマッピング。
  • TestECDSA 関数を追加し、ecdsaTests をループして各証明書のパースと検証を行うテストロジックを実装。

コアとなるコードの解説

src/pkg/crypto/x509/x509.go

type ecdsaSignature dsaSignature

これは、ECDSA署名がDSA署名と同様に、2つの大きな整数RとSで構成されることを示しています。Goの crypto/ecdsa パッケージの Sign 関数も (R, S *big.Int, err error) を返すため、この構造は自然です。

OIDの追加とマッピング

// OIDs for signature algorithms
var (
    // ... 既存のOID ...
    oidSignatureECDSAWithSHA1   = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
    oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
    oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
    oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
)

// ...

func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) SignatureAlgorithm {
    switch {
    // ... 既存のケース ...
    case oid.Equal(oidSignatureECDSAWithSHA1):
        return ECDSAWithSHA1
    case oid.Equal(oidSignatureECDSAWithSHA256):
        return ECDSAWithSHA256
    case oid.Equal(oidSignatureECDSAWithSHA384):
        return ECDSAWithSHA384
    case oid.Equal(oidSignatureECDSAWithSHA512):
        return ECDSAWithSHA512
    }
    return UnknownSignatureAlgorithm
}

これらの変更は、X.509証明書内で使用されるECDSA署名アルゴリズムを識別するための標準的なOIDを定義し、それらをGoの内部表現である SignatureAlgorithm 型にマッピングします。これにより、証明書をパースする際に、どのECDSAアルゴリズムが使用されているかを正確に認識できるようになります。

楕円曲線のOIDと getNamedCurveFromOID

var (
    oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
    oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
    oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
    oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
)

func getNamedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
    switch {
    case oid.Equal(oidNamedCurveP224):
        return elliptic.P224()
    case oid.Equal(oidNamedCurveP256):
        return elliptic.P256()
    case oid.Equal(oidNamedCurveP384):
        return elliptic.P384()
    case oid.Equal(oidNamedCurveP521):
        return elliptic.P521()
    }
    return nil
}

ECDSA公開鍵は、使用される楕円曲線の種類に依存します。このセクションでは、標準的な楕円曲線(NIST P-224, P-256, P-384, P-521)に対応するOIDを定義し、それらをGoの crypto/elliptic パッケージが提供する具体的な曲線オブジェクトに変換する関数を提供します。これにより、証明書から公開鍵を抽出する際に、正しい楕円曲線コンテキストを確立できます。

parsePublicKey におけるECDSAサポート

func parsePublicKey(algo PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{}, error) {
    switch algo {
    // ... 既存のケース ...
    case ECDSA:
        paramsData := keyData.Algorithm.Parameters.FullBytes
        namedCurveOID := new(asn1.ObjectIdentifier)
        _, err := asn1.Unmarshal(paramsData, namedCurveOID)
        if err != nil {
            return nil, err
        }
        namedCurve := getNamedCurveFromOID(*namedCurveOID)
        if namedCurve == nil {
            return nil, errors.New("crypto/x509: unsupported elliptic curve")
        }
        x, y := elliptic.Unmarshal(namedCurve, asn1Data)
        if x == nil {
            return nil, errors.New("crypto/x509: failed to unmarshal elliptic curve point")
        }
        pub := &ecdsa.PublicKey{
            Curve: namedCurve,
            X:     x,
            Y:     y,
        }
        return pub, nil
    // ...
    }
}

このコードは、X.509証明書からECDSA公開鍵をパースする中心的なロジックです。

  1. keyData.Algorithm.Parameters.FullBytes から楕円曲線のOIDを抽出します。
  2. getNamedCurveFromOID を使用して、対応する elliptic.Curve オブジェクトを取得します。
  3. asn1Data (公開鍵のビット列) を elliptic.Unmarshal を使用して、楕円曲線上の点 (X, Y座標) に変換します。
  4. 最終的に、パースされた曲線と座標を使用して ecdsa.PublicKey 構造体を構築し、返します。

Certificate.CheckSignature におけるECDSA検証

func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature []byte) error {
    // ... ハッシュタイプの決定 ...

    switch pub := c.PublicKey.(type) {
    // ... 既存のケース ...
    case *ecdsa.PublicKey:
        ecdsaSig := new(ecdsaSignature)
        if _, err := asn1.Unmarshal(signature, ecdsaSig); err != nil {
            return err
        }
        if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
            return errors.New("crypto/x509: ECDSA signature contained zero or negative values")
        }
        if !ecdsa.Verify(pub, digest, ecdsaSig.R, ecdsaSig.S) {
            return errors.New("crypto/x509: ECDSA verification failure")
        }
        return nil
    }
    return ErrUnsupportedAlgorithm
}

このセクションは、証明書の署名を検証する際にECDSA署名を処理します。

  1. 署名データ (signature バイト列) を ecdsaSignature 構造体にASN.1デコードします。
  2. デコードされたRとSの値が正であることを確認します(ECDSAの仕様による)。
  3. ecdsa.Verify 関数を呼び出して、公開鍵 (pub)、ハッシュ値 (digest)、および署名RとSを使用して署名を検証します。検証が成功すれば nil を返し、失敗すればエラーを返します。

src/pkg/crypto/x509/x509_test.go

テスト証明書と TestECDSA 関数

// Self-signed certificate using ECDSA with SHA1 & secp256r1
var ecdsaSHA1CertPem = `...` // PEMエンコードされた証明書データ

// ... 他のECDSAテスト証明書 ...

var ecdsaTests = []struct {
    sigAlgo SignatureAlgorithm
    pemCert string
}{
    {ECDSAWithSHA1, ecdsaSHA1CertPem},
    {ECDSAWithSHA256, ecdsaSHA256p256CertPem},
    {ECDSAWithSHA256, ecdsaSHA256p384CertPem},
    {ECDSAWithSHA384, ecdsaSHA384p521CertPem},
}

func TestECDSA(t *testing.T) {
    for i, test := range ecdsaTests {
        pemBlock, _ := pem.Decode([]byte(test.pemCert))
        cert, err := ParseCertificate(pemBlock.Bytes)
        // ... 検証ロジック ...
    }
}

このテストコードは、実際にECDSA署名された証明書をGoの crypto/x509 パッケージでパースし、その署名アルゴリズム、公開鍵アルゴリズム、そして最も重要な署名検証が正しく機能するかを確認します。異なるハッシュアルゴリズムと楕円曲線の組み合わせをテストすることで、実装の堅牢性を保証しています。

関連リンク

参考にした情報源リンク