philbin 1.0.1

A pure Rust AEGIS library with SIMD and runtime CPU detection
Documentation
use similar_asserts::assert_eq;
use std::assert_matches;
use test_utils::AegisTestCase;

use philbin::{
  careful::Nonce,
  easy::{
    AssociatedData, AuthTag128, Ciphertext, CiphertextMut, Error, Key,
    Plaintext, PlaintextMut, Result,
  },
};

// Takes a provided `philbin::careful` algo submod and generates tests that
// verify correct handling of the user-provided input buffers when they are
// longer or shorter than expected.
//
// Call example:
//   gen_buffer_len_tests!(aegis128l);
macro_rules! gen_buffer_len_tests {
  (
    $algo_mod:ident // Should be a `philbin::careful` algo submod
  ) => {
    // All of these tests use AuthTag128 and that's fine; the buffer handling
    // logic is the same for both AuthTag128 and AuthTag256.

    #[rstest::rstest]
    fn encryption_short_buffer(
      #[from(test_utils::$algo_mod::example)]
      expected: test_utils::AegisTestCase,
    ) -> anyhow::Result<()> {
      buf_len_tests::verify_encryption_short_buffer(
        philbin::careful::$algo_mod::encrypt_to_slice_detached,
        expected,
      )
    }

    #[rstest::rstest]
    fn encryption_long_buffer(
      #[from(test_utils::$algo_mod::example)]
      expected: test_utils::AegisTestCase,
    ) -> anyhow::Result<()> {
      buf_len_tests::verify_encryption_long_buffer(
        philbin::careful::$algo_mod::encrypt_to_slice_detached,
        expected,
      )
    }

    #[rstest::rstest]
    fn decryption_short_buffer(
      #[from(test_utils::$algo_mod::example)]
      expected: test_utils::AegisTestCase,
    ) -> anyhow::Result<()> {
      buf_len_tests::verify_decryption_short_buffer(
        philbin::careful::$algo_mod::decrypt_to_slice_detached,
        expected,
      )
    }

    #[rstest::rstest]
    fn decryption_long_buffer(
      #[from(test_utils::$algo_mod::example)]
      expected: test_utils::AegisTestCase,
    ) -> anyhow::Result<()> {
      buf_len_tests::verify_decryption_long_buffer(
        philbin::careful::$algo_mod::decrypt_to_slice_detached,
        expected,
      )
    }
  };
}
pub(crate) use gen_buffer_len_tests;

pub fn verify_encryption_short_buffer<const KEY_BYTES: usize>(
  func: fn(
    Plaintext,
    AssociatedData,
    &Key<KEY_BYTES>,
    Nonce<KEY_BYTES>,
    CiphertextMut,
  ) -> Result<AuthTag128>,
  expected: AegisTestCase,
) -> anyhow::Result<()> {
  // NOTE: Intentionally shorter than necessary to trigger error
  let mut actual_ciphertext = vec![0; 11];
  assert_matches!(
    func(
      Plaintext::new(&expected.plaintext),
      AssociatedData::new(&expected.associated_data),
      &Key::<KEY_BYTES>::from_bytes(expected.key)?,
      Nonce::<KEY_BYTES>::new(*expected.nonce.as_array().unwrap())?,
      CiphertextMut::new(&mut actual_ciphertext),
    ),
    Err(Error::OutputBufferTooSmall)
  );

  // Buffer must remain untouched!
  assert_eq!(&vec![0; actual_ciphertext.len()], &actual_ciphertext);

  Ok(())
}

pub fn verify_encryption_long_buffer<const KEY_BYTES: usize>(
  func: fn(
    Plaintext,
    AssociatedData,
    &Key<KEY_BYTES>,
    Nonce<KEY_BYTES>,
    CiphertextMut,
  ) -> Result<AuthTag128>,
  expected: AegisTestCase,
) -> anyhow::Result<()> {
  // NOTE: Intentionally longer than necessary to test correct handling.
  let mut actual_ciphertext = vec![0; expected.plaintext.len() + 100];
  let actual_tag: AuthTag128 = func(
    Plaintext::new(&expected.plaintext),
    AssociatedData::new(&expected.associated_data),
    &Key::<KEY_BYTES>::from_bytes(expected.key)?,
    Nonce::<KEY_BYTES>::new(*expected.nonce.as_array().unwrap())?,
    CiphertextMut::new(&mut actual_ciphertext),
  )?;
  assert_eq!(
    expected.ciphertext,
    actual_ciphertext[..expected.ciphertext.len()]
  );
  assert_eq!(expected.tag128, actual_tag);

  // Rest of the buffer must remain untouched!
  let rest_of_buffer = &actual_ciphertext[expected.ciphertext.len()..];
  assert_eq!(&vec![0; rest_of_buffer.len()], &rest_of_buffer);

  Ok(())
}

pub fn verify_decryption_short_buffer<const KEY_BYTES: usize>(
  func: fn(
    Ciphertext,
    &AuthTag128,
    AssociatedData,
    &Key<KEY_BYTES>,
    Nonce<KEY_BYTES>,
    PlaintextMut,
  ) -> Result<()>,
  expected: AegisTestCase,
) -> anyhow::Result<()> {
  // NOTE: Intentionally shorter than necessary to trigger error
  let mut actual_plaintext = vec![0; 11];
  assert_matches!(
    func(
      Ciphertext::new(&expected.ciphertext),
      &expected.tag128,
      AssociatedData::new(&expected.associated_data),
      &Key::<KEY_BYTES>::from_bytes(expected.key)?,
      Nonce::<KEY_BYTES>::new(*expected.nonce.as_array().unwrap())?,
      PlaintextMut::new(&mut actual_plaintext),
    ),
    Err(Error::OutputBufferTooSmall)
  );

  // Buffer must remain untouched!
  assert_eq!(&vec![0; actual_plaintext.len()], &actual_plaintext);

  Ok(())
}

pub fn verify_decryption_long_buffer<const KEY_BYTES: usize>(
  func: fn(
    Ciphertext,
    &AuthTag128,
    AssociatedData,
    &Key<KEY_BYTES>,
    Nonce<KEY_BYTES>,
    PlaintextMut,
  ) -> Result<()>,
  expected: AegisTestCase,
) -> anyhow::Result<()> {
  // NOTE: Intentionally longer than necessary to test correct handling.
  let mut actual_plaintext = vec![0; expected.ciphertext.len() + 100];
  func(
    Ciphertext::new(&expected.ciphertext),
    &expected.tag128,
    AssociatedData::new(&expected.associated_data),
    &Key::<KEY_BYTES>::from_bytes(expected.key)?,
    Nonce::<KEY_BYTES>::new(*expected.nonce.as_array().unwrap())?,
    PlaintextMut::new(&mut actual_plaintext),
  )?;
  assert_eq!(
    expected.plaintext,
    actual_plaintext[..expected.plaintext.len()]
  );

  // Rest of the buffer must remain untouched!
  let rest_of_buffer = &actual_plaintext[expected.plaintext.len()..];
  assert_eq!(&vec![0; rest_of_buffer.len()], &rest_of_buffer);

  Ok(())
}