/*
* GCM-SIV Mode (RFC 8452)
* (C) 2026 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/gcm_siv.h>

#include <botan/exceptn.h>
#include <botan/mem_ops.h>
#include <botan/internal/ct_utils.h>
#include <botan/internal/fmt.h>
#include <botan/internal/int_utils.h>
#include <botan/internal/loadstor.h>

namespace Botan {

namespace {

Key_Length_Specification gcm_siv_key_spec(const BlockCipher& cipher) {
   if(cipher.block_size() != 16) {
      throw Invalid_Argument("GCM-SIV requires a 128 bit block cipher");
   }

   // The key-generating key is either 128 or 256 bits, and the derived
   // message-encryption key is of the same length
   const bool k16 = cipher.valid_keylength(16);
   const bool k32 = cipher.valid_keylength(32);

   if(k16 && k32) {
      return Key_Length_Specification(16, 32, 16);
   } else if(k16) {
      return Key_Length_Specification(16);
   } else if(k32) {
      return Key_Length_Specification(32);
   } else {
      throw Invalid_Argument("GCM-SIV requires a cipher supporting 128 or 256 bit keys");
   }
}

}  // namespace

GCM_SIV_Mode::GCM_SIV_Mode(std::unique_ptr<BlockCipher> cipher) :
      m_cipher_name(cipher->name()),
      m_key_spec(gcm_siv_key_spec(*cipher)),
      m_cipher(std::move(cipher)),
      m_msg_cipher(m_cipher->new_object()) {}

GCM_SIV_Mode::~GCM_SIV_Mode() = default;

void GCM_SIV_Mode::clear() {
   m_cipher->clear();
   m_kgk_len = 0;
   zap(m_ad);
   reset();
}

void GCM_SIV_Mode::reset() {
   // The derived keys are per-message; start_msg rekeys both
   m_msg_cipher->clear();
   m_polyval.clear();
   secure_scrub_memory(m_nonce);
   m_msg_buf.clear();
   m_in_msg = false;
}

std::string GCM_SIV_Mode::name() const {
   return fmt("{}/GCM-SIV", m_cipher_name);
}

std::string GCM_SIV_Mode::provider() const {
   return m_polyval.provider();
}

size_t GCM_SIV_Mode::update_granularity() const {
   return 1;
}

size_t GCM_SIV_Mode::ideal_granularity() const {
   return BS * std::max<size_t>(2, BlockCipher::ParallelismMult);
}

bool GCM_SIV_Mode::valid_nonce_length(size_t len) const {
   return len == 12;
}

Key_Length_Specification GCM_SIV_Mode::key_spec() const {
   return m_key_spec;
}

bool GCM_SIV_Mode::has_keying_material() const {
   return m_cipher->has_keying_material();
}

void GCM_SIV_Mode::key_schedule(std::span<const uint8_t> key) {
   m_cipher->set_key(key);
   m_kgk_len = key.size();
   reset();
}

void GCM_SIV_Mode::set_associated_data_n(size_t idx, std::span<const uint8_t> ad) {
   BOTAN_ARG_CHECK(idx == 0, "GCM-SIV: cannot handle non-zero index in set_associated_data_n");
   BOTAN_STATE_CHECK(!m_in_msg);
   BOTAN_ARG_CHECK(static_cast<uint64_t>(ad.size()) <= MAX_INPUT_LEN, "GCM-SIV AD too large");
   m_ad.assign(ad.begin(), ad.end());
}

void GCM_SIV_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) {
   assert_key_material_set();
   BOTAN_STATE_CHECK(!m_in_msg);

   if(!valid_nonce_length(nonce_len)) {
      throw Invalid_IV_Length(name(), nonce_len);
   }

   copy_mem(m_nonce, std::span{nonce, nonce_len});

   /*
   Derive the per-nonce keys, RFC 8452 section 4:

   "These keys are generated by encrypting a series of plaintext blocks
   that contain a 32-bit, little-endian counter followed by the nonce,
   and then discarding the second half of the resulting ciphertext."
   */
   const size_t blocks = (m_kgk_len == 16) ? 4 : 6;

   std::array<uint8_t, 6 * BS> kb{};
   for(size_t i = 0; i != blocks; ++i) {
      store_le(static_cast<uint32_t>(i), &kb[BS * i]);
      copy_mem(&kb[BS * i + 4], m_nonce.data(), m_nonce.size());
   }
   m_cipher->encrypt_n(kb.data(), kb.data(), blocks);

   std::array<uint8_t, 16> auth_key{};
   secure_vector<uint8_t> enc_key(m_kgk_len);

   for(size_t i = 0; i != 2; ++i) {
      copy_mem(&auth_key[8 * i], &kb[BS * i], 8);
   }
   for(size_t i = 0; i != blocks - 2; ++i) {
      copy_mem(&enc_key[8 * i], &kb[BS * (i + 2)], 8);
   }

   m_polyval.set_key(auth_key);
   m_msg_cipher->set_key(enc_key);

   secure_scrub_memory(kb);
   secure_scrub_memory(auth_key);

   m_msg_buf.clear();
   m_in_msg = true;
}

