// Copyright 2024 Sun Yimin. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package cbcmac implements the Message Authentication Code with the block chipher mechanisms.
package cbcmac

import (
	"crypto/cipher"
	"crypto/subtle"

	"github.com/emmansun/gmsm/padding"
)

// Reference: GB/T 15821.1-2020 Security techniques
// Message authentication codes - Part 1: Mechanisms using block ciphers

// BlockCipherMAC is the interface that wraps the basic MAC method.
type BlockCipherMAC interface {
	// Size returns the MAC value's number of bytes.
	Size() int

	// MAC calculates the MAC of the given data.
	// The MAC value's number of bytes is returned by Size.
	// Intercept message authentication code as needed.
	MAC(src []byte) []byte
}

// cbcmac implements the basic CBC-MAC mode of operation for block ciphers.
type cbcmac struct {
	b    cipher.Block
	pad  padding.Padding
	size int
}

// NewCBCMAC returns a CBC-MAC (GB/T 15821.1-2020 MAC scheme 1) instance that
// implements the MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewCBCMAC(b cipher.Block, size int) BlockCipherMAC {
	return NewCBCMACWithPadding(b, size, padding.NewISO9797M2Padding)
}

// NewCBCMACWithPadding creates a new CBC-MAC (Cipher Block Chaining Message Authentication Code)
// with the specified block cipher, MAC size, and padding function. The MAC size must be greater
// than 0 and less than or equal to the block size of the cipher. If the size is invalid, the
// function will panic. The padding function is used to pad the input to the block size of the cipher.
func NewCBCMACWithPadding(b cipher.Block, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
	if size <= 0 || size > b.BlockSize() {
		panic("cbcmac: invalid size")
	}
	return &cbcmac{b: b, pad: newPaddingFunc(uint(b.BlockSize())), size: size}
}

func (c *cbcmac) Size() int {
	return c.size
}

// MAC calculates the MAC of the given data.
// The data is padded with the padding scheme of the block cipher before processing.
func (c *cbcmac) MAC(src []byte) []byte {
	src = c.pad.Pad(src)
	blockSize := c.b.BlockSize()
	tag := make([]byte, blockSize)
	for len(src) > 0 {
		subtle.XORBytes(tag, tag, src[:blockSize])
		c.b.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	return tag[:c.size]
}

// emac implements the EMAC mode of operation for block ciphers.
type emac struct {
	pad    padding.Padding
	b1, b2 cipher.Block
	size   int
}

// NewEMAC returns an EMAC (GB/T 15821.1-2020 MAC scheme 2) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewEMAC(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int) BlockCipherMAC {
	return NewEMACWithPadding(creator, key1, key2, size, padding.NewISO9797M2Padding)
}

// NewEMACWithPadding creates a new instance of EMAC (Encrypted Message Authentication Code) with padding.
func NewEMACWithPadding(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
	var b1, b2 cipher.Block
	var err error
	if b1, err = creator(key1); err != nil {
		panic(err)
	}
	if size <= 0 || size > b1.BlockSize() {
		panic("cbcmac: invalid size")
	}
	if b2, err = creator(key2); err != nil {
		panic(err)
	}
	return &emac{pad: newPaddingFunc(uint(b1.BlockSize())), b1: b1, b2: b2, size: size}
}

func (e *emac) Size() int {
	return e.size
}

func (e *emac) MAC(src []byte) []byte {
	src = e.pad.Pad(src)
	blockSize := e.b1.BlockSize()
	tag := make([]byte, blockSize)
	for len(src) > 0 {
		subtle.XORBytes(tag, tag, src[:blockSize])
		e.b1.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	e.b2.Encrypt(tag, tag)
	return tag[:e.size]
}

type ansiRetailMAC emac

// NewANSIRetailMAC returns an ANSI Retail MAC (GB/T 15821.1-2020 MAC scheme 3) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewANSIRetailMAC(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int) BlockCipherMAC {
	return NewANSIRetailMACWithPadding(creator, key1, key2, size, padding.NewISO9797M2Padding)
}

// NewANSIRetailMACWithPadding creates a new ANSI Retail MAC with padding.
func NewANSIRetailMACWithPadding(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
	return (*ansiRetailMAC)(NewEMACWithPadding(creator, key1, key2, size, newPaddingFunc).(*emac))
}

func (e *ansiRetailMAC) Size() int {
	return e.size
}

func (e *ansiRetailMAC) MAC(src []byte) []byte {
	src = e.pad.Pad(src)
	blockSize := e.b1.BlockSize()
	tag := make([]byte, blockSize)
	for len(src) > 0 {
		subtle.XORBytes(tag, tag, src[:blockSize])
		e.b1.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	e.b2.Decrypt(tag, tag)
	e.b1.Encrypt(tag, tag)
	return tag[:e.size]
}

type macDES struct {
	pad        padding.Padding
	b1, b2, b3 cipher.Block
	size       int
}

// NewMACDES returns a MAC-DES (GB/T 15821.1-2020 MAC scheme 4) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewMACDES(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int) BlockCipherMAC {
	return NewMACDESWithPadding(creator, key1, key2, size, padding.NewISO9797M2Padding)
}

