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

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

このコミットは、Go言語の crypto/tls パッケージにAES-GCM (Galois/Counter Mode) 暗号スイートのサポートを追加するものです。TLS (Transport Layer Security) における暗号スイートの選択は、通信のセキュリティとパフォーマンスに直接影響します。この変更は、既存の暗号スイートが抱える既知の脆弱性や構造上の問題を解決し、より堅牢な暗号化メカニズムを提供することを目的としています。

コミット

commit 2fe9a5a3e826d8b2dc45652e1b5d1c23eeeb428b
Author: Adam Langley <agl@golang.org>
Date:   Thu Aug 29 17:18:59 2013 -0400

    crypto/tls: support AES-GCM.
    
    AES-GCM is the only current TLS ciphersuite that doesn't have
    cryptographic weaknesses (RC4), nor major construction issues (CBC mode
    ciphers) and has some deployment (i.e. not-CCM).
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/13249044

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

https://github.com/golang/go/commit/2fe9a5a3e826d8b2dc4565e1b5d1c23eeeb428b

元コミット内容

crypto/tls: support AES-GCM.

AES-GCM is the only current TLS ciphersuite that doesn't have
cryptographic weaknesses (RC4), nor major construction issues (CBC mode
ciphers) and has some deployment (i.e. not-CCM).

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13249044

変更の背景

このコミットの主な背景には、当時のTLS暗号スイートが抱えていたセキュリティ上の課題があります。

  1. RC4の脆弱性: RC4 (Rivest Cipher 4) は、かつて広く使われていたストリーム暗号ですが、複数の攻撃手法(特にRC4 Bar Mitzvah攻撃など)によって深刻な脆弱性が指摘されていました。これにより、RC4を使用するTLS接続は盗聴のリスクに晒される可能性がありました。
  2. CBCモード暗号の構造上の問題: CBC (Cipher Block Chaining) モードは、ブロック暗号の一般的な運用モードですが、TLSにおけるCBCモードの実装にはLucky Thirteen攻撃などのタイミング攻撃に対する脆弱性が存在しました。これはパディングオラクル攻撃の一種で、復号エラーのタイミング情報から平文を推測される可能性があります。
  3. より安全な代替の必要性: 上記の脆弱性に対処するため、より安全で堅牢な暗号化モードへの移行が求められていました。AES-GCMは、認証付き暗号 (Authenticated Encryption with Associated Data, AEAD) の一種であり、データの機密性(暗号化)と完全性・認証(MAC)を同時に提供するため、これらの問題を解決する有力な候補でした。
  4. CCMの限定的な普及: AES-CCM (Counter with CBC-MAC) もAEADの一種ですが、AES-GCMに比べて普及度が低く、相互運用性の観点からAES-GCMが優先されました。

このコミットは、Goの crypto/tls ライブラリが、これらの既知の脆弱性を持つ暗号スイートから、より安全なAES-GCMへの移行をサポートすることで、TLS通信のセキュリティを向上させることを目的としています。

前提知識の解説

TLS (Transport Layer Security)

TLSは、インターネット上での通信を暗号化し、認証するためのプロトコルです。ウェブブラウジング(HTTPS)、電子メール、VoIPなど、様々なアプリケーションで利用されています。TLSは、ハンドシェイクプロトコルとレコードプロトコルという2つの主要な層で構成されています。

  • ハンドシェイクプロトコル: クライアントとサーバーが通信を開始する際に、互いの認証、暗号スイートのネゴシエーション、セッションキーの生成などを行います。
  • レコードプロトコル: ハンドシェイクで確立されたセッションキーと暗号スイートを使用して、実際のアプリケーションデータを暗号化・復号化し、完全性を保護します。

暗号スイート (Cipher Suite)

