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

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

このコミットは、Go言語のcrypto/x509パッケージに、SEC1形式の楕円曲線(EC)秘密鍵と、PKCS#8でカプセル化されたEC秘密鍵のパース(解析)機能を追加するものです。これにより、Goの標準ライブラリがより広範な暗号鍵フォーマットに対応できるようになり、相互運用性が向上します。

コミット

  • コミットハッシュ: 63315c0af1df2bb3fc443860c2cb70a4a60380b5
  • 作者: Joel Sing jsing@google.com
  • 日付: 2012年11月15日 木曜日 03:39:00 +1100

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

https://github.com/golang/go/commit/63315c0af1df2bb3fc443860c2cb70a4a60380b5

元コミット内容

    crypto/x509: add support for SEC1/EC private keys.
    
    Add support for parsing SEC1 EC private keys and PKCS8 encapsulated
    EC private key structures.
    
    R=agl
    CC=golang-dev
    https://golang.org/cl/6767045

変更の背景

Go言語のcrypto/x509パッケージは、X.509証明書や公開鍵・秘密鍵の処理を扱うための標準ライブラリです。このコミット以前は、楕円曲線暗号(ECC)の秘密鍵の主要なフォーマットであるSEC1形式や、それらを汎用的な秘密鍵コンテナであるPKCS#8形式でカプセル化したものを直接パースする機能が不足していました。

現代の暗号通信において、ECCはRSAに比べて短い鍵長で同等のセキュリティ強度を提供できるため、TLS/SSL、デジタル署名、暗号化など、様々な場面で広く利用されています。そのため、Go言語がこれらの鍵フォーマットをネイティブにサポートすることは、Goでセキュアなアプリケーションを開発する上で不可欠でした。この変更は、Goの暗号ライブラリの機能性と相互運用性を向上させることを目的としています。

前提知識の解説

1. 楕円曲線暗号 (ECC)

楕円曲線暗号(Elliptic Curve Cryptography, ECC)は、楕円曲線上の点の演算を利用した公開鍵暗号方式です。RSA暗号と比較して、より短い鍵長で同等のセキュリティ強度を実現できるため、計算資源が限られた環境(モバイルデバイスなど)や、通信帯域を節約したい場合に有利です。ECCは、デジタル署名(ECDSA)、鍵共有(ECDH)、暗号化などに応用されます。

2. SEC1 (Standards for Efficient Cryptography)

SEC1は、Standards for Efficient Cryptography Group (SECG) によって定義された、楕円曲線暗号に関する標準規格です。特に、楕円曲線秘密鍵の構造をASN.1 (Abstract Syntax Notation One) で定義しており、そのフォーマットはRFC 5915「Elliptic Curve Private Key Structure」で標準化されています。SEC1形式の秘密鍵は、通常、バージョン、秘密鍵の値(オクテット列)、楕円曲線ドメインパラメータ(通常は名前付き曲線OID)、およびオプションで公開鍵の情報を含みます。

3. PKCS#8 (Public-Key Cryptography Standards #8)

PKCS#8は、RSA Securityによって定義されたPublic-Key Cryptography Standards (PKCS) の一つで、秘密鍵情報を格納するための汎用的な構文を定めています。RFC 5208「PKCS #8: Private-Key Information Syntax Specification」で標準化されています。PKCS#8は、特定のアルゴリズムに依存しないため、RSA、DSA、ECなど、様々な種類の秘密鍵を統一的な形式で扱うことができます。

PKCS#8の主要な構造はPrivateKeyInfoで、これには以下の要素が含まれます。

  • version: 構造のバージョン。
  • privateKeyAlgorithm: 秘密鍵のアルゴリズムを識別するAlgorithmIdentifier。これにはアルゴリズムのオブジェクト識別子(OID)と、そのアルゴリズムに固有のパラメータが含まれます。
  • privateKey: 実際の秘密鍵データ。これはオクテット列として格納され、その内容はprivateKeyAlgorithmによって解釈されます。

