coset/mac/
mod.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17//! COSE_Mac functionality.
18
19use crate::{
20    cbor,
21    cbor::value::Value,
22    common::AsCborValue,
23    iana,
24    util::{cbor_type_error, to_cbor_array, ValueTryAs},
25    CoseError, CoseRecipient, Header, ProtectedHeader, Result,
26};
27use alloc::{borrow::ToOwned, vec, vec::Vec};
28
29#[cfg(test)]
30mod tests;
31
32/// Structure representing a message with authentication code (MAC).
33///
34/// ```cddl
35///  COSE_Mac = [
36///     Headers,
37///     payload : bstr / nil,
38///     tag : bstr,
39///     recipients :[+COSE_recipient]
40///  ]
41/// ```
42#[derive(Clone, Debug, Default, PartialEq)]
43pub struct CoseMac {
44    pub protected: ProtectedHeader,
45    pub unprotected: Header,
46    pub payload: Option<Vec<u8>>,
47    pub tag: Vec<u8>,
48    pub recipients: Vec<CoseRecipient>,
49}
50
51impl crate::CborSerializable for CoseMac {}
52
53impl crate::TaggedCborSerializable for CoseMac {
54    const TAG: u64 = iana::CborTag::CoseMac as u64;
55}
56
57impl AsCborValue for CoseMac {
58    fn from_cbor_value(value: Value) -> Result<Self> {
59        let mut a = value.try_as_array()?;
60        if a.len() != 5 {
61            return Err(CoseError::UnexpectedItem("array", "array with 5 items"));
62        }
63
64        // Remove array elements in reverse order to avoid shifts.
65        let recipients = a
66            .remove(4)
67            .try_as_array_then_convert(CoseRecipient::from_cbor_value)?;
68
69        Ok(Self {
70            recipients,
71            tag: a.remove(3).try_as_bytes()?,
72            payload: match a.remove(2) {
73                Value::Bytes(b) => Some(b),
74                Value::Null => None,
75                v => return cbor_type_error(&v, "bstr"),
76            },
77            unprotected: Header::from_cbor_value(a.remove(1))?,
78            protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
79        })
80    }
81
82    fn to_cbor_value(self) -> Result<Value> {
83        Ok(Value::Array(vec![
84            self.protected.cbor_bstr()?,
85            self.unprotected.to_cbor_value()?,
86            match self.payload {
87                None => Value::Null,
88                Some(b) => Value::Bytes(b),
89            },
90            Value::Bytes(self.tag),
91            to_cbor_array(self.recipients)?,
92        ]))
93    }
94}
95
96impl CoseMac {
97    /// Verify the `tag` value using the provided `mac` function, feeding it
98    /// the `tag` value and the combined to-be-MACed data (in that order).
99    ///
100    /// # Panics
101    ///
102    /// This function will panic if the `payload` has not been set.
103    #[deprecated = "Use verify_payload_tag() to ensure payload is present"]
104    pub fn verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E>
105    where
106        F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
107    {
108        let tbm = self.tbm(external_aad);
109        verify(&self.tag, &tbm)
110    }
111
112    /// Verify the `tag` value using the provided `mac` function, feeding it
113    /// the `tag` value and the combined to-be-MACed data (in that order).
114    /// If the `CoseMac` has no payload, then `missing_payload_error()` will
115    /// be returned.
116    pub fn verify_payload_tag<F, E, G>(
117        &self,
118        external_aad: &[u8],
119        missing_payload_error: G,
120        verify: F,
121    ) -> Result<(), E>
122    where
123        F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
124        G: FnOnce() -> E,
125    {
126        if self.payload.is_none() {
127            return Err(missing_payload_error());
128        }
129        let tbm = self.tbm(external_aad);
130        verify(&self.tag, &tbm)
131    }
132
133    /// Construct the to-be-MAC-ed data for this object. Any protected header values should be set
134    /// before using this method, as should the `payload`.
135    ///
136    /// # Panics
137    ///
138    /// This function will panic if the `payload` has not been set.
139    fn tbm(&self, external_aad: &[u8]) -> Vec<u8> {
140        mac_structure_data(
141            MacContext::CoseMac,
142            self.protected.clone(),
143            external_aad,
144            self.payload.as_ref().expect("payload missing"), // safe: documented
145        )
146    }
147}
148
149/// Builder for [`CoseMac`] objects.
150#[derive(Debug, Default)]
151pub struct CoseMacBuilder(CoseMac);
152
153impl CoseMacBuilder {
154    builder! {CoseMac}
155    builder_set_protected! {protected}
156    builder_set! {unprotected: Header}
157    builder_set! {tag: Vec<u8>}
158    builder_set_optional! {payload: Vec<u8>}
159
160    /// Add a [`CoseRecipient`].
161    #[must_use]
162    pub fn add_recipient(mut self, recipient: CoseRecipient) -> Self {
163        self.0.recipients.push(recipient);
164        self
165    }
166
167    /// Calculate the tag value, using `mac`. Any protected header values should be set
168    /// before using this method, as should the `payload`.
169    ///
170    /// # Panics
171    ///
172    /// This function will panic if the `payload` has not been set.
173    #[must_use]
174    pub fn create_tag<F>(self, external_aad: &[u8], create: F) -> Self
175    where
176        F: FnOnce(&[u8]) -> Vec<u8>,
177    {
178        let tbm = self.0.tbm(external_aad);
179        self.tag(create(&tbm))
180    }
181
182    /// Calculate the tag value, using `mac`. Any protected header values should be set
183    /// before using this method, as should the `payload`.
184    ///
185    /// # Panics
186    ///
187    /// This function will panic if the `payload` has not been set.
188    pub fn try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E>
189    where
190        F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
191    {
192        let tbm = self.0.tbm(external_aad);
193        Ok(self.tag(create(&tbm)?))
194    }
195}
196
197/// Structure representing a message with authentication code (MAC)
198/// where the relevant key is implicit.
199///
200/// ```cddl
201///  COSE_Mac0 = [
202///     Headers,
203///     payload : bstr / nil,
204///     tag : bstr,
205///  ]
206/// ```
207#[derive(Clone, Debug, Default, PartialEq)]
208pub struct CoseMac0 {
209    pub protected: ProtectedHeader,
210    pub unprotected: Header,
211    pub payload: Option<Vec<u8>>,
212    pub tag: Vec<u8>,
213}
214
215impl crate::CborSerializable for CoseMac0 {}
216
217impl crate::TaggedCborSerializable for CoseMac0 {
218    const TAG: u64 = iana::CborTag::CoseMac0 as u64;
219}
220
221impl AsCborValue for CoseMac0 {
222    fn from_cbor_value(value: Value) -> Result<Self> {
223        let mut a = value.try_as_array()?;
224        if a.len() != 4 {
225            return Err(CoseError::UnexpectedItem("array", "array with 4 items"));
226        }
227
228        // Remove array elements in reverse order to avoid shifts.
229        Ok(Self {
230            tag: a.remove(3).try_as_bytes()?,
231            payload: match a.remove(2) {
232                Value::Bytes(b) => Some(b),
233                Value::Null => None,
234                v => return cbor_type_error(&v, "bstr"),
235            },
236            unprotected: Header::from_cbor_value(a.remove(1))?,
237            protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
238        })
239    }
240
241    fn to_cbor_value(self) -> Result<Value> {
242        Ok(Value::Array(vec![
243            self.protected.cbor_bstr()?,
244            self.unprotected.to_cbor_value()?,
245            match self.payload {
246                None => Value::Null,
247                Some(b) => Value::Bytes(b),
248            },
249            Value::Bytes(self.tag),
250        ]))
251    }
252}
253
254impl CoseMac0 {
255    /// Verify the `tag` value using the provided `mac` function, feeding it
256    /// the `tag` value and the combined to-be-MACed data (in that order).
257    ///
258    /// # Panics
259    ///
260    /// This function will panic if the `payload` has not been set.
261    #[deprecated = "Use verify_payload_tag() to ensure payload is present"]
262    pub fn verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E>
263    where
264        F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
265    {
266        let tbm = self.tbm(external_aad);
267        verify(&self.tag, &tbm)
268    }
269
270    /// Verify the `tag` value using the provided `mac` function, feeding it
271    /// the `tag` value and the combined to-be-MACed data (in that order).
272    /// If the `CoseMac` has no payload, then `missing_payload_error()` will
273    /// be returned.
274    pub fn verify_payload_tag<F, E, G>(
275        &self,
276        external_aad: &[u8],
277        missing_payload_error: G,
278        verify: F,
279    ) -> Result<(), E>
280    where
281        F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
282        G: FnOnce() -> E,
283    {
284        if self.payload.is_none() {
285            return Err(missing_payload_error());
286        }
287        let tbm = self.tbm(external_aad);
288        verify(&self.tag, &tbm)
289    }
290
291    /// Construct the to-be-MAC-ed data for this object. Any protected header values should be set
292    /// before using this method, as should the `payload`.
293    ///
294    /// # Panics
295    ///
296    /// This function will panic if the `payload` has not been set.
297    fn tbm(&self, external_aad: &[u8]) -> Vec<u8> {
298        mac_structure_data(
299            MacContext::CoseMac0,
300            self.protected.clone(),
301            external_aad,
302            self.payload.as_ref().expect("payload missing"), // safe: documented
303        )
304    }
305}
306
307/// Builder for [`CoseMac0`] objects.
308#[derive(Debug, Default)]
309pub struct CoseMac0Builder(CoseMac0);
310
311impl CoseMac0Builder {
312    builder! {CoseMac0}
313    builder_set_protected! {protected}
314    builder_set! {unprotected: Header}
315    builder_set! {tag: Vec<u8>}
316    builder_set_optional! {payload: Vec<u8>}
317
318    /// Calculate the tag value, using `mac`. Any protected header values should be set
319    /// before using this method, as should the `payload`.
320    ///
321    /// # Panics
322    ///
323    /// This function will panic if the `payload` has not been set.
324    #[must_use]
325    pub fn create_tag<F>(self, external_aad: &[u8], create: F) -> Self
326    where
327        F: FnOnce(&[u8]) -> Vec<u8>,
328    {
329        let tbm = self.0.tbm(external_aad);
330        self.tag(create(&tbm))
331    }
332
333    /// Calculate the tag value, using `mac`. Any protected header values should be set
334    /// before using this method, as should the `payload`.
335    ///
336    /// # Panics
337    ///
338    /// This function will panic if the `payload` has not been set.
339    pub fn try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E>
340    where
341        F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
342    {
343        let tbm = self.0.tbm(external_aad);
344        Ok(self.tag(create(&tbm)?))
345    }
346}
347
348/// Possible MAC contexts.
349#[derive(Clone, Copy, Debug)]
350pub enum MacContext {
351    CoseMac,
352    CoseMac0,
353}
354
355impl MacContext {
356    /// Return the context string as per RFC 8152 section 6.3.
357    fn text(&self) -> &'static str {
358        match self {
359            MacContext::CoseMac => "MAC",
360            MacContext::CoseMac0 => "MAC0",
361        }
362    }
363}
364
365/// Create a binary blob that will be signed.
366//
367/// ```cddl
368///  MAC_structure = [
369///       context : "MAC" / "MAC0",
370///       protected : empty_or_serialized_map,
371///       external_aad : bstr,
372///       payload : bstr
373///  ]
374/// ```
375pub fn mac_structure_data(
376    context: MacContext,
377    protected: ProtectedHeader,
378    external_aad: &[u8],
379    payload: &[u8],
380) -> Vec<u8> {
381    let arr = vec![
382        Value::Text(context.text().to_owned()),
383        protected.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
384        Value::Bytes(external_aad.to_vec()),
385        Value::Bytes(payload.to_vec()),
386    ];
387
388    let mut data = Vec::new();
389    cbor::ser::into_writer(&Value::Array(arr), &mut data).unwrap(); // safe: always serializable
390    data
391}