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

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

このコミットは、Go言語の crypto/tls パッケージにおけるTLS 1.2クライアント証明書処理のバグ修正に関するものです。具体的には、TLS 1.2プロトコルにおいてクライアント証明書を送信する際に、本来含めるべき署名アルゴリズムとハッシュアルゴリズムのフィールドが省略されていた問題を解決します。

コミット

commit 6a1022a094ce66c5fe101d53d1953a5b6f91c3c0
Author: Adam Langley <agl@golang.org>
Date:   Mon Sep 16 16:39:42 2013 -0400

    crypto/tls: fix TLS 1.2 client certificates.
    
    With TLS 1.2, when sending client certificates the code was omitting
    the new (in TLS 1.2) signature and hash fields.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/13413050

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

https://github.com/golang/go/commit/6a1022a094ce66c5fe101d53d1953a5b6f91c3c0

元コミット内容

TLS 1.2において、クライアント証明書を送信する際に、コードがTLS 1.2で新しく導入された署名およびハッシュフィールドを省略していた問題を修正します。

変更の背景

TLS (Transport Layer Security) プロトコルは、インターネット上での安全な通信を確立するための標準です。TLSハンドシェイクは、クライアントとサーバーが安全な接続を確立するために必要なパラメータ(暗号スイート、鍵、証明書など)を交換するプロセスです。

クライアント証明書は、サーバーがクライアントの身元を確認するために使用されるオプションの機能です。クライアントが証明書を提示する場合、その証明書に対応する秘密鍵を所有していることを証明するために、ハンドシェイクメッセージのハッシュに署名し、CertificateVerifyメッセージとしてサーバーに送信します。

TLS 1.2では、このCertificateVerifyメッセージのフォーマットが変更されました。具体的には、署名に使用されたハッシュアルゴリズムと署名アルゴリズムのペアを明示的に示すための新しいフィールドが追加されました。これにより、サーバーはクライアントがどのアルゴリズムを使用して署名したかを正確に把握でき、プロトコルの堅牢性が向上しました。

このコミット以前のGoのcrypto/tls実装では、TLS 1.2プロトコルを使用しているにもかかわらず、クライアント証明書を送信する際にこの新しい署名およびハッシュフィールドが省略されていました。この省略は、TLS 1.2の仕様に準拠しておらず、相互運用性の問題や、場合によってはセキュリティ上の潜在的な問題(例えば、サーバーが署名を正しく検証できない、または古いアルゴリズムの使用を強制される可能性)を引き起こす可能性がありました。

このコミットは、この仕様不適合を修正し、GoのTLS実装がTLS 1.2クライアント証明書を正しく処理できるようにすることを目的としています。

前提知識の解説

  • TLS (Transport Layer Security): インターネットなどのコンピュータネットワーク上で、安全な通信を確立するための暗号プロトコル。HTTPSの基盤技術です。
  • TLSハンドシェイク: TLS接続の開始時に行われる一連のメッセージ交換。これにより、クライアントとサーバーは、使用する暗号スイート、セッション鍵、およびオプションで互いの身元(証明書)を確立します。
  • クライアント証明書: サーバーがクライアントの身元を認証するために使用するデジタル証明書。サーバー証明書とは異なり、クライアントがサーバーに対して自身の身元を証明するために提示します。
  • CertificateVerifyメッセージ: TLSハンドシェイク中にクライアントが送信するメッセージの一つ。クライアントが自身の秘密鍵を所有していることを証明するために、これまでのハンドシェイクメッセージのハッシュに署名したデータを含みます。
  • TLS 1.2: TLSプロトコルのバージョンの一つ。TLS 1.0やTLS 1.1に比べてセキュリティが強化され、新しい暗号アルゴリズムや機能が導入されました。特に、署名とハッシュアルゴリズムの明示的な指定はTLS 1.2の重要な変更点です。
  • 署名アルゴリズムとハッシュアルゴリズム:
    • ハッシュアルゴリズム (例: SHA256, SHA1): 入力データから固定長の「ハッシュ値」を生成する一方向関数。データの完全性チェックや、署名対象のデータ要約に用いられます。
    • 署名アルゴリズム (例: RSA, ECDSA): ハッシュ値と秘密鍵を用いてデジタル署名を生成し、公開鍵を用いてその署名を検証するアルゴリズム。
    • TLS 1.2では、CertificateVerifyメッセージにおいて、署名に使用されたハッシュアルゴリズムと署名アルゴリズムのペア(例: SHA256WithRSA)を明示的に指定するようになりました。これにより、サーバーはクライアントがどのアルゴリズムで署名したかを正確に認識し、適切な検証を行うことができます。

技術的詳細

