//! Parse and decode COSE signatures. use cbor::CborType; use cbor::decoder::decode; use {CoseError, SignatureAlgorithm}; use util::get_sig_struct_bytes; use std::collections::BTreeMap; pub const COSE_SIGN_TAG: u64 = 98; /// The result of `decode_signature` holding a decoded COSE signature. #[derive(Debug)] pub struct CoseSignature { pub signature_type: SignatureAlgorithm, pub signature: Vec, pub signer_cert: Vec, pub certs: Vec>, pub to_verify: Vec, } pub const COSE_TYPE_ES256: i64 = -7; pub const COSE_TYPE_ES384: i64 = -35; pub const COSE_TYPE_ES512: i64 = -36; pub const COSE_TYPE_PS256: i64 = -37; pub const COSE_HEADER_ALG: u64 = 1; pub const COSE_HEADER_KID: u64 = 4; macro_rules! unpack { ($to:tt, $var:ident) => ( match *$var { CborType::$to(ref cbor_object) => { cbor_object } _ => return Err(CoseError::UnexpectedType), }; ) } fn get_map_value( map: &BTreeMap, key: &CborType, ) -> Result { match map.get(key) { Some(x) => Ok(x.clone()), _ => Err(CoseError::MissingHeader), } } /// Ensure that the referenced `CborType` is an empty map. fn ensure_empty_map(map: &CborType) -> Result<(), CoseError> { let unpacked = unpack!(Map, map); if !unpacked.is_empty() { return Err(CoseError::MalformedInput); } Ok(()) } // This syntax is a little unintuitive. Taken together, the two previous definitions essentially // mean: // // COSE_Sign = [ // protected : empty_or_serialized_map, // unprotected : header_map // payload : bstr / nil, // signatures : [+ COSE_Signature] // ] // // (COSE_Sign is an array. The first element is an empty or serialized map (in our case, it is // never expected to be empty). The second element is a map (it is expected to be empty. The third // element is a bstr or nil (it is expected to be nil). The fourth element is an array of // COSE_Signature.) // // COSE_Signature = [ // Headers, // signature : bstr // ] // // but again, unpacking this: // // COSE_Signature = [ // protected : empty_or_serialized_map, // unprotected : header_map // signature : bstr // ] fn decode_signature_struct( cose_signature: &CborType, payload: &[u8], protected_body_head: &CborType, ) -> Result { let cose_signature = unpack!(Array, cose_signature); if cose_signature.len() != 3 { return Err(CoseError::MalformedInput); } let protected_signature_header_serialized = &cose_signature[0]; let protected_signature_header_bytes = unpack!(Bytes, protected_signature_header_serialized); // Parse the protected signature header. let protected_signature_header = &match decode(protected_signature_header_bytes) { Err(_) => return Err(CoseError::DecodingFailure), Ok(value) => value, }; let protected_signature_header = unpack!(Map, protected_signature_header); if protected_signature_header.len() != 2 { return Err(CoseError::MalformedInput); } let signature_algorithm = get_map_value( protected_signature_header, &CborType::Integer(COSE_HEADER_ALG), )?; let signature_algorithm = match signature_algorithm { CborType::SignedInteger(val) => { match val { COSE_TYPE_ES256 => SignatureAlgorithm::ES256, COSE_TYPE_ES384 => SignatureAlgorithm::ES384, COSE_TYPE_ES512 => SignatureAlgorithm::ES512, COSE_TYPE_PS256 => SignatureAlgorithm::PS256, _ => return Err(CoseError::UnexpectedHeaderValue), } } _ => return Err(CoseError::UnexpectedType), }; let ee_cert = &get_map_value( protected_signature_header, &CborType::Integer(COSE_HEADER_KID), )?; let ee_cert = unpack!(Bytes, ee_cert).clone(); // The unprotected header section is expected to be an empty map. ensure_empty_map(&cose_signature[1])?; // Build signature structure to verify. let signature_bytes = &cose_signature[2]; let signature_bytes = unpack!(Bytes, signature_bytes).clone(); let sig_structure_bytes = get_sig_struct_bytes( protected_body_head.clone(), protected_signature_header_serialized.clone(), payload, ); // Read intermediate certificates from protected_body_head. // Any tampering of the protected header during transport will be detected // because it is input to the signature verification. // Note that a protected header has to be present and hold a kid with an // empty list of intermediate certificates. let protected_body_head_bytes = unpack!(Bytes, protected_body_head); let protected_body_head_map = &match decode(protected_body_head_bytes) { Ok(value) => value, Err(_) => return Err(CoseError::DecodingFailure), }; let protected_body_head_map = unpack!(Map, protected_body_head_map); if protected_body_head_map.len() != 1 { return Err(CoseError::MalformedInput); } let intermediate_certs_array = &get_map_value(protected_body_head_map, &CborType::Integer(COSE_HEADER_KID))?; let intermediate_certs = unpack!(Array, intermediate_certs_array); let mut certs: Vec> = Vec::new(); for cert in intermediate_certs { let cert = unpack!(Bytes, cert); certs.push(cert.clone()); } Ok(CoseSignature { signature_type: signature_algorithm, signature: signature_bytes, signer_cert: ee_cert, certs: certs, to_verify: sig_structure_bytes, }) } /// Decode COSE signature bytes and return a vector of `CoseSignature`. /// ///```rust,ignore /// COSE_Sign = [ /// Headers, /// payload : bstr / nil, /// signatures : [+ COSE_Signature] /// ] /// /// Headers = ( /// protected : empty_or_serialized_map, /// unprotected : header_map /// ) ///``` pub fn decode_signature(bytes: &[u8], payload: &[u8]) -> Result, CoseError> { // This has to be a COSE_Sign object, which is a tagged array. let tagged_cose_sign = match decode(bytes) { Err(_) => return Err(CoseError::DecodingFailure), Ok(value) => value, }; let cose_sign_array = match tagged_cose_sign { CborType::Tag(tag, cose_sign) => { if tag != COSE_SIGN_TAG { return Err(CoseError::UnexpectedTag); } match *cose_sign { CborType::Array(values) => values, _ => return Err(CoseError::UnexpectedType), } } _ => return Err(CoseError::UnexpectedType), }; if cose_sign_array.len() != 4 { return Err(CoseError::MalformedInput); } // The unprotected header section is expected to be an empty map. ensure_empty_map(&cose_sign_array[1])?; // The payload is expected to be Null (i.e. this is a detached signature). match cose_sign_array[2] { CborType::Null => {} _ => return Err(CoseError::UnexpectedType), }; let signatures = &cose_sign_array[3]; let signatures = unpack!(Array, signatures); // Decode COSE_Signatures. // There has to be at least one signature to make this a valid COSE signature. if signatures.len() < 1 { return Err(CoseError::MalformedInput); } let mut result = Vec::new(); for cose_signature in signatures { // cose_sign_array[0] holds the protected body header. let signature = decode_signature_struct(cose_signature, payload, &cose_sign_array[0])?; result.push(signature); } Ok(result) }