use crate::{Result, SodiumError};
use std::convert::TryFrom;
use std::fmt;
pub mod chacha20;
pub mod salsa20;
pub mod xchacha20;
pub const KEYBYTES: usize = libsodium_sys::crypto_stream_KEYBYTES as usize;
pub const NONCEBYTES: usize = libsodium_sys::crypto_stream_NONCEBYTES as usize;
#[derive(Debug, Clone, Eq, PartialEq, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
pub struct Key([u8; KEYBYTES]);
impl Key {
pub fn generate() -> Result<Self> {
let mut key = [0u8; KEYBYTES];
unsafe {
libsodium_sys::crypto_stream_keygen(key.as_mut_ptr());
}
Ok(Key(key))
}
pub fn from_slice(slice: &[u8]) -> Result<Self> {
if slice.len() != KEYBYTES {
return Err(SodiumError::InvalidInput(format!(
"key must be exactly {KEYBYTES} bytes"
)));
}
let mut key = [0u8; KEYBYTES];
key.copy_from_slice(slice);
Ok(Key(key))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Key([...])") }
}
impl TryFrom<&[u8]> for Key {
type Error = SodiumError;
fn try_from(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
Key::from_slice(bytes)
}
}
impl AsRef<[u8]> for Key {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<[u8; KEYBYTES]> for Key {
fn from(bytes: [u8; KEYBYTES]) -> Self {
Self(bytes)
}
}
impl From<Key> for [u8; KEYBYTES] {
fn from(key: Key) -> [u8; KEYBYTES] {
key.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_generation() {
let key = Key::generate().unwrap();
assert_eq!(key.as_bytes().len(), KEYBYTES);
let key_bytes = key.as_bytes();
let key2 = Key::from_slice(key_bytes).unwrap();
assert_eq!(key2.as_bytes(), key_bytes);
}
#[test]
fn test_key_traits() {
let bytes = [0x42; KEYBYTES];
let key = Key::try_from(&bytes[..]).unwrap();
assert_eq!(key.as_bytes(), &bytes);
let invalid_bytes = [0x42; KEYBYTES - 1];
assert!(Key::try_from(&invalid_bytes[..]).is_err());
let bytes = [0x43; KEYBYTES];
let key2 = Key::from(bytes);
assert_eq!(key2.as_bytes(), &bytes);
let extracted: [u8; KEYBYTES] = key2.into();
assert_eq!(extracted, bytes);
let key3 = Key::generate().unwrap();
let slice_ref: &[u8] = key3.as_ref();
assert_eq!(slice_ref.len(), KEYBYTES);
}
#[test]
fn test_chacha20() {
let key = Key::generate().unwrap();
let nonce = chacha20::Nonce::from_bytes([0u8; chacha20::NONCEBYTES]);
let message = b"test message";
let stream = chacha20::stream(32, &nonce, &key).unwrap();
assert_eq!(stream.len(), 32);
assert!(stream.iter().any(|&b| b != 0));
let encrypted = chacha20::stream_xor(message, &nonce, &key).unwrap();
assert_ne!(&encrypted, message);
let decrypted = chacha20::stream_xor(&encrypted, &nonce, &key).unwrap();
assert_eq!(&decrypted, message);
}
#[test]
fn test_xchacha20() {
let key = Key::generate().unwrap();
let nonce = xchacha20::Nonce::from_bytes([0u8; xchacha20::NONCEBYTES]);
let message = b"test message";
let stream = xchacha20::stream(32, &nonce, &key).unwrap();
assert_eq!(stream.len(), 32);
assert!(stream.iter().any(|&b| b != 0));
let encrypted = xchacha20::stream_xor(message, &nonce, &key).unwrap();
assert_ne!(&encrypted, message);
let decrypted = xchacha20::stream_xor(&encrypted, &nonce, &key).unwrap();
assert_eq!(&decrypted, message);
}
#[test]
fn test_salsa20() {
let key = Key::generate().unwrap();
let nonce = salsa20::Nonce::from_bytes([0u8; salsa20::NONCEBYTES]);
let message = b"test message";
let stream = salsa20::stream(32, &nonce, &key);
assert_eq!(stream.len(), 32);
assert!(stream.iter().any(|&b| b != 0));
let encrypted = salsa20::stream_xor(message, &nonce, &key);
assert_ne!(&encrypted, message);
let decrypted = salsa20::stream_xor(&encrypted, &nonce, &key);
assert_eq!(&decrypted, message);
}
#[test]
fn test_invalid_nonce_length() {
let _key = Key::generate().unwrap();
let invalid_nonce_long = vec![0u8; chacha20::NONCEBYTES + 1];
let invalid_nonce_short = vec![0u8; chacha20::NONCEBYTES - 1];
assert!(chacha20::Nonce::try_from_slice(&invalid_nonce_long).is_err());
assert!(chacha20::Nonce::try_from_slice(&invalid_nonce_short).is_err());
let invalid_xchacha_nonce_long = vec![0u8; xchacha20::NONCEBYTES + 1];
let invalid_xchacha_nonce_short = vec![0u8; xchacha20::NONCEBYTES - 1];
assert!(xchacha20::Nonce::try_from_slice(&invalid_xchacha_nonce_long).is_err());
assert!(xchacha20::Nonce::try_from_slice(&invalid_xchacha_nonce_short).is_err());
let invalid_salsa_nonce_long = vec![0u8; salsa20::NONCEBYTES + 1];
let invalid_salsa_nonce_short = vec![0u8; salsa20::NONCEBYTES - 1];
assert!(salsa20::Nonce::try_from_slice(&invalid_salsa_nonce_long).is_err());
assert!(salsa20::Nonce::try_from_slice(&invalid_salsa_nonce_short).is_err());
}
}