1use 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::{get_assertion, make_credential, Ctap2Error};
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(
212 self.attested_credential_data
213 .clone()
214 .map(AttestedCredentialData::into_iter)
215 .into_iter()
216 .flatten(),
217 )
218 .chain(
219 self.extensions
220 .as_ref()
221 .map(|val| {
222 let mut bytes = Vec::new();
223 ciborium::ser::into_writer(val, &mut bytes).unwrap();
224 bytes
225 })
226 .into_iter()
227 .flatten(),
228 )
229 .collect()
230 }
231}
232
233#[derive(Debug, Clone, PartialEq)]
238pub struct AttestedCredentialData {
239 pub aaguid: Aaguid,
241
242 credential_id: Vec<u8>,
245
246 pub key: CoseKey,
257}
258
259impl AttestedCredentialData {
260 pub fn new(
265 aaguid: Aaguid,
266 credential_id: Vec<u8>,
267 key: CoseKey,
268 ) -> Result<Self, TryFromIntError> {
269 u16::try_from(credential_id.len())?;
271
272 Ok(Self {
273 aaguid,
274 credential_id,
275 key,
276 })
277 }
278
279 pub fn credential_id(&self) -> &[u8] {
281 &self.credential_id
282 }
283}
284
285impl AttestedCredentialData {
286 fn into_iter(self) -> impl Iterator<Item = u8> {
288 let cose_key = self.key.to_vec().unwrap();
291 self.aaguid
292 .0
293 .into_iter()
294 .chain(
296 u16::try_from(self.credential_id.len())
297 .unwrap()
298 .to_be_bytes(),
299 )
300 .chain(self.credential_id)
301 .chain(cose_key)
302 }
303
304 fn from_reader<R: Read>(reader: &mut R) -> coset::Result<Self> {
305 let mut aaguid = [0; 16];
306 reader.read_exact(&mut aaguid).map_err(io_error)?;
307 let aaguid = Aaguid(aaguid);
308
309 let mut cred_len = [0; 2];
310 reader.read_exact(&mut cred_len).map_err(io_error)?;
311 let cred_len: usize = u16::from_be_bytes(cred_len).into();
312
313 let mut credential_id = vec![0; cred_len];
314 reader.read_exact(&mut credential_id).map_err(io_error)?;
315
316 let cose_val = ciborium::de::from_reader(reader).map_err(io_error)?;
317 let key = CoseKey::from_cbor_value(cose_val)?;
318
319 Ok(Self {
320 aaguid,
321 credential_id,
322 key,
323 })
324 }
325}
326
327#[cfg(test)]
328mod test {
329 use ciborium::cbor;
330 use coset::CoseKeyBuilder;
331
332 use super::*;
333 use crate::utils::rand::random_vec;
334
335 #[test]
336 fn deserialize_authenticator_data_with_at_and_ed() {
337 let data = [
339 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20,
340 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b,
341 0x60, 0x84, 0x1e, 0xf0, 0xc5, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
342 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c,
343 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c, 0xbf, 0x5e, 0x4c,
344 0x14, 0x04, 0x4f, 0xf8, 0x87, 0x04, 0x11, 0x5e, 0x6c, 0x58, 0x94, 0xb8, 0x69, 0xbb,
345 0x45, 0x3c, 0x3f, 0xe2, 0x1e, 0xb1, 0x22, 0x44, 0xc6, 0xe7, 0xe9, 0x6a, 0xbe, 0xd3,
346 0x0f, 0x18, 0x1b, 0x9f, 0x86, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
347 0x20, 0x0c, 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c, 0xbf,
348 0xad, 0xd9, 0xa6, 0x97, 0xbb, 0x48, 0xd9, 0xd7, 0xff, 0x91, 0x0f, 0x0a, 0x6a, 0xc1,
349 0x0b, 0x91, 0x2b, 0xe9, 0x58, 0x22, 0x58, 0x20, 0x46, 0x78, 0x6f, 0x2a, 0x95, 0x76,
350 0x69, 0x8c, 0x9f, 0x3a, 0xe2, 0x52, 0x3b, 0x4e, 0xb9, 0x4b, 0x8e, 0x07, 0x4c, 0x35,
351 0xab, 0xc4, 0xdf, 0x68, 0x8f, 0xcd, 0x85, 0xd2, 0x9a, 0x01, 0xab, 0xba, 0xa1, 0x6b,
352 0x63, 0x72, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x02,
353 ];
354 let auth_data =
355 AuthenticatorData::from_slice(&data).expect("could not parse the authenticator data");
356
357 let expected = AuthenticatorData {
358 rp_id_hash: [
359 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20,
360 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b,
361 0x60, 0x84, 0x1e, 0xf0,
362 ],
363 flags: Flags::UP | Flags::UV | Flags::AT | Flags::ED,
364 counter: Some(1),
365 attested_credential_data: Some(AttestedCredentialData {
366 aaguid: Aaguid([0; 16]),
368 credential_id: vec![
369 0x0c, 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c, 0xbf,
370 0x5e, 0x4c, 0x14, 0x04, 0x4f, 0xf8, 0x87, 0x04, 0x11, 0x5e, 0x6c, 0x58, 0x94,
371 0xb8, 0x69, 0xbb, 0x45, 0x3c, 0x3f, 0xe2, 0x1e, 0xb1, 0x22, 0x44, 0xc6, 0xe7,
372 0xe9, 0x6a, 0xbe, 0xd3, 0x0f, 0x18, 0x1b, 0x9f, 0x86,
373 ],
374 key: CoseKeyBuilder::new_ec2_pub_key(
375 coset::iana::EllipticCurve::P_256,
376 vec![
377 0x0c, 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c,
378 0xbf, 0xad, 0xd9, 0xa6, 0x97, 0xbb, 0x48, 0xd9, 0xd7, 0xff, 0x91, 0x0f,
379 0x0a, 0x6a, 0xc1, 0x0b, 0x91, 0x2b, 0xe9, 0x58,
380 ],
381 vec![
382 0x46, 0x78, 0x6f, 0x2a, 0x95, 0x76, 0x69, 0x8c, 0x9f, 0x3a, 0xe2, 0x52,
383 0x3b, 0x4e, 0xb9, 0x4b, 0x8e, 0x07, 0x4c, 0x35, 0xab, 0xc4, 0xdf, 0x68,
384 0x8f, 0xcd, 0x85, 0xd2, 0x9a, 0x01, 0xab, 0xba,
385 ],
386 )
387 .algorithm(coset::iana::Algorithm::ES256)
388 .build(),
389 }),
390 extensions: Some(
391 cbor!({
392 "credProtect" => 2
393 })
394 .unwrap(),
395 ),
396 };
397 assert_eq!(expected, auth_data);
398 }
399
400 #[test]
401 fn deserialize_authenticator_data_with_only_at() {
402 let data = [
405 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20,
406 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b,
407 0x60, 0x84, 0x1e, 0xf0, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
408 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c,
409 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c, 0xbf, 0x5e, 0x4c,
410 0x14, 0x04, 0x4f, 0xf8, 0x87, 0x04, 0x11, 0x5e, 0x6c, 0x58, 0x94, 0xb8, 0x69, 0xbb,
411 0x45, 0x3c, 0x3f, 0xe2, 0x1e, 0xb1, 0x22, 0x44, 0xc6, 0xe7, 0xe9, 0x6a, 0xbe, 0xd3,
412 0x0f, 0x18, 0x1b, 0x9f, 0x86, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
413 0x20, 0x0c, 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c, 0xbf,
414 0xad, 0xd9, 0xa6, 0x97, 0xbb, 0x48, 0xd9, 0xd7, 0xff, 0x91, 0x0f, 0x0a, 0x6a, 0xc1,
415 0x0b, 0x91, 0x2b, 0xe9, 0x58, 0x22, 0x58, 0x20, 0x46, 0x78, 0x6f, 0x2a, 0x95, 0x76,
416 0x69, 0x8c, 0x9f, 0x3a, 0xe2, 0x52, 0x3b, 0x4e, 0xb9, 0x4b, 0x8e, 0x07, 0x4c, 0x35,
417 0xab, 0xc4, 0xdf, 0x68, 0x8f, 0xcd, 0x85, 0xd2, 0x9a, 0x01, 0xab, 0xba,
418 ];
419 let auth_data =
420 AuthenticatorData::from_slice(&data).expect("could not parse the authenticator data");
421
422 let expected = AuthenticatorData {
423 rp_id_hash: [
424 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20,
425 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b,
426 0x60, 0x84, 0x1e, 0xf0,
427 ],
428 flags: Flags::UP | Flags::UV | Flags::AT,
429 counter: Some(1),
430 attested_credential_data: Some(AttestedCredentialData {
431 aaguid: Aaguid([0; 16]),
433 credential_id: vec![
434 0x0c, 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c, 0xbf,
435 0x5e, 0x4c, 0x14, 0x04, 0x4f, 0xf8, 0x87, 0x04, 0x11, 0x5e, 0x6c, 0x58, 0x94,
436 0xb8, 0x69, 0xbb, 0x45, 0x3c, 0x3f, 0xe2, 0x1e, 0xb1, 0x22, 0x44, 0xc6, 0xe7,
437 0xe9, 0x6a, 0xbe, 0xd3, 0x0f, 0x18, 0x1b, 0x9f, 0x86,
438 ],
439 key: CoseKeyBuilder::new_ec2_pub_key(
440 coset::iana::EllipticCurve::P_256,
441 vec![
442 0x0c, 0x98, 0x51, 0xdc, 0x8b, 0xd1, 0xef, 0x2d, 0x08, 0x4b, 0x20, 0x1c,
443 0xbf, 0xad, 0xd9, 0xa6, 0x97, 0xbb, 0x48, 0xd9, 0xd7, 0xff, 0x91, 0x0f,
444 0x0a, 0x6a, 0xc1, 0x0b, 0x91, 0x2b, 0xe9, 0x58,
445 ],
446 vec![
447 0x46, 0x78, 0x6f, 0x2a, 0x95, 0x76, 0x69, 0x8c, 0x9f, 0x3a, 0xe2, 0x52,
448 0x3b, 0x4e, 0xb9, 0x4b, 0x8e, 0x07, 0x4c, 0x35, 0xab, 0xc4, 0xdf, 0x68,
449 0x8f, 0xcd, 0x85, 0xd2, 0x9a, 0x01, 0xab, 0xba,
450 ],
451 )
452 .algorithm(coset::iana::Algorithm::ES256)
453 .build(),
454 }),
455 extensions: None,
456 };
457 assert_eq!(expected, auth_data);
458 }
459
460 #[test]
461 fn deserialize_authenticator_data_with_only_ed() {
462 let data = [
465 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20,
466 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b,
467 0x60, 0x84, 0x1e, 0xf0, 0x85, 0x00, 0x00, 0x00, 0x01, 0xa1, 0x6b, 0x63, 0x72, 0x65,
468 0x64, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x02,
469 ];
470 let auth_data =
471 AuthenticatorData::from_slice(&data).expect("could not parse the authenticator data");
472
473 let expected = AuthenticatorData {
474 rp_id_hash: [
475 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20,
476 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b,
477 0x60, 0x84, 0x1e, 0xf0,
478 ],
479 flags: Flags::UP | Flags::UV | Flags::ED,
480 counter: Some(1),
481 attested_credential_data: None,
482 extensions: Some(
483 cbor!({
484 "credProtect" => 2
485 })
486 .unwrap(),
487 ),
488 };
489 assert_eq!(expected, auth_data);
490 }
491
492 #[test]
493 fn round_trip_deserialization() {
494 let expected = AuthenticatorData::new("future.1password.com", Some(0))
495 .set_attested_credential_data(AttestedCredentialData {
496 aaguid: Aaguid::new_empty(),
497 credential_id: random_vec(16),
498 key: CoseKeyBuilder::new_ec2_pub_key(
499 coset::iana::EllipticCurve::P_256,
500 random_vec(32),
502 random_vec(32),
503 )
504 .algorithm(coset::iana::Algorithm::ES256)
505 .build(),
506 });
507
508 let auth_data_bytes = expected.to_vec();
509
510 let auth_data =
511 AuthenticatorData::from_slice(&auth_data_bytes).expect("could not deserialize");
512
513 assert_eq!(expected, auth_data);
514 }
515
516 #[test]
517 fn add_empty_extensions_does_not_add_flag() {
518 let make_auth_data = AuthenticatorData::new("1password.com", None)
520 .set_make_credential_extensions(None)
521 .expect("falsely tried to serialize");
522 assert!(!make_auth_data.flags.contains(Flags::ED));
523
524 let make_auth_data = AuthenticatorData::new("1password.com", None)
526 .set_make_credential_extensions(Some(make_credential::SignedExtensionOutputs {
527 hmac_secret: None,
528 hmac_secret_mc: None,
529 }))
530 .expect("falsely tried to serialize");
531 assert!(!make_auth_data.flags.contains(Flags::ED));
532
533 let make_auth_data = AuthenticatorData::new("1password.com", None)
535 .set_assertion_extensions(None)
536 .expect("falsely tried to serialize");
537 assert!(!make_auth_data.flags.contains(Flags::ED));
538
539 let make_auth_data = AuthenticatorData::new("1password.com", None)
541 .set_assertion_extensions(Some(get_assertion::SignedExtensionOutputs {
542 hmac_secret: None,
543 }))
544 .expect("falsely tried to serialize");
545 assert!(!make_auth_data.flags.contains(Flags::ED));
546 }
547}