use cbor2::Cbor;
use crate::{
header::{decode_protected, encode_protected, validate_header_buckets},
iana, tag, util, Error, Header, Label, Macer,
};
#[derive(Clone, Debug, PartialEq, Cbor)]
#[cbor(tag = 17, array)]
struct Mac0Wire {
#[serde(with = "crate::strict::bytes")]
protected: Vec<u8>,
unprotected: Header,
#[serde(with = "crate::strict::optional_bytes")]
payload: Option<Vec<u8>>,
#[serde(with = "crate::strict::bytes")]
tag: Vec<u8>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Mac0Message {
pub protected: Header,
pub unprotected: Header,
pub payload: Option<Vec<u8>>,
tag: Vec<u8>,
protected_raw: Vec<u8>,
state: util::OperationState,
}
impl Mac0Message {
pub fn new(payload: Option<Vec<u8>>) -> Self {
Mac0Message {
payload,
..Default::default()
}
}
pub fn to_be_maced(
protected_raw: &[u8],
external_aad: &[u8],
payload: &[u8],
) -> Result<Vec<u8>, Error> {
util::encode_structure(&(
"MAC0",
serde_bytes::Bytes::new(protected_raw),
serde_bytes::Bytes::new(external_aad),
serde_bytes::Bytes::new(payload),
))
}
pub fn prepare_tag(
&mut self,
alg: Option<Label>,
kid: Option<&[u8]>,
external_aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
self.prepare_tag_headers(alg, kid)?;
let payload = util::require_embedded_payload(&self.payload, "Mac0Message::prepare_tag")?;
Self::to_be_maced(&self.protected_raw, external_aad.unwrap_or(&[]), payload)
}
pub fn prepare_detached_tag(
&mut self,
alg: Option<Label>,
kid: Option<&[u8]>,
detached_payload: &[u8],
external_aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
self.prepare_tag_headers(alg, kid)?;
let tbm = Self::to_be_maced(
&self.protected_raw,
external_aad.unwrap_or(&[]),
detached_payload,
)?;
self.payload = None;
Ok(tbm)
}
fn prepare_tag_headers(&mut self, alg: Option<Label>, kid: Option<&[u8]>) -> Result<(), Error> {
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)?;
let protected_raw = encode_protected(&self.protected)?;
self.protected_raw = protected_raw;
self.state = util::OperationState::Prepared;
self.tag.clear();
Ok(())
}
pub fn set_tag(&mut self, tag: impl Into<Vec<u8>>) -> Result<(), Error> {
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.tag = tag.into();
self.state = util::OperationState::Complete;
Ok(())
}
pub fn compute(&mut self, macer: &dyn Macer, external_aad: Option<&[u8]>) -> Result<(), Error> {
self.prepare_tag_headers(macer.alg(), macer.kid())?;
let payload = util::require_embedded_payload(&self.payload, "Mac0Message::compute")?;
let tbm = Self::to_be_maced(&self.protected_raw, external_aad.unwrap_or(&[]), payload)?;
let tag = macer.mac_create(&tbm)?;
self.set_tag(tag)
}
pub fn compute_detached(
&mut self,
macer: &dyn Macer,
detached_payload: &[u8],
external_aad: Option<&[u8]>,
) -> Result<(), Error> {
self.prepare_tag_headers(macer.alg(), macer.kid())?;
let tbm = Self::to_be_maced(
&self.protected_raw,
external_aad.unwrap_or(&[]),
detached_payload,
)?;
let tag = macer.mac_create(&tbm)?;
self.set_tag(tag)?;
self.payload = None;
Ok(())
}
pub fn compute_and_encode(
&mut self,
macer: &dyn Macer,
external_aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
self.compute(macer, external_aad)?;
self.to_vec()
}
pub fn compute_detached_and_encode(
&mut self,
macer: &dyn Macer,
detached_payload: &[u8],
external_aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
self.compute_detached(macer, detached_payload, external_aad)?;
self.to_vec()
}
pub fn to_vec(&self) -> Result<Vec<u8>, Error> {
self.encode(tag::MAC0_PREFIX)
}
pub fn to_cwt_vec(&self) -> Result<Vec<u8>, Error> {
self.encode(tag::CWT_MAC0_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(
"Mac0Message must be computed before encoding".into(),
));
}
validate_header_buckets(&self.protected, &self.unprotected)?;
crate::header::validate_protected_state(&self.protected, &self.protected_raw)?;
let unprotected = util::canonical_raw(&self.unprotected)?;
util::encode_prefixed(
prefix,
&(
serde_bytes::Bytes::new(&self.protected_raw),
&unprotected,
self.payload.as_deref().map(serde_bytes::Bytes::new),
serde_bytes::Bytes::new(&self.tag),
),
)
}
pub fn from_slice(data: &[u8]) -> Result<Self, Error> {
let body = tag::message_body(data, Self::TAG)?;
let wire: Mac0Wire = cbor2::from_slice(body)?;
let protected = decode_protected(&wire.protected)?;
validate_header_buckets(&protected, &wire.unprotected)?;
Ok(Mac0Message {
protected,
unprotected: wire.unprotected,
payload: wire.payload,
tag: wire.tag,
protected_raw: wire.protected,
state: util::OperationState::Complete,
})
}
pub fn verify(&self, macer: &dyn Macer, external_aad: Option<&[u8]>) -> Result<(), Error> {
if !self.state.complete() {
return Err(Error::InvalidState(
"Mac0Message must be decoded before verifying".into(),
));
}
let payload = util::require_embedded_payload(&self.payload, "Mac0Message::verify")?;
self.verify_payload(macer, payload, external_aad.unwrap_or(&[]))
}
pub fn verify_detached(
&self,
macer: &dyn Macer,
detached_payload: &[u8],
external_aad: Option<&[u8]>,
) -> Result<(), Error> {
if !self.state.complete() {
return Err(Error::InvalidState(
"Mac0Message must be decoded before verifying".into(),
));
}
if self.payload.is_some() {
return Err(Error::Custom(
"Mac0Message carries an embedded payload; use verify".into(),
));
}
self.verify_payload(macer, detached_payload, external_aad.unwrap_or(&[]))
}
fn verify_payload(
&self,
macer: &dyn Macer,
payload: &[u8],
external_aad: &[u8],
) -> Result<(), Error> {
crate::header::validate_protected_state(&self.protected, &self.protected_raw)?;
self.protected
.ensure_crit_understood(macer.understood_critical_headers())?;
util::check_protected_alg(&self.protected, &self.unprotected, macer.alg())?;
let tbm = Self::to_be_maced(&self.protected_raw, external_aad, payload)?;
macer.mac_verify(&tbm, &self.tag)
}
pub fn verify_and_decode(
macer: &dyn Macer,
data: &[u8],
external_aad: Option<&[u8]>,
) -> Result<Self, Error> {
let msg = Self::from_slice(data)?;
msg.verify(macer, external_aad)?;
Ok(msg)
}
pub fn verify_detached_and_decode(
macer: &dyn Macer,
data: &[u8],
detached_payload: &[u8],
external_aad: Option<&[u8]>,
) -> Result<Self, Error> {
let msg = Self::from_slice(data)?;
msg.verify_detached(macer, detached_payload, external_aad)?;
Ok(msg)
}
pub fn tag(&self) -> &[u8] {
&self.tag
}
pub fn protected_raw(&self) -> &[u8] {
&self.protected_raw
}
pub const TAG: u64 = iana::CBORTagCOSEMac0;
}
#[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::<Mac0Wire>(Some(iana::CBORTagCOSEMac0), true);
}
}