From b99746bd65745a9008d9c85ea3ffb6e1b151b1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Thu, 15 Feb 2024 10:56:21 +0100 Subject: [PATCH] Improve the error management --- Cargo.toml | 1 + src/error.rs | 35 +++++++++++++++++++++++ src/ikm.rs | 78 +++++++++++++++++++++++++++------------------------ src/lib.rs | 2 ++ src/scheme.rs | 6 ++-- 5 files changed, 84 insertions(+), 38 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.toml b/Cargo.toml index 82d7a67..310a9cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ hazardous-materials = [] [dependencies] base64ct = { version = "1.6.0", default-features = false, features = ["std"] } getrandom = { version = "0.2.12", default-features = false } +thiserror = { version = "1.0.57", default-features = false } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a4fd54f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,35 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("parsing error: invalid base64-urlsafe-nopadding data: {0}")] + ParsingBase64Error(base64ct::Error), + #[error("parsing error: invalid data length: {0} bytes")] + ParsingInvalidLength(usize), + #[error("parsing error: {0}: unknown scheme")] + ParsingUnknownScheme(u32), + #[error("unable to generate random values: {0}")] + RandomSourceError(getrandom::Error), + #[error("system time error: {0}")] + SystemTimeError(std::time::SystemTimeError), + #[error("system time error: {0}: unable to represent this timestamp as a system time")] + SystemTimeReprError(u64), +} + +impl From for Error { + fn from(error: base64ct::Error) -> Self { + Error::ParsingBase64Error(error) + } +} + +impl From for Error { + fn from(error: getrandom::Error) -> Self { + Error::RandomSourceError(error) + } +} + +impl From for Error { + fn from(error: std::time::SystemTimeError) -> Self { + Error::SystemTimeError(error) + } +} diff --git a/src/ikm.rs b/src/ikm.rs index 835f3a8..dfbfdc4 100644 --- a/src/ikm.rs +++ b/src/ikm.rs @@ -1,4 +1,4 @@ -use crate::Scheme; +use crate::{Error, Scheme}; use base64ct::{Base64UrlUnpadded, Encoding}; use std::time::{Duration, SystemTime}; @@ -16,7 +16,7 @@ pub struct InputKeyMaterial { } impl InputKeyMaterial { - fn as_bytes(&self) -> [u8; IKM_STRUCT_SIZE] { + fn as_bytes(&self) -> Result<[u8; IKM_STRUCT_SIZE], Error> { let mut res = Vec::with_capacity(IKM_STRUCT_SIZE); res.extend_from_slice(&self.id.to_le_bytes()); res.extend_from_slice(&(self.scheme as u32).to_le_bytes()); @@ -24,42 +24,38 @@ impl InputKeyMaterial { res.extend_from_slice( &self .created_at - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() + .duration_since(SystemTime::UNIX_EPOCH)? .as_secs() .to_le_bytes(), ); res.extend_from_slice( &self .expire_at - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() + .duration_since(SystemTime::UNIX_EPOCH)? .as_secs() .to_le_bytes(), ); res.push(self.is_revoked as u8); - res.try_into().unwrap() + Ok(res.try_into().unwrap()) } - fn from_bytes(b: [u8; IKM_STRUCT_SIZE]) -> Self { - Self { + fn from_bytes(b: [u8; IKM_STRUCT_SIZE]) -> Result { + Ok(Self { id: u32::from_le_bytes(b[0..4].try_into().unwrap()), - scheme: u32::from_le_bytes(b[4..8].try_into().unwrap()) - .try_into() - .unwrap(), + scheme: u32::from_le_bytes(b[4..8].try_into().unwrap()).try_into()?, content: b[8..40].try_into().unwrap(), - created_at: SystemTime::UNIX_EPOCH - .checked_add(Duration::from_secs(u64::from_le_bytes( - b[40..48].try_into().unwrap(), - ))) - .unwrap(), - expire_at: SystemTime::UNIX_EPOCH - .checked_add(Duration::from_secs(u64::from_le_bytes( - b[48..56].try_into().unwrap(), - ))) - .unwrap(), + created_at: InputKeyMaterial::bytes_to_system_time(&b[40..48])?, + expire_at: InputKeyMaterial::bytes_to_system_time(&b[48..56])?, is_revoked: b[56] != 0, - } + }) + } + + fn bytes_to_system_time(ts_slice: &[u8]) -> Result { + let ts_array: [u8; 8] = ts_slice.try_into().unwrap(); + let ts = u64::from_le_bytes(ts_array); + Ok(SystemTime::UNIX_EPOCH + .checked_add(Duration::from_secs(ts)) + .ok_or(Error::SystemTimeReprError(ts))?) } } @@ -74,11 +70,11 @@ impl InputKeyMaterialList { Self::default() } - pub fn add_ikm(&mut self) -> Result<(), getrandom::Error> { + pub fn add_ikm(&mut self) -> Result<(), Error> { self.add_ikm_with_duration(Duration::from_secs(crate::DEFAULT_IKM_DURATION)) } - pub fn add_ikm_with_duration(&mut self, duration: Duration) -> Result<(), getrandom::Error> { + pub fn add_ikm_with_duration(&mut self, duration: Duration) -> Result<(), Error> { let mut content: [u8; 32] = [0; 32]; getrandom::getrandom(&mut content)?; let created_at = SystemTime::now(); @@ -94,24 +90,24 @@ impl InputKeyMaterialList { Ok(()) } - pub fn export(&self) -> String { + pub fn export(&self) -> Result { 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 { - data.extend_from_slice(&ikm.as_bytes()); + data.extend_from_slice(&ikm.as_bytes()?); } - Base64UrlUnpadded::encode_string(&data) + Ok(Base64UrlUnpadded::encode_string(&data)) } - pub fn import(s: &str) -> Result { - let data = Base64UrlUnpadded::decode_vec(s).unwrap(); + pub fn import(s: &str) -> Result { + let data = Base64UrlUnpadded::decode_vec(s)?; if data.len() % IKM_STRUCT_SIZE != 4 { - return Err("Invalid string".to_string()); + return Err(Error::ParsingInvalidLength(data.len())); } let mut ikm_lst = Vec::with_capacity(data.len() / IKM_STRUCT_SIZE); for ikm_slice in data[4..].chunks_exact(IKM_STRUCT_SIZE) { - ikm_lst.push(InputKeyMaterial::from_bytes(ikm_slice.try_into().unwrap())); + ikm_lst.push(InputKeyMaterial::from_bytes(ikm_slice.try_into().unwrap())?); } Ok(Self { ikm_lst, @@ -163,7 +159,9 @@ mod tests { assert_eq!(lst.id_counter, 0); assert_eq!(lst.ikm_lst.len(), 0); - let s = lst.export(); + let res = lst.export(); + assert!(res.is_ok()); + let s = res.unwrap(); assert_eq!(&s, "AAAAAA"); } @@ -172,7 +170,9 @@ mod tests { let mut lst = InputKeyMaterialList::new(); let _ = lst.add_ikm(); - let s = lst.export(); + let res = lst.export(); + assert!(res.is_ok()); + let s = res.unwrap(); assert_eq!(s.len(), 82); } @@ -202,7 +202,10 @@ mod tests { fn export_import_empty() { let lst = InputKeyMaterialList::new(); - let s = lst.export(); + let res = lst.export(); + assert!(res.is_ok()); + let s = res.unwrap(); + let res = InputKeyMaterialList::import(&s); assert!(res.is_ok()); let lst_bis = res.unwrap(); @@ -219,7 +222,10 @@ mod tests { let _ = lst.add_ikm(); } - let s = lst.export(); + let res = lst.export(); + assert!(res.is_ok()); + let s = res.unwrap(); + let res = InputKeyMaterialList::import(&s); assert!(res.is_ok()); let lst_bis = res.unwrap(); diff --git a/src/lib.rs b/src/lib.rs index dbc3de2..4d07c03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ +mod error; mod ikm; mod scheme; +pub use error::Error; pub use ikm::InputKeyMaterialList; pub use scheme::Scheme; diff --git a/src/scheme.rs b/src/scheme.rs index 3f9f9cd..27a079f 100644 --- a/src/scheme.rs +++ b/src/scheme.rs @@ -1,15 +1,17 @@ +use crate::Error; + #[derive(Copy, Clone, Debug, PartialEq)] pub enum Scheme { XChaCha20Poly1305WithBlake3 = 1, } impl TryFrom for Scheme { - type Error = &'static str; + type Error = Error; fn try_from(value: u32) -> Result { match value { 1 => Ok(Scheme::XChaCha20Poly1305WithBlake3), - _ => Err("unknown scheme"), + _ => Err(Error::ParsingUnknownScheme(value)), } } }