rusty_paseto 0.10.0

A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens.
Documentation
use crate::core::PasetoError;
use ring::rand::{SecureRandom, SystemRandom};
use std::convert::{From, TryFrom};
use std::fmt::Debug;
use std::ops::Deref;
use zeroize::Zeroize;

/// A wrapper for a slice of bytes that constitute a key of a specific size
#[derive(Zeroize)]
#[zeroize(drop)]
#[derive(Clone)]
pub struct Key<const KEYSIZE: usize>([u8; KEYSIZE]);

impl<const KEYSIZE: usize> Default for Key<KEYSIZE> {
  fn default() -> Self {
    Self([0u8; KEYSIZE])
  }
}

impl<const KEYSIZE: usize> AsRef<[u8]> for Key<KEYSIZE> {
  fn as_ref(&self) -> &[u8] {
    &self.0
  }
}

impl<const KEYSIZE: usize> Deref for Key<KEYSIZE> {
  type Target = [u8; KEYSIZE];
  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

/// Fallible construction of a [`Key`] from a byte slice.
///
/// Returns [`PasetoError::InvalidKey`] if `key.len() != KEYSIZE`. This
/// replaces the previous `From<&[u8]>` impl, which silently panicked on
/// size mismatch via `copy_from_slice`. The infallible
/// [`From<&[u8; KEYSIZE]>`] and [`From<[u8; KEYSIZE]>`] impls remain for
/// callers who have an array of the correct size at compile time.
impl<const KEYSIZE: usize> TryFrom<&[u8]> for Key<KEYSIZE> {
  type Error = PasetoError;
  fn try_from(key: &[u8]) -> Result<Self, Self::Error> {
    if key.len() != KEYSIZE {
      return Err(PasetoError::InvalidKey);
    }
    let mut me = Self::default();
    me.0.copy_from_slice(key);
    Ok(me)
  }
}

impl<const KEYSIZE: usize> From<&[u8; KEYSIZE]> for Key<KEYSIZE> {
  fn from(key: &[u8; KEYSIZE]) -> Self {
    Self(*key)
  }
}

impl<const KEYSIZE: usize> From<[u8; KEYSIZE]> for Key<KEYSIZE> {
  fn from(key: [u8; KEYSIZE]) -> Self {
    Self(key)
  }
}

impl<const KEYSIZE: usize> TryFrom<&str> for Key<KEYSIZE> {
  type Error = PasetoError;
  fn try_from(value: &str) -> Result<Self, Self::Error> {
    let key = hex::decode(value).map_err(|_| PasetoError::InvalidKey)?;
    if key.len() != KEYSIZE {
      return Err(PasetoError::InvalidKey);
    }
    let mut me = Self::default();
    me.0.copy_from_slice(&key);
    Ok(me)
  }
}

impl<const KEYSIZE: usize> Key<KEYSIZE> {
  /// Uses the system's RNG to create a random slice of bytes of a specific size.
  ///
  /// # Errors
  ///
  /// Returns [`PasetoError::Cipher`] if the system's random number generator fails to fill the buffer.
  pub fn try_new_random() -> Result<Self, PasetoError> {
    let rng = SystemRandom::new();
    let mut buf = [0u8; KEYSIZE];
    rng.fill(&mut buf)?;
    Ok(Self(buf))
  }
}

#[cfg(test)]
mod key_size_check_tests {
  use super::*;

  #[test]
  fn try_from_rejects_short_slice() {
    let too_short = [0u8; 16];
    let result = Key::<32>::try_from(&too_short[..]);
    assert!(matches!(result, Err(PasetoError::InvalidKey)));
  }

  #[test]
  fn try_from_rejects_long_slice() {
    let too_long = [0u8; 48];
    let result = Key::<32>::try_from(&too_long[..]);
    assert!(matches!(result, Err(PasetoError::InvalidKey)));
  }

  #[test]
  fn try_from_accepts_exact_size() {
    let exact = [0u8; 32];
    let result = Key::<32>::try_from(&exact[..]);
    assert!(result.is_ok());
  }
}

impl<const KEYSIZE: usize> Debug for Key<KEYSIZE> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "!!! KEY IS PRIVATE !!!")
  }
}