PKCS#8は、秘密鍵を暗号化して格納する機能もサポートしており、パスワードベースの暗号化(PBE)と組み合わせて利用されることが多いです。

4. ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法であり、通信プロトコルや暗号化において広く使用されています。X.509証明書、PKCS標準、LDAPなど、多くのセキュリティ関連の標準がASN.1で定義されています。ASN.1で定義されたデータ構造は、通常、DER (Distinguished Encoding Rules) やPEM (Privacy-Enhanced Mail) などのエンコーディングルールに従ってバイト列に変換されます。

5. OID (Object Identifier)

OIDは、オブジェクトを一意に識別するための階層的な命名システムです。暗号化の文脈では、アルゴリズム、曲線、証明書ポリシーなどを識別するために使用されます。例えば、楕円曲線公開鍵アルゴリズムは特定のOID (1.2.840.10045.2.1) で識別され、特定の名前付き曲線(例: secp256r1)もそれぞれ固有のOIDを持っています。

6. crypto/x509パッケージ (Go言語)

Go言語の標準ライブラリであるcrypto/x509パッケージは、X.509証明書、証明書署名要求(CSR)、証明書失効リスト(CRL)、および符号化された公開鍵と秘密鍵のパースと生成を実装しています。このパッケージは、TLS/SSL通信やデジタル署名など、Goアプリケーションにおける暗号機能の基盤を提供します。

技術的詳細

このコミットの主要な目的は、crypto/x509パッケージがSEC1形式およびPKCS#8でカプセル化されたEC秘密鍵をパースできるようにすることです。

PKCS#8におけるEC秘密鍵のパース

pkcs8.goファイルでは、ParsePKCS8PrivateKey関数が変更されています。この関数は、PKCS#8形式のDERエンコードされた秘密鍵バイト列を受け取り、その内容を解析してGoの適切な秘密鍵構造体(例: *rsa.PrivateKey*ecdsa.PrivateKey)に変換します。

変更点として、privKey.Algo.Algorithm(秘密鍵のアルゴリズムを識別するOID)がoidPublicKeyECDSA(楕円曲線公開鍵アルゴリズムのOID)と一致する場合の処理が追加されました。この場合、PKCS#8構造内のパラメータ部分から名前付き曲線OIDを抽出し、実際のEC秘密鍵データ(privKey.PrivateKey)を新しいparseECPrivateKey関数に渡して解析します。

SEC1形式EC秘密鍵のパース

