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

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

このコミットは、Go言語のcrypto/tlsパッケージにおいて、TLS 1.2ハンドシェイク時にRSA+SHA1署名アルゴリズムのサポートをアドバタイズするように変更を加えるものです。これにより、SHA256サポートが必須であるにもかかわらず、SHA1サポートを提供しないハンドシェイクを中断する一部のサーバーとの互換性の問題が解決されます。具体的には、ServerKeyExchangeメッセージの署名にSHA1を使用できるようになりますが、クライアント証明書の署名にはSHA1は追加されません。

コミット

commit efed6f99d262d15e0863950843cd6089d9034e03
Author: Adam Langley <agl@golang.org>
Date:   Mon Oct 21 16:35:09 2013 -0400

    crypto/tls: advertise support for RSA+SHA1 in TLS 1.2 handshake.
    
    Despite SHA256 support being required for TLS 1.2 handshakes, some
    servers are aborting handshakes that don't offer SHA1 support.
    
    This change adds support for signing TLS 1.2 ServerKeyExchange messages
    with SHA1. It does not add support for signing TLS 1.2 client
    certificates with SHA1 as that would require the handshake to be
    buffered.
    
    Fixes #6618.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/15650043

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

https://github.com/golang/go/commit/efed6f99d262d15e0863950843cd6089d9034e03

元コミット内容

crypto/tls: advertise support for RSA+SHA1 in TLS 1.2 handshake.

Despite SHA256 support being required for TLS 1.2 handshakes, some
servers are aborting handshakes that don't offer SHA1 support.

This change adds support for signing TLS 1.2 ServerKeyExchange messages
with SHA1. It does not add support for signing TLS 1.2 client
certificates with SHA1 as that would require the handshake to be
buffered.

Fixes #6618.

変更の背景

TLS 1.2 (Transport Layer Security version 1.2) の仕様では、ハンドシェイクにおける署名アルゴリズムとしてSHA256以上のハッシュ関数を使用することが推奨され、事実上必須とされています。しかし、現実世界の一部のTLSサーバー実装には、この仕様に厳密に従わず、クライアントがSHA1ベースの署名アルゴリズムを提示しない場合にハンドシェイクを異常終了させてしまうものがありました。

Go言語のcrypto/tlsパッケージは、デフォルトでTLS 1.2の仕様に準拠し、SHA256ベースの署名アルゴリズムを優先的にアドバタイズしていました。このため、前述のような非標準的なサーバーとの間でTLS接続を確立できないという互換性の問題が発生していました。このコミットは、このような特定のサーバーとの相互運用性を確保するために、TLS 1.2ハンドシェイクのServerKeyExchangeメッセージにおいて、RSA+SHA1署名アルゴリズムのサポートを明示的にアドバタイズするように変更を加えるものです。

ただし、クライアント証明書の署名に関しては、SHA1サポートを追加するとハンドシェイクのバッファリングが必要になるという実装上の複雑さから、この変更の範囲外とされています。

前提知識の解説

TLS (Transport Layer Security)

TLSは、インターネット上でデータを安全にやり取りするための暗号化プロトコルです。クライアントとサーバー間の通信を認証し、データの機密性、完全性、および真正性を保証します。TLSハンドシェイクは、クライアントとサーバーが安全な通信チャネルを確立するために必要なパラメータ(プロトコルバージョン、暗号スイート、証明書など)をネゴシエートするプロセスです。

TLS 1.2

TLS 1.2は、2008年にRFC 5246として定義されたTLSプロトコルのバージョンです。以前のバージョンに比べてセキュリティが強化されており、特にハッシュアルゴリズムと署名アルゴリズムの選択肢が改善されました。TLS 1.2では、ハンドシェイクの署名にSHA256以上のハッシュ関数を使用することが推奨されています。

ハッシュ関数 (SHA1, SHA256)

ハッシュ関数は、任意の長さの入力データから固定長の出力(ハッシュ値またはダイジェスト)を生成する一方向関数です。

  • SHA1 (Secure Hash Algorithm 1): 160ビットのハッシュ値を生成します。かつて広く使用されましたが、衝突攻撃(異なる入力から同じハッシュ値が生成されること)の脆弱性が発見されたため、現在では非推奨とされています。
  • SHA256 (Secure Hash Algorithm 256): 256ビットのハッシュ値を生成します。SHA-2ファミリーの一部であり、SHA1よりも強力で、現在でも広く使用されているハッシュ関数です。

