[インデックス 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
パッケージ内の以下のファイルと関数に影響を与えます。
-
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
)を設定します。
-
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
を返します。
-
src/pkg/crypto/tls/handshake_server.go
:serverHandshakeState.doFullHandshake()
関数内で、クライアント証明書の検証ロジックが修正されます。hs.finishedHash.hashForClientCertificate
の呼び出しにおいて、返されるハッシュIDはサーバー側では直接使用されないため、_
で無視されます。これは、サーバーがクライアントから受け取ったCertificateVerify
メッセージ内の署名とハッシュの情報を基に検証を行うためです。
-
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.PrivateKey
とrsa.PrivateKey
のケースで、finishedHash.hashForClientCertificate
関数の呼び出しが変更されました。- 以前は
digest, _ := ...
のようにハッシュIDを無視していましたが、変更後はdigest, hashFunc, hashId := ...
のように3番目の戻り値であるhashId
も取得するようになりました。 - 取得した
hashId
はcertVerify.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以前のバージョンではこのフィールドが使用されないため、問題ありません。
- MD5SHA1の場合(TLS 1.1以前のRSA署名など)、TLS 1.2の仕様では対応するハッシュIDが明示されていないため、
これらの変更により、hashForClientCertificate
関数は、TLSバージョンと署名タイプに応じて適切なハッシュIDを返すようになり、handshake_client.go
でのCertificateVerify
メッセージの正しい構築を可能にします。
関連リンク
- Go言語の
crypto/tls
パッケージのドキュメント: https://pkg.go.dev/crypto/tls - TLS 1.2 RFC (RFC 5246): https://datatracker.ietf.org/doc/html/rfc5246 (特に7.4.8. Certificate Verifyメッセージのセクション)
参考にした情報源リンク
- コミットメッセージと差分 (
git diff
) - Go言語の
crypto/tls
パッケージのソースコード - TLS 1.2の仕様に関する一般的な知識