use cbor2::Cbor;
use crate::{
header::{decode_protected, encode_protected, validate_header_buckets},
iana,
recipient::validate_recipient_list,
tag, util, EncryptionContext, Encryptor, Error, Header, Label, Recipient,
};
#[derive(Clone, Debug, PartialEq, Cbor)]
#[cbor(tag = 96, array)]
struct EncryptWire {
#[serde(with = "crate::strict::bytes")]
protected: Vec<u8>,
unprotected: Header,
#[serde(with = "crate::strict::optional_bytes")]
ciphertext: Option<Vec<u8>>,
recipients: Vec<Recipient>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EncryptMessage {
pub protected: Header,
pub unprotected: Header,
pub payload: Option<Vec<u8>>,
pub recipients: Vec<Recipient>,
ciphertext: Vec<u8>,
ciphertext_detached: bool,
protected_raw: Vec<u8>,
state: util::OperationState,
}
impl EncryptMessage {
pub fn new(payload: Option<Vec<u8>>) -> Self {
EncryptMessage {
payload,
..Default::default()
}
}
pub fn to_be_encrypted(protected_raw: &[u8], external_aad: &[u8]) -> Result<Vec<u8>, Error> {
util::encode_structure(&(
"Encrypt",
serde_bytes::Bytes::new(protected_raw),
serde_bytes::Bytes::new(external_aad),
))
}
pub fn prepare_encryption(
&mut self,
alg: Option<Label>,
kid: Option<&[u8]>,
nonce_size: usize,
base_iv: Option<&[u8]>,
external_aad: Option<&[u8]>,
) -> Result<EncryptionContext, Error> {
if self.recipients.is_empty() {
return Err(Error::Custom("EncryptMessage has no recipients".into()));
}
validate_recipient_list(&self.recipients)?;
util::ensure_protected_alg(&mut self.protected, &mut self.unprotected, alg)?;
util::ensure_unprotected_kid(&self.protected, &mut self.unprotected, kid)?;
validate_header_buckets(&self.protected, &self.unprotected)?;
util::require_plaintext(&self.payload, "EncryptMessage::prepare_encryption")?;
let nonce = util::nonce_from_header_values(
&self.protected,
&self.unprotected,
nonce_size,
base_iv,
)?;
let protected_raw = encode_protected(&self.protected)?;
let aad = Self::to_be_encrypted(&protected_raw, external_aad.unwrap_or(&[]))?;
self.protected_raw = protected_raw;
self.state = util::OperationState::Prepared;
self.ciphertext.clear();
self.ciphertext_detached = false;
Ok(EncryptionContext { nonce, aad })
}
pub fn prepare_decryption(
&self,
alg: Option<Label>,
nonce_size: usize,
base_iv: Option<&[u8]>,
external_aad: Option<&[u8]>,
) -> Result<EncryptionContext, Error> {
self.prepare_decryption_with_crit(alg, nonce_size, base_iv, external_aad, &[])
}
pub fn prepare_decryption_with_crit(
&self,
alg: Option<Label>,
nonce_size: usize,
base_iv: Option<&[u8]>,
external_aad: Option<&[u8]>,
understood_critical_headers: &[Label],
) -> Result<EncryptionContext, Error> {
if !self.state.complete() {
return Err(Error::InvalidState(
"EncryptMessage must be decoded before decrypting".into(),
));
}
crate::header::validate_protected_state(&self.protected, &self.protected_raw)?;
self.protected
.ensure_crit_understood(understood_critical_headers)?;
util::check_protected_alg(&self.protected, &self.unprotected, alg)?;
let nonce = util::nonce_from_header_values(
&self.protected,
&self.unprotected,
nonce_size,
base_iv,
)?;
let aad = Self::to_be_encrypted(&self.protected_raw, external_aad.unwrap_or(&[]))?;
Ok(EncryptionContext { nonce, aad })
}
pub fn set_ciphertext(
&mut self,
ciphertext: impl Into<Vec<u8>>,
detached: bool,
) -> Result<(), Error> {
if self.recipients.is_empty() {
return Err(Error::Custom("EncryptMessage has no recipients".into()));
}
validate_recipient_list(&self.recipients)?;
validate_header_buckets(&self.protected, &self.unprotected)?;
if !self.state.initialized() {
self.protected_raw = encode_protected(&self.protected)?;
}
crate::header::validate_protected_state(&self.protected, &self.protected_raw)?;
self.ciphertext = ciphertext.into();
self.ciphertext_detached = detached;
self.state = util::OperationState::Complete;
Ok(())
}
pub fn encrypt(
&mut self,
encryptor: &dyn Encryptor,
external_aad: Option<&[u8]>,
) -> Result<(), Error> {
let context = self.prepare_encryption(
encryptor.alg(),
encryptor.kid(),
encryptor.nonce_size(),
encryptor.base_iv(),
external_aad,
)?;
let plaintext = util::require_plaintext(&self.payload, "EncryptMessage::encrypt")?;
let ciphertext = encryptor.encrypt(&context.nonce, plaintext, &context.aad)?;
self.set_ciphertext(ciphertext, false)
}
pub fn encrypt_detached(
&mut self,
encryptor: &dyn Encryptor,
external_aad: Option<&[u8]>,
) -> Result<&[u8], Error> {
self.encrypt(encryptor, external_aad)?;
self.ciphertext_detached = true;
Ok(&self.ciphertext)
}
pub fn encrypt_and_encode(
&mut self,
encryptor: &dyn Encryptor,
external_aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
self.encrypt(encryptor, external_aad)?;
self.to_vec()
}
pub fn encrypt_detached_and_encode(
&mut self,
encryptor: &dyn Encryptor,
external_aad: Option<&[u8]>,
) -> Result<(Vec<u8>, Vec<u8>), Error> {
self.encrypt_detached(encryptor, external_aad)?;
let encoded = self.to_vec()?;
let ciphertext = std::mem::take(&mut self.ciphertext);
Ok((encoded, ciphertext))
}
pub fn to_vec(&self) -> Result<Vec<u8>, Error> {
self.encode(tag::ENCRYPT_PREFIX)
}
pub fn to_cwt_vec(&self) -> Result<Vec<u8>, Error> {
self.encode(tag::CWT_ENCRYPT_PREFIX)
}
pub fn to_untagged_vec(&self) -> Result<Vec<u8>, Error> {
self.encode(&[])
}
fn encode(&self, prefix: &[u8]) -> Result<Vec<u8>, Error> {
if !self.state.complete() {
return Err(Error::InvalidState(
"EncryptMessage must be encrypted before encoding".into(),
));
}
if self.recipients.is_empty() {
return Err(Error::Custom("EncryptMessage has no recipients".into()));
}
validate_recipient_list(&self.recipients)?;
validate_header_buckets(&self.protected, &self.unprotected)?;
crate::header::validate_protected_state(&self.protected, &self.protected_raw)?;
let ciphertext = if self.ciphertext_detached {
None
} else {
Some(serde_bytes::Bytes::new(&self.ciphertext))
};
let unprotected = util::canonical_raw(&self.unprotected)?;
let recipients = util::canonical_raw(&self.recipients)?;
util::encode_prefixed(
prefix,
&(
serde_bytes::Bytes::new(&self.protected_raw),
&unprotected,
ciphertext,
&recipients,
),
)
}
pub fn from_slice(data: &[u8]) -> Result<Self, Error> {
let body = tag::message_body(data, Self::TAG)?;
let wire: EncryptWire = cbor2::from_slice(body)?;
if wire.recipients.is_empty() {
return Err(Error::Custom("EncryptMessage has no recipients".into()));
}
validate_recipient_list(&wire.recipients)?;
let protected = decode_protected(&wire.protected)?;
validate_header_buckets(&protected, &wire.unprotected)?;
let (ciphertext, ciphertext_detached) = match wire.ciphertext {
Some(ciphertext) => (ciphertext, false),
None => (Vec::new(), true),
};
Ok(EncryptMessage {
protected,
unprotected: wire.unprotected,
payload: None,
recipients: wire.recipients,
ciphertext,
ciphertext_detached,
protected_raw: wire.protected,
state: util::OperationState::Complete,
})
}
pub fn decrypt(
&mut self,
encryptor: &dyn Encryptor,
external_aad: Option<&[u8]>,
) -> Result<&[u8], Error> {
if !self.state.complete() {
return Err(Error::InvalidState(
"EncryptMessage must be decoded before decrypting".into(),
));
}
if self.ciphertext_detached {
return Err(Error::Custom(
"EncryptMessage has detached ciphertext; use decrypt_detached".into(),
));
}
let context = self.prepare_decryption_with_crit(
encryptor.alg(),
encryptor.nonce_size(),
encryptor.base_iv(),
external_aad,
encryptor.understood_critical_headers(),
)?;
let plaintext = encryptor.decrypt(&context.nonce, &self.ciphertext, &context.aad)?;
self.payload = Some(plaintext);
Ok(self.payload.as_deref().expect("payload was just set"))
}
pub fn decrypt_detached(
&mut self,
encryptor: &dyn Encryptor,
detached_ciphertext: &[u8],
external_aad: Option<&[u8]>,
) -> Result<&[u8], Error> {
if !self.state.complete() {
return Err(Error::InvalidState(
"EncryptMessage must be decoded before decrypting".into(),
));
}
if !self.ciphertext_detached {
return Err(Error::Custom(
"EncryptMessage carries embedded ciphertext; use decrypt".into(),
));
}
let context = self.prepare_decryption_with_crit(
encryptor.alg(),
encryptor.nonce_size(),
encryptor.base_iv(),
external_aad,
encryptor.understood_critical_headers(),
)?;
let plaintext = encryptor.decrypt(&context.nonce, detached_ciphertext, &context.aad)?;
self.ciphertext.clear();
self.ciphertext.extend_from_slice(detached_ciphertext);
self.payload = Some(plaintext);
Ok(self.payload.as_deref().expect("payload was just set"))
}
pub fn decrypt_and_decode(
encryptor: &dyn Encryptor,
data: &[u8],
external_aad: Option<&[u8]>,
) -> Result<Self, Error> {
let mut msg = Self::from_slice(data)?;
msg.decrypt(encryptor, external_aad)?;
Ok(msg)
}
pub fn decrypt_detached_and_decode(
encryptor: &dyn Encryptor,
data: &[u8],
detached_ciphertext: &[u8],
external_aad: Option<&[u8]>,
) -> Result<Self, Error> {
let mut msg = Self::from_slice(data)?;
msg.decrypt_detached(encryptor, detached_ciphertext, external_aad)?;
Ok(msg)
}
pub fn ciphertext(&self) -> &[u8] {
&self.ciphertext
}
pub fn protected_raw(&self) -> &[u8] {
&self.protected_raw
}
pub fn is_ciphertext_detached(&self) -> bool {
self.ciphertext_detached
}
pub const TAG: u64 = iana::CBORTagCOSEEncrypt;
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_cbor_shape<T: cbor2::Cbor>(tag: Option<u64>, array: bool) {
assert_eq!(T::TAG, tag);
assert_eq!(T::ARRAY, array);
}
#[test]
fn wire_metadata_declares_tagged_array_shape() {
assert_cbor_shape::<EncryptWire>(Some(iana::CBORTagCOSEEncrypt), true);
}
}