署名アルゴリズム (RSA, ECDSA)

デジタル署名は、データの真正性と完全性を保証するために使用されます。公開鍵暗号方式に基づいており、秘密鍵でデータを署名し、対応する公開鍵で署名を検証します。

  • RSA (Rivest–Shamir–Adleman): 最も広く使用されている公開鍵暗号アルゴリズムの一つで、デジタル署名にも利用されます。
  • ECDSA (Elliptic Curve Digital Signature Algorithm): 楕円曲線暗号に基づくデジタル署名アルゴリズムです。RSAよりも短い鍵長で同等のセキュリティ強度を提供できるため、モバイルデバイスなどリソースが限られた環境で有利です。

ServerKeyExchangeメッセージ

TLSハンドシェイク中にサーバーが送信するメッセージの一つです。DHE (Diffie-Hellman Ephemeral) や ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) などの鍵交換アルゴリズムを使用する場合に、サーバーの公開鍵パラメータが含まれます。このメッセージは、サーバーの秘密鍵で署名され、クライアントはサーバーの証明書に含まれる公開鍵を使用してその署名を検証します。

ClientHelloメッセージ

TLSハンドシェイクの最初のメッセージで、クライアントがサーバーに送信します。クライアントがサポートするTLSバージョン、暗号スイート、圧縮方式、拡張機能などをサーバーに通知します。

CertificateRequestメッセージ

サーバーがクライアントにクライアント証明書を要求する際に送信するメッセージです。このメッセージには、サーバーが受け入れる証明書の種類や、クライアント証明書の署名に利用できる署名アルゴリズムのリスト(signature_algorithms拡張)が含まれることがあります。

signature_algorithms拡張

TLS 1.2で導入されたClientHelloおよびCertificateRequestメッセージの拡張機能です。クライアントまたはサーバーがサポートするハッシュアルゴリズムと署名アルゴリズムのペアのリストを通知するために使用されます。これにより、より強力なアルゴリズムを優先的に使用したり、特定の脆弱なアルゴリズムを回避したりすることが可能になります。

技術的詳細

このコミットの主要な変更点は、GoのTLS実装がTLS 1.2ハンドシェイクにおいて、ServerKeyExchangeメッセージの署名にSHA1ハッシュアルゴリズムを許可するように拡張されたことです。

具体的には、以下の点が変更されています。

  1. supportedSKXSignatureAlgorithmsの導入: src/pkg/crypto/tls/common.goにおいて、supportedSignatureAlgorithmsという既存の変数から、ServerKeyExchangeメッセージの署名に利用可能なアルゴリズムのリストを定義するsupportedSKXSignatureAlgorithmsという新しい変数が分離されました。

    • supportedSKXSignatureAlgorithmsには、SHA256+RSA, SHA256+ECDSAに加えて、SHA1+RSASHA1+ECDSAが追加されました。
    • supportedClientCertSignatureAlgorithmsという新しい変数も導入され、これはクライアント証明書の署名に利用可能なアルゴリズムを定義します。このリストにはSHA1は含まれていません。
  2. ClientHelloでのアドバタイズ: src/pkg/crypto/tls/handshake_client.goClientHello生成ロジックが変更され、TLS 1.2以上のバージョンでは、クライアントがサポートする署名アルゴリズムとしてsupportedSKXSignatureAlgorithmsのリストをhello.signatureAndHashesに設定するようになりました。これにより、クライアントはServerKeyExchangeの署名にSHA1も受け入れることをサーバーに通知します。

  3. ServerKeyExchangeの署名ロジックの変更: src/pkg/crypto/tls/key_agreement.gohashForServerKeyExchange関数が拡張されました。

    • この関数は、TLS 1.2の場合に、クライアントが提示したsignature_algorithms拡張に基づいて、実際に使用するハッシュ関数(SHA1またはSHA256)を選択できるようになりました。
    • 新しい関数pickTLS12HashForSignatureが追加され、クライアントがサポートする署名アルゴリズムのリスト(clientSignatureAndHashes)と署名タイプ(RSAまたはECDSA)に基づいて、ServerKeyExchangeの署名に使用する最適なハッシュアルゴリズム(SHA1またはSHA256)を決定します。クライアントがsignature_algorithms拡張を提供しない場合、RFC 5246の規定に従いSHA1がデフォルトとして選択されます。
  4. ServerKeyExchangeの検証ロジックの変更: src/pkg/crypto/tls/key_agreement.goprocessServerKeyExchange関数も更新され、受信したServerKeyExchangeメッセージの署名を検証する際に、TLS 1.2の場合に署名に含まれるハッシュアルゴリズム識別子を正しく処理するようになりました。