// NewMACDESWithPadding creates a new BlockCipherMAC using DES encryption with padding.
func NewMACDESWithPadding(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
	var b1, b2, b3 cipher.Block
	var err error
	if b1, err = creator(key1); err != nil {
		panic(err)
	}
	if size <= 0 || size > b1.BlockSize() {
		panic("cbcmac: invalid size")
	}
	if b2, err = creator(key2); err != nil {
		panic(err)
	}
	key3 := make([]byte, len(key2))
	copy(key3, key2)
	for i := range key3 {
		key3[i] ^= 0xF0
	}
	if b3, err = creator(key3); err != nil {
		panic(err)
	}
	return &macDES{pad: newPaddingFunc(uint(b1.BlockSize())), b1: b1, b2: b2, b3: b3, size: size}
}

func (m *macDES) Size() int {
	return m.size
}

func (m *macDES) MAC(src []byte) []byte {
	src = m.pad.Pad(src)
	blockSize := m.b1.BlockSize()
	tag := make([]byte, blockSize)
	copy(tag, src[:blockSize])
	m.b1.Encrypt(tag, tag)
	m.b3.Encrypt(tag, tag)
	src = src[blockSize:]
	for len(src) > 0 {
		subtle.XORBytes(tag, tag, src[:blockSize])
		m.b1.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	m.b2.Encrypt(tag, tag)
	return tag[:m.size]
}

type cmac struct {
	b         cipher.Block
	k1, k2    []byte
	size      int
	blockSize int
	tag       []byte
	x         []byte
	nx        int
	len       uint64
}

// NewCMAC returns a CMAC (GB/T 15821.1-2020 MAC scheme 5) instance that implements MAC with the given block cipher.
//
// Reference: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38B.pdf
func NewCMAC(b cipher.Block, size int) *cmac {
	if size <= 0 || size > b.BlockSize() {
		panic("cbcmac: invalid size")
	}
	blockSize := b.BlockSize()
	k1 := make([]byte, blockSize)
	k2 := make([]byte, blockSize)
	b.Encrypt(k1, k1)
	msb := shiftLeft(k1)
	k1[len(k1)-1] ^= msb * 0b10000111

	copy(k2, k1)
	msb = shiftLeft(k2)
	k2[len(k2)-1] ^= msb * 0b10000111

	d := &cmac{b: b, k1: k1, k2: k2, size: size}
	d.blockSize = blockSize
	d.tag = make([]byte, blockSize)
	d.x = make([]byte, blockSize)
	return d
}

func (c *cmac) Reset() {
	for i := range c.tag {
		c.tag[i] = 0
	}
	c.nx = 0
	c.len = 0
}

func (c *cmac) BlockSize() int {
	return c.blockSize
}

func (c *cmac) Size() int {
	return c.size
}

func (d *cmac) Write(p []byte) (nn int, err error) {
	nn = len(p)
	if nn == 0 {
		// nothing to do
		return
	}
	d.len += uint64(nn)
	if d.nx == d.blockSize {
		// handle remaining full block
		d.block(d.x)
		d.nx = 0
	} else if d.nx > 0 {
		// handle remaining incomplete block
		n := copy(d.x[d.nx:], p)
		d.nx += n
		p = p[n:]
		if len(p) > 0 {
			d.block(d.x)
			d.nx = 0
		}
	}
	lenP := len(p)
	if lenP > d.blockSize {
		n := lenP &^ (d.blockSize - 1)
		if n == lenP {
			n -= d.blockSize
		}
		d.block(p[:n])
		p = p[n:]
	}
	// save remaining partial/full block
	if len(p) > 0 {
		d.nx = copy(d.x, p)
	}
	return
}

func (c *cmac) block(p []byte) {
	for len(p) >= c.blockSize {
		subtle.XORBytes(c.tag, p[:c.blockSize], c.tag)
		c.b.Encrypt(c.tag, c.tag)
		p = p[c.blockSize:]
	}
}

// Sum appends the current hash to in and returns the resulting slice.
// It does not change the underlying hash state.
func (d *cmac) Sum(in []byte) []byte {
	// Make a copy of d so that caller can keep writing and summing.
	// shared block cipher and k1, k2, x
	d0 := *d
	// use slices.Clone() later
	d0.tag = make([]byte, d.blockSize)
	copy(d0.tag, d.tag)
	hash := d0.checkSum()
	return append(in, hash...)
}

func (c *cmac) checkSum() []byte {
	tag := make([]byte, c.size)
	switch c.nx {
	case 0:
		// Special-cased as a single empty partial final block.
		copy(c.tag, c.k2)
		c.tag[0] ^= 0b10000000
	case c.blockSize:
		subtle.XORBytes(c.tag, c.x, c.tag)
		subtle.XORBytes(c.tag, c.k1, c.tag)
	default:
		subtle.XORBytes(c.tag, c.x, c.tag)
		c.tag[c.nx] ^= 0b10000000
		subtle.XORBytes(c.tag, c.k2, c.tag)
	}
	c.b.Encrypt(c.tag, c.tag)
	copy(tag, c.tag[:c.size])
	return tag
}

func (c *cmac) MAC(src []byte) []byte {
	c.Reset()
	c.Write(src)
	return c.Sum(nil)
}

// shiftLeft sets x to x << 1, and returns MSB₁(x).
func shiftLeft(x []byte) byte {
	var msb byte
	for i := len(x) - 1; i >= 0; i-- {
		msb, x[i] = x[i]>>7, x[i]<<1|msb
	}
	return msb
}

type lmac struct {
	b1, b2 cipher.Block
	pad    padding.Padding
	size   int
}

// NewLMAC returns an LMAC (GB/T 15821.1-2020 MAC scheme 6) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewLMAC(creator func(key []byte) (cipher.Block, error), key []byte, size int) BlockCipherMAC {
	return NewLMACWithPadding(creator, key, size, padding.NewISO9797M2Padding)
}

