1#[cfg(doc)]
3use crate::stubs::*;
4
5use serde::{Deserialize, Serialize};
6use serde_cbor_2::{
7 ser::to_vec_packed,
8 value::{from_value, to_value},
9 Value,
10};
11use std::fmt::Debug;
12use webauthn_rs_core::proto::COSEKey;
13use webauthn_rs_proto::CredentialProtectionPolicy;
14
15use crate::crypto::{compute_sha256, SHA256Hash};
16
17use super::*;
18
19macro_rules! cred_struct {
21 (
22 $(#[$outer:meta])*
23 $vis:vis struct $name:ident = $cmd:tt
24 ) => {
25 $(#[$outer])*
26 #[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)]
35 #[serde(into = "BTreeMap<u32, Value>")]
36 pub struct $name {
37 sub_command: u8,
39 sub_command_params: Option<BTreeMap<Value, Value>>,
43 pin_uv_protocol: Option<u32>,
45 pin_uv_auth_param: Option<Vec<u8>>,
48 }
49
50 impl CBORCommand for $name {
51 const CMD: u8 = $cmd;
52 type Response = CredentialManagementResponse;
53 }
54
55 impl CredentialManagementRequestTrait for $name {
56 const ENUMERATE_RPS_GET_NEXT: Self = Self {
57 sub_command: 0x03,
58 sub_command_params: None,
59 pin_uv_protocol: None,
60 pin_uv_auth_param: None,
61 };
62
63 const ENUMERATE_CREDENTIALS_GET_NEXT: Self = Self {
64 sub_command: 0x05,
65 sub_command_params: None,
66 pin_uv_protocol: None,
67 pin_uv_auth_param: None,
68 };
69
70 fn new(
71 s: CredSubCommand,
72 pin_uv_protocol: Option<u32>,
73 pin_uv_auth_param: Option<Vec<u8>>,
74 ) -> Self {
75 let sub_command = (&s).into();
76 let sub_command_params = s.into();
77
78 Self {
79 sub_command,
80 sub_command_params,
81 pin_uv_protocol,
82 pin_uv_auth_param,
83 }
84 }
85 }
86
87 impl From<$name> for BTreeMap<u32, Value> {
88 fn from(value: $name) -> Self {
89 let $name {
90 sub_command,
91 sub_command_params,
92 pin_uv_protocol,
93 pin_uv_auth_param,
94 } = value;
95
96 let mut keys = BTreeMap::new();
97 keys.insert(0x01, Value::Integer(sub_command.into()));
98
99 if let Some(v) = sub_command_params {
100 keys.insert(0x02, Value::Map(v));
101 }
102
103 if let Some(v) = pin_uv_protocol {
104 keys.insert(0x03, Value::Integer(v.into()));
105 }
106
107 if let Some(v) = pin_uv_auth_param {
108 keys.insert(0x04, Value::Bytes(v));
109 }
110
111 keys
112 }
113 }
114 };
115}
116
117pub trait CredentialManagementRequestTrait:
119 CBORCommand<Response = CredentialManagementResponse>
120{
121 fn new(
123 s: CredSubCommand,
124 pin_uv_protocol: Option<u32>,
125 pin_uv_auth_param: Option<Vec<u8>>,
126 ) -> Self;
127
128 const ENUMERATE_RPS_GET_NEXT: Self;
133
134 const ENUMERATE_CREDENTIALS_GET_NEXT: Self;
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Default)]
150pub enum CredSubCommand {
151 #[default]
152 Unknown,
153
154 GetCredsMetadata,
158
159 EnumerateRPsBegin,
165
166 EnumerateCredentialsBegin(SHA256Hash),
177
178 DeleteCredential(PublicKeyCredentialDescriptorCM),
180
181 UpdateUserInformation(PublicKeyCredentialDescriptorCM, UserCM),
185}
186
187impl From<&CredSubCommand> for u8 {
188 fn from(c: &CredSubCommand) -> Self {
189 use CredSubCommand::*;
190 match c {
191 Unknown => 0x00,
192 GetCredsMetadata => 0x01,
193 EnumerateRPsBegin => 0x02,
194 EnumerateCredentialsBegin(_) => 0x04,
196 DeleteCredential(_) => 0x06,
198 UpdateUserInformation(_, _) => 0x07,
199 }
200 }
201}
202
203impl From<CredSubCommand> for Option<BTreeMap<Value, Value>> {
204 fn from(c: CredSubCommand) -> Self {
205 use CredSubCommand::*;
206 match c {
207 Unknown | GetCredsMetadata | EnumerateRPsBegin => None,
208 EnumerateCredentialsBegin(rp_id_hash) => Some(BTreeMap::from([(
209 Value::Integer(0x01),
210 Value::Bytes(rp_id_hash.to_vec()),
211 )])),
212 DeleteCredential(credential_id) => Some(BTreeMap::from([(
213 Value::Integer(0x02),
214 to_value(credential_id).ok()?,
215 )])),
216 UpdateUserInformation(credential_id, user) => Some(BTreeMap::from([
217 (Value::Integer(0x02), to_value(credential_id).ok()?),
218 (Value::Integer(0x03), to_value(user).ok()?),
219 ])),
220 }
221 }
222}
223
224impl CredSubCommand {
225 pub fn prf(&self) -> Vec<u8> {
230 let subcommand = self.into();
231 let sub_command_params: Option<BTreeMap<Value, Value>> = self.to_owned().into();
232
233 let mut o = Vec::new();
234 o.push(subcommand);
235 if let Some(p) = sub_command_params
236 .as_ref()
237 .and_then(|p| to_vec_packed(p).ok())
238 {
239 o.extend_from_slice(p.as_slice())
240 }
241
242 o
243 }
244
245 #[inline]
253 pub fn enumerate_credentials_by_rpid(rp_id: &str) -> Self {
254 Self::EnumerateCredentialsBegin(compute_sha256(rp_id.as_bytes()))
255 }
256}
257
258#[derive(Debug, Default, Serialize, Clone, Deserialize, PartialEq, Eq)]
279#[serde(rename_all = "camelCase")]
280pub struct RelyingPartyCM {
281 pub name: Option<String>,
285
286 pub id: Option<String>,
294
295 #[serde(skip)]
299 pub hash: Option<SHA256Hash>,
300}
301
302#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
309#[serde(rename_all = "camelCase")]
310pub struct UserCM {
311 #[serde(with = "serde_bytes")]
313 pub id: Vec<u8>,
314
315 pub name: Option<String>,
320
321 pub display_name: Option<String>,
325}
326
327#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq)]
328#[serde(rename_all = "camelCase")]
329pub struct PublicKeyCredentialDescriptorCM {
330 #[serde(rename = "type")]
332 pub type_: String,
333 #[serde(with = "serde_bytes")]
335 pub id: Vec<u8>,
336}
337
338impl From<Vec<u8>> for PublicKeyCredentialDescriptorCM {
339 fn from(id: Vec<u8>) -> Self {
340 Self {
341 type_: "public-key".to_string(),
342 id,
343 }
344 }
345}
346
347#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
353#[serde(try_from = "BTreeMap<u32, Value>")]
354pub struct CredentialManagementResponse {
355 pub storage_metadata: Option<CredentialStorageMetadata>,
356 pub rp: Option<RelyingPartyCM>,
357 pub total_rps: Option<u32>,
358 pub discoverable_credential: DiscoverableCredential,
359 pub total_credentials: Option<u32>,
360}
361
362impl TryFrom<BTreeMap<u32, Value>> for CredentialManagementResponse {
363 type Error = &'static str;
364 fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
365 trace!(?raw);
366
367 let mut rp: Option<RelyingPartyCM> = if let Some(v) = raw.remove(&0x03) {
369 Some(from_value(v).map_err(|e| {
370 error!("parsing rp: {e:?}");
371 "parsing rp"
372 })?)
373 } else {
374 None
375 };
376
377 if let Some(rp_id_hash) = raw
379 .remove(&0x04)
380 .and_then(|v| value_to_vec_u8(v, "0x04"))
381 .and_then(|v| v.try_into().ok())
382 {
383 if let Some(rp) = &mut rp {
384 rp.hash = Some(rp_id_hash);
386 } else {
387 rp = Some(RelyingPartyCM {
388 hash: Some(rp_id_hash),
389 ..Default::default()
390 });
391 }
392 }
393
394 Ok(Self {
395 storage_metadata: CredentialStorageMetadata::try_from(&mut raw).ok(),
396 rp,
397 total_rps: raw.remove(&0x05).and_then(|v| value_to_u32(&v, "0x05")),
398 total_credentials: raw.remove(&0x09).and_then(|v| value_to_u32(&v, "0x09")),
399 discoverable_credential: DiscoverableCredential::try_from(&mut raw)?,
400 })
401 }
402}
403
404crate::deserialize_cbor!(CredentialManagementResponse);
405
406#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
411pub struct CredentialStorageMetadata {
412 pub existing_resident_credentials_count: u32,
414
415 pub max_possible_remaining_resident_credentials_count: u32,
431}
432
433impl TryFrom<&mut BTreeMap<u32, Value>> for CredentialStorageMetadata {
434 type Error = WebauthnCError;
435 fn try_from(raw: &mut BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
436 Ok(Self {
437 existing_resident_credentials_count: raw
438 .remove(&0x01)
439 .and_then(|v| value_to_u32(&v, "0x01"))
440 .ok_or(WebauthnCError::MissingRequiredField)?,
441 max_possible_remaining_resident_credentials_count: raw
442 .remove(&0x02)
443 .and_then(|v| value_to_u32(&v, "0x02"))
444 .ok_or(WebauthnCError::MissingRequiredField)?,
445 })
446 }
447}
448
449#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
450pub struct DiscoverableCredential {
451 pub user: Option<UserCM>,
452 pub credential_id: Option<PublicKeyCredentialDescriptorCM>,
453 pub public_key: Option<COSEKey>,
454 pub cred_protect: Option<CredentialProtectionPolicy>,
455 pub large_blob_key: Option<Vec<u8>>,
456}
457
458impl TryFrom<&mut BTreeMap<u32, Value>> for DiscoverableCredential {
459 type Error = &'static str;
460 fn try_from(raw: &mut BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
461 Ok(Self {
462 user: if let Some(v) = raw.remove(&0x06) {
463 Some(from_value(v).map_err(|e| {
464 error!("parsing user: {e:?}");
465 "parsing user"
466 })?)
467 } else {
468 None
469 },
470 credential_id: if let Some(v) = raw.remove(&0x07) {
471 Some(from_value(v).map_err(|e| {
472 error!("parsing credentialID: {e:?}");
473 "parsing credentialID"
474 })?)
475 } else {
476 None
477 },
478 public_key: if let Some(v) = raw.remove(&0x08) {
479 Some(COSEKey::try_from(&v).map_err(|e| {
482 error!("parsing publicKey: {e:?}");
483 "parsing publicKey"
484 })?)
485 } else {
486 None
487 },
488 cred_protect: raw
489 .remove(&0x0A)
490 .and_then(|v| value_to_u8(&v, "0x0A"))
491 .and_then(|v| CredentialProtectionPolicy::try_from(v).ok()),
492 large_blob_key: raw.remove(&0x0B).and_then(|v| value_to_vec_u8(v, "0x0B")),
493 })
494 }
495}
496
497cred_struct! {
498 pub struct CredentialManagementRequest = 0x0a
502}
503
504cred_struct! {
505 pub struct PrototypeCredentialManagementRequest = 0x41
509}
510
511#[cfg(test)]
512mod test {
513 use webauthn_rs_core::proto::{COSEEC2Key, COSEKeyType, ECDSACurve};
514 use webauthn_rs_proto::COSEAlgorithm;
515
516 use super::*;
517 const PIN_UV_AUTH_PARAM: [u8; 32] = [
518 0x13, 0xd7, 0xf2, 0xe7, 0xa0, 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c,
519 0xec, 0x5e, 0xc0, 0xc6, 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e,
520 0xfa, 0x5d,
521 ];
522
523 #[test]
524 fn get_cred_metadata() {
525 let _ = tracing_subscriber::fmt::try_init();
526
527 const SUBCOMMAND: CredSubCommand = CredSubCommand::GetCredsMetadata;
528 assert_eq!(vec![0x01], SUBCOMMAND.prf());
529
530 let c =
531 CredentialManagementRequest::new(SUBCOMMAND, Some(2), Some(PIN_UV_AUTH_PARAM.to_vec()));
532
533 assert_eq!(
534 vec![
535 0x0a, 0xa3, 0x01, 0x01, 0x03, 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
536 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
537 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
538 ],
539 c.cbor().expect("encode error")
540 );
541
542 let c = PrototypeCredentialManagementRequest::new(
543 SUBCOMMAND,
544 Some(1),
545 Some(PIN_UV_AUTH_PARAM.to_vec()),
546 );
547
548 assert_eq!(
549 vec![
550 0x41, 0xa3, 0x01, 0x01, 0x03, 0x01, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
551 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
552 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
553 ],
554 c.cbor().expect("encode error")
555 );
556
557 let r = [0xa2, 0x01, 0x03, 0x02, 0x16];
558 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
559 .expect("Failed to decode message");
560
561 assert_eq!(
562 CredentialManagementResponse {
563 storage_metadata: Some(CredentialStorageMetadata {
564 existing_resident_credentials_count: 3,
565 max_possible_remaining_resident_credentials_count: 22,
566 }),
567 ..Default::default()
568 },
569 a
570 )
571 }
572
573 #[test]
574 fn enumerate_rps_begin() {
575 let _ = tracing_subscriber::fmt::try_init();
576
577 const SUBCOMMAND: CredSubCommand = CredSubCommand::EnumerateRPsBegin;
578 assert_eq!(vec![0x02], SUBCOMMAND.prf());
579
580 let c =
581 CredentialManagementRequest::new(SUBCOMMAND, Some(2), Some(PIN_UV_AUTH_PARAM.to_vec()));
582
583 assert_eq!(
584 vec![
585 0x0a, 0xa3, 0x01, 0x02, 0x03, 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
586 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
587 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
588 ],
589 c.cbor().expect("encode error")
590 );
591
592 let c = PrototypeCredentialManagementRequest::new(
593 SUBCOMMAND,
594 Some(1),
595 Some(PIN_UV_AUTH_PARAM.to_vec()),
596 );
597
598 assert_eq!(
599 vec![
600 0x41, 0xa3, 0x01, 0x02, 0x03, 0x01, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
601 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
602 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
603 ],
604 c.cbor().expect("encode error")
605 );
606
607 let r = [
609 0xa3, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x78, 0x18, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74,
610 0x68, 0x6e, 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, 0x79, 0x65, 0x61, 0x72, 0x2e, 0x69,
611 0x64, 0x2e, 0x61, 0x75, 0x04, 0x58, 0x20, 0x6a, 0xb9, 0xbb, 0xf0, 0xdf, 0x9a, 0x16,
612 0xf9, 0x1d, 0xbb, 0x33, 0xbb, 0xb1, 0x32, 0xfa, 0xf9, 0xd1, 0x7c, 0x78, 0x2c, 0x48,
613 0x26, 0xc6, 0xec, 0x70, 0xec, 0xee, 0x58, 0xd9, 0x7e, 0xf5, 0x2a, 0x05, 0x02,
614 ];
615 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
616 .expect("Failed to decode message");
617
618 assert_eq!(
619 CredentialManagementResponse {
620 rp: Some(RelyingPartyCM {
621 name: None,
622 id: Some("webauthn.firstyear.id.au".to_string()),
623 hash: Some([
624 0x6a, 0xb9, 0xbb, 0xf0, 0xdf, 0x9a, 0x16, 0xf9, 0x1d, 0xbb, 0x33, 0xbb,
625 0xb1, 0x32, 0xfa, 0xf9, 0xd1, 0x7c, 0x78, 0x2c, 0x48, 0x26, 0xc6, 0xec,
626 0x70, 0xec, 0xee, 0x58, 0xd9, 0x7e, 0xf5, 0x2a
627 ]),
628 }),
629 total_rps: Some(2),
630 ..Default::default()
631 },
632 a
633 );
634
635 let r = [
637 0xa3, 0x03, 0xa3, 0x62, 0x69, 0x64, 0x78, 0x1e, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74,
638 0x68, 0x6e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x77, 0x65,
639 0x62, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x64, 0x69, 0x63, 0x6f,
640 0x6e, 0x78, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61,
641 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x49, 0x63, 0x6f,
642 0x6e, 0x2e, 0x70, 0x6e, 0x67, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x74, 0x57, 0x65, 0x62,
643 0x41, 0x75, 0x74, 0x68, 0x6e, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x65, 0x72,
644 0x76, 0x65, 0x72, 0x04, 0x58, 0x20, 0xe4, 0x53, 0x29, 0xd0, 0x3a, 0x20, 0x68, 0xd1,
645 0xca, 0xf7, 0xf7, 0xbb, 0x0a, 0xe9, 0x54, 0xe6, 0xb0, 0xe6, 0x25, 0x97, 0x45, 0xf3,
646 0x2f, 0x48, 0x29, 0xf7, 0x50, 0xf0, 0x50, 0x11, 0xf9, 0xc2, 0x05, 0x03,
647 ];
648 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
649 .expect("Failed to decode message");
650
651 assert_eq!(
652 CredentialManagementResponse {
653 rp: Some(RelyingPartyCM {
654 name: Some("WebAuthn Test Server".to_string()),
655 id: Some("webauthntest.azurewebsites.net".to_string()),
656 hash: Some([
657 0xe4, 0x53, 0x29, 0xd0, 0x3a, 0x20, 0x68, 0xd1, 0xca, 0xf7, 0xf7, 0xbb,
658 0x0a, 0xe9, 0x54, 0xe6, 0xb0, 0xe6, 0x25, 0x97, 0x45, 0xf3, 0x2f, 0x48,
659 0x29, 0xf7, 0x50, 0xf0, 0x50, 0x11, 0xf9, 0xc2
660 ]),
661 }),
662 total_rps: Some(3),
663 ..Default::default()
664 },
665 a
666 );
667
668 let r = [];
670 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
671 .expect("Failed to decode message");
672 assert_eq!(
673 CredentialManagementResponse {
674 total_rps: None,
675 ..Default::default()
676 },
677 a
678 );
679
680 let r = [0xa1, 0x05, 0x00];
682 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
683 .expect("Failed to decode message");
684
685 assert_eq!(
686 CredentialManagementResponse {
687 total_rps: Some(0),
688 ..Default::default()
689 },
690 a
691 );
692 }
693
694 #[test]
695 fn enumerate_rps_next() {
696 let _ = tracing_subscriber::fmt::try_init();
697
698 assert_eq!(
699 vec![0x0a, 0xa1, 0x01, 0x03],
700 CredentialManagementRequest::ENUMERATE_RPS_GET_NEXT
701 .cbor()
702 .expect("encode error")
703 );
704
705 assert_eq!(
706 vec![0x41, 0xa1, 0x01, 0x03],
707 PrototypeCredentialManagementRequest::ENUMERATE_RPS_GET_NEXT
708 .cbor()
709 .expect("encode error")
710 );
711
712 let r = [
713 0xa2, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x78, 0x21, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74,
714 0x68, 0x6e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
715 0x79, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x73, 0x2e, 0x69, 0x6f, 0x04,
716 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8,
717 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2,
718 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c,
719 ];
720 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
721 .expect("Failed to decode message");
722
723 assert_eq!(
724 CredentialManagementResponse {
725 rp: Some(RelyingPartyCM {
726 name: None,
727 id: Some("webauthntest.identitystandards.io".to_string()),
728 hash: Some([
729 0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8,
730 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f, 0x32,
731 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c
732 ]),
733 }),
734 ..Default::default()
735 },
736 a
737 );
738 }
739
740 #[test]
741 fn enumerate_credentials_begin() {
742 let _ = tracing_subscriber::fmt::try_init();
743 const SUBCOMMAND: CredSubCommand = CredSubCommand::EnumerateCredentialsBegin([
744 0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8, 0x94, 0xb1,
745 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2, 0xea, 0x8c,
746 0xeb, 0xc4, 0xad, 0x5c,
747 ]);
748
749 assert_eq!(
750 vec![
751 0x04, 0xa1, 0x01, 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb, 0x3a, 0xeb, 0x29, 0xc5,
752 0x5c, 0x94, 0xa8, 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4, 0xc8, 0xae, 0x70, 0x6f,
753 0x32, 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c
754 ],
755 SUBCOMMAND.prf()
756 );
757
758 assert_eq!(
759 CredSubCommand::enumerate_credentials_by_rpid("webauthntest.identitystandards.io"),
760 SUBCOMMAND
761 );
762
763 let c =
764 CredentialManagementRequest::new(SUBCOMMAND, Some(2), Some(PIN_UV_AUTH_PARAM.to_vec()));
765
766 assert_eq!(
767 vec![
768 0x0a, 0xa4, 0x01, 0x04, 0x02, 0xa1, 0x01, 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb,
769 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8, 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4,
770 0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c, 0x03,
771 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0, 0xb9, 0x61, 0x78, 0x39, 0xb3,
772 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6, 0xc0, 0x41, 0x54, 0x72, 0x74,
773 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
774 ],
775 c.cbor().expect("encode error")
776 );
777
778 let c = PrototypeCredentialManagementRequest::new(
779 SUBCOMMAND,
780 Some(1),
781 Some(PIN_UV_AUTH_PARAM.to_vec()),
782 );
783
784 assert_eq!(
785 vec![
786 0x41, 0xa4, 0x01, 0x04, 0x02, 0xa1, 0x01, 0x58, 0x20, 0x0b, 0x99, 0x7c, 0xcc, 0xeb,
787 0x3a, 0xeb, 0x29, 0xc5, 0x5c, 0x94, 0xa8, 0x94, 0xb1, 0x1c, 0xf0, 0x1a, 0x24, 0xb4,
788 0xc8, 0xae, 0x70, 0x6f, 0x32, 0x8c, 0xc2, 0xea, 0x8c, 0xeb, 0xc4, 0xad, 0x5c, 0x03,
789 0x01, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0, 0xb9, 0x61, 0x78, 0x39, 0xb3,
790 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6, 0xc0, 0x41, 0x54, 0x72, 0x74,
791 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d
792 ],
793 c.cbor().expect("encode error")
794 );
795
796 let r = [
797 0xa6, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40, 0x65,
798 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d,
799 0x65, 0x73, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61, 0x6d,
800 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
801 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x20,
802 0x44, 0x6f, 0x65, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24, 0xdb, 0xf7, 0xba,
803 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1, 0x64, 0x74, 0x79,
804 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08,
805 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x6f, 0xd0, 0x2c, 0xd1,
806 0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a, 0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45,
807 0x4d, 0x71, 0x47, 0x7a, 0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf,
808 0x22, 0x58, 0x20, 0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
809 0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9, 0x1d, 0x8d, 0xd0,
810 0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5, 0x09, 0x02, 0x0a, 0x01, 0x0b, 0x58, 0x20,
811 0xa3, 0x81, 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21, 0x2f, 0xf8,
812 0x2a, 0x30, 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34, 0x71, 0x76, 0x5c, 0x85,
813 0x3a, 0xa8, 0x0a, 0xe6,
814 ];
815 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
816 .expect("Failed to decode message");
817
818 assert_eq!(
819 CredentialManagementResponse {
820 discoverable_credential: DiscoverableCredential {
821 user: Some(UserCM {
822 id: b"alice@example.com".to_vec(),
823 name: Some("allison@example.com".to_string()),
824 display_name: Some("Allison Doe".to_string()),
825 }),
826 credential_id: Some(
827 vec![
828 0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10,
829 0x15, 0x89, 0x76, 0xf1
830 ]
831 .into()
832 ),
833 public_key: Some(COSEKey {
834 type_: COSEAlgorithm::ES256,
835 key: COSEKeyType::EC_EC2(COSEEC2Key {
836 curve: ECDSACurve::SECP256R1,
837 x: vec![
838 0x6f, 0xd0, 0x2c, 0xd1, 0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a,
839 0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45, 0x4d, 0x71, 0x47, 0x7a,
840 0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf
841 ]
842 .into(),
843 y: vec![
844 0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
845 0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9,
846 0x1d, 0x8d, 0xd0, 0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5
847 ]
848 .into()
849 })
850 }),
851 cred_protect: Some(CredentialProtectionPolicy::UserVerificationOptional),
852 large_blob_key: Some(vec![
853 0xa3, 0x81, 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21,
854 0x2f, 0xf8, 0x2a, 0x30, 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34,
855 0x71, 0x76, 0x5c, 0x85, 0x3a, 0xa8, 0x0a, 0xe6
856 ]),
857 },
858 total_credentials: Some(2),
859 ..Default::default()
860 },
861 a
862 );
863 }
864
865 #[test]
866 fn enumerate_credentials_next() {
867 let _ = tracing_subscriber::fmt::try_init();
868
869 assert_eq!(
870 vec![0x0a, 0xa1, 0x01, 0x05],
871 CredentialManagementRequest::ENUMERATE_CREDENTIALS_GET_NEXT
872 .cbor()
873 .expect("encode error")
874 );
875
876 assert_eq!(
877 vec![0x41, 0xa1, 0x01, 0x05],
878 PrototypeCredentialManagementRequest::ENUMERATE_CREDENTIALS_GET_NEXT
879 .cbor()
880 .expect("encode error")
881 );
882
883 let r = [
884 0xa5, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40, 0x65,
885 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d,
886 0x65, 0x73, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61, 0x6d,
887 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
888 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x20,
889 0x44, 0x6f, 0x65, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24, 0xdb, 0xf7, 0xba,
890 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1, 0x64, 0x74, 0x79,
891 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08,
892 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x6f, 0xd0, 0x2c, 0xd1,
893 0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a, 0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45,
894 0x4d, 0x71, 0x47, 0x7a, 0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf,
895 0x22, 0x58, 0x20, 0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
896 0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9, 0x1d, 0x8d, 0xd0,
897 0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5, 0x0a, 0x01, 0x0b, 0x58, 0x20, 0xa3, 0x81,
898 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21, 0x2f, 0xf8, 0x2a, 0x30,
899 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34, 0x71, 0x76, 0x5c, 0x85, 0x3a, 0xa8,
900 0x0a, 0xe6,
901 ];
902 let a = <CredentialManagementResponse as CBORResponse>::try_from(r.as_slice())
903 .expect("Failed to decode message");
904
905 assert_eq!(
906 CredentialManagementResponse {
907 discoverable_credential: DiscoverableCredential {
908 user: Some(UserCM {
909 id: b"alice@example.com".to_vec(),
910 name: Some("allison@example.com".to_string()),
911 display_name: Some("Allison Doe".to_string()),
912 }),
913 credential_id: Some(
914 vec![
915 0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10,
916 0x15, 0x89, 0x76, 0xf1
917 ]
918 .into()
919 ),
920 public_key: Some(COSEKey {
921 type_: COSEAlgorithm::ES256,
922 key: COSEKeyType::EC_EC2(COSEEC2Key {
923 curve: ECDSACurve::SECP256R1,
924 x: vec![
925 0x6f, 0xd0, 0x2c, 0xd1, 0x31, 0x25, 0x1d, 0x12, 0xda, 0x03, 0x8a,
926 0x1b, 0xd2, 0xdb, 0xb6, 0x47, 0xe2, 0x45, 0x4d, 0x71, 0x47, 0x7a,
927 0xd3, 0x1d, 0xbd, 0x7c, 0xdb, 0x15, 0xd2, 0x8d, 0xf8, 0xbf
928 ]
929 .into(),
930 y: vec![
931 0x04, 0x42, 0x2c, 0xca, 0xa2, 0x61, 0xc1, 0x97, 0x92, 0x1a, 0xab,
932 0xad, 0xa5, 0x57, 0x8e, 0x91, 0x55, 0xde, 0x56, 0xc4, 0xca, 0xd9,
933 0x1d, 0x8d, 0xd0, 0x7e, 0xe3, 0x78, 0x71, 0xf9, 0xf1, 0xf5
934 ]
935 .into()
936 })
937 }),
938 cred_protect: Some(CredentialProtectionPolicy::UserVerificationOptional),
939 large_blob_key: Some(vec![
940 0xa3, 0x81, 0x0f, 0xaf, 0xc1, 0x2a, 0xe8, 0xa3, 0xf5, 0xdd, 0x47, 0x21,
941 0x2f, 0xf8, 0x2a, 0x30, 0xd8, 0x2a, 0xd9, 0xf8, 0x6e, 0xdf, 0x06, 0x34,
942 0x71, 0x76, 0x5c, 0x85, 0x3a, 0xa8, 0x0a, 0xe6
943 ]),
944 },
945 ..Default::default()
946 },
947 a
948 );
949 }
950
951 #[test]
952 fn update_user_information() {
953 let _ = tracing_subscriber::fmt::try_init();
954
955 let s = CredSubCommand::UpdateUserInformation(
956 vec![
957 0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89,
958 0x76, 0xf1,
959 ]
960 .into(),
961 UserCM {
962 id: b"alice@example.com".to_vec(),
963 name: Some("allison@example.com".to_string()),
964 display_name: Some("Allison Doe".to_string()),
965 },
966 );
967 assert_eq!(
968 vec![
969 0x07, 0xa2, 0x02, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24, 0xdb, 0xf7, 0xba, 0x5d,
970 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1, 0x64, 0x74, 0x79, 0x70,
971 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x03, 0xa3,
972 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6d,
973 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x61,
974 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
975 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61,
976 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x20, 0x44, 0x6f, 0x65
977 ],
978 s.prf()
979 );
980
981 let c = CredentialManagementRequest::new(s, Some(2), Some(PIN_UV_AUTH_PARAM.into()));
983
984 assert_eq!(
985 vec![
986 0x0a, 0xa4, 0x01, 0x07, 0x02, 0xa2, 0x02, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x39, 0x24,
987 0xdb, 0xf7, 0xba, 0x5d, 0xe8, 0x82, 0x9c, 0x69, 0xee, 0x10, 0x15, 0x89, 0x76, 0xf1,
988 0x64, 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b,
989 0x65, 0x79, 0x03, 0xa3, 0x62, 0x69, 0x64, 0x51, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x40,
990 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61,
991 0x6d, 0x65, 0x73, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e, 0x40, 0x65, 0x78, 0x61,
992 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6b, 0x64, 0x69, 0x73, 0x70, 0x6c,
993 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x6b, 0x41, 0x6c, 0x6c, 0x69, 0x73, 0x6f, 0x6e,
994 0x20, 0x44, 0x6f, 0x65, 0x03, 0x02, 0x04, 0x58, 0x20, 0x13, 0xd7, 0xf2, 0xe7, 0xa0,
995 0xb9, 0x61, 0x78, 0x39, 0xb3, 0xfb, 0xbf, 0xc2, 0xf9, 0x0c, 0xec, 0x5e, 0xc0, 0xc6,
996 0xc0, 0x41, 0x54, 0x72, 0x74, 0x22, 0x56, 0xac, 0x6e, 0xd7, 0x9e, 0xfa, 0x5d,
997 ],
998 c.cbor().expect("encode error")
999 );
1000 }
1001}