From dec22c7750484c87ebc34426135362f24b20b3fc Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Mon, 15 Jan 2024 13:53:27 +0000 Subject: Upgrade coset to 0.3.6 This project was upgraded with external_updater. Usage: tools/external_updater/updater.sh update rust/crates/coset For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md Test: TreeHugger Change-Id: I1cba98672396da40c9ea47aad0ed7eca4d7c7f07 --- .cargo_vcs_info.json | 2 +- Android.bp | 6 +-- CHANGELOG.md | 7 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- Cargo.toml.orig | 2 +- METADATA | 21 ++++---- README.md | 4 +- src/common/mod.rs | 31 ++++++++++++ src/common/tests.rs | 66 ++++++++++++++++++++++++++ src/key/mod.rs | 22 ++++++++- src/key/tests.rs | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++- 12 files changed, 274 insertions(+), 23 deletions(-) diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 1b3ac45..2ca136f 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "90f5513741844bd5c5e0a0a751360c7edd0e8992" + "sha1": "46647ee89796d783fa09f0b19b184adda927caba" }, "path_in_vcs": "" } \ No newline at end of file diff --git a/Android.bp b/Android.bp index 316cf8c..9fa143c 100644 --- a/Android.bp +++ b/Android.bp @@ -23,7 +23,7 @@ rust_test { host_supported: true, crate_name: "coset", cargo_env_compat: true, - cargo_pkg_version: "0.3.5", + cargo_pkg_version: "0.3.6", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -47,7 +47,7 @@ rust_library { host_supported: true, crate_name: "coset", cargo_env_compat: true, - cargo_pkg_version: "0.3.5", + cargo_pkg_version: "0.3.6", srcs: ["src/lib.rs"], edition: "2018", features: [ @@ -70,7 +70,7 @@ rust_library_rlib { name: "libcoset_nostd", crate_name: "coset", cargo_env_compat: true, - cargo_pkg_version: "0.3.5", + cargo_pkg_version: "0.3.6", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ diff --git a/CHANGELOG.md b/CHANGELOG.md index b01d470..5fe755c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 0.3.6 - 2024-01-15 + +- Helpers for ordering of fields in a `COSE_Key`: + - Add `Label::cmp_canonical()` for RFC 7049 canonical ordering. + - Add `CborOrdering` enum to specify ordering. + - Add `CoseKey::canonicalize()` method to order fields. + ## 0.3.5 - 2023-09-29 - Add helper methods to create and verify detached signatures: diff --git a/Cargo.lock b/Cargo.lock index 8063a90..c7efb02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "coset" -version = "0.3.5" +version = "0.3.6" dependencies = [ "ciborium", "ciborium-io", diff --git a/Cargo.toml b/Cargo.toml index 540a482..020f39f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2018" name = "coset" -version = "0.3.5" +version = "0.3.6" authors = [ "David Drysdale ", "Paul Crowley ", diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 1e35011..94529ad 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "coset" -version = "0.3.5" +version = "0.3.6" authors = ["David Drysdale ", "Paul Crowley "] edition = "2018" license = "Apache-2.0" diff --git a/METADATA b/METADATA index 9e30903..5cbaf3c 100644 --- a/METADATA +++ b/METADATA @@ -5,19 +5,16 @@ name: "coset" description: "Set of types for supporting COSE" third_party { - url { - type: HOMEPAGE - value: "https://crates.io/crates/coset" - } - url { - type: ARCHIVE - value: "https://static.crates.io/crates/coset/coset-0.3.5.crate" - } - version: "0.3.5" license_type: NOTICE last_upgrade_date { - year: 2023 - month: 9 - day: 29 + year: 2024 + month: 1 + day: 15 + } + homepage: "https://crates.io/crates/coset" + identifier { + type: "Archive" + value: "https://static.crates.io/crates/coset/coset-0.3.6.crate" + version: "0.3.6" } } diff --git a/README.md b/README.md index fb51cd9..f30ad4e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # COSET -[![Docs](https://img.shields.io/badge/docs-rust-brightgreen?style=for-the-badge)](https://google.github.io/coset) +[![Docs](https://img.shields.io/badge/docs-rust-brightgreen?style=for-the-badge)](https://docs.rs/coset) [![CI Status](https://img.shields.io/github/actions/workflow/status/google/coset/ci.yml?branch=main&color=blue&style=for-the-badge)](https://github.com/google/coset/actions?query=workflow%3ACI) [![codecov](https://img.shields.io/codecov/c/github/google/coset?style=for-the-badge)](https://codecov.io/gh/google/coset) @@ -8,7 +8,7 @@ This crate holds a set of Rust types for working with CBOR Object Signing and En [RFC 8152](https://tools.ietf.org/html/rfc8152). It builds on the core [CBOR](https://tools.ietf.org/html/rfc7049) parsing functionality from the [`ciborium` crate](https://docs.rs/ciborium). -See [crate docs](https://google.github.io/coset/rust/coset/index.html), or the [signature +See [crate docs](https://docs.rs/coset), or the [signature example](examples/signature.rs) for documentation on how to use the code. **This repo is under construction** and so details of the API and the code may change without warning. diff --git a/src/common/mod.rs b/src/common/mod.rs index b94ec31..336db50 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -240,6 +240,37 @@ impl PartialOrd for Label { } } +impl Label { + /// Alternative ordering for `Label`, using the canonical ordering criteria from RFC 7049 + /// section 3.9 (where the primary sorting criterion is the length of the encoded form), rather + /// than the ordering given by RFC 8949 section 4.2.1 (lexicographic ordering of encoded form). + /// + /// # Panics + /// + /// Panics if either `Label` fails to serialize. + pub fn cmp_canonical(&self, other: &Self) -> Ordering { + let encoded_self = self.clone().to_vec().unwrap(); /* safe: documented */ + let encoded_other = other.clone().to_vec().unwrap(); /* safe: documented */ + if encoded_self.len() != encoded_other.len() { + // Shorter encoding sorts first. + encoded_self.len().cmp(&encoded_other.len()) + } else { + // Both encode to the same length, sort lexicographically on encoded form. + encoded_self.cmp(&encoded_other) + } + } +} + +/// Indicate which ordering should be applied to CBOR values. +pub enum CborOrdering { + /// Order values lexicographically, as per RFC 8949 section 4.2.1 (Core Deterministic Encoding + /// Requirements) + Lexicographic, + /// Order values by encoded length, then by lexicographic ordering of encoded form, as per RFC + /// 7049 section 3.9 (Canonical CBOR) / RFC 8949 section 4.2.3 (Length-First Map Key Ordering). + LengthFirstLexicographic, +} + impl AsCborValue for Label { fn from_cbor_value(value: Value) -> Result { match value { diff --git a/src/common/tests.rs b/src/common/tests.rs index e7b59aa..39ef41c 100644 --- a/src/common/tests.rs +++ b/src/common/tests.rs @@ -56,6 +56,7 @@ fn test_label_sort() { let pairs = vec![ (Label::Int(0x1234), Label::Text("a".to_owned())), (Label::Int(0x1234), Label::Text("ab".to_owned())), + (Label::Int(0x12345678), Label::Text("ab".to_owned())), (Label::Int(0), Label::Text("ab".to_owned())), (Label::Int(-1), Label::Text("ab".to_owned())), (Label::Int(0), Label::Int(10)), @@ -99,6 +100,71 @@ fn test_label_sort() { } } +#[test] +fn test_label_canonical_sort() { + // Pairs of `Label`s with the "smaller" first, as per RFC7049 "canonical" ordering. + let pairs = vec![ + (Label::Text("a".to_owned()), Label::Int(0x1234)), // different than above + (Label::Int(0x1234), Label::Text("ab".to_owned())), + (Label::Text("ab".to_owned()), Label::Int(0x12345678)), // different than above + (Label::Int(0), Label::Text("ab".to_owned())), + (Label::Int(-1), Label::Text("ab".to_owned())), + (Label::Int(0), Label::Int(10)), + (Label::Int(0), Label::Int(-10)), + (Label::Int(10), Label::Int(-1)), + (Label::Int(-1), Label::Int(-2)), + (Label::Int(0x12), Label::Int(0x1234)), + (Label::Int(0x99), Label::Int(0x1234)), + (Label::Int(0x1234), Label::Int(0x1235)), + (Label::Text("a".to_owned()), Label::Text("ab".to_owned())), + (Label::Text("aa".to_owned()), Label::Text("ab".to_owned())), + ]; + for (left, right) in pairs.into_iter() { + let value_cmp = left.cmp_canonical(&right); + + let left_data = left.clone().to_vec().unwrap(); + let right_data = right.clone().to_vec().unwrap(); + + let len_cmp = left_data.len().cmp(&right_data.len()); + let data_cmp = left_data.cmp(&right_data); + let reverse_cmp = right.cmp_canonical(&left); + let equal_cmp = left.cmp_canonical(&left); + + assert_eq!( + value_cmp, + Ordering::Less, + "{:?} (encoded: {}) < {:?} (encoded: {})", + left, + hex::encode(&left_data), + right, + hex::encode(&right_data) + ); + if len_cmp != Ordering::Equal { + assert_eq!( + len_cmp, + Ordering::Less, + "{:?}={} < {:?}={} by len", + left, + hex::encode(&left_data), + right, + hex::encode(&right_data) + ); + } else { + assert_eq!( + data_cmp, + Ordering::Less, + "{:?}={} < {:?}={} by data", + left, + hex::encode(&left_data), + right, + hex::encode(&right_data) + ); + } + assert_eq!(reverse_cmp, Ordering::Greater, "{:?} > {:?}", right, left); + assert_eq!(equal_cmp, Ordering::Equal, "{:?} = {:?}", left, left); + } +} + #[test] fn test_label_decode_fail() { let tests = [ diff --git a/src/key/mod.rs b/src/key/mod.rs index 07ee7a7..0df7d31 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -18,7 +18,7 @@ use crate::{ cbor::value::Value, - common::AsCborValue, + common::{AsCborValue, CborOrdering}, iana, iana::EnumI64, util::{to_cbor_array, ValueTryAs}, @@ -88,6 +88,26 @@ pub struct CoseKey { pub params: Vec<(Label, Value)>, } +impl CoseKey { + /// Re-order the contents of the key so that the contents will be emitted in one of the standard + /// CBOR sorted orders. + pub fn canonicalize(&mut self, ordering: CborOrdering) { + // The keys that are represented as named fields CBOR-encode as single bytes 0x01 - 0x05, + // which sort before any other CBOR values (other than 0x00) in either sorting scheme: + // - In length-first sorting, a single byte sorts before anything multi-byte and 1-5 sorts + // before any other value. + // - In encoded-lexicographic sorting, there are no valid CBOR-encoded single values that + // start with a byte in the range 0x01 - 0x05 other than the values 1-5. + // So we only need to sort the `params`. + match ordering { + CborOrdering::Lexicographic => self.params.sort_by(|l, r| l.0.cmp(&r.0)), + CborOrdering::LengthFirstLexicographic => { + self.params.sort_by(|l, r| l.0.cmp_canonical(&r.0)) + } + } + } +} + impl crate::CborSerializable for CoseKey {} const KTY: Label = Label::Int(iana::KeyParameter::Kty as i64); diff --git a/src/key/tests.rs b/src/key/tests.rs index ada3673..ccbf20f 100644 --- a/src/key/tests.rs +++ b/src/key/tests.rs @@ -15,7 +15,7 @@ //////////////////////////////////////////////////////////////////////////////// use super::*; -use crate::{cbor::value::Value, iana, util::expect_err, CborSerializable}; +use crate::{cbor::value::Value, iana, util::expect_err, CborOrdering, CborSerializable}; use alloc::{borrow::ToOwned, string::ToString, vec}; #[test] @@ -742,3 +742,133 @@ fn test_key_builder_core_param_panic() { .param(1, Value::Null) .build(); } + +#[test] +fn test_key_canonicalize() { + struct TestCase { + key_data: &'static str, // hex + rfc7049_key: CoseKey, + rfc8949_key: CoseKey, + rfc7049_data: Option<&'static str>, // hex, `None` indicates same as `key_data` + rfc8949_data: Option<&'static str>, // hex, `None` indicates same as `key_data` + } + let tests = [ + TestCase { + key_data: concat!( + "a2", // 2-map + "01", "01", // 1 (kty) => OKP + "03", "26", // 3 (alg) => -7 + ), + rfc7049_key: CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::ES256)), + ..Default::default() + }, + rfc8949_key: CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::ES256)), + ..Default::default() + }, + rfc7049_data: None, + rfc8949_data: None, + }, + TestCase { + key_data: concat!( + "a2", // 2-map + "03", "26", // 3 (alg) => -7 + "01", "01", // 1 (kty) => OKP + ), + rfc7049_key: CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::ES256)), + ..Default::default() + }, + rfc8949_key: CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::ES256)), + ..Default::default() + }, + rfc7049_data: Some(concat!( + "a2", // 2-map + "01", "01", // 1 (kty) => OKP + "03", "26", // 3 (alg) => -7 + )), + rfc8949_data: Some(concat!( + "a2", // 2-map + "01", "01", // 1 (kty) => OKP + "03", "26", // 3 (alg) => -7 + )), + }, + TestCase { + key_data: concat!( + "a4", // 4-map + "03", "26", // 3 (alg) => -7 + "1904d2", "01", // 1234 => 1 + "01", "01", // 1 (kty) => OKP + "6161", "01", // "a" => 1 + ), + // "a" encodes shorter than 1234, so appears first + rfc7049_key: CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::ES256)), + params: vec![ + (Label::Text("a".to_string()), Value::Integer(1.into())), + (Label::Int(1234), Value::Integer(1.into())), + ], + ..Default::default() + }, + // 1234 encodes with leading byte 0x19, so appears before a tstr + rfc8949_key: CoseKey { + kty: KeyType::Assigned(iana::KeyType::OKP), + alg: Some(Algorithm::Assigned(iana::Algorithm::ES256)), + params: vec![ + (Label::Int(1234), Value::Integer(1.into())), + (Label::Text("a".to_string()), Value::Integer(1.into())), + ], + ..Default::default() + }, + rfc7049_data: Some(concat!( + "a4", // 4-map + "01", "01", // 1 (kty) => OKP + "03", "26", // 3 (alg) => -7 + "6161", "01", // "a" => 1 + "1904d2", "01", // 1234 => 1 + )), + rfc8949_data: Some(concat!( + "a4", // 4-map + "01", "01", // 1 (kty) => OKP + "03", "26", // 3 (alg) => -7 + "1904d2", "01", // 1234 => 1 + "6161", "01", // "a" => 1 + )), + }, + ]; + for testcase in tests { + let key_data = hex::decode(testcase.key_data).unwrap(); + let mut key = CoseKey::from_slice(&key_data) + .unwrap_or_else(|e| panic!("Failed to deserialize {}: {e:?}", testcase.key_data)); + + // Canonicalize according to RFC 7049. + key.canonicalize(CborOrdering::LengthFirstLexicographic); + assert_eq!( + key, testcase.rfc7049_key, + "Mismatch for {}", + testcase.key_data + ); + let got = testcase.rfc7049_key.to_vec().unwrap(); + let want = testcase.rfc7049_data.unwrap_or(testcase.key_data); + assert_eq!(hex::encode(got), want, "Mismatch for {}", testcase.key_data); + + // Canonicalize according to RFC 8949. + key.canonicalize(CborOrdering::Lexicographic); + assert_eq!( + key, testcase.rfc8949_key, + "Mismatch for {}", + testcase.key_data + ); + + let got = testcase.rfc8949_key.to_vec().unwrap(); + let want = testcase.rfc8949_data.unwrap_or(testcase.key_data); + assert_eq!(hex::encode(got), want, "Mismatch for {}", testcase.key_data); + } +} -- cgit v1.2.3