passkey_types/ctap2/
attestation_fmt.rs1use std::{
2 io::{Cursor, Read},
3 num::TryFromIntError,
4};
5
6use ciborium::value::Value;
7use coset::{AsCborValue, CborSerializable, CoseKey};
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 crypto::sha256,
12 ctap2::{Aaguid, Flags},
13};
14
15use super::{Ctap2Error, get_assertion, make_credential};
16
17#[derive(Debug, PartialEq)]
28pub struct AuthenticatorData {
29 rp_id_hash: [u8; 32],
31
32 pub flags: Flags,
34
35 pub counter: Option<u32>,
37
38 pub attested_credential_data: Option<AttestedCredentialData>,
42
43 pub extensions: Option<Value>,
58}
59
60impl AuthenticatorData {
61 pub fn new(rp_id: &str, counter: Option<u32>) -> Self {
65 Self {
66 rp_id_hash: sha256(rp_id.as_bytes()),
67 flags: Flags::default(),
68 counter,
69 attested_credential_data: None,
70 extensions: None,
71 }
72 }
73
74 pub fn set_attested_credential_data(mut self, acd: AttestedCredentialData) -> Self {
78 self.attested_credential_data = Some(acd);
79 self.set_flags(Flags::AT)
80 }
81
82 pub fn set_flags(mut self, flags: Flags) -> Self {
84 self.flags |= flags;
85 self
86 }
87
88 pub fn rp_id_hash(&self) -> &[u8] {
90 &self.rp_id_hash
91 }
92
93 pub fn set_make_credential_extensions(
95 mut self,
96 extensions: Option<make_credential::SignedExtensionOutputs>,
97 ) -> Result<Self, Ctap2Error> {
98 let Some(ext) = extensions.and_then(|e| e.zip_contents()) else {
99 return Ok(self);
100 };
101
102 self.extensions =
103 Some(Value::serialized(&ext).map_err(|_| Ctap2Error::CborUnexpectedType)?);
104
105 Ok(self.set_flags(Flags::ED))
106 }
107
108 pub fn set_assertion_extensions(
110 mut self,
111 extensions: Option<get_assertion::SignedExtensionOutputs>,
112 ) -> Result<Self, Ctap2Error> {
113 let Some(ext) = extensions.and_then(|e| e.zip_contents()) else {
114 return Ok(self);
115 };
116
117 self.extensions =
118 Some(Value::serialized(&ext).map_err(|_| Ctap2Error::CborUnexpectedType)?);
119
120 Ok(self.set_flags(Flags::ED))
121 }
122}
123
124impl Serialize for AuthenticatorData {
125 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126 where
127 S: serde::Serializer,
128 {
129 let bytes = self.to_vec();
130 serializer.serialize_bytes(&bytes)
131 }
132}
133
134impl<'de> Deserialize<'de> for AuthenticatorData {
135 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
136 where
137 D: serde::Deserializer<'de>,
138 {
139 struct Visitor;
140 impl serde::de::Visitor<'_> for Visitor {
141 type Value = AuthenticatorData;
142
143 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
144 formatter.write_str("Authenticator Data")
145 }
146 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
147 where
148 E: serde::de::Error,
149 {
150 AuthenticatorData::from_slice(v).map_err(|e| E::custom(e.to_string()))
151 }
152 }
153 deserializer.deserialize_bytes(Visitor)
154 }
155}
156
157fn io_error<E>(_: E) -> coset::CoseError {
159 coset::CoseError::DecodeFailed(ciborium::de::Error::Io(coset::EndOfFile))
160}
161
162impl AuthenticatorData {
163 pub fn from_slice(v: &[u8]) -> coset::Result<Self> {
165 if v.len() < 37 {
167 return Err(io_error(()));
168 }
169
170 let (rp_id_hash, v) = v.split_at(32);
173 let (flag_byte, v) = v.split_at(1);
174 let (counter, v) = v.split_at(4);
175
176 let flags =
177 Flags::from_bits(flag_byte[0]).ok_or(coset::CoseError::OutOfRangeIntegerValue)?;
178 let mut managed_reader = Cursor::new(v);
179 let attested_credential_data = flags
180 .contains(Flags::AT)
181 .then(|| AttestedCredentialData::from_reader(&mut managed_reader))
182 .transpose()?;
183 let extensions = flags
184 .contains(Flags::ED)
185 .then(|| ciborium::de::from_reader(&mut managed_reader).map_err(io_error))
186 .transpose()?;
187
188 Ok(AuthenticatorData {
191 rp_id_hash: rp_id_hash.try_into().unwrap(),
192 flags,
193 counter: Some(u32::from_be_bytes(counter.try_into().unwrap())),
194 attested_credential_data,
195 extensions,
196 })
197 }
198
199 pub fn to_vec(&self) -> Vec<u8> {
201 let flags = if self.attested_credential_data.is_some() {
202 self.flags | Flags::AT
203 } else {
204 self.flags
205 };
206
207 self.rp_id_hash
208 .into_iter()
209 .chain(std::iter::once(flags.into()))
210 .chain(self.counter.unwrap_or_default().to_be_bytes())
211 .chain(self.attested_credential_data.clone().into_iter().flatten())
212 .chain(
213 self.extensions
214 .as_ref()
215 .map(|val| {
216 let mut bytes = Vec::new();
217 ciborium::ser::into_writer(val, &mut bytes).unwrap();
218 bytes
219 })
220 .into_iter()
221 .flatten(),
222 )
223 .collect()
224 }
225}
226
227#[derive(Debug, Clone, PartialEq)]
232pub struct AttestedCredentialData {
233 pub aaguid: Aaguid,
235
236 credential_id: Vec<u8>,
239
240 pub key: CoseKey,
251}
252
253impl AttestedCredentialData {
254 pub fn new(
259 aaguid: Aaguid,
260 credential_id: Vec<u8>,
261 key: CoseKey,
262 ) -> Result<Self, TryFromIntError> {
263 u16::try_from(credential_id.len())?;
265
266 Ok(Self {
267 aaguid,
268 credential_id,
269 key,
270 })
271 }
272
273 pub fn credential_id(&self) -> &[u8] {
275 &self.credential_id
276 }
277}
278
279impl AttestedCredentialData {
280 fn from_reader<R: Read>(reader: &mut R) -> coset::Result<Self> {
281 let mut aaguid = [0; 16];
282 reader.read_exact(&mut aaguid).map_err(io_error)?;
283 let aaguid = Aaguid(aaguid);
284
285 let mut cred_len = [0; 2];
286 reader.read_exact(&mut cred_len).map_err(io_error)?;
287 let cred_len: usize = u16::from_be_bytes(cred_len).into();
288
289 let mut credential_id = vec![0; cred_len];
290 reader.read_exact(&mut credential_id).map_err(io_error)?;
291
292 let cose_val = ciborium::de::from_reader(reader).map_err(io_error)?;
293 let key = CoseKey::from_cbor_value(cose_val)?;
294
295 Ok(Self {
296 aaguid,
297 credential_id,
298 key,
299 })
300 }
301}
302
303pub struct AttestedCredentialDataIterator {
305 aaguid: [u8; 16],
307 credential_id_len: [u8; 2],
308 credential_id: Vec<u8>,
309 cose_key: Vec<u8>,
312 state: AttestedCredentialDataIteratorState,
313}
314
315enum AttestedCredentialDataIteratorState {
316 Aaguid(u8),
317 CredIdLen(u8),
318 CredId(u16),
319 CoseKey(usize),
320 Done,
321}
322
323impl AttestedCredentialDataIterator {
324 fn new(data: AttestedCredentialData) -> Self {
325 let aaguid = data.aaguid.0;
327 let cred_id_len: [u8; 2] = u16::try_from(data.credential_id.len())
328 .expect("Credential ID length is guaranteed to fit within 16 bytes by AttestedCredentialData constructors")
329 .to_be_bytes();
330 let cose_key = data
332 .key
333 .clone()
334 .to_vec()
335 .expect("Properly formatted COSE key");
336 AttestedCredentialDataIterator {
337 aaguid,
338 credential_id_len: cred_id_len,
339 credential_id: data.credential_id,
340 cose_key,
341 state: AttestedCredentialDataIteratorState::Aaguid(0),
342 }
343 }
344}
345
346impl Iterator for AttestedCredentialDataIterator {
347 type Item = u8;
348
349 fn next(&mut self) -> Option<Self::Item> {
350 match self.state {
351 AttestedCredentialDataIteratorState::Aaguid(x) => {
352 debug_assert!(x < 16);
353 if x == 15 {
354 self.state = AttestedCredentialDataIteratorState::CredIdLen(0);
355 } else {
356 self.state = AttestedCredentialDataIteratorState::Aaguid(x + 1)
357 }
358 Some(self.aaguid[usize::from(x)])
359 }
360 AttestedCredentialDataIteratorState::CredIdLen(x) => {
361 debug_assert!(x < 2);
362 if x == 1 {
363 self.state = AttestedCredentialDataIteratorState::CredId(0);
364 } else {
365 self.state = AttestedCredentialDataIteratorState::CredIdLen(x + 1);
366 }
367 Some(self.credential_id_len[usize::from(x)])
368 }
369 AttestedCredentialDataIteratorState::CredId(x) => {
370 let cred_id_len: u16 = u16::try_from(self.credential_id.len())
372 .expect("credential ID length to be less than 2^16");
373 debug_assert!(x < cred_id_len);
374 if x == cred_id_len - 1 {
375 self.state = AttestedCredentialDataIteratorState::CoseKey(0);
376 } else {
377 self.state = AttestedCredentialDataIteratorState::CredId(x + 1);
378 }
379 Some(self.credential_id[usize::from(x)])
380 }
381 AttestedCredentialDataIteratorState::CoseKey(x) => {
382 if x == self.cose_key.len() - 1 {
383 self.state = AttestedCredentialDataIteratorState::Done;
384 } else {
385 self.state = AttestedCredentialDataIteratorState::CoseKey(x + 1);
386 }
387 Some(self.cose_key[x])
388 }
389 AttestedCredentialDataIteratorState::Done => None,
390 }
391 }
392}
393
394impl IntoIterator for AttestedCredentialData {
395 type Item = u8;
396 type IntoIter = AttestedCredentialDataIterator;
397
398 fn into_iter(self) -> Self::IntoIter {
399 AttestedCredentialDataIterator::new(self)
400 }
401}
402
403#[cfg(test)]
404mod test;