#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
pub mod aes_gcm;
#[cfg(not(feature = "fips"))]
pub mod chacha20poly1305;
pub const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16;
pub const AES_GCM_128_KEY_LEN: usize = 16;
pub const AES_GCM_256_KEY_LEN: usize = 32;
pub const CHACHA20_POLY1305_KEY_LEN: usize = 32;
pub type Nonce = [u8; NONCE_LEN];
pub type Tag = [u8; TAG_LEN];
mod sealed {
pub trait Sealed {}
impl Sealed for super::aes_gcm::AesGcm128 {}
impl Sealed for super::aes_gcm::AesGcm256 {}
#[cfg(not(feature = "fips"))]
impl Sealed for super::chacha20poly1305::ChaCha20Poly1305Cipher {}
}
pub trait AeadCipher: sealed::Sealed {
const KEY_LEN: usize;
fn new(key: &[u8]) -> Result<Self, AeadError>
where
Self: Sized;
fn generate_nonce() -> Nonce;
fn encrypt(
&self,
nonce: &Nonce,
plaintext: &[u8],
aad: Option<&[u8]>,
) -> Result<(Vec<u8>, Tag), AeadError>;
fn decrypt(
&self,
nonce: &Nonce,
ciphertext: &[u8],
tag: &Tag,
aad: Option<&[u8]>,
) -> Result<zeroize::Zeroizing<Vec<u8>>, AeadError>;
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum AeadError {
#[error("Invalid key length")]
InvalidKeyLength,
#[error("Invalid nonce length")]
InvalidNonceLength,
#[error("Encryption failed: {0}")]
EncryptionFailed(String),
#[error("Decryption failed: {0}")]
DecryptionFailed(String),
#[error("AEAD error: {0}")]
Other(String),
}
pub(crate) fn warn_if_all_zero_key(key: &[u8], cipher_label: &str) {
if key.iter().all(|&b| b == 0) {
tracing::warn!(
"All-zero key detected for {}. This is insecure in production.",
cipher_label
);
}
}
#[must_use]
pub fn verify_tag_constant_time(expected: &Tag, actual: &Tag) -> bool {
use subtle::ConstantTimeEq;
expected.ct_eq(actual).into()
}
pub fn zeroize_data(data: &mut [u8]) {
use zeroize::Zeroize;
data.zeroize();
}
#[cfg(not(feature = "fips"))]
pub use self::chacha20poly1305::{ChaCha20Poly1305Cipher, XChaCha20Poly1305Cipher};
#[cfg(test)]
#[allow(unused_imports)] mod tests {
use super::*;
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
use subtle::ConstantTimeEq;
let len_eq = a.len().ct_eq(&b.len());
let mut result = len_eq;
for (x, y) in a.iter().zip(b.iter()) {
result &= x.ct_eq(y);
}
result.into()
}
#[test]
fn test_constant_time_eq_equal_succeeds() {
assert!(constant_time_eq(b"hello", b"hello"));
assert!(constant_time_eq(b"", b""));
assert!(constant_time_eq(&[0u8; 32], &[0u8; 32]));
}
#[test]
fn test_constant_time_eq_not_equal_succeeds() {
assert!(!constant_time_eq(b"hello", b"world"));
assert!(!constant_time_eq(b"short", b"longer"));
assert!(!constant_time_eq(b"a", b""));
}
#[test]
fn test_aead_constants_succeeds() {
assert_eq!(NONCE_LEN, 12);
assert_eq!(TAG_LEN, 16);
assert_eq!(AES_GCM_128_KEY_LEN, 16);
assert_eq!(AES_GCM_256_KEY_LEN, 32);
assert_eq!(CHACHA20_POLY1305_KEY_LEN, 32);
}
#[test]
fn test_aead_error_display_fails() {
let err = AeadError::InvalidKeyLength;
assert_eq!(format!("{}", err), "Invalid key length");
let err = AeadError::InvalidNonceLength;
assert_eq!(format!("{}", err), "Invalid nonce length");
let err = AeadError::EncryptionFailed("test".to_string());
assert_eq!(format!("{}", err), "Encryption failed: test");
let err = AeadError::DecryptionFailed("oops".to_string());
assert_eq!(format!("{}", err), "Decryption failed: oops");
let err = AeadError::Other("misc".to_string());
assert_eq!(format!("{}", err), "AEAD error: misc");
}
#[test]
fn test_nonce_and_tag_types_succeeds() {
let nonce: Nonce = [0u8; NONCE_LEN];
assert_eq!(nonce.len(), 12);
let tag: Tag = [0u8; TAG_LEN];
assert_eq!(tag.len(), 16);
}
}