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