Files
yaak-mountain-loop/src-tauri/yaak-crypto/src/encryption.rs
2025-05-08 14:10:07 -07:00

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(())
}
}