この修正の核心は、TLS 1.2のCertificateVerifyメッセージの構造にあります。TLS 1.2の仕様では、このメッセージには以下の構造が含まれます。

struct {
    SignatureAndHashAlgorithm algorithm;
    opaque signature<0..2^16-1>;
} digitally-signed;

ここで、SignatureAndHashAlgorithmは、署名に使用されたハッシュアルゴリズムと署名アルゴリズムのペアを示す2バイトのフィールドです。

このコミットは、Goのcrypto/tlsパッケージ内の以下のファイルと関数に影響を与えます。

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

    • Conn.clientHandshake()関数内で、クライアント証明書を送信するロジックが修正されます。
    • certificateVerifyMsg構造体の初期化時に、TLS 1.2以上の場合にhasSignatureAndHashフィールドをtrueに設定するように変更されます。これにより、メッセージに署名とハッシュの情報を追加する準備が整います。
    • ECDSAおよびRSAの署名生成ロジックにおいて、finishedHash.hashForClientCertificateから返されるハッシュID(hashId)を取得し、それをcertVerify.signatureAndHash.hashに設定します。また、certVerify.signatureAndHash.signatureには対応する署名タイプ(signatureECDSAまたはsignatureRSA)を設定します。
  2. src/pkg/crypto/tls/prf.go:

    • finishedHash.hashForClientCertificate()関数のシグネチャが変更され、これまでのダイジェストとハッシュ関数に加えて、TLS 1.2で必要となるハッシュID(uint8型)を返すようになります。
    • TLS 1.2以上の場合、この関数はSHA256ハッシュアルゴリズムに対応するhashSHA256をハッシュIDとして返します。
    • TLS 1.1以前のECDSA署名の場合、SHA1ハッシュアルゴリズムに対応するhashSHA1をハッシュIDとして返します。
    • MD5SHA1の場合(TLS 1.1以前のRSA署名など)、TLS 1.2の仕様ではハッシュIDが明示されていないため、0を返します。
  3. src/pkg/crypto/tls/handshake_server.go:

    • serverHandshakeState.doFullHandshake()関数内で、クライアント証明書の検証ロジックが修正されます。
    • hs.finishedHash.hashForClientCertificateの呼び出しにおいて、返されるハッシュIDはサーバー側では直接使用されないため、_で無視されます。これは、サーバーがクライアントから受け取ったCertificateVerifyメッセージ内の署名とハッシュの情報を基に検証を行うためです。
  4. src/pkg/crypto/tls/handshake_client_test.go:

    • 新しいテストケースTestHandshakeClientTLS12ClientCertが追加され、TLS 1.2におけるクライアント証明書の送信が正しく行われることを検証します。
    • このテストは、実際のTLSハンドシェイクメッセージのバイト列を定義したclientTLS12ClientCertScriptを使用し、GoのTLSクライアントが生成するメッセージが期待されるTLS 1.2の仕様に準拠していることを確認します。

これらの変更により、GoのTLSクライアントはTLS 1.2の仕様に従ってCertificateVerifyメッセージを正しく構築し、署名とハッシュの情報をサーバーに送信できるようになります。

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

src/pkg/crypto/tls/handshake_client.go

