1use std::collections::BTreeMap;
10
11use serde::{Deserialize, Serialize};
12
13use crate::rfc::wire_types::attestation::{
14 AbsenceClaim, EntropyReport, ForensicSummary, ForgeryCostEstimate,
15};
16
17pub const POP_EAR_PROFILE: &str = "urn:ietf:params:rats:eat:profile:pop:1.0";
19
20pub const CWT_KEY_IAT: i64 = 6;
21pub const CWT_KEY_EAT_PROFILE: i64 = 265;
22pub const CWT_KEY_SUBMODS: i64 = 266;
23pub const EAR_KEY_STATUS: i64 = 1000;
24pub const EAR_KEY_TRUST_VECTOR: i64 = 1001;
25pub const EAR_KEY_POLICY_ID: i64 = 1003;
26pub const EAR_KEY_VERIFIER_ID: i64 = 1004;
27
28pub const POP_KEY_SEAL: i64 = 70001;
29pub const POP_KEY_EVIDENCE_REF: i64 = 70002;
30pub const POP_KEY_ENTROPY: i64 = 70003;
31pub const POP_KEY_FORGERY_COST: i64 = 70004;
32pub const POP_KEY_FORENSIC: i64 = 70005;
33pub const POP_KEY_CHAIN_LENGTH: i64 = 70006;
34pub const POP_KEY_CHAIN_DURATION: i64 = 70007;
35pub const POP_KEY_ABSENCE: i64 = 70008;
36pub const POP_KEY_WARNINGS: i64 = 70009;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[repr(i8)]
41pub enum Ar4siStatus {
42 None = 0,
44 Affirming = 2,
46 Warning = 32,
48 Contraindicated = 96,
50}
51
52impl Ar4siStatus {
53 pub fn from_i8(v: i8) -> Self {
55 match v {
56 2 => Self::Affirming,
57 32 => Self::Warning,
58 96 => Self::Contraindicated,
59 _ => Self::None,
60 }
61 }
62
63 pub fn as_str(&self) -> &'static str {
65 match self {
66 Self::None => "none",
67 Self::Affirming => "affirming",
68 Self::Warning => "warning",
69 Self::Contraindicated => "contraindicated",
70 }
71 }
72}
73
74#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
79pub struct TrustworthinessVector {
80 #[serde(rename = "0")]
82 pub instance_identity: i8,
83 #[serde(rename = "1")]
85 pub configuration: i8,
86 #[serde(rename = "2")]
88 pub executables: i8,
89 #[serde(rename = "3")]
91 pub file_system: i8,
92 #[serde(rename = "4")]
94 pub hardware: i8,
95 #[serde(rename = "5")]
97 pub runtime_opaque: i8,
98 #[serde(rename = "6")]
100 pub storage_opaque: i8,
101 #[serde(rename = "7")]
103 pub sourced_data: i8,
104}
105
106impl TrustworthinessVector {
107 pub fn min_component(&self) -> i8 {
109 [
110 self.instance_identity,
111 self.configuration,
112 self.executables,
113 self.file_system,
114 self.hardware,
115 self.runtime_opaque,
116 self.storage_opaque,
117 self.sourced_data,
118 ]
119 .into_iter()
120 .min()
121 .unwrap_or(0)
122 }
123
124 pub fn overall_status(&self) -> Ar4siStatus {
126 let min = self.min_component();
127 if min >= Ar4siStatus::Contraindicated as i8 {
128 Ar4siStatus::Contraindicated
129 } else if min >= Ar4siStatus::Warning as i8 {
130 Ar4siStatus::Warning
131 } else if min >= Ar4siStatus::Affirming as i8 {
132 Ar4siStatus::Affirming
133 } else {
134 Ar4siStatus::None
135 }
136 }
137
138 pub fn header_string(&self) -> String {
140 format!(
141 "II={} CO={} EX={} FS={} HW={} RO={} SO={} SD={}",
142 self.instance_identity,
143 self.configuration,
144 self.executables,
145 self.file_system,
146 self.hardware,
147 self.runtime_opaque,
148 self.storage_opaque,
149 self.sourced_data,
150 )
151 }
152
153 pub fn parse_header(s: &str) -> Option<Self> {
155 let mut vals = [0i8; 8];
156 let labels = ["II=", "CO=", "EX=", "FS=", "HW=", "RO=", "SO=", "SD="];
157 for (i, label) in labels.iter().enumerate() {
158 let part = s.split_whitespace().find(|p| p.starts_with(label))?;
159 vals[i] = part[label.len()..].parse().ok()?;
160 }
161 Some(Self {
162 instance_identity: vals[0],
163 configuration: vals[1],
164 executables: vals[2],
165 file_system: vals[3],
166 hardware: vals[4],
167 runtime_opaque: vals[5],
168 storage_opaque: vals[6],
169 sourced_data: vals[7],
170 })
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct VerifierId {
177 pub build: String,
179 pub developer: String,
181}
182
183impl Default for VerifierId {
184 fn default() -> Self {
185 Self {
186 build: format!("cpop-engine/{}", env!("CARGO_PKG_VERSION")),
187 developer: "writerslogic".to_string(),
188 }
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct SealClaims {
195 #[serde(with = "hex_bytes_32")]
197 pub h1: [u8; 32],
198 #[serde(with = "hex_bytes_32")]
200 pub h2: [u8; 32],
201 #[serde(with = "hex_bytes_32")]
203 pub h3: [u8; 32],
204 #[serde(with = "hex_bytes_64")]
206 pub signature: [u8; 64],
207 #[serde(with = "hex_bytes_32")]
209 pub public_key: [u8; 32],
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct EarAppraisal {
215 #[serde(rename = "1000")]
217 pub ear_status: Ar4siStatus,
218
219 #[serde(rename = "1001", default, skip_serializing_if = "Option::is_none")]
221 pub ear_trustworthiness_vector: Option<TrustworthinessVector>,
222
223 #[serde(rename = "1003", default, skip_serializing_if = "Option::is_none")]
225 pub ear_appraisal_policy_id: Option<String>,
226
227 #[serde(rename = "70001", default, skip_serializing_if = "Option::is_none")]
229 pub pop_seal: Option<SealClaims>,
230
231 #[serde(rename = "70002", default, skip_serializing_if = "Option::is_none")]
233 pub pop_evidence_ref: Option<Vec<u8>>,
234
235 #[serde(rename = "70003", default, skip_serializing_if = "Option::is_none")]
237 pub pop_entropy_report: Option<EntropyReport>,
238
239 #[serde(rename = "70004", default, skip_serializing_if = "Option::is_none")]
241 pub pop_forgery_cost: Option<ForgeryCostEstimate>,
242
243 #[serde(rename = "70005", default, skip_serializing_if = "Option::is_none")]
245 pub pop_forensic_summary: Option<ForensicSummary>,
246
247 #[serde(rename = "70006", default, skip_serializing_if = "Option::is_none")]
249 pub pop_chain_length: Option<u64>,
250
251 #[serde(rename = "70007", default, skip_serializing_if = "Option::is_none")]
253 pub pop_chain_duration: Option<u64>,
254
255 #[serde(rename = "70008", default, skip_serializing_if = "Option::is_none")]
257 pub pop_absence_claims: Option<Vec<AbsenceClaim>>,
258
259 #[serde(rename = "70009", default, skip_serializing_if = "Option::is_none")]
261 pub pop_warnings: Option<Vec<String>>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct EarToken {
267 #[serde(rename = "265")]
269 pub eat_profile: String,
270
271 #[serde(rename = "6")]
273 pub iat: i64,
274
275 #[serde(rename = "1004")]
277 pub ear_verifier_id: VerifierId,
278
279 #[serde(rename = "266")]
281 pub submods: BTreeMap<String, EarAppraisal>,
282}
283
284impl EarToken {
285 pub fn overall_status(&self) -> Ar4siStatus {
287 self.submods
288 .values()
289 .map(|a| a.ear_status as i8)
290 .min()
291 .map(Ar4siStatus::from_i8)
292 .unwrap_or(Ar4siStatus::None)
293 }
294
295 pub fn pop_appraisal(&self) -> Option<&EarAppraisal> {
296 self.submods.get("pop")
297 }
298}
299
300mod hex_bytes_32 {
301 use serde::{self, Deserialize, Deserializer, Serializer};
302
303 pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
304 where
305 S: Serializer,
306 {
307 serializer.serialize_str(&hex::encode(bytes))
308 }
309
310 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
311 where
312 D: Deserializer<'de>,
313 {
314 let s = String::deserialize(deserializer)?;
315 let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
316 if bytes.len() != 32 {
317 return Err(serde::de::Error::custom("expected 32 bytes"));
318 }
319 let mut arr = [0u8; 32];
320 arr.copy_from_slice(&bytes);
321 Ok(arr)
322 }
323}
324
325mod hex_bytes_64 {
326 use serde::{self, Deserialize, Deserializer, Serializer};
327
328 pub fn serialize<S>(bytes: &[u8; 64], serializer: S) -> Result<S::Ok, S::Error>
329 where
330 S: Serializer,
331 {
332 serializer.serialize_str(&hex::encode(bytes))
333 }
334
335 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 64], D::Error>
336 where
337 D: Deserializer<'de>,
338 {
339 let s = String::deserialize(deserializer)?;
340 let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
341 if bytes.len() != 64 {
342 return Err(serde::de::Error::custom("expected 64 bytes"));
343 }
344 let mut arr = [0u8; 64];
345 arr.copy_from_slice(&bytes);
346 Ok(arr)
347 }
348}