mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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:
parent
1768cb40b8
commit
272df5f6ba
3 changed files with 323 additions and 226 deletions
91
src/crypto/cipher/gcm_fips140v2.0_test.go
Normal file
91
src/crypto/cipher/gcm_fips140v2.0_test.go
Normal 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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -761,19 +761,13 @@ func TestGCMExtraMethods(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFIPSServiceIndicator(t *testing.T) {
|
func TestGCMNonces(t *testing.T) {
|
||||||
newGCM := func() cipher.AEAD {
|
|
||||||
key := make([]byte, 16)
|
|
||||||
block, _ := fipsaes.New(key)
|
|
||||||
aead, _ := gcm.NewGCMWithCounterNonce(block)
|
|
||||||
return aead
|
|
||||||
}
|
|
||||||
tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
|
tryNonce := func(aead cipher.AEAD, nonce []byte) bool {
|
||||||
fips140.ResetServiceIndicator()
|
fips140.ResetServiceIndicator()
|
||||||
aead.Seal(nil, nonce, []byte("x"), nil)
|
aead.Seal(nil, nonce, []byte("x"), nil)
|
||||||
return fips140.ServiceIndicator()
|
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()
|
t.Helper()
|
||||||
if !tryNonce(aead, nonce) {
|
if !tryNonce(aead, nonce) {
|
||||||
t.Errorf("expected service indicator true for %x", nonce)
|
t.Errorf("expected service indicator true for %x", nonce)
|
||||||
|
|
@ -790,47 +784,61 @@ func TestFIPSServiceIndicator(t *testing.T) {
|
||||||
tryNonce(aead, nonce)
|
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()
|
g := newGCM()
|
||||||
expectTrue(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, 0})
|
||||||
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})
|
||||||
expectTrue(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, 0, 100})
|
||||||
expectTrue(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, 0, 1, 0})
|
||||||
expectTrue(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, 0, 1, 0, 0})
|
||||||
expectTrue(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, 0, 1, 0, 0, 0})
|
||||||
expectTrue(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, 0, 1, 0, 0, 0, 0})
|
||||||
expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0})
|
expectOK(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})
|
expectOK(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, 1, 0, 0, 0, 0, 0, 0, 0})
|
||||||
// Changed name.
|
// Changed name.
|
||||||
expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0})
|
expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||||
|
|
||||||
g = newGCM()
|
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.
|
// Went down.
|
||||||
expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||||
|
|
||||||
g = newGCM()
|
g = newGCM()
|
||||||
expectTrue(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, 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, 13})
|
||||||
// Did not increment.
|
// Did not increment.
|
||||||
expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13})
|
expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13})
|
||||||
|
|
||||||
g = newGCM()
|
g = newGCM()
|
||||||
expectTrue(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, 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, 0xff})
|
||||||
// Wrap is ok as long as we don't run out of values.
|
// 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})
|
expectOK(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, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe})
|
||||||
// Run out of counters.
|
// Run out of counters.
|
||||||
expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff})
|
expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff})
|
||||||
|
|
||||||
g = newGCM()
|
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.
|
// Wrap with overflow.
|
||||||
expectPanic(t, g, []byte{1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0})
|
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 from x/crypto/ssh/cipher.go.
|
||||||
incIV := func(iv []byte) {
|
incIV := func(iv []byte) {
|
||||||
for i := 4 + 7; i >= 4; i-- {
|
for i := 4 + 7; i >= 4; i-- {
|
||||||
|
|
@ -841,57 +849,41 @@ func TestGCMForSSH(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expectOK := func(aead cipher.AEAD, iv []byte) {
|
aead := newGCM()
|
||||||
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)
|
|
||||||
}
|
|
||||||
iv := decodeHex(t, "11223344"+"0000000000000000")
|
iv := decodeHex(t, "11223344"+"0000000000000000")
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(iv)
|
incIV(iv)
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
iv = decodeHex(t, "11223344"+"fffffffffffffffe")
|
iv = decodeHex(t, "11223344"+"fffffffffffffffe")
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(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")
|
iv = decodeHex(t, "11223344"+"fffffffffffffffe")
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(iv)
|
incIV(iv)
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(iv)
|
incIV(iv)
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(iv)
|
incIV(iv)
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
|
|
||||||
aead, _ = gcm.NewGCMForSSH(block)
|
aead = newGCM()
|
||||||
iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa")
|
iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa")
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
iv = decodeHex(t, "11223344"+"ffffffffffffffff")
|
iv = decodeHex(t, "11223344"+"ffffffffffffffff")
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(iv)
|
incIV(iv)
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8")
|
iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8")
|
||||||
expectOK(aead, iv)
|
expectOK(t, aead, iv)
|
||||||
incIV(iv)
|
incIV(iv)
|
||||||
expectPanic(aead, iv)
|
expectPanic(t, aead, iv)
|
||||||
iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb")
|
iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb")
|
||||||
expectPanic(aead, iv)
|
expectPanic(t, aead, iv)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeHex(t *testing.T, s string) []byte {
|
func decodeHex(t *testing.T, s string) []byte {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"crypto/internal/fips140/alias"
|
"crypto/internal/fips140/alias"
|
||||||
"crypto/internal/fips140/drbg"
|
"crypto/internal/fips140/drbg"
|
||||||
"crypto/internal/fips140deps/byteorder"
|
"crypto/internal/fips140deps/byteorder"
|
||||||
|
"errors"
|
||||||
"math"
|
"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
|
// NewGCMWithCounterNonce returns a new AEAD that works like GCM, but enforces
|
||||||
// the construction of deterministic nonces. The nonce must be 96 bits, the
|
// 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
|
// 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.
|
// This complies with FIPS 140-3 IG C.H Scenario 3.
|
||||||
func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) {
|
func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) {
|
||||||
|
|
@ -56,10 +59,37 @@ func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) {
|
||||||
return &GCMWithCounterNonce{g: *g}, nil
|
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 {
|
type GCMWithCounterNonce struct {
|
||||||
g GCM
|
g GCM
|
||||||
ready bool
|
prefixReady bool
|
||||||
fixedName uint32
|
prefix uint32
|
||||||
|
startReady bool
|
||||||
start uint64
|
start uint64
|
||||||
next uint64
|
next uint64
|
||||||
}
|
}
|
||||||
|
|
@ -68,29 +98,38 @@ func (g *GCMWithCounterNonce) NonceSize() int { return gcmStandardNonceSize }
|
||||||
|
|
||||||
func (g *GCMWithCounterNonce) Overhead() int { return gcmTagSize }
|
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 {
|
func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||||
if len(nonce) != gcmStandardNonceSize {
|
if len(nonce) != gcmStandardNonceSize {
|
||||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||||
}
|
}
|
||||||
|
|
||||||
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
|
if !g.prefixReady {
|
||||||
if !g.ready {
|
// The first invocation sets the fixed prefix.
|
||||||
// The first invocation sets the fixed name encoding and start counter.
|
g.prefixReady = true
|
||||||
g.ready = true
|
g.prefix = byteorder.BEUint32(nonce[:4])
|
||||||
g.start = counter
|
|
||||||
g.fixedName = byteorder.BEUint32(nonce[:4])
|
|
||||||
}
|
}
|
||||||
if g.fixedName != byteorder.BEUint32(nonce[:4]) {
|
if g.prefix != byteorder.BEUint32(nonce[:4]) {
|
||||||
panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce")
|
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 -= g.start
|
||||||
|
|
||||||
// Ensure the counter is monotonically increasing.
|
// Ensure the counter is strictly increasing.
|
||||||
if counter == math.MaxUint64 {
|
if counter == math.MaxUint64 {
|
||||||
panic("crypto/cipher: counter wrapped")
|
panic("crypto/cipher: counter exhausted")
|
||||||
}
|
}
|
||||||
if counter < g.next {
|
if counter < g.next {
|
||||||
panic("crypto/cipher: counter decreased")
|
panic("crypto/cipher: counter decreased or remained the same")
|
||||||
}
|
}
|
||||||
g.next = counter + 1
|
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)
|
return g.g.Open(dst, nonce, ciphertext, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the
|
// NewGCMWithXORCounterNonce returns a new AEAD that works like GCM, but
|
||||||
// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325,
|
// enforces the construction of deterministic nonces. The nonce must be 96 bits,
|
||||||
// Section 7.2.1.
|
// 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.
|
// This complies with FIPS 140-3 IG C.H Scenario 3.
|
||||||
func NewGCMForTLS12(cipher *aes.Block) (*GCMForTLS12, error) {
|
func NewGCMWithXORCounterNonce(cipher *aes.Block) (*GCMWithXORCounterNonce, error) {
|
||||||
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &GCMForTLS12{g: *g}, nil
|
return &GCMWithXORCounterNonce{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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the
|
// NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the
|
||||||
// construction of nonces as specified in RFC 8446, Section 5.3.
|
// 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)
|
g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
g GCM
|
||||||
ready bool
|
ready bool
|
||||||
|
prefix uint32
|
||||||
mask uint64
|
mask uint64
|
||||||
next 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 {
|
if len(nonce) != gcmStandardNonceSize {
|
||||||
panic("crypto/cipher: incorrect nonce length given to GCM")
|
panic("crypto/cipher: incorrect nonce length given to GCM")
|
||||||
}
|
}
|
||||||
|
|
||||||
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
|
counter := byteorder.BEUint64(nonce[len(nonce)-8:])
|
||||||
if !g.ready {
|
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.ready = true
|
||||||
g.mask = counter
|
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
|
counter ^= g.mask
|
||||||
|
|
||||||
// Ensure the counter is monotonically increasing.
|
// Ensure the counter is strictly increasing.
|
||||||
if counter == math.MaxUint64 {
|
if counter == math.MaxUint64 {
|
||||||
panic("crypto/cipher: counter wrapped")
|
panic("crypto/cipher: counter exhausted")
|
||||||
}
|
}
|
||||||
if counter < g.next {
|
if counter < g.next {
|
||||||
panic("crypto/cipher: counter decreased")
|
panic("crypto/cipher: counter decreased or remained the same")
|
||||||
}
|
}
|
||||||
g.next = counter + 1
|
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)
|
return g.g.sealAfterIndicator(dst, nonce, plaintext, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GCMForTLS13) 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
fips140.RecordApproved()
|
fips140.RecordApproved()
|
||||||
return g.g.Open(dst, nonce, ciphertext, data)
|
return g.g.Open(dst, nonce, ciphertext, data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue