philbin 1.0.1

A pure Rust AEGIS library with SIMD and runtime CPU detection
Documentation
use crate::{
  error::{Error, Result},
  utils::validate_non_zero,
};
use std::fmt::Debug;
use zeroize::ZeroizeOnDrop;

/// Stores the secret key used in AEGIS encryption and decryption.
///
/// This type takes a const generic parameter (`BYTES`) specifying the
/// number of key bytes. `AEGIS-128[L|X]` ciphers use 128 bit keys while
/// `AEGIS-256[X]` ciphers use 256 bit keys. (All ciphers validate the
/// provided `Key` size at compile time making it impossible to use an
/// incorrect `Key` size.)
///
/// Since only 128 or 256 bit keys are valid for AEGIS ciphers, only values
/// `16` and `32` are supported for the `Key`'s `BYTES` const generic
/// parameter. This too is validated at compile time.
///
/// [`Key128`] (for `Key<16>`) and [`Key256`] (for `Key<32>`) type aliases are
/// provided for convenience and should be preferred over raw `Key` usage.
///
/// **Use [`Key::generate()`] to securely create random `Key`s** instead of
/// generating key bytes yourself and passing them to [`Key::new()`] or
/// [`Key::from_bytes()`]. The [`Key::generate()`] method will use an
/// appropriate [_cryptographically secure_ random number generator][csrng]
/// (CSRNG) provided by the OS.
///
/// You can access the internal key bytes with [`Key::expose_secret()`]. This
/// method is also the _only_ way to access secret bytes once they are stored in
/// `Key`, making security audits easier.
///
/// `Key` uses a custom implementation of [`Debug`][std::fmt::Debug] that
/// _always_ redacts the key bytes to prevent accidental exposure of secrets
/// through logs or other machinery.
///
/// This type is zeroized on [`Drop`].
///
/// Note that derives for [`Eq`] and [`PartialEq`] are intentionally omitted to
/// prevent accidental non-constant-time equality comparisons. Use
/// [`Key::expose_secret()`] and the
/// [`constant_time_eq`](https://lib.rs/crates/constant_time_eq) crate if you
/// need this.
/// [`Clone`] is not implemented to prevent accidental secret duplication.
///
/// [csrng]: https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
#[derive(ZeroizeOnDrop)]
pub struct Key<const BYTES: usize> {
  data: [u8; BYTES],
}

impl<const BYTES: usize> Key<BYTES> {
  /// The length of `Key` in bytes.
  pub const BYTES: usize = BYTES;

  /// Create a new, random `Key` from a cryptographically secure random
  /// number generator (CSRNG).
  ///
  /// # Errors
  ///
  /// - Returns `Error::RandError` if there's an error in fetching random
  ///   bytes.
  #[cfg(feature = "rand")]
  pub fn generate() -> Result<Self> {
    use crate::utils::random_bytes;
    Self::new(random_bytes()?)
  }

  /// Create a new `Key` from a correctly sized byte array.
  ///
  /// **Prefer [`Key::generate()`] over this method**.
  ///
  /// <div class="warning">
  ///
  /// The bytes you provide _must_ come from [_cryptographically secure_ random
  /// number generator][csrng] (CSRNG). Do _not_ use whatever RNG you found
  /// lying around.
  ///
  /// [csrng]: https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
  ///
  /// </div>
  ///
  /// # Errors
  ///
  /// - Returns [`Error::AllZeroNotAllowed`][Error] if the provided byte array
  ///   contains only zeros.
  pub fn new(data: [u8; BYTES]) -> Result<Self> {
    // Compile-time validation for correct size
    const {
      assert!(
        BYTES == 16 || BYTES == 32,
        "Only 16 or 32 byte (128 or 256 bit) keys are supported!"
      );
    }

    // The user is almost certainly making a (catastrophic) mistake if they
    // provide an all-zero key, so we return an error in that case.
    validate_non_zero(data).map(|x| Self { data: x })
  }

  /// Create a new `Key` from any `T` which implements [`AsRef<[u8]>`][AsRef].
  /// Thus it's possible to pass a [`&[u8]`][slice], a [`&mut [u8]`][slice],
  /// a [`Vec<u8>`], a [`Box<[u8]>`][Box] etc.
  ///
  /// **Prefer [`Key::generate()`] over this method**.
  ///
  /// <div class="warning">
  ///
  /// The bytes you provide _must_ come from [_cryptographically secure_ random
  /// number generator][csrng] (CSRNG). Do _not_ use whatever RNG you found
  /// lying around.
  ///
  /// [csrng]: https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
  ///
  /// </div>
  ///
  /// # Errors
  ///
  /// - Returns [`Error::InputBufferWrongSize`][Error] if the
  ///   length of the provided byte slice is not exactly equal to `BYTES`.
  /// - Returns [`Error::AllZeroNotAllowed`][Error] if the
  ///   provided byte slice contains only zeros.
  pub fn from_bytes<T: AsRef<[u8]>>(data: T) -> Result<Self> {
    // SECURITY: The (internal) IF check is safe WRT timing attacks because:
    // - The _length_ of the key is not secret.
    let array = data
      .as_ref()
      .as_array::<BYTES>()
      // It's important to reject inputs that are too long (not just too short)
      // because we MUST NOT silently ignore excess secret bytes. The caller may
      // not be aware that the buffer they provided is too big.
      .ok_or(Error::InputBufferWrongSize)?;

    Self::new(*array)
  }

  /// Access the secret key bytes.
  ///
  /// This method is named `expose_secret` instead of a more generic `as_array`
  /// to make it easier to `grep` for in a large codebase.
  #[must_use]
  pub fn expose_secret(&self) -> &[u8; BYTES] {
    &self.data
  }
}

// We need to implement Debug to make `Key` less annoying to deal with due to
// Debug trait bounds (especially in tests), but we always redact the contents.
impl<const BYTES: usize> Debug for Key<BYTES> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    // SECURITY: Key bytes are _always_ redacted in Debug output to prevent
    // accidental disclosure through logs and other machinery.
    f.debug_struct("Key").field("data", &"<redacted>").finish()
  }
}

/// A type alias for a 128 bit (16 byte) [`Key`].
pub type Key128 = Key<16>;

/// A type alias for a 256 bit (32 byte) [`Key`].
pub type Key256 = Key<32>;