ttk_server/attestation/eat.rs
1//! RFC 9711 Entity Attestation Token (EAT) data model.
2//!
3//! Defines the full set of IANA-registered EAT claim keys ([`EatClaimKey`]) and a
4//! strongly-typed claims-set structure ([`EatClaimsSet`]) that can be serialised to
5//! CBOR. Nitro-enclave-specific wrapping logic lives in [`crate::attestation::nitro_doc`].
6use ciborium::value::Value;
7
8// ---------------------------------------------------------------------------
9// IANA-registered EAT/CWT claim keys (RFC 9711)
10// https://www.iana.org/assignments/cwt
11// ---------------------------------------------------------------------------
12
13/// CWT / EAT claim key integers as registered by RFC 9711.
14///
15/// Standard JWT/CWT claims that predate EAT (e.g., `iat = 6`) are included
16/// for completeness. The RFC 9711-specific claims start at key 10 (Nonce)
17/// and run through 275 (Intended Use).
18#[allow(dead_code)]
19#[repr(i64)]
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum EatClaimKey {
22 // ---- Standard CWT claims used by EAT ----
23 /// Issued-At (seconds since epoch). CWT key 6.
24 Iat = 6,
25
26 // ---- RFC 9711 claims ----
27 /// Nonce – `eat_nonce`. Key 10. Value: bstr or array.
28 Nonce = 10,
29 /// Universal Entity ID – `ueid`. Key 256. Value: bstr.
30 Ueid = 256,
31 /// Semipermanent UEIDs – `sueids`. Key 257. Value: map.
32 Sueids = 257,
33 /// Hardware OEM ID – `oemid`. Key 258. Value: bstr or int.
34 OemId = 258,
35 /// Hardware Model – `hwmodel`. Key 259. Value: bstr.
36 HwModel = 259,
37 /// Hardware Version – `hwversion`. Key 260. Value: array.
38 HwVersion = 260,
39 /// Uptime – `uptime`. Key 261. Value: uint.
40 Uptime = 261,
41 /// OEM Authorized Boot – `oemboot`. Key 262. Value: bool.
42 OemBoot = 262,
43 /// Debug Status – `dbgstat`. Key 263. Value: uint.
44 DbgStat = 263,
45 /// Location – `location`. Key 264. Value: map.
46 Location = 264,
47 /// EAT Profile – `eat_profile`. Key 265. Value: uri or oid.
48 EatProfile = 265,
49 /// Submodules Section – `submods`. Key 266. Value: map.
50 Submods = 266,
51 /// Boot Count – `bootcount`. Key 267. Value: uint.
52 BootCount = 267,
53 /// Boot Seed – `bootseed`. Key 268. Value: bstr.
54 BootSeed = 268,
55 /// DLOAs (Digital Letters of Approval) – `dloas`. Key 269. Value: array.
56 Dloas = 269,
57 /// Software Name – `swname`. Key 270. Value: tstr.
58 SwName = 270,
59 /// Software Version – `swversion`. Key 271. Value: array.
60 SwVersion = 271,
61 /// Software Manifests – `manifests`. Key 272. Value: array.
62 Manifests = 272,
63 /// Measurements – `measurements`. Key 273. Value: array.
64 Measurements = 273,
65 /// Software Measurement Results – `measres`. Key 274. Value: array.
66 MeasRes = 274,
67 /// Intended Use – `intuse`. Key 275. Value: uint.
68 IntUse = 275,
69}
70
71/// Converts a claim key into its integer CBOR map key.
72impl From<EatClaimKey> for i64 {
73 /// Returns the numeric key of `k`.
74 fn from(k: EatClaimKey) -> i64 {
75 k as i64
76 }
77}
78
79// ---------------------------------------------------------------------------
80// RFC 9711 EAT claims-set structure
81// ---------------------------------------------------------------------------
82
83/// Strongly-typed representation of an RFC 9711 EAT claims-set.
84///
85/// Every field is `Option<…>` so that only the claims relevant to a
86/// particular token need to be populated. Fields whose CBOR value type
87/// is composite (map / array) are left as raw `ciborium::value::Value` so
88/// that callers can construct them without an additional indirection layer.
89///
90/// # Claim → CBOR key mapping
91///
92/// | Field | JWT name | CWT key |
93/// |-----------------|---------------|---------|
94/// | `iat` | — | 6 |
95/// | `nonce` | eat_nonce | 10 |
96/// | `ueid` | ueid | 256 |
97/// | `sueids` | sueids | 257 |
98/// | `oem_id` | oemid | 258 |
99/// | `hw_model` | hwmodel | 259 |
100/// | `hw_version` | hwversion | 260 |
101/// | `uptime` | uptime | 261 |
102/// | `oem_boot` | oemboot | 262 |
103/// | `dbg_stat` | dbgstat | 263 |
104/// | `location` | location | 264 |
105/// | `eat_profile` | eat_profile | 265 |
106/// | `submods` | submods | 266 |
107/// | `boot_count` | bootcount | 267 |
108/// | `boot_seed` | bootseed | 268 |
109/// | `dloas` | dloas | 269 |
110/// | `sw_name` | swname | 270 |
111/// | `sw_version` | swversion | 271 |
112/// | `manifests` | manifests | 272 |
113/// | `measurements` | measurements | 273 |
114/// | `meas_res` | measres | 274 |
115/// | `int_use` | intuse | 275 |
116#[derive(Clone, Debug, Default)]
117pub struct EatClaimsSet {
118 // ---- Standard CWT ----
119 /// Issued-At: seconds since POSIX epoch (CWT key 6).
120 pub iat: Option<i64>,
121
122 // ---- RFC 9711 ----
123 /// Nonce – `eat_nonce` (key 10).
124 /// CBOR value: bstr or array-of-bstr.
125 pub nonce: Option<Value>,
126
127 /// Universal Entity ID – `ueid` (key 256).
128 /// A byte string whose first byte encodes the UEID type
129 /// (0x01 = RAND, 0x02 = IEEE EUI, 0x03 = IMEI, …).
130 pub ueid: Option<Vec<u8>>,
131
132 /// Semipermanent UEIDs – `sueids` (key 257).
133 /// CBOR value: map of label → bstr.
134 pub sueids: Option<Value>,
135
136 /// Hardware OEM ID – `oemid` (key 258).
137 /// CBOR value: bstr or int.
138 pub oem_id: Option<Value>,
139
140 /// Hardware Model – `hwmodel` (key 259).
141 /// A byte string that uniquely identifies the model within an OEM.
142 pub hw_model: Option<Vec<u8>>,
143
144 /// Hardware Version – `hwversion` (key 260).
145 /// CBOR value: array \[version-string, scheme\].
146 pub hw_version: Option<Value>,
147
148 /// Uptime in seconds – `uptime` (key 261).
149 pub uptime: Option<u64>,
150
151 /// OEM Authorized Boot indicator – `oemboot` (key 262).
152 /// `true` when the booted software is OEM-authorized.
153 pub oem_boot: Option<bool>,
154
155 /// Debug Status – `dbgstat` (key 263).
156 /// Encoded as a uint; see RFC 9711 Section 4.2.8 for the registry.
157 pub dbg_stat: Option<u64>,
158
159 /// Geographic Location – `location` (key 264).
160 /// CBOR value: map with optional latitude, longitude, altitude, …
161 pub location: Option<Value>,
162
163 /// EAT Profile – `eat_profile` (key 265).
164 /// A URI or OID identifying the profile this token conforms to.
165 pub eat_profile: Option<String>,
166
167 /// Submodules Section – `submods` (key 266).
168 /// CBOR value: map of submodule-name → nested-token or claims-set.
169 pub submods: Option<Value>,
170
171 /// Boot Count – `bootcount` (key 267).
172 pub boot_count: Option<u64>,
173
174 /// Boot Seed – `bootseed` (key 268).
175 /// A byte string that changes on every boot; ties measurements to a
176 /// single boot cycle.
177 pub boot_seed: Option<Vec<u8>>,
178
179 /// DLOAs – `dloas` (key 269).
180 /// CBOR value: array of DLOA maps (Digital Letters of Approval).
181 pub dloas: Option<Value>,
182
183 /// Software Name – `swname` (key 270).
184 pub sw_name: Option<String>,
185
186 /// Software Version – `swversion` (key 271).
187 /// CBOR value: array \[version-string, scheme\].
188 pub sw_version: Option<Value>,
189
190 /// Software Manifests – `manifests` (key 272).
191 /// CBOR value: array of CoSWID or similar manifest structures.
192 pub manifests: Option<Value>,
193
194 /// Measurements – `measurements` (key 273).
195 /// CBOR value: array of measurement maps.
196 pub measurements: Option<Value>,
197
198 /// Software Measurement Results – `measres` (key 274).
199 /// CBOR value: array of measurement-result maps.
200 pub meas_res: Option<Value>,
201
202 /// Intended Use – `intuse` (key 275).
203 /// Encoded as a uint; see RFC 9711 Section 4.2.20 for the registry.
204 pub int_use: Option<u64>,
205}
206
207/// CBOR encoding and decoding of the claims-set.
208impl EatClaimsSet {
209 /// Serialise the populated fields into a CBOR map (`Value::Map`).
210 ///
211 /// Only `Some(…)` fields are emitted; absent claims are omitted from
212 /// the encoding, consistent with RFC 9711's optional-claim model.
213 pub fn to_cbor_value(&self) -> Value {
214 let mut map: Vec<(Value, Value)> = Vec::new();
215
216 macro_rules! push_int {
217 ($key:expr, $val:expr) => {
218 if let Some(v) = $val {
219 map.push((
220 Value::Integer(($key as i64).into()),
221 Value::Integer((v as i64).into()),
222 ));
223 }
224 };
225 }
226 macro_rules! push_bool {
227 ($key:expr, $val:expr) => {
228 if let Some(v) = $val {
229 map.push((Value::Integer(($key as i64).into()), Value::Bool(v)));
230 }
231 };
232 }
233 macro_rules! push_bytes {
234 ($key:expr, $val:expr) => {
235 if let Some(ref v) = $val {
236 map.push((
237 Value::Integer(($key as i64).into()),
238 Value::Bytes(v.clone()),
239 ));
240 }
241 };
242 }
243 macro_rules! push_text {
244 ($key:expr, $val:expr) => {
245 if let Some(ref v) = $val {
246 map.push((Value::Integer(($key as i64).into()), Value::Text(v.clone())));
247 }
248 };
249 }
250 macro_rules! push_raw {
251 ($key:expr, $val:expr) => {
252 if let Some(ref v) = $val {
253 map.push((Value::Integer(($key as i64).into()), v.clone()));
254 }
255 };
256 }
257
258 push_int!(EatClaimKey::Iat, self.iat);
259 push_raw!(EatClaimKey::Nonce, self.nonce);
260 push_bytes!(EatClaimKey::Ueid, self.ueid);
261 push_raw!(EatClaimKey::Sueids, self.sueids);
262 push_raw!(EatClaimKey::OemId, self.oem_id);
263 push_bytes!(EatClaimKey::HwModel, self.hw_model);
264 push_raw!(EatClaimKey::HwVersion, self.hw_version);
265 push_int!(EatClaimKey::Uptime, self.uptime);
266 push_bool!(EatClaimKey::OemBoot, self.oem_boot);
267 push_int!(EatClaimKey::DbgStat, self.dbg_stat);
268 push_raw!(EatClaimKey::Location, self.location);
269 push_text!(EatClaimKey::EatProfile, self.eat_profile);
270 push_raw!(EatClaimKey::Submods, self.submods);
271 push_int!(EatClaimKey::BootCount, self.boot_count);
272 push_bytes!(EatClaimKey::BootSeed, self.boot_seed);
273 push_raw!(EatClaimKey::Dloas, self.dloas);
274 push_text!(EatClaimKey::SwName, self.sw_name);
275 push_raw!(EatClaimKey::SwVersion, self.sw_version);
276 push_raw!(EatClaimKey::Manifests, self.manifests);
277 push_raw!(EatClaimKey::Measurements, self.measurements);
278 push_raw!(EatClaimKey::MeasRes, self.meas_res);
279 push_int!(EatClaimKey::IntUse, self.int_use);
280
281 Value::Map(map)
282 }
283
284 /// Encode the claims-set to raw CBOR bytes.
285 pub fn to_cbor_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
286 let mut out = Vec::new();
287 ciborium::ser::into_writer(&self.to_cbor_value(), &mut out)?;
288 Ok(out)
289 }
290
291 /// Parse from a CBOR `Value::Map` representation into [`EatClaimsSet`].
292 pub fn from_cbor_value(value: &Value) -> Result<Self, Box<dyn std::error::Error>> {
293 let map = match value {
294 Value::Map(m) => m,
295 _ => return Err("Expected CBOR Map for EatClaimsSet".into()),
296 };
297
298 let mut claims = EatClaimsSet::default();
299
300 for (k, v) in map {
301 let key_int = match k {
302 Value::Integer(i) => i128::from(*i) as i64,
303 _ => continue,
304 };
305
306 match key_int {
307 x if x == EatClaimKey::Iat as i64 => {
308 if let Value::Integer(i) = v {
309 claims.iat = Some(i128::from(*i) as i64);
310 }
311 }
312 x if x == EatClaimKey::Nonce as i64 => {
313 claims.nonce = Some(v.clone());
314 }
315 x if x == EatClaimKey::Ueid as i64 => {
316 if let Value::Bytes(b) = v {
317 claims.ueid = Some(b.clone());
318 }
319 }
320 x if x == EatClaimKey::Sueids as i64 => {
321 claims.sueids = Some(v.clone());
322 }
323 x if x == EatClaimKey::OemId as i64 => {
324 claims.oem_id = Some(v.clone());
325 }
326 x if x == EatClaimKey::HwModel as i64 => {
327 if let Value::Bytes(b) = v {
328 claims.hw_model = Some(b.clone());
329 }
330 }
331 x if x == EatClaimKey::HwVersion as i64 => {
332 claims.hw_version = Some(v.clone());
333 }
334 x if x == EatClaimKey::Uptime as i64 => {
335 if let Value::Integer(i) = v {
336 claims.uptime = Some(i128::from(*i) as u64);
337 }
338 }
339 x if x == EatClaimKey::OemBoot as i64 => {
340 if let Value::Bool(b) = v {
341 claims.oem_boot = Some(*b);
342 }
343 }
344 x if x == EatClaimKey::DbgStat as i64 => {
345 if let Value::Integer(i) = v {
346 claims.dbg_stat = Some(i128::from(*i) as u64);
347 }
348 }
349 x if x == EatClaimKey::Location as i64 => {
350 claims.location = Some(v.clone());
351 }
352 x if x == EatClaimKey::EatProfile as i64 => {
353 if let Value::Text(s) = v {
354 claims.eat_profile = Some(s.clone());
355 }
356 }
357 x if x == EatClaimKey::Submods as i64 => {
358 claims.submods = Some(v.clone());
359 }
360 x if x == EatClaimKey::BootCount as i64 => {
361 if let Value::Integer(i) = v {
362 claims.boot_count = Some(i128::from(*i) as u64);
363 }
364 }
365 x if x == EatClaimKey::BootSeed as i64 => {
366 if let Value::Bytes(b) = v {
367 claims.boot_seed = Some(b.clone());
368 }
369 }
370 x if x == EatClaimKey::Dloas as i64 => {
371 claims.dloas = Some(v.clone());
372 }
373 x if x == EatClaimKey::SwName as i64 => {
374 if let Value::Text(s) = v {
375 claims.sw_name = Some(s.clone());
376 }
377 }
378 x if x == EatClaimKey::SwVersion as i64 => {
379 claims.sw_version = Some(v.clone());
380 }
381 x if x == EatClaimKey::Manifests as i64 => {
382 claims.manifests = Some(v.clone());
383 }
384 x if x == EatClaimKey::Measurements as i64 => {
385 claims.measurements = Some(v.clone());
386 }
387 x if x == EatClaimKey::MeasRes as i64 => {
388 claims.meas_res = Some(v.clone());
389 }
390 x if x == EatClaimKey::IntUse as i64 => {
391 if let Value::Integer(i) = v {
392 claims.int_use = Some(i128::from(*i) as u64);
393 }
394 }
395 _ => {}
396 }
397 }
398
399 Ok(claims)
400 }
401
402 /// Decode the claims-set from raw CBOR bytes.
403 pub fn from_cbor_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
404 let value: Value = ciborium::de::from_reader(bytes)?;
405 Self::from_cbor_value(&value)
406 }
407
408 /// Alias for [`Self::from_cbor_bytes`].
409 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
410 Self::from_cbor_bytes(bytes)
411 }
412}