crypto/internal/fips140/aes/gcm: add more GCM nonce modes

First, this adds a GCM mode for QUIC, and a generic TLS 1.3/QUIC-like
XOR'd counter mode. QUIC constructs nonces exactly like TLS 1.3, but the
counter does not reset to zero on a key update, so the mask must be
provided explicitly (or we will panic well before running out of
counters because the wrong value is XOR'd out). See the analysis at
https://github.com/quic-go/quic-go/issues/5077#issuecomment-3570352683
for the details of QUIC and FIPS 140-3 compliance (including a
workaround for the key update issue).

Second, this coalesces all the compliance modes around two schemes:
fixed || counter, and fixed XOR counter. The former is used in TLS 1.2
and SSH, and the latter is used in TLS 1.3 and QUIC.

This would not be a backwards compatible change if these were public
APIs, but we only need different versions of the FIPS 140-3 module to be
source-compatible with the standard library, and the callers of these
NewGCMFor* functions only care that the return value implements
cipher.AEAD, at least for now.

This exposes no new public APIs, but lets us get started validating
these functions in v2.0.0 of the FIPS 140-3 module.

Updates #73110

Change-Id: I3d86cf8a3c4a96caf361c29f0db5f9706a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/723760
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Filippo Valsorda 2025-11-24 13:35:15 +01:00 committed by Gopher Robot
parent 1768cb40b8
commit 272df5f6ba
3 changed files with 323 additions and 226 deletions

View file

@ -0,0 +1,91 @@
// 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))
}
})
}

View file

@ -761,19 +761,13 @@ func TestGCMExtraMethods(t *testing.T) {
})
}
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)
@ -790,47 +784,61 @@ func TestFIPSServiceIndicator(t *testing.T) {
tryNonce(aead, nonce)
}
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
}
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})
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()
expectTrue(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, 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})
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()
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})
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.
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})
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()
expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
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})
}
})
func TestGCMForSSH(t *testing.T) {
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-- {
@ -841,57 +849,41 @@ func TestGCMForSSH(t *testing.T) {
}
}
expectOK := func(aead cipher.AEAD, iv []byte) {
aead.Seal(nil, iv, []byte("hello, world"), nil)
}
expectPanic := func(aead cipher.AEAD, iv []byte) {
defer func() {
if recover() == nil {
t.Errorf("expected panic")
}
}()
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)
}
aead := newGCM()
iv := decodeHex(t, "11223344"+"0000000000000000")
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectOK(aead, iv)
expectOK(t, aead, iv)
iv = decodeHex(t, "11223344"+"fffffffffffffffe")
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectPanic(aead, iv)
expectPanic(t, aead, iv)
aead, _ = gcm.NewGCMForSSH(block)
// Wrapping is ok as long as we don't run out of values.
aead = newGCM()
iv = decodeHex(t, "11223344"+"fffffffffffffffe")
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectOK(aead, iv)
expectOK(t, aead, iv)
aead, _ = gcm.NewGCMForSSH(block)
aead = newGCM()
iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa")
expectOK(aead, iv)
expectOK(t, aead, iv)
iv = decodeHex(t, "11223344"+"ffffffffffffffff")
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectOK(aead, iv)
expectOK(t, aead, iv)
iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8")
expectOK(aead, iv)
expectOK(t, aead, iv)
incIV(iv)
expectPanic(aead, iv)
expectPanic(t, aead, iv)
iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb")
expectPanic(aead, iv)
expectPanic(t, aead, iv)
})
}
func decodeHex(t *testing.T, s string) []byte {

View file

@ -10,6 +10,7 @@ import (
"crypto/internal/fips140/alias"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140deps/byteorder"
"errors"
"math"
)
@ -45,7 +46,9 @@ func SealWithRandomNonce(g *GCM, nonce, out, plaintext, additionalData []byte) {
// 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) {
@ -56,10 +59,37 @@ 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
prefixReady bool
prefix uint32
startReady bool
start uint64
next uint64
}
@ -68,29 +98,38 @@ 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")
}
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
if !g.ready {
// The first invocation sets the fixed name encoding and start counter.
g.ready = true
g.start = counter
g.fixedName = byteorder.BEUint32(nonce[:4])
if !g.prefixReady {
// The first invocation sets the fixed prefix.
g.prefixReady = true
g.prefix = byteorder.BEUint32(nonce[:4])
}
if g.fixedName != byteorder.BEUint32(nonce[:4]) {
panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce")
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
// 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
@ -103,93 +142,122 @@ func (g *GCMWithCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte,
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 {
// 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
}
gcm := &GCMWithXORCounterNonce{g: *g}
if err := gcm.SetNoncePrefixAndMask(iv); err != nil {
return nil, err
}
return gcm, nil
}
type GCMWithXORCounterNonce struct {
g GCM
ready bool
prefix uint32
mask uint64
next uint64
}
func (g *GCMForTLS13) NonceSize() int { return gcmStandardNonceSize }
// SetNoncePrefixAndMask sets the fixed prefix and XOR mask for the nonces used
// in Seal. It must be called before the first call to Seal.
//
// 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")
}
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 *GCMForTLS13) Overhead() int { return gcmTagSize }
func (g *GCMWithXORCounterNonce) NonceSize() int { return gcmStandardNonceSize }
func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte {
func (g *GCMWithXORCounterNonce) 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 *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, the counter is zero, so we learn the XOR mask.
// 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 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
@ -197,61 +265,7 @@ func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte {
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
}
func (g *GCMForTLS13) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
fips140.RecordApproved()
return g.g.Open(dst, nonce, ciphertext, data)
}
// 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) (*GCMForSSH, error) {
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
if err != nil {
return nil, err
}
return &GCMForSSH{g: *g}, nil
}
type GCMForSSH struct {
g GCM
ready bool
start uint64
next uint64
}
func (g *GCMForSSH) NonceSize() int { return gcmStandardNonceSize }
func (g *GCMForSSH) Overhead() int { return gcmTagSize }
func (g *GCMForSSH) 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.
g.ready = true
g.start = counter
}
counter -= g.start
// 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 *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)
}