Files
tsnet-proxy/vendor/github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305/chacha20poly1305.go
T
2026-10-22 12:27:19 +01:00

163 lines
4.8 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause
//go:build amd64 && gc && !purego
/*
Package chacha20poly1305 provides a ChaCha20-Poly1305 AEAD backed by the SSE assembly kernel golang.org/x/crypto deleted in commit 7ee5970 ("chacha20poly1305: drop pre-AVX assembly impl", v0.52.0). It is used on amd64 CPUs with SSSE3 that cannot use x/crypto's AVX2 kernel.
*/
package chacha20poly1305
import (
"crypto/cipher"
"encoding/binary"
"errors"
"unsafe"
"golang.org/x/sys/cpu"
xchacha20poly1305 "golang.org/x/crypto/chacha20poly1305"
)
const (
KeySize = 32
NonceSize = 12
Overhead = 16
)
// Available reports whether this CPU can run the kernel, which requires SSSE3.
func Available() bool { return cpu.X86.HasSSSE3 }
//go:noescape
func chacha20Poly1305Open(dst []byte, key []uint32, src []byte, ad []byte) bool
//go:noescape
func chacha20Poly1305Seal(dst []byte, key []uint32, src []byte, ad []byte)
type aead struct {
key [KeySize]byte
}
// New returns a ChaCha20-Poly1305 AEAD using the assembly kernel.
func New(key []byte) (cipher.AEAD, error) {
// Delegate to x/crypto rather than checking the key length here: its New also
// refuses ChaCha20-Poly1305 under fips140=only, and this package is reached
// instead of x/crypto on the CPUs it covers.
if _, err := xchacha20poly1305.New(key); err != nil {
return nil, err
}
if !Available() {
return nil, errors.New("chacha20poly1305: CPU lacks SSSE3")
}
a := &aead{}
copy(a.key[:], key)
return a, nil
}
func (a *aead) NonceSize() int { return NonceSize }
func (a *aead) Overhead() int { return Overhead }
// setupState writes a ChaCha20 input matrix to state, per RFC 8439 §2.3.
// Copied from x/crypto's chacha20poly1305_amd64.go.
func setupState(state *[16]uint32, key *[KeySize]byte, nonce []byte) {
state[0] = 0x61707865
state[1] = 0x3320646e
state[2] = 0x79622d32
state[3] = 0x6b206574
state[4] = binary.LittleEndian.Uint32(key[0:4])
state[5] = binary.LittleEndian.Uint32(key[4:8])
state[6] = binary.LittleEndian.Uint32(key[8:12])
state[7] = binary.LittleEndian.Uint32(key[12:16])
state[8] = binary.LittleEndian.Uint32(key[16:20])
state[9] = binary.LittleEndian.Uint32(key[20:24])
state[10] = binary.LittleEndian.Uint32(key[24:28])
state[11] = binary.LittleEndian.Uint32(key[28:32])
state[12] = 0
state[13] = binary.LittleEndian.Uint32(nonce[0:4])
state[14] = binary.LittleEndian.Uint32(nonce[4:8])
state[15] = binary.LittleEndian.Uint32(nonce[8:12])
}
func (a *aead) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != NonceSize {
panic("chacha20poly1305: bad nonce length passed to Seal")
}
if uint64(len(plaintext)) > (1<<38)-64 {
panic("chacha20poly1305: plaintext too large")
}
var state [16]uint32
setupState(&state, &a.key, nonce)
ret, out := sliceForAppend(dst, len(plaintext)+Overhead)
if inexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap of output and input")
}
if anyOverlap(out, additionalData) {
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
}
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
return ret
}
var errOpen = errors.New("chacha20poly1305: message authentication failed")
func (a *aead) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != NonceSize {
// Matches x/crypto: a wrong-size nonce is API misuse, not a decrypt failure.
panic("chacha20poly1305: bad nonce length passed to Open")
}
if len(ciphertext) < Overhead {
return nil, errOpen
}
if uint64(len(ciphertext)) > (1<<38)-48 {
panic("chacha20poly1305: ciphertext too large")
}
var state [16]uint32
setupState(&state, &a.key, nonce)
ciphertext = ciphertext[:len(ciphertext)-Overhead]
ret, out := sliceForAppend(dst, len(ciphertext))
if inexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap of output and input")
}
if anyOverlap(out, additionalData) {
panic("chacha20poly1305: invalid buffer overlap of output and additional data")
}
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
for i := range out {
out[i] = 0
}
return nil, errOpen
}
return ret, nil
}
func sliceForAppend(dst []byte, n int) (head, tail []byte) {
if total := len(dst) + n; cap(dst) >= total {
head = dst[:total]
} else {
head = make([]byte, total)
copy(head, dst)
}
tail = head[len(dst):]
return
}
// anyOverlap and inexactOverlap reproduce x/crypto/internal/alias, which cannot be
// imported from outside x/crypto: exact aliasing is fine, partial overlap is not.
func anyOverlap(x, y []byte) bool {
return len(x) > 0 && len(y) > 0 &&
uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) &&
uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1]))
}
func inexactOverlap(x, y []byte) bool {
if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] {
return false
}
return anyOverlap(x, y)
}