From 4485c476416696bf538a632748501b249e7c0b9b Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Mon, 31 Mar 2025 12:08:58 -0400 Subject: [PATCH] WIP support new Go fips140 module This will replace boring crypto at some point. We should modify our protocol a bit and instead change to NewGCMWithRandomNonce. --- go.mod | 4 +-- noiseutil/fips140.go | 64 +++++++++++++++++++++++++++++++++++++ noiseutil/fips140_test.go | 42 ++++++++++++++++++++++++ noiseutil/notboring.go | 4 +-- noiseutil/notboring_test.go | 4 +-- 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 noiseutil/fips140.go create mode 100644 noiseutil/fips140_test.go diff --git a/go.mod b/go.mod index de09c18..3c7a9fd 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/slackhq/nebula -go 1.23.6 +go 1.24.0 -toolchain go1.23.7 +toolchain go1.24.1 require ( dario.cat/mergo v1.0.1 diff --git a/noiseutil/fips140.go b/noiseutil/fips140.go new file mode 100644 index 0000000..2414f52 --- /dev/null +++ b/noiseutil/fips140.go @@ -0,0 +1,64 @@ +//go:build fips140 +// +build fips140 + +package noiseutil + +import ( + "crypto/cipher" + "encoding/binary" + + // unsafe needed for go:linkname + _ "unsafe" + + "github.com/flynn/noise" +) + +// EncryptLockNeeded indicates if calls to Encrypt need a lock +// This is true for boringcrypto because the Seal function verifies that the +// nonce is strictly increasing. +const EncryptLockNeeded = true + +//go:linkname aeadAESGCM crypto/tls.aeadAESGCM +func aeadAESGCM(key, noncePrefix []byte) cipher.AEAD + +type cipherFn struct { + fn func([32]byte) noise.Cipher + name string +} + +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{cipherAESGCM, "AESGCM"} + +var emptyPrefix = []byte{0, 0, 0, 0} + +func cipherAESGCM(k [32]byte) noise.Cipher { + // c, err := aes.NewCipher(k[:]) + // if err != nil { + // panic(err) + // } + gcm := aeadAESGCM(k[:], emptyPrefix) + return aeadCipher{ + gcm, + func(n uint64) []byte { + var nonce [12]byte + binary.BigEndian.PutUint64(nonce[4:], n) + return nonce[:] + }, + } +} + +type aeadCipher struct { + cipher.AEAD + 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) { + return c.Open(out, c.nonce(n), ciphertext, ad) +} diff --git a/noiseutil/fips140_test.go b/noiseutil/fips140_test.go new file mode 100644 index 0000000..9abcb20 --- /dev/null +++ b/noiseutil/fips140_test.go @@ -0,0 +1,42 @@ +//go:build fips140 +// +build fips140 + +package noiseutil + +import ( + "crypto/fips140" + "encoding/hex" + "log" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncryptLockNeeded(t *testing.T) { + assert.True(t, EncryptLockNeeded) +} + +// Ensure NewAESGCM validates the nonce is non-repeating +func TestNewAESGCM(t *testing.T) { + assert.True(t, fips140.Enabled()) + + key, _ := hex.DecodeString("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") + iv, _ := hex.DecodeString("00000000facedbaddecaf888") + plaintext, _ := hex.DecodeString("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39") + aad, _ := hex.DecodeString("feedfacedeadbeeffeedfacedeadbeefabaddad2") + expected, _ := hex.DecodeString("72ce2ea385f88c20d856e9d1248c2ca08562bbe8a61459ffae06ec393540518e9b6b4c40a146053f26a3df83c5384a48d273148b15aba64d970107432b2892741359275676441c1572c3fa9e") + + var keyArray [32]byte + copy(keyArray[:], key) + c := CipherAESGCM.Cipher(keyArray) + aead := c.(aeadCipher).AEAD + + dst := aead.Seal([]byte{}, iv, plaintext, aad) + log.Printf("%x", dst) + 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", func() { + dst = aead.Seal([]byte{}, iv, plaintext, aad) + }) +} diff --git a/noiseutil/notboring.go b/noiseutil/notboring.go index be746f4..9277969 100644 --- a/noiseutil/notboring.go +++ b/noiseutil/notboring.go @@ -1,5 +1,5 @@ -//go:build !boringcrypto -// +build !boringcrypto +//go:build !boringcrypto && !fips140 +// +build !boringcrypto,!fips140 package noiseutil diff --git a/noiseutil/notboring_test.go b/noiseutil/notboring_test.go index b865391..2d44bf8 100644 --- a/noiseutil/notboring_test.go +++ b/noiseutil/notboring_test.go @@ -1,5 +1,5 @@ -//go:build !boringcrypto -// +build !boringcrypto +//go:build !boringcrypto && !fips140 +// +build !boringcrypto,!fips140 package noiseutil