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

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

このコミットは、Go言語の標準ライブラリ crypto/tls パッケージに、TLS (Transport Layer Security) プロトコルにおける新しい暗号スイートである ECDHE-ECDSA のサポートを追加するものです。これにより、楕円曲線ディフィー・ヘルマン鍵交換 (ECDHE) と楕円曲線デジタル署名アルゴリズム (ECDSA) を組み合わせた、よりセキュアな通信が可能になります。

コミット

commit 7b7dac5e235145b08644e2fe4864b3d1fb8e2d5a
Author: Joel Sing <jsing@google.com>
Date:   Wed Jul 17 12:33:16 2013 -0400

    crypto/tls: Add support for ECDHE-ECDSA
    
    Add support for ECDHE-ECDSA (RFC4492), which uses an ephemeral server
    key pair to perform ECDH with ECDSA signatures. Like ECDHE-RSA,
    ECDHE-ECDSA also provides PFS.
    
    R=agl
    CC=golang-dev
    https://golang.org/cl/7006047

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

https://github.com/golang/go/commit/7b7dac5e235145b08644e2fe4864b3d1fb8e2d5a

元コミット内容

crypto/tls: Add support for ECDHE-ECDSA

このコミットは、ECDHE-ECDSA (RFC4492) のサポートを追加します。これは、一時的なサーバー鍵ペアを使用してECDHを実行し、ECDSA署名を使用します。ECDHE-RSA と同様に、ECDHE-ECDSA もPFS (Perfect Forward Secrecy) を提供します。

変更の背景

TLS/SSLプロトコルは、インターネット上での安全な通信を確立するために不可欠です。そのセキュリティは、使用される暗号スイート(鍵交換アルゴリズム、認証アルゴリズム、対称暗号化アルゴリズム、ハッシュアルゴリズムの組み合わせ)に大きく依存します。

従来のTLS暗号スイートでは、RSA鍵交換が広く使用されていました。しかし、RSA鍵交換にはいくつかの課題がありました。特に、サーバーの長期的な秘密鍵が漏洩した場合、過去の通信もすべて解読されてしまうという問題(Perfect Forward Secrecyの欠如)が指摘されていました。

この問題を解決するために、一時的な鍵交換方式である DHE (Diffie-Hellman Ephemeral) や ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) が導入されました。これらの方式では、セッションごとに新しい鍵ペアが生成されるため、たとえサーバーの長期的な秘密鍵が漏洩しても、過去の通信の秘密鍵は漏洩せず、通信の機密性が保たれます。これがPerfect Forward Secrecy (PFS) です。

ECDHE-RSA は既にGoの crypto/tls パッケージでサポートされていましたが、このコミットでは ECDHE-ECDSA のサポートが追加されました。ECDSA (Elliptic Curve Digital Signature Algorithm) は、RSAに比べて短い鍵長で同等のセキュリティ強度を提供できるため、計算コストや通信帯域の削減に貢献します。特に、モバイルデバイスなどリソースが限られた環境でのTLS通信において、その利点が顕著になります。

この変更の背景には、TLSのセキュリティ強化とパフォーマンス向上の両面からの要求がありました。

前提知識の解説

TLS/SSL (Transport Layer Security / Secure Sockets Layer)

TLSは、インターネット上でデータを安全にやり取りするための暗号化プロトコルです。クライアントとサーバー間の通信を暗号化し、データの完全性と認証を提供します。

暗号スイート (Cipher Suite)

TLS通信で使用される暗号アルゴリズムの組み合わせを定義するものです。例えば、TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA は以下を示します。

  • TLS: TLSプロトコルを使用
  • ECDHE: 鍵交換に楕円曲線ディフィー・ヘルマン一時鍵交換を使用
  • RSA: サーバー認証にRSA署名を使用
  • AES_128_CBC: 対称暗号化にAES 128ビットCBCモードを使用
  • SHA: メッセージ認証コードにSHA1を使用

鍵交換アルゴリズム (Key Exchange Algorithm)

クライアントとサーバーが共通の秘密鍵を安全に共有するためのアルゴリズムです。

  • RSA: サーバーの公開鍵でクライアントが生成した共通鍵を暗号化して送信します。サーバーの秘密鍵が漏洩すると、過去の通信もすべて解読される可能性があります。
  • Diffie-Hellman (DH): クライアントとサーバーがそれぞれ秘密の値を持ち、公開鍵を交換することで共通の秘密鍵を生成します。セッションごとに新しい鍵を生成する DHE はPFSを提供します。
  • Elliptic Curve Diffie-Hellman (ECDH): DHを楕円曲線暗号に適用したものです。DHよりも短い鍵長で同等のセキュリティ強度を提供できます。セッションごとに新しい鍵を生成する ECDHE はPFSを提供します。

