aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Drysdale <drysdale@google.com>2024-01-15 16:40:05 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2024-01-15 16:40:05 +0000
commita2203f26023ac08b5113a1874956ae14e68e33b2 (patch)
tree1eb96805c2d92c18c00731a6914964329a5e0bc6
parent0f91a4a9641d56a3c95777c82184c3ef4cfb5574 (diff)
parent69cc2134b2b58ab57242024a8382d788d58b43c9 (diff)
downloadcoset-a2203f26023ac08b5113a1874956ae14e68e33b2.tar.gz
Upgrade coset to 0.3.6 am: dec22c7750 am: 9b0ba0e259 am: 69cc2134b2
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/coset/+/2910262 Change-Id: I14b814aa32091178406b1aa2f1b229edde37a391 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Android.bp6
-rw-r--r--CHANGELOG.md7
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--Cargo.toml.orig2
-rw-r--r--METADATA21
-rw-r--r--README.md4
-rw-r--r--src/common/mod.rs31
-rw-r--r--src/common/tests.rs66
-rw-r--r--src/key/mod.rs22
-rw-r--r--src/key/tests.rs132
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 <drysdale@google.com>",
"Paul Crowley <paulcrowley@google.com>",
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 <drysdale@google.com>", "Paul Crowley <paulcrowley@google.com>"]
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<Self> {
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)),
@@ -100,6 +101,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 = [
("43010203", "expected int/tstr"),
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);
+ }
+}