// Copyright 2018-2026 the Deno authors. MIT license.

use aes::cipher::BlockEncryptMut;
use aes::cipher::KeyIvInit;
use aes::cipher::StreamCipher;
use aes::cipher::block_padding::Pkcs7;
use aes_gcm::AeadInPlace;
use aes_gcm::KeyInit;
use aes_gcm::Nonce;
use aes_gcm::aead::generic_array::ArrayLength;
use aes_gcm::aead::generic_array::typenum::U12;
use aes_gcm::aead::generic_array::typenum::U16;
use aes_gcm::aes::Aes128;
use aes_gcm::aes::Aes192;
use aes_gcm::aes::Aes256;
use aws_lc_rs::aead::Aad;
use aws_lc_rs::aead::CHACHA20_POLY1305;
use aws_lc_rs::aead::LessSafeKey;
use aws_lc_rs::aead::Nonce as AwsNonce;
use aws_lc_rs::aead::UnboundKey;
use ctr::Ctr32BE;
use ctr::Ctr64BE;
use ctr::Ctr128BE;
use rand::rngs::OsRng;
use rsa::pkcs1::DecodeRsaPublicKey;

use crate::shared::*;

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum EncryptError {
  #[class(inherit)]
  #[error(transparent)]
  General(
    #[from]
    #[inherit]
    SharedError,
  ),
  #[class(type)]
  #[error("invalid length")]
  InvalidLength,
  #[class("DOMExceptionOperationError")]
  #[error("invalid key or iv")]
  InvalidKeyOrIv,
  #[class(type)]
  #[error("iv length not equal to 12 or 16")]
  InvalidIvLength,
  #[class("DOMExceptionOperationError")]
  #[error("invalid tag length")]
  InvalidTagLength,
  #[class(type)]
  #[error("invalid ChaCha20-Poly1305 nonce length: expected 12 bytes")]
  InvalidChaChaNonceLength,
  #[class(type)]
  #[error("invalid ChaCha20-Poly1305 key length: expected 32 bytes")]
  InvalidChaChaKeyLength,
  #[class(type)]
  #[error("invalid counter length. Currently supported 32/64/128 bits")]
  InvalidCounterLength,
  #[class("DOMExceptionOperationError")]
  #[error("tried to encrypt too much data")]
  TooMuchData,
  #[class("DOMExceptionOperationError")]
  #[error("Encryption failed")]
  Failed,
}

pub(crate) fn encrypt_rsa_oaep(
  key: &RawKeyData,
  hash: ShaHash,
  label: Vec<u8>,
  data: &[u8],
) -> Result<Vec<u8>, EncryptError> {
  let public_key = key.as_rsa_public_key()?;
  let public_key = rsa::RsaPublicKey::from_pkcs1_der(&public_key)
    .map_err(|_| SharedError::FailedDecodePublicKey)?;
  let mut rng = OsRng;
  let padding = rsa_oaep_padding(hash, &label);
  let encrypted = public_key
    .encrypt(&mut rng, padding, data)
    .map_err(|_| EncryptError::Failed)?;
  Ok(encrypted)
}

pub(crate) fn encrypt_aes_cbc(
  key: &RawKeyData,
  length: usize,
  iv: Vec<u8>,
  data: &[u8],
) -> Result<Vec<u8>, EncryptError> {
  let key = key.as_secret_key()?;
  let ciphertext = match length {
    128 => {
      // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
      type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;

      let cipher = Aes128CbcEnc::new_from_slices(key, &iv)
        .map_err(|_| EncryptError::InvalidKeyOrIv)?;
      cipher.encrypt_padded_vec_mut::<Pkcs7>(data)
    }
    192 => {
      // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
      type Aes192CbcEnc = cbc::Encryptor<aes::Aes192>;

      let cipher = Aes192CbcEnc::new_from_slices(key, &iv)
        .map_err(|_| EncryptError::InvalidKeyOrIv)?;
      cipher.encrypt_padded_vec_mut::<Pkcs7>(data)
    }
    256 => {
      // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
      type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;

      let cipher = Aes256CbcEnc::new_from_slices(key, &iv)
        .map_err(|_| EncryptError::InvalidKeyOrIv)?;
      cipher.encrypt_padded_vec_mut::<Pkcs7>(data)
    }
    _ => return Err(EncryptError::InvalidLength),
  };
  Ok(ciphertext)
}

fn encrypt_aes_gcm_general<N: ArrayLength<u8>>(
  key: &[u8],
  iv: Vec<u8>,
  length: usize,
  ciphertext: &mut [u8],
  additional_data: Vec<u8>,
) -> Result<aes_gcm::Tag, EncryptError> {
  let nonce = Nonce::<N>::from_slice(&iv);
  let tag = match length {
    128 => {
      let cipher = aes_gcm::AesGcm::<Aes128, N>::new_from_slice(key)
        .map_err(|_| EncryptError::Failed)?;
      cipher
        .encrypt_in_place_detached(nonce, &additional_data, ciphertext)
        .map_err(|_| EncryptError::Failed)?
    }
    192 => {
      let cipher = aes_gcm::AesGcm::<Aes192, N>::new_from_slice(key)
        .map_err(|_| EncryptError::Failed)?;
      cipher
        .encrypt_in_place_detached(nonce, &additional_data, ciphertext)
        .map_err(|_| EncryptError::Failed)?
    }
    256 => {
      let cipher = aes_gcm::AesGcm::<Aes256, N>::new_from_slice(key)
        .map_err(|_| EncryptError::Failed)?;
      cipher
        .encrypt_in_place_detached(nonce, &additional_data, ciphertext)
        .map_err(|_| EncryptError::Failed)?
    }
    _ => return Err(EncryptError::InvalidLength),
  };

  Ok(tag)
}

