--- /dev/null
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !fips140v1.0
+
+package cipher_test
+
+import (
+ "crypto/cipher"
+ "crypto/internal/fips140"
+ fipsaes "crypto/internal/fips140/aes"
+ "crypto/internal/fips140/aes/gcm"
+ "encoding/binary"
+ "math"
+ "testing"
+)
+
+func TestGCMNoncesFIPSV2(t *testing.T) {
+ tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
+ fips140.ResetServiceIndicator()
+ aead.Seal(nil, nonce, []byte("x"), nil)
+ return fips140.ServiceIndicator()
+ }
+ expectOK := func(t *testing.T, aead cipher.AEAD, nonce []byte) {
+ t.Helper()
+ if !tryNonce(aead, nonce) {
+ t.Errorf("expected service indicator true for %x", nonce)
+ }
+ }
+ expectPanic := func(t *testing.T, aead cipher.AEAD, nonce []byte) {
+ t.Helper()
+ defer func() {
+ t.Helper()
+ if recover() == nil {
+ t.Errorf("expected panic for %x", nonce)
+ }
+ }()
+ tryNonce(aead, nonce)
+ }
+
+ t.Run("NewGCMWithXORCounterNonce", func(t *testing.T) {
+ newGCM := func() *gcm.GCMWithXORCounterNonce {
+ key := make([]byte, 16)
+ block, _ := fipsaes.New(key)
+ aead, _ := gcm.NewGCMWithXORCounterNonce(block)
+ return aead
+ }
+ nonce := func(mask []byte, counter uint64) []byte {
+ nonce := make([]byte, 12)
+ copy(nonce, mask)
+ n := binary.BigEndian.AppendUint64(nil, counter)
+ for i, b := range n {
+ nonce[4+i] ^= b
+ }
+ return nonce
+ }
+
+ for _, mask := range [][]byte{
+ decodeHex(t, "ffffffffffffffffffffffff"),
+ decodeHex(t, "aabbccddeeff001122334455"),
+ decodeHex(t, "000000000000000000000000"),
+ } {
+ g := newGCM()
+ // Mask is derived from first invocation with zero nonce.
+ expectOK(t, g, nonce(mask, 0))
+ expectOK(t, g, nonce(mask, 1))
+ expectOK(t, g, nonce(mask, 100))
+ expectPanic(t, g, nonce(mask, 100))
+ expectPanic(t, g, nonce(mask, 99))
+ expectOK(t, g, nonce(mask, math.MaxUint64-2))
+ expectOK(t, g, nonce(mask, math.MaxUint64-1))
+ expectPanic(t, g, nonce(mask, math.MaxUint64))
+ expectPanic(t, g, nonce(mask, 0))
+
+ g = newGCM()
+ g.SetNoncePrefixAndMask(mask)
+ expectOK(t, g, nonce(mask, 0xFFFFFFFF))
+ expectOK(t, g, nonce(mask, math.MaxUint64-2))
+ expectOK(t, g, nonce(mask, math.MaxUint64-1))
+ expectPanic(t, g, nonce(mask, math.MaxUint64))
+ expectPanic(t, g, nonce(mask, 0))
+
+ g = newGCM()
+ g.SetNoncePrefixAndMask(mask)
+ expectOK(t, g, nonce(mask, math.MaxUint64-1))
+ expectPanic(t, g, nonce(mask, math.MaxUint64))
+ expectPanic(t, g, nonce(mask, 0))
+ }
+ })
+}
})
}
-func TestFIPSServiceIndicator(t *testing.T) {
- newGCM := func() cipher.AEAD {
- key := make([]byte, 16)
- block, _ := fipsaes.New(key)
- aead, _ := gcm.NewGCMWithCounterNonce(block)
- return aead
- }
+func TestGCMNonces(t *testing.T) {
tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
fips140.ResetServiceIndicator()
aead.Seal(nil, nonce, []byte("x"), nil)
return fips140.ServiceIndicator()
}
- expectTrue := func(t *testing.T, aead cipher.AEAD, nonce []byte) {
+ expectOK := func(t *testing.T, aead cipher.AEAD, nonce []byte) {
t.Helper()
if !tryNonce(aead, nonce) {
t.Errorf("expected service indicator true for %x", nonce)
tryNonce(aead, nonce)
}
- g := newGCM()
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0})
- expectTrue(t, g, []byte{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0})
- // Changed name.
- expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0})
-
- g = newGCM()
- expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
- // Went down.
- expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
-
- g = newGCM()
- expectTrue(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})
- expectTrue(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13})
- // Did not increment.
- expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13})
-
- g = newGCM()
- expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00})
- expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
- // Wrap is ok as long as we don't run out of values.
- expectTrue(t, g, []byte{1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0})
- expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe})
- // Run out of counters.
- expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff})
-
- g = newGCM()
- expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
- // Wrap with overflow.
- expectPanic(t, g, []byte{1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0})
-}
-
-func TestGCMForSSH(t *testing.T) {
- // incIV from x/crypto/ssh/cipher.go.
- incIV := func(iv []byte) {
- for i := 4 + 7; i >= 4; i-- {
- iv[i]++
- if iv[i] != 0 {
- break
- }
+ t.Run("NewGCMWithCounterNonce", func(t *testing.T) {
+ newGCM := func() cipher.AEAD {
+ key := make([]byte, 16)
+ block, _ := fipsaes.New(key)
+ aead, _ := gcm.NewGCMWithCounterNonce(block)
+ return aead
}
- }
- expectOK := func(aead cipher.AEAD, iv []byte) {
- aead.Seal(nil, iv, []byte("hello, world"), nil)
- }
+ g := newGCM()
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0})
+ expectOK(t, g, []byte{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0})
+ // Changed name.
+ expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0})
+
+ g = newGCM()
+ expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
+ // Went down.
+ expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+
+ g = newGCM()
+ expectOK(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})
+ expectOK(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13})
+ // Did not increment.
+ expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13})
+
+ g = newGCM()
+ expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00})
+ expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
+ // Wrap is ok as long as we don't run out of values.
+ expectOK(t, g, []byte{1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0})
+ expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe})
+ // Run out of counters.
+ expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff})
+
+ g = newGCM()
+ expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
+ // Wrap with overflow.
+ expectPanic(t, g, []byte{1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0})
+ })
- expectPanic := func(aead cipher.AEAD, iv []byte) {
- defer func() {
- if recover() == nil {
- t.Errorf("expected panic")
+ t.Run("NewGCMForSSH", func(t *testing.T) {
+ newGCM := func() cipher.AEAD {
+ key := make([]byte, 16)
+ block, _ := fipsaes.New(key)
+ aead, _ := gcm.NewGCMForSSH(block)
+ return aead
+ }
+ // incIV from x/crypto/ssh/cipher.go.
+ incIV := func(iv []byte) {
+ for i := 4 + 7; i >= 4; i-- {
+ iv[i]++
+ if iv[i] != 0 {
+ break
+ }
}
- }()
- aead.Seal(nil, iv, []byte("hello, world"), nil)
- }
+ }
- key := make([]byte, 16)
- block, _ := fipsaes.New(key)
- aead, err := gcm.NewGCMForSSH(block)
- if err != nil {
- t.Fatal(err)
- }
- iv := decodeHex(t, "11223344"+"0000000000000000")
- expectOK(aead, iv)
- incIV(iv)
- expectOK(aead, iv)
- iv = decodeHex(t, "11223344"+"fffffffffffffffe")
- expectOK(aead, iv)
- incIV(iv)
- expectPanic(aead, iv)
-
- aead, _ = gcm.NewGCMForSSH(block)
- iv = decodeHex(t, "11223344"+"fffffffffffffffe")
- expectOK(aead, iv)
- incIV(iv)
- expectOK(aead, iv)
- incIV(iv)
- expectOK(aead, iv)
- incIV(iv)
- expectOK(aead, iv)
-
- aead, _ = gcm.NewGCMForSSH(block)
- iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa")
- expectOK(aead, iv)
- iv = decodeHex(t, "11223344"+"ffffffffffffffff")
- expectOK(aead, iv)
- incIV(iv)
- expectOK(aead, iv)
- iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8")
- expectOK(aead, iv)
- incIV(iv)
- expectPanic(aead, iv)
- iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb")
- expectPanic(aead, iv)
+ aead := newGCM()
+ iv := decodeHex(t, "11223344"+"0000000000000000")
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectOK(t, aead, iv)
+ iv = decodeHex(t, "11223344"+"fffffffffffffffe")
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectPanic(t, aead, iv)
+
+ // Wrapping is ok as long as we don't run out of values.
+ aead = newGCM()
+ iv = decodeHex(t, "11223344"+"fffffffffffffffe")
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectOK(t, aead, iv)
+
+ aead = newGCM()
+ iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa")
+ expectOK(t, aead, iv)
+ iv = decodeHex(t, "11223344"+"ffffffffffffffff")
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectOK(t, aead, iv)
+ iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8")
+ expectOK(t, aead, iv)
+ incIV(iv)
+ expectPanic(t, aead, iv)
+ iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb")
+ expectPanic(t, aead, iv)
+ })
}
func decodeHex(t *testing.T, s string) []byte {
"crypto/internal/fips140/alias"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140deps/byteorder"
+ "errors"
"math"
)
// NewGCMWithCounterNonce returns a new AEAD that works like GCM, but enforces
// the construction of deterministic nonces. The nonce must be 96 bits, the
// first 32 bits must be an encoding of the module name, and the last 64 bits
-// must be a counter.
+// must be a counter. The starting value of the counter is set on the first call
+// to Seal, and each subsequent call must increment it as a big-endian uint64.
+// If the counter reaches the starting value minus one, Seal will panic.
//
// This complies with FIPS 140-3 IG C.H Scenario 3.
func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) {
return &GCMWithCounterNonce{g: *g}, nil
}
+// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the
+// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325,
+// Section 7.2.1.
+//
+// This complies with FIPS 140-3 IG C.H Scenario 1.a.
+func NewGCMForTLS12(cipher *aes.Block) (*GCMWithCounterNonce, error) {
+ g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
+ if err != nil {
+ return nil, err
+ }
+ // TLS 1.2 counters always start at zero.
+ return &GCMWithCounterNonce{g: *g, startReady: true}, nil
+}
+
+// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the
+// construction of nonces as specified in RFC 5647.
+//
+// This complies with FIPS 140-3 IG C.H Scenario 1.d.
+func NewGCMForSSH(cipher *aes.Block) (*GCMWithCounterNonce, error) {
+ g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
+ if err != nil {
+ return nil, err
+ }
+ return &GCMWithCounterNonce{g: *g}, nil
+}
+
type GCMWithCounterNonce struct {
- g GCM
- ready bool
- fixedName uint32
- start uint64
- next uint64
+ g GCM
+ prefixReady bool
+ prefix uint32
+ startReady bool
+ start uint64
+ next uint64
}
func (g *GCMWithCounterNonce) NonceSize() int { return gcmStandardNonceSize }
func (g *GCMWithCounterNonce) Overhead() int { return gcmTagSize }
+// Seal implements the [cipher.AEAD] interface, checking that the nonce prefix
+// is stable and that the counter is strictly increasing.
+//
+// It is not safe for concurrent use.
func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte {
if len(nonce) != gcmStandardNonceSize {
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")
+ }
+
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
- if !g.ready {
- // The first invocation sets the fixed name encoding and start counter.
- g.ready = true
+ if !g.startReady {
+ // The first invocation sets the starting counter, if not fixed.
+ g.startReady = true
g.start = counter
- g.fixedName = byteorder.BEUint32(nonce[:4])
- }
- if g.fixedName != byteorder.BEUint32(nonce[:4]) {
- panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce")
}
counter -= g.start
- // Ensure the counter is monotonically increasing.
+ // Ensure the counter is strictly increasing.
if counter == math.MaxUint64 {
- panic("crypto/cipher: counter wrapped")
+ panic("crypto/cipher: counter exhausted")
}
if counter < g.next {
- panic("crypto/cipher: counter decreased")
+ panic("crypto/cipher: counter decreased or remained the same")
}
g.next = counter + 1
return g.g.Open(dst, nonce, ciphertext, data)
}
-// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the
-// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325,
-// Section 7.2.1.
+// NewGCMWithXORCounterNonce returns a new AEAD that works like GCM, but
+// enforces the construction of deterministic nonces. The nonce must be 96 bits,
+// the first 32 bits must be an encoding of the module name, and the last 64
+// bits must be a counter XOR'd with a fixed value. The module name and XOR mask
+// can be set with [GCMWithCounterNonce.SetNoncePrefixAndMask], or they are set
+// on the first call to Seal, assuming the counter starts at zero. Each
+// subsequent call must increment the counter as a big-endian uint64. If the
+// counter reaches 2⁶⁴ minus one, Seal will panic.
//
-// This complies with FIPS 140-3 IG C.H Scenario 1.a.
-func NewGCMForTLS12(cipher *aes.Block) (*GCMForTLS12, error) {
+// This complies with FIPS 140-3 IG C.H Scenario 3.
+func NewGCMWithXORCounterNonce(cipher *aes.Block) (*GCMWithXORCounterNonce, error) {
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
if err != nil {
return nil, err
}
- return &GCMForTLS12{g: *g}, nil
-}
-
-type GCMForTLS12 struct {
- g GCM
- next uint64
-}
-
-func (g *GCMForTLS12) NonceSize() int { return gcmStandardNonceSize }
-
-func (g *GCMForTLS12) Overhead() int { return gcmTagSize }
-
-func (g *GCMForTLS12) Seal(dst, nonce, plaintext, data []byte) []byte {
- if len(nonce) != gcmStandardNonceSize {
- panic("crypto/cipher: incorrect nonce length given to GCM")
- }
-
- counter := byteorder.BEUint64(nonce[len(nonce)-8:])
-
- // Ensure the counter is monotonically increasing.
- if counter == math.MaxUint64 {
- panic("crypto/cipher: counter wrapped")
- }
- if counter < g.next {
- panic("crypto/cipher: counter decreased")
- }
- g.next = counter + 1
-
- fips140.RecordApproved()
- return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
-}
-
-func (g *GCMForTLS12) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
- fips140.RecordApproved()
- return g.g.Open(dst, nonce, ciphertext, data)
+ return &GCMWithXORCounterNonce{g: *g}, nil
}
// NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the
// construction of nonces as specified in RFC 8446, Section 5.3.
-func NewGCMForTLS13(cipher *aes.Block) (*GCMForTLS13, error) {
+//
+// This complies with FIPS 140-3 IG C.H Scenario 1.a.
+func NewGCMForTLS13(cipher *aes.Block) (*GCMWithXORCounterNonce, error) {
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
if err != nil {
return nil, err
}
- return &GCMForTLS13{g: *g}, nil
+ return &GCMWithXORCounterNonce{g: *g}, nil
}
-type GCMForTLS13 struct {
- g GCM
- ready bool
- mask uint64
- next uint64
-}
-
-func (g *GCMForTLS13) NonceSize() int { return gcmStandardNonceSize }
-
-func (g *GCMForTLS13) Overhead() int { return gcmTagSize }
-
-func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte {
- if len(nonce) != gcmStandardNonceSize {
- panic("crypto/cipher: incorrect nonce length given to GCM")
- }
-
- counter := byteorder.BEUint64(nonce[len(nonce)-8:])
- if !g.ready {
- // In the first call, the counter is zero, so we learn the XOR mask.
- g.ready = true
- g.mask = counter
- }
- counter ^= g.mask
-
- // Ensure the counter is monotonically increasing.
- if counter == math.MaxUint64 {
- panic("crypto/cipher: counter wrapped")
+// NewGCMForQUIC returns a new AEAD that works like GCM, but enforces the
+// construction of nonces as specified in RFC 9001, Section 5.3.
+//
+// Unlike in TLS 1.3, the QUIC nonce counter does not always start at zero, as
+// the packet number does not reset on key updates, so the XOR mask must be
+// provided explicitly instead of being learned on the first Seal call. Note
+// that the nonce passed to Seal must already be XOR'd with the IV, the IV is
+// provided here only to allow Seal to enforce that the counter is strictly
+// increasing.
+//
+// This complies with FIPS 140-3 IG C.H Scenario 5.
+func NewGCMForQUIC(cipher *aes.Block, iv []byte) (*GCMWithXORCounterNonce, error) {
+ g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
+ if err != nil {
+ return nil, err
}
- if counter < g.next {
- panic("crypto/cipher: counter decreased")
+ gcm := &GCMWithXORCounterNonce{g: *g}
+ if err := gcm.SetNoncePrefixAndMask(iv); err != nil {
+ return nil, err
}
- g.next = counter + 1
-
- fips140.RecordApproved()
- return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
+ return gcm, nil
}
-func (g *GCMForTLS13) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
- fips140.RecordApproved()
- return g.g.Open(dst, nonce, ciphertext, data)
+type GCMWithXORCounterNonce struct {
+ g GCM
+ ready bool
+ prefix uint32
+ mask uint64
+ next uint64
}
-// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the
-// construction of nonces as specified in RFC 5647.
+// SetNoncePrefixAndMask sets the fixed prefix and XOR mask for the nonces used
+// in Seal. It must be called before the first call to Seal.
//
-// This complies with FIPS 140-3 IG C.H Scenario 1.d.
-func NewGCMForSSH(cipher *aes.Block) (*GCMForSSH, error) {
- g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
- if err != nil {
- return nil, err
+// The first 32 bits of nonce are used as the fixed prefix, and the last 64 bits
+// are used as the XOR mask.
+//
+// Note that Seal expects the nonce to be already XOR'd with the mask. The mask
+// is provided here only to allow Seal to enforce that the counter is strictly
+// increasing.
+func (g *GCMWithXORCounterNonce) SetNoncePrefixAndMask(nonce []byte) error {
+ if len(nonce) != gcmStandardNonceSize {
+ return errors.New("crypto/cipher: incorrect nonce length given to SetNoncePrefixAndMask")
}
- return &GCMForSSH{g: *g}, nil
-}
-
-type GCMForSSH struct {
- g GCM
- ready bool
- start uint64
- next uint64
+ if g.ready {
+ return errors.New("crypto/cipher: SetNoncePrefixAndMask called twice or after first Seal")
+ }
+ g.prefix = byteorder.BEUint32(nonce[:4])
+ g.mask = byteorder.BEUint64(nonce[4:])
+ g.ready = true
+ return nil
}
-func (g *GCMForSSH) NonceSize() int { return gcmStandardNonceSize }
+func (g *GCMWithXORCounterNonce) NonceSize() int { return gcmStandardNonceSize }
-func (g *GCMForSSH) Overhead() int { return gcmTagSize }
+func (g *GCMWithXORCounterNonce) Overhead() int { return gcmTagSize }
-func (g *GCMForSSH) Seal(dst, nonce, plaintext, data []byte) []byte {
+// Seal implements the [cipher.AEAD] interface, checking that the nonce prefix
+// is stable and that the counter is strictly increasing.
+//
+// It is not safe for concurrent use.
+func (g *GCMWithXORCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte {
if len(nonce) != gcmStandardNonceSize {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
if !g.ready {
- // In the first call we learn the start value.
+ // 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.start = counter
+ g.mask = counter
+ g.prefix = byteorder.BEUint32(nonce[:4])
}
- counter -= g.start
+ if g.prefix != byteorder.BEUint32(nonce[:4]) {
+ panic("crypto/cipher: GCM nonce prefix changed")
+ }
+ counter ^= g.mask
- // Ensure the counter is monotonically increasing.
+ // Ensure the counter is strictly increasing.
if counter == math.MaxUint64 {
- panic("crypto/cipher: counter wrapped")
+ panic("crypto/cipher: counter exhausted")
}
if counter < g.next {
- panic("crypto/cipher: counter decreased")
+ panic("crypto/cipher: counter decreased or remained the same")
}
g.next = counter + 1
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
}
-func (g *GCMForSSH) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
+func (g *GCMWithXORCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
fips140.RecordApproved()
return g.g.Open(dst, nonce, ciphertext, data)
}