[インデックス 17634] ファイルの概要
このコミットは、Go言語の標準ライブラリである crypto/tls
パッケージ内の変更に関するものです。crypto/tls
パッケージは、TLS (Transport Layer Security) プロトコルを実装しており、Goアプリケーションが安全な通信を行うための基盤を提供します。具体的には、サーバーとクライアント間のハンドシェイク、暗号スイートのネゴシエーション、データの暗号化と復号化などを担当します。
このコミットで変更された主要なファイルは以下の通りです。
src/pkg/crypto/tls/cipher_suites.go
: TLSハンドシェイク中に利用可能な暗号スイートの定義と、それらの特性を管理するファイルです。src/pkg/crypto/tls/handshake_server.go
: TLSサーバー側のハンドシェイク処理ロジックを実装しているファイルです。クライアントからの接続要求を受け入れ、証明書の提示、暗号スイートの選択、鍵交換などを行います。src/pkg/crypto/tls/handshake_server_test.go
:handshake_server.go
のテストコードが含まれており、サーバーハンドシェイクの様々なシナリオを検証します。
コミット
commit eef7035ec860a2a8eac39ed3f4c0cb2a384599a2
Author: Adam Langley <agl@golang.org>
Date: Tue Sep 17 13:30:36 2013 -0400
crypto/tls: don't select ECDSA ciphersuites with only an RSA certificate.
47ec7a68b1a2 added support for ECDSA ciphersuites but didn't alter the
cipher suite selection to take that into account. Thus Go servers could
try and select an ECDSA cipher suite while only having an RSA
certificate, leading to connection failures.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/13239053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eef7035ec860a2a8eac39ed3f4c0cb2a384599a2
元コミット内容
crypto/tls: don't select ECDSA ciphersuites with only an RSA certificate.
47ec7a68b1a2 added support for ECDSA ciphersuites but didn't alter the
cipher suite selection to take that into account. Thus Go servers could
try and select an ECDSA cipher suite while only having an RSA
certificate, leading to connection failures.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/13239053
変更の背景
このコミットの背景には、Go言語の crypto/tls
パッケージにおける暗号スイート選択ロジックの不整合がありました。以前のコミット 47ec7a68b1a2
でECDSA (Elliptic Curve Digital Signature Algorithm) ベースの暗号スイートがサポートに追加されました。しかし、この追加は、サーバーが利用可能な証明書の種類(RSAまたはECDSA)を考慮せずに暗号スイートを選択する既存のロジックを修正していませんでした。
その結果、GoサーバーがRSA証明書しか持っていないにもかかわらず、クライアントとのTLSハンドシェイク中にECDSA暗号スイートを選択しようとする問題が発生しました。TLSハンドシェイクでは、サーバーが提示する証明書の公開鍵のタイプ(RSAまたはECDSA)と、選択された暗号スイートが使用する署名アルゴリズムのタイプが一致している必要があります。この不一致が発生すると、ハンドシェイクが失敗し、クライアントとサーバー間の安全な接続が確立できなくなっていました。
このコミットは、この問題を解決し、サーバーが自身の証明書タイプに基づいて適切な暗号スイートのみを選択するように、暗号スイート選択ロジックを修正することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のTLS/SSLおよび暗号技術に関する前提知識が必要です。
1. TLS/SSLハンドシェイクの概要
TLS (Transport Layer Security) は、インターネット上で安全な通信を行うためのプロトコルです。クライアントとサーバーが安全な接続を確立する際に「ハンドシェイク」と呼ばれる一連のプロセスを実行します。サーバー側のハンドシェイクの主要なステップは以下の通りです。
- ClientHello: クライアントがサポートするTLSバージョン、暗号スイートのリスト、圧縮方式、拡張機能などをサーバーに送信します。
- ServerHello: サーバーはClientHelloを受け取り、クライアントと合意したTLSバージョン、選択した暗号スイート、セッションIDなどをクライアントに返します。
- Certificate: サーバーは自身の身元を証明するためにデジタル証明書をクライアントに提示します。この証明書にはサーバーの公開鍵が含まれています。
- ServerKeyExchange (オプション): 鍵交換アルゴリズムによっては、サーバーが追加の鍵交換情報を送信します(例: ECDHEの場合)。
- CertificateRequest (オプション): サーバーがクライアント認証を要求する場合、クライアント証明書を要求します。
- ServerHelloDone: サーバーが自身のハンドシェイクメッセージの送信を完了したことを示します。
- ClientKeyExchange: クライアントは、サーバーの公開鍵(証明書から取得)または鍵交換アルゴリズム(例: ECDHE)を使用して、セッション鍵の生成に必要な情報を暗号化してサーバーに送信します。
- ChangeCipherSpec: クライアントとサーバーが、これ以降の通信をネゴシエートされた暗号スイートとセッション鍵で暗号化することを示します。
- Finished: ハンドシェイクの完了を検証するためのメッセージを暗号化して送信します。
2. 暗号スイート (Cipher Suite)
暗号スイートは、TLS接続で使用される一連のアルゴリズムの組み合わせを定義します。通常、以下の要素を含みます。
- 鍵交換アルゴリズム (Key Exchange Algorithm): クライアントとサーバーがセッション鍵を安全に共有する方法を決定します。
- RSA: サーバーのRSA公開鍵を使用して、クライアントが生成したプリマスターシークレットを暗号化し、サーバーに送信します。サーバーは自身のRSA秘密鍵で復号します。Perfect Forward Secrecy (PFS) を提供しません。
- ECDHE (Elliptic Curve Diffie-Hellman Ephemeral): 楕円曲線ディフィー・ヘルマン鍵交換の「一時的 (Ephemeral)」バージョンです。セッションごとに新しい鍵ペアを生成するため、PFSを提供します。
- 署名アルゴリズム (Authentication/Signature Algorithm): サーバーが自身の身元を証明するためにデジタル署名を行うアルゴリズムです。サーバー証明書に含まれる公開鍵のタイプと一致する必要があります。
- RSA: RSA公開鍵暗号方式に基づくデジタル署名。
- ECDSA (Elliptic Curve Digital Signature Algorithm): 楕円曲線暗号に基づくデジタル署名。
- バルク暗号化アルゴリズム (Bulk Encryption Algorithm): 実際のデータ転送に使用される対称鍵暗号アルゴリズム(例: AES, RC4, 3DES)。
- メッセージ認証コード (MAC) アルゴリズム: メッセージの完全性と認証を保証するためのハッシュ関数(例: SHA1, SHA256)。
暗号スイートの命名規則は TLS_鍵交換_署名_暗号化_MAC
のようになります。
例: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- 鍵交換: ECDHE
- 署名: RSA (サーバー証明書がRSAである必要がある)
- 暗号化: AES_128_GCM
- MAC: SHA256
例: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- 鍵交換: ECDHE
- 署名: ECDSA (サーバー証明書がECDSAである必要がある)
- 暗号化: AES_128_GCM
- MAC: SHA256
3. RSA証明書とECDSA証明書
デジタル証明書は、公開鍵とそれに関連する所有者の情報を結びつけ、信頼された認証局 (CA) によって署名されたものです。証明書には公開鍵が含まれており、この公開鍵のタイプによって証明書の種類が区別されます。
- RSA証明書: 公開鍵がRSAアルゴリズムに基づいています。この証明書を持つサーバーは、RSA鍵交換やRSA署名を使用する暗号スイートと互換性があります。
- ECDSA証明書: 公開鍵が楕円曲線暗号 (ECC) に基づいています。この証明書を持つサーバーは、ECDSA署名を使用する暗号スイートと互換性があります。
サーバーが提示する証明書のタイプと、選択される暗号スイートの署名アルゴリズムは一致している必要があります。例えば、サーバーがRSA証明書しか持っていない場合、ECDSA署名を使用する暗号スイート(例: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
)を選択することはできません。なぜなら、サーバーはその暗号スイートで要求されるECDSA署名を行うための秘密鍵を持っていないからです。
技術的詳細
このコミットは、Goの crypto/tls
パッケージにおけるサーバー側の暗号スイート選択ロジックを強化し、サーバーが持つ証明書のタイプ(RSAまたはECDSA)と選択される暗号スイートの署名アルゴリズムが一致するように修正しています。
1. cipherSuite
構造体の変更とフラグの導入
以前の cipherSuite
構造体には elliptic bool
フィールドがあり、その暗号スイートが楕円曲線ベースの鍵交換(ECDHE)を使用するかどうかを示していました。しかし、これは鍵交換の種類のみを考慮しており、署名アルゴリズムの種類(RSAまたはECDSA)を区別していませんでした。
このコミットでは、より汎用的な flags int
フィールドが導入され、以下の新しい定数が定義されました。
const (
// suiteECDH indicates that the cipher suite involves elliptic curve
// Diffie-Hellman. This means that it should only be selected when the
// client indicates that it supports ECC with a curve and point format
// that we're happy with.
suiteECDHE = 1 << iota
// suiteECDSA indicates that the cipher suite involves an ECDSA
// signature and therefore may only be selected when the server's
// certificate is ECDSA. If this is not set then the cipher suite is
// RSA based.
suiteECDSA
)
suiteECDHE
: 暗号スイートがECDHE鍵交換を使用することを示します。suiteECDSA
: 暗号スイートがECDSA署名を使用することを示します。このフラグが設定されていない場合、その暗号スイートはRSAベースの署名を使用すると見なされます。
これにより、各暗号スイートが持つ特性(鍵交換の種類と署名アルゴリズムの種類)をより正確に表現できるようになりました。cipherSuites
配列内の各暗号スイート定義も、この新しい flags
フィールドを使用するように更新されています。例えば、TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
のようなECDSAベースの暗号スイートには suiteECDHE | suiteECDSA
のフラグが設定されます。
2. サーバーハンドシェイク状態 (serverHandshakeState
) の拡張
serverHandshakeState
構造体には、新たに ecdsaOk bool
フィールドと cert *Certificate
フィールドが追加されました。
type serverHandshakeState struct {
// ...
ellipticOk bool
ecdsaOk bool // 追加
sessionState *sessionState
finishedHash finishedHash
masterSecret []byte
certsFromClient [][]byte
cert *Certificate // 追加
}
cert
: サーバーがクライアントに提示する証明書を保持します。以前はハンドシェイク中にローカル変数として扱われていましたが、状態として保持されるようになりました。ecdsaOk
: サーバーが提示する証明書がECDSA秘密鍵を持っているかどうかを示すブール値です。これは、サーバーがECDSA署名を行う能力があるかどうかを判断するために使用されます。
serverHandshake
関数内で、サーバーが使用する証明書 (hs.cert
) が決定された後、その証明書の秘密鍵がECDSAタイプであるかどうかがチェックされ、hs.ecdsaOk
が設定されます。
// ...
hs.cert = &config.Certificates[0]
if len(hs.clientHello.serverName) > 0 {
hs.cert = config.getCertificateForName(hs.clientHello.serverName)
}
_, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey) // ここでecdsaOkを設定
// ...
3. 暗号スイート選択ロジックの修正 (tryCipherSuite
)
最も重要な変更は、Conn
型の tryCipherSuite
関数のシグネチャと内部ロジックです。この関数は、クライアントが提示した暗号スイートのリストの中から、サーバーがサポートし、かつ現在の状況で利用可能な最適な暗号スイートを選択する役割を担います。
変更前:
func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipticOk bool) *cipherSuite
変更後:
func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipticOk, ecdsaOk bool) *cipherSuite
新しい ecdsaOk
引数が追加されました。
そして、この関数内の暗号スイート選択ロジックが以下のように修正されました。
// Don't select a ciphersuite which we can't
// support for this client.
if (candidate.flags&suiteECDHE != 0) && !ellipticOk {
continue
}
// 重要な変更点: 署名アルゴリズムと証明書タイプの整合性をチェック
if (candidate.flags&suiteECDSA != 0) != ecdsaOk {
continue
}
return candidate
if (candidate.flags&suiteECDHE != 0) && !ellipticOk
: これは以前のcandidate.elliptic && !ellipticOk
と同等で、暗号スイートがECDHE鍵交換を要求するにもかかわらず、クライアントが楕円曲線暗号をサポートしていない場合にそのスイートをスキップします。if (candidate.flags&suiteECDSA != 0) != ecdsaOk
: これがこのコミットの核心的な変更です。candidate.flags&suiteECDSA != 0
: 選択しようとしている暗号スイートがECDSA署名を使用するかどうかをチェックします。ecdsaOk
: サーバーがECDSA証明書を持っているかどうかを示します。!=
: この論理は、「暗号スイートがECDSA署名を要求すること」と「サーバーがECDSA証明書を持っていること」が一致しない場合にtrue
となります。つまり、以下のいずれかの状況でcontinue
してこの暗号スイートをスキップします。- 暗号スイートがECDSA署名を要求する (
suiteECDSA
フラグあり) が、サーバーがECDSA証明書を持っていない (ecdsaOk
がfalse
)。 - 暗号スイートがECDSA署名を要求しない (
suiteECDSA
フラグなし、つまりRSAベース) が、サーバーがECDSA証明書を持っている (ecdsaOk
がtrue
)。
- 暗号スイートがECDSA署名を要求する (
このロジックにより、サーバーは自身の証明書タイプ(RSAまたはECDSA)と互換性のある署名アルゴリズムを持つ暗号スイートのみを選択するようになります。これにより、RSA証明書しか持たないサーバーが誤ってECDSA暗号スイートを選択し、ハンドシェイクが失敗する問題を根本的に解決します。
4. doFullHandshake
における hs.cert
の利用統一
doFullHandshake
関数内で、OCSP Stapling、証明書メッセージの送信、鍵交換情報の生成など、サーバー証明書を参照する箇所が、ローカル変数 cert
ではなく、hs.cert
フィールドを使用するように統一されました。これにより、コードの一貫性が向上し、証明書情報がハンドシェイク状態の一部として適切に管理されるようになります。
5. テストケースの追加
src/pkg/crypto/tls/handshake_server_test.go
に TestCipherSuiteCertPreferance
という新しいテストケースが追加されました。このテストは、サーバーがRSA証明書を持っている場合にRSA暗号スイートが選択され、ECDSA証明書を持っている場合にECDSA暗号スイートが選択されることを検証します。これにより、修正された暗号スイート選択ロジックが期待通りに機能することが保証されます。
コアとなるコードの変更箇所
src/pkg/crypto/tls/cipher_suites.go
--- a/src/pkg/crypto/tls/cipher_suites.go
+++ b/src/pkg/crypto/tls/cipher_suites.go
@@ -34,6 +34,19 @@ type keyAgreement interface {
generateClientKeyExchange(*Config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error)
}
+const (
+ // suiteECDH indicates that the cipher suite involves elliptic curve
+ // Diffie-Hellman. This means that it should only be selected when the
+ // client indicates that it supports ECC with a curve and point format
+ // that we're happy with.
+ suiteECDHE = 1 << iota
+ // suiteECDSA indicates that the cipher suite involves an ECDSA
+ // signature and therefore may only be selected when the server's
+ // certificate is ECDSA. If this is not set then the cipher suite is
+ // RSA based.
+ suiteECDSA
+)
+
// A cipherSuite is a specific combination of key agreement, cipher and MAC
// function. All cipher suites currently assume RSA key agreement.
type cipherSuite struct {
@@ -43,31 +56,29 @@ type cipherSuite struct {
macLen int
ivLen int
ka func(version uint16) keyAgreement
- // If elliptic is set, a server will only consider this ciphersuite if
- // the ClientHello indicated that the client supports an elliptic curve
- // and point format that we can handle.
- elliptic bool
- cipher func(key, iv []byte, isRead bool) interface{}
- mac func(version uint16, macKey []byte) macFunction
- aead func(key, fixedNonce []byte) cipher.AEAD
+ // flags is a bitmask of the suite* values, above.
+ flags int
+ cipher func(key, iv []byte, isRead bool) interface{}
+ mac func(version uint16, macKey []byte) macFunction
+ aead func(key, fixedNonce []byte) cipher.AEAD
}
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_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, true, nil, nil, aeadAESGCM},\
- {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, true, nil, nil, aeadAESGCM},\
- {TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1, nil},\
- {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, true, cipherRC4, macSHA1, nil},\
- {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1, nil},\
- {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, true, cipherAES, macSHA1, nil},\
- {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1, nil},\
- {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, true, cipherAES, macSHA1, nil},\
- {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, false, cipherRC4, macSHA1, nil},\
- {TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, false, cipherAES, macSHA1, nil},\
- {TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, false, cipherAES, macSHA1, nil},\
- {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, true, cipher3DES, macSHA1, nil},\
- {TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, false, cipher3DES, macSHA1, nil},\
+ {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE, nil, nil, aeadAESGCM},\
+ {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA, nil, nil, aeadAESGCM},\
+ {TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE, cipherRC4, macSHA1, nil},\
+ {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherRC4, macSHA1, nil},\
+ {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},\
+ {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},\
+ {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},\
+ {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},\
+ {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, 0, cipherRC4, macSHA1, nil},\
+ {TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},\
+ {TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},\
+ {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil},\
+ {TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, 0, cipher3DES, macSHA1, nil},\
}
func cipherRC4(key, iv []byte, isRead bool) interface{} {
src/pkg/crypto/tls/handshake_server.go
--- a/src/pkg/crypto/tls/handshake_server.go
+++ b/src/pkg/crypto/tls/handshake_server.go
@@ -23,10 +23,12 @@ type serverHandshakeState struct {
hello *serverHelloMsg
suite *cipherSuite
ellipticOk bool
+ ecdsaOk bool // 追加
sessionState *sessionState
finishedHash finishedHash
masterSecret []byte
certsFromClient [][]byte
+ cert *Certificate // 追加
}
// serverHandshake performs a TLS handshake as a server.
@@ -167,6 +169,16 @@ Curves:
hs.hello.nextProtos = config.NextProtos
}
+ if len(config.Certificates) == 0 {
+ return false, c.sendAlert(alertInternalError)
+ }
+ hs.cert = &config.Certificates[0]
+ if len(hs.clientHello.serverName) > 0 {
+ hs.cert = config.getCertificateForName(hs.clientHello.serverName)
+ }
+
+ _, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey) // ecdsaOk の設定
+
if hs.checkForResumption() {
return true, nil
}
@@ -181,7 +193,7 @@ Curves:
}
for _, id := range preferenceList {
- if hs.suite = c.tryCipherSuite(id, supportedList, hs.ellipticOk); hs.suite != nil {
+ if hs.suite = c.tryCipherSuite(id, supportedList, hs.ellipticOk, hs.ecdsaOk); hs.suite != nil { // ecdsaOk を引数に追加
break
}
}
@@ -222,7 +234,7 @@ func (hs *serverHandshakeState) checkForResumption() bool {
}
// Check that we also support the ciphersuite from the session.
- hs.suite = c.tryCipherSuite(hs.sessionState.cipherSuite, c.config.cipherSuites(), hs.ellipticOk)
+ hs.suite = c.tryCipherSuite(hs.sessionState.cipherSuite, c.config.cipherSuites(), hs.ellipticOk, hs.ecdsaOk) // ecdsaOk を引数に追加
if hs.suite == nil {
return false
}
@@ -264,15 +276,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
config := hs.c.config
c := hs.c
- if len(config.Certificates) == 0 {
- return c.sendAlert(alertInternalError)
- }
- cert := &config.Certificates[0]
- if len(hs.clientHello.serverName) > 0 {
- cert = config.getCertificateForName(hs.clientHello.serverName)
- }
-
- if hs.clientHello.ocspStapling && len(cert.OCSPStaple) > 0 {
+ if hs.clientHello.ocspStapling && len(hs.cert.OCSPStaple) > 0 { // hs.cert を使用
hs.hello.ocspStapling = true
}
@@ -282,20 +286,20 @@ func (hs *serverHandshakeState) doFullHandshake() error {
c.writeRecord(recordTypeHandshake, hs.hello.marshal())
certMsg := new(certificateMsg)
- certMsg.certificates = cert.Certificate
+ certMsg.certificates = hs.cert.Certificate // hs.cert を使用
hs.finishedHash.Write(certMsg.marshal())
c.writeRecord(recordTypeHandshake, certMsg.marshal())
if hs.hello.ocspStapling {
certStatus := new(certificateStatusMsg)
certStatus.statusType = statusTypeOCSP
- certStatus.response = cert.OCSPStaple
+ certStatus.response = hs.cert.OCSPStaple // hs.cert を使用
hs.finishedHash.Write(certStatus.marshal())
c.writeRecord(recordTypeHandshake, certStatus.marshal())
}
keyAgreement := hs.suite.ka(c.vers)
- skx, err := keyAgreement.generateServerKeyExchange(config, cert, hs.clientHello, hs.hello)
+ skx, err := keyAgreement.generateServerKeyExchange(config, hs.cert, hs.clientHello, hs.hello) // hs.cert を使用
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
@@ -419,7 +423,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
hs.finishedHash.Write(certVerify.marshal())
}
- preMasterSecret, err := keyAgreement.processClientKeyExchange(config, cert, ckx, c.vers)
+ preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers) // hs.cert を使用
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
@@ -601,7 +605,7 @@ func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (c
// tryCipherSuite returns a cipherSuite with the given id if that cipher suite
// is acceptable to use.
-func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipticOk bool) *cipherSuite {
+func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipticOk, ecdsaOk bool) *cipherSuite { // ecdsaOk を引数に追加
for _, supported := range supportedCipherSuites {
if id == supported {
var candidate *cipherSuite
@@ -617,7 +621,10 @@ func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipti
}
// Don't select a ciphersuite which we can't
// support for this client.
- if candidate.elliptic && !ellipticOk {
+ if (candidate.flags&suiteECDHE != 0) && !ellipticOk { // flags を使用
+ continue
+ }
+ if (candidate.flags&suiteECDSA != 0) != ecdsaOk { // 署名アルゴリズムと証明書タイプの整合性チェック
continue
}
return candidate
src/pkg/crypto/tls/handshake_server_test.go
--- a/src/pkg/crypto/tls/handshake_server_test.go
+++ b/src/pkg/crypto/tls/handshake_server_test.go
@@ -302,6 +302,28 @@ func TestClientAuthECDSA(t *testing.T) {
}\
}\
+// TestCipherSuiteCertPreferance ensures that we select an RSA ciphersuite with
+// an RSA certificate and an ECDSA ciphersuite with an ECDSA certificate.
+func TestCipherSuiteCertPreferance(t *testing.T) {
+ var config = *testConfig
+ config.CipherSuites = []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}
+ config.MaxVersion = VersionTLS11
+ config.PreferServerCipherSuites = true
+ testServerScript(t, "CipherSuiteCertPreference", tls11ECDHEAESServerScript, &config, nil)
+
+ config = *testConfig
+ config.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}
+ config.Certificates = []Certificate{
+ Certificate{
+ Certificate: [][]byte{testECDSACertificate},
+ PrivateKey: testECDSAPrivateKey,
+ },
+ }
+ config.BuildNameToCertificate()
+ config.PreferServerCipherSuites = true
+ testServerScript(t, "CipherSuiteCertPreference2", ecdheECDSAAESServerScript, &config, nil)
+}
+
func TestTLS11Server(t *testing.T) {
var config = *testConfig
config.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}
コアとなるコードの解説
src/pkg/crypto/tls/cipher_suites.go
の変更
-
suiteECDHE
とsuiteECDSA
定数の導入:const ( suiteECDHE = 1 << iota suiteECDSA )
これらの定数は、暗号スイートがECDHE鍵交換を使用するか、またはECDSA署名を使用するかを示すビットマスクとして機能します。
iota
を使用することで、suiteECDHE
は1
(001b)、suiteECDSA
は2
(010b) となります。 -
cipherSuite
構造体のelliptic
からflags
への変更:type cipherSuite struct { // ... // elliptic bool // 削除 flags int // 追加 // ... }
これにより、暗号スイートの特性を単一のブール値ではなく、複数のビットフラグで表現できるようになり、より柔軟な管理が可能になりました。
-
cipherSuites
配列の更新:var cipherSuites = []*cipherSuite{ // ... {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE, nil, nil, aeadAESGCM}, {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA, nil, nil, aeadAESGCM}, // ... {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, 0, cipherRC4, macSHA1, nil}, // ... }
各暗号スイートの定義において、以前の
elliptic
フィールドの値が新しいflags
フィールドに置き換えられました。例えば、TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
はECDHE鍵交換とECDSA署名の両方を使用するため、suiteECDHE | suiteECDSA
のフラグが設定されています。RSAベースの暗号スイート(例:TLS_RSA_WITH_RC4_128_SHA
)は、どちらのフラグも持たないため0
が設定されています。
src/pkg/crypto/tls/handshake_server.go
の変更
-
serverHandshakeState
構造体の拡張:type serverHandshakeState struct { // ... ellipticOk bool ecdsaOk bool // 追加: サーバーがECDSA証明書を持っているか // ... cert *Certificate // 追加: サーバーが提示する証明書 }
ecdsaOk
は、サーバーがECDSA秘密鍵を持つ証明書をロードしている場合にtrue
となります。cert
は、ハンドシェイク中に使用されるサーバー証明書へのポインタです。 -
serverHandshake
関数でのecdsaOk
の設定:hs.cert = &config.Certificates[0] if len(hs.clientHello.serverName) > 0 { hs.cert = config.getCertificateForName(hs.clientHello.serverName) } _, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey)
サーバーが提示する証明書 (
hs.cert
) の秘密鍵の型をチェックし、それが*ecdsa.PrivateKey
であればhs.ecdsaOk
をtrue
に設定します。これにより、サーバーがECDSA署名を行う能力があるかどうかがハンドシェイク状態に記録されます。 -
tryCipherSuite
関数のシグネチャ変更とロジックの修正:func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipticOk, ecdsaOk bool) *cipherSuite { // ... if (candidate.flags&suiteECDHE != 0) && !ellipticOk { continue } if (candidate.flags&suiteECDSA != 0) != ecdsaOk { continue } return candidate }
tryCipherSuite
関数は、ecdsaOk
という新しい引数を受け取るようになりました。 内部の選択ロジックでは、まずsuiteECDHE
フラグとellipticOk
をチェックし、クライアントがECDHEをサポートしない場合はスキップします。 次に、if (candidate.flags&suiteECDSA != 0) != ecdsaOk
の行が、このコミットの最も重要な部分です。candidate.flags&suiteECDSA != 0
: 候補の暗号スイートがECDSA署名を使用するかどうか。ecdsaOk
: サーバーがECDSA証明書を持っているかどうか。 この条件は、以下のいずれかの場合にtrue
となり、その暗号スイートはスキップされます。
- 暗号スイートがECDSA署名を要求する (
suiteECDSA
フラグあり) が、サーバーはRSA証明書しか持っていない (ecdsaOk
がfalse
)。 - 暗号スイートがRSA署名を使用する (
suiteECDSA
フラグなし) が、サーバーはECDSA証明書を持っている (ecdsaOk
がtrue
)。 このロジックにより、サーバーは自身の証明書タイプと一致する署名アルゴリズムを持つ暗号スイートのみを選択するようになります。
-
doFullHandshake
関数でのhs.cert
の利用:// ... if hs.clientHello.ocspStapling && len(hs.cert.OCSPStaple) > 0 { // cert -> hs.cert hs.hello.ocspStapling = true } // ... certMsg.certificates = hs.cert.Certificate // cert -> hs.cert // ... certStatus.response = hs.cert.OCSPStaple // cert -> hs.cert // ... skx, err := keyAgreement.generateServerKeyExchange(config, hs.cert, hs.clientHello, hs.hello) // cert -> hs.cert // ... preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers) // cert -> hs.cert // ...
doFullHandshake
内で、OCSP Stapling、証明書メッセージ、鍵交換など、サーバー証明書を参照するすべての箇所が、ローカル変数cert
ではなく、serverHandshakeState
のフィールドであるhs.cert
を使用するように変更されました。これにより、証明書情報の一貫した管理が保証されます。
src/pkg/crypto/tls/handshake_server_test.go
の変更
TestCipherSuiteCertPreferance
テストケースの追加:
このテストは、サーバーが設定された証明書タイプ(RSAまたはECDSA)に基づいて、適切な暗号スイート(ECDHE_RSAまたはECDHE_ECDSA)が選択されることを検証します。これにより、修正されたロジックが正しく機能することが確認されます。func TestCipherSuiteCertPreferance(t *testing.T) { // ... RSA証明書とECDHE_RSA/ECDHE_ECDSAスイートのテスト testServerScript(t, "CipherSuiteCertPreference", tls11ECDHEAESServerScript, &config, nil) // ... ECDSA証明書とECDHE_RSA/ECDHE_ECDSAスイートのテスト config.Certificates = []Certificate{ Certificate{ Certificate: [][]byte{testECDSACertificate}, PrivateKey: testECDSAPrivateKey, }, } // ... testServerScript(t, "CipherSuiteCertPreference2", ecdheECDSAAESServerScript, &config, nil) }
これらの変更により、GoのTLSサーバーは、自身の証明書タイプと互換性のない暗号スイートを誤って選択することがなくなり、TLSハンドシェイクの信頼性と成功率が向上しました。
関連リンク
- Go言語
crypto/tls
パッケージのドキュメント: https://pkg.go.dev/crypto/tls - TLS 1.2 RFC (RFC 5246): https://datatracker.ietf.org/doc/html/rfc5246
- 楕円曲線デジタル署名アルゴリズム (ECDSA) の概要: https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E7%BD%B2%E5%90%8D%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0
- Perfect Forward Secrecy (PFS) について: https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A9%E3%83%AF%E3%83%BC%E3%83%89%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%82%B7%E3%83%BC
参考にした情報源リンク
- Go言語の公式ドキュメント
- TLS/SSLプロトコルに関する一般的な情報源(RFCなど)
- デジタル証明書と公開鍵暗号に関する一般的な情報源
- コミットメッセージと変更されたソースコード
- Goのコードレビューシステム (Gerrit) の該当コミットページ: https://golang.org/cl/13239053 (コミットメッセージに記載)