137 lines
3.5 KiB
Go
137 lines
3.5 KiB
Go
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build arm
|
|
|
|
// Package chacha20poly1305 provides a ChaCha20-Poly1305 AEAD built
|
|
// from the hand-ported ChaCha20 and Poly1305 kernels in
|
|
// tsasm/arm/chacha20 and tsasm/arm/poly1305. It only builds on
|
|
// GOARCH=arm; other architectures should use
|
|
// golang.org/x/crypto/chacha20poly1305.
|
|
package chacha20poly1305
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
|
|
chacha "github.com/tailscale/wireguard-go/tsasm/arm/chacha20"
|
|
"github.com/tailscale/wireguard-go/tsasm/arm/poly1305"
|
|
)
|
|
|
|
const (
|
|
KeySize = 32
|
|
NonceSize = 12
|
|
Overhead = 16
|
|
)
|
|
|
|
type aead struct {
|
|
key [KeySize]byte
|
|
}
|
|
|
|
// New returns a chacha20poly1305 AEAD with the given 32-byte key.
|
|
func New(key []byte) (cipher.AEAD, error) {
|
|
if len(key) != KeySize {
|
|
return nil, errors.New("chacha20poly1305: bad key length")
|
|
}
|
|
a := &aead{}
|
|
copy(a.key[:], key)
|
|
return a, nil
|
|
}
|
|
|
|
func (a *aead) NonceSize() int { return NonceSize }
|
|
func (a *aead) Overhead() int { return Overhead }
|
|
|
|
func (a *aead) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
|
if len(nonce) != NonceSize {
|
|
panic("chacha20poly1305: bad nonce length")
|
|
}
|
|
ret, out := sliceForAppend(dst, len(plaintext)+Overhead)
|
|
|
|
var nonceArr [chacha.NonceSize]byte
|
|
copy(nonceArr[:], nonce)
|
|
|
|
// Derive the Poly1305 one-time key from the first 32 bytes of the
|
|
// ChaCha20 keystream at counter 0, then encrypt at counter 1.
|
|
var (
|
|
zeros [64]byte
|
|
polyBuf [64]byte
|
|
polyKey [32]byte
|
|
)
|
|
chacha.XORKeyStream(polyBuf[:], zeros[:], &a.key, &nonceArr, 0)
|
|
copy(polyKey[:], polyBuf[:32])
|
|
chacha.XORKeyStream(out[:len(plaintext)], plaintext, &a.key, &nonceArr, 1)
|
|
|
|
tag := computeTag(additionalData, out[:len(plaintext)], &polyKey)
|
|
copy(out[len(plaintext):], tag[:])
|
|
return ret
|
|
}
|
|
|
|
func (a *aead) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
|
if len(nonce) != NonceSize {
|
|
return nil, errors.New("chacha20poly1305: bad nonce length")
|
|
}
|
|
if len(ciphertext) < Overhead {
|
|
return nil, errors.New("chacha20poly1305: ciphertext too short")
|
|
}
|
|
ctLen := len(ciphertext) - Overhead
|
|
ct := ciphertext[:ctLen]
|
|
receivedTag := ciphertext[ctLen:]
|
|
|
|
var nonceArr [chacha.NonceSize]byte
|
|
copy(nonceArr[:], nonce)
|
|
|
|
var (
|
|
zeros [64]byte
|
|
polyBuf [64]byte
|
|
polyKey [32]byte
|
|
)
|
|
chacha.XORKeyStream(polyBuf[:], zeros[:], &a.key, &nonceArr, 0)
|
|
copy(polyKey[:], polyBuf[:32])
|
|
|
|
expectedTag := computeTag(additionalData, ct, &polyKey)
|
|
if subtle.ConstantTimeCompare(receivedTag, expectedTag[:]) != 1 {
|
|
return nil, errors.New("chacha20poly1305: message authentication failed")
|
|
}
|
|
|
|
ret, out := sliceForAppend(dst, ctLen)
|
|
chacha.XORKeyStream(out, ct, &a.key, &nonceArr, 1)
|
|
return ret, nil
|
|
}
|
|
|
|
var zeros16 [16]byte
|
|
|
|
// computeTag streams (aad || pad16 || ct || pad16 || u64le(|aad|) ||
|
|
// u64le(|ct|)) through a stack-allocated Poly1305 MAC.
|
|
func computeTag(aad, ct []byte, polyKey *[32]byte) [16]byte {
|
|
var mac poly1305.MAC
|
|
mac.Init(polyKey)
|
|
mac.Write(aad)
|
|
if rem := len(aad) % 16; rem != 0 {
|
|
mac.Write(zeros16[:16-rem])
|
|
}
|
|
mac.Write(ct)
|
|
if rem := len(ct) % 16; rem != 0 {
|
|
mac.Write(zeros16[:16-rem])
|
|
}
|
|
var lenBuf [16]byte
|
|
binary.LittleEndian.PutUint64(lenBuf[0:8], uint64(len(aad)))
|
|
binary.LittleEndian.PutUint64(lenBuf[8:16], uint64(len(ct)))
|
|
mac.Write(lenBuf[:])
|
|
|
|
var tag [16]byte
|
|
mac.Sum(&tag)
|
|
return tag
|
|
}
|
|
|
|
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
|
|
}
|