coffio/src/ikm.rs

324 lines
8.3 KiB
Rust
Raw Normal View History

2024-02-17 20:47:07 +01:00
use crate::error::{Error, Result};
use crate::Scheme;
2024-02-14 23:11:00 +01:00
use base64ct::{Base64UrlUnpadded, Encoding};
use std::time::{Duration, SystemTime};
2024-02-15 10:00:06 +01:00
const IKM_STRUCT_SIZE: usize = 57;
2024-02-14 23:11:00 +01:00
const IKM_CONTENT_SIZE: usize = 32;
#[derive(Debug)]
2024-02-15 18:21:43 +01:00
pub(crate) struct InputKeyMaterial {
pub(crate) id: u32,
pub(crate) scheme: Scheme,
pub(crate) content: [u8; IKM_CONTENT_SIZE],
pub(crate) created_at: SystemTime,
pub(crate) expire_at: SystemTime,
pub(crate) is_revoked: bool,
2024-02-14 23:11:00 +01:00
}
impl InputKeyMaterial {
#[cfg(feature = "ikm-management")]
2024-02-17 20:47:07 +01:00
fn as_bytes(&self) -> Result<[u8; IKM_STRUCT_SIZE]> {
2024-02-14 23:11:00 +01:00
let mut res = Vec::with_capacity(IKM_STRUCT_SIZE);
res.extend_from_slice(&self.id.to_le_bytes());
2024-02-15 10:00:06 +01:00
res.extend_from_slice(&(self.scheme as u32).to_le_bytes());
2024-02-14 23:11:00 +01:00
res.extend_from_slice(&self.content);
res.extend_from_slice(
&self
.created_at
2024-02-15 10:56:21 +01:00
.duration_since(SystemTime::UNIX_EPOCH)?
2024-02-14 23:11:00 +01:00
.as_secs()
.to_le_bytes(),
);
res.extend_from_slice(
&self
.expire_at
2024-02-15 10:56:21 +01:00
.duration_since(SystemTime::UNIX_EPOCH)?
2024-02-14 23:11:00 +01:00
.as_secs()
.to_le_bytes(),
);
res.push(self.is_revoked as u8);
2024-02-15 10:56:21 +01:00
Ok(res.try_into().unwrap())
2024-02-14 23:11:00 +01:00
}
2024-02-17 20:47:07 +01:00
pub(crate) fn from_bytes(b: [u8; IKM_STRUCT_SIZE]) -> Result<Self> {
2024-02-15 10:56:21 +01:00
Ok(Self {
2024-02-14 23:11:00 +01:00
id: u32::from_le_bytes(b[0..4].try_into().unwrap()),
2024-02-15 10:56:21 +01:00
scheme: u32::from_le_bytes(b[4..8].try_into().unwrap()).try_into()?,
2024-02-15 10:00:06 +01:00
content: b[8..40].try_into().unwrap(),
2024-02-15 10:56:21 +01:00
created_at: InputKeyMaterial::bytes_to_system_time(&b[40..48])?,
expire_at: InputKeyMaterial::bytes_to_system_time(&b[48..56])?,
2024-02-15 10:00:06 +01:00
is_revoked: b[56] != 0,
2024-02-15 10:56:21 +01:00
})
}
2024-02-17 20:47:07 +01:00
fn bytes_to_system_time(ts_slice: &[u8]) -> Result<SystemTime> {
2024-02-15 10:56:21 +01:00
let ts_array: [u8; 8] = ts_slice.try_into().unwrap();
let ts = u64::from_le_bytes(ts_array);
2024-02-15 11:48:13 +01:00
SystemTime::UNIX_EPOCH
2024-02-15 10:56:21 +01:00
.checked_add(Duration::from_secs(ts))
2024-02-15 11:48:13 +01:00
.ok_or(Error::SystemTimeReprError(ts))
2024-02-14 23:11:00 +01:00
}
}
#[derive(Debug, Default)]
pub struct InputKeyMaterialList {
ikm_lst: Vec<InputKeyMaterial>,
id_counter: u32,
}
impl InputKeyMaterialList {
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "ikm-management")]
2024-02-17 20:47:07 +01:00
pub fn add_ikm(&mut self) -> Result<()> {
2024-02-14 23:11:00 +01:00
self.add_ikm_with_duration(Duration::from_secs(crate::DEFAULT_IKM_DURATION))
}
#[cfg(feature = "ikm-management")]
2024-02-17 20:47:07 +01:00
pub fn add_ikm_with_duration(&mut self, duration: Duration) -> Result<()> {
2024-02-14 23:11:00 +01:00
let mut content: [u8; 32] = [0; 32];
getrandom::getrandom(&mut content)?;
let created_at = SystemTime::now();
self.id_counter += 1;
self.ikm_lst.push(InputKeyMaterial {
id: self.id_counter,
2024-02-15 10:00:06 +01:00
scheme: crate::DEFAULT_SCHEME,
2024-02-14 23:11:00 +01:00
created_at,
expire_at: created_at + duration,
is_revoked: false,
content,
});
Ok(())
}
#[cfg(feature = "ikm-management")]
2024-02-17 20:47:07 +01:00
pub fn export(&self) -> Result<String> {
2024-02-14 23:11:00 +01:00
let data_size = (self.ikm_lst.len() * IKM_STRUCT_SIZE) + 4;
let mut data = Vec::with_capacity(data_size);
data.extend_from_slice(&self.id_counter.to_le_bytes());
for ikm in &self.ikm_lst {
2024-02-15 10:56:21 +01:00
data.extend_from_slice(&ikm.as_bytes()?);
2024-02-14 23:11:00 +01:00
}
2024-02-15 10:56:21 +01:00
Ok(Base64UrlUnpadded::encode_string(&data))
2024-02-14 23:11:00 +01:00
}
2024-02-17 20:47:07 +01:00
pub fn import(s: &str) -> Result<Self> {
2024-02-15 10:56:21 +01:00
let data = Base64UrlUnpadded::decode_vec(s)?;
2024-02-14 23:11:00 +01:00
if data.len() % IKM_STRUCT_SIZE != 4 {
2024-02-15 10:56:21 +01:00
return Err(Error::ParsingInvalidLength(data.len()));
2024-02-14 23:11:00 +01:00
}
let mut ikm_lst = Vec::with_capacity(data.len() / IKM_STRUCT_SIZE);
for ikm_slice in data[4..].chunks_exact(IKM_STRUCT_SIZE) {
2024-02-15 10:56:21 +01:00
ikm_lst.push(InputKeyMaterial::from_bytes(ikm_slice.try_into().unwrap())?);
2024-02-14 23:11:00 +01:00
}
Ok(Self {
ikm_lst,
id_counter: u32::from_le_bytes(data[0..4].try_into().unwrap()),
})
}
#[cfg(feature = "encryption")]
2024-02-17 20:47:07 +01:00
pub(crate) fn get_latest_ikm(&self) -> Result<&InputKeyMaterial> {
self.ikm_lst
.iter()
.rev()
.find(|&ikm| !ikm.is_revoked && ikm.created_at < SystemTime::now())
.ok_or(Error::IkmNoneAvailable)
}
#[cfg(feature = "encryption")]
2024-02-17 20:47:07 +01:00
pub(crate) fn get_ikm_by_id(&self, id: u32) -> Result<&InputKeyMaterial> {
self.ikm_lst
.iter()
.find(|&ikm| ikm.id == id)
.ok_or(Error::IkmNotFound(id))
}
2024-02-14 23:11:00 +01:00
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
fn round_time(t: SystemTime) -> SystemTime {
let secs = t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(secs))
.unwrap()
}
#[test]
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
fn gen_ikm_list() {
let mut lst = InputKeyMaterialList::new();
assert_eq!(lst.id_counter, 0);
assert_eq!(lst.ikm_lst.len(), 0);
let res = lst.add_ikm();
assert!(res.is_ok());
assert_eq!(lst.id_counter, 1);
assert_eq!(lst.ikm_lst.len(), 1);
assert!(lst.ikm_lst.first().is_some());
let el = lst.ikm_lst.first().unwrap();
assert_eq!(el.id, 1);
assert_eq!(el.is_revoked, false);
let res = lst.add_ikm();
assert!(res.is_ok());
assert_eq!(lst.id_counter, 2);
assert_eq!(lst.ikm_lst.len(), 2);
let res = lst.add_ikm();
assert!(res.is_ok());
assert_eq!(lst.id_counter, 3);
assert_eq!(lst.ikm_lst.len(), 3);
}
#[test]
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
fn export_empty() {
let lst = InputKeyMaterialList::new();
assert_eq!(lst.id_counter, 0);
assert_eq!(lst.ikm_lst.len(), 0);
2024-02-15 10:56:21 +01:00
let res = lst.export();
assert!(res.is_ok());
let s = res.unwrap();
2024-02-14 23:11:00 +01:00
assert_eq!(&s, "AAAAAA");
}
#[test]
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
fn export() {
let mut lst = InputKeyMaterialList::new();
let _ = lst.add_ikm();
2024-02-15 10:56:21 +01:00
let res = lst.export();
assert!(res.is_ok());
let s = res.unwrap();
2024-02-15 10:00:06 +01:00
assert_eq!(s.len(), 82);
2024-02-14 23:11:00 +01:00
}
#[test]
fn import() {
2024-02-15 10:00:06 +01:00
let s =
"AQAAAAEAAAABAAAANGFtbdYEN0s7dzCfMm7dYeQWD64GdmuKsYSiKwppAhmkz81lAAAAACQDr2cAAAAAAA";
2024-02-14 23:11:00 +01:00
let res = InputKeyMaterialList::import(s);
assert!(res.is_ok());
let lst = res.unwrap();
assert_eq!(lst.id_counter, 1);
assert_eq!(lst.ikm_lst.len(), 1);
let ikm = lst.ikm_lst.first().unwrap();
assert_eq!(ikm.id, 1);
2024-02-15 10:00:06 +01:00
assert_eq!(ikm.scheme, Scheme::XChaCha20Poly1305WithBlake3);
2024-02-14 23:11:00 +01:00
assert_eq!(
ikm.content,
[
2024-02-15 10:00:06 +01:00
52, 97, 109, 109, 214, 4, 55, 75, 59, 119, 48, 159, 50, 110, 221, 97, 228, 22, 15,
174, 6, 118, 107, 138, 177, 132, 162, 43, 10, 105, 2, 25
2024-02-14 23:11:00 +01:00
]
);
assert_eq!(ikm.is_revoked, false);
}
#[test]
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
fn export_import_empty() {
let lst = InputKeyMaterialList::new();
2024-02-15 10:56:21 +01:00
let res = lst.export();
assert!(res.is_ok());
let s = res.unwrap();
2024-02-14 23:11:00 +01:00
let res = InputKeyMaterialList::import(&s);
assert!(res.is_ok());
let lst_bis = res.unwrap();
assert_eq!(lst_bis.id_counter, lst.id_counter);
assert_eq!(lst_bis.id_counter, 0);
assert_eq!(lst_bis.ikm_lst.len(), lst.ikm_lst.len());
assert_eq!(lst_bis.ikm_lst.len(), 0);
}
#[test]
#[cfg(feature = "ikm-management")]
2024-02-14 23:11:00 +01:00
fn export_import() {
let mut lst = InputKeyMaterialList::new();
for _ in 0..10 {
let _ = lst.add_ikm();
}
2024-02-15 10:56:21 +01:00
let res = lst.export();
assert!(res.is_ok());
let s = res.unwrap();
2024-02-14 23:11:00 +01:00
let res = InputKeyMaterialList::import(&s);
assert!(res.is_ok());
let lst_bis = res.unwrap();
assert_eq!(lst_bis.id_counter, lst.id_counter);
assert_eq!(lst_bis.id_counter, 10);
assert_eq!(lst_bis.ikm_lst.len(), lst.ikm_lst.len());
assert_eq!(lst_bis.ikm_lst.len(), 10);
for i in 0..10 {
let el = &lst.ikm_lst[i];
let el_bis = &lst_bis.ikm_lst[i];
assert_eq!(el_bis.id, el.id);
assert_eq!(el_bis.content, el.content);
assert_eq!(el_bis.created_at, round_time(el.created_at));
assert_eq!(el_bis.expire_at, round_time(el.expire_at));
assert_eq!(el_bis.is_revoked, el.is_revoked);
}
}
#[test]
#[cfg(feature = "encryption")]
fn get_latest_ikm() {
let mut lst = InputKeyMaterialList::new();
let _ = lst.add_ikm();
let _ = lst.add_ikm();
let _ = lst.add_ikm();
let res = lst.get_latest_ikm();
assert!(res.is_ok());
let latest_ikm = res.unwrap();
assert_eq!(latest_ikm.id, 3);
}
#[test]
#[cfg(feature = "encryption")]
fn get_latest_ikm_empty() {
2024-02-17 16:49:50 +01:00
let lst = InputKeyMaterialList::new();
let res = lst.get_latest_ikm();
assert!(res.is_err());
}
#[test]
#[cfg(feature = "encryption")]
fn get_ikm_by_id() {
let mut lst = InputKeyMaterialList::new();
let _ = lst.add_ikm();
let _ = lst.add_ikm();
let _ = lst.add_ikm();
for i in 1..=3 {
let res = lst.get_ikm_by_id(i);
assert!(res.is_ok());
let latest_ikm = res.unwrap();
assert_eq!(latest_ikm.id, i);
}
}
#[test]
#[cfg(feature = "encryption")]
fn get_ikm_by_id_noexists() {
let mut lst = InputKeyMaterialList::new();
let _ = lst.add_ikm();
let _ = lst.add_ikm();
let _ = lst.add_ikm();
let res = lst.get_ikm_by_id(42);
assert!(res.is_err());
}
2024-02-14 23:11:00 +01:00
}