crypto/internal/fips140/aes/gcm: don't panic on bad nonces out of FIPS 140-3 mode

The enforcement is good beyond compliance if it is correct, but I am
more nervous about accidental DoS due to mismatches between how the
caller calculates a nonce and how the enforcement expects it to be
calculated.

We need to have this enforcement in FIPS 140-3 mode, but no need to blow
ourselves up when it's off.

If all goes well, this code is unreachable anyway.

Change-Id: If73ec59ebbd283b0e5506354961a87a06a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/728504
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Filippo Valsorda 2025-12-09 00:47:26 +01:00
parent 550c0c898b
commit cd873cf7e9
3 changed files with 80 additions and 43 deletions

View File

@ -8,15 +8,32 @@ package cipher_test
import (
"crypto/cipher"
"crypto/internal/cryptotest"
"crypto/internal/fips140"
fipsaes "crypto/internal/fips140/aes"
"crypto/internal/fips140/aes/gcm"
"encoding/binary"
"internal/testenv"
"math"
"testing"
)
func TestGCMNoncesFIPSV2(t *testing.T) {
cryptotest.MustSupportFIPS140(t)
if !fips140.Enabled {
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestGCMNoncesFIPSV2$", "-test.v")
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, "GODEBUG=fips140=on")
out, err := cmd.CombinedOutput()
if len(out) != 0 {
t.Logf("\n%s", out)
}
if err != nil {
t.Errorf("fips140=on subprocess failed: %v", err)
}
return
}
tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
fips140.ResetServiceIndicator()
aead.Seal(nil, nonce, []byte("x"), nil)

View File

@ -17,6 +17,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"internal/testenv"
"io"
"reflect"
"testing"
@ -761,7 +762,22 @@ func TestGCMExtraMethods(t *testing.T) {
})
}
func TestGCMNonces(t *testing.T) {
func TestGCMNoncesFIPSV1(t *testing.T) {
cryptotest.MustSupportFIPS140(t)
if !fips140.Enabled {
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestGCMNoncesFIPSV1$", "-test.v")
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, "GODEBUG=fips140=on")
out, err := cmd.CombinedOutput()
if len(out) != 0 {
t.Logf("\n%s", out)
}
if err != nil {
t.Errorf("fips140=on subprocess failed: %v", err)
}
return
}
tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
fips140.ResetServiceIndicator()
aead.Seal(nil, nonce, []byte("x"), nil)

View File

@ -107,31 +107,33 @@ func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
if !g.prefixReady {
// The first invocation sets the fixed prefix.
g.prefixReady = true
g.prefix = byteorder.BEUint32(nonce[:4])
}
if g.prefix != byteorder.BEUint32(nonce[:4]) {
panic("crypto/cipher: GCM nonce prefix changed")
}
if fips140.Enabled {
if !g.prefixReady {
// The first invocation sets the fixed prefix.
g.prefixReady = true
g.prefix = byteorder.BEUint32(nonce[:4])
}
if g.prefix != byteorder.BEUint32(nonce[:4]) {
panic("crypto/cipher: GCM nonce prefix changed")
}
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
if !g.startReady {
// The first invocation sets the starting counter, if not fixed.
g.startReady = true
g.start = counter
}
counter -= g.start
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
if !g.startReady {
// The first invocation sets the starting counter, if not fixed.
g.startReady = true
g.start = counter
}
counter -= g.start
// Ensure the counter is strictly increasing.
if counter == math.MaxUint64 {
panic("crypto/cipher: counter exhausted")
// Ensure the counter is strictly increasing.
if counter == math.MaxUint64 {
panic("crypto/cipher: counter exhausted")
}
if counter < g.next {
panic("crypto/cipher: counter decreased or remained the same")
}
g.next = counter + 1
}
if counter < g.next {
panic("crypto/cipher: counter decreased or remained the same")
}
g.next = counter + 1
fips140.RecordApproved()
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
@ -250,28 +252,30 @@ func (g *GCMWithXORCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte
panic("crypto/cipher: incorrect nonce length given to GCM")
}
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
if !g.ready {
// In the first call, if [GCMWithXORCounterNonce.SetNoncePrefixAndMask]
// wasn't used, we assume the counter is zero to learn the XOR mask and
// fixed prefix.
g.ready = true
g.mask = counter
g.prefix = byteorder.BEUint32(nonce[:4])
}
if g.prefix != byteorder.BEUint32(nonce[:4]) {
panic("crypto/cipher: GCM nonce prefix changed")
}
counter ^= g.mask
if fips140.Enabled {
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
if !g.ready {
// In the first call, if [GCMWithXORCounterNonce.SetNoncePrefixAndMask]
// wasn't used, we assume the counter is zero to learn the XOR mask and
// fixed prefix.
g.ready = true
g.mask = counter
g.prefix = byteorder.BEUint32(nonce[:4])
}
if g.prefix != byteorder.BEUint32(nonce[:4]) {
panic("crypto/cipher: GCM nonce prefix changed")
}
counter ^= g.mask
// Ensure the counter is strictly increasing.
if counter == math.MaxUint64 {
panic("crypto/cipher: counter exhausted")
// Ensure the counter is strictly increasing.
if counter == math.MaxUint64 {
panic("crypto/cipher: counter exhausted")
}
if counter < g.next {
panic("crypto/cipher: counter decreased or remained the same")
}
g.next = counter + 1
}
if counter < g.next {
panic("crypto/cipher: counter decreased or remained the same")
}
g.next = counter + 1
fips140.RecordApproved()
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)