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 
19 use 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 };
27 use alloc::{borrow::ToOwned, vec, vec::Vec};
28 
29 #[cfg(test)]
30 mod 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)]
43 pub 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 
51 impl crate::CborSerializable for CoseMac {}
52 
53 impl crate::TaggedCborSerializable for CoseMac {
54     const TAG: u64 = iana::CborTag::CoseMac as u64;
55 }
56 
57 impl AsCborValue for CoseMac {
from_cbor_value(value: Value) -> Result<Self>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 
to_cbor_value(self) -> Result<Value>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 
96 impl 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.
verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E> where F: FnOnce(&[u8], &[u8]) -> Result<(), E>,103     pub fn verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E>
104     where
105         F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
106     {
107         let tbm = self.tbm(external_aad);
108         verify(&self.tag, &tbm)
109     }
110 
111     /// Construct the to-be-MAC-ed data for this object. Any protected header values should be set
112     /// before using this method, as should the `payload`.
113     ///
114     /// # Panics
115     ///
116     /// This function will panic if the `payload` has not been set.
tbm(&self, external_aad: &[u8]) -> Vec<u8>117     fn tbm(&self, external_aad: &[u8]) -> Vec<u8> {
118         mac_structure_data(
119             MacContext::CoseMac,
120             self.protected.clone(),
121             external_aad,
122             self.payload.as_ref().expect("payload missing"), // safe: documented
123         )
124     }
125 }
126 
127 /// Builder for [`CoseMac`] objects.
128 #[derive(Debug, Default)]
129 pub struct CoseMacBuilder(CoseMac);
130 
131 impl CoseMacBuilder {
132     builder! {CoseMac}
133     builder_set_protected! {protected}
134     builder_set! {unprotected: Header}
135     builder_set! {tag: Vec<u8>}
136     builder_set_optional! {payload: Vec<u8>}
137 
138     /// Add a [`CoseRecipient`].
139     #[must_use]
add_recipient(mut self, recipient: CoseRecipient) -> Self140     pub fn add_recipient(mut self, recipient: CoseRecipient) -> Self {
141         self.0.recipients.push(recipient);
142         self
143     }
144 
145     /// Calculate the tag value, using `mac`. Any protected header values should be set
146     /// before using this method, as should the `payload`.
147     ///
148     /// # Panics
149     ///
150     /// This function will panic if the `payload` has not been set.
151     #[must_use]
create_tag<F>(self, external_aad: &[u8], create: F) -> Self where F: FnOnce(&[u8]) -> Vec<u8>,152     pub fn create_tag<F>(self, external_aad: &[u8], create: F) -> Self
153     where
154         F: FnOnce(&[u8]) -> Vec<u8>,
155     {
156         let tbm = self.0.tbm(external_aad);
157         self.tag(create(&tbm))
158     }
159 
160     /// Calculate the tag value, using `mac`. Any protected header values should be set
161     /// before using this method, as should the `payload`.
162     ///
163     /// # Panics
164     ///
165     /// This function will panic if the `payload` has not been set.
try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E> where F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,166     pub fn try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E>
167     where
168         F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
169     {
170         let tbm = self.0.tbm(external_aad);
171         Ok(self.tag(create(&tbm)?))
172     }
173 }
174 
175 /// Structure representing a message with authentication code (MAC)
176 /// where the relevant key is implicit.
177 ///
178 /// ```cddl
179 ///  COSE_Mac0 = [
180 ///     Headers,
181 ///     payload : bstr / nil,
182 ///     tag : bstr,
183 ///  ]
184 /// ```
185 #[derive(Clone, Debug, Default, PartialEq)]
186 pub struct CoseMac0 {
187     pub protected: ProtectedHeader,
188     pub unprotected: Header,
189     pub payload: Option<Vec<u8>>,
190     pub tag: Vec<u8>,
191 }
192 
193 impl crate::CborSerializable for CoseMac0 {}
194 
195 impl crate::TaggedCborSerializable for CoseMac0 {
196     const TAG: u64 = iana::CborTag::CoseMac0 as u64;
197 }
198 
199 impl AsCborValue for CoseMac0 {
from_cbor_value(value: Value) -> Result<Self>200     fn from_cbor_value(value: Value) -> Result<Self> {
201         let mut a = value.try_as_array()?;
202         if a.len() != 4 {
203             return Err(CoseError::UnexpectedItem("array", "array with 4 items"));
204         }
205 
206         // Remove array elements in reverse order to avoid shifts.
207         Ok(Self {
208             tag: a.remove(3).try_as_bytes()?,
209             payload: match a.remove(2) {
210                 Value::Bytes(b) => Some(b),
211                 Value::Null => None,
212                 v => return cbor_type_error(&v, "bstr"),
213             },
214             unprotected: Header::from_cbor_value(a.remove(1))?,
215             protected: ProtectedHeader::from_cbor_bstr(a.remove(0))?,
216         })
217     }
218 
to_cbor_value(self) -> Result<Value>219     fn to_cbor_value(self) -> Result<Value> {
220         Ok(Value::Array(vec![
221             self.protected.cbor_bstr()?,
222             self.unprotected.to_cbor_value()?,
223             match self.payload {
224                 None => Value::Null,
225                 Some(b) => Value::Bytes(b),
226             },
227             Value::Bytes(self.tag),
228         ]))
229     }
230 }
231 
232 impl CoseMac0 {
233     /// Verify the `tag` value using the provided `mac` function, feeding it
234     /// the `tag` value and the combined to-be-MACed data (in that order).
235     ///
236     /// # Panics
237     ///
238     /// This function will panic if the `payload` has not been set.
verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E> where F: FnOnce(&[u8], &[u8]) -> Result<(), E>,239     pub fn verify_tag<F, E>(&self, external_aad: &[u8], verify: F) -> Result<(), E>
240     where
241         F: FnOnce(&[u8], &[u8]) -> Result<(), E>,
242     {
243         let tbm = self.tbm(external_aad);
244         verify(&self.tag, &tbm)
245     }
246 
247     /// Construct the to-be-MAC-ed data for this object. Any protected header values should be set
248     /// before using this method, as should the `payload`.
249     ///
250     /// # Panics
251     ///
252     /// This function will panic if the `payload` has not been set.
tbm(&self, external_aad: &[u8]) -> Vec<u8>253     fn tbm(&self, external_aad: &[u8]) -> Vec<u8> {
254         mac_structure_data(
255             MacContext::CoseMac0,
256             self.protected.clone(),
257             external_aad,
258             self.payload.as_ref().expect("payload missing"), // safe: documented
259         )
260     }
261 }
262 
263 /// Builder for [`CoseMac0`] objects.
264 #[derive(Debug, Default)]
265 pub struct CoseMac0Builder(CoseMac0);
266 
267 impl CoseMac0Builder {
268     builder! {CoseMac0}
269     builder_set_protected! {protected}
270     builder_set! {unprotected: Header}
271     builder_set! {tag: Vec<u8>}
272     builder_set_optional! {payload: Vec<u8>}
273 
274     /// Calculate the tag value, using `mac`. Any protected header values should be set
275     /// before using this method, as should the `payload`.
276     ///
277     /// # Panics
278     ///
279     /// This function will panic if the `payload` has not been set.
280     #[must_use]
create_tag<F>(self, external_aad: &[u8], create: F) -> Self where F: FnOnce(&[u8]) -> Vec<u8>,281     pub fn create_tag<F>(self, external_aad: &[u8], create: F) -> Self
282     where
283         F: FnOnce(&[u8]) -> Vec<u8>,
284     {
285         let tbm = self.0.tbm(external_aad);
286         self.tag(create(&tbm))
287     }
288 
289     /// Calculate the tag value, using `mac`. Any protected header values should be set
290     /// before using this method, as should the `payload`.
291     ///
292     /// # Panics
293     ///
294     /// This function will panic if the `payload` has not been set.
try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E> where F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,295     pub fn try_create_tag<F, E>(self, external_aad: &[u8], create: F) -> Result<Self, E>
296     where
297         F: FnOnce(&[u8]) -> Result<Vec<u8>, E>,
298     {
299         let tbm = self.0.tbm(external_aad);
300         Ok(self.tag(create(&tbm)?))
301     }
302 }
303 
304 /// Possible MAC contexts.
305 #[derive(Clone, Copy, Debug)]
306 pub enum MacContext {
307     CoseMac,
308     CoseMac0,
309 }
310 
311 impl MacContext {
312     /// Return the context string as per RFC 8152 section 6.3.
text(&self) -> &'static str313     fn text(&self) -> &'static str {
314         match self {
315             MacContext::CoseMac => "MAC",
316             MacContext::CoseMac0 => "MAC0",
317         }
318     }
319 }
320 
321 /// Create a binary blob that will be signed.
322 //
323 /// ```cddl
324 ///  MAC_structure = [
325 ///       context : "MAC" / "MAC0",
326 ///       protected : empty_or_serialized_map,
327 ///       external_aad : bstr,
328 ///       payload : bstr
329 ///  ]
330 /// ```
mac_structure_data( context: MacContext, protected: ProtectedHeader, external_aad: &[u8], payload: &[u8], ) -> Vec<u8>331 pub fn mac_structure_data(
332     context: MacContext,
333     protected: ProtectedHeader,
334     external_aad: &[u8],
335     payload: &[u8],
336 ) -> Vec<u8> {
337     let arr = vec![
338         Value::Text(context.text().to_owned()),
339         protected.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
340         Value::Bytes(external_aad.to_vec()),
341         Value::Bytes(payload.to_vec()),
342     ];
343 
344     let mut data = Vec::new();
345     cbor::ser::into_writer(&Value::Array(arr), &mut data).unwrap(); // safe: always serializable
346     data
347 }
348