Upgrade rust/crates/coset to 0.3.2 am: 4b3d58e0c6
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/coset/+/2139132
Change-Id: I35498a005a58b900c2790a1ecd36a7217c575676
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 2fcab48..badef4a 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "818bd1e3b3ff44e9b179532d2b4d942ea34bd1a1"
+ "sha1": "1c107ab343537ec70c4786948c386ba7eee1188a"
},
"path_in_vcs": ""
}
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3430ad1..4e9ee3f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,6 +49,26 @@
override: true
- run: cargo test --workspace -- --nocapture
+ examples:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust:
+ - stable
+ - beta
+ - nightly-2022-01-01
+ steps:
+ - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
+ with:
+ submodules: true
+ - uses: actions-rs/toolchain@63eb9591781c46a70274cb3ebdf190fce92702e8 # v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ components: rustfmt
+ override: true
+ - run: for eg in `ls examples/*.rs | xargs basename --suffix=.rs`; do cargo run --example ${eg}; done
+
no_std:
name: Build for a no_std target
runs-on: ubuntu-latest
diff --git a/Android.bp b/Android.bp
index 5b6ae3f..c26900e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,12 +23,16 @@
host_supported: true,
crate_name: "coset",
cargo_env_compat: true,
- cargo_pkg_version: "0.3.1",
+ cargo_pkg_version: "0.3.2",
srcs: ["src/lib.rs"],
edition: "2018",
rustlibs: [
"libciborium",
"libciborium_io",
],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
vendor_available: true,
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8d4915..a3657e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Change Log
+## 0.3.2 - 2022-04-02
+
+- Add basic [CWT](https://datatracker.ietf.org/doc/html/rfc8392) support in `cwt` module, via the `ClaimsSet` type.
+
## 0.3.1 - 2022-02-23
- Implement `Display` for `CoseError`.
diff --git a/Cargo.lock b/Cargo.lock
index d257ebb..f7425cf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -31,7 +31,7 @@
[[package]]
name = "coset"
-version = "0.3.1"
+version = "0.3.2"
dependencies = [
"ciborium",
"ciborium-io",
diff --git a/Cargo.toml b/Cargo.toml
index eb4661b..f47e6ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,13 +12,20 @@
[package]
edition = "2018"
name = "coset"
-version = "0.3.1"
-authors = ["David Drysdale <drysdale@google.com>", "Paul Crowley <paulcrowley@google.com>"]
+version = "0.3.2"
+authors = [
+ "David Drysdale <drysdale@google.com>",
+ "Paul Crowley <paulcrowley@google.com>",
+]
description = "Set of types for supporting COSE"
-keywords = ["cryptography", "cose"]
+keywords = [
+ "cryptography",
+ "cose",
+]
categories = ["cryptography"]
license = "Apache-2.0"
repository = "https://github.com/google/coset"
+
[dependencies.ciborium]
version = "^0.2.0"
default-features = false
@@ -26,5 +33,6 @@
[dependencies.ciborium-io]
version = "^0.2.0"
features = ["alloc"]
+
[dev-dependencies.hex]
version = "^0.4.2"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 9555fdf..fe96fe2 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "coset"
-version = "0.3.1"
+version = "0.3.2"
authors = ["David Drysdale <drysdale@google.com>", "Paul Crowley <paulcrowley@google.com>"]
edition = "2018"
license = "Apache-2.0"
diff --git a/METADATA b/METADATA
index c3bb0c2..a0d1f4a 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/coset/coset-0.3.1.crate"
+ value: "https://static.crates.io/crates/coset/coset-0.3.2.crate"
}
- version: "0.3.1"
+ version: "0.3.2"
license_type: NOTICE
last_upgrade_date {
year: 2022
- month: 3
- day: 1
+ month: 6
+ day: 28
}
}
diff --git a/examples/cwt.rs b/examples/cwt.rs
new file mode 100644
index 0000000..f9f113b
--- /dev/null
+++ b/examples/cwt.rs
@@ -0,0 +1,89 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+//! Example program demonstrating signed CWT processing.
+use coset::{cwt, iana, CborSerializable, CoseError};
+
+#[derive(Copy, Clone)]
+struct FakeSigner {}
+
+// Use a fake signer/verifier (to avoid pulling in lots of dependencies).
+impl FakeSigner {
+ fn sign(&self, data: &[u8]) -> Vec<u8> {
+ data.to_vec()
+ }
+
+ fn verify(&self, sig: &[u8], data: &[u8]) -> Result<(), String> {
+ if sig != self.sign(data) {
+ Err("failed to verify".to_owned())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+fn main() -> Result<(), CoseError> {
+ // Build a fake signer/verifier (to avoid pulling in lots of dependencies).
+ let signer = FakeSigner {};
+ let verifier = signer;
+
+ // Build a CWT ClaimsSet (cf. RFC 8392 A.3).
+ let claims = cwt::ClaimsSetBuilder::new()
+ .issuer("coap://as.example.com".to_string())
+ .subject("erikw".to_string())
+ .audience("coap://light.example.com".to_string())
+ .expiration_time(cwt::Timestamp::WholeSeconds(1444064944))
+ .not_before(cwt::Timestamp::WholeSeconds(1443944944))
+ .issued_at(cwt::Timestamp::WholeSeconds(1443944944))
+ .cwt_id(vec![0x0b, 0x71])
+ .build();
+ let aad = b"";
+
+ // Build a `CoseSign1` object.
+ let protected = coset::HeaderBuilder::new()
+ .algorithm(iana::Algorithm::ES256)
+ .build();
+ let unprotected = coset::HeaderBuilder::new()
+ .key_id(b"AsymmetricECDSA256".to_vec())
+ .build();
+ let sign1 = coset::CoseSign1Builder::new()
+ .protected(protected)
+ .unprotected(unprotected)
+ .payload(claims.clone().to_vec()?)
+ .create_signature(aad, |pt| signer.sign(pt))
+ .build();
+
+ // Serialize to bytes.
+ let sign1_data = sign1.to_vec()?;
+
+ // At the receiving end, deserialize the bytes back to a `CoseSign1` object.
+ let sign1 = coset::CoseSign1::from_slice(&sign1_data)?;
+
+ // Real code would:
+ // - Use the key ID to identify the relevant local key.
+ // - Check that the key is of the same type as `sign1.protected.algorithm`.
+
+ // Check the signature.
+ let result = sign1.verify_signature(aad, |sig, data| verifier.verify(sig, data));
+ println!("Signature verified: {:?}.", result);
+ assert!(result.is_ok());
+
+ // Now it's safe to parse the payload.
+ let recovered_claims = cwt::ClaimsSet::from_slice(&sign1.payload.unwrap())?;
+
+ assert_eq!(recovered_claims, claims);
+ Ok(())
+}
diff --git a/examples/signature.rs b/examples/signature.rs
index 4512df7..cf8b91a 100644
--- a/examples/signature.rs
+++ b/examples/signature.rs
@@ -15,7 +15,7 @@
////////////////////////////////////////////////////////////////////////////////
//! Example program demonstrating signature creation.
-use coset::{iana, CborSerializable};
+use coset::{iana, CborSerializable, CoseError};
#[derive(Copy, Clone)]
struct FakeSigner {}
@@ -35,7 +35,7 @@
}
}
-fn main() {
+fn main() -> Result<(), CoseError> {
// Build a fake signer/verifier (to avoid pulling in lots of dependencies).
let signer = FakeSigner {};
let verifier = signer;
@@ -56,7 +56,7 @@
.build();
// Serialize to bytes.
- let sign1_data = sign1.to_vec().unwrap();
+ let sign1_data = sign1.to_vec()?;
println!(
"'{}' + '{}' => {}",
String::from_utf8_lossy(pt),
@@ -65,7 +65,7 @@
);
// At the receiving end, deserialize the bytes back to a `CoseSign1` object.
- let mut sign1 = coset::CoseSign1::from_slice(&sign1_data).unwrap();
+ let mut sign1 = coset::CoseSign1::from_slice(&sign1_data)?;
// Check the signature, which needs to have the same `aad` provided.
let result = sign1.verify_signature(aad, |sig, data| verifier.verify(sig, data));
@@ -85,7 +85,9 @@
// Changing a protected header invalidates the signature.
sign1.protected.header.content_type = Some(coset::ContentType::Text("text/plain".to_owned()));
+ sign1.protected.original_data = None;
assert!(sign1
.verify_signature(aad, |sig, data| verifier.verify(sig, data))
.is_err());
+ Ok(())
}
diff --git a/patches/std.diff b/patches/std.diff
index 3f9d738..244b78c 100644
--- a/patches/std.diff
+++ b/patches/std.diff
@@ -1,8 +1,8 @@
diff --git a/src/lib.rs b/src/lib.rs
-index 2a8ceb3..3c46fcf 100644
+index 4ce9c93..a800c89 100644
--- a/src/lib.rs
+++ b/src/lib.rs
-@@ -100,6 +100,9 @@
+@@ -100,6 +100,10 @@
#![deny(rustdoc::broken_intra_doc_links)]
extern crate alloc;
@@ -12,5 +12,4 @@
+
/// Re-export of the `ciborium` crate used for underlying CBOR encoding.
pub use ciborium as cbor;
-
-
+
\ No newline at end of file
diff --git a/src/cwt/mod.rs b/src/cwt/mod.rs
new file mode 100644
index 0000000..fe5e410
--- /dev/null
+++ b/src/cwt/mod.rs
@@ -0,0 +1,186 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+//! CBOR Web Token functionality.
+
+use crate::{
+ cbor::value::Value,
+ common::AsCborValue,
+ iana,
+ iana::EnumI64,
+ util::{cbor_type_error, ValueTryAs},
+ CoseError,
+};
+use alloc::{collections::BTreeSet, string::String, vec::Vec};
+use core::convert::TryInto;
+
+#[cfg(test)]
+mod tests;
+
+/// Number of seconds since UNIX epoch.
+#[derive(Clone, Debug, PartialEq)]
+pub enum Timestamp {
+ WholeSeconds(i64),
+ FractionalSeconds(f64),
+}
+
+impl AsCborValue for Timestamp {
+ fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+ match value {
+ Value::Integer(i) => Ok(Timestamp::WholeSeconds(i.try_into()?)),
+ Value::Float(f) => Ok(Timestamp::FractionalSeconds(f)),
+ _ => cbor_type_error(&value, "int/float"),
+ }
+ }
+ fn to_cbor_value(self) -> Result<Value, CoseError> {
+ Ok(match self {
+ Timestamp::WholeSeconds(t) => Value::Integer(t.into()),
+ Timestamp::FractionalSeconds(f) => Value::Float(f),
+ })
+ }
+}
+
+/// Claim name.
+pub type ClaimName = crate::RegisteredLabelWithPrivate<iana::CwtClaimName>;
+
+/// Structure representing a CWT Claims Set.
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct ClaimsSet {
+ /// Issuer
+ pub issuer: Option<String>,
+ /// Subject
+ pub subject: Option<String>,
+ /// Audience
+ pub audience: Option<String>,
+ /// Expiration Time
+ pub expiration_time: Option<Timestamp>,
+ /// Not Before
+ pub not_before: Option<Timestamp>,
+ /// Issued At
+ pub issued_at: Option<Timestamp>,
+ /// CWT ID
+ pub cwt_id: Option<Vec<u8>>,
+ /// Any additional claims.
+ pub rest: Vec<(ClaimName, Value)>,
+}
+
+impl crate::CborSerializable for ClaimsSet {}
+
+const ISS: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Iss);
+const SUB: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Sub);
+const AUD: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Aud);
+const EXP: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Exp);
+const NBF: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Nbf);
+const IAT: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Iat);
+const CTI: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Cti);
+
+impl AsCborValue for ClaimsSet {
+ fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+ let m = match value {
+ Value::Map(m) => m,
+ v => return cbor_type_error(&v, "map"),
+ };
+
+ let mut claims = Self::default();
+ let mut seen = BTreeSet::new();
+ for (n, value) in m.into_iter() {
+ // The `ciborium` CBOR library does not police duplicate map keys, so do it here.
+ let name = ClaimName::from_cbor_value(n)?;
+ if seen.contains(&name) {
+ return Err(CoseError::DuplicateMapKey);
+ }
+ seen.insert(name.clone());
+ match name {
+ x if x == ISS => claims.issuer = Some(value.try_as_string()?),
+ x if x == SUB => claims.subject = Some(value.try_as_string()?),
+ x if x == AUD => claims.audience = Some(value.try_as_string()?),
+ x if x == EXP => claims.expiration_time = Some(Timestamp::from_cbor_value(value)?),
+ x if x == NBF => claims.not_before = Some(Timestamp::from_cbor_value(value)?),
+ x if x == IAT => claims.issued_at = Some(Timestamp::from_cbor_value(value)?),
+ x if x == CTI => claims.cwt_id = Some(value.try_as_bytes()?),
+ name => claims.rest.push((name, value)),
+ }
+ }
+ Ok(claims)
+ }
+
+ fn to_cbor_value(self) -> Result<Value, CoseError> {
+ let mut map = Vec::new();
+ if let Some(iss) = self.issuer {
+ map.push((ISS.to_cbor_value()?, Value::Text(iss)));
+ }
+ if let Some(sub) = self.subject {
+ map.push((SUB.to_cbor_value()?, Value::Text(sub)));
+ }
+ if let Some(aud) = self.audience {
+ map.push((AUD.to_cbor_value()?, Value::Text(aud)));
+ }
+ if let Some(exp) = self.expiration_time {
+ map.push((EXP.to_cbor_value()?, exp.to_cbor_value()?));
+ }
+ if let Some(nbf) = self.not_before {
+ map.push((NBF.to_cbor_value()?, nbf.to_cbor_value()?));
+ }
+ if let Some(iat) = self.issued_at {
+ map.push((IAT.to_cbor_value()?, iat.to_cbor_value()?));
+ }
+ if let Some(cti) = self.cwt_id {
+ map.push((CTI.to_cbor_value()?, Value::Bytes(cti)));
+ }
+ for (label, value) in self.rest {
+ map.push((label.to_cbor_value()?, value));
+ }
+ Ok(Value::Map(map))
+ }
+}
+
+/// Builder for [`ClaimsSet`] objects.
+#[derive(Default)]
+pub struct ClaimsSetBuilder(ClaimsSet);
+
+impl ClaimsSetBuilder {
+ builder! {ClaimsSet}
+ builder_set_optional! {issuer: String}
+ builder_set_optional! {subject: String}
+ builder_set_optional! {audience: String}
+ builder_set_optional! {expiration_time: Timestamp}
+ builder_set_optional! {not_before: Timestamp}
+ builder_set_optional! {issued_at: Timestamp}
+ builder_set_optional! {cwt_id: Vec<u8>}
+
+ /// Set a claim name:value pair.
+ ///
+ /// # Panics
+ ///
+ /// This function will panic if it used to set a claim with name from the range [1, 7].
+ #[must_use]
+ pub fn claim(mut self, name: iana::CwtClaimName, value: Value) -> Self {
+ if name.to_i64() >= iana::CwtClaimName::Iss.to_i64()
+ && name.to_i64() <= iana::CwtClaimName::Cti.to_i64()
+ {
+ panic!("claim() method used to set core claim"); // safe: invalid input
+ }
+ self.0.rest.push((ClaimName::Assigned(name), value));
+ self
+ }
+
+ /// Set a claim name:value pair where the `name` is text.
+ #[must_use]
+ pub fn text_claim(mut self, name: String, value: Value) -> Self {
+ self.0.rest.push((ClaimName::Text(name), value));
+ self
+ }
+}
diff --git a/src/cwt/tests.rs b/src/cwt/tests.rs
new file mode 100644
index 0000000..7487d1a
--- /dev/null
+++ b/src/cwt/tests.rs
@@ -0,0 +1,210 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+use super::*;
+use crate::{cbor::value::Value, iana, iana::WithPrivateRange, util::expect_err, CborSerializable};
+use alloc::{borrow::ToOwned, vec};
+
+#[test]
+fn test_cwt_encode() {
+ let tests = vec![
+ (
+ ClaimsSet {
+ issuer: Some("abc".to_owned()),
+ ..Default::default()
+ },
+ concat!(
+ "a1", // 1-map
+ "01", "63", "616263" // 1 (iss) => 3-tstr
+ ),
+ ),
+ (ClaimsSetBuilder::new().build(), concat!("a0")),
+ (
+ ClaimsSetBuilder::new()
+ .issuer("aaa".to_owned())
+ .subject("bb".to_owned())
+ .audience("c".to_owned())
+ .expiration_time(Timestamp::WholeSeconds(0x100))
+ .not_before(Timestamp::WholeSeconds(0x200))
+ .issued_at(Timestamp::WholeSeconds(0x10))
+ .cwt_id(vec![1, 2, 3, 4])
+ .build(),
+ concat!(
+ "a7", // 7-map
+ "01", "63", "616161", // 1 (iss) => 3-tstr
+ "02", "62", "6262", // 2 (sub) => 2-tstr
+ "03", "61", "63", // 3 (aud) => 1-tstr
+ "04", "19", "0100", // 4 (exp) => uint
+ "05", "19", "0200", // 5 (nbf) => uint
+ "06", "10", // 6 (iat) => uint
+ "07", "44", "01020304" // 7 => bstr
+ ),
+ ),
+ (
+ ClaimsSetBuilder::new()
+ .claim(
+ iana::CwtClaimName::Cnf,
+ Value::Map(vec![(Value::Integer(0.into()), Value::Integer(0.into()))]),
+ )
+ .build(),
+ concat!(
+ "a1", // 1-map
+ "08", "a1", "00", "00"
+ ),
+ ),
+ (
+ ClaimsSetBuilder::new()
+ .text_claim("aa".to_owned(), Value::Integer(0.into()))
+ .build(),
+ concat!(
+ "a1", // 1-map
+ "62", "6161", "00",
+ ),
+ ),
+ (
+ ClaimsSetBuilder::new()
+ .expiration_time(Timestamp::FractionalSeconds(1.5))
+ .build(),
+ concat!(
+ "a1", // 1-map
+ "04", // 4 (exp) =>
+ // Note: ciborium serializes floats as the smallest float type that
+ // will parse back to the original f64! As a result, 1.5 is encoded
+ // as an f16.
+ "f9", "3e00",
+ ),
+ ),
+ ];
+ for (i, (claims, claims_data)) in tests.iter().enumerate() {
+ let got = claims.clone().to_vec().unwrap();
+ assert_eq!(*claims_data, hex::encode(&got), "case {}", i);
+
+ let got = ClaimsSet::from_slice(&got).unwrap();
+ assert_eq!(*claims, got);
+ }
+}
+
+#[test]
+fn test_cwt_decode_fail() {
+ let tests = vec![
+ (
+ concat!(
+ "81", // 1-arr
+ "01",
+ ),
+ "expected map",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "01", "08", // 1 (iss) => int (invalid value type)
+ ),
+ "expected tstr",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "02", "08", // 2 (sub) => int (invalid value type)
+ ),
+ "expected tstr",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "03", "08", // 3 (aud) => int (invalid value type)
+ ),
+ "expected tstr",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "04", "40", // 4 (exp) => bstr (invalid value type)
+ ),
+ "expected int/float",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "05", "40", // 5 (nbf) => bstr (invalid value type)
+ ),
+ "expected int/float",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "06", "40", // 6 (iat) => bstr (invalid value type)
+ ),
+ "expected int/float",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "07", "01", // 5 (cti) => uint (invalid value type)
+ ),
+ "expected bstr",
+ ),
+ (
+ concat!(
+ "a1", // 1-map
+ "07", "40", // 5 (cti) => 0-bstr
+ "06", "01", // 6 (iat) => 1
+ ),
+ "extraneous data",
+ ),
+ (
+ concat!(
+ "a2", // 1-map
+ "07", "40", // 5 (cti) => 0-bstr
+ "07", "40", // 5 (cti) => 0-bstr
+ ),
+ "duplicate map key",
+ ),
+ ];
+ for (claims_data, err_msg) in tests.iter() {
+ let data = hex::decode(claims_data).unwrap();
+ let result = ClaimsSet::from_slice(&data);
+ expect_err(result, err_msg);
+ }
+}
+
+#[test]
+fn test_cwt_is_private() {
+ assert!(!iana::CwtClaimName::is_private(1));
+ assert!(iana::CwtClaimName::is_private(-500_000));
+}
+
+#[test]
+#[should_panic]
+fn test_cwt_claims_builder_core_param_panic() {
+ // Attempting to set a core claim (in range [1,7]) via `.claim()` panics.
+ let _claims = ClaimsSetBuilder::new()
+ .claim(iana::CwtClaimName::Iss, Value::Null)
+ .build();
+}
+
+#[test]
+fn test_cwt_dup_claim() {
+ // Set a duplicate map key.
+ let claims = ClaimsSetBuilder::new()
+ .claim(iana::CwtClaimName::AceProfile, Value::Integer(1.into()))
+ .claim(iana::CwtClaimName::AceProfile, Value::Integer(2.into()))
+ .build();
+ // Encoding succeeds.
+ let data = claims.to_vec().unwrap();
+ // But an attempt to parse the encoded data fails.
+ let result = ClaimsSet::from_slice(&data);
+ expect_err(result, "duplicate map key");
+}
diff --git a/src/iana/mod.rs b/src/iana/mod.rs
index 41c2ef8..3702014 100644
--- a/src/iana/mod.rs
+++ b/src/iana/mod.rs
@@ -20,6 +20,7 @@
//! - <https://www.iana.org/assignments/cose/cose.xhtml>
//! - <https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml>
//! - <https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats>
+//! - <https://www.iana.org/assignments/cwt/cwt.xhtml>
#[cfg(test)]
mod tests;
@@ -743,3 +744,54 @@
VndOmaLwm2mCbor: 11544,
}
}
+
+iana_registry! {
+ /// CBOR Web Token (CWT) Claims
+ /// From IANA registry <https://www.iana.org/assignments/cwt/cwt.xhtml>
+ /// as of 2021-10-21.
+ CwtClaimName {
+ /// Health certificate ("hcert": map).
+ Hcert: -260,
+ /// Challenge nonce ("EUPHNonce": bstr).
+ EuphNonce: -259,
+ /// Signing prefix for multi-app restricted operating environment ("EATMAROEPrefix": bstr).
+ EatMaroePrefix: -258,
+ /// FIDO Device Onboarding EAT ("EAT-FDO": array).
+ EatFido: -257,
+ /// Reserved value.
+ Reserved: 0,
+ /// Issuer ("iss": tstr).
+ Iss: 1,
+ /// Subject ("sub": tstr)
+ Sub: 2,
+ /// Audience ("aud": tstr)
+ Aud: 3,
+ /// Expiration Time, as seconds since UNIX epoch ("exp": int/float)
+ Exp: 4,
+ /// Not Before, as seconds since UNIX epoch ("nbf": int/float)
+ Nbf: 5,
+ /// Issued at, as seconds since UNIX epoch ("iat": int/float)
+ Iat: 6,
+ /// CWT ID ("cti": bstr)
+ Cti: 7,
+ /// Confirmation ("cnf": map)
+ Cnf: 8,
+ /// Scope of an access token ("scope": bstr/tstr)
+ Scope: 9,
+ /// The ACE profile a token is supposed to be used with ("ace_profile": int)
+ AceProfile: 38,
+ /// The client-nonce sent to the AS by the RS via the client ("cnonce": bstr)
+ CNonce: 39,
+ /// The expiration time of a token measured from when it was received at the RS in seconds ("exi": int)
+ Exi: 40,
+ }
+}
+
+/// Integer values for CWT claims below this value are reserved for private use.
+pub const CWT_CLAIM_PRIVATE_USE_MAX: i64 = -65536;
+
+impl WithPrivateRange for CwtClaimName {
+ fn is_private(i: i64) -> bool {
+ i < CWT_CLAIM_PRIVATE_USE_MAX
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index a0af18a..a800c89 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -110,6 +110,7 @@
#[macro_use]
pub(crate) mod util;
+pub mod cwt;
#[macro_use]
pub mod iana;
diff --git a/src/util/mod.rs b/src/util/mod.rs
index d12337c..5608a4f 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -21,7 +21,7 @@
common::AsCborValue,
CoseError, Result,
};
-use alloc::{boxed::Box, vec::Vec};
+use alloc::{boxed::Box, string::String, vec::Vec};
#[cfg(test)]
mod tests;
@@ -71,6 +71,9 @@
/// Extractor for [`Value::Tag`]
fn try_as_tag(self) -> Result<(u64, Box<Value>)>;
+
+ /// Extractor for [`Value::Text`]
+ fn try_as_string(self) -> Result<String>;
}
impl ValueTryAs for Value {
@@ -131,6 +134,14 @@
cbor_type_error(&self, "tag")
}
}
+
+ fn try_as_string(self) -> Result<String> {
+ if let Value::Text(s) = self {
+ Ok(s)
+ } else {
+ cbor_type_error(&self, "tstr")
+ }
+ }
}
/// Convert each item of an iterator to CBOR, and wrap the lot in