認証アルゴリズム (Authentication Algorithm)

サーバーが自身の身元をクライアントに証明し、クライアントがサーバーの身元を確認するためのアルゴリズムです。

  • RSA: サーバーの証明書に含まれるRSA公開鍵を使用して、サーバーが送信する署名を検証します。
  • ECDSA (Elliptic Curve Digital Signature Algorithm): サーバーの証明書に含まれるECDSA公開鍵を使用して、サーバーが送信する署名を検証します。RSAに比べて短い鍵長で同等のセキュリティ強度を提供できます。

Perfect Forward Secrecy (PFS)

前方秘匿性とも呼ばれます。セッション鍵が、長期的な秘密鍵の漏洩によっても侵害されない特性を指します。つまり、もしサーバーの秘密鍵が将来的に漏洩したとしても、過去の通信内容が解読されることはありません。これは、セッションごとに一時的な鍵ペアを生成する DHEECDHE のような鍵交換アルゴリズムによって実現されます。

RFC4492

"Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)" というタイトルのRFC (Request for Comments) です。このRFCは、TLSプロトコルで楕円曲線暗号 (ECC) を使用するための暗号スイートと関連するメカニズムを定義しています。ECDHE-ECDSA はこのRFCで定義されている暗号スイートの一つです。

技術的詳細

このコミットは、Goの crypto/tls パッケージに ECDHE-ECDSA 暗号スイートを統合するために、複数のファイルにわたる変更を加えています。

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

    • PublicKey インターフェースが追加されました。これにより、RSA公開鍵だけでなく、ECDSA公開鍵も統一的に扱えるようになります。これは、crypto/tls パッケージが様々な公開鍵アルゴリズムをサポートするための基盤となります。
  2. src/pkg/crypto/tls/cipher_suites.go:

    • 新しい ECDHE-ECDSA 暗号スイートが cipherSuites リストに追加されました。具体的には、TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA が追加されています。
    • これらの新しい暗号スイートに対応する ecdheECDSAKA 関数が追加されました。この関数は、ecdheKeyAgreement 構造体を初期化し、署名タイプとして signatureECDSA を設定します。これにより、ECDHE鍵交換とECDSA署名を組み合わせたハンドシェイクが可能になります。
    • 既存の ecdheRSAKA 関数も ecdheKeyAgreement 構造体を使用するように変更され、署名タイプとして signatureRSA が設定されるようになりました。これにより、ECDHE鍵交換のロジックが共通化され、署名アルゴリズムのみが異なる形で再利用できるようになりました。
    • IANA (Internet Assigned Numbers Authority) によって割り当てられた新しい暗号スイートID (例: 0xc007, 0xc009, 0xc00a) が定数として追加されました。
  3. src/pkg/crypto/tls/common.go:

    • certTypeECDSASign などの新しい証明書タイプ定数が追加されました。これらはRFC4492で定義されており、ECDSA鍵を含む証明書を識別するために使用されます。
    • supportedSignatureAlgorithms リストに {hashSHA256, signatureECDSA} が追加されました。これにより、TLS 1.2ハンドシェイクにおいて、クライアントとサーバーがECDSA署名アルゴリズムをサポートしていることを互いに通知できるようになります。
    • Certificate 構造体の PrivateKey フィールドのコメントが更新され、*ecdsa.PrivateKey もサポートされることが明記されました。
    • dsaSignatureecdsaSignature 構造体が追加されました。これらはDSAおよびECDSA署名のRとS成分を表現するために使用されます。
  4. src/pkg/crypto/tls/handshake_client.go:

    • クライアントハンドシェイク中にサーバー証明書の公開鍵タイプを検証するロジックが更新され、RSA公開鍵だけでなくECDSA公開鍵も受け入れられるようになりました。
    • クライアント認証 (Client Authentication) の際に、クライアントが自身の証明書に対応する秘密鍵で署名を行うロジックが変更されました。以前はRSA署名のみをサポートしていましたが、この変更によりECDSA秘密鍵での署名もサポートされるようになりました。具体的には、crypto/ecdsa パッケージの ecdsa.Sign 関数が使用され、ASN.1エンコーディングされたECDSA署名が生成されます。
  5. src/pkg/crypto/tls/handshake_server.go:

    • サーバーハンドシェイク中にクライアント証明書を要求する際に、certTypeECDSASign も要求されるようになりました。これにより、サーバーはクライアントからECDSA証明書を受け入れる準備ができます。
    • クライアント認証の際に、クライアントから送信された certificateVerifyMsg の署名を検証するロジックが更新されました。以前はRSA署名のみを検証していましたが、この変更によりECDSA署名も検証できるようになりました。crypto/ecdsa パッケージの ecdsa.Verify 関数が使用され、ASN.1デコードされたECDSA署名が検証されます。
    • processCertsFromClient 関数が、RSA公開鍵だけでなくECDSA公開鍵も返すように変更されました。
  6. src/pkg/crypto/tls/handshake_client_test.go および src/pkg/crypto/tls/handshake_server_test.go:

    • ECDHE-ECDSA 暗号スイートのサポートを検証するための新しいテストケースが追加されました。これには、ECDSA証明書と秘密鍵の定義、および対応するクライアント/サーバーハンドシェイクスクリプトが含まれます。
    • 既存のテストケース名がより明確になるように変更されました (例: TestHandshakeClientRC4 から TestHandshakeClientRSARC4)。
    • テスト設定にECDSA証明書と秘密鍵をロードするロジックが追加されました。

