use chacha20poly1305::XChaCha20Poly1305;
use chacha20poly1305::aead::{Aead, AeadCore, KeyInit, OsRng};
use super::{DataKey, KeyError, KeyRing, WrappedKey};
const NONCE: usize = 24;
fn cipher(key: &DataKey) -> XChaCha20Poly1305 {
XChaCha20Poly1305::new(key.expose().into())
}
pub(super) async fn seal(
keys: &dyn KeyRing,
scope: &str,
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, KeyError> {
let (key, wrapped) = keys.data_key(scope).await?;
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
let sealed = cipher(&key)
.encrypt(
&nonce,
chacha20poly1305::aead::Payload {
msg: plaintext,
aad,
},
)
.map_err(|e| KeyError::Refused(format!("sealing a payload failed: {e}")))?;
let wrapped_bytes = crate::core::canon::to_bytes(&wrapped)
.map_err(|e| KeyError::Refused(format!("a wrapped key would not serialise: {e}")))?;
let len = u32::try_from(wrapped_bytes.len())
.map_err(|_| KeyError::Refused("the wrapped key is implausibly large".to_owned()))?;
let mut envelope = Vec::with_capacity(4 + wrapped_bytes.len() + NONCE + sealed.len());
envelope.extend_from_slice(&len.to_be_bytes());
envelope.extend_from_slice(&wrapped_bytes);
envelope.extend_from_slice(&nonce);
envelope.extend_from_slice(&sealed);
Ok(envelope)
}
pub(super) async fn open(
keys: &dyn KeyRing,
aad: &[u8],
envelope: &[u8],
) -> Result<Vec<u8>, KeyError> {
if envelope.len() < 4 {
return Err(KeyError::Refused("the envelope has no header".to_owned()));
}
let (len_bytes, rest) = envelope.split_at(4);
let len = u32::from_be_bytes(len_bytes.try_into().unwrap_or([0; 4])) as usize;
let needed = len
.checked_add(NONCE)
.filter(|needed| rest.len() >= *needed);
if needed.is_none() {
return Err(KeyError::Refused(
"the envelope is shorter than its own header claims".to_owned(),
));
}
let (wrapped_bytes, rest) = rest.split_at(len);
let wrapped: WrappedKey = serde_json::from_slice(wrapped_bytes)
.map_err(|e| KeyError::Refused(format!("the wrapped key would not parse: {e}")))?;
let (nonce, sealed) = rest.split_at(NONCE);
let key = keys.open(&wrapped).await?;
cipher(&key)
.decrypt(
nonce.into(),
chacha20poly1305::aead::Payload { msg: sealed, aad },
)
.map_err(|_| KeyError::Refused("the sealed payload did not authenticate".to_owned()))
}