pub(crate) fn encrypt_aes_gcm(
  key: &RawKeyData,
  length: usize,
  tag_length: usize,
  iv: Vec<u8>,
  additional_data: Option<Vec<u8>>,
  data: &[u8],
) -> Result<Vec<u8>, EncryptError> {
  let key = key.as_secret_key()?;
  let additional_data = additional_data.unwrap_or_default();

  let mut ciphertext = data.to_vec();
  // Fixed 96-bit OR 128-bit nonce
  let tag = match iv.len() {
    12 => encrypt_aes_gcm_general::<U12>(
      key,
      iv,
      length,
      &mut ciphertext,
      additional_data,
    )?,
    16 => encrypt_aes_gcm_general::<U16>(
      key,
      iv,
      length,
      &mut ciphertext,
      additional_data,
    )?,
    _ => return Err(EncryptError::InvalidIvLength),
  };

  // Truncated tag to the specified tag length.
  // `tag` is fixed to be 16 bytes long and (tag_length / 8) is always <= 16
  let tag = &tag[..(tag_length / 8)];

  // C | T
  ciphertext.extend_from_slice(tag);

  Ok(ciphertext)
}

pub(crate) fn encrypt_aes_ocb(
  key: &RawKeyData,
  length: usize,
  tag_length: usize,
  iv: Vec<u8>,
  additional_data: Option<Vec<u8>>,
  data: &[u8],
) -> Result<Vec<u8>, EncryptError> {
  use aes_gcm::aead::generic_array::GenericArray;
  use aes_gcm::aead::generic_array::typenum::U6;
  use aes_gcm::aead::generic_array::typenum::U7;
  use aes_gcm::aead::generic_array::typenum::U8;
  use aes_gcm::aead::generic_array::typenum::U9;
  use aes_gcm::aead::generic_array::typenum::U10;
  use aes_gcm::aead::generic_array::typenum::U11;
  use aes_gcm::aead::generic_array::typenum::U12;
  use aes_gcm::aead::generic_array::typenum::U13;
  use aes_gcm::aead::generic_array::typenum::U14;
  use aes_gcm::aead::generic_array::typenum::U15;
  use aes_gcm::aead::generic_array::typenum::U16;
  use ocb3::Ocb3;
  use ocb3::aead::AeadInPlace as Ocb3AeadInPlace;
  use ocb3::aead::KeyInit as Ocb3KeyInit;

  let key = key.as_secret_key()?;
  let additional_data = additional_data.unwrap_or_default();

  let mut ciphertext = data.to_vec();

  // The WICG spec permits a 64-, 96- or 128-bit tag for AES-OCB. The `ocb3`
  // crate's TagSize is a compile-time type parameter, so map each permitted
  // length to its typenum (U8/U12/U16). Anything else is an OperationError.
  let tag_size = match tag_length {
    64 => OcbTagSize::U8,
    96 => OcbTagSize::U12,
    128 => OcbTagSize::U16,
    _ => return Err(EncryptError::InvalidTagLength),
  };

  // RFC 7253 permits nonces up to 15 bytes; the `ocb3` crate supports 6..=15.
  // The NonceSize is a compile-time type parameter, so dispatch the runtime
  // length to the matching typenum. Lengths outside 6..=15 are unsupported.
  if iv.len() < 6 || iv.len() > 15 {
    return Err(EncryptError::InvalidIvLength);
  }

  // For a concrete (cipher, nonce, tag) triple the `ocb3` sealed NonceSizes /
  // TagSizes bounds are satisfied automatically; encrypt in place and return
  // the detached tag (already exactly `$tag` bytes long, no truncation).
  macro_rules! ocb_encrypt {
    ($aes:ty, $nonce:ty, $tag:ty) => {{
      let cipher = Ocb3::<$aes, $nonce, $tag>::new_from_slice(key)
        .map_err(|_| EncryptError::Failed)?;
      let nonce = GenericArray::<u8, $nonce>::from_slice(&iv);
      cipher
        .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext)
        .map_err(|_| EncryptError::Failed)?
        .to_vec()
    }};
  }
  macro_rules! ocb_encrypt_for_tag {
    ($nonce:ty, $tag:ty) => {
      match length {
        128 => ocb_encrypt!(aes::Aes128, $nonce, $tag),
        192 => ocb_encrypt!(aes::Aes192, $nonce, $tag),
        256 => ocb_encrypt!(aes::Aes256, $nonce, $tag),
        _ => return Err(EncryptError::InvalidLength),
      }
    };
  }
  macro_rules! ocb_encrypt_for_key {
    ($nonce:ty) => {
      match tag_size {
        OcbTagSize::U8 => ocb_encrypt_for_tag!($nonce, U8),
        OcbTagSize::U12 => ocb_encrypt_for_tag!($nonce, U12),
        OcbTagSize::U16 => ocb_encrypt_for_tag!($nonce, U16),
      }
    };
  }

  let tag = match iv.len() {
    6 => ocb_encrypt_for_key!(U6),
    7 => ocb_encrypt_for_key!(U7),
    8 => ocb_encrypt_for_key!(U8),
    9 => ocb_encrypt_for_key!(U9),
    10 => ocb_encrypt_for_key!(U10),
    11 => ocb_encrypt_for_key!(U11),
    12 => ocb_encrypt_for_key!(U12),
    13 => ocb_encrypt_for_key!(U13),
    14 => ocb_encrypt_for_key!(U14),
    15 => ocb_encrypt_for_key!(U15),
    _ => return Err(EncryptError::InvalidIvLength),
  };

  // The detached tag is already `tag_length / 8` bytes (TagSize), so no
  // truncation is required.
  // C | T
  ciphertext.extend_from_slice(&tag);

  Ok(ciphertext)
}