暗号スイートは、TLS接続で使用される暗号化アルゴリズムの組み合わせを定義するものです。通常、以下の要素を含みます。

  • 鍵交換アルゴリズム (Key Exchange Algorithm): サーバーとクライアントが共有秘密鍵を安全に確立する方法(例: RSA, ECDHE)。
  • 認証アルゴリズム (Authentication Algorithm): サーバー(およびオプションでクライアント)の身元を検証する方法(例: RSA, ECDSA)。
  • バルク暗号化アルゴリズム (Bulk Encryption Algorithm): 実際のデータを暗号化する方法(例: AES, RC4, 3DES)。
  • メッセージ認証コード (MAC) アルゴリズム (Message Authentication Code Algorithm): データの完全性と認証を保証する方法(例: SHA1, SHA256)。

AES-GCM (Advanced Encryption Standard - Galois/Counter Mode)

AES-GCMは、AESブロック暗号をGCMモードで運用する認証付き暗号 (AEAD) です。

  • AES (Advanced Encryption Standard): 広く採用されているブロック暗号標準です。
  • GCM (Galois/Counter Mode): カウンターモード (CTR) とGMAC (Galois Message Authentication Code) を組み合わせた運用モードです。
    • CTRモード: ブロック暗号をストリーム暗号のように動作させ、並列処理が可能です。
    • GMAC: 認証タグを生成し、データの完全性と認証を提供します。
  • AEAD (Authenticated Encryption with Associated Data): データの機密性(暗号化)と完全性・認証を同時に提供する暗号化モードです。これにより、暗号文の改ざんや偽造を検出できます。GCMは、暗号化と認証を単一のパスで処理できるため、効率的です。

RC4 (Rivest Cipher 4)

RC4は、ストリーム暗号の一種で、かつてTLSで広く使用されていました。しかし、鍵スケジューリングアルゴリズムの脆弱性や、特定の鍵ストリームの繰り返しパターンにより、統計的な偏りが生じ、攻撃者が平文を推測できる可能性が指摘されました。

CBC (Cipher Block Chaining) モード

CBCは、ブロック暗号の運用モードの一つで、前のブロックの暗号文が次のブロックの平文とXORされることで連鎖的に暗号化されます。TLSにおけるCBCモードの実装では、パディングの処理方法に起因するタイミング攻撃(Lucky Thirteen攻撃など)の脆弱性が発見されました。攻撃者は、復号エラーのタイミングの違いを観測することで、パディングの正当性を判断し、最終的に平文を推測することが可能になります。

ノンス (Nonce)

ノンス (Number used once) は、暗号化操作において一度だけ使用されるランダムまたは擬似ランダムな値です。GCMのようなAEADモードでは、各暗号化操作でユニークなノンスを使用することが極めて重要です。同じ鍵とノンスのペアで複数のメッセージを暗号化すると、セキュリティが著しく損なわれる可能性があります。TLSのAES-GCMでは、ノンスの一部としてシーケンス番号が使用されます。

技術的詳細

