diff --git a/src/canonicalization.rs b/src/canonicalization.rs index b28984a..d64a0ad 100644 --- a/src/canonicalization.rs +++ b/src/canonicalization.rs @@ -4,8 +4,8 @@ const CANONICALIZATION_BUFFER_SIZE: usize = 1024; const CANONICALIZATION_SEPARATOR: &str = ":"; #[inline] -pub(crate) fn join_canonicalized_str(s1: &str, s2: &str) -> String { - format!("{s1}{CANONICALIZATION_SEPARATOR}{s2}") +pub(crate) fn join_canonicalized_str(elems: &[String]) -> String { + elems.join(CANONICALIZATION_SEPARATOR) } pub(crate) fn canonicalize(context: &[impl AsRef<[u8]>]) -> String { @@ -51,13 +51,31 @@ mod tests { #[test] fn test_join_canonicalized_empty() { - assert_eq!(join_canonicalized_str("", ""), ":"); + assert_eq!(join_canonicalized_str(&[]), ""); + } + + #[test] + fn test_join_canonicalized_one() { + assert_eq!( + join_canonicalized_str(&["QWO7RGDt".to_string()]), + "QWO7RGDt" + ); + } + + #[test] + fn test_join_canonicalized_one_empty() { + assert_eq!(join_canonicalized_str(&[String::new()]), ""); + } + + #[test] + fn test_join_canonicalized_empty_str() { + assert_eq!(join_canonicalized_str(&[String::new(), String::new()]), ":"); } #[test] fn test_join_canonicalized_with_data() { assert_eq!( - join_canonicalized_str("QWO7RGDt:f-JmDPvU", "_Sfx61Fp"), + join_canonicalized_str(&["QWO7RGDt:f-JmDPvU".into(), "_Sfx61Fp".into()]), "QWO7RGDt:f-JmDPvU:_Sfx61Fp" ); } diff --git a/src/encryption.rs b/src/encryption.rs index 05e4b02..4bc104c 100644 --- a/src/encryption.rs +++ b/src/encryption.rs @@ -1,11 +1,12 @@ use crate::canonicalization::{canonicalize, join_canonicalized_str}; use crate::error::Result; use crate::kdf::{derive_key, KeyContext}; -use crate::{storage, InputKeyMaterialList}; +use crate::{storage, IkmId, InputKeyMaterialList}; use std::time::{SystemTime, UNIX_EPOCH}; pub(crate) type DecryptionFunction = dyn Fn(&[u8], &EncryptedData, &str) -> Result>; -pub(crate) type EncryptionFunction = dyn Fn(&[u8], &[u8], &str) -> Result; +pub(crate) type EncryptionFunction = dyn Fn(&[u8], &[u8], &[u8], &str) -> Result; +pub(crate) type GenNonceFunction = dyn Fn() -> Result>; pub struct DataContext { ctx: Vec, @@ -33,14 +34,23 @@ pub(crate) struct EncryptedData { #[inline] fn generate_aad( + ikm_id: IkmId, + nonce: &[u8], key_context: &KeyContext, data_context: &DataContext, time_period: Option, ) -> String { + let ikm_id_canon = canonicalize(&[ikm_id.to_le_bytes()]); + let nonce_canon = canonicalize(&[nonce]); let elems = key_context.get_ctx_elems(time_period); let key_context_canon = canonicalize(&elems); let data_context_canon = canonicalize(data_context.get_ctx_elems()); - join_canonicalized_str(&key_context_canon, &data_context_canon) + join_canonicalized_str(&[ + ikm_id_canon, + nonce_canon, + key_context_canon, + data_context_canon, + ]) } pub fn encrypt( @@ -57,9 +67,11 @@ pub fn encrypt( }; let ikm = ikml.get_latest_ikm()?; let key = derive_key(ikm, key_context, tp); - let aad = generate_aad(key_context, data_context, tp); + let gen_nonce_function = ikm.scheme.get_gen_nonce(); + let nonce = gen_nonce_function()?; + let aad = generate_aad(ikm.id, &nonce, key_context, data_context, tp); let encryption_function = ikm.scheme.get_encryption(); - let encrypted_data = encryption_function(&key, data.as_ref(), &aad)?; + let encrypted_data = encryption_function(&key, &nonce, data.as_ref(), &aad)?; Ok(storage::encode_cipher(ikm.id, &encrypted_data, tp)) } @@ -72,7 +84,7 @@ pub fn decrypt( let (ikm_id, encrypted_data, tp) = storage::decode_cipher(stored_data)?; let ikm = ikml.get_ikm_by_id(ikm_id)?; let key = derive_key(ikm, key_context, tp); - let aad = generate_aad(key_context, data_context, tp); + let aad = generate_aad(ikm.id, &encrypted_data.nonce, key_context, data_context, tp); let decryption_function = ikm.scheme.get_decryption(); decryption_function(&key, &encrypted_data, &aad) } @@ -82,7 +94,7 @@ mod tests { use super::*; use crate::{DataContext, KeyContext}; - const TEST_CIPHERTEXT: &str = "AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA"; + const TEST_CIPHERTEXT: &str = "AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA"; const TEST_DATA: &[u8] = b"Lorem ipsum dolor sit amet."; const TEST_KEY_CTX: [&str; 3] = ["db_name", "table_name", "column_name"]; const TEST_DATA_CTX: [&str; 1] = ["018db876-3d9d-79af-9460-55d17da991d8"]; @@ -170,12 +182,12 @@ mod tests { fn decrypt_invalid_ciphertext() { let tests = &[ ("", "empty data"), - ("AQAATA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA", "unknown ikm id"), - ("AQAAAA:MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA", "invalid nonce"), - ("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:8izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA", "invalid ciphertext"), - ("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NaAAAAAAAAA", "invalid time period"), - ("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:", "empty time period"), - ("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A", "missing time period"), + ("AQAATA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA", "unknown ikm id"), + ("AQAAAA:MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA", "invalid nonce"), + ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:8e_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA", "invalid ciphertext"), + ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NaAAAAAAAAA", "invalid time period"), + ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:", "empty time period"), + ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg", "missing time period"), ]; let lst = get_ikm_lst(); diff --git a/src/scheme.rs b/src/scheme.rs index 4e71bb0..d8c9f7e 100644 --- a/src/scheme.rs +++ b/src/scheme.rs @@ -1,5 +1,5 @@ #[cfg(feature = "encryption")] -use crate::encryption::{DecryptionFunction, EncryptionFunction}; +use crate::encryption::{DecryptionFunction, EncryptionFunction, GenNonceFunction}; #[cfg(feature = "encryption")] use crate::kdf::KdfFunction; use crate::Error; @@ -45,6 +45,14 @@ impl Scheme { } } } + + pub(crate) fn get_gen_nonce(&self) -> Box { + match self { + Scheme::XChaCha20Poly1305WithBlake3 => { + Box::new(xchacha20poly1305::xchacha20poly1305_gen_nonce) + } + } + } } impl TryFrom for Scheme { diff --git a/src/scheme/xchacha20poly1305.rs b/src/scheme/xchacha20poly1305.rs index e2bb8f2..79fca39 100644 --- a/src/scheme/xchacha20poly1305.rs +++ b/src/scheme/xchacha20poly1305.rs @@ -3,18 +3,22 @@ use crate::error::Result; use chacha20poly1305::aead::{Aead, KeyInit, Payload}; use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; +pub(crate) fn xchacha20poly1305_gen_nonce() -> Result> { + // X-variant: the nonce's size is 192 bits (24 bytes) + let mut nonce: [u8; 24] = [0; 24]; + getrandom::getrandom(&mut nonce)?; + Ok(nonce.to_vec()) +} + pub(crate) fn xchacha20poly1305_encrypt( key: &[u8], + nonce: &[u8], data: &[u8], aad: &str, ) -> Result { - // Adapt the key + // Adapt the key and nonce let key = Key::from_slice(key); - - // Generate a nonce - let mut nonce: [u8; 24] = [0; 24]; - getrandom::getrandom(&mut nonce)?; - let nonce = XNonce::from_slice(&nonce); + let nonce = XNonce::from_slice(nonce); // Prepare the payload let payload = Payload {