これらの変更により、GoのTLSクライアントは、TLS 1.2ハンドシェイクにおいて、SHA1ベースのServerKeyExchange署名を要求する一部のサーバーとも正常に通信できるようになります。

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

src/pkg/crypto/tls/common.go

--- a/src/pkg/crypto/tls/common.go
+++ b/src/pkg/crypto/tls/common.go
@@ -126,10 +126,19 @@ type signatureAndHash struct {
 	hash, signature uint8
 }
 
-// supportedSignatureAlgorithms contains the signature and hash algorithms that
-// the code can advertise as supported both in a TLS 1.2 ClientHello and
+// supportedSKXSignatureAlgorithms contains the signature and hash algorithms
+// that the code advertises as supported in a TLS 1.2 ClientHello.
+var supportedSKXSignatureAlgorithms = []signatureAndHash{
+	{hashSHA256, signatureRSA},
+	{hashSHA256, signatureECDSA},
+	{hashSHA1, signatureRSA},
+	{hashSHA1, signatureECDSA},
+}
+
+// supportedClientCertSignatureAlgorithms contains the signature and hash
+// algorithms that the code advertises as supported in a TLS 1.2
 // CertificateRequest.
-var supportedSignatureAlgorithms = []signatureAndHash{
+var supportedClientCertSignatureAlgorithms = []signatureAndHash{
 	{hashSHA256, signatureRSA},
 	{hashSHA256, signatureECDSA},
 }

src/pkg/crypto/tls/handshake_client.go

--- a/src/pkg/crypto/tls/handshake_client.go
+++ b/src/pkg/crypto/tls/handshake_client.go
@@ -63,7 +63,7 @@ NextCipherSuite:
 	}
 
 	if hello.vers >= VersionTLS12 {
-		hello.signatureAndHashes = supportedSignatureAlgorithms
+		hello.signatureAndHashes = supportedSKXSignatureAlgorithms
 	}
 
 	c.writeRecord(recordTypeHandshake, hello.marshal())

src/pkg/crypto/tls/handshake_server.go

--- a/src/pkg/crypto/tls/handshake_server.go
+++ b/src/pkg/crypto/tls/handshake_server.go
@@ -318,7 +318,7 @@ func (hs *serverHandshakeState) doFullHandshake() error {
 		}
 		if c.vers >= VersionTLS12 {
 			certReq.hasSignatureAndHash = true
-			certReq.signatureAndHashes = supportedSignatureAlgorithms
+			certReq.signatureAndHashes = supportedClientCertSignatureAlgorithms
 		}
 
 		// An empty list of certificateAuthorities signals to

src/pkg/crypto/tls/key_agreement.go

--- a/src/pkg/crypto/tls/key_agreement.go
+++ b/src/pkg/crypto/tls/key_agreement.go
@@ -117,15 +117,47 @@ func sha256Hash(slices [][]byte) []byte {
 }
 
 // hashForServerKeyExchange hashes the given slices and returns their digest
