a3s_runtime/
attestation.rs1use crate::contract::{
2 ArtifactRef, IsolationLevel, RuntimeEvidence, RuntimeObservation, RuntimeUnitClass,
3 RuntimeUnitSpec, RuntimeUnitState,
4};
5use serde::Serialize;
6use sha2::{Digest, Sha256};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct RuntimeAttestationBinding {
16 pub unit_id: String,
17 pub generation: u64,
18 pub class: RuntimeUnitClass,
19 pub isolation: IsolationLevel,
20 pub state: RuntimeUnitState,
21 pub spec_digest: String,
22 pub identity_attachment_digest: String,
23 pub provider_resource_id: String,
24 pub provider_build: String,
25 pub observed_at_ms: u64,
26 pub evidence: RuntimeEvidence,
27 pub provider_attestation: ArtifactRef,
28}
29
30impl RuntimeAttestationBinding {
31 pub fn from_observation(
35 spec: &RuntimeUnitSpec,
36 observation: &RuntimeObservation,
37 ) -> Result<Self, String> {
38 observation.validate_against(spec)?;
39 let identity_attachment_digest = spec
40 .identity_attachment_digest
41 .as_ref()
42 .ok_or_else(|| "Runtime specification has no identity attachment".to_string())?;
43 let provider_resource_id = observation
44 .provider_resource_id
45 .as_ref()
46 .ok_or_else(|| "Runtime attestation has no provider resource identity".to_string())?;
47 let provider_build = observation
48 .provider_build
49 .as_ref()
50 .ok_or_else(|| "Runtime attestation has no provider build identity".to_string())?;
51 let evidence = observation
52 .evidence
53 .as_ref()
54 .ok_or_else(|| "Runtime attestation has no provider evidence".to_string())?;
55 if evidence.provider_build != *provider_build {
56 return Err("Runtime attestation provider build evidence drifted".into());
57 }
58 if evidence.identity_attachment_digest.as_ref() != Some(identity_attachment_digest) {
59 return Err("Runtime attestation identity attachment evidence drifted".into());
60 }
61 let provider_attestation = observation
62 .provider_attestation
63 .as_ref()
64 .ok_or_else(|| "Runtime observation has no provider attestation".to_string())?;
65 if observation.observed_at_ms == 0 {
66 return Err("Runtime attestation observation time must be positive".into());
67 }
68 let value = Self {
69 unit_id: spec.unit_id.clone(),
70 generation: spec.generation,
71 class: spec.class,
72 isolation: spec.isolation,
73 state: observation.state,
74 spec_digest: observation.spec_digest.clone(),
75 identity_attachment_digest: identity_attachment_digest.clone(),
76 provider_resource_id: provider_resource_id.clone(),
77 provider_build: provider_build.clone(),
78 observed_at_ms: observation.observed_at_ms,
79 evidence: evidence.clone(),
80 provider_attestation: provider_attestation.clone(),
81 };
82 value.validate()?;
83 Ok(value)
84 }
85
86 pub fn validate(&self) -> Result<(), String> {
87 crate::contract::validate_id("unit_id", &self.unit_id, 512)?;
88 if self.generation == 0 || self.observed_at_ms == 0 {
89 return Err(
90 "Runtime attestation generation and observation time must be positive".into(),
91 );
92 }
93 crate::contract::validate_digest(&self.spec_digest)?;
94 crate::contract::validate_digest(&self.identity_attachment_digest)?;
95 crate::contract::validate_nonempty(
96 "provider_resource_id",
97 &self.provider_resource_id,
98 1024,
99 )?;
100 crate::contract::validate_nonempty("provider_build", &self.provider_build, 255)?;
101 self.evidence.validate()?;
102 self.provider_attestation.validate()?;
103 if self.evidence.provider_build != self.provider_build
104 || self.evidence.spec_digest != self.spec_digest
105 || self.evidence.identity_attachment_digest.as_ref()
106 != Some(&self.identity_attachment_digest)
107 {
108 return Err("Runtime attestation evidence does not match its binding".into());
109 }
110 Ok(())
111 }
112
113 pub fn digest(&self) -> Result<String, String> {
115 self.validate()?;
116 #[derive(Serialize)]
117 struct CanonicalBinding<'a> {
118 unit_id: &'a str,
119 generation: u64,
120 class: RuntimeUnitClass,
121 isolation: IsolationLevel,
122 state: RuntimeUnitState,
123 spec_digest: &'a str,
124 identity_attachment_digest: &'a str,
125 provider_resource_id: &'a str,
126 provider_build: &'a str,
127 observed_at_ms: u64,
128 evidence: &'a RuntimeEvidence,
129 provider_attestation: &'a ArtifactRef,
130 }
131 let bytes = serde_json::to_vec(&CanonicalBinding {
132 unit_id: &self.unit_id,
133 generation: self.generation,
134 class: self.class,
135 isolation: self.isolation,
136 state: self.state,
137 spec_digest: &self.spec_digest,
138 identity_attachment_digest: &self.identity_attachment_digest,
139 provider_resource_id: &self.provider_resource_id,
140 provider_build: &self.provider_build,
141 observed_at_ms: self.observed_at_ms,
142 evidence: &self.evidence,
143 provider_attestation: &self.provider_attestation,
144 })
145 .map_err(|error| format!("could not encode Runtime attestation binding: {error}"))?;
146 Ok(format!("sha256:{:x}", Sha256::digest(bytes)))
147 }
148}