• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 Headers functionality.
18 
19 use crate::{
20     cbor::value::Value,
21     common::AsCborValue,
22     iana,
23     iana::EnumI64,
24     util::{cbor_type_error, to_cbor_array, ValueTryAs},
25     Algorithm, CborSerializable, CoseError, CoseSignature, Label, RegisteredLabel, Result,
26 };
27 use alloc::{collections::BTreeSet, string::String, vec, vec::Vec};
28 
29 #[cfg(test)]
30 mod tests;
31 
32 /// Content type.
33 pub type ContentType = crate::RegisteredLabel<iana::CoapContentFormat>;
34 
35 /// Structure representing a common COSE header map.
36 ///
37 /// ```cddl
38 ///   header_map = {
39 ///       Generic_Headers,
40 ///       * label => values
41 ///   }
42 ///
43 ///   Generic_Headers = (
44 ///       ? 1 => int / tstr,  ; algorithm identifier
45 ///       ? 2 => [+label],    ; criticality
46 ///       ? 3 => tstr / int,  ; content type
47 ///       ? 4 => bstr,        ; key identifier
48 ///       ? 5 => bstr,        ; IV
49 ///       ? 6 => bstr,        ; Partial IV
50 ///       ? 7 => COSE_Signature / [+COSE_Signature] ; Counter signature
51 ///   )
52 ///  ```
53 #[derive(Clone, Debug, Default, PartialEq)]
54 pub struct Header {
55     /// Cryptographic algorithm to use
56     pub alg: Option<Algorithm>,
57     /// Critical headers to be understood
58     pub crit: Vec<RegisteredLabel<iana::HeaderParameter>>,
59     /// Content type of the payload
60     pub content_type: Option<ContentType>,
61     /// Key identifier.
62     pub key_id: Vec<u8>,
63     /// Full initialization vector
64     pub iv: Vec<u8>,
65     /// Partial initialization vector
66     pub partial_iv: Vec<u8>,
67     /// Counter signature
68     pub counter_signatures: Vec<CoseSignature>,
69     /// Any additional header (label,value) pairs.  If duplicate labels are present, CBOR-encoding
70     /// will fail.
71     pub rest: Vec<(Label, Value)>,
72 }
73 
74 impl Header {
75     /// Indicate whether the `Header` is empty.
is_empty(&self) -> bool76     pub fn is_empty(&self) -> bool {
77         self.alg.is_none()
78             && self.crit.is_empty()
79             && self.content_type.is_none()
80             && self.key_id.is_empty()
81             && self.iv.is_empty()
82             && self.partial_iv.is_empty()
83             && self.counter_signatures.is_empty()
84             && self.rest.is_empty()
85     }
86 }
87 
88 impl crate::CborSerializable for Header {}
89 
90 const ALG: Label = Label::Int(iana::HeaderParameter::Alg as i64);
91 const CRIT: Label = Label::Int(iana::HeaderParameter::Crit as i64);
92 const CONTENT_TYPE: Label = Label::Int(iana::HeaderParameter::ContentType as i64);
93 const KID: Label = Label::Int(iana::HeaderParameter::Kid as i64);
94 const IV: Label = Label::Int(iana::HeaderParameter::Iv as i64);
95 const PARTIAL_IV: Label = Label::Int(iana::HeaderParameter::PartialIv as i64);
96 const COUNTER_SIG: Label = Label::Int(iana::HeaderParameter::CounterSignature as i64);
97 
98 impl AsCborValue for Header {
from_cbor_value(value: Value) -> Result<Self>99     fn from_cbor_value(value: Value) -> Result<Self> {
100         let m = value.try_as_map()?;
101         let mut headers = Self::default();
102         let mut seen = BTreeSet::new();
103         for (l, value) in m.into_iter() {
104             // The `ciborium` CBOR library does not police duplicate map keys.
105             // RFC 8152 section 14 requires that COSE does police duplicates, so do it here.
106             let label = Label::from_cbor_value(l)?;
107             if seen.contains(&label) {
108                 return Err(CoseError::DuplicateMapKey);
109             }
110             seen.insert(label.clone());
111             match label {
112                 ALG => headers.alg = Some(Algorithm::from_cbor_value(value)?),
113 
114                 CRIT => match value {
115                     Value::Array(a) => {
116                         if a.is_empty() {
117                             return Err(CoseError::UnexpectedItem(
118                                 "empty array",
119                                 "non-empty array",
120                             ));
121                         }
122                         for v in a {
123                             headers.crit.push(
124                                 RegisteredLabel::<iana::HeaderParameter>::from_cbor_value(v)?,
125                             );
126                         }
127                     }
128                     v => return cbor_type_error(&v, "array value"),
129                 },
130 
131                 CONTENT_TYPE => {
132                     headers.content_type = Some(ContentType::from_cbor_value(value)?);
133                     if let Some(ContentType::Text(text)) = &headers.content_type {
134                         if text.is_empty() {
135                             return Err(CoseError::UnexpectedItem("empty tstr", "non-empty tstr"));
136                         }
137                         if text.trim() != text {
138                             return Err(CoseError::UnexpectedItem(
139                                 "leading/trailing whitespace",
140                                 "no leading/trailing whitespace",
141                             ));
142                         }
143                         // Basic check that the content type is of form type/subtype.
144                         // We don't check the precise definition though (RFC 6838 s4.2)
145                         if text.matches('/').count() != 1 {
146                             return Err(CoseError::UnexpectedItem(
147                                 "arbitrary text",
148                                 "text of form type/subtype",
149                             ));
150                         }
151                     }
152                 }
153 
154                 KID => {
155                     headers.key_id = value.try_as_nonempty_bytes()?;
156                 }
157 
158                 IV => {
159                     headers.iv = value.try_as_nonempty_bytes()?;
160                 }
161 
162                 PARTIAL_IV => {
163                     headers.partial_iv = value.try_as_nonempty_bytes()?;
164                 }
165                 COUNTER_SIG => {
166                     let sig_or_sigs = value.try_as_array()?;
167                     if sig_or_sigs.is_empty() {
168                         return Err(CoseError::UnexpectedItem(
169                             "empty sig array",
170                             "non-empty sig array",
171                         ));
172                     }
173                     // The encoding of counter signature[s] is pesky:
174                     // - a single counter signature is encoded as `COSE_Signature` (a 3-tuple)
175                     // - multiple counter signatures are encoded as `[+ COSE_Signature]`
176                     //
177                     // Determine which is which by looking at the first entry of the array:
178                     // - If it's a bstr, sig_or_sigs is a single signature.
179                     // - If it's an array, sig_or_sigs is an array of signatures
180                     match &sig_or_sigs[0] {
181                         Value::Bytes(_) => headers
182                             .counter_signatures
183                             .push(CoseSignature::from_cbor_value(Value::Array(sig_or_sigs))?),
184                         Value::Array(_) => {
185                             for sig in sig_or_sigs.into_iter() {
186                                 headers
187                                     .counter_signatures
188                                     .push(CoseSignature::from_cbor_value(sig)?);
189                             }
190                         }
191                         v => return cbor_type_error(v, "array or bstr value"),
192                     }
193                 }
194 
195                 label => headers.rest.push((label, value)),
196             }
197             // RFC 8152 section 3.1: "The 'Initialization Vector' and 'Partial Initialization
198             // Vector' parameters MUST NOT both be present in the same security layer."
199             if !headers.iv.is_empty() && !headers.partial_iv.is_empty() {
200                 return Err(CoseError::UnexpectedItem(
201                     "IV and partial-IV specified",
202                     "only one of IV and partial IV",
203                 ));
204             }
205         }
206         Ok(headers)
207     }
208 
to_cbor_value(mut self) -> Result<Value>209     fn to_cbor_value(mut self) -> Result<Value> {
210         let mut map = Vec::<(Value, Value)>::new();
211         if let Some(alg) = self.alg {
212             map.push((ALG.to_cbor_value()?, alg.to_cbor_value()?));
213         }
214         if !self.crit.is_empty() {
215             map.push((CRIT.to_cbor_value()?, to_cbor_array(self.crit)?));
216         }
217         if let Some(content_type) = self.content_type {
218             map.push((CONTENT_TYPE.to_cbor_value()?, content_type.to_cbor_value()?));
219         }
220         if !self.key_id.is_empty() {
221             map.push((KID.to_cbor_value()?, Value::Bytes(self.key_id)));
222         }
223         if !self.iv.is_empty() {
224             map.push((IV.to_cbor_value()?, Value::Bytes(self.iv)));
225         }
226         if !self.partial_iv.is_empty() {
227             map.push((PARTIAL_IV.to_cbor_value()?, Value::Bytes(self.partial_iv)));
228         }
229         if !self.counter_signatures.is_empty() {
230             if self.counter_signatures.len() == 1 {
231                 // A single counter signature is encoded differently.
232                 map.push((
233                     COUNTER_SIG.to_cbor_value()?,
234                     self.counter_signatures.remove(0).to_cbor_value()?,
235                 ));
236             } else {
237                 map.push((
238                     COUNTER_SIG.to_cbor_value()?,
239                     to_cbor_array(self.counter_signatures)?,
240                 ));
241             }
242         }
243         let mut seen = BTreeSet::new();
244         for (label, value) in self.rest.into_iter() {
245             if seen.contains(&label) {
246                 return Err(CoseError::DuplicateMapKey);
247             }
248             seen.insert(label.clone());
249             map.push((label.to_cbor_value()?, value));
250         }
251         Ok(Value::Map(map))
252     }
253 }
254 
255 /// Builder for [`Header`] objects.
256 #[derive(Debug, Default)]
257 pub struct HeaderBuilder(Header);
258 
259 impl HeaderBuilder {
260     builder! {Header}
261     builder_set! {key_id: Vec<u8>}
262 
263     /// Set the algorithm.
264     #[must_use]
algorithm(mut self, alg: iana::Algorithm) -> Self265     pub fn algorithm(mut self, alg: iana::Algorithm) -> Self {
266         self.0.alg = Some(Algorithm::Assigned(alg));
267         self
268     }
269 
270     /// Add a critical header.
271     #[must_use]
add_critical(mut self, param: iana::HeaderParameter) -> Self272     pub fn add_critical(mut self, param: iana::HeaderParameter) -> Self {
273         self.0.crit.push(RegisteredLabel::Assigned(param));
274         self
275     }
276 
277     /// Add a critical header.
278     #[must_use]
add_critical_label(mut self, label: RegisteredLabel<iana::HeaderParameter>) -> Self279     pub fn add_critical_label(mut self, label: RegisteredLabel<iana::HeaderParameter>) -> Self {
280         self.0.crit.push(label);
281         self
282     }
283 
284     /// Set the content type to a numeric value.
285     #[must_use]
content_format(mut self, content_type: iana::CoapContentFormat) -> Self286     pub fn content_format(mut self, content_type: iana::CoapContentFormat) -> Self {
287         self.0.content_type = Some(ContentType::Assigned(content_type));
288         self
289     }
290 
291     /// Set the content type to a text value.
292     #[must_use]
content_type(mut self, content_type: String) -> Self293     pub fn content_type(mut self, content_type: String) -> Self {
294         self.0.content_type = Some(ContentType::Text(content_type));
295         self
296     }
297 
298     /// Set the IV, and clear any partial IV already set.
299     #[must_use]
iv(mut self, iv: Vec<u8>) -> Self300     pub fn iv(mut self, iv: Vec<u8>) -> Self {
301         self.0.iv = iv;
302         self.0.partial_iv.clear();
303         self
304     }
305 
306     /// Set the partial IV, and clear any IV already set.
307     #[must_use]
partial_iv(mut self, iv: Vec<u8>) -> Self308     pub fn partial_iv(mut self, iv: Vec<u8>) -> Self {
309         self.0.partial_iv = iv;
310         self.0.iv.clear();
311         self
312     }
313 
314     /// Add a counter signature.
315     #[must_use]
add_counter_signature(mut self, sig: CoseSignature) -> Self316     pub fn add_counter_signature(mut self, sig: CoseSignature) -> Self {
317         self.0.counter_signatures.push(sig);
318         self
319     }
320 
321     /// Set a header label:value pair. If duplicate labels are added to a [`Header`],
322     /// subsequent attempts to CBOR-encode the header will fail.
323     ///
324     /// # Panics
325     ///
326     /// This function will panic if it used to set a header label from the range [1, 6].
327     #[must_use]
value(mut self, label: i64, value: Value) -> Self328     pub fn value(mut self, label: i64, value: Value) -> Self {
329         if label >= iana::HeaderParameter::Alg.to_i64()
330             && label <= iana::HeaderParameter::CounterSignature.to_i64()
331         {
332             panic!("value() method used to set core header parameter"); // safe: invalid input
333         }
334         self.0.rest.push((Label::Int(label), value));
335         self
336     }
337 
338     /// Set a header label:value pair where the `label` is text.
339     #[must_use]
text_value(mut self, label: String, value: Value) -> Self340     pub fn text_value(mut self, label: String, value: Value) -> Self {
341         self.0.rest.push((Label::Text(label), value));
342         self
343     }
344 }
345 
346 /// Structure representing a protected COSE header map.
347 #[derive(Clone, Debug, Default, PartialEq)]
348 pub struct ProtectedHeader {
349     /// If this structure was created by parsing serialized data, this field
350     /// holds the entire contents of the original `bstr` data.
351     pub original_data: Option<Vec<u8>>,
352     /// Parsed header information.
353     pub header: Header,
354 }
355 
356 impl ProtectedHeader {
357     /// Constructor from a [`Value`] that holds a `bstr` encoded header.
358     #[inline]
from_cbor_bstr(val: Value) -> Result<Self>359     pub fn from_cbor_bstr(val: Value) -> Result<Self> {
360         let data = val.try_as_bytes()?;
361         let header = if data.is_empty() {
362             // An empty bstr is used as a short cut for an empty header map.
363             Header::default()
364         } else {
365             Header::from_slice(&data)?
366         };
367         Ok(ProtectedHeader {
368             original_data: Some(data),
369             header,
370         })
371     }
372 
373     /// Convert this header to a `bstr` encoded map, as a [`Value`], consuming the object along the
374     /// way.
375     #[inline]
cbor_bstr(self) -> Result<Value>376     pub fn cbor_bstr(self) -> Result<Value> {
377         Ok(Value::Bytes(
378             if let Some(original_data) = self.original_data {
379                 original_data
380             } else if self.is_empty() {
381                 vec![]
382             } else {
383                 self.to_vec()?
384             },
385         ))
386     }
387 
388     /// Indicate whether the `ProtectedHeader` is empty.
is_empty(&self) -> bool389     pub fn is_empty(&self) -> bool {
390         self.header.is_empty()
391     }
392 }
393 
394 impl crate::CborSerializable for ProtectedHeader {}
395 
396 impl AsCborValue for ProtectedHeader {
from_cbor_value(value: Value) -> Result<Self>397     fn from_cbor_value(value: Value) -> Result<Self> {
398         Ok(ProtectedHeader {
399             original_data: None,
400             header: Header::from_cbor_value(value)?,
401         })
402     }
403 
to_cbor_value(self) -> Result<Value>404     fn to_cbor_value(self) -> Result<Value> {
405         self.header.to_cbor_value()
406     }
407 }
408