diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/cose/src/decoder.rs | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cose/src/decoder.rs')
-rw-r--r-- | third_party/rust/cose/src/decoder.rs | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/third_party/rust/cose/src/decoder.rs b/third_party/rust/cose/src/decoder.rs new file mode 100644 index 0000000000..90463d5dd2 --- /dev/null +++ b/third_party/rust/cose/src/decoder.rs @@ -0,0 +1,235 @@ +//! 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<u8>, + pub signer_cert: Vec<u8>, + pub certs: Vec<Vec<u8>>, + pub to_verify: Vec<u8>, +} + +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<CborType, CborType>, + key: &CborType, +) -> Result<CborType, CoseError> { + 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<CoseSignature, CoseError> { + 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<u8>> = 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<Vec<CoseSignature>, 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) +} |