// Copyright 2024 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 (
	"crypto/internal/fips140"
	"crypto/internal/fips140/aes"
	"crypto/internal/fips140/subtle"
)

// CMAC implements the CMAC mode from NIST SP 800-38B.
//
// It is optimized for use in Counter KDF (SP 800-108r1) and XAES-256-GCM
// (https://c2sp.org/XAES-256-GCM), rather than for exposing it to applications
// as a stand-alone MAC.
type CMAC struct {
	b  aes.Block
	k1 [aes.BlockSize]byte
	k2 [aes.BlockSize]byte
}

func NewCMAC(b *aes.Block) *CMAC {
	c := &CMAC{b: *b}
	c.deriveSubkeys()
	return c
}

func (c *CMAC) deriveSubkeys() {
	aes.EncryptBlockInternal(&c.b, c.k1[:], c.k1[:])
	msb := shiftLeft(&c.k1)
	c.k1[len(c.k1)-1] ^= msb * 0b10000111

	c.k2 = c.k1
	msb = shiftLeft(&c.k2)
	c.k2[len(c.k2)-1] ^= msb * 0b10000111
}

func (c *CMAC) MAC(m []byte) [aes.BlockSize]byte {
	fips140.RecordApproved()
	_ = c.b // Hoist the nil check out of the loop.
	var x [aes.BlockSize]byte
	if len(m) == 0 {
		// Special-cased as a single empty partial final block.
		x = c.k2
		x[len(m)] ^= 0b10000000
		aes.EncryptBlockInternal(&c.b, x[:], x[:])
		return x
	}
	for len(m) >= aes.BlockSize {
		subtle.XORBytes(x[:], m[:aes.BlockSize], x[:])
		if len(m) == aes.BlockSize {
			// Final complete block.
			subtle.XORBytes(x[:], c.k1[:], x[:])
		}
		aes.EncryptBlockInternal(&c.b, x[:], x[:])
		m = m[aes.BlockSize:]
	}
	if len(m) > 0 {
		// Final incomplete block.
		subtle.XORBytes(x[:], m, x[:])
		subtle.XORBytes(x[:], c.k2[:], x[:])
		x[len(m)] ^= 0b10000000
		aes.EncryptBlockInternal(&c.b, x[:], x[:])
	}
	return x
}

// shiftLeft sets x to x << 1, and returns MSB₁(x).
func shiftLeft(x *[aes.BlockSize]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
}
