use crate::{
cbor,
cbor::value::Value,
common::AsCborValue,
iana,
util::{cbor_type_error, to_cbor_array, ValueTryAs},
CoseError, CoseRecipient, Header, ProtectedHeader, Result,
};
use alloc::{borrow::ToOwned, vec, vec::Vec};
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CoseMac {
pub protected: ProtectedHeader,
pub unprotected: Header,
pub payload: Option<Vec<u8>>,
pub tag: Vec<u8>,
pub recipients: Vec<CoseRecipient>,
}
impl crate::CborSerializable for CoseMac {}
impl crate::TaggedCborSerializable for CoseMac {
const TAG: u64 = iana::CborTag::CoseMac as u64;
}
impl AsCborValue for CoseMac {
fn from_cbor_value(value: Value) -> Result<Self> {
let mut a = value.try_as_array()?;
if a.len() != 5 {
return Err(CoseError::UnexpectedItem("array", "array with 5 items"));
}
let recipients = a
.remove(4)
.try_as_array_then_convert(CoseRecipient::from_cbor_value)?;
Ok(Self {
recipients,
tag: a.remove(3).try_as_bytes()?,
payload: match a.remove(2) {
Value::Bytes(b) => Some(b),
Value::Null => None,
v => return cbor_type_error(&v, "bstr"),
},
unprotected: Header::from_cbor_value(a.remove(1))?,
protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
})
}
fn to_cbor_value(self) -> Result<Value> {
Ok(Value::Array(vec![
self.protected.cbor_bstr()?,
self.unprotected.to_cbor_value()?,
match self.payload {
None => Value::Null,
Some(b) => Value::Bytes(b),
},
Value::Bytes(self.tag),
to_cbor_array(self.recipients)?,
]))
}
}
impl CoseMac {
#[deprecated = "Use verify_payload_tag() to ensure payload is present"]
pub fn verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
{
let tbm = self.tbm(external_aad);
verify(&self.tag, &tbm)
}
pub fn verify_payload_tag<F, E, G>(
&self,
external_aad: &[u8],
missing_payload_error: G,
verify: F,
) -> Result<(), E>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
G: FnOnce() -> E,
{
if self.payload.is_none() {
return Err(missing_payload_error());
}
let tbm = self.tbm(external_aad);
verify(&self.tag, &tbm)
}
fn tbm(&self, external_aad: &[u8]) -> Vec<u8> {
mac_structure_data(
MacContext::CoseMac,
self.protected.clone(),
external_aad,
self.payload.as_ref().expect("payload missing"), )
}
}
#[derive(Debug, Default)]
pub struct CoseMacBuilder(CoseMac);
impl CoseMacBuilder {
builder! {CoseMac}
builder_set_protected! {protected}
builder_set! {unprotected: Header}
builder_set! {tag: Vec<u8>}
builder_set_optional! {payload: Vec<u8>}
#[must_use]
pub fn add_recipient(mut self, recipient: CoseRecipient) -> Self {
self.0.recipients.push(recipient);
self
}
#[must_use]
pub fn create_tag<F>(self, external_aad: &[u8], create: F) -> Self
where
F: FnOnce(&[u8]) -> Vec<u8>,
{
let tbm = self.0.tbm(external_aad);
self.tag(create(&tbm))
}
pub fn try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
{
let tbm = self.0.tbm(external_aad);
Ok(self.tag(create(&tbm)?))
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CoseMac0 {
pub protected: ProtectedHeader,
pub unprotected: Header,
pub payload: Option<Vec<u8>>,
pub tag: Vec<u8>,
}
impl crate::CborSerializable for CoseMac0 {}
impl crate::TaggedCborSerializable for CoseMac0 {
const TAG: u64 = iana::CborTag::CoseMac0 as u64;
}
impl AsCborValue for CoseMac0 {
fn from_cbor_value(value: Value) -> Result<Self> {
let mut a = value.try_as_array()?;
if a.len() != 4 {
return Err(CoseError::UnexpectedItem("array", "array with 4 items"));
}
Ok(Self {
tag: a.remove(3).try_as_bytes()?,
payload: match a.remove(2) {
Value::Bytes(b) => Some(b),
Value::Null => None,
v => return cbor_type_error(&v, "bstr"),
},
unprotected: Header::from_cbor_value(a.remove(1))?,
protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
})
}
fn to_cbor_value(self) -> Result<Value> {
Ok(Value::Array(vec![
self.protected.cbor_bstr()?,
self.unprotected.to_cbor_value()?,
match self.payload {
None => Value::Null,
Some(b) => Value::Bytes(b),
},
Value::Bytes(self.tag),
]))
}
}
impl CoseMac0 {
#[deprecated = "Use verify_payload_tag() to ensure payload is present"]
pub fn verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
{
let tbm = self.tbm(external_aad);
verify(&self.tag, &tbm)
}
pub fn verify_payload_tag<F, E, G>(
&self,
external_aad: &[u8],
missing_payload_error: G,
verify: F,
) -> Result<(), E>
where
F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
G: FnOnce() -> E,
{
if self.payload.is_none() {
return Err(missing_payload_error());
}
let tbm = self.tbm(external_aad);
verify(&self.tag, &tbm)
}
fn tbm(&self, external_aad: &[u8]) -> Vec<u8> {
mac_structure_data(
MacContext::CoseMac0,
self.protected.clone(),
external_aad,
self.payload.as_ref().expect("payload missing"), )
}
}
#[derive(Debug, Default)]
pub struct CoseMac0Builder(CoseMac0);
impl CoseMac0Builder {
builder! {CoseMac0}
builder_set_protected! {protected}
builder_set! {unprotected: Header}
builder_set! {tag: Vec<u8>}
builder_set_optional! {payload: Vec<u8>}
#[must_use]
pub fn create_tag<F>(self, external_aad: &[u8], create: F) -> Self
where
F: FnOnce(&[u8]) -> Vec<u8>,
{
let tbm = self.0.tbm(external_aad);
self.tag(create(&tbm))
}
pub fn try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
{
let tbm = self.0.tbm(external_aad);
Ok(self.tag(create(&tbm)?))
}
}
#[derive(Clone, Copy, Debug)]
pub enum MacContext {
CoseMac,
CoseMac0,
}
impl MacContext {
fn text(&self) -> &'static str {
match self {
MacContext::CoseMac => "MAC",
MacContext::CoseMac0 => "MAC0",
}
}
}
pub fn mac_structure_data(
context: MacContext,
protected: ProtectedHeader,
external_aad: &[u8],
payload: &[u8],
) -> Vec<u8> {
let arr = vec![
Value::Text(context.text().to_owned()),
protected.cbor_bstr().expect("failed to serialize header"), Value::Bytes(external_aad.to_vec()),
Value::Bytes(payload.to_vec()),
];
let mut data = Vec::new();
cbor::ser::into_writer(&Value::Array(arr), &mut data).unwrap(); data
}