--- a/src/pkg/crypto/tls/handshake_client.go
+++ b/src/pkg/crypto/tls/handshake_client.go
@@ -281,17 +281,24 @@ func (c *Conn) clientHandshake() error {
 
 	if chainToSend != nil {
 		var signed []byte
-		certVerify := new(certificateVerifyMsg)
+		certVerify := &certificateVerifyMsg{
+			hasSignatureAndHash: c.vers >= VersionTLS12,
+		}
+
 		switch key := c.config.Certificates[0].PrivateKey.(type) {
 		case *ecdsa.PrivateKey:
-\t\t\tdigest, _ := finishedHash.hashForClientCertificate(signatureECDSA)
+\t\t\tdigest, _, hashId := finishedHash.hashForClientCertificate(signatureECDSA)
 			r, s, err := ecdsa.Sign(c.config.rand(), key, digest)
 			if err == nil {
 				signed, err = asn1.Marshal(ecdsaSignature{r, s})
 			}
+\t\t\tcertVerify.signatureAndHash.signature = signatureECDSA
+\t\t\tcertVerify.signatureAndHash.hash = hashId
 		case *rsa.PrivateKey:
-\t\t\tdigest, hashFunc := finishedHash.hashForClientCertificate(signatureRSA)
+\t\t\tdigest, hashFunc, hashId := finishedHash.hashForClientCertificate(signatureRSA)
 			signed, err = rsa.SignPKCS1v15(c.config.rand(), key, hashFunc, digest)
+\t\t\tcertVerify.signatureAndHash.signature = signatureRSA
+\t\t\tcertVerify.signatureAndHash.hash = hashId
 		default:
 			err = errors.New("unknown private key type")
 		}

src/pkg/crypto/tls/prf.go

--- a/src/pkg/crypto/tls/prf.go
+++ b/src/pkg/crypto/tls/prf.go
@@ -272,20 +272,20 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte {
 	return out
 }
 
-// hashForClientCertificate returns a digest and hash function identifier
-// suitable for signing by a TLS client certificate.
-func (h finishedHash) hashForClientCertificate(sigType uint8) ([]byte, crypto.Hash) {
+// hashForClientCertificate returns a digest, hash function, and TLS 1.2 hash
+// id suitable for signing by a TLS client certificate.
+func (h finishedHash) hashForClientCertificate(sigType uint8) ([]byte, crypto.Hash, uint8) {
 	if h.version >= VersionTLS12 {
 		digest := h.server.Sum(nil)
-\t\treturn digest, crypto.SHA256
+\t\treturn digest, crypto.SHA256, hashSHA256
 	}
 	if sigType == signatureECDSA {
 		digest := h.server.Sum(nil)
-\t\treturn digest, crypto.SHA1
+\t\treturn digest, crypto.SHA1, hashSHA1
 	}
 
 	digest := make([]byte, 0, 36)
 	digest = h.serverMD5.Sum(digest)
 	digest = h.server.Sum(digest)
-\treturn digest, crypto.MD5SHA1
+\treturn digest, crypto.MD5SHA1, 0 /* not specified in TLS 1.2. */
 }

コアとなるコードの解説

handshake_client.go の変更点

  • certificateVerifyMsg の初期化:

    • 以前はnew(certificateVerifyMsg)で初期化されていましたが、変更後は&certificateVerifyMsg{hasSignatureAndHash: c.vers >= VersionTLS12,}と明示的に初期化されます。
    • hasSignatureAndHashフィールドは、現在のTLSバージョンがVersionTLS12(TLS 1.2)以上である場合にtrueに設定されます。これにより、TLS 1.2以降のバージョンでは、CertificateVerifyメッセージに署名とハッシュのアルゴリズム情報を含める必要があることを示します。
  • 署名生成とフィールド設定:

    • ecdsa.PrivateKeyrsa.PrivateKeyのケースで、finishedHash.hashForClientCertificate関数の呼び出しが変更されました。
    • 以前はdigest, _ := ...のようにハッシュIDを無視していましたが、変更後はdigest, hashFunc, hashId := ...のように3番目の戻り値であるhashIdも取得するようになりました。
    • 取得したhashIdcertVerify.signatureAndHash.hashに設定されます。
    • また、certVerify.signatureAndHash.signatureには、それぞれsignatureECDSAまたはsignatureRSAが設定されます。
    • これらの変更により、クライアントはTLS 1.2の仕様に従って、署名に使用したアルゴリズム(ECDSAまたはRSA)とハッシュアルゴリズム(SHA256など)の情報をCertificateVerifyメッセージに含めることができるようになります。

prf.go の変更点

  • hashForClientCertificate 関数のシグネチャ変更:

    • この関数の戻り値にuint8型のhashIdが追加されました。これにより、この関数は署名に使用するダイジェスト、ハッシュ関数、そしてTLS 1.2で必要となるハッシュIDを返すようになります。
  • TLS 1.2 のハッシュID設定:

    • h.version >= VersionTLS12の場合、以前はreturn digest, crypto.SHA256でしたが、変更後はreturn digest, crypto.SHA256, hashSHA256となります。hashSHA256はTLS 1.2の仕様で定義されたSHA256ハッシュアルゴリズムのIDです。
  • ECDSA 署名のハッシュID設定:

    • sigType == signatureECDSAの場合(TLS 1.1以前のECDSA署名)、以前はreturn digest, crypto.SHA1でしたが、変更後はreturn digest, crypto.SHA1, hashSHA1となります。hashSHA1はTLS 1.2の仕様で定義されたSHA1ハッシュアルゴリズムのIDです。
  • MD5SHA1 のハッシュID設定:

    • MD5SHA1の場合(TLS 1.1以前のRSA署名など)、TLS 1.2の仕様では対応するハッシュIDが明示されていないため、return digest, crypto.MD5SHA1, 0 /* not specified in TLS 1.2. */と、0が返されます。これは、TLS 1.2以前のバージョンではこのフィールドが使用されないため、問題ありません。

これらの変更により、hashForClientCertificate関数は、TLSバージョンと署名タイプに応じて適切なハッシュIDを返すようになり、handshake_client.goでのCertificateVerifyメッセージの正しい構築を可能にします。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (git diff)
  • Go言語のcrypto/tlsパッケージのソースコード
  • TLS 1.2の仕様に関する一般的な知識