新しく追加されたsec1.goファイルには、SEC1形式のEC秘密鍵をパースするためのロジックが実装されています。

  1. ecPrivateKey構造体: この構造体は、RFC 5915およびSEC1で定義されているASN.1のECPrivateKey構造をGoの構造体として表現しています。

    type ecPrivateKey struct {
        Version       int
        PrivateKey    []byte
        NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
        PublicKey     asn1.BitString        `asn1:"optional,explicit,tag:1"`
    }
    
    • Version: 秘密鍵構造のバージョン。SEC1では通常1です。
    • PrivateKey: 実際のEC秘密鍵の値(整数)をバイト列として格納します。
    • NamedCurveOID: 使用されている楕円曲線を識別するOID。ASN.1のOPTIONALフィールドですが、RFC 5915では必須とされています。explicit,tag:0は、ASN.1のタグ付けに関する指定です。
    • PublicKey: 対応する公開鍵。これもOPTIONALフィールドです。explicit,tag:1は、ASN.1のタグ付けに関する指定です。
  2. ParseECPrivateKey関数: この関数は、SEC1形式のDERエンコードされたEC秘密鍵バイト列を受け取り、*ecdsa.PrivateKey構造体に変換します。内部的にはparseECPrivateKeyを呼び出します。

  3. parseECPrivateKey関数: この内部関数が実際のパースロジックを担います。

    • まず、入力されたDERバイト列をecPrivateKey構造体にアンマーシャル(ASN.1デコード)します。
    • privKey.Versionが期待されるバージョン(ecPrivKeyVersion = 1)と異なる場合はエラーを返します。
    • 楕円曲線の特定:
      • 呼び出し元(PKCS#8パースなど)からnamedCurveOIDが提供されている場合は、そのOIDを使用します。
      • 提供されていない場合は、ecPrivateKey構造体自身に含まれるprivKey.NamedCurveOIDを使用します。
      • namedCurveFromOID関数(既存の関数)を使って、OIDに対応するelliptic.Curveインターフェースの実装を取得します。不明な曲線OIDの場合はエラーとなります。
    • 秘密鍵の値の検証と設定:
      • privKey.PrivateKey(秘密鍵のバイト列)をmath/big.Intに変換します。
      • 秘密鍵の値が曲線の位数N以上である場合(無効な秘密鍵)はエラーを返します。
      • 新しいecdsa.PrivateKey構造体を作成し、パースした曲線と秘密鍵の値Dを設定します。
    • 公開鍵の導出:
      • curve.ScalarBaseMult(privKey.PrivateKey)を呼び出して、秘密鍵から対応する公開鍵のX座標とY座標を計算し、priv.Xpriv.Yに設定します。これにより、完全なecdsa.PrivateKey構造体が構築されます。

テストの追加

pkcs8_test.gosec1_test.goには、それぞれPKCS#8でカプセル化されたEC秘密鍵とSEC1形式のEC秘密鍵のパースをテストするための新しいテストケースが追加されています。これらのテストは、opensslコマンドで生成されたサンプル鍵データを使用しており、実装が正しく機能することを確認します。

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

このコミットでは、以下の4つのファイルが変更されています。

  1. src/pkg/crypto/x509/pkcs8.go:

    • ParsePKCS8PrivateKey関数に、PKCS#8でカプセル化されたEC秘密鍵をパースするロジックが追加されました。
    • oidPublicKeyECDSA(楕円曲線公開鍵アルゴリズムのOID)を識別し、parseECPrivateKey関数を呼び出す分岐が追加されています。
    • 参照ドキュメントとしてRFC5208が追記されました。
  2. src/pkg/crypto/x509/pkcs8_test.go:

    • TestPKCS8テスト関数が修正され、RSA秘密鍵のテストに加えて、PKCS#8でカプセル化されたEC秘密鍵のパーステストが追加されました。
    • 新しいテストデータpkcs8ECPrivateKeyHexが定義されています。
  3. src/pkg/crypto/x509/sec1.go:

    • 新規ファイルとして追加されました。
    • SEC1形式のEC秘密鍵のASN.1構造を表現するecPrivateKey構造体が定義されています。
    • SEC1形式のEC秘密鍵をパースするParseECPrivateKey関数と、その内部で利用されるparseECPrivateKey関数が実装されています。
    • RFC5915およびSEC1の参照がコメントとして記載されています。
  4. src/pkg/crypto/x509/sec1_test.go:

    • 新規ファイルとして追加されました。
    • TestParseECPrivateKeyテスト関数が実装され、SEC1形式のEC秘密鍵のパースを検証します。
    • テストデータecPrivateKeyHexが定義されています。

コアとなるコードの解説

src/pkg/crypto/x509/pkcs8.go の変更点

 // ParsePKCS8PrivateKey parses an unencrypted, PKCS#8 private key. See
 // http://www.rsa.com/rsalabs/node.asp?id=2130 and RFC5208.
 func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
 	var privKey pkcs8
 	if _, err := asn1.Unmarshal(der, &privKey); err != nil {
 		return nil, errors.New("crypto/x509: failed to parse PKCS#8 private key: " + err.Error())
 	}

 	switch {
 	case privKey.Algo.Algorithm.Equal(oidPublicKeyRSA):
 		key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
 		if err != nil {
 			return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
 		}
 		return key, nil

 	case privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA): // ★追加された部分
 		bytes := privKey.Algo.Parameters.FullBytes
 		namedCurveOID := new(asn1.ObjectIdentifier)
 		if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil {
 			namedCurveOID = nil
 		}
 		key, err = parseECPrivateKey(namedCurveOID, privKey.PrivateKey)
 		if err != nil {
 			return nil, errors.New("crypto/x509: failed to parse EC private key embedded in PKCS#8: " + err.Error())
 		}
 		return key, nil

 	default:
 		return nil, fmt.Errorf("crypto/x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm)
 	}
 }

