[インデックス 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)
前方秘匿性とも呼ばれます。セッション鍵が、長期的な秘密鍵の漏洩によっても侵害されない特性を指します。つまり、もしサーバーの秘密鍵が将来的に漏洩したとしても、過去の通信内容が解読されることはありません。これは、セッションごとに一時的な鍵ペアを生成する DHE
や ECDHE
のような鍵交換アルゴリズムによって実現されます。
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
暗号スイートを統合するために、複数のファイルにわたる変更を加えています。
-
src/pkg/crypto/crypto.go
:PublicKey
インターフェースが追加されました。これにより、RSA公開鍵だけでなく、ECDSA公開鍵も統一的に扱えるようになります。これは、crypto/tls
パッケージが様々な公開鍵アルゴリズムをサポートするための基盤となります。
-
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
) が定数として追加されました。
- 新しい
-
src/pkg/crypto/tls/common.go
:certTypeECDSASign
などの新しい証明書タイプ定数が追加されました。これらはRFC4492で定義されており、ECDSA鍵を含む証明書を識別するために使用されます。supportedSignatureAlgorithms
リストに{hashSHA256, signatureECDSA}
が追加されました。これにより、TLS 1.2ハンドシェイクにおいて、クライアントとサーバーがECDSA署名アルゴリズムをサポートしていることを互いに通知できるようになります。Certificate
構造体のPrivateKey
フィールドのコメントが更新され、*ecdsa.PrivateKey
もサポートされることが明記されました。dsaSignature
とecdsaSignature
構造体が追加されました。これらはDSAおよびECDSA署名のRとS成分を表現するために使用されます。
-
src/pkg/crypto/tls/handshake_client.go
:- クライアントハンドシェイク中にサーバー証明書の公開鍵タイプを検証するロジックが更新され、RSA公開鍵だけでなくECDSA公開鍵も受け入れられるようになりました。
- クライアント認証 (Client Authentication) の際に、クライアントが自身の証明書に対応する秘密鍵で署名を行うロジックが変更されました。以前はRSA署名のみをサポートしていましたが、この変更によりECDSA秘密鍵での署名もサポートされるようになりました。具体的には、
crypto/ecdsa
パッケージのecdsa.Sign
関数が使用され、ASN.1エンコーディングされたECDSA署名が生成されます。
-
src/pkg/crypto/tls/handshake_server.go
:- サーバーハンドシェイク中にクライアント証明書を要求する際に、
certTypeECDSASign
も要求されるようになりました。これにより、サーバーはクライアントからECDSA証明書を受け入れる準備ができます。 - クライアント認証の際に、クライアントから送信された
certificateVerifyMsg
の署名を検証するロジックが更新されました。以前はRSA署名のみを検証していましたが、この変更によりECDSA署名も検証できるようになりました。crypto/ecdsa
パッケージのecdsa.Verify
関数が使用され、ASN.1デコードされたECDSA署名が検証されます。 processCertsFromClient
関数が、RSA公開鍵だけでなくECDSA公開鍵も返すように変更されました。
- サーバーハンドシェイク中にクライアント証明書を要求する際に、
-
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アルゴリズムを適切に処理するための基盤を構築しています。
関連リンク
- RFC 4492: Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)
- Go
crypto/tls
package documentation (現在のバージョン) - Perfect Forward Secrecy (PFS) とは何か (一般的な解説)
参考にした情報源リンク
- 上記のGitHubコミットページ
- Go言語の
crypto/tls
パッケージのソースコード - RFC 4492
- TLS/SSL、暗号スイート、PFSに関する一般的なセキュリティ情報源 (例: Cloudflare Learning Center, Wikipedia)
- Go言語の
crypto/ecdsa
およびcrypto/rsa
パッケージのドキュメント