From c22a3c8e6f7479d33d281d32a86b9b978c8e718e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 5 Feb 2024 12:27:21 +0700 Subject: [PATCH] handshake: validate HKDF-Expand-Label against crypto/tls implementation (#4311) * handshake: validate HKDF-Expand-Label against crypto/tls implementation * handshake: add a benchmark for HKDF-Expand-Label --- internal/handshake/hkdf.go | 2 +- internal/handshake/hkdf_test.go | 65 +++++++++++++++++++++++++++++---- internal/qtls/cipher_suite.go | 12 ------ 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/internal/handshake/hkdf.go b/internal/handshake/hkdf.go index c4fd86c57b3..0caf1c8e52c 100644 --- a/internal/handshake/hkdf.go +++ b/internal/handshake/hkdf.go @@ -7,7 +7,7 @@ import ( "golang.org/x/crypto/hkdf" ) -// hkdfExpandLabel HKDF expands a label. +// hkdfExpandLabel HKDF expands a label as defined in RFC 8446, section 7.1. // Since this implementation avoids using a cryptobyte.Builder, it is about 15% faster than the // hkdfExpandLabel in the standard library. func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { diff --git a/internal/handshake/hkdf_test.go b/internal/handshake/hkdf_test.go index e79a6f1e96a..1cc1612991e 100644 --- a/internal/handshake/hkdf_test.go +++ b/internal/handshake/hkdf_test.go @@ -2,16 +2,67 @@ package handshake import ( "crypto" + "crypto/cipher" + "crypto/tls" + "testing" + _ "unsafe" + + "golang.org/x/exp/rand" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -var _ = Describe("Initial AEAD using AES-GCM", func() { - // Result generated by running in qtls: - // cipherSuiteTLS13ByID(TLS_AES_128_GCM_SHA256).expandLabel([]byte("secret"), []byte("context"), "label", 42) - It("gets the same results as qtls", func() { - expanded := hkdfExpandLabel(crypto.SHA256, []byte("secret"), []byte("context"), "label", 42) - Expect(expanded).To(Equal([]byte{0x78, 0x87, 0x6a, 0xb5, 0x84, 0xa2, 0x26, 0xb7, 0x8, 0x5a, 0x7b, 0x3a, 0x4c, 0xbb, 0x1e, 0xbc, 0x2f, 0x9b, 0x67, 0xd0, 0x6a, 0xa2, 0x24, 0xb4, 0x7d, 0x29, 0x3c, 0x7a, 0xce, 0xc7, 0xc3, 0x74, 0xcd, 0x59, 0x7a, 0xa8, 0x21, 0x5e, 0xe7, 0xca, 0x1, 0xda})) - }) +type cipherSuiteTLS13 struct { + ID uint16 + KeyLen int + AEAD func(key, fixedNonce []byte) cipher.AEAD + Hash crypto.Hash +} + +//go:linkname cipherSuiteTLS13ByID crypto/tls.cipherSuiteTLS13ByID +func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 + +//go:linkname expandLabel crypto/tls.(*cipherSuiteTLS13).expandLabel +func expandLabel(cs *cipherSuiteTLS13, secret []byte, label string, context []byte, length int) []byte + +var _ = Describe("HKDF", func() { + DescribeTable("gets the same results as crypto/tls", + func(cipherSuite uint16, secret, context []byte, label string, length int) { + cs := cipherSuiteTLS13ByID(cipherSuite) + expected := expandLabel(cs, secret, label, context, length) + expanded := hkdfExpandLabel(cs.Hash, secret, context, label, length) + Expect(expanded).To(Equal(expected)) + }, + Entry("TLS_AES_128_GCM_SHA256", tls.TLS_AES_128_GCM_SHA256, []byte("secret"), []byte("context"), "label", 42), + Entry("TLS_AES_256_GCM_SHA384", tls.TLS_AES_256_GCM_SHA384, []byte("secret"), []byte("context"), "label", 100), + Entry("TLS_CHACHA20_POLY1305_SHA256", tls.TLS_CHACHA20_POLY1305_SHA256, []byte("secret"), []byte("context"), "label", 77), + ) }) + +func BenchmarkHKDFExpandLabelStandardLibrary(b *testing.B) { + b.Run("TLS_AES_128_GCM_SHA256", func(b *testing.B) { benchmarkHKDFExpandLabel(b, tls.TLS_AES_128_GCM_SHA256, true) }) + b.Run("TLS_AES_256_GCM_SHA384", func(b *testing.B) { benchmarkHKDFExpandLabel(b, tls.TLS_AES_256_GCM_SHA384, true) }) + b.Run("TLS_CHACHA20_POLY1305_SHA256", func(b *testing.B) { benchmarkHKDFExpandLabel(b, tls.TLS_CHACHA20_POLY1305_SHA256, true) }) +} + +func BenchmarkHKDFExpandLabelOptimized(b *testing.B) { + b.Run("TLS_AES_128_GCM_SHA256", func(b *testing.B) { benchmarkHKDFExpandLabel(b, tls.TLS_AES_128_GCM_SHA256, false) }) + b.Run("TLS_AES_256_GCM_SHA384", func(b *testing.B) { benchmarkHKDFExpandLabel(b, tls.TLS_AES_256_GCM_SHA384, false) }) + b.Run("TLS_CHACHA20_POLY1305_SHA256", func(b *testing.B) { benchmarkHKDFExpandLabel(b, tls.TLS_CHACHA20_POLY1305_SHA256, false) }) +} + +func benchmarkHKDFExpandLabel(b *testing.B, cipherSuite uint16, useStdLib bool) { + b.ReportAllocs() + cs := cipherSuiteTLS13ByID(cipherSuite) + secret := make([]byte, 32) + rand.Read(secret) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if useStdLib { + expandLabel(cs, secret, "label", []byte("context"), 42) + } else { + hkdfExpandLabel(cs.Hash, secret, []byte("context"), "label", 42) + } + } +} diff --git a/internal/qtls/cipher_suite.go b/internal/qtls/cipher_suite.go index 16558f8bcd3..32a921cd556 100644 --- a/internal/qtls/cipher_suite.go +++ b/internal/qtls/cipher_suite.go @@ -1,23 +1,11 @@ package qtls import ( - "crypto" - "crypto/cipher" "crypto/tls" "fmt" "unsafe" ) -type cipherSuiteTLS13 struct { - ID uint16 - KeyLen int - AEAD func(key, fixedNonce []byte) cipher.AEAD - Hash crypto.Hash -} - -//go:linkname cipherSuiteTLS13ByID crypto/tls.cipherSuiteTLS13ByID -func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 - //go:linkname cipherSuitesTLS13 crypto/tls.cipherSuitesTLS13 var cipherSuitesTLS13 []unsafe.Pointer