これらの変更により、Goの crypto/tls パッケージは ECDHE-ECDSA 暗号スイートを完全にサポートし、より多様なTLS通信シナリオに対応できるようになりました。

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

このコミットのコアとなる変更は、主に以下のファイルに集中しています。

  • src/pkg/crypto/tls/cipher_suites.go: 新しい ECDHE-ECDSA 暗号スイートの定義と、それに対応する鍵交換関数の追加。
  • src/pkg/crypto/tls/common.go: PublicKey インターフェースの導入、ECDSA関連の証明書タイプと署名アルゴリズムの追加。
  • src/pkg/crypto/tls/handshake_client.go: クライアント側でのECDSA公開鍵の検証とECDSA秘密鍵での署名処理の追加。
  • src/pkg/crypto/tls/handshake_server.go: サーバー側でのECDSA証明書要求とECDSA署名検証処理の追加。

特に、cipher_suites.go での新しい暗号スイートの追加と、handshake_client.go および handshake_server.go でのECDSA鍵と署名の処理ロジックの変更が、この機能追加の核心部分です。

src/pkg/crypto/tls/cipher_suites.go の変更例

// 変更前
var cipherSuites = []*cipherSuite{
	// Ciphersuite order is chosen so that ECDHE comes before plain RSA
	// and RC4 comes before AES (because of the Lucky13 attack).
	{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1},
	{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
	{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
	{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, false, cipherRC4, macSHA1},
	{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, false, cipherAES, macSHA1},
	{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, false, cipherAES, macSHA1},
}

// 変更後
var cipherSuites = []*cipherSuite{
	// Ciphersuite order is chosen so that ECDHE comes before plain RSA
	// and RC4 comes before AES (because of the Lucky13 attack).
	{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1},
	{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, true, cipherRC4, macSHA1}, // 追加
	{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
	{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, true, cipherAES, macSHA1}, // 追加
	{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
	{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, true, cipherAES, macSHA1}, // 追加
	{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, false, cipherRC4, macSHA1},
	{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, false, cipherAES, macSHA1},
	{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, false, cipherAES, macSHA1},
}

// 新しい鍵交換関数
func ecdheECDSAKA(version uint16) keyAgreement {
	return &ecdheKeyAgreement{
		sigType: signatureECDSA,
		version: version,
	}
}

// 既存の鍵交換関数の変更
func ecdheRSAKA(version uint16) keyAgreement {
	return &ecdheKeyAgreement{
		sigType: signatureRSA, // sigType を追加
		version: version,
	}
}

src/pkg/crypto/tls/handshake_client.go の変更例

// 変更前: サーバー証明書の公開鍵検証
if _, ok := certs[0].PublicKey.(*rsa.PublicKey); !ok {
	return c.sendAlert(alertUnsupportedCertificate)
}

// 変更後: サーバー証明書の公開鍵検証 (ECDSAもサポート)
switch certs[0].PublicKey.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey:
	break
default:
	return c.sendAlert(alertUnsupportedCertificate)
}

// 変更前: クライアント認証時の署名 (RSAのみ)
digest, hashFunc := finishedHash.hashForClientCertificate()
signed, err := rsa.SignPKCS1v15(c.config.rand(), c.config.Certificates[0].PrivateKey.(*rsa.PrivateKey), hashFunc, digest)

// 変更後: クライアント認証時の署名 (RSAとECDSAをサポート)
var signed []byte
certVerify := new(certificateVerifyMsg)
switch key := c.config.Certificates[0].PrivateKey.(type) {
case *ecdsa.PrivateKey:
	digest, _ := finishedHash.hashForClientCertificate(signatureECDSA)
	r, s, err := ecdsa.Sign(c.config.rand(), key, digest)
	if err == nil {
		signed, err = asn1.Marshal(ecdsaSignature{r, s})
	}
case *rsa.PrivateKey:
	digest, hashFunc := finishedHash.hashForClientCertificate(signatureRSA)
	signed, err = rsa.SignPKCS1v15(c.config.rand(), key, hashFunc, digest)
default:
	err = errors.New("unknown private key type")
}

コアとなるコードの解説

cipher_suites.go

  • cipherSuites リストへの追加: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA などの新しい暗号スイートが追加されました。これにより、GoのTLS実装がこれらの暗号スイートをネゴシエートできるようになります。各エントリは、暗号スイートID、鍵長、MAC長、IV長、鍵交換関数、PFSサポートの有無、対称暗号化アルゴリズム、MACアルゴリズムなどの情報を含んでいます。
  • ecdheECDSAKA 関数の追加: この関数は、ecdheKeyAgreement 構造体を返します。この構造体は、ECDHE鍵交換のロジックをカプセル化し、sigType フィールドで署名アルゴリズム(この場合は signatureECDSA)を指定します。これにより、ECDHE鍵交換とECDSA署名を組み合わせたハンドシェイクフローが確立されます。
  • ecdheRSAKA 関数の変更: 既存の ecdheRSAKA 関数も ecdheKeyAgreement 構造体を使用するように変更され、sigType フィールドに signatureRSA が設定されました。これにより、ECDHE鍵交換の共通ロジックが ecdheKeyAgreement に集約され、コードの重複が削減されました。

handshake_client.go

  • サーバー証明書の公開鍵検証: クライアントがサーバーから受け取った証明書の公開鍵を検証する際に、RSA公開鍵だけでなくECDSA公開鍵も有効なものとして認識するようになりました。これは、switch ステートメントを使用して certs[0].PublicKey の型をチェックすることで実現されています。
  • クライアント認証時の署名処理: クライアントがサーバーに自身の証明書を提示し、その秘密鍵で署名を行う場合、以前はRSA秘密鍵のみをサポートしていました。この変更により、ecdsa.PrivateKey もサポートされるようになりました。クライアントの秘密鍵の型に応じて、ecdsa.Sign または rsa.SignPKCS1v15 が呼び出され、適切な署名が生成されます。ECDSA署名はRとSの2つの整数で構成され、ASN.1形式でエンコードされて送信されます。

handshake_server.go

  • クライアント証明書要求: サーバーがクライアントに証明書を要求する際に、certTypeRSASign に加えて certTypeECDSASign も要求するようになりました。これにより、サーバーはクライアントからRSAまたはECDSAのいずれかの証明書を受け入れることができます。
  • クライアント署名検証: サーバーがクライアントから受け取った certificateVerifyMsg の署名を検証する際に、以前はRSA署名のみを検証していました。この変更により、ECDSA署名も検証できるようになりました。サーバーはクライアントの公開鍵の型をチェックし、それに応じて ecdsa.Verify または rsa.VerifyPKCS1v15 を呼び出して署名を検証します。ECDSA署名の場合、受信した署名データはASN.1デコードされてRとSの成分が抽出され、検証に使用されます。

これらの変更は、TLSハンドシェイクの鍵交換と認証のフェーズにおいて、ECDSAアルゴリズムを適切に処理するための基盤を構築しています。

関連リンク

参考にした情報源リンク

  • 上記のGitHubコミットページ
  • Go言語の crypto/tls パッケージのソースコード
  • RFC 4492
  • TLS/SSL、暗号スイート、PFSに関する一般的なセキュリティ情報源 (例: Cloudflare Learning Center, Wikipedia)
  • Go言語の crypto/ecdsa および crypto/rsa パッケージのドキュメント