From 90c763027070b1ef0fd71e88375008f47d28ecc3 Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Wed, 8 Jul 2026 10:19:48 -0400 Subject: [PATCH] extract the internal FIPS GCM implementation We do this because the TLS wrapper is not thread safe on Open. instead of locking around it we can grab the internal implementation that is thread safe. This is the FIPS module implementation: `crypto/internal/fips140/aes/gcm.GCMWithXORCounterNonce` - https://github.com/golang/go/blob/go1.26.4/src/crypto/internal/fips140/aes/gcm/gcm_nonces.go#L212-L287 The wrapper is struct `crypto/tls.xorNonceAEAD`, with field `aead`: - https://github.com/golang/go/blob/go1.26.4/src/crypto/tls/cipher_suites.go#L482-L487 This can be cleaned up once these FIPS implementations are exposed directly: - https://github.com/golang/go/issues/73110 --- noiseutil/fips140.go | 40 +++++++++++++++++++++++++++++++-------- noiseutil/fips140_test.go | 2 +- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/noiseutil/fips140.go b/noiseutil/fips140.go index 741d3b57..83a8a179 100644 --- a/noiseutil/fips140.go +++ b/noiseutil/fips140.go @@ -4,8 +4,11 @@ import ( "bytes" "crypto/cipher" "encoding/binary" + "reflect" + "unsafe" // unsafe needed for go:linkname + _ "crypto/tls" _ "unsafe" "github.com/flynn/noise" @@ -33,18 +36,18 @@ func (c cipherFn) CipherName() string { return c.name } var CipherAESGCMFIPS140 noise.CipherFunc = cipherFn{cipherAESGCMFIPS140, "AESGCM"} // tls.aeadAESGCMTLS13 uses a 4 byte static prefix and an 8 byte XOR mask -var emptyPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -var emptyNonce = []byte{0, 0, 0, 0, 0, 0, 0, 0} +var emptyNonce = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} func cipherAESGCMFIPS140(k [32]byte) noise.Cipher { - gcm := aeadAESGCMTLS13(k[:], emptyPrefix) + gcm := aeadAESGCMTLS13(k[:], emptyNonce) + gcm = extractFIPSAEAD(gcm) return &aeadGCMFIPS140Cipher{ AEAD: gcm, ready: false, nonce: func(n uint64) []byte { // tls.aeadAESGCMTLS13 uses a 4 byte static prefix and an 8 byte nonce - var nonce [8]byte - binary.BigEndian.PutUint64(nonce[:], n) + var nonce [12]byte + binary.BigEndian.PutUint64(nonce[4:], n) return nonce[:] }, } @@ -56,8 +59,29 @@ type aeadGCMFIPS140Cipher struct { nonce func(uint64) []byte } +// Extract the internal FIPS GCM implementation from the tls wrapper. The TLS +// wrapper is not thread safe around Open, so instead of locking around it we +// can grab the internal implementation that is thread safe. This is the FIPS +// module implementation: `crypto/internal/fips140/aes/gcm.GCMWithXORCounterNonce` +// +// - https://github.com/golang/go/blob/go1.26.4/src/crypto/internal/fips140/aes/gcm/gcm_nonces.go#L212-L287 +// +// The wrapper is struct `crypto/tls.xorNonceAEAD` , with field `aead`: +// +// - https://github.com/golang/go/blob/go1.26.4/src/crypto/tls/cipher_suites.go#L482-L487 +// +// This can be cleaned up once these FIPS implementations are exposed directly: +// +// - https://github.com/golang/go/issues/73110 +func extractFIPSAEAD(xorNonceAEAD cipher.AEAD) cipher.AEAD { + r := reflect.ValueOf(xorNonceAEAD) + v := r.Elem().FieldByName("aead") + v2 := reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem() + return v2.Interface().(cipher.AEAD) +} + func (c *aeadGCMFIPS140Cipher) init(nonce []byte) { - // crypto/tls.aeadAESGCMTLS13 expects that the first call to Seal + // GCMWithXORCounterNonce expects that the first call to Seal // is with a counter of `0`, this is how it extracts the nonce mask. // We can clean this up in the future when NewGCMWithCounterNonce or // NewGCMForQUIC are available: @@ -84,11 +108,11 @@ func (c *aeadGCMFIPS140Cipher) Decrypt(out []byte, n uint64, ad, ciphertext []by func (c *aeadGCMFIPS140Cipher) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) { binary.BigEndian.PutUint64(nb[4:], n) - out = c.Seal(out, nb[4:], plaintext, ad) + out = c.Seal(out, nb, plaintext, ad) return out, nil } func (c *aeadGCMFIPS140Cipher) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) { binary.BigEndian.PutUint64(nb[4:], n) - return c.Open(out, nb[4:], ciphertext, ad) + return c.Open(out, nb, ciphertext, ad) } diff --git a/noiseutil/fips140_test.go b/noiseutil/fips140_test.go index 012cd2ed..8f7a5eb0 100644 --- a/noiseutil/fips140_test.go +++ b/noiseutil/fips140_test.go @@ -17,7 +17,7 @@ func TestNewAESGCM(t *testing.T) { } key, _ := hex.DecodeString("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") - iv, _ := hex.DecodeString("facedbaddecaf888") + iv, _ := hex.DecodeString("00000000facedbaddecaf888") plaintext, _ := hex.DecodeString("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39") aad, _ := hex.DecodeString("feedfacedeadbeeffeedfacedeadbeefabaddad2") expected, _ := hex.DecodeString("6a65c2edd45bd63c7e29f40e3d2ed8ba2b99f4c83135383d5676652f255059ceb24863ff10afb1089db701245da87fb88d3acd5f9dd0770cac220c3c04145caf25e190aeb775e7080401c628")