summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cose/src/decoder.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/cose/src/decoder.rs
parentInitial commit. (diff)
downloadfirefox-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.rs235
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)
+}