use aes_gcm::Aes256Gcm;
use aes_gcm::aead::{AeadInOut, KeyInit, Nonce};
use cratefield_kms::Dek;
use crate::SealedError;
const NONCE_LEN: usize = 12;
const TAG_LEN: usize = 16;
fn aad(subject: &str, blob_id: &str, version: u32) -> Vec<u8> {
format!("cratefield-sealed-outer-v1|{subject}|{blob_id}|{version}").into_bytes()
}
pub(crate) fn random(len: usize) -> Result<Vec<u8>, SealedError> {
let mut bytes = vec![0_u8; len];
getrandom::fill(&mut bytes)
.map_err(|err| SealedError::Internal(format!("the OS random source failed: {err}")))?;
Ok(bytes)
}
fn cipher(dek: &Dek) -> Result<Aes256Gcm, SealedError> {
Aes256Gcm::new_from_slice(dek.expose())
.map_err(|err| SealedError::Internal(format!("the outer cipher refused the key: {err}")))
}
fn nonce_of(bytes: &[u8]) -> Nonce<Aes256Gcm> {
let mut nonce = <Nonce<Aes256Gcm>>::default();
nonce.copy_from_slice(bytes);
nonce
}
pub(crate) fn seal(
dek: &Dek,
plaintext: &[u8],
subject: &str,
blob_id: &str,
version: u32,
) -> Result<Vec<u8>, SealedError> {
let cipher = cipher(dek)?;
let nonce = random(NONCE_LEN)?;
let mut body = plaintext.to_vec();
cipher
.encrypt_in_place(
&nonce_of(&nonce),
&aad(subject, blob_id, version),
&mut body,
)
.map_err(|err| SealedError::Internal(format!("the outer seal failed: {err}")))?;
let mut out = nonce;
out.extend_from_slice(&body);
Ok(out)
}
pub(crate) fn open(
dek: &Dek,
sealed: &[u8],
subject: &str,
blob_id: &str,
version: u32,
) -> Result<Vec<u8>, SealedError> {
if sealed.len() < NONCE_LEN + TAG_LEN {
return Err(SealedError::Tampered(
"an outer ciphertext is shorter than its own framing".into(),
));
}
let mut body = sealed[NONCE_LEN..].to_vec();
cipher(dek)?
.decrypt_in_place(
&nonce_of(&sealed[..NONCE_LEN]),
&aad(subject, blob_id, version),
&mut body,
)
.map_err(|_| {
SealedError::Tampered(format!(
"the outer wrap failed authentication for `{blob_id}` v{version}"
))
})?;
Ok(body)
}