Add the IKM id and the nonce to the AAD

This commit is contained in:
Rodolphe Bréard 2024-03-11 14:55:08 +01:00
parent bf98245b04
commit bc3cfe71dc
4 changed files with 66 additions and 24 deletions

View file

@ -4,8 +4,8 @@ const CANONICALIZATION_BUFFER_SIZE: usize = 1024;
const CANONICALIZATION_SEPARATOR: &str = ":"; const CANONICALIZATION_SEPARATOR: &str = ":";
#[inline] #[inline]
pub(crate) fn join_canonicalized_str(s1: &str, s2: &str) -> String { pub(crate) fn join_canonicalized_str(elems: &[String]) -> String {
format!("{s1}{CANONICALIZATION_SEPARATOR}{s2}") elems.join(CANONICALIZATION_SEPARATOR)
} }
pub(crate) fn canonicalize(context: &[impl AsRef<[u8]>]) -> String { pub(crate) fn canonicalize(context: &[impl AsRef<[u8]>]) -> String {
@ -51,13 +51,31 @@ mod tests {
#[test] #[test]
fn test_join_canonicalized_empty() { 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] #[test]
fn test_join_canonicalized_with_data() { fn test_join_canonicalized_with_data() {
assert_eq!( assert_eq!(
join_canonicalized_str("QWO7RGDt:f-JmDPvU", "_Sfx61Fp"), join_canonicalized_str(&["QWO7RGDt:f-JmDPvU".into(), "_Sfx61Fp".into()]),
"QWO7RGDt:f-JmDPvU:_Sfx61Fp" "QWO7RGDt:f-JmDPvU:_Sfx61Fp"
); );
} }

View file

@ -1,11 +1,12 @@
use crate::canonicalization::{canonicalize, join_canonicalized_str}; use crate::canonicalization::{canonicalize, join_canonicalized_str};
use crate::error::Result; use crate::error::Result;
use crate::kdf::{derive_key, KeyContext}; use crate::kdf::{derive_key, KeyContext};
use crate::{storage, InputKeyMaterialList}; use crate::{storage, IkmId, InputKeyMaterialList};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) type DecryptionFunction = dyn Fn(&[u8], &EncryptedData, &str) -> Result<Vec<u8>>; pub(crate) type DecryptionFunction = dyn Fn(&[u8], &EncryptedData, &str) -> Result<Vec<u8>>;
pub(crate) type EncryptionFunction = dyn Fn(&[u8], &[u8], &str) -> Result<EncryptedData>; pub(crate) type EncryptionFunction = dyn Fn(&[u8], &[u8], &[u8], &str) -> Result<EncryptedData>;
pub(crate) type GenNonceFunction = dyn Fn() -> Result<Vec<u8>>;
pub struct DataContext { pub struct DataContext {
ctx: Vec<String>, ctx: Vec<String>,
@ -33,14 +34,23 @@ pub(crate) struct EncryptedData {
#[inline] #[inline]
fn generate_aad( fn generate_aad(
ikm_id: IkmId,
nonce: &[u8],
key_context: &KeyContext, key_context: &KeyContext,
data_context: &DataContext, data_context: &DataContext,
time_period: Option<u64>, time_period: Option<u64>,
) -> String { ) -> 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 elems = key_context.get_ctx_elems(time_period);
let key_context_canon = canonicalize(&elems); let key_context_canon = canonicalize(&elems);
let data_context_canon = canonicalize(data_context.get_ctx_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( pub fn encrypt(
@ -57,9 +67,11 @@ pub fn encrypt(
}; };
let ikm = ikml.get_latest_ikm()?; let ikm = ikml.get_latest_ikm()?;
let key = derive_key(ikm, key_context, tp); 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 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)) 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_id, encrypted_data, tp) = storage::decode_cipher(stored_data)?;
let ikm = ikml.get_ikm_by_id(ikm_id)?; let ikm = ikml.get_ikm_by_id(ikm_id)?;
let key = derive_key(ikm, key_context, tp); 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(); let decryption_function = ikm.scheme.get_decryption();
decryption_function(&key, &encrypted_data, &aad) decryption_function(&key, &encrypted_data, &aad)
} }
@ -82,7 +94,7 @@ mod tests {
use super::*; use super::*;
use crate::{DataContext, KeyContext}; 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_DATA: &[u8] = b"Lorem ipsum dolor sit amet.";
const TEST_KEY_CTX: [&str; 3] = ["db_name", "table_name", "column_name"]; const TEST_KEY_CTX: [&str; 3] = ["db_name", "table_name", "column_name"];
const TEST_DATA_CTX: [&str; 1] = ["018db876-3d9d-79af-9460-55d17da991d8"]; const TEST_DATA_CTX: [&str; 1] = ["018db876-3d9d-79af-9460-55d17da991d8"];
@ -170,12 +182,12 @@ mod tests {
fn decrypt_invalid_ciphertext() { fn decrypt_invalid_ciphertext() {
let tests = &[ let tests = &[
("", "empty data"), ("", "empty data"),
("AQAATA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA", "unknown ikm id"), ("AQAATA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA", "unknown ikm id"),
("AQAAAA:MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA", "invalid nonce"), ("AQAAAA:MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA", "invalid nonce"),
("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:8izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NgAAAAAAAAA", "invalid ciphertext"), ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:8e_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA", "invalid ciphertext"),
("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:NaAAAAAAAAA", "invalid time period"), ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NaAAAAAAAAA", "invalid time period"),
("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A:", "empty time period"), ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:", "empty time period"),
("AQAAAA:elFanOvp5DNewgq75T5U6wLYNn8zzo1n:9izU-8cw4oSIU4lqcYrfEBzOXluS7lVcUbF_KnEg0HFp2srx6xq3Bir91A", "missing time period"), ("AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg", "missing time period"),
]; ];
let lst = get_ikm_lst(); let lst = get_ikm_lst();

View file

@ -1,5 +1,5 @@
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
use crate::encryption::{DecryptionFunction, EncryptionFunction}; use crate::encryption::{DecryptionFunction, EncryptionFunction, GenNonceFunction};
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
use crate::kdf::KdfFunction; use crate::kdf::KdfFunction;
use crate::Error; use crate::Error;
@ -45,6 +45,14 @@ impl Scheme {
} }
} }
} }
pub(crate) fn get_gen_nonce(&self) -> Box<GenNonceFunction> {
match self {
Scheme::XChaCha20Poly1305WithBlake3 => {
Box::new(xchacha20poly1305::xchacha20poly1305_gen_nonce)
}
}
}
} }
impl TryFrom<SchemeSerializeType> for Scheme { impl TryFrom<SchemeSerializeType> for Scheme {

View file

@ -3,18 +3,22 @@ use crate::error::Result;
use chacha20poly1305::aead::{Aead, KeyInit, Payload}; use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce};
pub(crate) fn xchacha20poly1305_gen_nonce() -> Result<Vec<u8>> {
// 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( pub(crate) fn xchacha20poly1305_encrypt(
key: &[u8], key: &[u8],
nonce: &[u8],
data: &[u8], data: &[u8],
aad: &str, aad: &str,
) -> Result<EncryptedData> { ) -> Result<EncryptedData> {
// Adapt the key // Adapt the key and nonce
let key = Key::from_slice(key); let key = Key::from_slice(key);
let nonce = XNonce::from_slice(nonce);
// Generate a nonce
let mut nonce: [u8; 24] = [0; 24];
getrandom::getrandom(&mut nonce)?;
let nonce = XNonce::from_slice(&nonce);
// Prepare the payload // Prepare the payload
let payload = Payload { let payload = Payload {