-// and the identifier of the hash function used.
-func hashForServerKeyExchange(sigType uint8, version uint16, slices ...[]byte) ([]byte, crypto.Hash) {
+// and the identifier of the hash function used. The hashFunc argument is only
+// used for >= TLS 1.2 and precisely identifies the hash function to use.
+func hashForServerKeyExchange(sigType, hashFunc uint8, version uint16, slices ...[]byte) ([]byte, crypto.Hash, error) {
 	if version >= VersionTLS12 {
-		return sha256Hash(slices), crypto.SHA256
+		switch hashFunc {
+		case hashSHA256:
+			return sha256Hash(slices), crypto.SHA256, nil
+		case hashSHA1:
+			return sha1Hash(slices), crypto.SHA1, nil
+		default:
+			return nil, crypto.Hash(0), errors.New("tls: unknown hash function used by peer")
+		}
 	}
 	if sigType == signatureECDSA {
-		return sha1Hash(slices), crypto.SHA1
+		return sha1Hash(slices), crypto.SHA1, nil
 	}
-	return md5SHA1Hash(slices), crypto.MD5SHA1
+	return md5SHA1Hash(slices), crypto.MD5SHA1, nil
+}
+
+// pickTLS12HashForSignature returns a TLS 1.2 hash identifier for signing a
+// ServerKeyExchange given the signature type being used and the client's
+// advertized list of supported signature and hash combinations.
+func pickTLS12HashForSignature(sigType uint8, clientSignatureAndHashes []signatureAndHash) (uint8, error) {
+	if len(clientSignatureAndHashes) == 0 {
+		// If the client didn't specify any signature_algorithms
+		// extension then we can assume that it supports SHA1. See
+		// http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
+		return hashSHA1, nil
+	}
+
+	for _, sigAndHash := range clientSignatureAndHashes {
+		if sigAndHash.signature != sigType {
+			continue
+		}
+		switch sigAndHash.hash {
+		case hashSHA1, hashSHA256:
+			return sigAndHash.hash, nil
+		}
+	}
+
+	return 0, errors.New("tls: client doesn't support any common hash functions")
 }
 
 // ecdheRSAKeyAgreement implements a TLS key agreement where the server
@@ -181,7 +213,17 @@ Curve:
 	serverECDHParams[3] = byte(len(ecdhePublic))
 	copy(serverECDHParams[4:], ecdhePublic)
 
-	digest, hashFunc := hashForServerKeyExchange(ka.sigType, ka.version, clientHello.random, hello.random, serverECDHParams)
+	var tls12HashId uint8
+	if ka.version >= VersionTLS12 {
+		if tls12HashId, err = pickTLS12HashForSignature(ka.sigType, clientHello.signatureAndHashes); err != nil {
+			return nil, err
+		}
+	}
+
+	digest, hashFunc, err := hashForServerKeyExchange(ka.sigType, tls12HashId, ka.version, clientHello.random, hello.random, serverECDHParams)
+	if err != nil {
+		return nil, err
+	}
 	var sig []byte
 	switch ka.sigType {
 	case signatureECDSA:
@@ -216,7 +258,7 @@ Curve:
 	copy(skx.key, serverECDHParams)
 	k := skx.key[len(serverECDHParams):]
 	if ka.version >= VersionTLS12 {
-		k[0] = hashSHA256
+		k[0] = tls12HashId
 		k[1] = ka.sigType
 		k = k[2:]
 	}
@@ -279,9 +321,16 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
 	if len(sig) < 2 {
 		return errServerKeyExchange
 	}
+\n+	var tls12HashId uint8
 	if ka.version >= VersionTLS12 {
-\t\t// ignore SignatureAndHashAlgorithm
-\t\tsig = sig[2:]
+\t\t// handle SignatureAndHashAlgorithm
+\t\tvar sigAndHash []uint8
+\t\tsigAndHash, sig = sig[:2], sig[2:]
+\t\tif sigAndHash[1] != ka.sigType {
+\t\t\treturn errServerKeyExchange
+\t\t}
+\t\ttls12HashId = sigAndHash[0]
 	\tif len(sig) < 2 {
 	\t\treturn errServerKeyExchange
 	\t}
@@ -292,7 +351,10 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell
 	}
 	sig = sig[2:]
 
-\tdigest, hashFunc := hashForServerKeyExchange(ka.sigType, ka.version, clientHello.random, serverHello.random, serverECDHParams)
+\tdigest, hashFunc, err := hashForServerKeyExchange(ka.sigType, tls12HashId, ka.version, clientHello.random, serverHello.random, serverECDHParams)
+\tif err != nil {
+\t\treturn err
+\t}
 	switch ka.sigType {
 	case signatureECDSA:
 		pubKey, ok := cert.PublicKey.(*ecdsa.PublicKey)

コアとなるコードの解説

src/pkg/crypto/tls/common.go

  • supportedSKXSignatureAlgorithms: この新しいグローバル変数は、TLS 1.2ハンドシェイクにおいて、GoのTLSクライアントがServerKeyExchangeメッセージの署名に利用可能であるとアドバタイズするハッシュ・署名アルゴリズムのペアを定義します。以前はsupportedSignatureAlgorithmsがクライアントHelloとCertificateRequestの両方で使用されていましたが、この変更によりServerKeyExchangeの署名に特化したリストが導入されました。特に、SHA1+RSAとSHA1+ECDSAが追加されており、これが本コミットの目的であるSHA1サポートのアドバタイズを実現しています。
  • supportedClientCertSignatureAlgorithms: クライアント証明書の署名に利用可能なアルゴリズムを定義する新しい変数です。このリストにはSHA1は含まれていません。これは、コミットメッセージにある「クライアント証明書の署名にSHA1サポートを追加すると、ハンドシェイクのバッファリングが必要になる」という制約を反映しています。

src/pkg/crypto/tls/handshake_client.go

  • ClientHelloメッセージの生成時に、TLS 1.2以上のバージョンであれば、hello.signatureAndHashesに新しく定義されたsupportedSKXSignatureAlgorithmsを設定するように変更されました。これにより、GoのTLSクライアントは、ServerKeyExchangeの署名にSHA1も受け入れることをサーバーに明示的に通知します。

src/pkg/crypto/tls/handshake_server.go

  • サーバー側でCertificateRequestメッセージを生成する際に、クライアント証明書の署名アルゴリズムとしてsupportedClientCertSignatureAlgorithmsを使用するように変更されました。これにより、サーバーはクライアント証明書にSHA1ベースの署名を要求しないようになります。

src/pkg/crypto/tls/key_agreement.go

  • hashForServerKeyExchange関数の変更:
    • この関数は、ServerKeyExchangeメッセージの署名に使用するハッシュ値を計算します。変更前はTLS 1.2の場合に無条件でSHA256を使用していましたが、変更後はhashFuncという新しい引数を受け取るようになりました。
    • TLS 1.2の場合、このhashFuncの値に基づいてSHA256またはSHA1のどちらのハッシュ関数を使用するかを動的に選択します。これにより、SHA1を要求するサーバーとの互換性が向上します。
    • 返り値にerrorが追加され、未知のハッシュ関数が指定された場合にエラーを返すようになりました。
  • pickTLS12HashForSignature関数の追加:
    • この新しい関数は、ServerKeyExchangeの署名に使用するTLS 1.2のハッシュ識別子(SHA1またはSHA256)を決定します。
    • クライアントが提供するsignature_algorithms拡張のリスト(clientSignatureAndHashes)を調べ、サーバーが使用する署名タイプ(RSAまたはECDSA)と互換性のあるハッシュアルゴリズムを探します。
    • SHA256が利用可能であればそれを優先しますが、SHA1も許容します。
    • もしクライアントがsignature_algorithms拡張を提供しない場合(これはTLS 1.2の仕様では許可されていますが、推奨されません)、RFC 5246の規定に従い、デフォルトでSHA1が選択されます。これは、古いクライアントや一部のサーバーがこの拡張を送信しない場合に、後方互換性を確保するための重要なロジックです。
  • ecdheRSAKeyAgreement.acquireServerKeyExchangeおよびecdheKeyAgreement.processServerKeyExchangeの変更:
    • ServerKeyExchangeメッセージの生成と検証のロジックが更新され、pickTLS12HashForSignature関数を使用して適切なハッシュアルゴリズムを選択し、そのハッシュアルゴリズム識別子を署名データに含める(または署名データから読み取る)ようになりました。これにより、TLS 1.2ハンドシェイクにおけるSHA1署名の送受信が正しく処理されます。

これらの変更により、GoのTLS実装は、TLS 1.2の厳格な要件と、一部のレガシーなサーバー実装との間のギャップを埋め、より広範な相互運用性を実現しています。

関連リンク

参考にした情報源リンク