2024-02-17 20:26:45 +01:00
|
|
|
use crate::canonicalization::{canonicalize, join_canonicalized_str};
|
2024-03-17 14:31:01 +01:00
|
|
|
use crate::context::{DataContext, KeyContext};
|
2024-02-17 20:47:07 +01:00
|
|
|
use crate::error::Result;
|
2024-03-17 14:31:01 +01:00
|
|
|
use crate::kdf::derive_key;
|
2024-03-11 14:55:08 +01:00
|
|
|
use crate::{storage, IkmId, InputKeyMaterialList};
|
2024-03-02 14:53:38 +01:00
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
2024-02-17 20:26:45 +01:00
|
|
|
|
2024-04-07 11:48:33 +02:00
|
|
|
pub struct Coffio<'a> {
|
2024-03-17 14:23:03 +01:00
|
|
|
ikm_list: &'a InputKeyMaterialList,
|
2024-02-25 13:40:19 +01:00
|
|
|
}
|
|
|
|
|
2024-04-07 11:48:33 +02:00
|
|
|
impl<'a> Coffio<'a> {
|
2024-03-17 14:23:03 +01:00
|
|
|
pub fn new(ikm_list: &'a InputKeyMaterialList) -> Self {
|
|
|
|
Self { ikm_list }
|
|
|
|
}
|
2024-02-17 20:26:45 +01:00
|
|
|
|
2024-03-17 14:23:03 +01:00
|
|
|
#[inline]
|
|
|
|
fn generate_aad(
|
|
|
|
ikm_id: IkmId,
|
|
|
|
nonce: &[u8],
|
|
|
|
key_context: &KeyContext,
|
|
|
|
data_context: &DataContext,
|
|
|
|
time_period: Option<u64>,
|
|
|
|
) -> 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(&[
|
|
|
|
ikm_id_canon,
|
|
|
|
nonce_canon,
|
|
|
|
key_context_canon,
|
|
|
|
data_context_canon,
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn encrypt(
|
|
|
|
&self,
|
|
|
|
key_context: &KeyContext,
|
|
|
|
data_context: &DataContext,
|
2024-03-24 09:25:27 +01:00
|
|
|
data: impl AsRef<[u8]>,
|
2024-04-07 12:22:04 +02:00
|
|
|
) -> Result<String> {
|
|
|
|
self.process_encrypt_at(key_context, data_context, data, SystemTime::now())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "encrypt-at")]
|
|
|
|
pub fn encrypt_at(
|
|
|
|
&self,
|
|
|
|
key_context: &KeyContext,
|
|
|
|
data_context: &DataContext,
|
|
|
|
data: impl AsRef<[u8]>,
|
|
|
|
encryption_time: SystemTime,
|
|
|
|
) -> Result<String> {
|
|
|
|
self.process_encrypt_at(key_context, data_context, data, encryption_time)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_encrypt_at(
|
|
|
|
&self,
|
|
|
|
key_context: &KeyContext,
|
|
|
|
data_context: &DataContext,
|
|
|
|
data: impl AsRef<[u8]>,
|
|
|
|
encryption_time: SystemTime,
|
2024-03-17 14:23:03 +01:00
|
|
|
) -> Result<String> {
|
|
|
|
let tp = if key_context.is_periodic() {
|
2024-04-07 12:22:04 +02:00
|
|
|
let ts = encryption_time.duration_since(UNIX_EPOCH)?.as_secs();
|
2024-03-17 14:23:03 +01:00
|
|
|
key_context.get_time_period(ts)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2024-04-07 12:22:04 +02:00
|
|
|
let ikm = self.ikm_list.get_latest_ikm(encryption_time)?;
|
2024-03-17 14:23:03 +01:00
|
|
|
let key = derive_key(ikm, key_context, tp);
|
|
|
|
let gen_nonce_function = ikm.scheme.get_gen_nonce();
|
|
|
|
let nonce = gen_nonce_function()?;
|
|
|
|
let aad = Self::generate_aad(ikm.id, &nonce, key_context, data_context, tp);
|
|
|
|
let encryption_function = ikm.scheme.get_encryption();
|
|
|
|
let encrypted_data = encryption_function(&key, &nonce, data.as_ref(), &aad)?;
|
|
|
|
Ok(storage::encode_cipher(ikm.id, &encrypted_data, tp))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn decrypt(
|
|
|
|
&self,
|
|
|
|
key_context: &KeyContext,
|
|
|
|
data_context: &DataContext,
|
2024-03-24 09:25:27 +01:00
|
|
|
stored_data: &str,
|
2024-03-17 14:23:03 +01:00
|
|
|
) -> Result<Vec<u8>> {
|
|
|
|
let (ikm_id, encrypted_data, tp) = storage::decode_cipher(stored_data)?;
|
|
|
|
let ikm = self.ikm_list.get_ikm_by_id(ikm_id)?;
|
|
|
|
let key = derive_key(ikm, key_context, tp);
|
|
|
|
let aad = Self::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)
|
|
|
|
}
|
2024-02-25 13:40:19 +01:00
|
|
|
}
|
|
|
|
|
2024-02-17 20:26:45 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2024-03-09 17:29:55 +01:00
|
|
|
use crate::{DataContext, KeyContext};
|
2024-02-17 20:26:45 +01:00
|
|
|
|
2024-03-11 14:55:08 +01:00
|
|
|
const TEST_CIPHERTEXT: &str = "AQAAAA:W-nzcGkPU6eWj_JjjqLpQk6WSe_CIUPF:we_HR8yD3XnQ9aaJlZFvqPitnDlQHexw4QPaYaOTzpHSWNW86QQrLRRZOg:NgAAAAAAAAA";
|
2024-02-17 20:26:45 +01:00
|
|
|
const TEST_DATA: &[u8] = b"Lorem ipsum dolor sit amet.";
|
2024-03-24 09:47:36 +01:00
|
|
|
const TEST_KEY_CTX: &[&str] = &["db_name", "table_name", "column_name"];
|
|
|
|
const TEST_DATA_CTX: &[&str] = &["018db876-3d9d-79af-9460-55d17da991d8"];
|
2024-02-17 20:26:45 +01:00
|
|
|
|
2024-03-09 12:40:28 +01:00
|
|
|
fn get_static_key_ctx() -> KeyContext {
|
|
|
|
let mut ctx: KeyContext = TEST_KEY_CTX.into();
|
|
|
|
ctx.set_static();
|
|
|
|
ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_static_empty_key_ctx() -> KeyContext {
|
|
|
|
let mut ctx = KeyContext::from([]);
|
|
|
|
ctx.set_static();
|
|
|
|
ctx
|
|
|
|
}
|
|
|
|
|
2024-03-24 12:16:54 +01:00
|
|
|
fn get_ikm_lst_chacha20poly1305_blake3() -> InputKeyMaterialList {
|
2024-02-17 20:26:45 +01:00
|
|
|
InputKeyMaterialList::import(
|
2024-03-02 11:00:59 +01:00
|
|
|
"AQAAAA:AQAAAAEAAAC_vYEw1ujVG5i-CtoPYSzik_6xaAq59odjPm5ij01-e6zz4mUAAAAALJGBiwAAAAAA",
|
2024-02-17 20:26:45 +01:00
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
2024-03-24 12:16:54 +01:00
|
|
|
fn get_ikm_lst_aes128gcm_sha256() -> InputKeyMaterialList {
|
|
|
|
InputKeyMaterialList::import(
|
|
|
|
"AQAAAA:AQAAAAIAAAA2lXqTSduZ22J0LiwEhmENjB6pLo0GVKvAQYocJcAAp1f8_2UAAAAAuzDPeAAAAAAA",
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
2024-02-17 20:26:45 +01:00
|
|
|
#[test]
|
2024-03-24 12:16:54 +01:00
|
|
|
fn encrypt_decrypt_no_context_chacha20poly1305_blake3() {
|
|
|
|
let lst = get_ikm_lst_chacha20poly1305_blake3();
|
2024-03-09 17:29:55 +01:00
|
|
|
let key_ctx = get_static_empty_key_ctx();
|
|
|
|
let data_ctx = DataContext::from([]);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-02 14:53:38 +01:00
|
|
|
|
2024-02-25 13:40:19 +01:00
|
|
|
// Encrypt
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.encrypt(&key_ctx, &data_ctx, TEST_DATA);
|
2024-03-02 11:05:56 +01:00
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
2024-02-17 20:26:45 +01:00
|
|
|
let ciphertext = res.unwrap();
|
|
|
|
assert!(ciphertext.starts_with("AQAAAA:"));
|
|
|
|
assert_eq!(ciphertext.len(), 98);
|
|
|
|
|
2024-02-25 13:40:19 +01:00
|
|
|
// Decrypt
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, &ciphertext);
|
2024-03-02 11:05:56 +01:00
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
2024-02-25 13:40:19 +01:00
|
|
|
let plaintext = res.unwrap();
|
|
|
|
assert_eq!(plaintext, TEST_DATA);
|
2024-02-17 20:26:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2024-03-24 12:16:54 +01:00
|
|
|
fn encrypt_decrypt_no_context_aes128gcm_sha256() {
|
|
|
|
let lst = get_ikm_lst_aes128gcm_sha256();
|
|
|
|
let key_ctx = get_static_empty_key_ctx();
|
|
|
|
let data_ctx = DataContext::from([]);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-24 12:16:54 +01:00
|
|
|
|
|
|
|
// Encrypt
|
|
|
|
let res = cb.encrypt(&key_ctx, &data_ctx, TEST_DATA);
|
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let ciphertext = res.unwrap();
|
|
|
|
assert!(ciphertext.starts_with("AQAAAA:"));
|
|
|
|
assert_eq!(ciphertext.len(), 82);
|
|
|
|
|
|
|
|
// Decrypt
|
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, &ciphertext);
|
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let plaintext = res.unwrap();
|
|
|
|
assert_eq!(plaintext, TEST_DATA);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn encrypt_decrypt_with_static_context_chacha20poly1305_blake3() {
|
|
|
|
let lst = get_ikm_lst_chacha20poly1305_blake3();
|
2024-03-09 12:40:28 +01:00
|
|
|
let key_ctx = get_static_key_ctx();
|
2024-03-09 17:29:55 +01:00
|
|
|
let data_ctx = DataContext::from(TEST_DATA_CTX);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-09 12:40:28 +01:00
|
|
|
|
|
|
|
// Encrypt
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.encrypt(&key_ctx, &data_ctx, TEST_DATA);
|
2024-03-02 11:05:56 +01:00
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
2024-02-17 20:26:45 +01:00
|
|
|
let ciphertext = res.unwrap();
|
|
|
|
assert!(ciphertext.starts_with("AQAAAA:"));
|
|
|
|
assert_eq!(ciphertext.len(), 98);
|
|
|
|
|
2024-02-25 13:40:19 +01:00
|
|
|
// Decrypt
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, &ciphertext);
|
2024-03-09 12:40:28 +01:00
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let plaintext = res.unwrap();
|
|
|
|
assert_eq!(plaintext, TEST_DATA);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2024-03-24 12:16:54 +01:00
|
|
|
fn encrypt_decrypt_with_static_context_aes128gcm_sha256() {
|
|
|
|
let lst = get_ikm_lst_aes128gcm_sha256();
|
|
|
|
let key_ctx = get_static_key_ctx();
|
|
|
|
let data_ctx = DataContext::from(TEST_DATA_CTX);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-24 12:16:54 +01:00
|
|
|
|
|
|
|
// Encrypt
|
|
|
|
let res = cb.encrypt(&key_ctx, &data_ctx, TEST_DATA);
|
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let ciphertext = res.unwrap();
|
|
|
|
assert!(ciphertext.starts_with("AQAAAA:"));
|
|
|
|
assert_eq!(ciphertext.len(), 82);
|
|
|
|
|
|
|
|
// Decrypt
|
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, &ciphertext);
|
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let plaintext = res.unwrap();
|
|
|
|
assert_eq!(plaintext, TEST_DATA);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn encrypt_decrypt_with_context_chacha20poly1305_blake3() {
|
|
|
|
let lst = get_ikm_lst_chacha20poly1305_blake3();
|
2024-03-09 12:40:28 +01:00
|
|
|
let key_ctx = KeyContext::from(TEST_KEY_CTX);
|
2024-03-09 17:29:55 +01:00
|
|
|
let data_ctx = DataContext::from(TEST_DATA_CTX);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-09 12:40:28 +01:00
|
|
|
|
|
|
|
// Encrypt
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.encrypt(&key_ctx, &data_ctx, TEST_DATA);
|
2024-03-09 12:40:28 +01:00
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let ciphertext = res.unwrap();
|
|
|
|
assert!(ciphertext.starts_with("AQAAAA:"));
|
|
|
|
assert_eq!(ciphertext.len(), 110);
|
|
|
|
|
|
|
|
// Decrypt
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, &ciphertext);
|
2024-03-02 11:05:56 +01:00
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
2024-02-25 13:40:19 +01:00
|
|
|
let plaintext = res.unwrap();
|
|
|
|
assert_eq!(plaintext, TEST_DATA);
|
2024-02-17 20:26:45 +01:00
|
|
|
}
|
2024-03-11 10:44:08 +01:00
|
|
|
|
2024-03-24 12:16:54 +01:00
|
|
|
#[test]
|
|
|
|
fn encrypt_decrypt_with_context_aes128gcm_sha256() {
|
|
|
|
let lst = get_ikm_lst_aes128gcm_sha256();
|
|
|
|
let key_ctx = KeyContext::from(TEST_KEY_CTX);
|
|
|
|
let data_ctx = DataContext::from(TEST_DATA_CTX);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-24 12:16:54 +01:00
|
|
|
|
|
|
|
// Encrypt
|
|
|
|
let res = cb.encrypt(&key_ctx, &data_ctx, TEST_DATA);
|
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let ciphertext = res.unwrap();
|
|
|
|
assert!(ciphertext.starts_with("AQAAAA:"));
|
|
|
|
assert_eq!(ciphertext.len(), 94);
|
|
|
|
|
|
|
|
// Decrypt
|
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, &ciphertext);
|
|
|
|
assert!(res.is_ok(), "res: {res:?}");
|
|
|
|
let plaintext = res.unwrap();
|
|
|
|
assert_eq!(plaintext, TEST_DATA);
|
|
|
|
}
|
|
|
|
|
2024-03-11 10:44:08 +01:00
|
|
|
#[test]
|
|
|
|
fn decrypt_invalid_ciphertext() {
|
|
|
|
let tests = &[
|
|
|
|
("", "empty data"),
|
2024-03-11 14:55:08 +01:00
|
|
|
("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"),
|
2024-03-11 10:44:08 +01:00
|
|
|
];
|
|
|
|
|
2024-03-24 12:16:54 +01:00
|
|
|
let lst = get_ikm_lst_chacha20poly1305_blake3();
|
2024-03-11 10:44:08 +01:00
|
|
|
let key_ctx = KeyContext::from(TEST_KEY_CTX);
|
|
|
|
let data_ctx = DataContext::from(TEST_DATA_CTX);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-11 10:44:08 +01:00
|
|
|
|
|
|
|
// Test if the reference ciphertext used for the tests is actually valid
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, TEST_CIPHERTEXT);
|
2024-03-11 10:44:08 +01:00
|
|
|
assert!(res.is_ok(), "invalid reference ciphertext");
|
|
|
|
|
|
|
|
// Test if altered versions of the reference ciphertext are refused
|
|
|
|
for (ciphertext, error_str) in tests {
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, ciphertext);
|
2024-03-11 10:44:08 +01:00
|
|
|
assert!(res.is_err(), "failed error detection: {error_str}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn invalid_context() {
|
2024-03-24 12:16:54 +01:00
|
|
|
let lst = get_ikm_lst_chacha20poly1305_blake3();
|
2024-03-11 10:44:08 +01:00
|
|
|
let key_ctx = KeyContext::from(TEST_KEY_CTX);
|
|
|
|
let data_ctx = DataContext::from(TEST_DATA_CTX);
|
2024-04-07 11:48:33 +02:00
|
|
|
let cb = Coffio::new(&lst);
|
2024-03-11 10:44:08 +01:00
|
|
|
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &data_ctx, TEST_CIPHERTEXT);
|
2024-03-11 10:44:08 +01:00
|
|
|
assert!(res.is_ok(), "invalid reference ciphertext");
|
|
|
|
|
|
|
|
let invalid_key_ctx = KeyContext::from(["invalid", "key", "context"]);
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&invalid_key_ctx, &data_ctx, TEST_CIPHERTEXT);
|
2024-03-11 10:44:08 +01:00
|
|
|
assert!(res.is_err(), "failed error detection: invalid key context");
|
|
|
|
|
|
|
|
let invalid_data_ctx = DataContext::from(["invalid", "data", "context"]);
|
2024-03-24 09:25:27 +01:00
|
|
|
let res = cb.decrypt(&key_ctx, &invalid_data_ctx, TEST_CIPHERTEXT);
|
2024-03-11 10:44:08 +01:00
|
|
|
assert!(res.is_err(), "failed error detection: invalid key context");
|
|
|
|
}
|
2024-02-17 20:26:45 +01:00
|
|
|
}
|