From 06fb503fc322712147ef2e7237f065e6100defa7 Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Tue, 9 Jun 2026 10:30:51 -0400 Subject: [PATCH] WIP --- Makefile | 19 ++++++++----- cmd/nebula-cert/fips140.go | 2 +- cmd/nebula-service/fips140.go | 2 +- cmd/nebula/fips140.go | 2 +- noiseutil/cipher_state.go | 2 +- noiseutil/fips140.go | 50 ++++++++++++++++++++--------------- noiseutil/fips140_test.go | 5 +++- noiseutil/notboring.go | 14 ++++++++-- 8 files changed, 61 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 0d1a8d94..978f7b07 100644 --- a/Makefile +++ b/Makefile @@ -188,15 +188,20 @@ build/linux-arm64-boringcrypto/%: LDFLAGS += -checklinkname=0 # fips140 build/linux-amd64-fips140/%: GOENV += GOFIPS140=v1.0.0 -build/linux-amd64-fips140/%: LDFLAGS += -checklinkname=0 -X runtime.godebugDefault=fips140=only +build/linux-amd64-fips140/%: LDFLAGS += -X runtime.godebugDefault=fips140=only +build/linux-amd64-fips140/%: BUILD_ARGS += -tags fips140 build/linux-arm64-fips140/%: GOENV += GOFIPS140=v1.0.0 -build/linux-arm64-fips140/%: LDFLAGS += -checklinkname=0 -X runtime.godebugDefault=fips140=only +build/linux-arm64-fips140/%: LDFLAGS += -X runtime.godebugDefault=fips140=only +build/linux-arm64-fips140/%: BUILD_ARGS += -tags fips140 build/darwin-arm64-fips140/%: GOENV += GOFIPS140=v1.0.0 -build/darwin-arm64-fips140/%: LDFLAGS += -checklinkname=0 -X runtime.godebugDefault=fips140=only +build/darwin-arm64-fips140/%: LDFLAGS += -X runtime.godebugDefault=fips140=only +build/darwin-arm64-fips140/%: BUILD_ARGS += -tags fips140 build/windows-amd64-fips140/%: GOENV += GOFIPS140=v1.0.0 -build/windows-amd64-fips140/%: LDFLAGS += -checklinkname=0 -X runtime.godebugDefault=fips140=only +build/windows-amd64-fips140/%: LDFLAGS += -X runtime.godebugDefault=fips140=only +build/windows-amd64-fips140/%: BUILD_ARGS += -tags fips140 build/windows-arm64-fips140/%: GOENV += GOFIPS140=v1.0.0 -build/windows-arm64-fips140/%: LDFLAGS += -checklinkname=0 -X runtime.godebugDefault=fips140=only +build/windows-arm64-fips140/%: LDFLAGS += -X runtime.godebugDefault=fips140=only +build/windows-arm64-fips140/%: BUILD_ARGS += -tags fips140 build/%/nebula: .FORCE GOOS=$(firstword $(subst -, , $*)) \ @@ -280,8 +285,8 @@ endif fips140: @echo > $(NULL_FILE) $(eval GOENV += GOFIPS140=v1.0.0) - $(eval LDFLAGS += -checklinkname=0 -X runtime.godebugDefault=fips140=only) - $(eval TEST_FLAGS += -ldflags -checklinkname=0) + $(eval LDFLAGS += -X runtime.godebugDefault=fips140=only) + $(eval BUILD_ARGS += -tags fips140) $(eval TEST_ENV += $(GOENV)) ifeq ($(words $(MAKECMDGOALS)),1) @$(MAKE) fips140 ${.DEFAULT_GOAL} --no-print-directory diff --git a/cmd/nebula-cert/fips140.go b/cmd/nebula-cert/fips140.go index 80f33773..53065e25 100644 --- a/cmd/nebula-cert/fips140.go +++ b/cmd/nebula-cert/fips140.go @@ -1,4 +1,4 @@ -//go:build fips140v1.0 || fips140v1.26 +//go:build fips140 package main diff --git a/cmd/nebula-service/fips140.go b/cmd/nebula-service/fips140.go index 80f33773..53065e25 100644 --- a/cmd/nebula-service/fips140.go +++ b/cmd/nebula-service/fips140.go @@ -1,4 +1,4 @@ -//go:build fips140v1.0 || fips140v1.26 +//go:build fips140 package main diff --git a/cmd/nebula/fips140.go b/cmd/nebula/fips140.go index 80f33773..53065e25 100644 --- a/cmd/nebula/fips140.go +++ b/cmd/nebula/fips140.go @@ -1,4 +1,4 @@ -//go:build fips140v1.0 || fips140v1.26 +//go:build fips140 package main diff --git a/noiseutil/cipher_state.go b/noiseutil/cipher_state.go index 3aef2cff..f4753247 100644 --- a/noiseutil/cipher_state.go +++ b/noiseutil/cipher_state.go @@ -33,7 +33,7 @@ func NewCipherState(s *noise.CipherState, cipherFunc noise.CipherFunc) CipherSta return cs } switch cipherFunc.CipherName() { - case CipherAESGCM.CipherName(): + case noise.CipherAESGCM.CipherName(): return NewCipherStateAESGCM(s) case noise.CipherChaChaPoly.CipherName(): return NewCipherStateChaChaPoly(s) diff --git a/noiseutil/fips140.go b/noiseutil/fips140.go index 42f4aebd..3332c565 100644 --- a/noiseutil/fips140.go +++ b/noiseutil/fips140.go @@ -1,3 +1,5 @@ +//go:build !boringcrypto + package noiseutil import ( @@ -11,9 +13,10 @@ import ( "github.com/flynn/noise" ) -// TODO: Use NewGCMWithCounterNonce once available: +// TODO: Use NewGCMWithCounterNonce or NewGCMForQUIC once available: // - https://github.com/golang/go/issues/73110 -// Using tls.aeadAESGCM gives us the TLS 1.2 GCM, which also verifies +// - https://github.com/golang/go/issues/79219 +// Using tls.aeadAESGCMTLS13 gives us the TLS 1.3 GCM, which also verifies // that the nonce is strictly increasing. // //go:linkname aeadAESGCMTLS13 crypto/tls.aeadAESGCMTLS13 @@ -28,7 +31,7 @@ func (c cipherFn) Cipher(k [32]byte) noise.Cipher { return c.fn(k) } func (c cipherFn) CipherName() string { return c.name } // CipherAESGCM is the AES256-GCM AEAD cipher (using aeadAESGCM when fips140 is enabled) -var CipherAESGCM 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 var emptyPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -36,11 +39,11 @@ var emptyNonce = []byte{0, 0, 0, 0, 0, 0, 0, 0} func cipherAESGCMFIPS140(k [32]byte) noise.Cipher { gcm := aeadAESGCMTLS13(k[:], emptyPrefix) - return aeadCipher{ - gcm, - false, - func(n uint64) []byte { - // tls.aeadAESGCM uses a 4 byte static prefix and an 8 byte nonce + return &aeadCipher{ + 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) return nonce[:] @@ -48,37 +51,42 @@ func cipherAESGCMFIPS140(k [32]byte) noise.Cipher { } } -type aeadCipher struct { - cipher.AEAD - initialized bool - nonce func(uint64) []byte -} - -func (c aeadCipher) Seal(dst, nonce, plaintext, additionalData []byte) []byte { - if !c.initialized { +func (c *aeadCipher) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + // runtime.Breakpoint() + if !c.ready { + // crypto/tls.aeadAESGCMTLS13 expected 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: if !bytes.Equal(emptyNonce, nonce) { c.AEAD.Seal([]byte{}, emptyNonce, []byte{}, []byte{}) } - c.initialized = true + c.ready = true } return c.AEAD.Seal(dst, nonce, plaintext, additionalData) } -func (c aeadCipher) Encrypt(out []byte, n uint64, ad, plaintext []byte) []byte { +type aeadCipher struct { + cipher.AEAD + ready bool + nonce func(uint64) []byte +} + +func (c *aeadCipher) Encrypt(out []byte, n uint64, ad, plaintext []byte) []byte { return c.Seal(out, c.nonce(n), plaintext, ad) } -func (c aeadCipher) Decrypt(out []byte, n uint64, ad, ciphertext []byte) ([]byte, error) { +func (c *aeadCipher) Decrypt(out []byte, n uint64, ad, ciphertext []byte) ([]byte, error) { return c.Open(out, c.nonce(n), ciphertext, ad) } -func (c aeadCipher) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) { +func (c *aeadCipher) 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) return out, nil } -func (c aeadCipher) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) { +func (c *aeadCipher) 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) } diff --git a/noiseutil/fips140_test.go b/noiseutil/fips140_test.go index d00057b6..28bd3dc7 100644 --- a/noiseutil/fips140_test.go +++ b/noiseutil/fips140_test.go @@ -33,7 +33,10 @@ func TestNewAESGCM(t *testing.T) { assert.Equal(t, expected, dst) // We expect this to fail since we are re-encrypting with a repeat IV - assert.PanicsWithValue(t, "crypto/cipher: counter decreased or remained the same", func() { + // TODO: the error message has changed between fips module versions, best way to verify it? + // assert.PanicsWithValue(t, "crypto/cipher: counter decreased", func() { + // assert.PanicsWithValue(t, "crypto/cipher: counter decreased or remained the same", func() { + assert.Panics(t, func() { dst = aead.Seal([]byte{}, iv, plaintext, aad) }) } diff --git a/noiseutil/notboring.go b/noiseutil/notboring.go index d12450c8..f288bf88 100644 --- a/noiseutil/notboring.go +++ b/noiseutil/notboring.go @@ -4,10 +4,20 @@ package noiseutil import ( "crypto/fips140" + + "github.com/flynn/noise" ) // EncryptLockNeeded indicates if calls to Encrypt need a lock var EncryptLockNeeded = fips140.Enabled() -// CipherAESGCM is the standard noise.CipherAESGCM when boringcrypto is not enabled -// var CipherAESGCM noise.CipherFunc = noise.CipherAESGCM +var CipherAESGCM noise.CipherFunc = initAESGCM() + +func initAESGCM() noise.CipherFunc { + if fips140.Enabled() { + return CipherAESGCMFIPS140 + } else { + return noise.CipherAESGCM + } + +}