/// The AES-OCB tag sizes permitted by the WICG WebCrypto extension, mapping
/// each WICG `tagLength` to the `ocb3` TagSize typenum used at instantiation.
#[derive(Clone, Copy)]
enum OcbTagSize {
  U8,
  U12,
  U16,
}

pub(crate) fn encrypt_chacha20_poly1305(
  key: &RawKeyData,
  nonce: &[u8],
  additional_data: Option<Vec<u8>>,
  data: &[u8],
) -> Result<Vec<u8>, EncryptError> {
  let key_bytes = key.as_secret_key()?;
  if key_bytes.len() != 32 {
    return Err(EncryptError::InvalidChaChaKeyLength);
  }
  if nonce.len() != 12 {
    return Err(EncryptError::InvalidChaChaNonceLength);
  }
  // RFC 8439 caps plaintext length per nonce at 2^32 * 64 - 64 bytes.
  if data.len() as u64 > ((1u64 << 32) - 1) * 64 {
    return Err(EncryptError::TooMuchData);
  }

  let unbound_key = UnboundKey::new(&CHACHA20_POLY1305, key_bytes)
    .map_err(|_| EncryptError::Failed)?;
  let sealing_key = LessSafeKey::new(unbound_key);
  let aws_nonce = AwsNonce::try_assume_unique_for_key(nonce)
    .map_err(|_| EncryptError::Failed)?;
  let aad = additional_data.unwrap_or_default();

  let mut in_out = data.to_vec();
  sealing_key
    .seal_in_place_append_tag(aws_nonce, Aad::from(&aad), &mut in_out)
    .map_err(|_| EncryptError::Failed)?;

  Ok(in_out)
}

fn encrypt_aes_ctr_gen<B>(
  key: &[u8],
  counter: &[u8],
  data: &[u8],
) -> Result<Vec<u8>, EncryptError>
where
  B: KeyIvInit + StreamCipher,
{
  let mut cipher = B::new(key.into(), counter.into());

  let mut ciphertext = data.to_vec();
  cipher
    .try_apply_keystream(&mut ciphertext)
    .map_err(|_| EncryptError::TooMuchData)?;

  Ok(ciphertext)
}

pub(crate) fn encrypt_aes_ctr(
  key: &RawKeyData,
  key_length: usize,
  counter: &[u8],
  ctr_length: usize,
  data: &[u8],
) -> Result<Vec<u8>, EncryptError> {
  let key = key.as_secret_key()?;

  match ctr_length {
    32 => match key_length {
      128 => encrypt_aes_ctr_gen::<Ctr32BE<aes::Aes128>>(key, counter, data),
      192 => encrypt_aes_ctr_gen::<Ctr32BE<aes::Aes192>>(key, counter, data),
      256 => encrypt_aes_ctr_gen::<Ctr32BE<aes::Aes256>>(key, counter, data),
      _ => Err(EncryptError::InvalidLength),
    },
    64 => match key_length {
      128 => encrypt_aes_ctr_gen::<Ctr64BE<aes::Aes128>>(key, counter, data),
      192 => encrypt_aes_ctr_gen::<Ctr64BE<aes::Aes192>>(key, counter, data),
      256 => encrypt_aes_ctr_gen::<Ctr64BE<aes::Aes256>>(key, counter, data),
      _ => Err(EncryptError::InvalidLength),
    },
    128 => match key_length {
      128 => encrypt_aes_ctr_gen::<Ctr128BE<aes::Aes128>>(key, counter, data),
      192 => encrypt_aes_ctr_gen::<Ctr128BE<aes::Aes192>>(key, counter, data),
      256 => encrypt_aes_ctr_gen::<Ctr128BE<aes::Aes256>>(key, counter, data),
      _ => Err(EncryptError::InvalidLength),
    },
    _ => Err(EncryptError::InvalidCounterLength),
  }
}
