1use std::collections::HashMap;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use ed25519_dalek::{Signature, Verifier, VerifyingKey};
5use serde::Deserialize;
6use serde_cbor::Value;
7use thiserror::Error;
8
9use crate::messages::DiscoveryReply;
10
11#[derive(Debug, Error)]
12pub enum AttestationError {
13 #[error("device identity attestation missing")]
14 Missing,
15 #[error("attestation decode failed: {0}")]
16 Decode(String),
17 #[error("attestation missing field: {0}")]
18 MissingField(&'static str),
19 #[error("attestation invalid field: {0}")]
20 InvalidField(&'static str),
21 #[error("attestation unsupported algorithm: {0}")]
22 UnsupportedAlgorithm(String),
23 #[error("attestation signer not trusted: {0}")]
24 UnknownSigner(String),
25 #[error("attestation signature invalid")]
26 InvalidSignature,
27 #[error("attestation expired")]
28 Expired,
29 #[error("attestation identity mismatch: {0}")]
30 IdentityMismatch(&'static str),
31 #[error("device identity pubkey missing from discovery")]
32 MissingDeviceIdentityPubkey,
33}
34
35#[derive(Debug, Clone, Default)]
36pub struct AttesterRegistry {
37 keys: HashMap<String, VerifyingKey>,
38}
39
40impl AttesterRegistry {
41 pub fn new() -> Self {
42 Self {
43 keys: HashMap::new(),
44 }
45 }
46
47 pub fn insert(&mut self, kid: impl Into<String>, key: VerifyingKey) {
48 self.keys.insert(kid.into(), key);
49 }
50
51 pub fn insert_key_bytes(
52 &mut self,
53 kid: impl Into<String>,
54 key_bytes: &[u8],
55 ) -> Result<(), AttestationError> {
56 let key: [u8; 32] = key_bytes
57 .try_into()
58 .map_err(|_| AttestationError::InvalidField("signer_kid_pubkey"))?;
59 let verifying = VerifyingKey::from_bytes(&key)
60 .map_err(|_| AttestationError::InvalidField("signer_kid_pubkey"))?;
61 self.insert(kid, verifying);
62 Ok(())
63 }
64
65 pub fn get(&self, kid: &str) -> Option<&VerifyingKey> {
66 self.keys.get(kid)
67 }
68
69 pub fn is_empty(&self) -> bool {
70 self.keys.is_empty()
71 }
72}
73
74#[derive(Debug)]
75pub struct VerifiedDeviceIdentityAttestation {
76 pub signer_kid: String,
77 pub expires_at: Option<u64>,
78}
79
80#[derive(Debug, Error)]
81pub enum AttesterBundleError {
82 #[error("attesters bundle decode failed: {0}")]
83 Decode(String),
84 #[error("attesters bundle missing field: {0}")]
85 MissingField(&'static str),
86 #[error("attesters bundle invalid field: {0}")]
87 InvalidField(&'static str),
88 #[error("attesters bundle unsupported algorithm: {0}")]
89 UnsupportedAlgorithm(String),
90 #[error("attesters bundle signature invalid")]
91 InvalidSignature,
92 #[error("attesters bundle expired")]
93 Expired,
94 #[error("root key invalid")]
95 InvalidRootKey,
96}
97
98#[derive(Debug, Clone, Deserialize)]
99pub struct AttesterRecord {
100 pub kid: String,
101 #[serde(with = "serde_bytes")]
102 pub pubkey: Vec<u8>,
103 pub alg: String,
104 pub status: String,
105 #[serde(default)]
106 pub revoked_at: Option<u64>,
107 #[serde(default)]
108 pub expires_at: Option<u64>,
109}
110
111#[derive(Debug, Clone)]
112pub struct VerifiedAttesterBundle {
113 pub issued_at: u64,
114 pub expires_at: u64,
115 pub signer_kid: Option<String>,
116 pub attesters: Vec<AttesterRecord>,
117 pub registry: AttesterRegistry,
118}
119
120#[derive(Debug, Deserialize)]
121struct DeviceIdentityPayload {
122 device_id: String,
123 #[serde(rename = "mfg")]
124 manufacturer_id: String,
125 #[serde(rename = "model")]
126 model_id: String,
127 #[serde(rename = "hw_rev")]
128 hardware_rev: String,
129 #[serde(rename = "pub_ed25519", with = "serde_bytes")]
130 pub_ed25519: Vec<u8>,
131 #[serde(default)]
132 expires_at: Option<u64>,
133}
134
135fn value_as_text(value: &Value) -> Option<String> {
136 match value {
137 Value::Text(text) => Some(text.clone()),
138 Value::Bytes(bytes) => Some(hex::encode(bytes)),
139 _ => None,
140 }
141}
142
143fn value_as_u64(value: &Value) -> Option<u64> {
144 match value {
145 Value::Integer(int) if *int >= 0 => Some(*int as u64),
146 Value::Float(float) if *float >= 0.0 => Some(*float as u64),
147 _ => None,
148 }
149}
150
151fn value_as_bytes(value: &Value) -> Option<Vec<u8>> {
152 match value {
153 Value::Bytes(bytes) => Some(bytes.clone()),
154 _ => None,
155 }
156}
157
158fn map_lookup<'a>(
159 map: &'a std::collections::BTreeMap<Value, Value>,
160 key: &str,
161) -> Option<&'a Value> {
162 map.iter().find_map(|(k, v)| match k {
163 Value::Text(text) if text == key => Some(v),
164 _ => None,
165 })
166}
167
168pub fn verify_device_identity_attestation(
169 reply: &DiscoveryReply,
170 registry: &AttesterRegistry,
171 now: SystemTime,
172) -> Result<VerifiedDeviceIdentityAttestation, AttestationError> {
173 if reply.device_identity_attestation.is_empty() {
174 return Err(AttestationError::Missing);
175 }
176 if reply.device_identity_pubkey.is_empty() {
177 return Err(AttestationError::MissingDeviceIdentityPubkey);
178 }
179
180 let envelope: Value = serde_cbor::from_slice(&reply.device_identity_attestation)
181 .map_err(|err| AttestationError::Decode(err.to_string()))?;
182 let map = match envelope {
183 Value::Map(map) => map,
184 _ => return Err(AttestationError::InvalidField("attestation_map")),
185 };
186
187 let payload_bytes = map_lookup(&map, "payload")
188 .and_then(value_as_bytes)
189 .ok_or(AttestationError::MissingField("payload"))?;
190 let sig_bytes = map_lookup(&map, "sig")
191 .and_then(value_as_bytes)
192 .ok_or(AttestationError::MissingField("sig"))?;
193 let alg = map_lookup(&map, "alg")
194 .and_then(value_as_text)
195 .ok_or(AttestationError::MissingField("alg"))?;
196 let signer_kid = map_lookup(&map, "signer_kid")
197 .and_then(value_as_text)
198 .ok_or(AttestationError::MissingField("signer_kid"))?;
199 let expires_at = map_lookup(&map, "expires_at").and_then(value_as_u64);
200
201 let alg_lower = alg.to_ascii_lowercase();
202 if alg_lower != "ed25519" && alg_lower != "eddsa" {
203 return Err(AttestationError::UnsupportedAlgorithm(alg));
204 }
205
206 let attester_key = registry
207 .get(&signer_kid)
208 .ok_or_else(|| AttestationError::UnknownSigner(signer_kid.clone()))?;
209
210 let signature =
211 Signature::from_slice(&sig_bytes).map_err(|_| AttestationError::InvalidSignature)?;
212 attester_key
213 .verify(&payload_bytes, &signature)
214 .map_err(|_| AttestationError::InvalidSignature)?;
215
216 let payload: DeviceIdentityPayload = serde_cbor::from_slice(&payload_bytes)
217 .map_err(|err| AttestationError::Decode(err.to_string()))?;
218
219 if payload.device_id != reply.device_id {
220 return Err(AttestationError::IdentityMismatch("device_id"));
221 }
222 if payload.manufacturer_id != reply.manufacturer_id {
223 return Err(AttestationError::IdentityMismatch("manufacturer_id"));
224 }
225 if payload.model_id != reply.model_id {
226 return Err(AttestationError::IdentityMismatch("model_id"));
227 }
228 if payload.hardware_rev != reply.hardware_rev {
229 return Err(AttestationError::IdentityMismatch("hardware_rev"));
230 }
231 if payload.pub_ed25519 != reply.device_identity_pubkey {
232 return Err(AttestationError::IdentityMismatch("device_identity_pubkey"));
233 }
234
235 let now_secs = now
236 .duration_since(UNIX_EPOCH)
237 .map(|d| d.as_secs())
238 .unwrap_or_default();
239 let expires = expires_at.or(payload.expires_at);
240 if let Some(expires_at) = expires {
241 if expires_at <= now_secs {
242 return Err(AttestationError::Expired);
243 }
244 }
245
246 Ok(VerifiedDeviceIdentityAttestation {
247 signer_kid,
248 expires_at: expires,
249 })
250}
251
252#[derive(Debug, Deserialize)]
253struct AttesterBundleEnvelope {
254 v: u8,
255 #[serde(with = "serde_bytes")]
256 payload: Vec<u8>,
257 #[serde(with = "serde_bytes")]
258 sig: Vec<u8>,
259 alg: String,
260 #[serde(default)]
261 signer_kid: Option<String>,
262}
263
264#[derive(Debug, Deserialize)]
265struct AttesterBundlePayload {
266 v: u8,
267 issued_at: u64,
268 expires_at: u64,
269 attesters: Vec<AttesterRecord>,
270}
271
272pub fn verify_attester_bundle(
273 bundle_bytes: &[u8],
274 root_pubkey: &[u8],
275 now: SystemTime,
276) -> Result<VerifiedAttesterBundle, AttesterBundleError> {
277 let key_bytes: [u8; 32] = root_pubkey
278 .try_into()
279 .map_err(|_| AttesterBundleError::InvalidRootKey)?;
280 let root_key =
281 VerifyingKey::from_bytes(&key_bytes).map_err(|_| AttesterBundleError::InvalidRootKey)?;
282
283 let envelope: AttesterBundleEnvelope = serde_cbor::from_slice(bundle_bytes)
284 .map_err(|err| AttesterBundleError::Decode(err.to_string()))?;
285 if envelope.v == 0 {
286 return Err(AttesterBundleError::InvalidField("v"));
287 }
288
289 let alg_lower = envelope.alg.to_ascii_lowercase();
290 if alg_lower != "ed25519" && alg_lower != "eddsa" {
291 return Err(AttesterBundleError::UnsupportedAlgorithm(envelope.alg));
292 }
293
294 let signature =
295 Signature::from_slice(&envelope.sig).map_err(|_| AttesterBundleError::InvalidSignature)?;
296 root_key
297 .verify(&envelope.payload, &signature)
298 .map_err(|_| AttesterBundleError::InvalidSignature)?;
299
300 let payload: AttesterBundlePayload = serde_cbor::from_slice(&envelope.payload)
301 .map_err(|err| AttesterBundleError::Decode(err.to_string()))?;
302
303 if payload.v == 0 {
304 return Err(AttesterBundleError::InvalidField("payload.v"));
305 }
306
307 let now_secs = now
308 .duration_since(UNIX_EPOCH)
309 .map(|d| d.as_secs())
310 .unwrap_or_default();
311 if payload.expires_at <= now_secs {
312 return Err(AttesterBundleError::Expired);
313 }
314
315 let mut registry = AttesterRegistry::new();
316 let mut active_attesters = Vec::new();
317
318 for attester in payload.attesters.into_iter() {
319 let status_lower = attester.status.to_ascii_lowercase();
320 if status_lower != "active" {
321 continue;
322 }
323 if let Some(revoked_at) = attester.revoked_at {
324 if revoked_at <= now_secs {
325 continue;
326 }
327 }
328 if let Some(expires_at) = attester.expires_at {
329 if expires_at <= now_secs {
330 continue;
331 }
332 }
333 let alg_lower = attester.alg.to_ascii_lowercase();
334 if alg_lower != "ed25519" && alg_lower != "eddsa" {
335 return Err(AttesterBundleError::UnsupportedAlgorithm(attester.alg));
336 }
337 registry
338 .insert_key_bytes(attester.kid.clone(), &attester.pubkey)
339 .map_err(|_| AttesterBundleError::InvalidField("attester.pubkey"))?;
340 active_attesters.push(attester);
341 }
342
343 Ok(VerifiedAttesterBundle {
344 issued_at: payload.issued_at,
345 expires_at: payload.expires_at,
346 signer_kid: envelope.signer_kid,
347 attesters: active_attesters,
348 registry,
349 })
350}