mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:13:51 +01:00
99 lines
3.7 KiB
Rust
99 lines
3.7 KiB
Rust
use crate::error::Error::{DecryptionError, EncryptionError, InvalidEncryptedData};
|
|
use crate::error::Result;
|
|
use chacha20poly1305::aead::generic_array::typenum::Unsigned;
|
|
use chacha20poly1305::aead::{Aead, AeadCore, Key, KeyInit, OsRng};
|
|
use chacha20poly1305::XChaCha20Poly1305;
|
|
|
|
const ENCRYPTION_TAG: &str = "yA4k3nC";
|
|
const ENCRYPTION_VERSION: u8 = 1;
|
|
|
|
pub(crate) fn encrypt_data(data: &[u8], key: &Key<XChaCha20Poly1305>) -> Result<Vec<u8>> {
|
|
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
|
let cipher = XChaCha20Poly1305::new(&key);
|
|
let ciphered_data = cipher.encrypt(&nonce, data).map_err(|_| EncryptionError)?;
|
|
|
|
let mut data: Vec<u8> = Vec::new();
|
|
data.extend_from_slice(ENCRYPTION_TAG.as_bytes()); // Tag
|
|
data.push(ENCRYPTION_VERSION); // Version
|
|
data.extend_from_slice(&nonce.as_slice()); // Nonce
|
|
data.extend_from_slice(&ciphered_data); // Ciphertext
|
|
|
|
Ok(data)
|
|
}
|
|
|
|
pub(crate) fn decrypt_data(cipher_data: &[u8], key: &Key<XChaCha20Poly1305>) -> Result<Vec<u8>> {
|
|
// Yaak Tag + ID + Version + Nonce + ... ciphertext ...
|
|
let (tag, rest) =
|
|
cipher_data.split_at_checked(ENCRYPTION_TAG.len()).ok_or(InvalidEncryptedData)?;
|
|
if tag != ENCRYPTION_TAG.as_bytes() {
|
|
return Err(InvalidEncryptedData);
|
|
}
|
|
|
|
let (version, rest) = rest.split_at_checked(1).ok_or(InvalidEncryptedData)?;
|
|
if version[0] != ENCRYPTION_VERSION {
|
|
return Err(InvalidEncryptedData);
|
|
}
|
|
|
|
let nonce_bytes = <XChaCha20Poly1305 as AeadCore>::NonceSize::to_usize();
|
|
let (nonce, ciphered_data) = rest.split_at_checked(nonce_bytes).ok_or(InvalidEncryptedData)?;
|
|
|
|
let cipher = XChaCha20Poly1305::new(&key);
|
|
cipher.decrypt(nonce.into(), ciphered_data).map_err(|_e| DecryptionError)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::encryption::{decrypt_data, encrypt_data};
|
|
use crate::error::Error::InvalidEncryptedData;
|
|
use crate::error::Result;
|
|
use chacha20poly1305::aead::OsRng;
|
|
use chacha20poly1305::{KeyInit, XChaCha20Poly1305};
|
|
|
|
#[test]
|
|
fn test_encrypt_decrypt() -> Result<()> {
|
|
let key = XChaCha20Poly1305::generate_key(OsRng);
|
|
let encrypted = encrypt_data("hello world".as_bytes(), &key)?;
|
|
let decrypted = decrypt_data(encrypted.as_slice(), &key)?;
|
|
assert_eq!(String::from_utf8(decrypted).unwrap(), "hello world");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_decrypt_empty() -> Result<()> {
|
|
let key = XChaCha20Poly1305::generate_key(OsRng);
|
|
let encrypted = encrypt_data(&[], &key)?;
|
|
assert_eq!(encrypted.len(), 48);
|
|
let decrypted = decrypt_data(encrypted.as_slice(), &key)?;
|
|
assert_eq!(String::from_utf8(decrypted).unwrap(), "");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_decrypt_bad_version() -> Result<()> {
|
|
let key = XChaCha20Poly1305::generate_key(OsRng);
|
|
let mut encrypted = encrypt_data("hello world".as_bytes(), &key)?;
|
|
encrypted[7] = 0;
|
|
let decrypted = decrypt_data(encrypted.as_slice(), &key);
|
|
assert!(matches!(decrypted, Err(InvalidEncryptedData)));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_decrypt_bad_tag() -> Result<()> {
|
|
let key = XChaCha20Poly1305::generate_key(OsRng);
|
|
let mut encrypted = encrypt_data("hello world".as_bytes(), &key)?;
|
|
encrypted[0] = 2;
|
|
let decrypted = decrypt_data(encrypted.as_slice(), &key);
|
|
assert!(matches!(decrypted, Err(InvalidEncryptedData)));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_decrypt_unencrypted_data() -> Result<()> {
|
|
let key = XChaCha20Poly1305::generate_key(OsRng);
|
|
let decrypted = decrypt_data("123".as_bytes(), &key);
|
|
assert!(matches!(decrypted, Err(InvalidEncryptedData)));
|
|
Ok(())
|
|
}
|
|
}
|