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}