Skip to main content

chio_appraisal/
lib.rs

1pub use chio_core_types::{canonical, capability, crypto, error, receipt, Error};
2
3use std::collections::{BTreeMap, BTreeSet};
4
5pub use chio_core_types::runtime_attestation::AttestationVerifierFamily;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::canonical::canonical_json_bytes;
10use crate::capability::{
11    canonicalize_attestation_verifier, AttestationTrustError, AttestationTrustPolicy,
12    RuntimeAssuranceTier, RuntimeAttestationEvidence, WorkloadIdentity, WorkloadIdentityError,
13};
14use crate::crypto::sha256_hex;
15use crate::error::Result as ChioResult;
16use crate::receipt::SignedExportEnvelope;
17
18pub const AZURE_MAA_ATTESTATION_SCHEMA: &str = "chio.runtime-attestation.azure-maa.jwt.v1";
19pub const AWS_NITRO_ATTESTATION_SCHEMA: &str = "chio.runtime-attestation.aws-nitro-attestation.v1";
20pub const GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA: &str =
21    "chio.runtime-attestation.google-confidential-vm.jwt.v1";
22pub const ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA: &str =
23    "chio.runtime-attestation.enterprise-verifier.json.v1";
24pub const AZURE_MAA_VERIFIER_ADAPTER: &str = "azure_maa";
25pub const AWS_NITRO_VERIFIER_ADAPTER: &str = "aws_nitro";
26pub const GOOGLE_CONFIDENTIAL_VM_VERIFIER_ADAPTER: &str = "google_confidential_vm";
27pub const ENTERPRISE_VERIFIER_ADAPTER: &str = "enterprise_verifier";
28
29pub const RUNTIME_ATTESTATION_APPRAISAL_SCHEMA: &str = "chio.runtime-attestation.appraisal.v1";
30pub const RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA: &str =
31    "chio.runtime-attestation.appraisal-artifact.v1";
32pub const RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA: &str =
33    "chio.runtime-attestation.appraisal-report.v1";
34pub const RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_INVENTORY_SCHEMA: &str =
35    "chio.runtime-attestation.appraisal-artifact-inventory.v1";
36pub const RUNTIME_ATTESTATION_NORMALIZED_CLAIM_VOCABULARY_SCHEMA: &str =
37    "chio.runtime-attestation.normalized-claim-vocabulary.v1";
38pub const RUNTIME_ATTESTATION_REASON_TAXONOMY_SCHEMA: &str =
39    "chio.runtime-attestation.reason-taxonomy.v1";
40pub const RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA: &str =
41    "chio.runtime-attestation.appraisal-result.v1";
42pub const RUNTIME_ATTESTATION_APPRAISAL_IMPORT_REPORT_SCHEMA: &str =
43    "chio.runtime-attestation.appraisal-import-report.v1";
44pub const RUNTIME_ATTESTATION_VERIFIER_DESCRIPTOR_SCHEMA: &str =
45    "chio.runtime-attestation.verifier-descriptor.v1";
46pub const RUNTIME_ATTESTATION_REFERENCE_VALUE_SET_SCHEMA: &str =
47    "chio.runtime-attestation.reference-values.v1";
48pub const RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA: &str =
49    "chio.runtime-attestation.trust-bundle.v1";
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum RuntimeAttestationAppraisalVerdict {
54    Accepted,
55    Rejected,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "snake_case")]
60pub enum RuntimeAttestationAppraisalReasonCode {
61    EvidenceVerified,
62    UnsupportedEvidence,
63    UnsupportedClaimMapping,
64    AmbiguousClaimMapping,
65    PolicyRejected,
66    InvalidClaims,
67    EvidenceStale,
68    MeasurementMismatch,
69    DebugStateUnknown,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
73#[serde(rename_all = "snake_case")]
74pub enum RuntimeAttestationNormalizedClaimCode {
75    AttestationType,
76    RuntimeIdentity,
77    WorkloadIdentityScheme,
78    WorkloadIdentityUri,
79    ModuleId,
80    MeasurementDigest,
81    MeasurementRegisters,
82    HardwareModel,
83    SecureBootState,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
87#[serde(rename_all = "snake_case")]
88pub enum RuntimeAttestationNormalizedClaimCategory {
89    Identity,
90    Measurement,
91    Platform,
92    Configuration,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
96#[serde(rename_all = "snake_case")]
97pub enum RuntimeAttestationNormalizedClaimConfidence {
98    Verified,
99    Derived,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub enum RuntimeAttestationNormalizedClaimFreshness {
105    EvidenceWindow,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
109#[serde(rename_all = "snake_case")]
110pub enum RuntimeAttestationClaimProvenance {
111    EvidenceEnvelope,
112    VendorClaims,
113    WorkloadProjection,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum RuntimeAttestationAppraisalReasonGroup {
119    Verification,
120    Compatibility,
121    Freshness,
122    Measurement,
123    DebugPosture,
124    Policy,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub enum RuntimeAttestationAppraisalReasonDisposition {
130    Pass,
131    Warn,
132    Deny,
133    Degrade,
134    Unknown,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct RuntimeAttestationNormalizedClaim {
140    pub code: RuntimeAttestationNormalizedClaimCode,
141    pub legacy_assertion_key: String,
142    pub category: RuntimeAttestationNormalizedClaimCategory,
143    pub confidence: RuntimeAttestationNormalizedClaimConfidence,
144    pub freshness: RuntimeAttestationNormalizedClaimFreshness,
145    pub provenance: RuntimeAttestationClaimProvenance,
146    pub value: Value,
147}
148
149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct RuntimeAttestationNormalizedClaimVocabularyEntry {
152    pub code: RuntimeAttestationNormalizedClaimCode,
153    pub legacy_assertion_key: String,
154    pub category: RuntimeAttestationNormalizedClaimCategory,
155    pub confidence: RuntimeAttestationNormalizedClaimConfidence,
156    pub freshness: RuntimeAttestationNormalizedClaimFreshness,
157    pub description: String,
158    #[serde(default, skip_serializing_if = "Vec::is_empty")]
159    pub supported_verifier_families: Vec<AttestationVerifierFamily>,
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct RuntimeAttestationNormalizedClaimVocabulary {
165    pub schema: String,
166    #[serde(default, skip_serializing_if = "Vec::is_empty")]
167    pub entries: Vec<RuntimeAttestationNormalizedClaimVocabularyEntry>,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct RuntimeAttestationAppraisalReason {
173    pub code: RuntimeAttestationAppraisalReasonCode,
174    pub group: RuntimeAttestationAppraisalReasonGroup,
175    pub disposition: RuntimeAttestationAppraisalReasonDisposition,
176    pub description: String,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct RuntimeAttestationReasonTaxonomy {
182    pub schema: String,
183    #[serde(default, skip_serializing_if = "Vec::is_empty")]
184    pub entries: Vec<RuntimeAttestationAppraisalReason>,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct RuntimeAttestationAppraisalResultSubject {
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub runtime_identity: Option<String>,
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub workload_identity: Option<WorkloadIdentity>,
194}
195
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct RuntimeAttestationAppraisalResult {
199    pub schema: String,
200    pub result_id: String,
201    pub exported_at: u64,
202    pub issuer: String,
203    pub appraisal: RuntimeAttestationAppraisalArtifact,
204    pub exporter_policy_outcome: RuntimeAttestationPolicyOutcome,
205    pub subject: RuntimeAttestationAppraisalResultSubject,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
209#[serde(rename_all = "snake_case")]
210pub enum RuntimeAttestationImportDisposition {
211    Allow,
212    Attenuate,
213    Reject,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(rename_all = "snake_case")]
218pub enum RuntimeAttestationImportReasonCode {
219    NoLocalPolicy,
220    InvalidSignature,
221    UnsupportedAppraisalSchema,
222    ResultStale,
223    EvidenceStale,
224    ExporterPolicyRejected,
225    UntrustedIssuer,
226    UntrustedSigner,
227    UnsupportedVerifierFamily,
228    MissingRequiredClaim,
229    ClaimMismatch,
230    TierAttenuated,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct RuntimeAttestationImportReason {
236    pub code: RuntimeAttestationImportReasonCode,
237    pub description: String,
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct RuntimeAttestationImportedAppraisalPolicy {
243    #[serde(default, skip_serializing_if = "Vec::is_empty")]
244    pub trusted_issuers: Vec<String>,
245    #[serde(default, skip_serializing_if = "Vec::is_empty")]
246    pub trusted_signer_keys: Vec<String>,
247    #[serde(default, skip_serializing_if = "Vec::is_empty")]
248    pub allowed_verifier_families: Vec<AttestationVerifierFamily>,
249    #[serde(default, skip_serializing_if = "Option::is_none")]
250    pub max_result_age_seconds: Option<u64>,
251    #[serde(default, skip_serializing_if = "Option::is_none")]
252    pub max_evidence_age_seconds: Option<u64>,
253    #[serde(default, skip_serializing_if = "Option::is_none")]
254    pub maximum_effective_tier: Option<RuntimeAssuranceTier>,
255    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
256    pub required_claims: BTreeMap<RuntimeAttestationNormalizedClaimCode, String>,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct RuntimeAttestationAppraisalImportOutcome {
262    pub disposition: RuntimeAttestationImportDisposition,
263    pub effective_tier: RuntimeAssuranceTier,
264    #[serde(default, skip_serializing_if = "Vec::is_empty")]
265    pub reason_codes: Vec<RuntimeAttestationImportReasonCode>,
266    #[serde(default, skip_serializing_if = "Vec::is_empty")]
267    pub reasons: Vec<RuntimeAttestationImportReason>,
268}
269
270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct RuntimeAttestationAppraisalImportRequest {
273    pub signed_result: SignedRuntimeAttestationAppraisalResult,
274    pub local_policy: RuntimeAttestationImportedAppraisalPolicy,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278#[serde(rename_all = "camelCase")]
279pub struct RuntimeAttestationAppraisalImportReport {
280    pub schema: String,
281    pub evaluated_at: u64,
282    pub signer_key_hex: String,
283    pub result: RuntimeAttestationAppraisalResult,
284    pub local_policy_outcome: RuntimeAttestationAppraisalImportOutcome,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct RuntimeAttestationVerifierDescriptorDocument {
290    pub schema: String,
291    pub descriptor_id: String,
292    pub verifier: String,
293    pub verifier_family: AttestationVerifierFamily,
294    pub adapter: String,
295    #[serde(default, skip_serializing_if = "Vec::is_empty")]
296    pub attestation_schemas: Vec<String>,
297    pub appraisal_artifact_schema: String,
298    pub appraisal_result_schema: String,
299    #[serde(default, skip_serializing_if = "Vec::is_empty")]
300    pub signing_key_fingerprints: Vec<String>,
301    #[serde(default, skip_serializing_if = "Option::is_none")]
302    pub reference_values_uri: Option<String>,
303    pub issued_at: u64,
304    pub expires_at: u64,
305}
306
307pub type SignedRuntimeAttestationVerifierDescriptor =
308    SignedExportEnvelope<RuntimeAttestationVerifierDescriptorDocument>;
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum RuntimeAttestationReferenceValueState {
313    Active,
314    Superseded,
315    Revoked,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct RuntimeAttestationReferenceValueSet {
321    pub schema: String,
322    pub reference_value_id: String,
323    pub descriptor_id: String,
324    pub verifier_family: AttestationVerifierFamily,
325    pub attestation_schema: String,
326    #[serde(default, skip_serializing_if = "Option::is_none")]
327    pub source_uri: Option<String>,
328    pub issued_at: u64,
329    pub expires_at: u64,
330    pub state: RuntimeAttestationReferenceValueState,
331    #[serde(default, skip_serializing_if = "Option::is_none")]
332    pub superseded_by: Option<String>,
333    #[serde(default, skip_serializing_if = "Option::is_none")]
334    pub revoked_reason: Option<String>,
335    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
336    pub measurements: BTreeMap<String, Value>,
337}
338
339pub type SignedRuntimeAttestationReferenceValueSet =
340    SignedExportEnvelope<RuntimeAttestationReferenceValueSet>;
341
342#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct RuntimeAttestationTrustBundleDocument {
345    pub schema: String,
346    pub bundle_id: String,
347    pub publisher: String,
348    pub version: u64,
349    pub issued_at: u64,
350    pub expires_at: u64,
351    #[serde(default, skip_serializing_if = "Vec::is_empty")]
352    pub descriptors: Vec<SignedRuntimeAttestationVerifierDescriptor>,
353    #[serde(default, skip_serializing_if = "Vec::is_empty")]
354    pub reference_values: Vec<SignedRuntimeAttestationReferenceValueSet>,
355}
356
357pub type SignedRuntimeAttestationTrustBundle =
358    SignedExportEnvelope<RuntimeAttestationTrustBundleDocument>;
359
360#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct RuntimeAttestationTrustBundleVerification {
363    pub schema: String,
364    pub bundle_id: String,
365    pub publisher: String,
366    pub version: u64,
367    pub descriptor_count: usize,
368    pub reference_value_count: usize,
369    #[serde(default, skip_serializing_if = "Vec::is_empty")]
370    pub verifier_families: Vec<AttestationVerifierFamily>,
371    pub verified_at: u64,
372}
373
374#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
375pub enum RuntimeAttestationAppraisalError {
376    #[error("runtime attestation schema `{schema}` is not recognized by the canonical appraisal boundary")]
377    UnsupportedSchema { schema: String },
378}
379
380#[derive(Debug, Clone, thiserror::Error)]
381pub enum RuntimeAttestationVerificationError {
382    #[error("runtime attestation workload identity is invalid: {0}")]
383    InvalidWorkloadIdentity(#[from] WorkloadIdentityError),
384    #[error("runtime attestation evidence is stale at {now} (issued_at={issued_at}, expires_at={expires_at})")]
385    StaleEvidence {
386        now: u64,
387        issued_at: u64,
388        expires_at: u64,
389    },
390    #[error(transparent)]
391    Appraisal(#[from] RuntimeAttestationAppraisalError),
392    #[error("runtime attestation evidence rejected by local trust policy: {0}")]
393    TrustPolicy(#[from] AttestationTrustError),
394}
395
396pub struct RuntimeAttestationVerifierDescriptorArgs<'a> {
397    pub signer: &'a crate::crypto::Keypair,
398    pub descriptor_id: String,
399    pub verifier: String,
400    pub verifier_family: AttestationVerifierFamily,
401    pub adapter: String,
402    pub attestation_schemas: Vec<String>,
403    pub signing_key_fingerprints: Vec<String>,
404    pub reference_values_uri: Option<String>,
405    pub issued_at: u64,
406    pub expires_at: u64,
407}
408
409pub fn create_signed_runtime_attestation_verifier_descriptor(
410    args: RuntimeAttestationVerifierDescriptorArgs<'_>,
411) -> ChioResult<SignedRuntimeAttestationVerifierDescriptor> {
412    let descriptor = RuntimeAttestationVerifierDescriptorDocument {
413        schema: RUNTIME_ATTESTATION_VERIFIER_DESCRIPTOR_SCHEMA.to_string(),
414        descriptor_id: args.descriptor_id,
415        verifier: args.verifier,
416        verifier_family: args.verifier_family,
417        adapter: args.adapter,
418        attestation_schemas: args.attestation_schemas,
419        appraisal_artifact_schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
420        appraisal_result_schema: RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA.to_string(),
421        signing_key_fingerprints: args.signing_key_fingerprints,
422        reference_values_uri: args.reference_values_uri,
423        issued_at: args.issued_at,
424        expires_at: args.expires_at,
425    };
426    validate_runtime_attestation_verifier_descriptor(&descriptor)?;
427    SignedExportEnvelope::sign(descriptor, args.signer)
428}
429
430pub fn verify_signed_runtime_attestation_verifier_descriptor(
431    descriptor: &SignedRuntimeAttestationVerifierDescriptor,
432    now: u64,
433) -> ChioResult<()> {
434    validate_runtime_attestation_verifier_descriptor(&descriptor.body)?;
435    if now < descriptor.body.issued_at {
436        return Err(crate::Error::CanonicalJson(format!(
437            "runtime attestation verifier descriptor `{}` is not yet valid",
438            descriptor.body.descriptor_id
439        )));
440    }
441    if now > descriptor.body.expires_at {
442        return Err(crate::Error::CanonicalJson(format!(
443            "runtime attestation verifier descriptor `{}` has expired",
444            descriptor.body.descriptor_id
445        )));
446    }
447    if !descriptor.verify_signature()? {
448        return Err(crate::Error::CanonicalJson(format!(
449            "runtime attestation verifier descriptor `{}` signature verification failed",
450            descriptor.body.descriptor_id
451        )));
452    }
453    Ok(())
454}
455
456pub struct RuntimeAttestationReferenceValueSetArgs<'a> {
457    pub signer: &'a crate::crypto::Keypair,
458    pub reference_value_id: String,
459    pub descriptor_id: String,
460    pub verifier_family: AttestationVerifierFamily,
461    pub attestation_schema: String,
462    pub source_uri: Option<String>,
463    pub issued_at: u64,
464    pub expires_at: u64,
465    pub state: RuntimeAttestationReferenceValueState,
466    pub superseded_by: Option<String>,
467    pub revoked_reason: Option<String>,
468    pub measurements: BTreeMap<String, Value>,
469}
470
471pub fn create_signed_runtime_attestation_reference_value_set(
472    args: RuntimeAttestationReferenceValueSetArgs<'_>,
473) -> ChioResult<SignedRuntimeAttestationReferenceValueSet> {
474    let reference_value_set = RuntimeAttestationReferenceValueSet {
475        schema: RUNTIME_ATTESTATION_REFERENCE_VALUE_SET_SCHEMA.to_string(),
476        reference_value_id: args.reference_value_id,
477        descriptor_id: args.descriptor_id,
478        verifier_family: args.verifier_family,
479        attestation_schema: args.attestation_schema,
480        source_uri: args.source_uri,
481        issued_at: args.issued_at,
482        expires_at: args.expires_at,
483        state: args.state,
484        superseded_by: args.superseded_by,
485        revoked_reason: args.revoked_reason,
486        measurements: args.measurements,
487    };
488    validate_runtime_attestation_reference_value_set(&reference_value_set)?;
489    SignedExportEnvelope::sign(reference_value_set, args.signer)
490}
491
492pub fn verify_signed_runtime_attestation_reference_value_set(
493    reference_value_set: &SignedRuntimeAttestationReferenceValueSet,
494    now: u64,
495) -> ChioResult<()> {
496    validate_runtime_attestation_reference_value_set(&reference_value_set.body)?;
497    if now < reference_value_set.body.issued_at {
498        return Err(crate::Error::CanonicalJson(format!(
499            "runtime attestation reference-value set `{}` is not yet valid",
500            reference_value_set.body.reference_value_id
501        )));
502    }
503    if now > reference_value_set.body.expires_at {
504        return Err(crate::Error::CanonicalJson(format!(
505            "runtime attestation reference-value set `{}` has expired",
506            reference_value_set.body.reference_value_id
507        )));
508    }
509    if !reference_value_set.verify_signature()? {
510        return Err(crate::Error::CanonicalJson(format!(
511            "runtime attestation reference-value set `{}` signature verification failed",
512            reference_value_set.body.reference_value_id
513        )));
514    }
515    Ok(())
516}
517
518pub struct RuntimeAttestationTrustBundleArgs<'a> {
519    pub signer: &'a crate::crypto::Keypair,
520    pub bundle_id: String,
521    pub publisher: String,
522    pub version: u64,
523    pub issued_at: u64,
524    pub expires_at: u64,
525    pub descriptors: Vec<SignedRuntimeAttestationVerifierDescriptor>,
526    pub reference_values: Vec<SignedRuntimeAttestationReferenceValueSet>,
527}
528
529pub fn create_signed_runtime_attestation_trust_bundle(
530    args: RuntimeAttestationTrustBundleArgs<'_>,
531) -> ChioResult<SignedRuntimeAttestationTrustBundle> {
532    let bundle = RuntimeAttestationTrustBundleDocument {
533        schema: RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA.to_string(),
534        bundle_id: args.bundle_id,
535        publisher: args.publisher,
536        version: args.version,
537        issued_at: args.issued_at,
538        expires_at: args.expires_at,
539        descriptors: args.descriptors,
540        reference_values: args.reference_values,
541    };
542    validate_runtime_attestation_trust_bundle(&bundle, args.issued_at)?;
543    SignedExportEnvelope::sign(bundle, args.signer)
544}
545
546pub fn verify_signed_runtime_attestation_trust_bundle(
547    bundle: &SignedRuntimeAttestationTrustBundle,
548    now: u64,
549) -> ChioResult<RuntimeAttestationTrustBundleVerification> {
550    validate_runtime_attestation_trust_bundle(&bundle.body, now)?;
551    if !bundle.verify_signature()? {
552        return Err(crate::Error::CanonicalJson(format!(
553            "runtime attestation trust bundle `{}` signature verification failed",
554            bundle.body.bundle_id
555        )));
556    }
557    let verifier_families = bundle
558        .body
559        .descriptors
560        .iter()
561        .map(|descriptor| descriptor.body.verifier_family)
562        .collect::<BTreeSet<_>>()
563        .into_iter()
564        .collect::<Vec<_>>();
565    Ok(RuntimeAttestationTrustBundleVerification {
566        schema: RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA.to_string(),
567        bundle_id: bundle.body.bundle_id.clone(),
568        publisher: bundle.body.publisher.clone(),
569        version: bundle.body.version,
570        descriptor_count: bundle.body.descriptors.len(),
571        reference_value_count: bundle.body.reference_values.len(),
572        verifier_families,
573        verified_at: now,
574    })
575}
576
577fn validate_runtime_attestation_verifier_descriptor(
578    descriptor: &RuntimeAttestationVerifierDescriptorDocument,
579) -> ChioResult<()> {
580    if descriptor.schema != RUNTIME_ATTESTATION_VERIFIER_DESCRIPTOR_SCHEMA {
581        return Err(crate::Error::CanonicalJson(format!(
582            "runtime attestation verifier descriptor schema must be {RUNTIME_ATTESTATION_VERIFIER_DESCRIPTOR_SCHEMA}"
583        )));
584    }
585    if descriptor.descriptor_id.trim().is_empty() {
586        return Err(crate::Error::CanonicalJson(
587            "runtime attestation verifier descriptor must include a non-empty descriptor_id"
588                .to_string(),
589        ));
590    }
591    if descriptor.verifier.trim().is_empty() {
592        return Err(crate::Error::CanonicalJson(format!(
593            "runtime attestation verifier descriptor `{}` must include a non-empty verifier",
594            descriptor.descriptor_id
595        )));
596    }
597    if descriptor.adapter.trim().is_empty() {
598        return Err(crate::Error::CanonicalJson(format!(
599            "runtime attestation verifier descriptor `{}` must include a non-empty adapter",
600            descriptor.descriptor_id
601        )));
602    }
603    if descriptor.issued_at > descriptor.expires_at {
604        return Err(crate::Error::CanonicalJson(format!(
605            "runtime attestation verifier descriptor `{}` must not expire before it is issued",
606            descriptor.descriptor_id
607        )));
608    }
609    if descriptor.attestation_schemas.is_empty() {
610        return Err(crate::Error::CanonicalJson(format!(
611            "runtime attestation verifier descriptor `{}` must include at least one attestation schema",
612            descriptor.descriptor_id
613        )));
614    }
615    validate_sorted_unique_strings(
616        &descriptor.attestation_schemas,
617        "attestation_schemas",
618        &descriptor.descriptor_id,
619    )?;
620    if descriptor.appraisal_artifact_schema != RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA {
621        return Err(crate::Error::CanonicalJson(format!(
622            "runtime attestation verifier descriptor `{}` must reference the canonical appraisal artifact schema",
623            descriptor.descriptor_id
624        )));
625    }
626    if descriptor.appraisal_result_schema != RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA {
627        return Err(crate::Error::CanonicalJson(format!(
628            "runtime attestation verifier descriptor `{}` must reference the canonical appraisal result schema",
629            descriptor.descriptor_id
630        )));
631    }
632    if descriptor.signing_key_fingerprints.is_empty() {
633        return Err(crate::Error::CanonicalJson(format!(
634            "runtime attestation verifier descriptor `{}` must include at least one signing-key fingerprint",
635            descriptor.descriptor_id
636        )));
637    }
638    validate_sorted_unique_strings(
639        &descriptor.signing_key_fingerprints,
640        "signing_key_fingerprints",
641        &descriptor.descriptor_id,
642    )?;
643    if let Some(reference_values_uri) = &descriptor.reference_values_uri {
644        if reference_values_uri.trim().is_empty() {
645            return Err(crate::Error::CanonicalJson(format!(
646                "runtime attestation verifier descriptor `{}` cannot include an empty reference_values_uri",
647                descriptor.descriptor_id
648            )));
649        }
650    }
651    Ok(())
652}
653
654fn validate_runtime_attestation_reference_value_set(
655    reference_value_set: &RuntimeAttestationReferenceValueSet,
656) -> ChioResult<()> {
657    if reference_value_set.schema != RUNTIME_ATTESTATION_REFERENCE_VALUE_SET_SCHEMA {
658        return Err(crate::Error::CanonicalJson(format!(
659            "runtime attestation reference-value schema must be {RUNTIME_ATTESTATION_REFERENCE_VALUE_SET_SCHEMA}"
660        )));
661    }
662    if reference_value_set.reference_value_id.trim().is_empty() {
663        return Err(crate::Error::CanonicalJson(
664            "runtime attestation reference-value set must include a non-empty reference_value_id"
665                .to_string(),
666        ));
667    }
668    if reference_value_set.descriptor_id.trim().is_empty() {
669        return Err(crate::Error::CanonicalJson(format!(
670            "runtime attestation reference-value set `{}` must include a non-empty descriptor_id",
671            reference_value_set.reference_value_id
672        )));
673    }
674    if reference_value_set.attestation_schema.trim().is_empty() {
675        return Err(crate::Error::CanonicalJson(format!(
676            "runtime attestation reference-value set `{}` must include a non-empty attestation_schema",
677            reference_value_set.reference_value_id
678        )));
679    }
680    if reference_value_set.issued_at > reference_value_set.expires_at {
681        return Err(crate::Error::CanonicalJson(format!(
682            "runtime attestation reference-value set `{}` must not expire before it is issued",
683            reference_value_set.reference_value_id
684        )));
685    }
686    if let Some(source_uri) = &reference_value_set.source_uri {
687        if source_uri.trim().is_empty() {
688            return Err(crate::Error::CanonicalJson(format!(
689                "runtime attestation reference-value set `{}` cannot include an empty source_uri",
690                reference_value_set.reference_value_id
691            )));
692        }
693    }
694    if reference_value_set.measurements.is_empty() {
695        return Err(crate::Error::CanonicalJson(format!(
696            "runtime attestation reference-value set `{}` must include at least one measurement",
697            reference_value_set.reference_value_id
698        )));
699    }
700    match reference_value_set.state {
701        RuntimeAttestationReferenceValueState::Active => {
702            if reference_value_set.superseded_by.is_some()
703                || reference_value_set.revoked_reason.is_some()
704            {
705                return Err(crate::Error::CanonicalJson(format!(
706                    "active runtime attestation reference-value set `{}` cannot include supersession or revocation fields",
707                    reference_value_set.reference_value_id
708                )));
709            }
710        }
711        RuntimeAttestationReferenceValueState::Superseded => {
712            let superseded_by = reference_value_set.superseded_by.as_deref().ok_or_else(|| {
713                crate::Error::CanonicalJson(format!(
714                    "superseded runtime attestation reference-value set `{}` must include superseded_by",
715                    reference_value_set.reference_value_id
716                ))
717            })?;
718            if superseded_by == reference_value_set.reference_value_id {
719                return Err(crate::Error::CanonicalJson(format!(
720                    "runtime attestation reference-value set `{}` cannot supersede itself",
721                    reference_value_set.reference_value_id
722                )));
723            }
724            if reference_value_set.revoked_reason.is_some() {
725                return Err(crate::Error::CanonicalJson(format!(
726                    "superseded runtime attestation reference-value set `{}` cannot include revoked_reason",
727                    reference_value_set.reference_value_id
728                )));
729            }
730        }
731        RuntimeAttestationReferenceValueState::Revoked => {
732            if reference_value_set.superseded_by.is_some() {
733                return Err(crate::Error::CanonicalJson(format!(
734                    "revoked runtime attestation reference-value set `{}` cannot include superseded_by",
735                    reference_value_set.reference_value_id
736                )));
737            }
738            if reference_value_set
739                .revoked_reason
740                .as_deref()
741                .map(str::trim)
742                .unwrap_or_default()
743                .is_empty()
744            {
745                return Err(crate::Error::CanonicalJson(format!(
746                    "revoked runtime attestation reference-value set `{}` must include revoked_reason",
747                    reference_value_set.reference_value_id
748                )));
749            }
750        }
751    }
752    Ok(())
753}
754
755fn validate_runtime_attestation_trust_bundle(
756    bundle: &RuntimeAttestationTrustBundleDocument,
757    now: u64,
758) -> ChioResult<()> {
759    if bundle.schema != RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA {
760        return Err(crate::Error::CanonicalJson(format!(
761            "runtime attestation trust-bundle schema must be {RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA}"
762        )));
763    }
764    if bundle.bundle_id.trim().is_empty() {
765        return Err(crate::Error::CanonicalJson(
766            "runtime attestation trust bundle must include a non-empty bundle_id".to_string(),
767        ));
768    }
769    if bundle.publisher.trim().is_empty() {
770        return Err(crate::Error::CanonicalJson(format!(
771            "runtime attestation trust bundle `{}` must include a non-empty publisher",
772            bundle.bundle_id
773        )));
774    }
775    if bundle.version == 0 {
776        return Err(crate::Error::CanonicalJson(format!(
777            "runtime attestation trust bundle `{}` must include a non-zero version",
778            bundle.bundle_id
779        )));
780    }
781    if bundle.issued_at > bundle.expires_at {
782        return Err(crate::Error::CanonicalJson(format!(
783            "runtime attestation trust bundle `{}` must not expire before it is issued",
784            bundle.bundle_id
785        )));
786    }
787    if now < bundle.issued_at {
788        return Err(crate::Error::CanonicalJson(format!(
789            "runtime attestation trust bundle `{}` is not yet valid",
790            bundle.bundle_id
791        )));
792    }
793    if now > bundle.expires_at {
794        return Err(crate::Error::CanonicalJson(format!(
795            "runtime attestation trust bundle `{}` has expired",
796            bundle.bundle_id
797        )));
798    }
799    if bundle.descriptors.is_empty() {
800        return Err(crate::Error::CanonicalJson(format!(
801            "runtime attestation trust bundle `{}` must include at least one verifier descriptor",
802            bundle.bundle_id
803        )));
804    }
805
806    let mut descriptor_ids = BTreeSet::new();
807    let mut descriptors = BTreeMap::new();
808    for descriptor in &bundle.descriptors {
809        verify_signed_runtime_attestation_verifier_descriptor(descriptor, now)?;
810        let descriptor_id = descriptor.body.descriptor_id.clone();
811        if !descriptor_ids.insert(descriptor_id.clone()) {
812            return Err(crate::Error::CanonicalJson(format!(
813                "runtime attestation trust bundle `{}` contains duplicate verifier descriptor `{descriptor_id}`",
814                bundle.bundle_id
815            )));
816        }
817        descriptors.insert(descriptor_id, &descriptor.body);
818    }
819
820    let mut reference_value_ids = BTreeSet::new();
821    let mut active_slots = BTreeSet::new();
822    let mut reference_value_states = BTreeMap::new();
823    for reference_value in &bundle.reference_values {
824        verify_signed_runtime_attestation_reference_value_set(reference_value, now)?;
825        let reference_value_id = reference_value.body.reference_value_id.clone();
826        if !reference_value_ids.insert(reference_value_id.clone()) {
827            return Err(crate::Error::CanonicalJson(format!(
828                "runtime attestation trust bundle `{}` contains duplicate reference-value set `{reference_value_id}`",
829                bundle.bundle_id
830            )));
831        }
832        let descriptor = descriptors
833            .get(&reference_value.body.descriptor_id)
834            .ok_or_else(|| {
835                crate::Error::CanonicalJson(format!(
836                "runtime attestation trust bundle `{}` references unknown verifier descriptor `{}`",
837                bundle.bundle_id, reference_value.body.descriptor_id
838            ))
839            })?;
840        if descriptor.verifier_family != reference_value.body.verifier_family {
841            return Err(crate::Error::CanonicalJson(format!(
842                "runtime attestation reference-value set `{}` does not match verifier-family {:?} of descriptor `{}`",
843                reference_value_id, descriptor.verifier_family, descriptor.descriptor_id
844            )));
845        }
846        if !descriptor
847            .attestation_schemas
848            .contains(&reference_value.body.attestation_schema)
849        {
850            return Err(crate::Error::CanonicalJson(format!(
851                "runtime attestation reference-value set `{}` uses attestation schema `{}` outside descriptor `{}`",
852                reference_value_id, reference_value.body.attestation_schema, descriptor.descriptor_id
853            )));
854        }
855        if reference_value.body.state == RuntimeAttestationReferenceValueState::Active {
856            let slot = (
857                reference_value.body.descriptor_id.clone(),
858                reference_value.body.attestation_schema.clone(),
859            );
860            if !active_slots.insert(slot) {
861                return Err(crate::Error::CanonicalJson(format!(
862                    "runtime attestation trust bundle `{}` contains ambiguous active reference values for descriptor `{}` and schema `{}`",
863                    bundle.bundle_id,
864                    reference_value.body.descriptor_id,
865                    reference_value.body.attestation_schema
866                )));
867            }
868        }
869        reference_value_states.insert(
870            reference_value_id,
871            (
872                reference_value.body.state,
873                reference_value.body.superseded_by.clone(),
874            ),
875        );
876    }
877
878    for (reference_value_id, (state, superseded_by)) in &reference_value_states {
879        if *state == RuntimeAttestationReferenceValueState::Superseded {
880            let successor = superseded_by.as_ref().ok_or_else(|| {
881                crate::Error::CanonicalJson(format!(
882                    "superseded runtime attestation reference-value set `{reference_value_id}` must include superseded_by"
883                ))
884            })?;
885            if !reference_value_states.contains_key(successor) {
886                return Err(crate::Error::CanonicalJson(format!(
887                    "runtime attestation trust bundle `{}` references unknown successor `{successor}` for superseded set `{reference_value_id}`",
888                    bundle.bundle_id
889                )));
890            }
891        }
892    }
893    Ok(())
894}
895
896fn validate_sorted_unique_strings(values: &[String], field: &str, id: &str) -> ChioResult<()> {
897    if values.iter().any(|value| value.trim().is_empty()) {
898        return Err(crate::Error::CanonicalJson(format!(
899            "{field} for `{id}` cannot contain empty values"
900        )));
901    }
902    let mut sorted = values.to_vec();
903    sorted.sort();
904    sorted.dedup();
905    if sorted != values {
906        return Err(crate::Error::CanonicalJson(format!(
907            "{field} for `{id}` must be stored in sorted unique order"
908        )));
909    }
910    Ok(())
911}
912
913#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
914#[serde(rename_all = "camelCase")]
915pub struct RuntimeAttestationVerifierDescriptor {
916    pub adapter: String,
917    pub verifier_family: AttestationVerifierFamily,
918}
919
920#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
921#[serde(rename_all = "camelCase")]
922pub struct RuntimeAttestationClaimSets {
923    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
924    pub normalized_assertions: BTreeMap<String, Value>,
925    #[serde(default, skip_serializing_if = "Vec::is_empty")]
926    pub normalized_claims: Vec<RuntimeAttestationNormalizedClaim>,
927    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
928    pub vendor_claims: BTreeMap<String, Value>,
929}
930
931#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
932#[serde(rename_all = "camelCase")]
933pub struct RuntimeAttestationPolicyProjection {
934    pub verdict: RuntimeAttestationAppraisalVerdict,
935    pub effective_tier: RuntimeAssuranceTier,
936    #[serde(default, skip_serializing_if = "Vec::is_empty")]
937    pub reason_codes: Vec<RuntimeAttestationAppraisalReasonCode>,
938    #[serde(default, skip_serializing_if = "Vec::is_empty")]
939    pub reasons: Vec<RuntimeAttestationAppraisalReason>,
940}
941
942#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
943#[serde(rename_all = "camelCase")]
944pub struct RuntimeAttestationAppraisalArtifact {
945    pub schema: String,
946    pub evidence: RuntimeAttestationEvidenceDescriptor,
947    pub verifier: RuntimeAttestationVerifierDescriptor,
948    pub claims: RuntimeAttestationClaimSets,
949    pub policy: RuntimeAttestationPolicyProjection,
950    #[serde(default, skip_serializing_if = "Option::is_none")]
951    pub workload_identity: Option<WorkloadIdentity>,
952}
953
954#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
955#[serde(rename_all = "camelCase")]
956pub struct RuntimeAttestationAppraisalArtifactInventoryEntry {
957    pub attestation_schema: String,
958    pub artifact_schema: String,
959    pub verifier_family: AttestationVerifierFamily,
960    pub adapter: String,
961    pub vendor_claim_namespace: String,
962    #[serde(default, skip_serializing_if = "Vec::is_empty")]
963    pub normalized_assertion_keys: Vec<String>,
964    #[serde(default, skip_serializing_if = "Vec::is_empty")]
965    pub normalized_claim_codes: Vec<RuntimeAttestationNormalizedClaimCode>,
966    #[serde(default, skip_serializing_if = "Vec::is_empty")]
967    pub default_reason_codes: Vec<RuntimeAttestationAppraisalReasonCode>,
968}
969
970#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
971#[serde(rename_all = "camelCase")]
972pub struct RuntimeAttestationAppraisalArtifactInventory {
973    pub schema: String,
974    #[serde(default, skip_serializing_if = "Vec::is_empty")]
975    pub entries: Vec<RuntimeAttestationAppraisalArtifactInventoryEntry>,
976}
977
978#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
979#[serde(rename_all = "camelCase")]
980pub struct RuntimeAttestationEvidenceDescriptor {
981    pub schema: String,
982    pub verifier: String,
983    pub issued_at: u64,
984    pub expires_at: u64,
985    pub evidence_sha256: String,
986}
987
988impl From<&RuntimeAttestationEvidence> for RuntimeAttestationEvidenceDescriptor {
989    fn from(value: &RuntimeAttestationEvidence) -> Self {
990        Self {
991            schema: value.schema.clone(),
992            verifier: value.verifier.clone(),
993            issued_at: value.issued_at,
994            expires_at: value.expires_at,
995            evidence_sha256: value.evidence_sha256.clone(),
996        }
997    }
998}
999
1000#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1001#[serde(rename_all = "camelCase")]
1002pub struct RuntimeAttestationAppraisal {
1003    pub schema: String,
1004    pub adapter: String,
1005    pub verifier_family: AttestationVerifierFamily,
1006    pub evidence: RuntimeAttestationEvidenceDescriptor,
1007    pub verdict: RuntimeAttestationAppraisalVerdict,
1008    pub effective_tier: RuntimeAssuranceTier,
1009    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1010    pub normalized_assertions: BTreeMap<String, Value>,
1011    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1012    pub normalized_claims: Vec<RuntimeAttestationNormalizedClaim>,
1013    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1014    pub vendor_claims: BTreeMap<String, Value>,
1015    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1016    pub reason_codes: Vec<RuntimeAttestationAppraisalReasonCode>,
1017    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1018    pub reasons: Vec<RuntimeAttestationAppraisalReason>,
1019    #[serde(default, skip_serializing_if = "Option::is_none")]
1020    pub workload_identity: Option<WorkloadIdentity>,
1021    #[serde(default, skip_serializing_if = "Option::is_none")]
1022    pub artifact: Option<RuntimeAttestationAppraisalArtifact>,
1023}
1024
1025#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1026#[serde(rename_all = "camelCase")]
1027pub struct RuntimeAttestationAppraisalRequest {
1028    pub runtime_attestation: RuntimeAttestationEvidence,
1029}
1030
1031#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1032#[serde(rename_all = "camelCase")]
1033pub struct RuntimeAttestationAppraisalResultExportRequest {
1034    pub issuer: String,
1035    pub runtime_attestation: RuntimeAttestationEvidence,
1036}
1037
1038#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1039#[serde(rename_all = "camelCase")]
1040pub struct RuntimeAttestationPolicyOutcome {
1041    pub trust_policy_configured: bool,
1042    pub accepted: bool,
1043    pub effective_tier: RuntimeAssuranceTier,
1044    #[serde(default, skip_serializing_if = "Option::is_none")]
1045    pub reason: Option<String>,
1046}
1047
1048#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1049#[serde(rename_all = "camelCase")]
1050pub struct VerifiedRuntimeAttestationProvenance {
1051    pub verifier_family: AttestationVerifierFamily,
1052    pub verifier_adapter: String,
1053    pub canonical_verifier: String,
1054    #[serde(default, skip_serializing_if = "Option::is_none")]
1055    pub matched_trust_rule: Option<String>,
1056}
1057
1058#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct VerifiedRuntimeAttestationRecord {
1061    pub evidence: RuntimeAttestationEvidence,
1062    pub appraisal: RuntimeAttestationAppraisal,
1063    pub policy_outcome: RuntimeAttestationPolicyOutcome,
1064    pub subject: RuntimeAttestationAppraisalResultSubject,
1065    pub provenance: VerifiedRuntimeAttestationProvenance,
1066    pub verified_at: u64,
1067}
1068
1069impl VerifiedRuntimeAttestationRecord {
1070    #[must_use]
1071    pub fn is_locally_accepted(&self) -> bool {
1072        self.policy_outcome.accepted
1073    }
1074
1075    #[must_use]
1076    pub fn evidence_schema(&self) -> &str {
1077        self.evidence.schema.as_str()
1078    }
1079
1080    #[must_use]
1081    pub fn evidence_sha256(&self) -> &str {
1082        self.evidence.evidence_sha256.as_str()
1083    }
1084
1085    #[must_use]
1086    pub fn canonical_verifier(&self) -> &str {
1087        self.provenance.canonical_verifier.as_str()
1088    }
1089
1090    #[must_use]
1091    pub fn verifier_family(&self) -> AttestationVerifierFamily {
1092        self.provenance.verifier_family
1093    }
1094
1095    #[must_use]
1096    pub fn effective_tier(&self) -> RuntimeAssuranceTier {
1097        self.policy_outcome.effective_tier
1098    }
1099
1100    #[must_use]
1101    pub fn workload_identity(&self) -> Option<&WorkloadIdentity> {
1102        self.subject.workload_identity.as_ref()
1103    }
1104
1105    #[must_use]
1106    pub fn matched_trust_rule(&self) -> Option<&str> {
1107        self.provenance.matched_trust_rule.as_deref()
1108    }
1109
1110    #[must_use]
1111    pub fn matches_evidence(&self, evidence: &RuntimeAttestationEvidence) -> bool {
1112        self.evidence_schema() == evidence.schema
1113            && self.evidence_sha256() == evidence.evidence_sha256
1114            && self.canonical_verifier() == canonicalize_attestation_verifier(&evidence.verifier)
1115            && verifier_family_for_attestation_schema(evidence.schema.as_str())
1116                == Some(self.verifier_family())
1117    }
1118}
1119
1120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1121#[serde(rename_all = "camelCase")]
1122pub struct RuntimeAttestationAppraisalReport {
1123    pub schema: String,
1124    pub generated_at: u64,
1125    pub appraisal: RuntimeAttestationAppraisal,
1126    pub policy_outcome: RuntimeAttestationPolicyOutcome,
1127}
1128
1129pub type SignedRuntimeAttestationAppraisalReport =
1130    SignedExportEnvelope<RuntimeAttestationAppraisalReport>;
1131pub type SignedRuntimeAttestationAppraisalResult =
1132    SignedExportEnvelope<RuntimeAttestationAppraisalResult>;
1133
1134impl RuntimeAttestationNormalizedClaimCode {
1135    #[must_use]
1136    pub fn legacy_assertion_key(self) -> &'static str {
1137        match self {
1138            Self::AttestationType => "attestationType",
1139            Self::RuntimeIdentity => "runtimeIdentity",
1140            Self::WorkloadIdentityScheme => "workloadIdentityScheme",
1141            Self::WorkloadIdentityUri => "workloadIdentityUri",
1142            Self::ModuleId => "moduleId",
1143            Self::MeasurementDigest => "digest",
1144            Self::MeasurementRegisters => "pcrs",
1145            Self::HardwareModel => "hardwareModel",
1146            Self::SecureBootState => "secureBoot",
1147        }
1148    }
1149
1150    #[must_use]
1151    pub fn category(self) -> RuntimeAttestationNormalizedClaimCategory {
1152        match self {
1153            Self::RuntimeIdentity | Self::WorkloadIdentityScheme | Self::WorkloadIdentityUri => {
1154                RuntimeAttestationNormalizedClaimCategory::Identity
1155            }
1156            Self::ModuleId | Self::MeasurementDigest | Self::MeasurementRegisters => {
1157                RuntimeAttestationNormalizedClaimCategory::Measurement
1158            }
1159            Self::AttestationType | Self::HardwareModel => {
1160                RuntimeAttestationNormalizedClaimCategory::Platform
1161            }
1162            Self::SecureBootState => RuntimeAttestationNormalizedClaimCategory::Configuration,
1163        }
1164    }
1165
1166    #[must_use]
1167    pub fn confidence(self) -> RuntimeAttestationNormalizedClaimConfidence {
1168        match self {
1169            Self::WorkloadIdentityScheme | Self::WorkloadIdentityUri => {
1170                RuntimeAttestationNormalizedClaimConfidence::Derived
1171            }
1172            _ => RuntimeAttestationNormalizedClaimConfidence::Verified,
1173        }
1174    }
1175
1176    #[must_use]
1177    pub fn freshness(self) -> RuntimeAttestationNormalizedClaimFreshness {
1178        RuntimeAttestationNormalizedClaimFreshness::EvidenceWindow
1179    }
1180
1181    #[must_use]
1182    pub fn default_provenance(self) -> RuntimeAttestationClaimProvenance {
1183        match self {
1184            Self::RuntimeIdentity => RuntimeAttestationClaimProvenance::EvidenceEnvelope,
1185            Self::WorkloadIdentityScheme | Self::WorkloadIdentityUri => {
1186                RuntimeAttestationClaimProvenance::WorkloadProjection
1187            }
1188            _ => RuntimeAttestationClaimProvenance::VendorClaims,
1189        }
1190    }
1191
1192    #[must_use]
1193    pub fn description(self) -> &'static str {
1194        match self {
1195            Self::AttestationType => {
1196                "Portable platform attestation profile or technology class."
1197            }
1198            Self::RuntimeIdentity => {
1199                "Opaque runtime identity string carried by the verified evidence."
1200            }
1201            Self::WorkloadIdentityScheme => {
1202                "Normalized scheme for projected workload identity material."
1203            }
1204            Self::WorkloadIdentityUri => {
1205                "Normalized workload identity URI when Chio has an explicit mapping."
1206            }
1207            Self::ModuleId => "Vendor-scoped enclave or module identifier.",
1208            Self::MeasurementDigest => {
1209                "Primary verified digest or measurement identifier from the vendor evidence."
1210            }
1211            Self::MeasurementRegisters => {
1212                "Verified measurement-register set preserved without claiming cross-vendor equivalence."
1213            }
1214            Self::HardwareModel => {
1215                "Verified hardware model identifier for the attested platform."
1216            }
1217            Self::SecureBootState => {
1218                "Normalized secure-boot state derived from the verified evidence."
1219            }
1220        }
1221    }
1222
1223    #[must_use]
1224    pub fn supported_verifier_families(self) -> Vec<AttestationVerifierFamily> {
1225        match self {
1226            Self::AttestationType => vec![
1227                AttestationVerifierFamily::AzureMaa,
1228                AttestationVerifierFamily::GoogleAttestation,
1229                AttestationVerifierFamily::EnterpriseVerifier,
1230            ],
1231            Self::RuntimeIdentity => vec![
1232                AttestationVerifierFamily::AzureMaa,
1233                AttestationVerifierFamily::GoogleAttestation,
1234                AttestationVerifierFamily::EnterpriseVerifier,
1235            ],
1236            Self::WorkloadIdentityScheme | Self::WorkloadIdentityUri => {
1237                vec![
1238                    AttestationVerifierFamily::AzureMaa,
1239                    AttestationVerifierFamily::GoogleAttestation,
1240                    AttestationVerifierFamily::EnterpriseVerifier,
1241                ]
1242            }
1243            Self::ModuleId | Self::MeasurementDigest | Self::MeasurementRegisters => {
1244                vec![
1245                    AttestationVerifierFamily::AwsNitro,
1246                    AttestationVerifierFamily::EnterpriseVerifier,
1247                ]
1248            }
1249            Self::HardwareModel | Self::SecureBootState => {
1250                vec![
1251                    AttestationVerifierFamily::GoogleAttestation,
1252                    AttestationVerifierFamily::EnterpriseVerifier,
1253                ]
1254            }
1255        }
1256    }
1257}
1258
1259impl RuntimeAttestationNormalizedClaim {
1260    #[must_use]
1261    pub fn new(code: RuntimeAttestationNormalizedClaimCode, value: Value) -> Self {
1262        Self {
1263            code,
1264            legacy_assertion_key: code.legacy_assertion_key().to_string(),
1265            category: code.category(),
1266            confidence: code.confidence(),
1267            freshness: code.freshness(),
1268            provenance: code.default_provenance(),
1269            value,
1270        }
1271    }
1272}
1273
1274impl RuntimeAttestationAppraisalReasonCode {
1275    #[must_use]
1276    pub fn group(self) -> RuntimeAttestationAppraisalReasonGroup {
1277        match self {
1278            Self::EvidenceVerified => RuntimeAttestationAppraisalReasonGroup::Verification,
1279            Self::UnsupportedEvidence
1280            | Self::UnsupportedClaimMapping
1281            | Self::AmbiguousClaimMapping => RuntimeAttestationAppraisalReasonGroup::Compatibility,
1282            Self::PolicyRejected => RuntimeAttestationAppraisalReasonGroup::Policy,
1283            Self::InvalidClaims | Self::MeasurementMismatch => {
1284                RuntimeAttestationAppraisalReasonGroup::Measurement
1285            }
1286            Self::EvidenceStale => RuntimeAttestationAppraisalReasonGroup::Freshness,
1287            Self::DebugStateUnknown => RuntimeAttestationAppraisalReasonGroup::DebugPosture,
1288        }
1289    }
1290
1291    #[must_use]
1292    pub fn disposition(self) -> RuntimeAttestationAppraisalReasonDisposition {
1293        match self {
1294            Self::EvidenceVerified => RuntimeAttestationAppraisalReasonDisposition::Pass,
1295            Self::UnsupportedEvidence => RuntimeAttestationAppraisalReasonDisposition::Unknown,
1296            Self::UnsupportedClaimMapping => RuntimeAttestationAppraisalReasonDisposition::Degrade,
1297            Self::AmbiguousClaimMapping
1298            | Self::PolicyRejected
1299            | Self::InvalidClaims
1300            | Self::MeasurementMismatch => RuntimeAttestationAppraisalReasonDisposition::Deny,
1301            Self::EvidenceStale => RuntimeAttestationAppraisalReasonDisposition::Degrade,
1302            Self::DebugStateUnknown => RuntimeAttestationAppraisalReasonDisposition::Warn,
1303        }
1304    }
1305
1306    #[must_use]
1307    pub fn description(self) -> &'static str {
1308        match self {
1309            Self::EvidenceVerified => {
1310                "The verifier accepted the evidence and Chio derived a portable appraisal."
1311            }
1312            Self::UnsupportedEvidence => {
1313                "The evidence schema is outside the current portable appraisal boundary."
1314            }
1315            Self::UnsupportedClaimMapping => {
1316                "Some provider output could not be represented in Chio's portable claim vocabulary."
1317            }
1318            Self::AmbiguousClaimMapping => {
1319                "Provider output could map to more than one portable meaning, so Chio fails closed."
1320            }
1321            Self::PolicyRejected => {
1322                "Local Chio policy rejected the appraisal outcome or prevented trust widening."
1323            }
1324            Self::InvalidClaims => {
1325                "The verified evidence carried claims that were structurally invalid for the expected verifier family."
1326            }
1327            Self::EvidenceStale => {
1328                "The evidence was accepted cryptographically but is too old for the requested policy posture."
1329            }
1330            Self::MeasurementMismatch => {
1331                "The verified measurement material does not satisfy the required portable policy semantics."
1332            }
1333            Self::DebugStateUnknown => {
1334                "The verifier family does not provide one portable debug-posture signal, so Chio preserves uncertainty explicitly."
1335            }
1336        }
1337    }
1338}
1339
1340impl RuntimeAttestationAppraisalReason {
1341    #[must_use]
1342    pub fn from_code(code: RuntimeAttestationAppraisalReasonCode) -> Self {
1343        Self {
1344            code,
1345            group: code.group(),
1346            disposition: code.disposition(),
1347            description: code.description().to_string(),
1348        }
1349    }
1350}
1351
1352impl RuntimeAttestationImportReasonCode {
1353    #[must_use]
1354    pub fn description(self) -> &'static str {
1355        match self {
1356            Self::NoLocalPolicy => {
1357                "No explicit local import policy was provided, so Chio rejects the foreign result fail closed."
1358            }
1359            Self::InvalidSignature => {
1360                "The signed appraisal result failed signature verification."
1361            }
1362            Self::UnsupportedAppraisalSchema => {
1363                "The imported result or nested appraisal artifact is outside Chio's supported portable appraisal boundary."
1364            }
1365            Self::ResultStale => {
1366                "The imported signed result is older than the allowed local freshness window."
1367            }
1368            Self::EvidenceStale => {
1369                "The evidence carried by the imported result is older than the allowed local freshness window."
1370            }
1371            Self::ExporterPolicyRejected => {
1372                "The exporting operator did not accept the appraisal as trust-widening evidence."
1373            }
1374            Self::UntrustedIssuer => {
1375                "The imported result issuer is not explicitly trusted by local policy."
1376            }
1377            Self::UntrustedSigner => {
1378                "The imported result signer key is not explicitly trusted by local policy."
1379            }
1380            Self::UnsupportedVerifierFamily => {
1381                "The imported verifier family is not allowed by local policy."
1382            }
1383            Self::MissingRequiredClaim => {
1384                "A required portable normalized claim is missing from the imported result."
1385            }
1386            Self::ClaimMismatch => {
1387                "A required portable normalized claim value does not match local policy."
1388            }
1389            Self::TierAttenuated => {
1390                "Chio accepted the imported result only after capping its effective runtime-assurance tier locally."
1391            }
1392        }
1393    }
1394}
1395
1396impl RuntimeAttestationImportReason {
1397    #[must_use]
1398    pub fn from_code(code: RuntimeAttestationImportReasonCode) -> Self {
1399        Self {
1400            code,
1401            description: code.description().to_string(),
1402        }
1403    }
1404}
1405
1406impl RuntimeAttestationImportedAppraisalPolicy {
1407    #[must_use]
1408    pub fn is_explicit(&self) -> bool {
1409        !self.trusted_issuers.is_empty()
1410            || !self.trusted_signer_keys.is_empty()
1411            || !self.allowed_verifier_families.is_empty()
1412            || self.max_result_age_seconds.is_some()
1413            || self.max_evidence_age_seconds.is_some()
1414            || self.maximum_effective_tier.is_some()
1415            || !self.required_claims.is_empty()
1416    }
1417}
1418
1419impl RuntimeAttestationAppraisalResult {
1420    pub fn from_report(
1421        issuer: impl Into<String>,
1422        report: &RuntimeAttestationAppraisalReport,
1423    ) -> ChioResult<Self> {
1424        let issuer = issuer.into();
1425        if issuer.trim().is_empty() {
1426            return Err(crate::Error::CanonicalJson(
1427                "runtime attestation appraisal result issuer must not be empty".to_string(),
1428            ));
1429        }
1430        let appraisal = report.appraisal.artifact.clone().ok_or_else(|| {
1431            crate::Error::CanonicalJson(
1432                "runtime attestation appraisal report is missing the nested artifact".to_string(),
1433            )
1434        })?;
1435        let subject = RuntimeAttestationAppraisalResultSubject {
1436            runtime_identity: report
1437                .appraisal
1438                .normalized_assertions
1439                .get("runtimeIdentity")
1440                .and_then(Value::as_str)
1441                .map(ToOwned::to_owned),
1442            workload_identity: report.appraisal.workload_identity.clone(),
1443        };
1444        let descriptor = serde_json::json!({
1445            "schema": RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA,
1446            "exportedAt": report.generated_at,
1447            "issuer": issuer,
1448            "appraisal": appraisal,
1449            "exporterPolicyOutcome": report.policy_outcome,
1450            "subject": subject,
1451        });
1452        let result_id = format!(
1453            "appraisal-result-{}",
1454            sha256_hex(&canonical_json_bytes(&descriptor)?)
1455        );
1456
1457        Ok(Self {
1458            schema: RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA.to_string(),
1459            result_id,
1460            exported_at: report.generated_at,
1461            issuer,
1462            appraisal,
1463            exporter_policy_outcome: report.policy_outcome.clone(),
1464            subject,
1465        })
1466    }
1467}
1468
1469struct RuntimeAttestationArtifactArgs<'a> {
1470    adapter: String,
1471    verifier_family: AttestationVerifierFamily,
1472    evidence: &'a RuntimeAttestationEvidence,
1473    normalized_assertions: BTreeMap<String, Value>,
1474    vendor_claims: BTreeMap<String, Value>,
1475    verdict: RuntimeAttestationAppraisalVerdict,
1476    effective_tier: RuntimeAssuranceTier,
1477    reason_codes: Vec<RuntimeAttestationAppraisalReasonCode>,
1478}
1479
1480impl RuntimeAttestationAppraisal {
1481    fn artifact(args: RuntimeAttestationArtifactArgs<'_>) -> RuntimeAttestationAppraisalArtifact {
1482        let normalized_claims = normalized_claims_from_assertions(&args.normalized_assertions);
1483        let reasons = reasons_from_codes(&args.reason_codes);
1484        RuntimeAttestationAppraisalArtifact {
1485            schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
1486            evidence: RuntimeAttestationEvidenceDescriptor::from(args.evidence),
1487            verifier: RuntimeAttestationVerifierDescriptor {
1488                adapter: args.adapter,
1489                verifier_family: args.verifier_family,
1490            },
1491            claims: RuntimeAttestationClaimSets {
1492                normalized_assertions: args.normalized_assertions,
1493                normalized_claims,
1494                vendor_claims: args.vendor_claims,
1495            },
1496            policy: RuntimeAttestationPolicyProjection {
1497                verdict: args.verdict,
1498                effective_tier: args.effective_tier,
1499                reason_codes: args.reason_codes,
1500                reasons,
1501            },
1502            workload_identity: args.evidence.workload_identity.clone(),
1503        }
1504    }
1505
1506    #[must_use]
1507    pub fn accepted(
1508        adapter: impl Into<String>,
1509        verifier_family: AttestationVerifierFamily,
1510        evidence: &RuntimeAttestationEvidence,
1511        normalized_assertions: BTreeMap<String, Value>,
1512        vendor_claims: BTreeMap<String, Value>,
1513        reason_codes: Vec<RuntimeAttestationAppraisalReasonCode>,
1514    ) -> Self {
1515        let adapter = adapter.into();
1516        let normalized_claims = normalized_claims_from_assertions(&normalized_assertions);
1517        let reasons = reasons_from_codes(&reason_codes);
1518        let artifact = Self::artifact(RuntimeAttestationArtifactArgs {
1519            adapter: adapter.clone(),
1520            verifier_family,
1521            evidence,
1522            normalized_assertions: normalized_assertions.clone(),
1523            vendor_claims: vendor_claims.clone(),
1524            verdict: RuntimeAttestationAppraisalVerdict::Accepted,
1525            effective_tier: evidence.tier,
1526            reason_codes: reason_codes.clone(),
1527        });
1528        Self {
1529            schema: RUNTIME_ATTESTATION_APPRAISAL_SCHEMA.to_string(),
1530            adapter,
1531            verifier_family,
1532            evidence: RuntimeAttestationEvidenceDescriptor::from(evidence),
1533            verdict: RuntimeAttestationAppraisalVerdict::Accepted,
1534            effective_tier: evidence.tier,
1535            normalized_assertions,
1536            normalized_claims,
1537            vendor_claims,
1538            reason_codes,
1539            reasons,
1540            workload_identity: evidence.workload_identity.clone(),
1541            artifact: Some(artifact),
1542        }
1543    }
1544
1545    #[must_use]
1546    pub fn rejected(
1547        adapter: impl Into<String>,
1548        verifier_family: AttestationVerifierFamily,
1549        evidence: &RuntimeAttestationEvidence,
1550        normalized_assertions: BTreeMap<String, Value>,
1551        vendor_claims: BTreeMap<String, Value>,
1552        reason_codes: Vec<RuntimeAttestationAppraisalReasonCode>,
1553    ) -> Self {
1554        let adapter = adapter.into();
1555        let normalized_claims = normalized_claims_from_assertions(&normalized_assertions);
1556        let reasons = reasons_from_codes(&reason_codes);
1557        let artifact = Self::artifact(RuntimeAttestationArtifactArgs {
1558            adapter: adapter.clone(),
1559            verifier_family,
1560            evidence,
1561            normalized_assertions: normalized_assertions.clone(),
1562            vendor_claims: vendor_claims.clone(),
1563            verdict: RuntimeAttestationAppraisalVerdict::Rejected,
1564            effective_tier: RuntimeAssuranceTier::None,
1565            reason_codes: reason_codes.clone(),
1566        });
1567        Self {
1568            schema: RUNTIME_ATTESTATION_APPRAISAL_SCHEMA.to_string(),
1569            adapter,
1570            verifier_family,
1571            evidence: RuntimeAttestationEvidenceDescriptor::from(evidence),
1572            verdict: RuntimeAttestationAppraisalVerdict::Rejected,
1573            effective_tier: RuntimeAssuranceTier::None,
1574            normalized_assertions,
1575            normalized_claims,
1576            vendor_claims,
1577            reason_codes,
1578            reasons,
1579            workload_identity: evidence.workload_identity.clone(),
1580            artifact: Some(artifact),
1581        }
1582    }
1583}
1584
1585#[must_use]
1586pub fn runtime_attestation_appraisal_artifact_inventory(
1587) -> RuntimeAttestationAppraisalArtifactInventory {
1588    RuntimeAttestationAppraisalArtifactInventory {
1589        schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_INVENTORY_SCHEMA.to_string(),
1590        entries: vec![
1591            RuntimeAttestationAppraisalArtifactInventoryEntry {
1592                attestation_schema: AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
1593                artifact_schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
1594                verifier_family: AttestationVerifierFamily::AzureMaa,
1595                adapter: AZURE_MAA_VERIFIER_ADAPTER.to_string(),
1596                vendor_claim_namespace: "azureMaa".to_string(),
1597                normalized_assertion_keys: vec![
1598                    "attestationType".to_string(),
1599                    "runtimeIdentity".to_string(),
1600                    "workloadIdentityScheme".to_string(),
1601                    "workloadIdentityUri".to_string(),
1602                ],
1603                normalized_claim_codes: vec![
1604                    RuntimeAttestationNormalizedClaimCode::AttestationType,
1605                    RuntimeAttestationNormalizedClaimCode::RuntimeIdentity,
1606                    RuntimeAttestationNormalizedClaimCode::WorkloadIdentityScheme,
1607                    RuntimeAttestationNormalizedClaimCode::WorkloadIdentityUri,
1608                ],
1609                default_reason_codes: vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1610            },
1611            RuntimeAttestationAppraisalArtifactInventoryEntry {
1612                attestation_schema: AWS_NITRO_ATTESTATION_SCHEMA.to_string(),
1613                artifact_schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
1614                verifier_family: AttestationVerifierFamily::AwsNitro,
1615                adapter: AWS_NITRO_VERIFIER_ADAPTER.to_string(),
1616                vendor_claim_namespace: "awsNitro".to_string(),
1617                normalized_assertion_keys: vec![
1618                    "moduleId".to_string(),
1619                    "digest".to_string(),
1620                    "pcrs".to_string(),
1621                ],
1622                normalized_claim_codes: vec![
1623                    RuntimeAttestationNormalizedClaimCode::ModuleId,
1624                    RuntimeAttestationNormalizedClaimCode::MeasurementDigest,
1625                    RuntimeAttestationNormalizedClaimCode::MeasurementRegisters,
1626                ],
1627                default_reason_codes: vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1628            },
1629            RuntimeAttestationAppraisalArtifactInventoryEntry {
1630                attestation_schema: GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA.to_string(),
1631                artifact_schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
1632                verifier_family: AttestationVerifierFamily::GoogleAttestation,
1633                adapter: GOOGLE_CONFIDENTIAL_VM_VERIFIER_ADAPTER.to_string(),
1634                vendor_claim_namespace: "googleAttestation".to_string(),
1635                normalized_assertion_keys: vec![
1636                    "attestationType".to_string(),
1637                    "hardwareModel".to_string(),
1638                    "secureBoot".to_string(),
1639                    "runtimeIdentity".to_string(),
1640                    "workloadIdentityScheme".to_string(),
1641                    "workloadIdentityUri".to_string(),
1642                ],
1643                normalized_claim_codes: vec![
1644                    RuntimeAttestationNormalizedClaimCode::AttestationType,
1645                    RuntimeAttestationNormalizedClaimCode::HardwareModel,
1646                    RuntimeAttestationNormalizedClaimCode::SecureBootState,
1647                    RuntimeAttestationNormalizedClaimCode::RuntimeIdentity,
1648                    RuntimeAttestationNormalizedClaimCode::WorkloadIdentityScheme,
1649                    RuntimeAttestationNormalizedClaimCode::WorkloadIdentityUri,
1650                ],
1651                default_reason_codes: vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1652            },
1653            RuntimeAttestationAppraisalArtifactInventoryEntry {
1654                attestation_schema: ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA.to_string(),
1655                artifact_schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
1656                verifier_family: AttestationVerifierFamily::EnterpriseVerifier,
1657                adapter: ENTERPRISE_VERIFIER_ADAPTER.to_string(),
1658                vendor_claim_namespace: "enterpriseVerifier".to_string(),
1659                normalized_assertion_keys: vec![
1660                    "attestationType".to_string(),
1661                    "runtimeIdentity".to_string(),
1662                    "workloadIdentityScheme".to_string(),
1663                    "workloadIdentityUri".to_string(),
1664                    "moduleId".to_string(),
1665                    "digest".to_string(),
1666                    "pcrs".to_string(),
1667                    "hardwareModel".to_string(),
1668                    "secureBoot".to_string(),
1669                ],
1670                normalized_claim_codes: vec![
1671                    RuntimeAttestationNormalizedClaimCode::AttestationType,
1672                    RuntimeAttestationNormalizedClaimCode::RuntimeIdentity,
1673                    RuntimeAttestationNormalizedClaimCode::WorkloadIdentityScheme,
1674                    RuntimeAttestationNormalizedClaimCode::WorkloadIdentityUri,
1675                    RuntimeAttestationNormalizedClaimCode::ModuleId,
1676                    RuntimeAttestationNormalizedClaimCode::MeasurementDigest,
1677                    RuntimeAttestationNormalizedClaimCode::MeasurementRegisters,
1678                    RuntimeAttestationNormalizedClaimCode::HardwareModel,
1679                    RuntimeAttestationNormalizedClaimCode::SecureBootState,
1680                ],
1681                default_reason_codes: vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1682            },
1683        ],
1684    }
1685}
1686
1687#[must_use]
1688pub fn runtime_attestation_normalized_claim_vocabulary(
1689) -> RuntimeAttestationNormalizedClaimVocabulary {
1690    let entries = vec![
1691        RuntimeAttestationNormalizedClaimCode::AttestationType,
1692        RuntimeAttestationNormalizedClaimCode::RuntimeIdentity,
1693        RuntimeAttestationNormalizedClaimCode::WorkloadIdentityScheme,
1694        RuntimeAttestationNormalizedClaimCode::WorkloadIdentityUri,
1695        RuntimeAttestationNormalizedClaimCode::ModuleId,
1696        RuntimeAttestationNormalizedClaimCode::MeasurementDigest,
1697        RuntimeAttestationNormalizedClaimCode::MeasurementRegisters,
1698        RuntimeAttestationNormalizedClaimCode::HardwareModel,
1699        RuntimeAttestationNormalizedClaimCode::SecureBootState,
1700    ]
1701    .into_iter()
1702    .map(|code| RuntimeAttestationNormalizedClaimVocabularyEntry {
1703        code,
1704        legacy_assertion_key: code.legacy_assertion_key().to_string(),
1705        category: code.category(),
1706        confidence: code.confidence(),
1707        freshness: code.freshness(),
1708        description: code.description().to_string(),
1709        supported_verifier_families: code.supported_verifier_families(),
1710    })
1711    .collect();
1712
1713    RuntimeAttestationNormalizedClaimVocabulary {
1714        schema: RUNTIME_ATTESTATION_NORMALIZED_CLAIM_VOCABULARY_SCHEMA.to_string(),
1715        entries,
1716    }
1717}
1718
1719#[must_use]
1720pub fn runtime_attestation_reason_taxonomy() -> RuntimeAttestationReasonTaxonomy {
1721    let entries = vec![
1722        RuntimeAttestationAppraisalReasonCode::EvidenceVerified,
1723        RuntimeAttestationAppraisalReasonCode::UnsupportedEvidence,
1724        RuntimeAttestationAppraisalReasonCode::UnsupportedClaimMapping,
1725        RuntimeAttestationAppraisalReasonCode::AmbiguousClaimMapping,
1726        RuntimeAttestationAppraisalReasonCode::PolicyRejected,
1727        RuntimeAttestationAppraisalReasonCode::InvalidClaims,
1728        RuntimeAttestationAppraisalReasonCode::EvidenceStale,
1729        RuntimeAttestationAppraisalReasonCode::MeasurementMismatch,
1730        RuntimeAttestationAppraisalReasonCode::DebugStateUnknown,
1731    ]
1732    .into_iter()
1733    .map(RuntimeAttestationAppraisalReason::from_code)
1734    .collect();
1735
1736    RuntimeAttestationReasonTaxonomy {
1737        schema: RUNTIME_ATTESTATION_REASON_TAXONOMY_SCHEMA.to_string(),
1738        entries,
1739    }
1740}
1741
1742#[must_use]
1743pub fn verifier_family_for_attestation_schema(schema: &str) -> Option<AttestationVerifierFamily> {
1744    match schema {
1745        AZURE_MAA_ATTESTATION_SCHEMA => Some(AttestationVerifierFamily::AzureMaa),
1746        AWS_NITRO_ATTESTATION_SCHEMA => Some(AttestationVerifierFamily::AwsNitro),
1747        GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA => {
1748            Some(AttestationVerifierFamily::GoogleAttestation)
1749        }
1750        ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA => {
1751            Some(AttestationVerifierFamily::EnterpriseVerifier)
1752        }
1753        _ => None,
1754    }
1755}
1756
1757fn normalized_claim_code_for_assertion_key(
1758    key: &str,
1759) -> Option<RuntimeAttestationNormalizedClaimCode> {
1760    match key {
1761        "attestationType" => Some(RuntimeAttestationNormalizedClaimCode::AttestationType),
1762        "runtimeIdentity" => Some(RuntimeAttestationNormalizedClaimCode::RuntimeIdentity),
1763        "workloadIdentityScheme" => {
1764            Some(RuntimeAttestationNormalizedClaimCode::WorkloadIdentityScheme)
1765        }
1766        "workloadIdentityUri" => Some(RuntimeAttestationNormalizedClaimCode::WorkloadIdentityUri),
1767        "moduleId" => Some(RuntimeAttestationNormalizedClaimCode::ModuleId),
1768        "digest" => Some(RuntimeAttestationNormalizedClaimCode::MeasurementDigest),
1769        "pcrs" => Some(RuntimeAttestationNormalizedClaimCode::MeasurementRegisters),
1770        "hardwareModel" => Some(RuntimeAttestationNormalizedClaimCode::HardwareModel),
1771        "secureBoot" => Some(RuntimeAttestationNormalizedClaimCode::SecureBootState),
1772        _ => None,
1773    }
1774}
1775
1776fn normalized_claims_from_assertions(
1777    normalized_assertions: &BTreeMap<String, Value>,
1778) -> Vec<RuntimeAttestationNormalizedClaim> {
1779    normalized_assertions
1780        .iter()
1781        .filter_map(|(key, value)| {
1782            normalized_claim_code_for_assertion_key(key)
1783                .map(|code| RuntimeAttestationNormalizedClaim::new(code, value.clone()))
1784        })
1785        .collect()
1786}
1787
1788fn reasons_from_codes(
1789    reason_codes: &[RuntimeAttestationAppraisalReasonCode],
1790) -> Vec<RuntimeAttestationAppraisalReason> {
1791    reason_codes
1792        .iter()
1793        .copied()
1794        .map(RuntimeAttestationAppraisalReason::from_code)
1795        .collect()
1796}
1797
1798fn import_reasons_from_codes(
1799    reason_codes: &[RuntimeAttestationImportReasonCode],
1800) -> Vec<RuntimeAttestationImportReason> {
1801    reason_codes
1802        .iter()
1803        .copied()
1804        .map(RuntimeAttestationImportReason::from_code)
1805        .collect()
1806}
1807
1808fn normalized_claim_value_string(claim: &RuntimeAttestationNormalizedClaim) -> Option<String> {
1809    match &claim.value {
1810        Value::String(value) => Some(value.clone()),
1811        Value::Bool(value) => Some(value.to_string()),
1812        Value::Number(value) => Some(value.to_string()),
1813        other => serde_json::to_string(other).ok(),
1814    }
1815}
1816
1817#[must_use]
1818pub fn evaluate_imported_runtime_attestation_appraisal(
1819    request: &RuntimeAttestationAppraisalImportRequest,
1820    now: u64,
1821) -> RuntimeAttestationAppraisalImportReport {
1822    let result = request.signed_result.body.clone();
1823    let signer_key_hex = request.signed_result.signer_key.to_hex();
1824    let mut reason_codes = Vec::new();
1825
1826    if !request.local_policy.is_explicit() {
1827        reason_codes.push(RuntimeAttestationImportReasonCode::NoLocalPolicy);
1828    }
1829    if !request.signed_result.verify_signature().unwrap_or(false) {
1830        reason_codes.push(RuntimeAttestationImportReasonCode::InvalidSignature);
1831    }
1832    if result.schema != RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA
1833        || result.appraisal.schema != RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA
1834    {
1835        reason_codes.push(RuntimeAttestationImportReasonCode::UnsupportedAppraisalSchema);
1836    }
1837    match verifier_family_for_attestation_schema(&result.appraisal.evidence.schema) {
1838        Some(expected_family) if expected_family == result.appraisal.verifier.verifier_family => {}
1839        _ => reason_codes.push(RuntimeAttestationImportReasonCode::UnsupportedAppraisalSchema),
1840    }
1841    if let Some(max_result_age_seconds) = request.local_policy.max_result_age_seconds {
1842        let age = now.saturating_sub(result.exported_at);
1843        if age > max_result_age_seconds {
1844            reason_codes.push(RuntimeAttestationImportReasonCode::ResultStale);
1845        }
1846    }
1847    if let Some(max_evidence_age_seconds) = request.local_policy.max_evidence_age_seconds {
1848        let age = now.saturating_sub(result.appraisal.evidence.issued_at);
1849        if age > max_evidence_age_seconds {
1850            reason_codes.push(RuntimeAttestationImportReasonCode::EvidenceStale);
1851        }
1852    }
1853    if !result.exporter_policy_outcome.accepted
1854        || result.appraisal.policy.verdict != RuntimeAttestationAppraisalVerdict::Accepted
1855    {
1856        reason_codes.push(RuntimeAttestationImportReasonCode::ExporterPolicyRejected);
1857    }
1858    if !request.local_policy.trusted_issuers.is_empty()
1859        && !request
1860            .local_policy
1861            .trusted_issuers
1862            .iter()
1863            .any(|trusted| trusted == &result.issuer)
1864    {
1865        reason_codes.push(RuntimeAttestationImportReasonCode::UntrustedIssuer);
1866    }
1867    if !request.local_policy.trusted_signer_keys.is_empty()
1868        && !request
1869            .local_policy
1870            .trusted_signer_keys
1871            .iter()
1872            .any(|trusted| trusted == &signer_key_hex)
1873    {
1874        reason_codes.push(RuntimeAttestationImportReasonCode::UntrustedSigner);
1875    }
1876    if !request.local_policy.allowed_verifier_families.is_empty()
1877        && !request
1878            .local_policy
1879            .allowed_verifier_families
1880            .contains(&result.appraisal.verifier.verifier_family)
1881    {
1882        reason_codes.push(RuntimeAttestationImportReasonCode::UnsupportedVerifierFamily);
1883    }
1884
1885    for (required_code, expected_value) in &request.local_policy.required_claims {
1886        let actual = result
1887            .appraisal
1888            .claims
1889            .normalized_claims
1890            .iter()
1891            .find(|claim| &claim.code == required_code);
1892        match actual {
1893            Some(claim) => {
1894                let actual =
1895                    normalized_claim_value_string(claim).unwrap_or_else(|| "null".to_string());
1896                if &actual != expected_value {
1897                    reason_codes.push(RuntimeAttestationImportReasonCode::ClaimMismatch);
1898                }
1899            }
1900            None => reason_codes.push(RuntimeAttestationImportReasonCode::MissingRequiredClaim),
1901        }
1902    }
1903
1904    let imported_tier = result
1905        .appraisal
1906        .policy
1907        .effective_tier
1908        .min(result.exporter_policy_outcome.effective_tier);
1909    let mut disposition = RuntimeAttestationImportDisposition::Allow;
1910    let mut effective_tier = imported_tier;
1911
1912    if !reason_codes.is_empty() {
1913        disposition = RuntimeAttestationImportDisposition::Reject;
1914        effective_tier = RuntimeAssuranceTier::None;
1915    } else if let Some(maximum_effective_tier) = request.local_policy.maximum_effective_tier {
1916        if imported_tier > maximum_effective_tier {
1917            disposition = RuntimeAttestationImportDisposition::Attenuate;
1918            effective_tier = maximum_effective_tier;
1919            reason_codes.push(RuntimeAttestationImportReasonCode::TierAttenuated);
1920        }
1921    }
1922
1923    RuntimeAttestationAppraisalImportReport {
1924        schema: RUNTIME_ATTESTATION_APPRAISAL_IMPORT_REPORT_SCHEMA.to_string(),
1925        evaluated_at: now,
1926        signer_key_hex,
1927        result,
1928        local_policy_outcome: RuntimeAttestationAppraisalImportOutcome {
1929            disposition,
1930            effective_tier,
1931            reasons: import_reasons_from_codes(&reason_codes),
1932            reason_codes,
1933        },
1934    }
1935}
1936
1937pub fn derive_runtime_attestation_appraisal(
1938    evidence: &RuntimeAttestationEvidence,
1939) -> Result<RuntimeAttestationAppraisal, RuntimeAttestationAppraisalError> {
1940    match evidence.schema.as_str() {
1941        AZURE_MAA_ATTESTATION_SCHEMA => Ok(RuntimeAttestationAppraisal::accepted(
1942            AZURE_MAA_VERIFIER_ADAPTER,
1943            AttestationVerifierFamily::AzureMaa,
1944            evidence,
1945            azure_normalized_assertions(evidence),
1946            extract_vendor_claims(evidence, "azureMaa"),
1947            vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1948        )),
1949        AWS_NITRO_ATTESTATION_SCHEMA => Ok(RuntimeAttestationAppraisal::accepted(
1950            AWS_NITRO_VERIFIER_ADAPTER,
1951            AttestationVerifierFamily::AwsNitro,
1952            evidence,
1953            aws_nitro_normalized_assertions(evidence),
1954            extract_vendor_claims(evidence, "awsNitro"),
1955            vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1956        )),
1957        GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA => Ok(RuntimeAttestationAppraisal::accepted(
1958            GOOGLE_CONFIDENTIAL_VM_VERIFIER_ADAPTER,
1959            AttestationVerifierFamily::GoogleAttestation,
1960            evidence,
1961            google_confidential_vm_normalized_assertions(evidence),
1962            extract_vendor_claims(evidence, "googleAttestation"),
1963            vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1964        )),
1965        ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA => Ok(RuntimeAttestationAppraisal::accepted(
1966            ENTERPRISE_VERIFIER_ADAPTER,
1967            AttestationVerifierFamily::EnterpriseVerifier,
1968            evidence,
1969            enterprise_verifier_normalized_assertions(evidence),
1970            extract_vendor_claims(evidence, "enterpriseVerifier"),
1971            vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
1972        )),
1973        _ => Err(RuntimeAttestationAppraisalError::UnsupportedSchema {
1974            schema: evidence.schema.clone(),
1975        }),
1976    }
1977}
1978
1979pub fn verify_runtime_attestation_record(
1980    evidence: &RuntimeAttestationEvidence,
1981    trust_policy: Option<&AttestationTrustPolicy>,
1982    now: u64,
1983) -> Result<VerifiedRuntimeAttestationRecord, RuntimeAttestationVerificationError> {
1984    let appraisal = derive_runtime_attestation_appraisal(evidence)?;
1985    let subject = verified_runtime_attestation_subject(evidence)?;
1986    let policy_outcome = verify_runtime_attestation_policy_outcome(evidence, trust_policy, now)?;
1987    Ok(VerifiedRuntimeAttestationRecord {
1988        evidence: evidence.clone(),
1989        provenance: VerifiedRuntimeAttestationProvenance {
1990            verifier_family: appraisal.verifier_family,
1991            verifier_adapter: appraisal.adapter.clone(),
1992            canonical_verifier: canonicalize_attestation_verifier(&evidence.verifier),
1993            matched_trust_rule: policy_outcome.matched_trust_rule.clone(),
1994        },
1995        appraisal,
1996        policy_outcome: policy_outcome.outcome,
1997        subject,
1998        verified_at: now,
1999    })
2000}
2001
2002fn verified_runtime_attestation_subject(
2003    evidence: &RuntimeAttestationEvidence,
2004) -> Result<RuntimeAttestationAppraisalResultSubject, RuntimeAttestationVerificationError> {
2005    Ok(RuntimeAttestationAppraisalResultSubject {
2006        runtime_identity: evidence.runtime_identity.clone(),
2007        workload_identity: evidence.normalized_workload_identity()?,
2008    })
2009}
2010
2011#[derive(Debug, Clone)]
2012struct VerifiedRuntimeAttestationPolicyVerification {
2013    outcome: RuntimeAttestationPolicyOutcome,
2014    matched_trust_rule: Option<String>,
2015}
2016
2017fn verify_runtime_attestation_policy_outcome(
2018    evidence: &RuntimeAttestationEvidence,
2019    trust_policy: Option<&AttestationTrustPolicy>,
2020    now: u64,
2021) -> Result<VerifiedRuntimeAttestationPolicyVerification, RuntimeAttestationVerificationError> {
2022    let trust_policy_configured = trust_policy.is_some_and(|policy| !policy.rules.is_empty());
2023    if trust_policy_configured {
2024        let resolved = evidence
2025            .resolve_effective_runtime_assurance(trust_policy, now)
2026            .map_err(RuntimeAttestationVerificationError::TrustPolicy)?;
2027        let matched_trust_rule = resolved.matched_rule.clone();
2028        return Ok(VerifiedRuntimeAttestationPolicyVerification {
2029            outcome: RuntimeAttestationPolicyOutcome {
2030                trust_policy_configured: true,
2031                accepted: true,
2032                effective_tier: resolved.effective_tier,
2033                reason: matched_trust_rule
2034                    .as_ref()
2035                    .map(|rule| format!("matched attestation trust rule `{rule}`")),
2036            },
2037            matched_trust_rule,
2038        });
2039    }
2040
2041    evidence.validate_workload_identity_binding()?;
2042    if !evidence.is_valid_at(now) {
2043        return Err(RuntimeAttestationVerificationError::StaleEvidence {
2044            now,
2045            issued_at: evidence.issued_at,
2046            expires_at: evidence.expires_at,
2047        });
2048    }
2049
2050    Ok(VerifiedRuntimeAttestationPolicyVerification {
2051        outcome: RuntimeAttestationPolicyOutcome {
2052            trust_policy_configured: false,
2053            accepted: false,
2054            effective_tier: RuntimeAssuranceTier::None,
2055            reason: Some(
2056                "runtime attestation evidence did not cross a local verified trust boundary"
2057                    .to_string(),
2058            ),
2059        },
2060        matched_trust_rule: None,
2061    })
2062}
2063
2064fn extract_vendor_claims(
2065    evidence: &RuntimeAttestationEvidence,
2066    vendor_key: &str,
2067) -> BTreeMap<String, Value> {
2068    evidence
2069        .claims
2070        .as_ref()
2071        .and_then(|claims| claims.get(vendor_key))
2072        .and_then(Value::as_object)
2073        .map(|claims| {
2074            claims
2075                .iter()
2076                .map(|(key, value)| (key.clone(), value.clone()))
2077                .collect()
2078        })
2079        .unwrap_or_default()
2080}
2081
2082fn azure_normalized_assertions(evidence: &RuntimeAttestationEvidence) -> BTreeMap<String, Value> {
2083    let vendor_claims = extract_vendor_claims(evidence, "azureMaa");
2084    let mut normalized = BTreeMap::new();
2085    if let Some(attestation_type) = vendor_claims.get("attestationType") {
2086        normalized.insert("attestationType".to_string(), attestation_type.clone());
2087    }
2088    if let Some(runtime_identity) = evidence.runtime_identity.as_ref() {
2089        normalized.insert(
2090            "runtimeIdentity".to_string(),
2091            Value::String(runtime_identity.clone()),
2092        );
2093    }
2094    push_workload_identity_assertions(&mut normalized, evidence.workload_identity.as_ref());
2095    normalized
2096}
2097
2098fn aws_nitro_normalized_assertions(
2099    evidence: &RuntimeAttestationEvidence,
2100) -> BTreeMap<String, Value> {
2101    let vendor_claims = extract_vendor_claims(evidence, "awsNitro");
2102    let mut normalized = BTreeMap::new();
2103    if let Some(module_id) = vendor_claims.get("moduleId") {
2104        normalized.insert("moduleId".to_string(), module_id.clone());
2105    }
2106    if let Some(digest) = vendor_claims.get("digest") {
2107        normalized.insert("digest".to_string(), digest.clone());
2108    }
2109    if let Some(pcrs) = vendor_claims.get("pcrs") {
2110        normalized.insert("pcrs".to_string(), pcrs.clone());
2111    }
2112    normalized
2113}
2114
2115fn google_confidential_vm_normalized_assertions(
2116    evidence: &RuntimeAttestationEvidence,
2117) -> BTreeMap<String, Value> {
2118    let vendor_claims = extract_vendor_claims(evidence, "googleAttestation");
2119    let mut normalized = BTreeMap::new();
2120    if let Some(attestation_type) = vendor_claims.get("attestationType") {
2121        normalized.insert("attestationType".to_string(), attestation_type.clone());
2122    }
2123    if let Some(hardware_model) = vendor_claims.get("hardwareModel") {
2124        normalized.insert("hardwareModel".to_string(), hardware_model.clone());
2125    }
2126    if let Some(secure_boot) = vendor_claims.get("secureBoot") {
2127        normalized.insert("secureBoot".to_string(), secure_boot.clone());
2128    }
2129    if let Some(runtime_identity) = evidence.runtime_identity.as_ref() {
2130        normalized.insert(
2131            "runtimeIdentity".to_string(),
2132            Value::String(runtime_identity.clone()),
2133        );
2134    }
2135    push_workload_identity_assertions(&mut normalized, evidence.workload_identity.as_ref());
2136    normalized
2137}
2138
2139fn enterprise_verifier_normalized_assertions(
2140    evidence: &RuntimeAttestationEvidence,
2141) -> BTreeMap<String, Value> {
2142    let vendor_claims = extract_vendor_claims(evidence, "enterpriseVerifier");
2143    let mut normalized = BTreeMap::new();
2144    for key in [
2145        "attestationType",
2146        "moduleId",
2147        "digest",
2148        "pcrs",
2149        "hardwareModel",
2150        "secureBoot",
2151    ] {
2152        if let Some(value) = vendor_claims.get(key) {
2153            normalized.insert(key.to_string(), value.clone());
2154        }
2155    }
2156    if let Some(runtime_identity) = evidence.runtime_identity.as_ref() {
2157        normalized.insert(
2158            "runtimeIdentity".to_string(),
2159            Value::String(runtime_identity.clone()),
2160        );
2161    }
2162    push_workload_identity_assertions(&mut normalized, evidence.workload_identity.as_ref());
2163    normalized
2164}
2165
2166fn push_workload_identity_assertions(
2167    normalized: &mut BTreeMap<String, Value>,
2168    workload_identity: Option<&WorkloadIdentity>,
2169) {
2170    if let Some(workload_identity) = workload_identity {
2171        normalized.insert(
2172            "workloadIdentityScheme".to_string(),
2173            Value::String(format!("{:?}", workload_identity.scheme).to_lowercase()),
2174        );
2175        normalized.insert(
2176            "workloadIdentityUri".to_string(),
2177            Value::String(workload_identity.uri.clone()),
2178        );
2179    }
2180}
2181
2182#[cfg(test)]
2183mod tests {
2184    use super::*;
2185    use crate::capability::{
2186        AttestationTrustPolicy, AttestationTrustRule, RuntimeAssuranceTier,
2187        RuntimeAttestationEvidence, WorkloadCredentialKind, WorkloadIdentity,
2188        WorkloadIdentityScheme,
2189    };
2190    use serde_json::json;
2191
2192    fn sample_evidence() -> RuntimeAttestationEvidence {
2193        RuntimeAttestationEvidence {
2194            schema: "chio.runtime-attestation.azure-maa.jwt.v1".to_string(),
2195            verifier: "https://maa.contoso.test".to_string(),
2196            tier: RuntimeAssuranceTier::Attested,
2197            issued_at: 100,
2198            expires_at: 200,
2199            evidence_sha256: "abc123".to_string(),
2200            runtime_identity: Some("spiffe://contoso.test/runtime/worker".to_string()),
2201            workload_identity: Some(WorkloadIdentity {
2202                scheme: WorkloadIdentityScheme::Spiffe,
2203                credential_kind: WorkloadCredentialKind::Uri,
2204                uri: "spiffe://contoso.test/runtime/worker".to_string(),
2205                trust_domain: "contoso.test".to_string(),
2206                path: "/runtime/worker".to_string(),
2207            }),
2208            claims: Some(json!({
2209                "azureMaa": {
2210                    "attestationType": "sgx"
2211                }
2212            })),
2213        }
2214    }
2215
2216    fn sample_trust_policy() -> AttestationTrustPolicy {
2217        AttestationTrustPolicy {
2218            rules: vec![AttestationTrustRule {
2219                name: "azure-contoso".to_string(),
2220                schema: AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
2221                verifier: "https://maa.contoso.test".to_string(),
2222                effective_tier: RuntimeAssuranceTier::Verified,
2223                verifier_family: Some(AttestationVerifierFamily::AzureMaa),
2224                max_evidence_age_seconds: Some(120),
2225                allowed_attestation_types: vec!["sgx".to_string()],
2226                required_assertions: BTreeMap::new(),
2227            }],
2228        }
2229    }
2230
2231    fn sample_nitro_evidence() -> RuntimeAttestationEvidence {
2232        RuntimeAttestationEvidence {
2233            schema: AWS_NITRO_ATTESTATION_SCHEMA.to_string(),
2234            verifier: "https://nitro.aws.example/".to_string(),
2235            tier: RuntimeAssuranceTier::Attested,
2236            issued_at: 100,
2237            expires_at: 200,
2238            evidence_sha256: "nitro-digest-1".to_string(),
2239            runtime_identity: None,
2240            workload_identity: None,
2241            claims: Some(json!({
2242                "awsNitro": {
2243                    "moduleId": "nitro-enclave-1",
2244                    "digest": "sha384:nitro-measurement",
2245                    "pcrs": {"0": "0123"}
2246                }
2247            })),
2248        }
2249    }
2250
2251    fn sample_nitro_trust_policy() -> AttestationTrustPolicy {
2252        AttestationTrustPolicy {
2253            rules: vec![AttestationTrustRule {
2254                name: "aws-nitro-contoso".to_string(),
2255                schema: AWS_NITRO_ATTESTATION_SCHEMA.to_string(),
2256                verifier: "https://nitro.aws.example".to_string(),
2257                effective_tier: RuntimeAssuranceTier::Verified,
2258                verifier_family: Some(AttestationVerifierFamily::AwsNitro),
2259                max_evidence_age_seconds: Some(120),
2260                allowed_attestation_types: Vec::new(),
2261                required_assertions: BTreeMap::from([
2262                    ("moduleId".to_string(), "nitro-enclave-1".to_string()),
2263                    ("digest".to_string(), "sha384:nitro-measurement".to_string()),
2264                ]),
2265            }],
2266        }
2267    }
2268
2269    fn sample_descriptor_document() -> RuntimeAttestationVerifierDescriptorDocument {
2270        RuntimeAttestationVerifierDescriptorDocument {
2271            schema: RUNTIME_ATTESTATION_VERIFIER_DESCRIPTOR_SCHEMA.to_string(),
2272            descriptor_id: "azure-prod".to_string(),
2273            verifier: "https://maa.contoso.test".to_string(),
2274            verifier_family: AttestationVerifierFamily::AzureMaa,
2275            adapter: AZURE_MAA_VERIFIER_ADAPTER.to_string(),
2276            attestation_schemas: vec![AZURE_MAA_ATTESTATION_SCHEMA.to_string()],
2277            appraisal_artifact_schema: RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA.to_string(),
2278            appraisal_result_schema: RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA.to_string(),
2279            signing_key_fingerprints: vec!["sha256:azure-key-1".to_string()],
2280            reference_values_uri: Some("https://maa.contoso.test/reference-values".to_string()),
2281            issued_at: 100,
2282            expires_at: 300,
2283        }
2284    }
2285
2286    fn sample_reference_value_set() -> RuntimeAttestationReferenceValueSet {
2287        RuntimeAttestationReferenceValueSet {
2288            schema: RUNTIME_ATTESTATION_REFERENCE_VALUE_SET_SCHEMA.to_string(),
2289            reference_value_id: "azure-rv-1".to_string(),
2290            descriptor_id: "azure-prod".to_string(),
2291            verifier_family: AttestationVerifierFamily::AzureMaa,
2292            attestation_schema: AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
2293            source_uri: Some("https://maa.contoso.test/reference-values/1".to_string()),
2294            issued_at: 100,
2295            expires_at: 300,
2296            state: RuntimeAttestationReferenceValueState::Active,
2297            superseded_by: None,
2298            revoked_reason: None,
2299            measurements: BTreeMap::from([("mrEnclave".to_string(), json!("abc123"))]),
2300        }
2301    }
2302
2303    fn sample_signed_descriptor(
2304        signer: &crate::crypto::Keypair,
2305    ) -> SignedRuntimeAttestationVerifierDescriptor {
2306        SignedExportEnvelope::sign(sample_descriptor_document(), signer).expect("sign descriptor")
2307    }
2308
2309    fn sample_signed_reference_value_set(
2310        signer: &crate::crypto::Keypair,
2311    ) -> SignedRuntimeAttestationReferenceValueSet {
2312        SignedExportEnvelope::sign(sample_reference_value_set(), signer)
2313            .expect("sign reference values")
2314    }
2315
2316    fn sample_trust_bundle_document(
2317        signer: &crate::crypto::Keypair,
2318    ) -> RuntimeAttestationTrustBundleDocument {
2319        RuntimeAttestationTrustBundleDocument {
2320            schema: RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA.to_string(),
2321            bundle_id: "bundle-1".to_string(),
2322            publisher: "https://trust.contoso.test".to_string(),
2323            version: 1,
2324            issued_at: 100,
2325            expires_at: 300,
2326            descriptors: vec![sample_signed_descriptor(signer)],
2327            reference_values: vec![sample_signed_reference_value_set(signer)],
2328        }
2329    }
2330
2331    fn empty_import_policy() -> RuntimeAttestationImportedAppraisalPolicy {
2332        RuntimeAttestationImportedAppraisalPolicy {
2333            trusted_issuers: Vec::new(),
2334            trusted_signer_keys: Vec::new(),
2335            allowed_verifier_families: Vec::new(),
2336            max_result_age_seconds: None,
2337            max_evidence_age_seconds: None,
2338            maximum_effective_tier: None,
2339            required_claims: BTreeMap::new(),
2340        }
2341    }
2342
2343    fn sample_appraisal_result() -> RuntimeAttestationAppraisalResult {
2344        let appraisal = derive_runtime_attestation_appraisal(&sample_evidence())
2345            .expect("derive sample appraisal");
2346        let report = RuntimeAttestationAppraisalReport {
2347            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
2348            generated_at: 150,
2349            appraisal,
2350            policy_outcome: RuntimeAttestationPolicyOutcome {
2351                trust_policy_configured: false,
2352                accepted: true,
2353                effective_tier: RuntimeAssuranceTier::Attested,
2354                reason: None,
2355            },
2356        };
2357        RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
2358            .expect("result from sample report")
2359    }
2360
2361    #[test]
2362    fn verified_runtime_attestation_record_requires_local_trust_boundary_for_tier_promotion() {
2363        let verified =
2364            verify_runtime_attestation_record(&sample_evidence(), None, 150).expect("record");
2365
2366        assert_eq!(verified.verified_at, 150);
2367        assert_eq!(
2368            verified.subject.runtime_identity.as_deref(),
2369            Some("spiffe://contoso.test/runtime/worker")
2370        );
2371        assert_eq!(
2372            verified
2373                .workload_identity()
2374                .expect("verified record should carry canonical workload identity")
2375                .trust_domain,
2376            "contoso.test"
2377        );
2378        assert_eq!(
2379            verified.appraisal.effective_tier,
2380            RuntimeAssuranceTier::Attested
2381        );
2382        assert!(!verified.policy_outcome.trust_policy_configured);
2383        assert!(!verified.policy_outcome.accepted);
2384        assert!(!verified.is_locally_accepted());
2385        assert_eq!(verified.effective_tier(), RuntimeAssuranceTier::None);
2386        assert_eq!(
2387            verified.provenance.verifier_adapter,
2388            AZURE_MAA_VERIFIER_ADAPTER
2389        );
2390        assert_eq!(
2391            verified.provenance.canonical_verifier,
2392            "https://maa.contoso.test"
2393        );
2394        assert_eq!(verified.matched_trust_rule(), None);
2395        assert_eq!(
2396            verified.policy_outcome.effective_tier,
2397            RuntimeAssuranceTier::None
2398        );
2399        assert_eq!(
2400            verified.policy_outcome.reason.as_deref(),
2401            Some("runtime attestation evidence did not cross a local verified trust boundary")
2402        );
2403    }
2404
2405    #[test]
2406    fn verified_runtime_attestation_record_promotes_tier_only_after_local_policy_verification() {
2407        let verified = verify_runtime_attestation_record(
2408            &sample_evidence(),
2409            Some(&sample_trust_policy()),
2410            150,
2411        )
2412        .expect("trusted record");
2413
2414        assert!(verified.policy_outcome.trust_policy_configured);
2415        assert!(verified.policy_outcome.accepted);
2416        assert!(verified.is_locally_accepted());
2417        assert_eq!(verified.effective_tier(), RuntimeAssuranceTier::Verified);
2418        assert_eq!(
2419            verified.provenance.verifier_family,
2420            AttestationVerifierFamily::AzureMaa
2421        );
2422        assert_eq!(verified.matched_trust_rule(), Some("azure-contoso"));
2423        assert_eq!(
2424            verified.policy_outcome.effective_tier,
2425            RuntimeAssuranceTier::Verified
2426        );
2427        assert_eq!(
2428            verified.policy_outcome.reason.as_deref(),
2429            Some("matched attestation trust rule `azure-contoso`")
2430        );
2431    }
2432
2433    #[test]
2434    fn verified_runtime_attestation_record_accepts_nitro_evidence_across_trust_boundary() {
2435        let evidence = sample_nitro_evidence();
2436        let verified =
2437            verify_runtime_attestation_record(&evidence, Some(&sample_nitro_trust_policy()), 150)
2438                .expect("nitro record should verify across the trust boundary");
2439
2440        assert!(verified.is_locally_accepted());
2441        assert_eq!(verified.effective_tier(), RuntimeAssuranceTier::Verified);
2442        assert_eq!(verified.evidence_schema(), AWS_NITRO_ATTESTATION_SCHEMA);
2443        assert_eq!(verified.evidence_sha256(), "nitro-digest-1");
2444        assert_eq!(verified.canonical_verifier(), "https://nitro.aws.example");
2445        assert_eq!(
2446            verified.verifier_family(),
2447            AttestationVerifierFamily::AwsNitro
2448        );
2449        assert!(verified.matches_evidence(&evidence));
2450
2451        let mut modified = evidence.clone();
2452        modified.evidence_sha256 = "nitro-digest-2".to_string();
2453        assert!(!verified.matches_evidence(&modified));
2454    }
2455
2456    #[test]
2457    fn verifier_descriptor_validation_and_verification_reject_invalid_variants() {
2458        let mut descriptor = sample_descriptor_document();
2459        descriptor.schema = "chio.runtime-attestation.verifier-descriptor.v0".to_string();
2460        assert!(
2461            validate_runtime_attestation_verifier_descriptor(&descriptor)
2462                .expect_err("invalid descriptor schema")
2463                .to_string()
2464                .contains("schema must be")
2465        );
2466
2467        let mut descriptor = sample_descriptor_document();
2468        descriptor.descriptor_id = "  ".to_string();
2469        assert!(
2470            validate_runtime_attestation_verifier_descriptor(&descriptor)
2471                .expect_err("empty descriptor id")
2472                .to_string()
2473                .contains("descriptor_id")
2474        );
2475
2476        let mut descriptor = sample_descriptor_document();
2477        descriptor.verifier = " ".to_string();
2478        assert!(
2479            validate_runtime_attestation_verifier_descriptor(&descriptor)
2480                .expect_err("empty verifier")
2481                .to_string()
2482                .contains("non-empty verifier")
2483        );
2484
2485        let mut descriptor = sample_descriptor_document();
2486        descriptor.adapter = " ".to_string();
2487        assert!(
2488            validate_runtime_attestation_verifier_descriptor(&descriptor)
2489                .expect_err("empty adapter")
2490                .to_string()
2491                .contains("non-empty adapter")
2492        );
2493
2494        let mut descriptor = sample_descriptor_document();
2495        descriptor.issued_at = 301;
2496        assert!(
2497            validate_runtime_attestation_verifier_descriptor(&descriptor)
2498                .expect_err("inverted descriptor window")
2499                .to_string()
2500                .contains("must not expire before it is issued")
2501        );
2502
2503        let mut descriptor = sample_descriptor_document();
2504        descriptor.attestation_schemas.clear();
2505        assert!(
2506            validate_runtime_attestation_verifier_descriptor(&descriptor)
2507                .expect_err("missing schemas")
2508                .to_string()
2509                .contains("at least one attestation schema")
2510        );
2511
2512        let mut descriptor = sample_descriptor_document();
2513        descriptor.attestation_schemas = vec![
2514            GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA.to_string(),
2515            AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
2516        ];
2517        assert!(
2518            validate_runtime_attestation_verifier_descriptor(&descriptor)
2519                .expect_err("unsorted schemas")
2520                .to_string()
2521                .contains("sorted unique order")
2522        );
2523
2524        let mut descriptor = sample_descriptor_document();
2525        descriptor.attestation_schemas = vec![String::new()];
2526        assert!(
2527            validate_runtime_attestation_verifier_descriptor(&descriptor)
2528                .expect_err("empty schema entry")
2529                .to_string()
2530                .contains("cannot contain empty values")
2531        );
2532
2533        let mut descriptor = sample_descriptor_document();
2534        descriptor.appraisal_artifact_schema = "chio.runtime-attestation.other.v1".to_string();
2535        assert!(
2536            validate_runtime_attestation_verifier_descriptor(&descriptor)
2537                .expect_err("wrong artifact schema")
2538                .to_string()
2539                .contains("canonical appraisal artifact schema")
2540        );
2541
2542        let mut descriptor = sample_descriptor_document();
2543        descriptor.appraisal_result_schema = "chio.runtime-attestation.other-result.v1".to_string();
2544        assert!(
2545            validate_runtime_attestation_verifier_descriptor(&descriptor)
2546                .expect_err("wrong result schema")
2547                .to_string()
2548                .contains("canonical appraisal result schema")
2549        );
2550
2551        let mut descriptor = sample_descriptor_document();
2552        descriptor.signing_key_fingerprints.clear();
2553        assert!(
2554            validate_runtime_attestation_verifier_descriptor(&descriptor)
2555                .expect_err("missing signing keys")
2556                .to_string()
2557                .contains("at least one signing-key fingerprint")
2558        );
2559
2560        let mut descriptor = sample_descriptor_document();
2561        descriptor.signing_key_fingerprints =
2562            vec!["sha256:key-b".to_string(), "sha256:key-a".to_string()];
2563        assert!(
2564            validate_runtime_attestation_verifier_descriptor(&descriptor)
2565                .expect_err("unsorted signing keys")
2566                .to_string()
2567                .contains("sorted unique order")
2568        );
2569
2570        let mut descriptor = sample_descriptor_document();
2571        descriptor.signing_key_fingerprints = vec![" ".to_string()];
2572        assert!(
2573            validate_runtime_attestation_verifier_descriptor(&descriptor)
2574                .expect_err("empty signing key")
2575                .to_string()
2576                .contains("cannot contain empty values")
2577        );
2578
2579        let mut descriptor = sample_descriptor_document();
2580        descriptor.reference_values_uri = Some(" ".to_string());
2581        assert!(
2582            validate_runtime_attestation_verifier_descriptor(&descriptor)
2583                .expect_err("empty reference_values_uri")
2584                .to_string()
2585                .contains("reference_values_uri")
2586        );
2587
2588        let signer = crate::crypto::Keypair::generate();
2589        let signed = sample_signed_descriptor(&signer);
2590        assert!(
2591            verify_signed_runtime_attestation_verifier_descriptor(&signed, 50)
2592                .expect_err("descriptor not yet valid")
2593                .to_string()
2594                .contains("not yet valid")
2595        );
2596        assert!(
2597            verify_signed_runtime_attestation_verifier_descriptor(&signed, 400)
2598                .expect_err("descriptor expired")
2599                .to_string()
2600                .contains("has expired")
2601        );
2602
2603        let mut tampered = signed.clone();
2604        tampered.body.verifier = "https://maa.other.example".to_string();
2605        assert!(
2606            verify_signed_runtime_attestation_verifier_descriptor(&tampered, 150)
2607                .expect_err("descriptor signature failure")
2608                .to_string()
2609                .contains("signature verification failed")
2610        );
2611    }
2612
2613    #[test]
2614    fn reference_value_validation_and_verification_reject_invalid_variants() {
2615        let mut reference_value_set = sample_reference_value_set();
2616        reference_value_set.schema = "chio.runtime-attestation.reference-values.v0".to_string();
2617        assert!(
2618            validate_runtime_attestation_reference_value_set(&reference_value_set)
2619                .expect_err("invalid reference-value schema")
2620                .to_string()
2621                .contains("schema must be")
2622        );
2623
2624        let mut reference_value_set = sample_reference_value_set();
2625        reference_value_set.reference_value_id = " ".to_string();
2626        assert!(
2627            validate_runtime_attestation_reference_value_set(&reference_value_set)
2628                .expect_err("empty reference_value_id")
2629                .to_string()
2630                .contains("reference_value_id")
2631        );
2632
2633        let mut reference_value_set = sample_reference_value_set();
2634        reference_value_set.descriptor_id = " ".to_string();
2635        assert!(
2636            validate_runtime_attestation_reference_value_set(&reference_value_set)
2637                .expect_err("empty descriptor_id")
2638                .to_string()
2639                .contains("descriptor_id")
2640        );
2641
2642        let mut reference_value_set = sample_reference_value_set();
2643        reference_value_set.attestation_schema = " ".to_string();
2644        assert!(
2645            validate_runtime_attestation_reference_value_set(&reference_value_set)
2646                .expect_err("empty attestation schema")
2647                .to_string()
2648                .contains("attestation_schema")
2649        );
2650
2651        let mut reference_value_set = sample_reference_value_set();
2652        reference_value_set.issued_at = 301;
2653        assert!(
2654            validate_runtime_attestation_reference_value_set(&reference_value_set)
2655                .expect_err("inverted reference-value window")
2656                .to_string()
2657                .contains("must not expire before it is issued")
2658        );
2659
2660        let mut reference_value_set = sample_reference_value_set();
2661        reference_value_set.source_uri = Some(" ".to_string());
2662        assert!(
2663            validate_runtime_attestation_reference_value_set(&reference_value_set)
2664                .expect_err("empty source uri")
2665                .to_string()
2666                .contains("source_uri")
2667        );
2668
2669        let mut reference_value_set = sample_reference_value_set();
2670        reference_value_set.measurements.clear();
2671        assert!(
2672            validate_runtime_attestation_reference_value_set(&reference_value_set)
2673                .expect_err("missing measurements")
2674                .to_string()
2675                .contains("at least one measurement")
2676        );
2677
2678        let mut reference_value_set = sample_reference_value_set();
2679        reference_value_set.superseded_by = Some("azure-rv-2".to_string());
2680        assert!(
2681            validate_runtime_attestation_reference_value_set(&reference_value_set)
2682                .expect_err("active state with supersession")
2683                .to_string()
2684                .contains("cannot include supersession or revocation fields")
2685        );
2686
2687        let mut reference_value_set = sample_reference_value_set();
2688        reference_value_set.state = RuntimeAttestationReferenceValueState::Superseded;
2689        assert!(
2690            validate_runtime_attestation_reference_value_set(&reference_value_set)
2691                .expect_err("superseded without successor")
2692                .to_string()
2693                .contains("must include superseded_by")
2694        );
2695
2696        let mut reference_value_set = sample_reference_value_set();
2697        reference_value_set.state = RuntimeAttestationReferenceValueState::Superseded;
2698        reference_value_set.superseded_by = Some("azure-rv-1".to_string());
2699        assert!(
2700            validate_runtime_attestation_reference_value_set(&reference_value_set)
2701                .expect_err("self supersession")
2702                .to_string()
2703                .contains("cannot supersede itself")
2704        );
2705
2706        let mut reference_value_set = sample_reference_value_set();
2707        reference_value_set.state = RuntimeAttestationReferenceValueState::Superseded;
2708        reference_value_set.superseded_by = Some("azure-rv-2".to_string());
2709        reference_value_set.revoked_reason = Some("compromised".to_string());
2710        assert!(
2711            validate_runtime_attestation_reference_value_set(&reference_value_set)
2712                .expect_err("superseded with revoked_reason")
2713                .to_string()
2714                .contains("cannot include revoked_reason")
2715        );
2716
2717        let mut reference_value_set = sample_reference_value_set();
2718        reference_value_set.state = RuntimeAttestationReferenceValueState::Revoked;
2719        reference_value_set.superseded_by = Some("azure-rv-2".to_string());
2720        reference_value_set.revoked_reason = Some("compromised".to_string());
2721        assert!(
2722            validate_runtime_attestation_reference_value_set(&reference_value_set)
2723                .expect_err("revoked with superseded_by")
2724                .to_string()
2725                .contains("cannot include superseded_by")
2726        );
2727
2728        let mut reference_value_set = sample_reference_value_set();
2729        reference_value_set.state = RuntimeAttestationReferenceValueState::Revoked;
2730        reference_value_set.revoked_reason = Some(" ".to_string());
2731        assert!(
2732            validate_runtime_attestation_reference_value_set(&reference_value_set)
2733                .expect_err("revoked without reason")
2734                .to_string()
2735                .contains("must include revoked_reason")
2736        );
2737
2738        let signer = crate::crypto::Keypair::generate();
2739        let signed = sample_signed_reference_value_set(&signer);
2740        assert!(
2741            verify_signed_runtime_attestation_reference_value_set(&signed, 50)
2742                .expect_err("reference values not yet valid")
2743                .to_string()
2744                .contains("not yet valid")
2745        );
2746        assert!(
2747            verify_signed_runtime_attestation_reference_value_set(&signed, 400)
2748                .expect_err("reference values expired")
2749                .to_string()
2750                .contains("has expired")
2751        );
2752
2753        let mut tampered = signed.clone();
2754        tampered.body.source_uri = Some("https://maa.other.example/reference-values".to_string());
2755        assert!(
2756            verify_signed_runtime_attestation_reference_value_set(&tampered, 150)
2757                .expect_err("reference-value signature failure")
2758                .to_string()
2759                .contains("signature verification failed")
2760        );
2761    }
2762
2763    #[test]
2764    fn trust_bundle_validation_and_verification_reject_remaining_invalid_states() {
2765        let signer = crate::crypto::Keypair::generate();
2766        let descriptor = sample_signed_descriptor(&signer);
2767        let reference_value = sample_signed_reference_value_set(&signer);
2768
2769        let mut bundle = sample_trust_bundle_document(&signer);
2770        bundle.schema = "chio.runtime-attestation.trust-bundle.v0".to_string();
2771        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2772            .expect_err("invalid bundle schema")
2773            .to_string()
2774            .contains("schema must be"));
2775
2776        let mut bundle = sample_trust_bundle_document(&signer);
2777        bundle.bundle_id = " ".to_string();
2778        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2779            .expect_err("empty bundle id")
2780            .to_string()
2781            .contains("bundle_id"));
2782
2783        let mut bundle = sample_trust_bundle_document(&signer);
2784        bundle.publisher = " ".to_string();
2785        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2786            .expect_err("empty bundle publisher")
2787            .to_string()
2788            .contains("non-empty publisher"));
2789
2790        let mut bundle = sample_trust_bundle_document(&signer);
2791        bundle.version = 0;
2792        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2793            .expect_err("bundle version zero")
2794            .to_string()
2795            .contains("non-zero version"));
2796
2797        let mut bundle = sample_trust_bundle_document(&signer);
2798        bundle.issued_at = 301;
2799        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2800            .expect_err("inverted bundle window")
2801            .to_string()
2802            .contains("must not expire before it is issued"));
2803
2804        let bundle = sample_trust_bundle_document(&signer);
2805        assert!(validate_runtime_attestation_trust_bundle(&bundle, 50)
2806            .expect_err("bundle not yet valid")
2807            .to_string()
2808            .contains("not yet valid"));
2809        assert!(validate_runtime_attestation_trust_bundle(&bundle, 400)
2810            .expect_err("bundle expired")
2811            .to_string()
2812            .contains("has expired"));
2813
2814        let mut bundle = sample_trust_bundle_document(&signer);
2815        bundle.descriptors.clear();
2816        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2817            .expect_err("bundle without descriptors")
2818            .to_string()
2819            .contains("at least one verifier descriptor"));
2820
2821        let mut bundle = sample_trust_bundle_document(&signer);
2822        bundle.descriptors = vec![descriptor.clone(), descriptor.clone()];
2823        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2824            .expect_err("duplicate descriptor ids")
2825            .to_string()
2826            .contains("duplicate verifier descriptor"));
2827
2828        let mut bundle = sample_trust_bundle_document(&signer);
2829        bundle.reference_values = vec![reference_value.clone(), reference_value.clone()];
2830        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2831            .expect_err("duplicate reference-value ids")
2832            .to_string()
2833            .contains("duplicate reference-value set"));
2834
2835        let mut unknown_reference = sample_reference_value_set();
2836        unknown_reference.descriptor_id = "azure-other".to_string();
2837        let mut bundle = sample_trust_bundle_document(&signer);
2838        bundle.reference_values =
2839            vec![SignedExportEnvelope::sign(unknown_reference, &signer)
2840                .expect("sign unknown reference")];
2841        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2842            .expect_err("unknown descriptor")
2843            .to_string()
2844            .contains("unknown verifier descriptor"));
2845
2846        let mut schema_mismatch_reference = sample_reference_value_set();
2847        schema_mismatch_reference.attestation_schema =
2848            GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA.to_string();
2849        let mut bundle = sample_trust_bundle_document(&signer);
2850        bundle.reference_values =
2851            vec![
2852                SignedExportEnvelope::sign(schema_mismatch_reference, &signer)
2853                    .expect("sign schema mismatch reference"),
2854            ];
2855        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2856            .expect_err("schema outside descriptor")
2857            .to_string()
2858            .contains("outside descriptor"));
2859
2860        let mut superseded_reference = sample_reference_value_set();
2861        superseded_reference.reference_value_id = "azure-rv-old".to_string();
2862        superseded_reference.state = RuntimeAttestationReferenceValueState::Superseded;
2863        superseded_reference.superseded_by = Some("azure-rv-next".to_string());
2864        let mut bundle = sample_trust_bundle_document(&signer);
2865        bundle.reference_values = vec![SignedExportEnvelope::sign(superseded_reference, &signer)
2866            .expect("sign superseded reference")];
2867        assert!(validate_runtime_attestation_trust_bundle(&bundle, 150)
2868            .expect_err("missing superseded successor")
2869            .to_string()
2870            .contains("unknown successor"));
2871
2872        let signed_bundle =
2873            SignedExportEnvelope::sign(sample_trust_bundle_document(&signer), &signer)
2874                .expect("sign trust bundle");
2875        let mut tampered = signed_bundle.clone();
2876        tampered.body.publisher = "https://trust.other.example".to_string();
2877        assert!(
2878            verify_signed_runtime_attestation_trust_bundle(&tampered, 150)
2879                .expect_err("bundle signature failure")
2880                .to_string()
2881                .contains("signature verification failed")
2882        );
2883    }
2884
2885    #[test]
2886    fn runtime_attestation_import_reasons_cover_remaining_policy_fail_closed_paths() {
2887        let signed_result = SignedRuntimeAttestationAppraisalResult::sign(
2888            sample_appraisal_result(),
2889            &crate::crypto::Keypair::generate(),
2890        )
2891        .expect("sign result");
2892
2893        let import = evaluate_imported_runtime_attestation_appraisal(
2894            &RuntimeAttestationAppraisalImportRequest {
2895                signed_result,
2896                local_policy: empty_import_policy(),
2897            },
2898            160,
2899        );
2900
2901        assert_eq!(
2902            import.local_policy_outcome.reason_codes,
2903            vec![RuntimeAttestationImportReasonCode::NoLocalPolicy]
2904        );
2905        assert_eq!(
2906            import.local_policy_outcome.reasons[0].description,
2907            RuntimeAttestationImportReasonCode::NoLocalPolicy.description()
2908        );
2909    }
2910
2911    #[test]
2912    fn imported_runtime_attestation_rejects_untrusted_exporters_and_claim_policy_failures() {
2913        let mut result = sample_appraisal_result();
2914        result.exporter_policy_outcome.accepted = false;
2915        let signer = crate::crypto::Keypair::generate();
2916        let signed_result =
2917            SignedRuntimeAttestationAppraisalResult::sign(result, &signer).expect("sign result");
2918
2919        let import = evaluate_imported_runtime_attestation_appraisal(
2920            &RuntimeAttestationAppraisalImportRequest {
2921                signed_result,
2922                local_policy: RuntimeAttestationImportedAppraisalPolicy {
2923                    trusted_issuers: vec!["did:chio:test:trusted".to_string()],
2924                    trusted_signer_keys: vec!["sha256:not-the-real-signer".to_string()],
2925                    allowed_verifier_families: vec![AttestationVerifierFamily::GoogleAttestation],
2926                    max_result_age_seconds: None,
2927                    max_evidence_age_seconds: None,
2928                    maximum_effective_tier: None,
2929                    required_claims: BTreeMap::from([
2930                        (
2931                            RuntimeAttestationNormalizedClaimCode::ModuleId,
2932                            "module-1".to_string(),
2933                        ),
2934                        (
2935                            RuntimeAttestationNormalizedClaimCode::AttestationType,
2936                            "sev".to_string(),
2937                        ),
2938                    ]),
2939                },
2940            },
2941            160,
2942        );
2943
2944        assert_eq!(
2945            import.local_policy_outcome.disposition,
2946            RuntimeAttestationImportDisposition::Reject
2947        );
2948        for code in [
2949            RuntimeAttestationImportReasonCode::ExporterPolicyRejected,
2950            RuntimeAttestationImportReasonCode::UntrustedIssuer,
2951            RuntimeAttestationImportReasonCode::UntrustedSigner,
2952            RuntimeAttestationImportReasonCode::UnsupportedVerifierFamily,
2953            RuntimeAttestationImportReasonCode::MissingRequiredClaim,
2954            RuntimeAttestationImportReasonCode::ClaimMismatch,
2955        ] {
2956            assert!(import.local_policy_outcome.reason_codes.contains(&code));
2957            assert!(import
2958                .local_policy_outcome
2959                .reasons
2960                .iter()
2961                .any(|reason| reason.code == code && reason.description == code.description()));
2962        }
2963    }
2964
2965    #[test]
2966    fn normalized_claim_values_and_result_guards_cover_remaining_branch_paths() {
2967        assert_eq!(
2968            normalized_claim_value_string(&RuntimeAttestationNormalizedClaim::new(
2969                RuntimeAttestationNormalizedClaimCode::SecureBootState,
2970                Value::Bool(true),
2971            )),
2972            Some("true".to_string())
2973        );
2974        assert_eq!(
2975            normalized_claim_value_string(&RuntimeAttestationNormalizedClaim::new(
2976                RuntimeAttestationNormalizedClaimCode::ModuleId,
2977                Value::Number(serde_json::Number::from(7)),
2978            )),
2979            Some("7".to_string())
2980        );
2981        assert_eq!(
2982            normalized_claim_value_string(&RuntimeAttestationNormalizedClaim::new(
2983                RuntimeAttestationNormalizedClaimCode::MeasurementRegisters,
2984                json!({"0": "abcd"}),
2985            )),
2986            Some("{\"0\":\"abcd\"}".to_string())
2987        );
2988
2989        let appraisal = derive_runtime_attestation_appraisal(&sample_evidence())
2990            .expect("derive appraisal for result guard test");
2991        let report = RuntimeAttestationAppraisalReport {
2992            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
2993            generated_at: 150,
2994            appraisal,
2995            policy_outcome: RuntimeAttestationPolicyOutcome {
2996                trust_policy_configured: false,
2997                accepted: true,
2998                effective_tier: RuntimeAssuranceTier::Attested,
2999                reason: None,
3000            },
3001        };
3002
3003        assert!(
3004            RuntimeAttestationAppraisalResult::from_report("  ", &report)
3005                .expect_err("empty issuer")
3006                .to_string()
3007                .contains("issuer must not be empty")
3008        );
3009
3010        let mut missing_artifact_report = report.clone();
3011        missing_artifact_report.appraisal.artifact = None;
3012        assert!(RuntimeAttestationAppraisalResult::from_report(
3013            "did:chio:test:issuer",
3014            &missing_artifact_report,
3015        )
3016        .expect_err("missing artifact")
3017        .to_string()
3018        .contains("missing the nested artifact"));
3019
3020        assert_eq!(
3021            verifier_family_for_attestation_schema(ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA),
3022            Some(AttestationVerifierFamily::EnterpriseVerifier)
3023        );
3024        assert_eq!(
3025            verifier_family_for_attestation_schema("urn:chio:unknown"),
3026            None
3027        );
3028    }
3029
3030    #[test]
3031    fn derive_runtime_attestation_appraisal_supports_aws_nitro_and_rejects_unknown_schema() {
3032        let evidence = RuntimeAttestationEvidence {
3033            schema: AWS_NITRO_ATTESTATION_SCHEMA.to_string(),
3034            verifier: "https://nitro.aws.example".to_string(),
3035            tier: RuntimeAssuranceTier::Attested,
3036            issued_at: 100,
3037            expires_at: 200,
3038            evidence_sha256: "aws-digest".to_string(),
3039            runtime_identity: None,
3040            workload_identity: None,
3041            claims: Some(json!({
3042                "awsNitro": {
3043                    "moduleId": "nitro-enclave-1",
3044                    "digest": "sha384:aws-measurement",
3045                    "pcrs": {"0": "0123"}
3046                }
3047            })),
3048        };
3049
3050        let appraisal = derive_runtime_attestation_appraisal(&evidence)
3051            .expect("aws nitro evidence should derive");
3052        assert_eq!(
3053            appraisal.verifier_family,
3054            AttestationVerifierFamily::AwsNitro
3055        );
3056        assert_eq!(
3057            appraisal.normalized_assertions["moduleId"],
3058            "nitro-enclave-1"
3059        );
3060        assert_eq!(
3061            appraisal.normalized_assertions["digest"],
3062            "sha384:aws-measurement"
3063        );
3064        assert_eq!(
3065            appraisal.normalized_assertions["pcrs"],
3066            json!({"0": "0123"})
3067        );
3068        let artifact = appraisal.artifact.expect("aws appraisal artifact");
3069        assert!(artifact.claims.normalized_claims.iter().any(|claim| {
3070            claim.code == RuntimeAttestationNormalizedClaimCode::MeasurementRegisters
3071                && claim.value == json!({"0": "0123"})
3072        }));
3073
3074        let mut unsupported = sample_evidence();
3075        unsupported.schema = "chio.runtime-attestation.unknown.v1".to_string();
3076        let error = derive_runtime_attestation_appraisal(&unsupported)
3077            .expect_err("unsupported schema should fail");
3078        assert!(matches!(
3079            error,
3080            RuntimeAttestationAppraisalError::UnsupportedSchema { schema }
3081            if schema == "chio.runtime-attestation.unknown.v1"
3082        ));
3083    }
3084
3085    #[test]
3086    fn runtime_attestation_appraisal_copies_evidence_descriptor_fields() {
3087        let evidence = sample_evidence();
3088        let appraisal = RuntimeAttestationAppraisal::accepted(
3089            "azure_maa",
3090            AttestationVerifierFamily::AzureMaa,
3091            &evidence,
3092            BTreeMap::new(),
3093            BTreeMap::new(),
3094            vec![RuntimeAttestationAppraisalReasonCode::EvidenceVerified],
3095        );
3096
3097        assert_eq!(appraisal.schema, RUNTIME_ATTESTATION_APPRAISAL_SCHEMA);
3098        assert_eq!(appraisal.evidence.schema, evidence.schema);
3099        assert_eq!(appraisal.evidence.verifier, evidence.verifier);
3100        assert_eq!(appraisal.evidence.evidence_sha256, evidence.evidence_sha256);
3101        assert_eq!(
3102            appraisal.verdict,
3103            RuntimeAttestationAppraisalVerdict::Accepted
3104        );
3105        assert_eq!(appraisal.effective_tier, RuntimeAssuranceTier::Attested);
3106        let artifact = appraisal
3107            .artifact
3108            .expect("accepted appraisal should carry artifact");
3109        assert_eq!(
3110            artifact.schema,
3111            RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_SCHEMA
3112        );
3113        assert_eq!(artifact.verifier.adapter, "azure_maa");
3114        assert_eq!(
3115            artifact.verifier.verifier_family,
3116            AttestationVerifierFamily::AzureMaa
3117        );
3118        assert_eq!(
3119            artifact.policy.verdict,
3120            RuntimeAttestationAppraisalVerdict::Accepted
3121        );
3122        assert_eq!(
3123            artifact.policy.effective_tier,
3124            RuntimeAssuranceTier::Attested
3125        );
3126        assert_eq!(
3127            appraisal.reasons,
3128            vec![RuntimeAttestationAppraisalReason::from_code(
3129                RuntimeAttestationAppraisalReasonCode::EvidenceVerified
3130            )]
3131        );
3132    }
3133
3134    #[test]
3135    fn rejected_runtime_attestation_appraisal_drops_effective_tier() {
3136        let evidence = sample_evidence();
3137        let appraisal = RuntimeAttestationAppraisal::rejected(
3138            "azure_maa",
3139            AttestationVerifierFamily::AzureMaa,
3140            &evidence,
3141            BTreeMap::new(),
3142            BTreeMap::new(),
3143            vec![RuntimeAttestationAppraisalReasonCode::PolicyRejected],
3144        );
3145
3146        assert_eq!(
3147            appraisal.verdict,
3148            RuntimeAttestationAppraisalVerdict::Rejected
3149        );
3150        assert_eq!(appraisal.effective_tier, RuntimeAssuranceTier::None);
3151        assert_eq!(
3152            appraisal.reason_codes,
3153            vec![RuntimeAttestationAppraisalReasonCode::PolicyRejected]
3154        );
3155        let artifact = appraisal
3156            .artifact
3157            .expect("rejected appraisal should carry artifact");
3158        assert_eq!(
3159            artifact.policy.verdict,
3160            RuntimeAttestationAppraisalVerdict::Rejected
3161        );
3162        assert_eq!(artifact.policy.effective_tier, RuntimeAssuranceTier::None);
3163        assert_eq!(
3164            artifact.policy.reason_codes,
3165            vec![RuntimeAttestationAppraisalReasonCode::PolicyRejected]
3166        );
3167        assert_eq!(
3168            artifact.policy.reasons,
3169            vec![RuntimeAttestationAppraisalReason::from_code(
3170                RuntimeAttestationAppraisalReasonCode::PolicyRejected
3171            )]
3172        );
3173    }
3174
3175    #[test]
3176    fn derive_runtime_attestation_appraisal_supports_google_confidential_vm() {
3177        let evidence = RuntimeAttestationEvidence {
3178            schema: GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA.to_string(),
3179            verifier: "https://confidentialcomputing.googleapis.com".to_string(),
3180            tier: RuntimeAssuranceTier::Attested,
3181            issued_at: 100,
3182            expires_at: 200,
3183            evidence_sha256: "google-digest".to_string(),
3184            runtime_identity: Some(
3185                "//compute.googleapis.com/projects/demo/zones/us-central1-a/instances/vm-1"
3186                    .to_string(),
3187            ),
3188            workload_identity: None,
3189            claims: Some(json!({
3190                "googleAttestation": {
3191                    "attestationType": "confidential_vm",
3192                    "hardwareModel": "GCP_AMD_SEV",
3193                    "secureBoot": "enabled"
3194                }
3195            })),
3196        };
3197
3198        let appraisal = derive_runtime_attestation_appraisal(&evidence)
3199            .expect("google evidence should derive a canonical appraisal");
3200        assert_eq!(
3201            appraisal.verifier_family,
3202            AttestationVerifierFamily::GoogleAttestation
3203        );
3204        assert_eq!(
3205            appraisal.normalized_assertions["attestationType"],
3206            "confidential_vm"
3207        );
3208        assert_eq!(
3209            appraisal.normalized_assertions["hardwareModel"],
3210            "GCP_AMD_SEV"
3211        );
3212        assert_eq!(appraisal.normalized_assertions["secureBoot"], "enabled");
3213        let artifact = appraisal
3214            .artifact
3215            .expect("derived appraisal should carry artifact");
3216        assert_eq!(
3217            artifact.claims.normalized_assertions["secureBoot"],
3218            "enabled"
3219        );
3220        assert!(artifact.claims.normalized_claims.iter().any(|claim| {
3221            claim.code == RuntimeAttestationNormalizedClaimCode::SecureBootState
3222                && claim.category == RuntimeAttestationNormalizedClaimCategory::Configuration
3223                && claim.provenance == RuntimeAttestationClaimProvenance::VendorClaims
3224                && claim.value == Value::String("enabled".to_string())
3225        }));
3226        assert_eq!(
3227            artifact.verifier.adapter,
3228            GOOGLE_CONFIDENTIAL_VM_VERIFIER_ADAPTER
3229        );
3230    }
3231
3232    #[test]
3233    fn derive_runtime_attestation_appraisal_supports_enterprise_verifier() {
3234        let evidence = RuntimeAttestationEvidence {
3235            schema: ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA.to_string(),
3236            verifier: "https://attest.contoso.example".to_string(),
3237            tier: RuntimeAssuranceTier::Attested,
3238            issued_at: 100,
3239            expires_at: 200,
3240            evidence_sha256: "enterprise-digest".to_string(),
3241            runtime_identity: Some("spiffe://chio.example/workloads/enterprise".to_string()),
3242            workload_identity: Some(
3243                WorkloadIdentity::parse_spiffe_uri("spiffe://chio.example/workloads/enterprise")
3244                    .expect("parse enterprise workload identity"),
3245            ),
3246            claims: Some(json!({
3247                "enterpriseVerifier": {
3248                    "attestationType": "enterprise_confidential_vm",
3249                    "hardwareModel": "AMD_SEV_SNP",
3250                    "secureBoot": "enabled",
3251                    "digest": "sha384:enterprise-measurement",
3252                    "pcrs": {
3253                        "0": "8f7f1be8"
3254                    }
3255                }
3256            })),
3257        };
3258
3259        let appraisal = derive_runtime_attestation_appraisal(&evidence)
3260            .expect("enterprise evidence should derive a canonical appraisal");
3261        assert_eq!(
3262            appraisal.verifier_family,
3263            AttestationVerifierFamily::EnterpriseVerifier
3264        );
3265        assert_eq!(
3266            appraisal.normalized_assertions["attestationType"],
3267            "enterprise_confidential_vm"
3268        );
3269        assert_eq!(
3270            appraisal.normalized_assertions["hardwareModel"],
3271            "AMD_SEV_SNP"
3272        );
3273        assert_eq!(appraisal.normalized_assertions["secureBoot"], "enabled");
3274        assert_eq!(
3275            appraisal.normalized_assertions["digest"],
3276            "sha384:enterprise-measurement"
3277        );
3278        let artifact = appraisal
3279            .artifact
3280            .expect("derived appraisal should carry artifact");
3281        assert_eq!(artifact.verifier.adapter, ENTERPRISE_VERIFIER_ADAPTER);
3282        assert!(artifact.claims.normalized_claims.iter().any(|claim| {
3283            claim.code == RuntimeAttestationNormalizedClaimCode::MeasurementDigest
3284                && claim.value == Value::String("sha384:enterprise-measurement".to_string())
3285        }));
3286    }
3287
3288    #[test]
3289    fn runtime_attestation_appraisal_inventory_lists_supported_bridges() {
3290        let inventory = runtime_attestation_appraisal_artifact_inventory();
3291
3292        assert_eq!(
3293            inventory.schema,
3294            RUNTIME_ATTESTATION_APPRAISAL_ARTIFACT_INVENTORY_SCHEMA
3295        );
3296        assert_eq!(inventory.entries.len(), 4);
3297        assert!(inventory
3298            .entries
3299            .iter()
3300            .any(
3301                |entry| entry.attestation_schema == AZURE_MAA_ATTESTATION_SCHEMA
3302                    && entry.vendor_claim_namespace == "azureMaa"
3303            ));
3304        assert!(inventory
3305            .entries
3306            .iter()
3307            .any(
3308                |entry| entry.attestation_schema == AWS_NITRO_ATTESTATION_SCHEMA
3309                    && entry.vendor_claim_namespace == "awsNitro"
3310            ));
3311        assert!(inventory
3312            .entries
3313            .iter()
3314            .any(
3315                |entry| entry.attestation_schema == GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA
3316                    && entry.vendor_claim_namespace == "googleAttestation"
3317            ));
3318        assert!(inventory
3319            .entries
3320            .iter()
3321            .any(
3322                |entry| entry.attestation_schema == ENTERPRISE_VERIFIER_ATTESTATION_SCHEMA
3323                    && entry.vendor_claim_namespace == "enterpriseVerifier"
3324            ));
3325        assert!(inventory.entries.iter().any(|entry| {
3326            entry.attestation_schema == AWS_NITRO_ATTESTATION_SCHEMA
3327                && entry
3328                    .normalized_claim_codes
3329                    .contains(&RuntimeAttestationNormalizedClaimCode::MeasurementDigest)
3330        }));
3331    }
3332
3333    #[test]
3334    fn runtime_attestation_claim_vocabulary_lists_portable_codes() {
3335        let vocabulary = runtime_attestation_normalized_claim_vocabulary();
3336
3337        assert_eq!(
3338            vocabulary.schema,
3339            RUNTIME_ATTESTATION_NORMALIZED_CLAIM_VOCABULARY_SCHEMA
3340        );
3341        assert!(vocabulary.entries.iter().any(|entry| {
3342            entry.code == RuntimeAttestationNormalizedClaimCode::SecureBootState
3343                && entry.legacy_assertion_key == "secureBoot"
3344                && entry.category == RuntimeAttestationNormalizedClaimCategory::Configuration
3345                && entry
3346                    .supported_verifier_families
3347                    .contains(&AttestationVerifierFamily::GoogleAttestation)
3348        }));
3349        assert!(vocabulary.entries.iter().any(|entry| {
3350            entry.code == RuntimeAttestationNormalizedClaimCode::MeasurementDigest
3351                && entry
3352                    .supported_verifier_families
3353                    .contains(&AttestationVerifierFamily::EnterpriseVerifier)
3354        }));
3355    }
3356
3357    #[test]
3358    fn runtime_attestation_reason_taxonomy_lists_structured_reasons() {
3359        let taxonomy = runtime_attestation_reason_taxonomy();
3360
3361        assert_eq!(taxonomy.schema, RUNTIME_ATTESTATION_REASON_TAXONOMY_SCHEMA);
3362        assert!(taxonomy.entries.iter().any(|entry| {
3363            entry.code == RuntimeAttestationAppraisalReasonCode::EvidenceVerified
3364                && entry.group == RuntimeAttestationAppraisalReasonGroup::Verification
3365                && entry.disposition == RuntimeAttestationAppraisalReasonDisposition::Pass
3366        }));
3367        assert!(taxonomy.entries.iter().any(|entry| {
3368            entry.code == RuntimeAttestationAppraisalReasonCode::UnsupportedClaimMapping
3369                && entry.group == RuntimeAttestationAppraisalReasonGroup::Compatibility
3370                && entry.disposition == RuntimeAttestationAppraisalReasonDisposition::Degrade
3371        }));
3372    }
3373
3374    #[test]
3375    fn runtime_attestation_trust_bundle_verifies_signed_descriptor_and_reference_values() {
3376        let signer = crate::crypto::Keypair::generate();
3377        let descriptor = create_signed_runtime_attestation_verifier_descriptor(
3378            RuntimeAttestationVerifierDescriptorArgs {
3379                signer: &signer,
3380                descriptor_id: "azure-prod".to_string(),
3381                verifier: "https://maa.contoso.test".to_string(),
3382                verifier_family: AttestationVerifierFamily::AzureMaa,
3383                adapter: AZURE_MAA_VERIFIER_ADAPTER.to_string(),
3384                attestation_schemas: vec![AZURE_MAA_ATTESTATION_SCHEMA.to_string()],
3385                signing_key_fingerprints: vec!["sha256:azure-key-1".to_string()],
3386                reference_values_uri: Some("https://maa.contoso.test/reference-values".to_string()),
3387                issued_at: 100,
3388                expires_at: 300,
3389            },
3390        )
3391        .expect("descriptor");
3392        let reference_values = create_signed_runtime_attestation_reference_value_set(
3393            RuntimeAttestationReferenceValueSetArgs {
3394                signer: &signer,
3395                reference_value_id: "azure-rv-1".to_string(),
3396                descriptor_id: "azure-prod".to_string(),
3397                verifier_family: AttestationVerifierFamily::AzureMaa,
3398                attestation_schema: AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
3399                source_uri: Some("https://maa.contoso.test/reference-values/1".to_string()),
3400                issued_at: 100,
3401                expires_at: 300,
3402                state: RuntimeAttestationReferenceValueState::Active,
3403                superseded_by: None,
3404                revoked_reason: None,
3405                measurements: BTreeMap::from([("mrEnclave".to_string(), json!("abc123"))]),
3406            },
3407        )
3408        .expect("reference values");
3409        let bundle =
3410            create_signed_runtime_attestation_trust_bundle(RuntimeAttestationTrustBundleArgs {
3411                signer: &signer,
3412                bundle_id: "bundle-1".to_string(),
3413                publisher: "https://trust.contoso.test".to_string(),
3414                version: 1,
3415                issued_at: 100,
3416                expires_at: 300,
3417                descriptors: vec![descriptor],
3418                reference_values: vec![reference_values],
3419            })
3420            .expect("bundle");
3421
3422        let verification =
3423            verify_signed_runtime_attestation_trust_bundle(&bundle, 150).expect("verify");
3424
3425        assert_eq!(verification.schema, RUNTIME_ATTESTATION_TRUST_BUNDLE_SCHEMA);
3426        assert_eq!(verification.descriptor_count, 1);
3427        assert_eq!(verification.reference_value_count, 1);
3428        assert_eq!(
3429            verification.verifier_families,
3430            vec![AttestationVerifierFamily::AzureMaa]
3431        );
3432    }
3433
3434    #[test]
3435    fn runtime_attestation_trust_bundle_rejects_expired_descriptor() {
3436        let signer = crate::crypto::Keypair::generate();
3437        let descriptor = create_signed_runtime_attestation_verifier_descriptor(
3438            RuntimeAttestationVerifierDescriptorArgs {
3439                signer: &signer,
3440                descriptor_id: "azure-prod".to_string(),
3441                verifier: "https://maa.contoso.test".to_string(),
3442                verifier_family: AttestationVerifierFamily::AzureMaa,
3443                adapter: AZURE_MAA_VERIFIER_ADAPTER.to_string(),
3444                attestation_schemas: vec![AZURE_MAA_ATTESTATION_SCHEMA.to_string()],
3445                signing_key_fingerprints: vec!["sha256:azure-key-1".to_string()],
3446                reference_values_uri: None,
3447                issued_at: 100,
3448                expires_at: 120,
3449            },
3450        )
3451        .expect("descriptor");
3452        let bundle =
3453            create_signed_runtime_attestation_trust_bundle(RuntimeAttestationTrustBundleArgs {
3454                signer: &signer,
3455                bundle_id: "bundle-2".to_string(),
3456                publisher: "https://trust.contoso.test".to_string(),
3457                version: 1,
3458                issued_at: 100,
3459                expires_at: 300,
3460                descriptors: vec![descriptor],
3461                reference_values: Vec::new(),
3462            })
3463            .expect("bundle");
3464
3465        let error = verify_signed_runtime_attestation_trust_bundle(&bundle, 150)
3466            .expect_err("expired descriptor");
3467        assert!(error
3468            .to_string()
3469            .contains("verifier descriptor `azure-prod` has expired"));
3470    }
3471
3472    #[test]
3473    fn runtime_attestation_trust_bundle_rejects_ambiguous_active_reference_values() {
3474        let signer = crate::crypto::Keypair::generate();
3475        let descriptor = create_signed_runtime_attestation_verifier_descriptor(
3476            RuntimeAttestationVerifierDescriptorArgs {
3477                signer: &signer,
3478                descriptor_id: "azure-prod".to_string(),
3479                verifier: "https://maa.contoso.test".to_string(),
3480                verifier_family: AttestationVerifierFamily::AzureMaa,
3481                adapter: AZURE_MAA_VERIFIER_ADAPTER.to_string(),
3482                attestation_schemas: vec![AZURE_MAA_ATTESTATION_SCHEMA.to_string()],
3483                signing_key_fingerprints: vec!["sha256:azure-key-1".to_string()],
3484                reference_values_uri: None,
3485                issued_at: 100,
3486                expires_at: 300,
3487            },
3488        )
3489        .expect("descriptor");
3490        let reference_a = create_signed_runtime_attestation_reference_value_set(
3491            RuntimeAttestationReferenceValueSetArgs {
3492                signer: &signer,
3493                reference_value_id: "azure-rv-1".to_string(),
3494                descriptor_id: "azure-prod".to_string(),
3495                verifier_family: AttestationVerifierFamily::AzureMaa,
3496                attestation_schema: AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
3497                source_uri: None,
3498                issued_at: 100,
3499                expires_at: 300,
3500                state: RuntimeAttestationReferenceValueState::Active,
3501                superseded_by: None,
3502                revoked_reason: None,
3503                measurements: BTreeMap::from([("mrEnclave".to_string(), json!("abc123"))]),
3504            },
3505        )
3506        .expect("reference values");
3507        let reference_b = create_signed_runtime_attestation_reference_value_set(
3508            RuntimeAttestationReferenceValueSetArgs {
3509                signer: &signer,
3510                reference_value_id: "azure-rv-2".to_string(),
3511                descriptor_id: "azure-prod".to_string(),
3512                verifier_family: AttestationVerifierFamily::AzureMaa,
3513                attestation_schema: AZURE_MAA_ATTESTATION_SCHEMA.to_string(),
3514                source_uri: None,
3515                issued_at: 100,
3516                expires_at: 300,
3517                state: RuntimeAttestationReferenceValueState::Active,
3518                superseded_by: None,
3519                revoked_reason: None,
3520                measurements: BTreeMap::from([("mrEnclave".to_string(), json!("def456"))]),
3521            },
3522        )
3523        .expect("reference values");
3524        let error =
3525            create_signed_runtime_attestation_trust_bundle(RuntimeAttestationTrustBundleArgs {
3526                signer: &signer,
3527                bundle_id: "bundle-3".to_string(),
3528                publisher: "https://trust.contoso.test".to_string(),
3529                version: 1,
3530                issued_at: 100,
3531                expires_at: 300,
3532                descriptors: vec![descriptor],
3533                reference_values: vec![reference_a, reference_b],
3534            })
3535            .expect_err("ambiguous reference values");
3536        assert!(error
3537            .to_string()
3538            .contains("ambiguous active reference values"));
3539    }
3540
3541    #[test]
3542    fn runtime_attestation_trust_bundle_rejects_reference_values_outside_descriptor_contract() {
3543        let signer = crate::crypto::Keypair::generate();
3544        let descriptor = create_signed_runtime_attestation_verifier_descriptor(
3545            RuntimeAttestationVerifierDescriptorArgs {
3546                signer: &signer,
3547                descriptor_id: "azure-prod".to_string(),
3548                verifier: "https://maa.contoso.test".to_string(),
3549                verifier_family: AttestationVerifierFamily::AzureMaa,
3550                adapter: AZURE_MAA_VERIFIER_ADAPTER.to_string(),
3551                attestation_schemas: vec![AZURE_MAA_ATTESTATION_SCHEMA.to_string()],
3552                signing_key_fingerprints: vec!["sha256:azure-key-1".to_string()],
3553                reference_values_uri: None,
3554                issued_at: 100,
3555                expires_at: 300,
3556            },
3557        )
3558        .expect("descriptor");
3559        let reference_values = create_signed_runtime_attestation_reference_value_set(
3560            RuntimeAttestationReferenceValueSetArgs {
3561                signer: &signer,
3562                reference_value_id: "google-rv-1".to_string(),
3563                descriptor_id: "azure-prod".to_string(),
3564                verifier_family: AttestationVerifierFamily::GoogleAttestation,
3565                attestation_schema: GOOGLE_CONFIDENTIAL_VM_ATTESTATION_SCHEMA.to_string(),
3566                source_uri: None,
3567                issued_at: 100,
3568                expires_at: 300,
3569                state: RuntimeAttestationReferenceValueState::Active,
3570                superseded_by: None,
3571                revoked_reason: None,
3572                measurements: BTreeMap::from([("hwModel".to_string(), json!("GCP_AMD_SEV"))]),
3573            },
3574        )
3575        .expect("reference values");
3576        let error =
3577            create_signed_runtime_attestation_trust_bundle(RuntimeAttestationTrustBundleArgs {
3578                signer: &signer,
3579                bundle_id: "bundle-4".to_string(),
3580                publisher: "https://trust.contoso.test".to_string(),
3581                version: 1,
3582                issued_at: 100,
3583                expires_at: 300,
3584                descriptors: vec![descriptor],
3585                reference_values: vec![reference_values],
3586            })
3587            .expect_err("mismatched reference values");
3588        assert!(error.to_string().contains("does not match verifier-family"));
3589    }
3590
3591    #[test]
3592    fn runtime_attestation_appraisal_result_ids_are_deterministic() {
3593        let evidence = sample_evidence();
3594        let appraisal = derive_runtime_attestation_appraisal(&evidence).expect("derive appraisal");
3595        let report = RuntimeAttestationAppraisalReport {
3596            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
3597            generated_at: 150,
3598            appraisal,
3599            policy_outcome: RuntimeAttestationPolicyOutcome {
3600                trust_policy_configured: true,
3601                accepted: true,
3602                effective_tier: RuntimeAssuranceTier::Verified,
3603                reason: None,
3604            },
3605        };
3606
3607        let first = RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
3608            .unwrap();
3609        let second =
3610            RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
3611                .unwrap();
3612
3613        assert_eq!(
3614            first.schema,
3615            RUNTIME_ATTESTATION_APPRAISAL_RESULT_SCHEMA.to_string()
3616        );
3617        assert_eq!(first.result_id, second.result_id);
3618        assert!(first.result_id.starts_with("appraisal-result-"));
3619    }
3620
3621    #[test]
3622    fn imported_runtime_attestation_rejects_invalid_signature() {
3623        let evidence = sample_evidence();
3624        let appraisal = derive_runtime_attestation_appraisal(&evidence).expect("derive appraisal");
3625        let report = RuntimeAttestationAppraisalReport {
3626            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
3627            generated_at: 150,
3628            appraisal,
3629            policy_outcome: RuntimeAttestationPolicyOutcome {
3630                trust_policy_configured: false,
3631                accepted: true,
3632                effective_tier: RuntimeAssuranceTier::Attested,
3633                reason: None,
3634            },
3635        };
3636        let result =
3637            RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
3638                .unwrap();
3639        let signer = crate::crypto::Keypair::generate();
3640        let signed = SignedRuntimeAttestationAppraisalResult::sign(result, &signer).unwrap();
3641        let mut tampered = signed.clone();
3642        tampered.body.issuer = "did:chio:test:other".to_string();
3643
3644        let import = evaluate_imported_runtime_attestation_appraisal(
3645            &RuntimeAttestationAppraisalImportRequest {
3646                signed_result: tampered,
3647                local_policy: RuntimeAttestationImportedAppraisalPolicy {
3648                    max_result_age_seconds: Some(120),
3649                    ..RuntimeAttestationImportedAppraisalPolicy {
3650                        trusted_issuers: Vec::new(),
3651                        trusted_signer_keys: Vec::new(),
3652                        allowed_verifier_families: Vec::new(),
3653                        max_result_age_seconds: None,
3654                        max_evidence_age_seconds: None,
3655                        maximum_effective_tier: None,
3656                        required_claims: BTreeMap::new(),
3657                    }
3658                },
3659            },
3660            160,
3661        );
3662
3663        assert_eq!(
3664            import.local_policy_outcome.disposition,
3665            RuntimeAttestationImportDisposition::Reject
3666        );
3667        assert_eq!(
3668            import.local_policy_outcome.reason_codes,
3669            vec![RuntimeAttestationImportReasonCode::InvalidSignature]
3670        );
3671    }
3672
3673    #[test]
3674    fn imported_runtime_attestation_can_be_attenuated_locally() {
3675        let evidence = sample_evidence();
3676        let appraisal = derive_runtime_attestation_appraisal(&evidence).expect("derive appraisal");
3677        let report = RuntimeAttestationAppraisalReport {
3678            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
3679            generated_at: 150,
3680            appraisal,
3681            policy_outcome: RuntimeAttestationPolicyOutcome {
3682                trust_policy_configured: false,
3683                accepted: true,
3684                effective_tier: RuntimeAssuranceTier::Attested,
3685                reason: None,
3686            },
3687        };
3688        let result =
3689            RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
3690                .unwrap();
3691        let signer = crate::crypto::Keypair::generate();
3692        let signed = SignedRuntimeAttestationAppraisalResult::sign(result, &signer).unwrap();
3693
3694        let import = evaluate_imported_runtime_attestation_appraisal(
3695            &RuntimeAttestationAppraisalImportRequest {
3696                signed_result: signed,
3697                local_policy: RuntimeAttestationImportedAppraisalPolicy {
3698                    trusted_issuers: vec!["did:chio:test:issuer".to_string()],
3699                    trusted_signer_keys: vec![signer.public_key().to_hex()],
3700                    allowed_verifier_families: vec![AttestationVerifierFamily::AzureMaa],
3701                    max_result_age_seconds: Some(300),
3702                    max_evidence_age_seconds: Some(300),
3703                    maximum_effective_tier: Some(RuntimeAssuranceTier::Basic),
3704                    required_claims: BTreeMap::new(),
3705                },
3706            },
3707            160,
3708        );
3709
3710        assert_eq!(
3711            import.local_policy_outcome.disposition,
3712            RuntimeAttestationImportDisposition::Attenuate
3713        );
3714        assert_eq!(
3715            import.local_policy_outcome.effective_tier,
3716            RuntimeAssuranceTier::Basic
3717        );
3718        assert_eq!(
3719            import.local_policy_outcome.reason_codes,
3720            vec![RuntimeAttestationImportReasonCode::TierAttenuated]
3721        );
3722    }
3723
3724    #[test]
3725    fn imported_runtime_attestation_rejects_stale_result_and_evidence() {
3726        let evidence = sample_evidence();
3727        let appraisal = derive_runtime_attestation_appraisal(&evidence).expect("derive appraisal");
3728        let report = RuntimeAttestationAppraisalReport {
3729            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
3730            generated_at: 150,
3731            appraisal,
3732            policy_outcome: RuntimeAttestationPolicyOutcome {
3733                trust_policy_configured: false,
3734                accepted: true,
3735                effective_tier: RuntimeAssuranceTier::Attested,
3736                reason: None,
3737            },
3738        };
3739        let result =
3740            RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
3741                .unwrap();
3742        let signer = crate::crypto::Keypair::generate();
3743        let signed = SignedRuntimeAttestationAppraisalResult::sign(result, &signer).unwrap();
3744
3745        let import = evaluate_imported_runtime_attestation_appraisal(
3746            &RuntimeAttestationAppraisalImportRequest {
3747                signed_result: signed,
3748                local_policy: RuntimeAttestationImportedAppraisalPolicy {
3749                    trusted_issuers: vec!["did:chio:test:issuer".to_string()],
3750                    trusted_signer_keys: vec![signer.public_key().to_hex()],
3751                    allowed_verifier_families: vec![AttestationVerifierFamily::AzureMaa],
3752                    max_result_age_seconds: Some(20),
3753                    max_evidence_age_seconds: Some(20),
3754                    maximum_effective_tier: None,
3755                    required_claims: BTreeMap::new(),
3756                },
3757            },
3758            200,
3759        );
3760
3761        assert_eq!(
3762            import.local_policy_outcome.disposition,
3763            RuntimeAttestationImportDisposition::Reject
3764        );
3765        assert!(import
3766            .local_policy_outcome
3767            .reason_codes
3768            .contains(&RuntimeAttestationImportReasonCode::ResultStale));
3769        assert!(import
3770            .local_policy_outcome
3771            .reason_codes
3772            .contains(&RuntimeAttestationImportReasonCode::EvidenceStale));
3773    }
3774
3775    #[test]
3776    fn imported_runtime_attestation_rejects_schema_family_mismatch() {
3777        let evidence = sample_evidence();
3778        let appraisal = derive_runtime_attestation_appraisal(&evidence).expect("derive appraisal");
3779        let report = RuntimeAttestationAppraisalReport {
3780            schema: RUNTIME_ATTESTATION_APPRAISAL_REPORT_SCHEMA.to_string(),
3781            generated_at: 150,
3782            appraisal,
3783            policy_outcome: RuntimeAttestationPolicyOutcome {
3784                trust_policy_configured: false,
3785                accepted: true,
3786                effective_tier: RuntimeAssuranceTier::Attested,
3787                reason: None,
3788            },
3789        };
3790        let mut result =
3791            RuntimeAttestationAppraisalResult::from_report("did:chio:test:issuer", &report)
3792                .unwrap();
3793        result.appraisal.verifier.verifier_family = AttestationVerifierFamily::GoogleAttestation;
3794        let signer = crate::crypto::Keypair::generate();
3795        let signed = SignedRuntimeAttestationAppraisalResult::sign(result, &signer).unwrap();
3796
3797        let import = evaluate_imported_runtime_attestation_appraisal(
3798            &RuntimeAttestationAppraisalImportRequest {
3799                signed_result: signed,
3800                local_policy: RuntimeAttestationImportedAppraisalPolicy {
3801                    trusted_issuers: vec!["did:chio:test:issuer".to_string()],
3802                    trusted_signer_keys: vec![signer.public_key().to_hex()],
3803                    allowed_verifier_families: vec![AttestationVerifierFamily::GoogleAttestation],
3804                    max_result_age_seconds: Some(300),
3805                    max_evidence_age_seconds: Some(300),
3806                    maximum_effective_tier: None,
3807                    required_claims: BTreeMap::new(),
3808                },
3809            },
3810            160,
3811        );
3812
3813        assert_eq!(
3814            import.local_policy_outcome.disposition,
3815            RuntimeAttestationImportDisposition::Reject
3816        );
3817        assert_eq!(
3818            import.local_policy_outcome.reason_codes,
3819            vec![RuntimeAttestationImportReasonCode::UnsupportedAppraisalSchema]
3820        );
3821    }
3822}