//go:build boringcrypto
// +build boringcrypto

package noiseutil

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/binary"

	// unsafe needed for go:linkname
	_ "unsafe"

	"github.com/flynn/noise"
)

// EncryptLockNeeded indicates if calls to Encrypt need a lock
// This is true for boringcrypto because the Seal function verifies that the
// nonce is strictly increasing.
const EncryptLockNeeded = true

// NewGCMTLS is no longer exposed in go1.19+, so we need to link it in
// See: https://github.com/golang/go/issues/56326
//
// NewGCMTLS is the internal method used with boringcrypto that provides a
// validated mode of AES-GCM which enforces the nonce is strictly
// monotonically increasing.  This is the TLS 1.2 specification for nonce
// generation (which also matches the method used by the Noise Protocol)
//
// - https://github.com/golang/go/blob/go1.19/src/crypto/tls/cipher_suites.go#L520-L522
// - https://github.com/golang/go/blob/go1.19/src/crypto/internal/boring/aes.go#L235-L237
// - https://github.com/golang/go/blob/go1.19/src/crypto/internal/boring/aes.go#L250
// - https://github.com/google/boringssl/blob/ae223d6138807a13006342edfeef32e813246b39/include/openssl/aead.h#L379-L381
// - https://github.com/google/boringssl/blob/ae223d6138807a13006342edfeef32e813246b39/crypto/fipsmodule/cipher/e_aes.c#L1082-L1093
//
//go:linkname newGCMTLS crypto/internal/boring.NewGCMTLS
func newGCMTLS(c cipher.Block) (cipher.AEAD, error)

type cipherFn struct {
	fn   func([32]byte) noise.Cipher
	name string
}

func (c cipherFn) Cipher(k [32]byte) noise.Cipher { return c.fn(k) }
func (c cipherFn) CipherName() string             { return c.name }

// CipherAESGCM is the AES256-GCM AEAD cipher (using NewGCMTLS when GoBoring is present)
var CipherAESGCM noise.CipherFunc = cipherFn{cipherAESGCMBoring, "AESGCM"}

func cipherAESGCMBoring(k [32]byte) noise.Cipher {
	c, err := aes.NewCipher(k[:])
	if err != nil {
		panic(err)
	}
	gcm, err := newGCMTLS(c)
	if err != nil {
		panic(err)
	}
	return aeadCipher{
		gcm,
		func(n uint64) []byte {
			var nonce [12]byte
			binary.BigEndian.PutUint64(nonce[4:], n)
			return nonce[:]
		},
	}
}

type aeadCipher struct {
	cipher.AEAD
	nonce func(uint64) []byte
}

func (c aeadCipher) Encrypt(out []byte, n uint64, ad, plaintext []byte) []byte {
	return c.Seal(out, c.nonce(n), plaintext, ad)
}

func (c aeadCipher) Decrypt(out []byte, n uint64, ad, ciphertext []byte) ([]byte, error) {
	return c.Open(out, c.nonce(n), ciphertext, ad)
}
