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
This commit is contained in:
Wade Simmons
2026-07-08 10:19:48 -04:00
parent 81afed439e
commit 90c7630270
2 changed files with 33 additions and 9 deletions
+32 -8
View File
@@ -4,8 +4,11 @@ import (
"bytes" "bytes"
"crypto/cipher" "crypto/cipher"
"encoding/binary" "encoding/binary"
"reflect"
"unsafe"
// unsafe needed for go:linkname // unsafe needed for go:linkname
_ "crypto/tls"
_ "unsafe" _ "unsafe"
"github.com/flynn/noise" "github.com/flynn/noise"
@@ -33,18 +36,18 @@ func (c cipherFn) CipherName() string { return c.name }
var CipherAESGCMFIPS140 noise.CipherFunc = cipherFn{cipherAESGCMFIPS140, "AESGCM"} var CipherAESGCMFIPS140 noise.CipherFunc = cipherFn{cipherAESGCMFIPS140, "AESGCM"}
// tls.aeadAESGCMTLS13 uses a 4 byte static prefix and an 8 byte XOR mask // 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, 0, 0, 0, 0}
var emptyNonce = []byte{0, 0, 0, 0, 0, 0, 0, 0}
func cipherAESGCMFIPS140(k [32]byte) noise.Cipher { func cipherAESGCMFIPS140(k [32]byte) noise.Cipher {
gcm := aeadAESGCMTLS13(k[:], emptyPrefix) gcm := aeadAESGCMTLS13(k[:], emptyNonce)
gcm = extractFIPSAEAD(gcm)
return &aeadGCMFIPS140Cipher{ return &aeadGCMFIPS140Cipher{
AEAD: gcm, AEAD: gcm,
ready: false, ready: false,
nonce: func(n uint64) []byte { nonce: func(n uint64) []byte {
// tls.aeadAESGCMTLS13 uses a 4 byte static prefix and an 8 byte nonce // tls.aeadAESGCMTLS13 uses a 4 byte static prefix and an 8 byte nonce
var nonce [8]byte var nonce [12]byte
binary.BigEndian.PutUint64(nonce[:], n) binary.BigEndian.PutUint64(nonce[4:], n)
return nonce[:] return nonce[:]
}, },
} }
@@ -56,8 +59,29 @@ type aeadGCMFIPS140Cipher struct {
nonce func(uint64) []byte 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) { 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. // 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 // We can clean this up in the future when NewGCMWithCounterNonce or
// NewGCMForQUIC are available: // 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) { func (c *aeadGCMFIPS140Cipher) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) {
binary.BigEndian.PutUint64(nb[4:], n) binary.BigEndian.PutUint64(nb[4:], n)
out = c.Seal(out, nb[4:], plaintext, ad) out = c.Seal(out, nb, plaintext, ad)
return out, nil return out, nil
} }
func (c *aeadGCMFIPS140Cipher) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) { func (c *aeadGCMFIPS140Cipher) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) {
binary.BigEndian.PutUint64(nb[4:], n) binary.BigEndian.PutUint64(nb[4:], n)
return c.Open(out, nb[4:], ciphertext, ad) return c.Open(out, nb, ciphertext, ad)
} }
+1 -1
View File
@@ -17,7 +17,7 @@ func TestNewAESGCM(t *testing.T) {
} }
key, _ := hex.DecodeString("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") key, _ := hex.DecodeString("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308")
iv, _ := hex.DecodeString("facedbaddecaf888") iv, _ := hex.DecodeString("00000000facedbaddecaf888")
plaintext, _ := hex.DecodeString("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39") plaintext, _ := hex.DecodeString("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39")
aad, _ := hex.DecodeString("feedfacedeadbeeffeedfacedeadbeefabaddad2") aad, _ := hex.DecodeString("feedfacedeadbeeffeedfacedeadbeefabaddad2")
expected, _ := hex.DecodeString("6a65c2edd45bd63c7e29f40e3d2ed8ba2b99f4c83135383d5676652f255059ceb24863ff10afb1089db701245da87fb88d3acd5f9dd0770cac220c3c04145caf25e190aeb775e7080401c628") expected, _ := hex.DecodeString("6a65c2edd45bd63c7e29f40e3d2ed8ba2b99f4c83135383d5676652f255059ceb24863ff10afb1089db701245da87fb88d3acd5f9dd0770cac220c3c04145caf25e190aeb775e7080401c628")