blob: 73493f6cd2311b02fdc5edc813579fe17c6171c7 [file] [log] [blame]
// Copyright 2024 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.
package cipher
import (
"crypto/internal/fips140/aes"
"crypto/internal/fips140/aes/gcm"
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"crypto/subtle"
"errors"
"internal/byteorder"
)
const (
gcmBlockSize = 16
gcmStandardNonceSize = 12
gcmTagSize = 16
gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes.
)
// NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode
// with the standard nonce length.
//
// In general, the GHASH operation performed by this implementation of GCM is not constant-time.
// An exception is when the underlying [Block] was created by aes.NewCipher
// on systems with hardware support for AES. See the [crypto/aes] package documentation for details.
func NewGCM(cipher Block) (AEAD, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
}
return newGCM(cipher, gcmStandardNonceSize, gcmTagSize)
}
// NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois
// Counter Mode, which accepts nonces of the given length. The length must not
// be zero.
//
// Only use this function if you require compatibility with an existing
// cryptosystem that uses non-standard nonce lengths. All other users should use
// [NewGCM], which is faster and more resistant to misuse.
func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
}
return newGCM(cipher, size, gcmTagSize)
}
// NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois
// Counter Mode, which generates tags with the given length.
//
// Tag sizes between 12 and 16 bytes are allowed.
//
// Only use this function if you require compatibility with an existing
// cryptosystem that uses non-standard tag lengths. All other users should use
// [NewGCM], which is more resistant to misuse.
func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
}
return newGCM(cipher, gcmStandardNonceSize, tagSize)
}
func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) {
c, ok := cipher.(*aes.Block)
if !ok {
if fips140only.Enabled {
return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode")
}
return newGCMFallback(cipher, nonceSize, tagSize)
}
// We don't return gcm.New directly, because it would always return a non-nil
// AEAD interface value with type *gcm.GCM even if the *gcm.GCM is nil.
g, err := gcm.New(c, nonceSize, tagSize)
if err != nil {
return nil, err
}
return g, nil
}
// NewGCMWithRandomNonce returns the given cipher wrapped in Galois Counter
// Mode, with randomly-generated nonces. The cipher must have been created by
// [crypto/aes.NewCipher].
//
// It generates a random 96-bit nonce, which is prepended to the ciphertext by Seal,
// and is extracted from the ciphertext by Open. The NonceSize of the AEAD is zero,
// while the Overhead is 28 bytes (the combination of nonce size and tag size).
//
// A given key MUST NOT be used to encrypt more than 2^32 messages, to limit the
// risk of a random nonce collision to negligible levels.
func NewGCMWithRandomNonce(cipher Block) (AEAD, error) {
c, ok := cipher.(*aes.Block)
if !ok {
return nil, errors.New("cipher: NewGCMWithRandomNonce requires aes.Block")
}
g, err := gcm.New(c, gcmStandardNonceSize, gcmTagSize)
if err != nil {
return nil, err
}
return gcmWithRandomNonce{g}, nil
}
type gcmWithRandomNonce struct {
*gcm.GCM
}
func (g gcmWithRandomNonce) NonceSize() int {
return 0
}
func (g gcmWithRandomNonce) Overhead() int {
return gcmStandardNonceSize + gcmTagSize
}
func (g gcmWithRandomNonce) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != 0 {
panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
}
ret, out := sliceForAppend(dst, gcmStandardNonceSize+len(plaintext)+gcmTagSize)
if alias.InexactOverlap(out, plaintext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
nonce = out[:gcmStandardNonceSize]
ciphertext := out[gcmStandardNonceSize:]
// The AEAD interface allows using plaintext[:0] or ciphertext[:0] as dst.
//
// This is kind of a problem when trying to prepend or trim a nonce, because the
// actual AES-GCTR blocks end up overlapping but not exactly.
//
// In Open, we write the output *before* the input, so unless we do something
// weird like working through a chunk of block backwards, it works out.
//
// In Seal, we could work through the input backwards or intentionally load
// ahead before writing.
//
// However, the crypto/internal/fips140/aes/gcm APIs also check for exact overlap,
// so for now we just do a memmove if we detect overlap.
//
// ┌───────────────────────────┬ ─ ─
// │PPPPPPPPPPPPPPPPPPPPPPPPPPP│ │
// └▽─────────────────────────▲┴ ─ ─
// ╲ Seal ╲
// ╲ Open ╲
// ┌───▼─────────────────────────△──┐
// │NN|CCCCCCCCCCCCCCCCCCCCCCCCCCC|T│
// └────────────────────────────────┘
//
if alias.AnyOverlap(out, plaintext) {
copy(ciphertext, plaintext)
plaintext = ciphertext[:len(plaintext)]
}
gcm.SealWithRandomNonce(g.GCM, nonce, ciphertext, plaintext, additionalData)
return ret
}
func (g gcmWithRandomNonce) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != 0 {
panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
}
if len(ciphertext) < gcmStandardNonceSize+gcmTagSize {
return nil, errOpen
}
ret, out := sliceForAppend(dst, len(ciphertext)-gcmStandardNonceSize-gcmTagSize)
if alias.InexactOverlap(out, ciphertext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
// See the discussion in Seal. Note that if there is any overlap at this
// point, it's because out = ciphertext, so out must have enough capacity
// even if we sliced the tag off. Also note how [AEAD] specifies that "the
// contents of dst, up to its capacity, may be overwritten".
if alias.AnyOverlap(out, ciphertext) {
nonce = make([]byte, gcmStandardNonceSize)
copy(nonce, ciphertext)
copy(out[:len(ciphertext)], ciphertext[gcmStandardNonceSize:])
ciphertext = out[:len(ciphertext)-gcmStandardNonceSize]
} else {
nonce = ciphertext[:gcmStandardNonceSize]
ciphertext = ciphertext[gcmStandardNonceSize:]
}
_, err := g.GCM.Open(out[:0], nonce, ciphertext, additionalData)
if err != nil {
return nil, err
}
return ret, nil
}
// gcmAble is an interface implemented by ciphers that have a specific optimized
// implementation of GCM. crypto/aes doesn't use this anymore, and we'd like to
// eventually remove it.
type gcmAble interface {
NewGCM(nonceSize, tagSize int) (AEAD, error)
}
func newGCMFallback(cipher Block, nonceSize, tagSize int) (AEAD, error) {
if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize {
return nil, errors.New("cipher: incorrect tag size given to GCM")
}
if nonceSize <= 0 {
return nil, errors.New("cipher: the nonce can't have zero length")
}
if cipher, ok := cipher.(gcmAble); ok {
return cipher.NewGCM(nonceSize, tagSize)
}
if cipher.BlockSize() != gcmBlockSize {
return nil, errors.New("cipher: NewGCM requires 128-bit block cipher")
}
return &gcmFallback{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize}, nil
}
// gcmFallback is only used for non-AES ciphers, which regrettably we
// theoretically support. It's a copy of the generic implementation from
// crypto/internal/fips140/aes/gcm/gcm_generic.go, refer to that file for more details.
type gcmFallback struct {
cipher Block
nonceSize int
tagSize int
}
func (g *gcmFallback) NonceSize() int {
return g.nonceSize
}
func (g *gcmFallback) Overhead() int {
return g.tagSize
}
func (g *gcmFallback) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != g.nonceSize {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
if g.nonceSize == 0 {
panic("crypto/cipher: incorrect GCM nonce size")
}
if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize {
panic("crypto/cipher: message too large for GCM")
}
ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
if alias.InexactOverlap(out, plaintext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
var H, counter, tagMask [gcmBlockSize]byte
g.cipher.Encrypt(H[:], H[:])
deriveCounter(&H, &counter, nonce)
gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter)
gcmCounterCryptGeneric(g.cipher, out, plaintext, &counter)
var tag [gcmTagSize]byte
gcmAuth(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData)
copy(out[len(plaintext):], tag[:])
return ret
}
var errOpen = errors.New("cipher: message authentication failed")
func (g *gcmFallback) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != g.nonceSize {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
if g.tagSize < gcmMinimumTagSize {
panic("crypto/cipher: incorrect GCM tag size")
}
if len(ciphertext) < g.tagSize {
return nil, errOpen
}
if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) {
return nil, errOpen
}
ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize)
if alias.InexactOverlap(out, ciphertext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
var H, counter, tagMask [gcmBlockSize]byte
g.cipher.Encrypt(H[:], H[:])
deriveCounter(&H, &counter, nonce)
gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter)
tag := ciphertext[len(ciphertext)-g.tagSize:]
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
var expectedTag [gcmTagSize]byte
gcmAuth(expectedTag[:], &H, &tagMask, ciphertext, additionalData)
if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
// We sometimes decrypt and authenticate concurrently, so we overwrite
// dst in the event of a tag mismatch. To be consistent across platforms
// and to avoid releasing unauthenticated plaintext, we clear the buffer
// in the event of an error.
clear(out)
return nil, errOpen
}
gcmCounterCryptGeneric(g.cipher, out, ciphertext, &counter)
return ret, nil
}
func deriveCounter(H, counter *[gcmBlockSize]byte, nonce []byte) {
if len(nonce) == gcmStandardNonceSize {
copy(counter[:], nonce)
counter[gcmBlockSize-1] = 1
} else {
lenBlock := make([]byte, 16)
byteorder.BEPutUint64(lenBlock[8:], uint64(len(nonce))*8)
J := gcm.GHASH(H, nonce, lenBlock)
copy(counter[:], J)
}
}
func gcmCounterCryptGeneric(b Block, out, src []byte, counter *[gcmBlockSize]byte) {
var mask [gcmBlockSize]byte
for len(src) >= gcmBlockSize {
b.Encrypt(mask[:], counter[:])
gcmInc32(counter)
subtle.XORBytes(out, src, mask[:])
out = out[gcmBlockSize:]
src = src[gcmBlockSize:]
}
if len(src) > 0 {
b.Encrypt(mask[:], counter[:])
gcmInc32(counter)
subtle.XORBytes(out, src, mask[:])
}
}
func gcmInc32(counterBlock *[gcmBlockSize]byte) {
ctr := counterBlock[len(counterBlock)-4:]
byteorder.BEPutUint32(ctr, byteorder.BEUint32(ctr)+1)
}
func gcmAuth(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) {
lenBlock := make([]byte, 16)
byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8)
byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8)
S := gcm.GHASH(H, additionalData, ciphertext, lenBlock)
subtle.XORBytes(out, S, tagMask[:])
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}