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