// NewLMACWithPadding creates a new LMAC (Length-based Message Authentication Code) with padding.
func NewLMACWithPadding(creator func(key []byte) (cipher.Block, error), key []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
	var b, b1, b2 cipher.Block
	var err error
	if b, err = creator(key); err != nil {
		panic(err)
	}
	if size <= 0 || size > b.BlockSize() {
		panic("cbcmac: invalid size")
	}
	blockSize := b.BlockSize()
	key1 := make([]byte, blockSize)
	key1[blockSize-1] = 0x01
	key2 := make([]byte, blockSize)
	key2[blockSize-1] = 0x02
	b.Encrypt(key1, key1)
	b.Encrypt(key2, key2)
	if b1, err = creator(key1); err != nil {
		panic(err)
	}
	if b2, err = creator(key2); err != nil {
		panic(err)
	}

	return &lmac{b1: b1, b2: b2, pad: newPaddingFunc(uint(blockSize)), size: size}
}

func (l *lmac) Size() int {
	return l.b1.BlockSize()
}

func (l *lmac) MAC(src []byte) []byte {
	src = l.pad.Pad(src)
	blockSize := l.b1.BlockSize()
	tag := make([]byte, blockSize)
	for len(src) > blockSize {
		subtle.XORBytes(tag, tag, src[:blockSize])
		l.b1.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	subtle.XORBytes(tag, tag, src[:blockSize])
	l.b2.Encrypt(tag, tag)
	return tag
}

type trCBCMAC struct {
	b    cipher.Block
	size int
}

// NewTRCBCMAC returns a TR-CBC-MAC (GB/T 15821.1-2020 MAC scheme 7) instance that
// implements MAC with the given block cipher.
//
// Reference: TrCBC: Another look at CBC-MAC.
func NewTRCBCMAC(b cipher.Block, size int) BlockCipherMAC {
	if size <= 0 || size > b.BlockSize() {
		panic("cbcmac: invalid size")
	}
	return &trCBCMAC{b: b, size: size}
}

func (t *trCBCMAC) Size() int {
	return t.size
}

func (t *trCBCMAC) MAC(src []byte) []byte {
	blockSize := t.b.BlockSize()
	tag := make([]byte, blockSize)
	padded := false
	if len(src) == 0 || len(src)%blockSize != 0 {
		pad := padding.NewISO9797M2Padding(uint(blockSize))
		src = pad.Pad(src)
		padded = true
	}
	for len(src) > 0 {
		subtle.XORBytes(tag, tag, src[:blockSize])
		t.b.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	if padded {
		return tag[blockSize-t.size:]
	}
	return tag[:t.size]
}

type cbcrMAC struct {
	b    cipher.Block
	size int
}

// NewCBCRMAC returns a CBCRMAC (GB/T 15821.1-2020 MAC scheme 8) instance that implements MAC with the given block cipher.
//
// Reference: CBCR: CBC MAC with rotating transformations.
func NewCBCRMAC(b cipher.Block, size int) BlockCipherMAC {
	if size <= 0 || size > b.BlockSize() {
		panic("cbcmac: invalid size")
	}
	return &cbcrMAC{b: b, size: size}
}

func (c *cbcrMAC) Size() int {
	return c.size
}

func (c *cbcrMAC) MAC(src []byte) []byte {
	blockSize := c.b.BlockSize()
	tag := make([]byte, blockSize)
	c.b.Encrypt(tag, tag)
	padded := false
	if len(src) == 0 || len(src)%blockSize != 0 {
		pad := padding.NewISO9797M2Padding(uint(blockSize))
		src = pad.Pad(src)
		padded = true
	}

	for len(src) > blockSize {
		subtle.XORBytes(tag, tag, src[:blockSize])
		c.b.Encrypt(tag, tag)
		src = src[blockSize:]
	}
	subtle.XORBytes(tag, tag, src[:blockSize])
	if padded {
		shiftLeft(tag)
	} else {
		shiftRight(tag)
	}
	c.b.Encrypt(tag, tag)
	return tag[:c.size]
}

func shiftRight(x []byte) {
	var lsb byte
	for i := 0; i < len(x); i++ {
		lsb, x[i] = x[i]<<7, x[i]>>1|lsb
	}
	x[0] ^= lsb
}