このコミットは、Goの crypto/tls パッケージにAES-GCM暗号スイートを統合するために、以下の主要な変更を導入しています。

  1. cipherSuite 構造体の拡張:

    • src/pkg/crypto/tls/cipher_suites.go 内の cipherSuite 構造体に、AEAD暗号スイートをサポートするための新しいフィールド aead func(key, fixedNonce []byte) cipher.AEAD が追加されました。これにより、従来のストリーム暗号 (cipher) やブロック暗号 (mac) とは異なるAEADインターフェースを扱うことができるようになります。
    • 既存の暗号スイート定義(RC4, CBCモード)ではこの aead フィールドは nil に設定され、新しく追加されるAES-GCMスイートでのみ使用されます。
  2. AES-GCM暗号スイートの追加:

    • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) と TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b) の2つの新しいAES-GCM暗号スイートが cipherSuites リストに追加されました。これらは、鍵長、MAC長、IV長が適切に設定され、aeadAESGCM 関数が指定されています。
    • これらの新しい暗号スイートは、セキュリティ上の推奨順序に従って、既存のRC4やCBCモードのスイートよりも上位に配置されています。
  3. fixedNonceAEAD 構造体と aeadAESGCM 関数の導入:

    • src/pkg/crypto/tls/cipher_suites.gofixedNonceAEAD という新しい構造体が定義されました。これは cipher.AEAD インターフェースをラップし、TLSのAES-GCMで必要とされる固定ノンス(implicit nonce)とシーケンス番号(explicit nonce)の組み合わせを処理します。
    • TLS 1.2のAES-GCMでは、ノンスは12バイトで構成され、そのうち4バイトが固定(implicit nonce)、残りの8バイトが明示的(explicit nonce)です。この明示的なノンスとしてTLSレコードのシーケンス番号が使用されます。fixedNonceAEAD は、この固定ノンス部分を保持し、シーケンス番号と組み合わせて完全なノンスを生成します。
    • aeadAESGCM 関数は、AES-GCMの cipher.AEAD インターフェースを生成するファクトリ関数です。aes.NewCiphercipher.NewGCM を使用してAES-GCMインスタンスを作成し、それを fixedNonceAEAD でラップして返します。
  4. conn.go における暗号化/復号化ロジックの変更:

    • src/pkg/crypto/tls/conn.gohalfConn.decrypt および halfConn.encrypt メソッドが、AEAD暗号スイートを処理できるように修正されました。
    • AEADの場合、explicitIVLen (明示的IVの長さ) が8バイトに設定されます。これは、TLSのAES-GCMでシーケンス番号がノンスとして使用される部分に対応します。
    • AEADの復号化 (decrypt) では、ペイロードから明示的ノンス(シーケンス番号)を抽出し、追加認証データ (Additional Data) を構築して c.Open を呼び出します。追加認証データは、シーケンス番号、レコードヘッダの一部、平文の長さから構成されます。
    • AEADの暗号化 (encrypt) では、明示的ノンス(シーケンス番号)と追加認証データを構築し、c.Seal を呼び出します。
    • 従来のMAC計算とパディング処理は、AEADを使用しない暗号スイートの場合にのみ実行されるように条件分岐が追加されました。AEADはMACとパディングを内部で処理するため、別途MAC計算は不要です。
    • シーケンス番号のインクリメント (hc.incSeq()) の位置が調整され、AEADと非AEADの両方で適切に処理されるようになりました。
  5. ハンドシェイク処理の変更:

    • src/pkg/crypto/tls/handshake_client.gosrc/pkg/crypto/tls/handshake_server.go において、クライアントとサーバーのハンドシェイク中に暗号スイートが選択された際、そのスイートがAEADであるかどうかに応じて、suite.ciphersuite.mac を使用するか、または suite.aead を使用するかのロジックが追加されました。
  6. テストケースの追加:

    • src/pkg/crypto/tls/handshake_server_test.goTestAESGCM という新しいテストケースが追加されました。このテストは、AES-GCM暗号スイートを使用したTLS 1.2接続が正しく確立され、データが暗号化・復号化されることを検証します。これには、クライアントとサーバー間の実際のTLSハンドシェイクとデータ交換をシミュレートするためのバイト列 (aesGCMServerScript) が含まれています。

これらの変更により、Goの crypto/tls パッケージは、よりセキュアなAES-GCM暗号スイートをサポートし、TLS通信のセキュリティと堅牢性を向上させました。特に、RC4やCBCモードの既知の脆弱性に対する対策が講じられています。

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

src/pkg/crypto/tls/cipher_suites.go

--- a/src/pkg/crypto/tls/cipher_suites.go
+++ b/src/pkg/crypto/tls/cipher_suites.go
@@ -49,22 +49,25 @@ type cipherSuite struct {
 	elliptic bool
 	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_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},
