use crate::errors::OpenIdError;
use crate::errors::Res;
use aes_gcm::aead::{Aead, Generate};
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use rand::RngExt;
use rkyv::api::high::{HighSerializer, HighValidator};
use rkyv::bytecheck::CheckBytes;
use rkyv::de::Pool;
use rkyv::rancor::Strategy;
use rkyv::ser::allocator::ArenaHandle;
use rkyv::util::AlignedVec;
use rkyv::{Archive, Deserialize, Serialize};
const NONCE_LEN: usize = 12;
pub struct CryptoWrapper {
key: Key<Aes256Gcm>,
}
impl CryptoWrapper {
pub fn new_random() -> Self {
Self {
key: Key::<Aes256Gcm>::generate_from_rng(&mut rand::rng()),
}
}
pub fn encrypt(
&self,
data: &impl for<'a> Serialize<HighSerializer<AlignedVec, ArenaHandle<'a>, rkyv::rancor::Error>>,
) -> Res<String> {
let aes_key = Aes256Gcm::new(&self.key);
let nonce_bytes = rand::rng().random::<[u8; NONCE_LEN]>();
let serialized_data = rkyv::to_bytes(data)?;
let mut enc = aes_key
.encrypt(&Nonce::from(nonce_bytes), serialized_data.as_slice())
.unwrap();
enc.extend_from_slice(&nonce_bytes);
Ok(BASE64_STANDARD.encode(enc))
}
pub fn decrypt<T>(&self, input: &str) -> Res<T>
where
T: Archive,
T::Archived: for<'a> CheckBytes<HighValidator<'a, rkyv::rancor::Error>>
+ Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
{
let bytes = BASE64_STANDARD.decode(input)?;
if bytes.len() < NONCE_LEN {
return Err(OpenIdError::DecInputStringSmallerThanNonce);
}
let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN);
assert_eq!(nonce.len(), NONCE_LEN);
let aes_key = Aes256Gcm::new(&self.key);
let dec = match aes_key.decrypt(&Nonce::try_from(nonce)?, enc) {
Ok(d) => d,
Err(e) => {
tracing::error!("Failed to decrypt wrapped data! {e:#?}");
return Err(OpenIdError::DecryptWrappedData);
}
};
Ok(rkyv::from_bytes(&dec)?)
}
}
#[cfg(test)]
mod test {
use crate::crypto_wrapper::CryptoWrapper;
#[derive(Eq, PartialEq, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
struct Message(String);
#[test]
fn encrypt_and_decrypt() {
let wrapper = CryptoWrapper::new_random();
let msg = Message("Pierre was here".to_string());
let enc = wrapper.encrypt(&msg).unwrap();
let dec: Message = wrapper.decrypt(&enc).unwrap();
assert_eq!(dec, msg)
}
#[test]
fn encrypt_and_decrypt_invalid() {
let wrapper_1 = CryptoWrapper::new_random();
let wrapper_2 = CryptoWrapper::new_random();
let msg = Message("Pierre was here".to_string());
let enc = wrapper_1.encrypt(&msg).unwrap();
wrapper_2.decrypt::<Message>(&enc).unwrap_err();
}
}