0
0
mirror of https://github.com/XTLS/REALITY.git synced 2025-08-22 14:38:35 +00:00
XTLS_REALITY/gcm/gcm.go
2025-05-05 11:40:53 -04:00

143 lines
4.2 KiB
Go

// Copyright 2013 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 gcm
import (
"errors"
"github.com/xtls/reality/aes"
"github.com/xtls/reality/alias"
)
// GCM represents a Galois Counter Mode with a specific key.
type GCM struct {
cipher aes.Block
nonceSize int
tagSize int
gcmPlatformData
}
func New(cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) {
// This function is outlined to let the allocation happen on the parent stack.
return newGCM(&GCM{}, cipher, nonceSize, tagSize)
}
// newGCM is marked go:noinline to avoid it inlining into New, and making New
// too complex to inline itself.
//
//go:noinline
func newGCM(g *GCM, cipher *aes.Block, nonceSize, tagSize int) (*GCM, 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.BlockSize() != gcmBlockSize {
return nil, errors.New("cipher: NewGCM requires 128-bit block cipher")
}
g.cipher = *cipher
g.nonceSize = nonceSize
g.tagSize = tagSize
initGCM(g)
return g, nil
}
const (
gcmBlockSize = 16
gcmTagSize = 16
gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes.
gcmStandardNonceSize = 12
)
func (g *GCM) NonceSize() int {
return g.nonceSize
}
func (g *GCM) Overhead() int {
return g.tagSize
}
func (g *GCM) Seal(dst, nonce, plaintext, data []byte) []byte {
// fips140.RecordNonApproved()
return g.sealAfterIndicator(dst, nonce, plaintext, data)
}
func (g *GCM) sealAfterIndicator(dst, nonce, plaintext, data []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, data) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
seal(out, g, nonce, plaintext, data)
return ret
}
var errOpen = errors.New("cipher: message authentication failed")
func (g *GCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
if len(nonce) != g.nonceSize {
panic("crypto/cipher: incorrect nonce length given to GCM")
}
// Sanity check to prevent the authentication from always succeeding if an
// implementation leaves tagSize uninitialized, for example.
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, data) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
// fips140.RecordApproved()
if err := open(out, g, nonce, ciphertext, data); err != nil {
// 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, err
}
return ret, nil
}
// 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
}