-	{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, true, cipher3DES, macSHA1},
-	{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, false, cipher3DES, macSHA1},
+	{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},
 }
 
 func cipherRC4(key, iv []byte, isRead bool) interface{} {
@@ -106,6 +109,46 @@ type macFunction interface {
 	MAC(digestBuf, seq, header, data []byte) []byte
 }
 
+// fixedNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to
+// each call.
+type fixedNonceAEAD struct {
+	// sealNonce and openNonce are buffers where the larger nonce will be
+	// constructed. Since a seal and open operation may be running
+	// concurrently, there is a separate buffer for each.
+	sealNonce, openNonce []byte
+	aead                 cipher.AEAD
+}
+
+func (f *fixedNonceAEAD) NonceSize() int { return 8 }
+func (f *fixedNonceAEAD) Overhead() int  { return f.aead.Overhead() }
+
+func (f *fixedNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
+	copy(f.sealNonce[len(f.sealNonce)-8:], nonce)
+	return f.aead.Seal(out, f.sealNonce, plaintext, additionalData)
+}
+
+func (f *fixedNonceAEAD) Open(out, nonce, plaintext, additionalData []byte) ([]byte, error) {
+	copy(f.openNonce[len(f.openNonce)-8:], nonce)
+	return f.aead.Open(out, f.openNonce, plaintext, additionalData)
+}
+
+func aeadAESGCM(key, fixedNonce []byte) cipher.AEAD {
+	aes, err := aes.NewCipher(key)
+	if err != nil {
+		panic(err)
+	}
+	aead, err := cipher.NewGCM(aes)
+	if err != nil {
+		panic(err)
+	}
+
+	nonce1, nonce2 := make([]byte, 12), make([]byte, 12)
+	copy(nonce1, fixedNonce)
+	copy(nonce2, fixedNonce)
+
+	return &fixedNonceAEAD{nonce1, nonce2, aead}
+}
+
 // ssl30MAC implements the SSLv3 MAC function, as defined in
 // www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt section 5.2.3.1
 type ssl30MAC struct {
@@ -197,15 +240,17 @@ func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
 // A list of the possible cipher suite ids. Taken from
 // http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
 const (
-	TLS_RSA_WITH_RC4_128_SHA             uint16 = 0x0005
-	TLS_RSA_WITH_3DES_EDE_CBC_SHA        uint16 = 0x000a
-	TLS_RSA_WITH_AES_128_CBC_SHA         uint16 = 0x002f
-	TLS_RSA_WITH_AES_256_CBC_SHA         uint16 = 0x0035
-	TLS_ECDHE_ECDSA_WITH_RC4_128_SHA     uint16 = 0xc007
-	TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
-	TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
-	TLS_ECDHE_RSA_WITH_RC4_128_SHA       uint16 = 0xc011
-	TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA  uint16 = 0xc012
-	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA   uint16 = 0xc013
-	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA   uint16 = 0xc014
+	TLS_RSA_WITH_RC4_128_SHA                uint16 = 0x0005
+	TLS_RSA_WITH_3DES_EDE_CBC_SHA           uint16 = 0x000a
+	TLS_RSA_WITH_AES_128_CBC_SHA            uint16 = 0x002f
+	TLS_RSA_WITH_AES_256_CBC_SHA            uint16 = 0x0035
+	TLS_ECDHE_ECDSA_WITH_RC4_128_SHA        uint16 = 0xc007
+	TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA    uint16 = 0xc009
+	TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA    uint16 = 0xc00a
+	TLS_ECDHE_RSA_WITH_RC4_128_SHA          uint16 = 0xc011
+	TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA     uint16 = 0xc012
+	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA      uint16 = 0xc013
+	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA      uint16 = 0xc014
+	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256   uint16 = 0xc02f
+	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
 )

src/pkg/crypto/tls/conn.go

--- a/src/pkg/crypto/tls/conn.go
+++ b/src/pkg/crypto/tls/conn.go
@@ -146,6 +146,9 @@ func (hc *halfConn) changeCipherSpec() error {
 	hc.mac = hc.nextMac
 	hc.nextCipher = nil
 	hc.nextMac = nil
+	for i := range hc.seq {
+		hc.seq[i] = 0
+	}
 	return nil
 }
 
@@ -255,6 +258,26 @@ func (hc *halfConn) decrypt(b *block) (ok bool, prefixLen int, alertValue alert)
 		switch c := hc.cipher.(type) {
 		case cipher.Stream:
 			c.XORKeyStream(payload, payload)
+		case cipher.AEAD:
+			explicitIVLen = 8
+			if len(payload) < explicitIVLen {
+				return false, 0, alertBadRecordMAC
+			}
+			nonce := payload[:8]
+			payload = payload[8:]
+
+			var additionalData [13]byte
+			copy(additionalData[:], hc.seq[:])
+			copy(additionalData[8:], b.data[:3])
+			n := len(payload) - c.Overhead()
+			additionalData[11] = byte(n >> 8)
+			additionalData[12] = byte(n)
+			var err error
+			payload, err = c.Open(payload[:0], nonce, payload, additionalData[:])
+			if err != nil {
+				return false, 0, alertBadRecordMAC
+			}
+			b.resize(recordHeaderLen + explicitIVLen + len(payload))
 		case cbcMode:
 			blockSize := c.BlockSize()
 			if hc.version >= VersionTLS11 {
@@ -305,13 +328,13 @@ func (hc *halfConn) decrypt(b *block) (ok bool, prefixLen int, alertValue alert)
 		b.resize(recordHeaderLen + explicitIVLen + n)
 		remoteMAC := payload[n:]
 		localMAC := hc.mac.MAC(hc.inDigestBuf, hc.seq[0:], b.data[:recordHeaderLen], payload[:n])
-		hc.incSeq()
 
 		if subtle.ConstantTimeCompare(localMAC, remoteMAC) != 1 || paddingGood != 255 {
 			return false, 0, alertBadRecordMAC
 		}
 		hc.inDigestBuf = localMAC
 	}
+	hc.incSeq()
 
 	return true, recordHeaderLen + explicitIVLen, 0
 }
@@ -338,7 +361,6 @@ func (hc *halfConn) encrypt(b *block, explicitIVLen int) (bool, alert) {
 	// mac
 	if hc.mac != nil {
 		mac := hc.mac.MAC(hc.outDigestBuf, hc.seq[0:], b.data[:recordHeaderLen], b.data[recordHeaderLen+explicitIVLen:])
-		hc.incSeq()
 
 		n := len(b.data)
 		b.resize(n + len(mac))
@@ -353,6 +375,20 @@ func (hc *halfConn) encrypt(b *block, explicitIVLen int) (bool, alert) {
 		switch c := hc.cipher.(type) {
 		case cipher.Stream:
 			c.XORKeyStream(payload, payload)
+		case cipher.AEAD:
+			payloadLen := len(b.data) - recordHeaderLen - explicitIVLen
+			b.resize(len(b.data) + c.Overhead())
+			nonce := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen]
+			payload := b.data[recordHeaderLen+explicitIVLen:]
+			payload = payload[:payloadLen]
+
+			var additionalData [13]byte
+			copy(additionalData[:], hc.seq[:])
+			copy(additionalData[8:], b.data[:3])
+			additionalData[11] = byte(payloadLen >> 8)
+			additionalData[12] = byte(payloadLen)
+
+			c.Seal(payload[:0], nonce, payload, additionalData[:])
 		case cbcMode:
 			blockSize := c.BlockSize()
 			if explicitIVLen > 0 {
@@ -372,6 +408,7 @@ func (hc *halfConn) encrypt(b *block, explicitIVLen int) (bool, alert) {
 	n := len(b.data) - recordHeaderLen
 	b.data[3] = byte(n >> 8)
 	b.data[4] = byte(n)
+	hc.incSeq()
 
 	return true, 0
 }
@@ -660,6 +697,7 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) {
 		if m > maxPlaintext {
 			m = maxPlaintext
 		}
+		explicitIVLen := 0
+		explicitIVIsSeq := false
 
 		var cbc cbcMode
 		if c.out.version >= VersionTLS11 {
@@ -668,8 +706,18 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) {
 				explicitIVLen = cbc.BlockSize()
 			}
 		}
+		if explicitIVLen == 0 {
+			if _, ok := c.out.cipher.(cipher.AEAD); ok {
+				explicitIVLen = 8
+				// The AES-GCM construction in TLS has an
+				// explicit nonce so that the nonce can be
+				// random. However, the nonce is only 8 bytes
+				// which is too small for a secure, random
+				// nonce. Therefore we use the sequence number
+				// as the nonce.
+				explicitIVIsSeq = true
+			}
+		}
 		b.resize(recordHeaderLen + explicitIVLen + m)
 		b.data[0] = byte(typ)
 		vers := c.vers
@@ -682,8 +732,12 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) {
 		b.data[4] = byte(n)
 		if explicitIVLen > 0 {
 			explicitIV := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen]
-			if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil {
-				break
+			if explicitIVIsSeq {
+				copy(explicitIV, c.out.seq[:])
+			} else {
+				if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil {
+					break
+				}
 			}
 		}
 		copy(b.data[recordHeaderLen+explicitIVLen:], data)

コアとなるコードの解説

cipher_suites.go

  • cipherSuite 構造体への aead フィールド追加:
    • aead func(key, fixedNonce []byte) cipher.AEAD が追加されたことで、AEAD暗号スイートを表現できるようになりました。ciphermac は従来の暗号スイート用です。
  • cipherSuites リストの更新:
    • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 が追加され、aeadAESGCM 関数が指定されています。これらのスイートでは ciphermacnil に設定されます。
    • 新しいAES-GCMスイートは、セキュリティ上の理由からリストの先頭に配置されています。これは、ハンドシェイク時にクライアントが提示する暗号スイートの優先順位に影響します。
  • fixedNonceAEAD 構造体:
    • TLSのAES-GCMでは、ノンスは12バイトですが、そのうち4バイトは固定値(implicit nonce)で、残りの8バイトはシーケンス番号(explicit nonce)です。
    • fixedNonceAEAD は、この固定ノンス部分を sealNonceopenNonce に保持し、Seal および Open メソッド内で、引数として渡される8バイトのシーケンス番号と組み合わせて完全な12バイトのノンスを構築します。これにより、Goの標準 cipher.AEAD インターフェースをTLSのGCM要件に適合させています。
  • aeadAESGCM 関数:
    • この関数は、AES鍵と固定ノンスを受け取り、fixedNonceAEAD のインスタンスを返します。
    • 内部では aes.NewCipher でAESブロック暗号を、cipher.NewGCM でGCMモードのAEADインスタンスを作成しています。

conn.go

  • halfConn.decrypt および halfConn.encrypt の変更:
    • switch c := hc.cipher.(type) 文に case cipher.AEAD: が追加され、AEAD暗号スイートの場合の処理が定義されました。
    • 復号化 (decrypt):
      • explicitIVLen が8に設定されます。これは、TLSのAES-GCMでシーケンス番号がノンスとして使用される部分です。
      • ペイロードから8バイトのノンス(シーケンス番号)を抽出します。
      • additionalData (追加認証データ) を構築します。これは、シーケンス番号、レコードヘッダのタイプとバージョン、そして平文の長さから構成されます。このデータは暗号化されませんが、認証タグの計算に含まれるため、改ざんを検出できます。
      • c.Open を呼び出して復号化と認証を行います。エラーが発生した場合(認証失敗など)は alertBadRecordMAC を返します。
    • 暗号化 (encrypt):
      • 同様に explicitIVLen が8に設定されます。
      • additionalData を構築し、c.Seal を呼び出して暗号化と認証を行います。
    • シーケンス番号のインクリメント:
      • hc.incSeq() の呼び出し位置が、AEADと非AEADの両方で適切にシーケンス番号がインクリメントされるように調整されました。AEADではMAC計算が不要になったため、その後の共通の処理として移動されています。
  • Conn.writeRecord の変更:
    • explicitIVLen の計算ロジックが変更され、AEAD暗号スイートの場合に explicitIVLen が8に設定されるようになりました。
    • explicitIVIsSeq という新しいフラグが導入され、AEADの場合にノンスとしてシーケンス番号を使用することを示します。
    • explicitIV の生成ロジックが変更され、explicitIVIsSeqtrue の場合はシーケンス番号をコピーし、それ以外の場合は乱数を使用するように分岐しています。これは、TLSのAES-GCMの仕様に準拠するためです。

これらの変更により、GoのTLS実装は、AES-GCMの認証付き暗号の特性を適切に利用し、より安全な通信を提供できるようになりました。

関連リンク

参考にした情報源リンク