size_t GCM_SIV_Mode::process_msg(uint8_t buf[], size_t sz) {
   BOTAN_STATE_CHECK(m_in_msg);

   // Early rejection to bound buffering; the exact (per-direction) limits
   // are checked in finish. The tag is included to allow decryption inputs.
   if(sz > MAX_INPUT_LEN + BS - m_msg_buf.size()) {
      throw Invalid_State("GCM-SIV message length limit exceeded");
   }

   // All input is buffered until finish
   m_msg_buf.insert(m_msg_buf.end(), buf, buf + sz);
   return 0;
}

std::array<uint8_t, GCM_SIV_Mode::BS> GCM_SIV_Mode::compute_tag(std::span<const uint8_t> ptext) {
   m_polyval.update(m_ad);
   m_polyval.zero_pad();
   m_polyval.update(ptext);
   m_polyval.zero_pad();

   const uint64_t ad_bits = 8 * static_cast<uint64_t>(m_ad.size());
   const uint64_t pt_bits = 8 * static_cast<uint64_t>(ptext.size());
   m_polyval.update(store_le(ad_bits, pt_bits));

   std::array<uint8_t, BS> S{};
   m_polyval.final(S);

   /*
   RFC 8452 section 4: "XOR the first twelve bytes of S_s with the nonce
   and clear the most significant bit of the last byte.  Encrypt the
   result with AES using the message-encryption key to produce the tag."
   */
   xor_buf(S.data(), m_nonce.data(), m_nonce.size());
   S[15] &= 0x7f;
   m_msg_cipher->encrypt(S);

   return S;
}

void GCM_SIV_Mode::ctr_xor(std::span<const uint8_t, BS> tag, uint8_t buf[], size_t len) {
   /*
   RFC 8452 section 4: "The initial counter block is the tag with the
   most significant bit of the last byte set to one.  The counter
   advances by incrementing the first 32 bits interpreted as an
   unsigned, little-endian integer, wrapping at 2^32."
   */
   std::array<uint8_t, BS> ctr_block{};
   copy_mem(ctr_block, tag);
   ctr_block[15] |= 0x80;

   uint32_t ctr32 = load_le<uint32_t>(ctr_block.data(), 0);

   secure_vector<uint8_t> ks(m_msg_cipher->parallel_bytes());

   while(len > 0) {
      const size_t blocks = std::min((len + BS - 1) / BS, ks.size() / BS);

      for(size_t i = 0; i != blocks; ++i) {
         copy_mem(&ks[BS * i], ctr_block.data(), BS);
         store_le(ctr32, &ks[BS * i]);
         ctr32 += 1;
      }

      m_msg_cipher->encrypt_n(ks.data(), ks.data(), blocks);

      const size_t todo = std::min(len, blocks * BS);
      xor_buf(buf, ks.data(), todo);
      buf += todo;
      len -= todo;
   }
}

size_t GCM_SIV_Encryption::output_length(size_t input_length) const {
   return add_or_throw(input_length, tag_size(), "GCM-SIV input too large");
}

void GCM_SIV_Encryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
   BOTAN_STATE_CHECK(in_msg());
   BOTAN_ARG_CHECK(offset <= buffer.size(), "Invalid offset");

   buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end());
   msg_buf().clear();

   const size_t ptext_len = buffer.size() - offset;
   BOTAN_ARG_CHECK(static_cast<uint64_t>(ptext_len) <= MAX_INPUT_LEN, "GCM-SIV plaintext too large");
   uint8_t* buf = buffer.data() + offset;

   const auto tag = compute_tag({buf, ptext_len});
   ctr_xor(tag, buf, ptext_len);

   buffer += std::make_pair(tag.data(), tag.size());
   reset();
}

size_t GCM_SIV_Decryption::output_length(size_t input_length) const {
   BOTAN_ARG_CHECK(input_length >= tag_size(), "Message too short to be valid");
   return input_length - tag_size();
}

void GCM_SIV_Decryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
   BOTAN_STATE_CHECK(in_msg());
   BOTAN_ARG_CHECK(offset <= buffer.size(), "Invalid offset");

   if(!msg_buf().empty()) {
      buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end());
      msg_buf().clear();
   }

   const size_t sz = buffer.size() - offset;
   BOTAN_ARG_CHECK(sz >= tag_size(), "input did not include the tag");

   const size_t ctext_len = sz - tag_size();
   BOTAN_ARG_CHECK(static_cast<uint64_t>(ctext_len) <= MAX_INPUT_LEN, "GCM-SIV ciphertext too large");
   uint8_t* buf = buffer.data() + offset;

   std::array<uint8_t, 16> included_tag{};
   copy_mem(included_tag, std::span{buf + ctext_len, 16});

   ctr_xor(included_tag, buf, ctext_len);

   const auto expected_tag = compute_tag({buf, ctext_len});

   reset();

   if(!CT::is_equal(expected_tag.data(), included_tag.data(), included_tag.size()).as_bool()) {
      clear_mem(std::span{buffer}.subspan(offset, ctext_len));
      throw Invalid_Authentication_Tag("GCM-SIV tag check failed");
   }

   buffer.resize(offset + ctext_len);
}

}  // namespace Botan