この変更により、ParsePKCS8PrivateKey関数は、PKCS#8構造内のアルゴリズム識別子(privKey.Algo.Algorithm)がECDSA(oidPublicKeyECDSA)である場合に、新しく追加されたparseECPrivateKey関数を呼び出すようになりました。privKey.Algo.Parameters.FullBytesから名前付き曲線OIDを抽出し、それをparseECPrivateKeyに渡すことで、正しい楕円曲線が特定されます。

src/pkg/crypto/x509/sec1.go の新規追加

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x509

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"encoding/asn1"
	"errors"
	"fmt"
	"math/big"
)

const ecPrivKeyVersion = 1

// ecPrivateKey reflects an ASN.1 Elliptic Curve Private Key Structure.
// References:
//   RFC5915
//   SEC1 - http://www.secg.org/download/aid-780/sec1-v2.pdf
// Per RFC5915 the NamedCurveOID is marked as ASN.1 OPTIONAL, however in
// most cases it is not.
type ecPrivateKey struct {
	Version       int
	PrivateKey    []byte
	NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
	PublicKey     asn1.BitString        `asn1:"optional,explicit,tag:1"`
}

// ParseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
func ParseECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error) {
	return parseECPrivateKey(nil, der)
}

// parseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
// The OID for the named curve may be provided from another source (such as
// the PKCS8 container) - if it is provided then use this instead of the OID
// that may exist in the EC private key structure.
func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *ecdsa.PrivateKey, err error) {
	var privKey ecPrivateKey
	if _, err := asn1.Unmarshal(der, &privKey); err != nil {
		return nil, errors.New("crypto/x509: failed to parse EC private key: " + err.Error())
	}
	if privKey.Version != ecPrivKeyVersion {
		return nil, fmt.Errorf("crypto/x509: unknown EC private key version %d", privKey.Version)
	}

	var curve elliptic.Curve
	if namedCurveOID != nil {
		curve = namedCurveFromOID(*namedCurveOID)
	} else {
		curve = namedCurveFromOID(privKey.NamedCurveOID)
	}
	if curve == nil {
		return nil, errors.New("crypto/x509: unknown elliptic curve")
	}

	k := new(big.Int).SetBytes(privKey.PrivateKey)
	if k.Cmp(curve.Params().N) >= 0 {
		return nil, errors.New("crypto/x509: invalid elliptic curve private key value")
	}
	priv := new(ecdsa.PrivateKey)
	priv.Curve = curve
	priv.D = k
	priv.X, priv.Y = curve.ScalarBaseMult(privKey.PrivateKey) // 秘密鍵から公開鍵を導出

	return priv, nil
}

このファイルは、SEC1形式のEC秘密鍵のパースロジックを完全にカプセル化しています。ecPrivateKey構造体はASN.1の定義をGoで表現し、parseECPrivateKey関数はDERバイト列をこの構造体にアンマーシャルし、バージョンチェック、曲線の特定、秘密鍵の値の検証、そして最終的に公開鍵の導出を行って、Goの*ecdsa.PrivateKeyオブジェクトを返します。特に、curve.ScalarBaseMult(privKey.PrivateKey)は、秘密鍵から対応する公開鍵を計算する重要なステップです。

関連リンク

参考にした情報源リンク