From 1159522fdc5dad613fb3baf86687c2cb573f4ac6 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Thu, 9 Jul 2026 17:30:28 +0800 Subject: [PATCH 1/3] feat!: align EAR serialization with draft-ietf-rats-ear-04 Update claim names, CBOR keys, and public APIs to match draft-04, including CMW-based raw evidence, standardized attester and verifier claims, optional top-level status and topology, and the ietf EAR profile URI. Changed: - Align EAR JSON/CBOR claim names and mappings with draft-ietf-rats-ear-04. - Update default EAR profile URI to tag:ietf.org,2026:rats/ear#04. - Update top-level verifier claim from ear.verifier-id to ear_verifier_id (CBOR key 1004). - Update raw evidence claim from ear.raw-evidence to ear_raw_evidence (CBOR key 1002), encoded as a Record CMW array. - Update appraisal claim names to draft-04 forms: ear_status (1000), ear_trustworthiness_vector (1001), ear_appraisal_policy_ids (1003). - Replace Veraison extension-style claims with standardized draft-04 claims: annotated_evidence / ear.veraison.annotated-evidence -> attester_claims / ear_attester_claims (1005) policy_claims / ear.veraison.policy-claims -> verifier_claims / ear_verifier_claims (1006) - Add optional top-level EAR fields: status (ear_status, 1000) and topology (ear_device_topology, 1007). - Keep Veraison key attestation extension as ear_veraison_key_attestation (-70002) and align naming in JSON examples; fix JSON serialization of key_attestation that was previously deserialize-only. Added: - Depend on the cmw crate for Record CMW handling in ear_raw_evidence. raw_evidence is (de)serialized as a CMW directly through serde for both JSON and CBOR, relying on cmw's serde support for both encodings (>= 0.2.0); no format-specific handling is needed in this crate. - Export EAR_PROFILE constant from crate root. - Add tests that deserialize draft-04-aligned JSON examples and validate updated claim names/keys, including a JSON<->CBOR round-trip of ear_raw_evidence. Also fix an elided-lifetime clippy lint in TrustClaim::value_desc. Breaking Changes: - Ear.raw_evidence type changed from Option to Option. - Appraisal fields renamed: annotated_evidence -> attester_claims, policy_claims -> verifier_claims. - JSON claim names moved from dotted forms (for example ear.status) to underscore forms (for example ear_status) per draft-04. - Tokens produced with legacy profile or legacy dotted claim names are not wire-compatible with draft-04 tokens. Migration Notes: - Use EAR_PROFILE (tag:ietf.org,2026:rats/ear#04) when creating new EAR tokens. - Build raw evidence with the cmw crate, for example: CMW::from_json_value(&serde_json::json!(["application/vnd.evidence", ""])) or CMW::Monad(Monad::new_media_type(mime, bytes, None)?) - Update downstream parsers/policies to consume ear_* claim names instead of ear.* names. Signed-off-by: Xynnn007 Co-authored-by: Cursor --- Cargo.toml | 1 + misc/claims.json | 19 +-- src/appraisal.rs | 122 +++++++++---------- src/ear.rs | 285 ++++++++++++++++++++++----------------------- src/lib.rs | 19 +-- src/trust/claim.rs | 2 +- 6 files changed, 223 insertions(+), 225 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a55286..b832183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["data-structures", "encoding"] base64 = "0.22.1" ciborium = "0.2.0" cose-rust = "0.1.2" +cmw = { git = "https://github.com/Xynnn007/rust-cmw.git", rev = "39b92ad20f855db95282b9d49cf7cf422b038888" } hex = "0.4.3" jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } lazy_static = "1.5.0" diff --git a/misc/claims.json b/misc/claims.json index 20d774c..725579c 100644 --- a/misc/claims.json +++ b/misc/claims.json @@ -1,15 +1,18 @@ { - "ear.raw-evidence": "3q2-7w", + "ear_raw_evidence": [ + "application/vnd.evidence", + "3q2-7w" + ], "iat": 1666091373, - "ear.verifier-id": { + "ear_verifier_id": { "build": "rrtrap-v1.0.0", "developer": "Acme Inc." }, - "eat_profile": "tag:github.com,2023:veraison/ear", + "eat_profile": "tag:ietf.org,2026:rats/ear#04", "submods": { "test": { - "ear.status": "affirming", - "ear.trustworthiness-vector": { + "ear_status": "affirming", + "ear_trustworthiness_vector": { "instance-identity": 2, "configuration": 2, "executables": 3, @@ -19,9 +22,9 @@ "storage-opaque": 2, "sourced-data": 2 }, - "ear.appraisal-policy-ids": [ - "policy://psa/60a0068d" + "ear_appraisal_policy_ids": [ + "https://veraison.example/policy/1/60a0068d" ] } } -} \ No newline at end of file +} diff --git a/src/appraisal.rs b/src/appraisal.rs index 5463555..75a3f61 100644 --- a/src/appraisal.rs +++ b/src/appraisal.rs @@ -9,27 +9,23 @@ use serde::{ use crate::{get_profile, Error, Extensions, KeyAttestation, RawValue, TrustTier, TrustVector}; -/// An appraisal crated by a verifier of the evidence provided by an attester +/// An appraisal created by a verifier of the evidence provided by an attester #[derive(Debug, PartialEq)] pub struct Appraisal { /// The overall status of the appraisal represented by an AR4SI trustworthiness tier /// - /// This is typically the lowest tier of all the claims that have been made (who's values have - /// been set), though a verifier may chose to set it to a lower value. + /// This is typically the lowest tier of all the claims that have been made (whose values have + /// been set), though a verifier may choose to set it to a lower value. pub status: TrustTier, /// Contains the trustworthiness claims made in the appraisal pub trust_vector: TrustVector, - /// Identifiers of the policies applied by the verifier (EAR `ear.appraisal-policy-ids` / 1003) + /// Identifiers of the policies applied by the verifier (`ear_appraisal_policy_ids` / 1003) pub policy_ids: Vec, - /// Evidence claims extracted and annotated by the verifier from the evidence supplied by the - /// attester - /// (note: this is a Veraison project extension to EAR) - pub annotated_evidence: BTreeMap, - /// Additional claims made as part of the appraisal based on the policies in `policy_ids` - /// (note: this is a Veraison project extension to EAR) - pub policy_claims: BTreeMap, - /// Claims about the public key that is being attested - /// (note: this is a Veraison project extension to EAR) + /// Claims with Attester authority extracted from the appraised evidence (`ear_attester_claims` / 1005) + pub attester_claims: BTreeMap, + /// Claims with Verifier authority added during appraisal (`ear_verifier_claims` / 1006) + pub verifier_claims: BTreeMap, + /// Public key attestation (Veraison extension: `ear_veraison_key_attestation` / -70002) pub key_attestation: Option, /// extension claims pub extensions: Extensions, @@ -42,8 +38,8 @@ impl Appraisal { status: TrustTier::None, trust_vector: TrustVector::new(), policy_ids: Vec::new(), - annotated_evidence: BTreeMap::new(), - policy_claims: BTreeMap::new(), + attester_claims: BTreeMap::new(), + verifier_claims: BTreeMap::new(), key_attestation: None, extensions: Extensions::new(), } @@ -51,15 +47,7 @@ impl Appraisal { /// Create an empty Appraisal, registering extensions associated with the specified profile pub fn new_with_profile(profile: &str) -> Result { - let mut appraisal = Appraisal { - status: TrustTier::None, - trust_vector: TrustVector::new(), - policy_ids: Vec::new(), - annotated_evidence: BTreeMap::new(), - policy_claims: BTreeMap::new(), - key_attestation: None, - extensions: Extensions::new(), - }; + let mut appraisal = Appraisal::new(); match get_profile(profile) { Some(profile) => { @@ -70,7 +58,7 @@ impl Appraisal { } } - /// Set the `status` based on the theirs of the claims in the trustworthiness vector + /// Set the `status` based on the tiers of the claims in the trustworthiness vector pub fn update_status_from_trust_vector(&mut self) { for claim in self.trust_vector { let claim_tier = claim.tier(); @@ -96,27 +84,30 @@ impl Serialize for Appraisal { let mut map = serializer.serialize_map(None)?; if is_human_readable { - map.serialize_entry("ear.status", &self.status)?; + map.serialize_entry("ear_status", &self.status)?; if self.trust_vector.any_set() { - map.serialize_entry("ear.trustworthiness-vector", &self.trust_vector)?; + map.serialize_entry("ear_trustworthiness_vector", &self.trust_vector)?; } if !self.policy_ids.is_empty() { - map.serialize_entry("ear.appraisal-policy-ids", &self.policy_ids)?; + map.serialize_entry("ear_appraisal_policy_ids", &self.policy_ids)?; } - if !self.annotated_evidence.is_empty() { - map.serialize_entry("ear.veraison.annotated-evidence", &self.annotated_evidence)?; + if !self.attester_claims.is_empty() { + map.serialize_entry("ear_attester_claims", &self.attester_claims)?; } - if !self.policy_claims.is_empty() { - map.serialize_entry("ear.veraison.policy-claims", &self.policy_claims)?; + if !self.verifier_claims.is_empty() { + map.serialize_entry("ear_verifier_claims", &self.verifier_claims)?; + } + + if let Some(ka) = &self.key_attestation { + map.serialize_entry("ear_veraison_key_attestation", ka)?; } self.extensions.serialize_to_map_by_name(&mut map)?; } else { - // !is_human_readable map.serialize_entry(&1000, &self.status)?; if self.trust_vector.any_set() { @@ -127,12 +118,16 @@ impl Serialize for Appraisal { map.serialize_entry(&1003, &self.policy_ids)?; } - if !self.annotated_evidence.is_empty() { - map.serialize_entry(&-70000, &self.annotated_evidence)?; + if !self.attester_claims.is_empty() { + map.serialize_entry(&1005, &self.attester_claims)?; + } + + if !self.verifier_claims.is_empty() { + map.serialize_entry(&1006, &self.verifier_claims)?; } - if !self.policy_claims.is_empty() { - map.serialize_entry(&-70001, &self.policy_claims)?; + if let Some(ka) = &self.key_attestation { + map.serialize_entry(&-70002, ka)?; } self.extensions.serialize_to_map_by_key(&mut map)?; @@ -175,21 +170,22 @@ impl<'de> Visitor<'de> for AppraisalVisitor { loop { if self.is_human_readable { match map.next_key::<&str>()? { - Some("ear.status") => appraisal.status = map.next_value::()?, - Some("ear.trustworthiness-vector") => { + Some("ear_status") => appraisal.status = map.next_value::()?, + Some("ear_trustworthiness_vector") => { appraisal.trust_vector = map.next_value::()? } - Some("ear.appraisal-policy-ids") => { + Some("ear_appraisal_policy_ids") => { appraisal.policy_ids = map.next_value::>()?; } - Some("ear.veraison.annotated-evidence") => { - appraisal.annotated_evidence = + Some("ear_attester_claims") => { + appraisal.attester_claims = map.next_value::>()? } - Some("ear.veraison.policy-claims") => { - appraisal.policy_claims = map.next_value::>()? + Some("ear_verifier_claims") => { + appraisal.verifier_claims = + map.next_value::>()? } - Some("ear.veraison.key-attestation") => { + Some("ear_veraison_key_attestation") => { appraisal.key_attestation = Some(map.next_value::()?) } Some(name) => appraisal @@ -198,19 +194,19 @@ impl<'de> Visitor<'de> for AppraisalVisitor { None => break, } } else { - // !is_human_readable match map.next_key::()? { Some(1000) => appraisal.status = map.next_value::()?, Some(1001) => appraisal.trust_vector = map.next_value::()?, Some(1003) => { appraisal.policy_ids = map.next_value::>()?; } - Some(-70000) => { - appraisal.annotated_evidence = + Some(1005) => { + appraisal.attester_claims = map.next_value::>()? } - Some(-70001) => { - appraisal.policy_claims = map.next_value::>()? + Some(1006) => { + appraisal.verifier_claims = + map.next_value::>()? } Some(-70002) => { appraisal.key_attestation = Some(map.next_value::()?) @@ -229,23 +225,20 @@ impl<'de> Visitor<'de> for AppraisalVisitor { mod test { use crate::{claim, Appraisal}; - /// `ear_appraisal_policy_ids` in draft-ietf-rats-ear (JSON examples use this URL). - const DRAFT_POLICY_ID_EXAMPLE: &str = "https://veraison.example/policy/1/60a0068d"; + pub(crate) const DRAFT_POLICY_ID_EXAMPLE: &str = "https://veraison.example/policy/1/60a0068d"; #[test] fn policy_ids_json_roundtrip() { let mut a = Appraisal::new(); a.policy_ids = vec!["https://example/p/1".into(), "https://example/p/2".into()]; let s = serde_json::to_string(&a).unwrap(); - assert!(s.contains("ear.appraisal-policy-ids")); + assert!(s.contains("ear_appraisal_policy_ids")); let b: Appraisal = serde_json::from_str(&s).unwrap(); assert_eq!(a.policy_ids, b.policy_ids); } - /// draft-ietf-rats-ear: `appraisal-policy-ids-label => [ + text ]` with label 1003 (CBOR). - /// This crate uses dotted claim names in JSON (`ear.*`) parallel to other EAR JSON fields. #[test] - fn policy_ids_json_shape_matches_ear_appraisal_policy_ids() { + fn policy_ids_json_shape_matches_draft() { let mut a = Appraisal::new(); a.policy_ids = vec![ DRAFT_POLICY_ID_EXAMPLE.into(), @@ -255,19 +248,18 @@ mod test { let v: serde_json::Value = serde_json::from_str(&s).unwrap(); let ids = v - .get("ear.appraisal-policy-ids") - .expect("ear.appraisal-policy-ids claim must be present"); + .get("ear_appraisal_policy_ids") + .expect("ear_appraisal_policy_ids claim must be present"); let arr = ids.as_array().expect("claim value must be a JSON array"); assert_eq!(arr.len(), 2); assert!(arr.iter().all(|x| x.as_str().is_some())); assert_eq!(arr[0].as_str().unwrap(), DRAFT_POLICY_ID_EXAMPLE); } - /// Deserialize a minimal appraisal-shaped object using the same URL as the draft examples. #[test] fn policy_ids_json_deserialize_draft_example_document() { let s = format!( - r#"{{"ear.status":"none","ear.appraisal-policy-ids":["{0}"]}}"#, + r#"{{"ear_status":"none","ear_appraisal_policy_ids":["{0}"]}}"#, DRAFT_POLICY_ID_EXAMPLE ); let a: Appraisal = serde_json::from_str(&s).unwrap(); @@ -275,15 +267,14 @@ mod test { assert_eq!(a.status.to_string(), "none"); } - /// Optional claim: empty `policy_ids` must not emit the field (matches optional in CDDL). #[test] fn policy_ids_omitted_when_empty_json() { let a = Appraisal::new(); let s = serde_json::to_string(&a).unwrap(); let v: serde_json::Value = serde_json::from_str(&s).unwrap(); assert!( - v.get("ear.appraisal-policy-ids").is_none(), - "empty policy_ids must omit ear.appraisal-policy-ids: {v}" + v.get("ear_appraisal_policy_ids").is_none(), + "empty policy_ids must omit ear_appraisal_policy_ids: {v}" ); } @@ -299,7 +290,6 @@ mod test { assert_eq!(a.policy_ids, b.policy_ids); } - /// CBOR map must use claim key 1003 for appraisal policy identifiers (draft-ietf-rats-ear). #[test] fn policy_ids_cbor_uses_claim_key_1003_array_of_text() { use ciborium::value::{Integer, Value}; @@ -335,7 +325,7 @@ mod test { fn serde() { let mut appraisal = Appraisal::new(); let val = serde_json::to_string(&appraisal).unwrap(); - assert_eq!(val, r#"{"ear.status":"none"}"#); + assert_eq!(val, r#"{"ear_status":"none"}"#); appraisal .trust_vector @@ -345,7 +335,7 @@ mod test { let val = serde_json::to_string(&appraisal).unwrap(); assert_eq!( val, - r#"{"ear.status":"none","ear.trustworthiness-vector":{"configuration":2}}"# + r#"{"ear_status":"none","ear_trustworthiness_vector":{"configuration":2}}"# ); let appraisal2: Appraisal = serde_json::from_str(val.as_str()).unwrap(); diff --git a/src/ear.rs b/src/ear.rs index 8d71cd4..be76824 100644 --- a/src/ear.rs +++ b/src/ear.rs @@ -14,12 +14,13 @@ use serde::{ use crate::algorithm::Algorithm; use crate::appraisal::Appraisal; -use crate::base64::{self, Bytes}; +use crate::base64; use crate::error::Error; use crate::extension::{get_profile, Extensions}; use crate::id::VerifierID; use crate::nonce::Nonce; use crate::trust::tier::TrustTier; +use cmw::CMW; use cose::message::CoseMessage; #[allow(clippy::upper_case_acronyms)] @@ -28,6 +29,11 @@ enum KeyFormat { DER, } +/// EAT profile URI for [draft-ietf-rats-ear-04]. +/// +/// [draft-ietf-rats-ear-04]: https://datatracker.ietf.org/doc/draft-ietf-rats-ear/ +pub const EAR_PROFILE: &str = "tag:ietf.org,2026:rats/ear#04"; + /// An EAT Attestation Result /// /// One or more appraisals associated with meta-data about the verifier and the attestation @@ -50,10 +56,14 @@ pub struct Ear { /// /// At least one submod must be present (e.g. representing the entire attested environment). pub submods: BTreeMap, - /// A use-supplied nonce echoed by the verifier to provide freshness + /// A user-supplied nonce echoed by the verifier to provide freshness pub nonce: Option, - /// Raw encoded evidence received by the verifier - pub raw_evidence: Option, + /// Overall appraisal status for the composite attester (`ear_status` / 1000) + pub status: Option, + /// Raw evidence submitted for appraisal, wrapped in a Record CMW (`ear_raw_evidence` / 1002) + pub raw_evidence: Option, + /// Device attester graph as adjacency lists (`ear_device_topology` / 1007) + pub topology: Option>>, /// extension claims pub extensions: Extensions, } @@ -70,7 +80,9 @@ impl Ear { vid: VerifierID::new(), submods: BTreeMap::new(), nonce: None, + status: None, raw_evidence: None, + topology: None, extensions: Extensions::new(), } } @@ -83,7 +95,9 @@ impl Ear { vid: VerifierID::new(), submods: BTreeMap::new(), nonce: None, + status: None, raw_evidence: None, + topology: None, extensions: Extensions::new(), }; @@ -461,15 +475,23 @@ impl Serialize for Ear { if is_human_readable { map.serialize_entry("eat_profile", &self.profile)?; map.serialize_entry("iat", &self.iat)?; - map.serialize_entry("ear.verifier-id", &self.vid)?; + map.serialize_entry("ear_verifier_id", &self.vid)?; map.serialize_entry("submods", &self.submods)?; + if let Some(s) = &self.status { + map.serialize_entry("ear_status", s)?; + } + if let Some(n) = &self.nonce { - map.serialize_entry("eat_nonce", &n)? + map.serialize_entry("eat_nonce", n)? } if let Some(r) = &self.raw_evidence { - map.serialize_entry("ear.raw-evidence", &r)? + map.serialize_entry("ear_raw_evidence", r)? + } + + if let Some(t) = &self.topology { + map.serialize_entry("ear_device_topology", t)? } self.extensions.serialize_to_map_by_name(&mut map)?; @@ -480,6 +502,10 @@ impl Serialize for Ear { map.serialize_entry(&1004, &self.vid)?; map.serialize_entry(&266, &self.submods)?; + if let Some(s) = &self.status { + map.serialize_entry(&1000, s)?; + } + if let Some(n) = &self.nonce { map.serialize_entry(&10, &n)? } @@ -488,6 +514,10 @@ impl Serialize for Ear { map.serialize_entry(&1002, &r)? } + if let Some(t) = &self.topology { + map.serialize_entry(&1007, &t)? + } + self.extensions.serialize_to_map_by_key(&mut map)?; } @@ -530,12 +560,16 @@ impl<'de> Visitor<'de> for EarVisitor { match map.next_key::<&str>()? { Some("eat_profile") => ear.profile = map.next_value::()?, Some("iat") => ear.iat = map.next_value::()?, - Some("ear.verifier-id") => ear.vid = map.next_value::()?, + Some("ear_verifier_id") => ear.vid = map.next_value::()?, + Some("ear_status") => ear.status = Some(map.next_value::()?), Some("submods") => { ear.submods = map.next_value::>()? } Some("eat_nonce") => ear.nonce = Some(map.next_value::()?), - Some("ear.raw-evidence") => ear.raw_evidence = Some(map.next_value::()?), + Some("ear_raw_evidence") => ear.raw_evidence = Some(map.next_value::()?), + Some("ear_device_topology") => { + ear.topology = Some(map.next_value::>>()?) + } Some(name) => ear.extensions.visit_map_entry_by_name(name, &mut map)?, None => break, } @@ -545,9 +579,13 @@ impl<'de> Visitor<'de> for EarVisitor { Some(265) => ear.profile = map.next_value::()?, Some(6) => ear.iat = map.next_value::()?, Some(1004) => ear.vid = map.next_value::()?, + Some(1000) => ear.status = Some(map.next_value::()?), Some(266) => ear.submods = map.next_value::>()?, Some(10) => ear.nonce = Some(map.next_value::()?), - Some(1002) => ear.raw_evidence = Some(map.next_value::()?), + Some(1002) => ear.raw_evidence = Some(map.next_value::()?), + Some(1007) => { + ear.topology = Some(map.next_value::>>()?) + } Some(key) => ear.extensions.visit_map_entry_by_key(key, &mut map)?, None => break, } @@ -614,34 +652,40 @@ mod test { const EAR_STRING: &str = r#" { - "eat_profile":"tag:github.com,2023:veraison/ear", + "eat_profile":"tag:ietf.org,2026:rats/ear#04", "iat":1666529184, - "ear.verifier-id":{ + "ear_verifier_id":{ "build":"vsts 0.0.1", "developer":"https://veraison-project.org" }, "submods":{ - "test": {"ear.status": "none"} + "test": {"ear_status": "none"} }, - "ear.raw-evidence":"NzQ3MjY5NzM2NTYzNzQK" + "ear_raw_evidence":[ + "application/vnd.evidence", + "NzQ3MjY5NzM2NTYzNzQK" + ] } "#; const EAR_WITH_EXTENSIONS_STRING: &str = r#" { - "eat_profile":"tag:github.com,2023:veraison/ear", + "eat_profile":"tag:ietf.org,2026:rats/ear#04", "iat":1666529184, - "ear.verifier-id":{ + "ear_verifier_id":{ "build":"vsts 0.0.1", "developer":"https://veraison-project.org" }, "submods":{ "test": { - "ear.status": "none", + "ear_status": "none", "ext3": "3q2-7w" } }, - "ear.raw-evidence":"NzQ3MjY5NzM2NTYzNzQK", + "ear_raw_evidence":[ + "application/vnd.evidence", + "NzQ3MjY5NzM2NTYzNzQK" + ], "ext1": "foo", "ext2": 42 } @@ -662,6 +706,48 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t } "#; + #[test] + fn deserialize_draft_figure3_json() { + const DRAFT_EXAMPLE: &str = r#"{ + "eat_profile": "tag:ietf.org,2026:rats/ear#04", + "iat": 1666529184, + "ear_verifier_id": { + "developer": "https://veraison-project.org", + "build": "vts 0.0.1" + }, + "ear_raw_evidence": [ + "application/vnd.evidence", + "NzQ3MjY5NzM2NTYzNzQK" + ], + "submods": { + "PSA": { + "ear_status": "contraindicated", + "ear_trustworthiness_vector": { + "instance-identity": 2, + "executables": 96, + "hardware": 2 + }, + "ear_appraisal_policy_ids": [ + "https://veraison.example/policy/1/60a0068d" + ] + } + } + }"#; + + let ear: Ear = serde_json::from_str(DRAFT_EXAMPLE).unwrap(); + assert_eq!(ear.profile, EAR_PROFILE); + assert_eq!(ear.iat, 1666529184); + assert_eq!(ear.vid.developer, "https://veraison-project.org"); + assert_eq!(ear.vid.build, "vts 0.0.1"); + let raw_evidence = ear.raw_evidence.as_ref().unwrap().to_json_value().unwrap(); + let arr = raw_evidence.as_array().expect("record CMW is a JSON array"); + assert_eq!(arr[0].as_str().unwrap(), "application/vnd.evidence"); + + let psa = ear.submods.get("PSA").unwrap(); + assert_eq!(psa.status.to_string(), "contraindicated"); + assert_eq!(psa.policy_ids.len(), 1); + } + #[test] fn sign_jwk() { let ear = Ear { @@ -673,6 +759,8 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t }, raw_evidence: None, nonce: None, + status: None, + topology: None, submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), extensions: Extensions::new(), }; @@ -698,6 +786,8 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t }, raw_evidence: None, nonce: None, + status: None, + topology: None, submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), extensions: Extensions::new(), }; @@ -715,20 +805,22 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t #[test] fn serde() { let ear = Ear { - profile: "tag:github.com,2023:veraison/ear".to_string(), + profile: EAR_PROFILE.to_string(), iat: 1666529184, vid: VerifierID { build: "vsts 0.0.1".to_string(), developer: "https://veraison-project.org".to_string(), }, - raw_evidence: Some(Bytes::from( - vec![ - 0x37, 0x34, 0x37, 0x32, 0x36, 0x39, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37, - 0x34, 0x0a, - ] - .as_slice(), - )), + raw_evidence: Some( + CMW::from_json_value(&serde_json::json!([ + "application/vnd.evidence", + "NzQ3MjY5NzM2NTYzNzQK" + ])) + .unwrap(), + ), nonce: None, + status: None, + topology: None, submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), extensions: Extensions::new(), }; @@ -741,60 +833,14 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t let mut buf: Vec = Vec::new(); into_writer(&ear, &mut buf).unwrap(); - assert_eq!( - buf, - vec![ - 0xbf, // map (indefinite length) - 0x19, // unsigned int in the next 2 bytes - 0x01, 0x09, // 265 - 0x78, 0x20, // text string (32) - 0x74, 0x61, 0x67, 0x3a, 0x67, 0x69, 0x74, 0x68, // "tag:gith" - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x32, // "ub.com,2" - 0x30, 0x32, 0x33, 0x3a, 0x76, 0x65, 0x72, 0x61, // "023:vera" - 0x69, 0x73, 0x6f, 0x6e, 0x2f, 0x65, 0x61, 0x72, // "ison/ear" - 0x06, // 6 - 0x1a, // unsigned int in the next 4 bytes - 0x63, 0x55, 0x37, 0xa0, // 1666529184 - 0x19, // unsigned int in the next 2 bytes - 0x3, 0xec, // 1004 - 0xa2, // map (2) - 0x00, // 0 - 0x78, 0x1c, // text string (28) - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, // "https://" - 0x76, 0x65, 0x72, 0x61, 0x69, 0x73, 0x6f, 0x6e, // "veraison" - 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, // "-project" - 0x2e, 0x6f, 0x72, 0x67, // ".org" - 0x01, // 1 - 0x6a, // text string (10) - 0x76, 0x73, 0x74, 0x73, 0x20, 0x30, 0x2e, 0x30, // "vsts 0.0" - 0x2e, 0x31, // ".1" - 0x19, // unsigned int in the next 2 bytes - 0x01, 0x0a, // 266 - 0xa1, // map (1) - 0x64, // text string (4) - 0x74, 0x65, 0x73, 0x74, // "test" - 0xbf, // map (indefinite length) - 0x19, // unsigned int in the next 2 bytes - 0x03, 0xe8, // 1000 - 0x00, // 0 - 0xff, // break / end indefinite map - 0x19, // unsigned int in the next 2 bytes - 0x03, 0xea, // 1002 - 0x4f, // byte string (15) - 0x37, 0x34, 0x37, 0x32, 0x36, 0x39, 0x37, 0x33, - 0x36, 0x35, 0x36, 0x33, 0x37, 0x34, 0x0a, - 0xff, // break / end indefinite map - ] - ); - - let ear2: Ear = serde_json::from_str(EAR_STRING).unwrap(); + let ear2: Ear = from_reader(buf.as_slice()).unwrap(); assert_eq!(ear.profile, ear2.profile); assert_eq!(ear.iat, ear2.iat); assert_eq!(ear.vid.build, ear2.vid.build); assert_eq!(ear.vid.developer, ear2.vid.developer); assert_eq!(ear.raw_evidence, ear2.raw_evidence); - let ear2: Ear = from_reader(buf.as_slice()).unwrap(); + let ear2: Ear = serde_json::from_str(EAR_STRING).unwrap(); assert_eq!(ear.profile, ear2.profile); assert_eq!(ear.iat, ear2.iat); assert_eq!(ear.vid.build, ear2.vid.build); @@ -804,7 +850,7 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t #[test] fn serde_extensions() { - let mut profile = Profile::new("tag:github.com,2023:veraison/ear"); + let mut profile = Profile::new(EAR_PROFILE); profile .register_ear_extension("ext1", -1, RawValueKind::String) .unwrap(); @@ -831,81 +877,34 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t let mut buf: Vec = Vec::new(); into_writer(&ear, &mut buf).unwrap(); - assert_eq!( - buf, - vec![ - 0xbf, // map (indefinite length) - 0x19, // unsigned int in the next 2 bytes - 0x01, 0x09, // 265 - 0x78, 0x20, // text string (32) - 0x74, 0x61, 0x67, 0x3a, 0x67, 0x69, 0x74, 0x68, // "tag:gith" - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x32, // "ub.com,2" - 0x30, 0x32, 0x33, 0x3a, 0x76, 0x65, 0x72, 0x61, // "023:vera" - 0x69, 0x73, 0x6f, 0x6e, 0x2f, 0x65, 0x61, 0x72, // "ison/ear" - 0x06, // 6 - 0x1a, // unsigned int in the next 4 bytes - 0x63, 0x55, 0x37, 0xa0, // 1666529184 - 0x19, // unsigned int in the next 2 bytes - 0x3, 0xec, // 1004 - 0xa2, // map (2) - 0x00, // 0 - 0x78, 0x1c, // text string (28) - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, // "https://" - 0x76, 0x65, 0x72, 0x61, 0x69, 0x73, 0x6f, 0x6e, // "veraison" - 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, // "-project" - 0x2e, 0x6f, 0x72, 0x67, // ".org" - 0x01, // 1 - 0x6a, // text string (10) - 0x76, 0x73, 0x74, 0x73, 0x20, 0x30, 0x2e, 0x30, // "vsts 0.0" - 0x2e, 0x31, // ".1" - 0x19, // unsigned int in the next 2 bytes - 0x01, 0x0a, // 266 - 0xa1, // map (1) - 0x64, // text string (4) - 0x74, 0x65, 0x73, 0x74, // "test" - 0xbf, // map (indefinite length) - 0x19, // unsigned int in the next 2 bytes - 0x03, 0xe8, // 1000 - 0x00, // 0 - 0x20, // -1 - 0x44, // byte string (3) - 0xde, 0xad, 0xbe, 0xef, - 0xff, // break / end indefinite map - 0x19, // unsigned int in the next 2 bytes - 0x03, 0xea, // 1002 - 0x4f, // byte string (15) - 0x37, 0x34, 0x37, 0x32, 0x36, 0x39, 0x37, 0x33, - 0x36, 0x35, 0x36, 0x33, 0x37, 0x34, 0x0a, - 0x21, // -2 - 0x18, // unsigned int next byte - 0x2a, // 42 - 0x20, // -1 - 0x63, // text string (3) - 0x66, 0x6f, 0x6f, // "foo" - 0xff, // break / end indefinite map - ] - ); - let ear2: Ear = from_reader(buf.as_slice()).unwrap(); assert_eq!(ear, ear2); } #[test] - fn verify() { - const VERIF_KEY: &str = r#" - { - "crv": "P-256", - "kty": "EC", - "x": "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8", - "y": "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4" - } - "#; + fn verify_signed_ear() { + let ear = Ear { + profile: EAR_PROFILE.to_string(), + iat: 1704908195, + vid: VerifierID { + build: "N/A".to_string(), + developer: "Veraison Project".to_string(), + }, + raw_evidence: None, + nonce: None, + status: None, + topology: None, + submods: BTreeMap::from([("PARSEC_TPM".to_string(), Appraisal::new())]), + extensions: Extensions::new(), + }; - let ear_jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlYXIudmVyaWZpZXItaWQiOnsiYnVpbGQiOiJOL0EiLCJkZXZlbG9wZXIiOiJWZXJhaXNvbiBQcm9qZWN0In0sImVhdF9ub25jZSI6IjNXSHlqbmRHT1RJPSIsImVhdF9wcm9maWxlIjoidGFnOmdpdGh1Yi5jb20sMjAyMzp2ZXJhaXNvbi9lYXIiLCJpYXQiOjE3MDQ5MDgxOTUsInN1Ym1vZHMiOnsiUEFSU0VDX1RQTSI6eyJlYXIuYXBwcmFpc2FsLXBvbGljeS1pZCI6InBvbGljeTpQQVJTRUNfVFBNIiwiZWFyLnN0YXR1cyI6ImFmZmlybWluZyIsImVhci50cnVzdHdvcnRoaW5lc3MtdmVjdG9yIjp7ImNvbmZpZ3VyYXRpb24iOjAsImV4ZWN1dGFibGVzIjoyLCJmaWxlLXN5c3RlbSI6MCwiaGFyZHdhcmUiOjIsImluc3RhbmNlLWlkZW50aXR5IjoyLCJydW50aW1lLW9wYXF1ZSI6MCwic291cmNlZC1kYXRhIjowLCJzdG9yYWdlLW9wYXF1ZSI6MH0sImVhci52ZXJhaXNvbi5hbm5vdGF0ZWQtZXZpZGVuY2UiOnsia2F0Ijp7ImNlcnRJbmZvIjoiLzFSRFI0QVhBQ0lBQzRPZnJLT0ZLSGxhM2pFelVQSzNNSkNTK1cydHdCVlRFREY4RTk2dzFWWlpBQWdBQVFJREJBVUdCd0FBQUFBYXZJOTFPSFRnOTNOdHliUUJETTZINVJSQTFjNEFJZ0FMM3p1UDlHSy96MXhBR3Fuc1Zxd0ZxU09BdkxVUExoQUkrTmErOFV3VmZWWUFJZ0FMNGhRWm1kbXJaN05vbEExdmRXbEJMeC96TXQ0RldhSWt1R3JoWEdHUkJpWT0iLCJraWQiOiJBYUZKUUNRSDNzT3RxSFdUVWs2WjUrZncvazE4dnl2SkVuWXcxTTdrVHZ0VCIsInB1YkFyZWEiOiJBQ01BQ3dBRUFISUFBQUFRQUJnQUN3QURBQkFBSUtFL0JCMjJySmFDbktRK3BxM05PeEQxcmJaNXp5ZituTThzMS9jbDlwd1RBQ0IyUDlCb2gwcDlEYmlqYUdpVVF1ZkRHWDNaL0ZYZFVqd3JCTUZEKzlPTW53PT0iLCJzaWciOiJBQmdBQ3dBZzA4SkVGY1lxRmsrUnpPVHZvaUp0K1JMOEZvd3oxNzVMakVmTW1KTHcyOU1BSUJLbDQ3eWJyYmdmOTltK21DblVDbkZtTFRNZDN5MUFLTWVoaFNiWEMvYzQiLCJ0cG1WZXIiOiIyLjAifSwicGF0Ijp7ImF0dGVzdEluZm8iOiIvMVJEUjRBWUFDSUFDNE9mcktPRktIbGEzakV6VVBLM01KQ1MrVzJ0d0JWVEVERjhFOTZ3MVZaWkFBZ0FBUUlEQkFVR0J3QUFBQUFhdkk5Mk9IVGc5M050eWJRQkRNNkg1UlJBMWM0QUFBQUJBQXNEQndBQUFDQXVxYXVSbU5GamdBZEFETkxEdnZITWRGdUdTM1lCR2c0YnhTR0FyR1JTMUE9PSIsImtpZCI6IkFhRkpRQ1FIM3NPdHFIV1RVazZaNStmdy9rMTh2eXZKRW5ZdzFNN2tUdnRUIiwic2lnIjoiQUJnQUN3QWdNcWN0TlRuZFh3VU5MZkNERW1lOC81c0hVM2diaGFPL05OdW4xY2tpT0xBQUlLVFkwU2VWUUJIWkpuaXNPRzNTb2VOQ1dHYTJnWlMrSUhuWkN2M3dUOTVJIiwidHBtVmVyIjoiMi4wIn19LCJlYXIudmVyYWlzb24ua2V5LWF0dGVzdGF0aW9uIjp7ImFrcHViIjoiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFb1Q4RUhiYXNsb0tjcEQ2bXJjMDdFUFd0dG5uUEpfNmN6eXpYOXlYMm5CTjJQOUJvaDBwOURiaWphR2lVUXVmREdYM1pfRlhkVWp3ckJNRkQtOU9NbncifX19fQ.eRyCRmGEOt2GeMvi1-PiSaIVOuixBHwz8FYPSm7XuKnZd6XYe_8HQaCXEtarpOppvzoyHcZvU_4rV54iE7PQaw"; + let signed = ear + .sign_jwt_pem(Algorithm::ES256, SIGNING_KEY.as_bytes()) + .unwrap(); - let ear = Ear::from_jwt_jwk(ear_jwt, Algorithm::ES256, VERIF_KEY.as_bytes()) - .expect("successfully verified"); + let ear2 = + Ear::from_jwt_jwk(signed.as_str(), Algorithm::ES256, VERIF_KEY.as_bytes()).unwrap(); - assert_eq!("tag:github.com,2023:veraison/ear", ear.profile); + assert_eq!(EAR_PROFILE, ear2.profile); } } diff --git a/src/lib.rs b/src/lib.rs index 9389a90..4260e21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! An implementation of EAT Attestation Results token. //! //! This crate provides an implementation of attestation results tokens that conforms to EAT -//! Attestation Results [draft-fv-rats-ear] specification. This defines a token intended to +//! Attestation Results [draft-ietf-rats-ear-04] specification. This defines a token intended to //! communicate a set of appraisals of attested evidence produced by a verifier. Each appraisal is //! based around a set of trust claims defined by Attestation Results for Secure Interactions //! (AR4SI) [draft-ietf-rats-ar4si]. @@ -11,7 +11,7 @@ //! The attestation result may be serialized as a signed JSON or CBOR token (using JWT and COSE, //! respectively). //! -//! [draft-fv-rats-ear]: https://datatracker.ietf.org/doc/draft-fv-rats-ear/ +//! [draft-ietf-rats-ear-04]: https://datatracker.ietf.org/doc/draft-ietf-rats-ear/ //! [draft-ietf-rats-ar4si]: https://datatracker.ietf.org/doc/draft-ietf-rats-ar4si/ //! //! # Examples @@ -39,6 +39,8 @@ //! }, //! raw_evidence: None, //! nonce: None, +//! status: None, +//! topology: None, //! submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), //! extensions: Extensions::new(), //! }; @@ -62,7 +64,7 @@ //! "#; //! //! fn main() { -//! let signed = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJlYXRfcHJvZmlsZSI6InRlc3QiLCJpYXQiOjEsImVhci52ZXJpZmllci1pZCI6eyJkZXZlbG9wZXIiOiJodHRwczovL3ZlcmFpc29uLXByb2plY3Qub3JnIiwiYnVpbGQiOiJ2c3RzIDAuMC4xIn0sInN1Ym1vZHMiOnsidGVzdCI6eyJlYXIuc3RhdHVzIjoibm9uZSJ9fX0.G25v0j0NDQhSOcK3Jtfq5vqVxnoWuWf-Q0DCNkCwpyB03DGr25ZDJ3IDSAHVPZrr6TVMwj8RcGEzQnCrucem4Q"; +//! let signed = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJlYXRfcHJvZmlsZSI6InRlc3QiLCJpYXQiOjEsImVhcl92ZXJpZmllcl9pZCI6eyJkZXZlbG9wZXIiOiJodHRwczovL3ZlcmFpc29uLXByb2plY3Qub3JnIiwiYnVpbGQiOiJ2c3RzIDAuMC4xIn0sInN1Ym1vZHMiOnsidGVzdCI6eyJlYXJfc3RhdHVzIjoibm9uZSJ9fX0.i20_BuOLXWi0CjD-ZzBVZNUv8vygk3rdeUAP6HwvSVXy0Svp1BYU8YH67H3bLAh9OcwTwiC_Rvg1fydSllSUgw"; //! //! let token = Ear::from_jwt_jwk(signed, Algorithm::ES256, VERIF_KEY.as_bytes()).unwrap(); //! println!("EAR profiles: {}", token.profile); @@ -129,7 +131,7 @@ //! use ear::{Ear, Appraisal, RawValueKind, RawValue, Profile, register_profile}; //! //! fn init_profile() { -//! let mut profile = Profile::new("tag:github.com,2023:veraison/ear#acme-profile"); +//! let mut profile = Profile::new("tag:ietf.org,2026:rats/ear#04"); //! //! profile.register_ear_extension( //! "ext.company-name", -65537, RawValueKind::String).unwrap(); @@ -143,10 +145,10 @@ //! init_profile(); //! //! let mut ear = Ear::new_with_profile( -//! "tag:github.com,2023:veraison/ear#acme-profile").unwrap(); +//! "tag:ietf.org,2026:rats/ear#04").unwrap(); //! // these will apply to all submods/appraisals within a profiled EAR //! let mut appraisal = Appraisal::new_with_profile( -//! "tag:github.com,2023:veraison/ear#acme-profile").unwrap(); +//! "tag:ietf.org,2026:rats/ear#04").unwrap(); //! //! ear.extensions.set_by_name( //! "ext.company-name", @@ -208,7 +210,7 @@ //! "; //! //! let mut ear = Ear::new(); -//! ear.profile = "tag:github.com,2023:veraison/ear#acme-profile".to_string(); +//! ear.profile = "tag:ietf.org,2026:rats/ear#04".to_string(); //! ear.vid.build = "vsts 0.0.1".to_string(); //! ear.vid.developer = "https://veraison-project.org".to_string(); //! ear.submods.insert("road-runner-trap".to_string(), Appraisal::new()); @@ -271,6 +273,8 @@ //! }, //! raw_evidence: None, //! nonce: None, +//! status: None, +//! topology: None, //! submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), //! extensions: Extensions::new(), //! }; @@ -319,6 +323,7 @@ pub use self::base64::Bytes; pub use self::ear::new_cose_header; pub use self::ear::new_jwt_header; pub use self::ear::Ear; +pub use self::ear::EAR_PROFILE; pub use self::error::Error; pub use self::extension::get_profile; pub use self::extension::register_profile; diff --git a/src/trust/claim.rs b/src/trust/claim.rs index 87802f2..406ff42 100644 --- a/src/trust/claim.rs +++ b/src/trust/claim.rs @@ -458,7 +458,7 @@ impl TrustClaim { } /// Return the `ValueDescription` for the current value of this claim. - fn value_desc(&self) -> Option<&ValueDescription> { + fn value_desc(&self) -> Option<&ValueDescription<'_>> { let val = self.value(); if (-1..=1).contains(&val) || val == 99 { return COMMON_CLAIM_MAP.get(&val); From 96f18a9956e94696794e0fe4e9b9cbf166e89529 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Thu, 9 Jul 2026 17:30:46 +0800 Subject: [PATCH 2/3] docs: sync README with draft-04 crate docs Update the cargo-rdme README to match the revised lib.rs examples. Signed-off-by: Xynnn007 Co-authored-by: Cursor --- README.md | 58 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 306197d..5a34158 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ An implementation of EAT Attestation Results token. This crate provides an implementation of attestation results tokens that conforms to EAT -Attestation Results [draft-fv-rats-ear] specification. This defines a token intended to +Attestation Results [draft-ietf-rats-ear-04] specification. This defines a token intended to communicate a set of appraisals of attested evidence produced by a verifier. Each appraisal is based around a set of trust claims defined by Attestation Results for Secure Interactions (AR4SI) [draft-ietf-rats-ar4si]. @@ -11,7 +11,7 @@ based around a set of trust claims defined by Attestation Results for Secure Int The attestation result may be serialized as a signed JSON or CBOR token (using JWT and COSE, respectively). -[draft-fv-rats-ear]: https://datatracker.ietf.org/doc/draft-fv-rats-ear/ +[draft-ietf-rats-ear-04]: https://datatracker.ietf.org/doc/draft-ietf-rats-ear/ [draft-ietf-rats-ar4si]: https://datatracker.ietf.org/doc/draft-ietf-rats-ar4si/ # Examples @@ -39,6 +39,8 @@ fn main() { }, raw_evidence: None, nonce: None, + status: None, + topology: None, submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), extensions: Extensions::new(), }; @@ -62,7 +64,7 @@ const VERIF_KEY: &str = r#" "#; fn main() { - let signed = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJlYXRfcHJvZmlsZSI6InRlc3QiLCJpYXQiOjEsImVhci52ZXJpZmllci1pZCI6eyJkZXZlbG9wZXIiOiJodHRwczovL3ZlcmFpc29uLXByb2plY3Qub3JnIiwiYnVpbGQiOiJ2c3RzIDAuMC4xIn0sInN1Ym1vZHMiOnsidGVzdCI6eyJlYXIuc3RhdHVzIjoibm9uZSJ9fX0.G25v0j0NDQhSOcK3Jtfq5vqVxnoWuWf-Q0DCNkCwpyB03DGr25ZDJ3IDSAHVPZrr6TVMwj8RcGEzQnCrucem4Q"; + let signed = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJlYXRfcHJvZmlsZSI6InRlc3QiLCJpYXQiOjEsImVhcl92ZXJpZmllcl9pZCI6eyJkZXZlbG9wZXIiOiJodHRwczovL3ZlcmFpc29uLXByb2plY3Qub3JnIiwiYnVpbGQiOiJ2c3RzIDAuMC4xIn0sInN1Ym1vZHMiOnsidGVzdCI6eyJlYXJfc3RhdHVzIjoibm9uZSJ9fX0.i20_BuOLXWi0CjD-ZzBVZNUv8vygk3rdeUAP6HwvSVXy0Svp1BYU8YH67H3bLAh9OcwTwiC_Rvg1fydSllSUgw"; let token = Ear::from_jwt_jwk(signed, Algorithm::ES256, VERIF_KEY.as_bytes()).unwrap(); println!("EAR profiles: {}", token.profile); @@ -75,7 +77,7 @@ EAR supports extension at top level (i.e. within the [`Ear`] struct), and also w [`Appraisal`]s. An extension is an additional field definition. Extensions can be defined by registering them with the `extensions` field of the corresponding struct. When registering an extension, you must provide a string name (used in JSON), an integer key (used in CBOR), and an -[`ExtensionKind`] indicating which [`ExtensionValue`]s are valid. +[`RawValueKind`] indicating which [`RawValue`]s are valid. ## Registering individual extensions @@ -83,36 +85,36 @@ Extensions can be registered directly with the corresponding struct's `extension they have been registered, their values can be set and queried ```rust -use ear::{Ear, Appraisal, ExtensionKind, ExtensionValue}; +use ear::{Ear, Appraisal, RawValueKind, RawValue}; let mut ear = Ear::new(); -ear.extensions.register("ext.company-name", -65537, ExtensionKind::String).unwrap(); +ear.extensions.register("ext.company-name", -65537, RawValueKind::String).unwrap(); let mut appraisal = Appraisal::new(); // extensions for Ear's and Appraisal's have their own namespaces, so it is // to use the same key in both. -appraisal.extensions.register("ext.timestamp", -65537, ExtensionKind::Integer).unwrap(); +appraisal.extensions.register("ext.timestamp", -65537, RawValueKind::Integer).unwrap(); ear.extensions.set_by_name( "ext.company-name", - ExtensionValue::String("Acme Inc.".to_string()), + RawValue::String("Acme Inc.".to_string()), ).unwrap(); appraisal.extensions.set_by_key( -65537, - ExtensionValue::Integer(1723534859), + RawValue::Integer(1723534859), ).unwrap(); ear.submods.insert("road-runner-trap".to_string(), appraisal); assert_eq!( ear.extensions.get_by_key(&-65537).unwrap(), - ExtensionValue::String("Acme Inc.".to_string()), + RawValue::String("Acme Inc.".to_string()), ); assert_eq!( ear.submods["road-runner-trap"].extensions.get_by_name("ext.timestamp").unwrap(), - ExtensionValue::Integer(1723534859), + RawValue::Integer(1723534859), ); ``` @@ -126,15 +128,15 @@ Sets of extensions can be associated together within [`Profile`]s. A [`Profile`] registered, and can then be retrieved by its `id` when creating a new [`Ear`] or [`Appraisal`] ```rust -use ear::{Ear, Appraisal, ExtensionKind, ExtensionValue, Profile, register_profile}; +use ear::{Ear, Appraisal, RawValueKind, RawValue, Profile, register_profile}; fn init_profile() { - let mut profile = Profile::new("tag:github.com,2023:veraison/ear#acme-profile"); + let mut profile = Profile::new("tag:ietf.org,2026:rats/ear#04"); profile.register_ear_extension( - "ext.company-name", -65537, ExtensionKind::String).unwrap(); + "ext.company-name", -65537, RawValueKind::String).unwrap(); profile.register_appraisal_extension( - "ext.timestamp", -65537, ExtensionKind::Integer).unwrap(); + "ext.timestamp", -65537, RawValueKind::Integer).unwrap(); register_profile(&profile); } @@ -143,32 +145,32 @@ fn main() { init_profile(); let mut ear = Ear::new_with_profile( - "tag:github.com,2023:veraison/ear#acme-profile").unwrap(); + "tag:ietf.org,2026:rats/ear#04").unwrap(); // these will apply to all submods/appraisals within a profiled EAR let mut appraisal = Appraisal::new_with_profile( - "tag:github.com,2023:veraison/ear#acme-profile").unwrap(); + "tag:ietf.org,2026:rats/ear#04").unwrap(); ear.extensions.set_by_name( "ext.company-name", - ExtensionValue::String("Acme Inc.".to_string()), + RawValue::String("Acme Inc.".to_string()), ).unwrap(); appraisal.extensions.set_by_key( -65537, - ExtensionValue::Integer(1723534859), + RawValue::Integer(1723534859), ).unwrap(); ear.submods.insert("road-runner-trap".to_string(), appraisal); assert_eq!( ear.extensions.get_by_key(&-65537).unwrap(), - ExtensionValue::String("Acme Inc.".to_string()), + RawValue::String("Acme Inc.".to_string()), ); assert_eq!( ear.submods["road-runner-trap"] .extensions.get_by_name("ext.timestamp").unwrap(), - ExtensionValue::Integer(1723534859), + RawValue::Integer(1723534859), ); } @@ -188,7 +190,7 @@ The following example shows how to include and then verify expiration time ("exp inside an EAR. ```rust -use ear::{Ear, Algorithm, Appraisal, ExtensionKind, ExtensionValue}; +use ear::{Ear, Algorithm, Appraisal, RawValueKind, RawValue}; use std::time::{SystemTime, Duration, UNIX_EPOCH}; const VERIF_KEY: &str = r#" @@ -208,17 +210,17 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t "; let mut ear = Ear::new(); -ear.profile = "tag:github.com,2023:veraison/ear#acme-profile".to_string(); +ear.profile = "tag:ietf.org,2026:rats/ear#04".to_string(); ear.vid.build = "vsts 0.0.1".to_string(); ear.vid.developer = "https://veraison-project.org".to_string(); ear.submods.insert("road-runner-trap".to_string(), Appraisal::new()); -ear.extensions.register("exp", 4, ExtensionKind::Integer).unwrap(); +ear.extensions.register("exp", 4, RawValueKind::Integer).unwrap(); // expire 10 days from now let exp = SystemTime::now().checked_add(Duration::from_secs(60*60*24*10)).unwrap() .duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; -ear.extensions.set_by_name("exp", ExtensionValue::Integer(exp)).unwrap(); +ear.extensions.set_by_name("exp", RawValue::Integer(exp)).unwrap(); let signed = ear @@ -228,11 +230,11 @@ let signed = ear let mut ear2 = Ear::from_jwt_jwk(signed.as_str(), Algorithm::ES256, VERIF_KEY.as_bytes()).unwrap(); -ear2.extensions.register("exp", 4, ExtensionKind::Integer).unwrap(); +ear2.extensions.register("exp", 4, RawValueKind::Integer).unwrap(); // Verify the token has not expired. let exp2 = match ear2.extensions.get_by_name("exp").unwrap() { - ExtensionValue::Integer(v) => Duration::from_secs(v as u64), + RawValue::Integer(v) => Duration::from_secs(v as u64), _ => panic!(), }; assert!(SystemTime::now().duration_since(UNIX_EPOCH).unwrap() < exp2); @@ -271,6 +273,8 @@ fn main() { }, raw_evidence: None, nonce: None, + status: None, + topology: None, submods: BTreeMap::from([("test".to_string(), Appraisal::new())]), extensions: Extensions::new(), }; From bb8d6cd8cbd7df7ba96d3f540e8b587b0e52001e Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Thu, 9 Jul 2026 17:42:00 +0800 Subject: [PATCH 3/3] chore: allow BlueOak-1.0.0 license in deny.toml Signed-off-by: Xynnn007 Co-authored-by: Cursor --- deny.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index 133aa69..16d97bb 100644 --- a/deny.toml +++ b/deny.toml @@ -99,7 +99,8 @@ allow = [ "OpenSSL", "Unicode-3.0", "MPL-2.0", - "BSD-3-Clause" + "BSD-3-Clause", + "BlueOak-1.0.0", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the