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