-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Support native Golang "fips140" mode #1696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 23 commits
4485c47
3da3d41
6da314a
31cc3a4
f6b206d
0eb92dc
fd3fa57
b418a08
6fa1ecd
2d5d86f
08ee2ab
243cf4a
ef8b700
e6eeb37
fcfbec4
f9532f4
65450f9
ae58085
1403473
bdbd483
4ff9e97
d4d747f
b79fdc2
5dd566e
7f6976b
441a67f
a1b8954
b5ad62a
8671a4e
0897f49
c3e2a2b
cfcdcb0
dd081ff
f1a8054
c73245c
7c8a70b
1d17c78
56a09b7
cf5d73d
adb01f6
c7e0354
37b752b
90ea634
7cd3875
06fb503
d725e53
f437c7d
32db819
9709893
69a6db7
cb9547b
3b30526
a383937
69c6132
060b46f
67da623
7d087a6
92bd785
2efdcb0
ac5a18e
d42407c
81afed4
90c7630
14c9288
6675f5a
4e8733c
3c1dd52
3264ebf
048fa3f
6be3d22
e16a31f
8d8cc4f
009d42b
e832250
0d91e3e
fc950cf
69e5e8e
00b498f
0e1f534
738b4fa
7c00c74
bf2f26b
a5cb635
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -121,12 +121,12 @@ bin-pkcs11: CGO_ENABLED = 1 | |
| bin-pkcs11: bin | ||
|
|
||
| bin: | ||
| go build $(BUILD_ARGS) -ldflags "$(LDFLAGS)" -o ./nebula${NEBULA_CMD_SUFFIX} ${NEBULA_CMD_PATH} | ||
| go build $(BUILD_ARGS) -ldflags "$(LDFLAGS)" -o ./nebula-cert${NEBULA_CMD_SUFFIX} ./cmd/nebula-cert | ||
| $(GOENV) go build $(BUILD_ARGS) -ldflags "$(LDFLAGS)" -o ./nebula${NEBULA_CMD_SUFFIX} ${NEBULA_CMD_PATH} | ||
| $(GOENV) go build $(BUILD_ARGS) -ldflags "$(LDFLAGS)" -o ./nebula-cert${NEBULA_CMD_SUFFIX} ./cmd/nebula-cert | ||
|
|
||
| install: | ||
| go install $(BUILD_ARGS) -ldflags "$(LDFLAGS)" ${NEBULA_CMD_PATH} | ||
| go install $(BUILD_ARGS) -ldflags "$(LDFLAGS)" ./cmd/nebula-cert | ||
| $(GOENV) go install $(BUILD_ARGS) -ldflags "$(LDFLAGS)" ${NEBULA_CMD_PATH} | ||
| $(GOENV) go install $(BUILD_ARGS) -ldflags "$(LDFLAGS)" ./cmd/nebula-cert | ||
|
|
||
| build/linux-arm-%: GOENV += GOARM=$(word 3, $(subst -, ,$*)) | ||
| build/linux-mips-%: GOENV += GOMIPS=$(word 3, $(subst -, ,$*)) | ||
|
|
@@ -169,7 +169,7 @@ vet: | |
| go vet $(VET_FLAGS) -v ./... | ||
|
|
||
| test: | ||
| go test -v ./... | ||
| $(TEST_ENV) go test $(TEST_FLAGS) -v ./... | ||
|
|
||
| test-boringcrypto: | ||
| GOEXPERIMENT=boringcrypto CGO_ENABLED=1 go test -ldflags "-checklinkname=0" -v ./... | ||
|
|
@@ -215,17 +215,27 @@ ifeq ($(words $(MAKECMDGOALS)),1) | |
| @$(MAKE) service ${.DEFAULT_GOAL} --no-print-directory | ||
| endif | ||
|
|
||
| fips140: | ||
| @echo > $(NULL_FILE) | ||
| $(eval GOENV += GOFIPS140=v1.0.0) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will also need to require go 1.26 and include this LDFLAGS: Otherwise fips is enabled but all non fips crypto just keeps working. Will need to couple this with a change to assert
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added the default, but not enforcing and instead just logging the state of it. Considering the check.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implemented with a build tag fips140enforce |
||
| $(eval LDFLAGS += -checklinkname=0) | ||
| $(eval TEST_FLAGS += -ldflags -checklinkname=0) | ||
| $(eval TEST_ENV += $(GOENV)) | ||
| ifeq ($(words $(MAKECMDGOALS)),1) | ||
| @$(MAKE) fips140 ${.DEFAULT_GOAL} --no-print-directory | ||
| endif | ||
|
|
||
| bin-docker: bin build/linux-amd64/nebula build/linux-amd64/nebula-cert | ||
|
|
||
| smoke-docker: bin-docker | ||
| cd .github/workflows/smoke/ && ./build.sh | ||
| cd .github/workflows/smoke/ && ./smoke.sh | ||
| cd .github/workflows/smoke/ && NAME="smoke-p256" CURVE="P256" ./build.sh | ||
| cd .github/workflows/smoke/ && NAME="smoke-p256" ./smoke.sh | ||
| cd .github/workflows/smoke/ && $(GOENV) ./build.sh | ||
| cd .github/workflows/smoke/ && $(GOENV)./smoke.sh | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a space after
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch, fixed |
||
| cd .github/workflows/smoke/ && $(GOENV) NAME="smoke-p256" CURVE="P256" ./build.sh | ||
| cd .github/workflows/smoke/ && $(GOENV) NAME="smoke-p256" ./smoke.sh | ||
|
|
||
| smoke-relay-docker: bin-docker | ||
| cd .github/workflows/smoke/ && ./build-relay.sh | ||
| cd .github/workflows/smoke/ && ./smoke-relay.sh | ||
| cd .github/workflows/smoke/ && $(GOENV) ./build-relay.sh | ||
| cd .github/workflows/smoke/ && $(GOENV) ./smoke-relay.sh | ||
|
|
||
| smoke-docker-race: BUILD_ARGS = -race | ||
| smoke-docker-race: CGO_ENABLED = 1 | ||
|
|
@@ -236,5 +246,5 @@ smoke-vagrant/%: bin-docker build/%/nebula | |
| cd .github/workflows/smoke/ && ./smoke-vagrant.sh $* | ||
|
|
||
| .FORCE: | ||
| .PHONY: bench bench-cpu bench-cpu-long bin build-test-mobile e2e e2ev e2evv e2evvv e2evvvv proto release service smoke-docker smoke-docker-race test test-cov-html smoke-vagrant/% | ||
| .PHONY: bench bench-cpu bench-cpu-long bin build-test-mobile e2e e2ev e2evv e2evvv e2evvvv fips140 proto release service smoke-docker smoke-docker-race test test-cov-html smoke-vagrant/% | ||
| .DEFAULT_GOAL := bin | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,11 @@ func NewNebulaCipherState(s *noise.CipherState) *NebulaCipherState { | |
| return &NebulaCipherState{c: x.(cipher.AEAD)} | ||
| } | ||
|
|
||
| type cipherAEADDanger interface { | ||
| EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) | ||
| DecryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. im confused, when you call this function it is still plaintext
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. err nm lol I see |
||
| } | ||
|
|
||
| // EncryptDanger encrypts and authenticates a given payload. | ||
| // | ||
| // out is a destination slice to hold the output of the EncryptDanger operation. | ||
|
|
@@ -33,33 +38,43 @@ func NewNebulaCipherState(s *noise.CipherState) *NebulaCipherState { | |
| // be re-used by callers to minimize garbage collection. | ||
| func (s *NebulaCipherState) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) { | ||
| if s != nil { | ||
| // TODO: Is this okay now that we have made messageCounter atomic? | ||
| // Alternative may be to split the counter space into ranges | ||
| //if n <= s.n { | ||
| // return nil, errors.New("CRITICAL: a duplicate counter value was used") | ||
| //} | ||
| //s.n = n | ||
| nb[0] = 0 | ||
| nb[1] = 0 | ||
| nb[2] = 0 | ||
| nb[3] = 0 | ||
| noiseEndianness.PutUint64(nb[4:], n) | ||
| out = s.c.Seal(out, nb, plaintext, ad) | ||
| //l.Debugf("Encryption: outlen: %d, nonce: %d, ad: %s, plainlen %d", len(out), n, ad, len(plaintext)) | ||
| return out, nil | ||
| switch ce := s.c.(type) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be awesome to swap out the interface and avoid the type check for each packet.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in latest commit |
||
| case cipherAEADDanger: | ||
| return ce.EncryptDanger(out, ad, plaintext, n, nb) | ||
| default: | ||
| // TODO: Is this okay now that we have made messageCounter atomic? | ||
| // Alternative may be to split the counter space into ranges | ||
| //if n <= s.n { | ||
| // return nil, errors.New("CRITICAL: a duplicate counter value was used") | ||
| //} | ||
| //s.n = n | ||
| nb[0] = 0 | ||
| nb[1] = 0 | ||
| nb[2] = 0 | ||
| nb[3] = 0 | ||
| noiseEndianness.PutUint64(nb[4:], n) | ||
| out = s.c.Seal(out, nb, plaintext, ad) | ||
| //l.Debugf("Encryption: outlen: %d, nonce: %d, ad: %s, plainlen %d", len(out), n, ad, len(plaintext)) | ||
| return out, nil | ||
| } | ||
| } else { | ||
| return nil, errors.New("no cipher state available to encrypt") | ||
| } | ||
| } | ||
|
|
||
| func (s *NebulaCipherState) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) { | ||
| if s != nil { | ||
| nb[0] = 0 | ||
| nb[1] = 0 | ||
| nb[2] = 0 | ||
| nb[3] = 0 | ||
| noiseEndianness.PutUint64(nb[4:], n) | ||
| return s.c.Open(out, nb, ciphertext, ad) | ||
| switch ce := s.c.(type) { | ||
| case cipherAEADDanger: | ||
| return ce.DecryptDanger(out, ad, ciphertext, n, nb) | ||
| default: | ||
| nb[0] = 0 | ||
| nb[1] = 0 | ||
| nb[2] = 0 | ||
| nb[3] = 0 | ||
| noiseEndianness.PutUint64(nb[4:], n) | ||
| return s.c.Open(out, nb, ciphertext, ad) | ||
| } | ||
| } else { | ||
| return []byte{}, nil | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| //go:build fips140v1.0 | ||
| // +build fips140v1.0 | ||
|
|
||
| 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 fips140 because the Seal function verifies that the | ||
| // nonce is strictly increasing. | ||
| const EncryptLockNeeded = true | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other part of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implemented with a build tag |
||
|
|
||
| // TODO: Use NewGCMWithCounterNonce once available: | ||
| // - https://github.com/golang/go/issues/73110 | ||
| // Using tls.aeadAESGCM gives us the TLS 1.2 GCM, which also verifies | ||
| // that the nonce is strictly increasing. | ||
| // | ||
| //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"} | ||
|
|
||
| // tls.aeadAESGCM uses a 4 byte static prefix and an 8 byte nonce | ||
| var emptyPrefix = []byte{0, 0, 0, 0} | ||
|
|
||
| func cipherAESGCM(k [32]byte) noise.Cipher { | ||
| gcm := aeadAESGCM(k[:], emptyPrefix) | ||
| return aeadCipher{ | ||
| gcm, | ||
| func(n uint64) []byte { | ||
| // tls.aeadAESGCM uses a 4 byte static prefix and an 8 byte nonce | ||
| var nonce [8]byte | ||
| binary.BigEndian.PutUint64(nonce[:], 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) | ||
| } | ||
|
|
||
| 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) { | ||
| binary.BigEndian.PutUint64(nb[4:], n) | ||
| return c.Open(out, nb[4:], ciphertext, ad) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| //go:build fips140v1.0 | ||
| // +build fips140v1.0 | ||
|
|
||
| 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) | ||
| }) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
boringcryptooutputs with a suffix on the folder like:build/linux-amd64-boringcrypto/nebulafips140outputs to a colliding path with nonfips140:build/linux-amd64/nebula, seems like a good idea to push this intobuild/linux-amd64-fips140/nebula.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch! let me rework this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
release-fips140andbin-fips140for this