webauthn_rs/interface.rs
1//! Types that are expected to be serialised in applications using [crate::Webauthn]
2
3use serde::{Deserialize, Serialize};
4
5use webauthn_rs_core::error::WebauthnError;
6use webauthn_rs_core::proto::{
7 AttestationCa, AttestationCaList, AuthenticationResult, AuthenticationState, COSEAlgorithm,
8 COSEKey, Credential, CredentialID, ParsedAttestation, RegistrationState,
9};
10
11/// An in progress registration session for a [Passkey].
12///
13/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
14///
15/// Failure to do so *may* open you to replay attacks which can significantly weaken the
16/// security of this system.
17///
18/// In some cases you *may* wish to serialise this value. For details on how to achieve this
19/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
20#[derive(Debug, Clone)]
21#[cfg_attr(
22 feature = "danger-allow-state-serialisation",
23 derive(Serialize, Deserialize)
24)]
25pub struct PasskeyRegistration {
26 pub(crate) rs: RegistrationState,
27}
28
29/// An in progress authentication session for a [Passkey].
30///
31/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
32///
33/// Failure to do so *may* open you to replay attacks which can significantly weaken the
34/// security of this system.
35///
36/// In some cases you *may* wish to serialise this value. For details on how to achieve this
37/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
38#[derive(Debug, Clone)]
39#[cfg_attr(
40 feature = "danger-allow-state-serialisation",
41 derive(Serialize, Deserialize)
42)]
43pub struct PasskeyAuthentication {
44 pub(crate) ast: AuthenticationState,
45}
46
47/// A Passkey for a user. A passkey is a term that covers all possible authenticators that may exist.
48/// These could be roaming credentials such as Apple's Account back passkeys, they could be a users
49/// Yubikey, a Windows Hello TPM, or even a password manager softtoken.
50///
51/// Passkeys *may* opportunistically have some properties such as discoverability (residence). This
52/// is not a guarantee since enforcing residence on devices like Yubikeys that have limited storage
53/// and no administration of resident keys may break the device.
54///
55/// These can be safely serialised and deserialised from a database for persistence.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct Passkey {
58 pub(crate) cred: Credential,
59}
60
61impl Passkey {
62 /// Retrieve a reference to this Pass Key's credential ID.
63 pub fn cred_id(&self) -> &CredentialID {
64 &self.cred.cred_id
65 }
66
67 /// Retrieve the type of cryptographic algorithm used by this key
68 pub fn cred_algorithm(&self) -> &COSEAlgorithm {
69 &self.cred.cred.type_
70 }
71
72 /// Retrieve a reference to this Passkey's credential public key.
73 pub fn get_public_key(&self) -> &COSEKey {
74 &self.cred.cred
75 }
76
77 /// Post authentication, update this credential's properties.
78 ///
79 /// To determine if this is required, you can inspect the result of
80 /// `authentication_result.needs_update()`. Counterintuitively, most passkeys
81 /// will never need their properties updated! This is because many passkeys lack an
82 /// internal device activation counter (due to their synchronisation), and the
83 /// backup-state flags are rarely if ever changed.
84 ///
85 /// If the credential_id does not match, None is returned.
86 /// If the cred id matches and the credential is updated, Some(true) is returned.
87 /// If the cred id matches, but the credential is not changed, Some(false) is returned.
88 pub fn update_credential(&mut self, res: &AuthenticationResult) -> Option<bool> {
89 if res.cred_id() == self.cred_id() {
90 let mut changed = false;
91 if res.counter() > self.cred.counter {
92 self.cred.counter = res.counter();
93 changed = true;
94 }
95
96 if res.backup_state() != self.cred.backup_state {
97 self.cred.backup_state = res.backup_state();
98 changed = true;
99 }
100
101 if res.backup_eligible() && !self.cred.backup_eligible {
102 self.cred.backup_eligible = true;
103 changed = true;
104 }
105
106 Some(changed)
107 } else {
108 None
109 }
110 }
111}
112
113#[cfg(feature = "danger-credential-internals")]
114impl From<Passkey> for Credential {
115 fn from(pk: Passkey) -> Self {
116 pk.cred
117 }
118}
119
120#[cfg(feature = "danger-credential-internals")]
121impl From<Credential> for Passkey {
122 /// Convert a generic webauthn credential into a Passkey
123 fn from(cred: Credential) -> Self {
124 Passkey { cred }
125 }
126}
127
128impl PartialEq for Passkey {
129 fn eq(&self, other: &Self) -> bool {
130 self.cred.cred_id == other.cred.cred_id
131 }
132}
133
134// AttestedPasskey
135
136/// An in progress registration session for a [AttestedPasskey].
137///
138/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
139///
140/// Failure to do so *may* open you to replay attacks which can significantly weaken the
141/// security of this system.
142///
143/// In some cases you *may* wish to serialise this value. For details on how to achieve this
144/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
145#[derive(Debug, Clone)]
146#[cfg_attr(
147 feature = "danger-allow-state-serialisation",
148 derive(Serialize, Deserialize)
149)]
150#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
151pub struct AttestedPasskeyRegistration {
152 pub(crate) rs: RegistrationState,
153 pub(crate) ca_list: AttestationCaList,
154}
155
156/// An in progress authentication session for a [AttestedPasskey].
157///
158/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
159///
160/// Failure to do so *may* open you to replay attacks which can significantly weaken the
161/// security of this system.
162///
163/// In some cases you *may* wish to serialise this value. For details on how to achieve this
164/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
165#[derive(Debug, Clone)]
166#[cfg_attr(
167 feature = "danger-allow-state-serialisation",
168 derive(Serialize, Deserialize)
169)]
170#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
171pub struct AttestedPasskeyAuthentication {
172 pub(crate) ast: AuthenticationState,
173}
174
175/// An attested passkey for a user. This is a specialisation of [Passkey] as you can
176/// limit the make and models of authenticators that a user may register. Additionally
177/// these keys will always enforce user verification.
178///
179/// These can be safely serialised and deserialised from a database for use.
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
182pub struct AttestedPasskey {
183 pub(crate) cred: Credential,
184}
185
186#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
187impl AttestedPasskey {
188 /// Retrieve a reference to this AttestedPasskey Key's credential ID.
189 pub fn cred_id(&self) -> &CredentialID {
190 &self.cred.cred_id
191 }
192
193 /// Retrieve the type of cryptographic algorithm used by this key
194 pub fn cred_algorithm(&self) -> &COSEAlgorithm {
195 &self.cred.cred.type_
196 }
197
198 /// Retrieve a reference to the attestation used during this [`Credential`]'s
199 /// registration. This can tell you information about the manufacturer and
200 /// what type of credential it is.
201 pub fn attestation(&self) -> &ParsedAttestation {
202 &self.cred.attestation
203 }
204
205 /// Post authentication, update this credential's properties.
206 ///
207 /// To determine if this is required, you can inspect the result of
208 /// `authentication_result.needs_update()`. Generally this will always
209 /// be true as this class of key will maintain an activation counter which
210 /// allows (limited) protection against device cloning.
211 ///
212 /// If the credential_id does not match, None is returned. If the cred id matches
213 /// and the credential is updated, Some(true) is returned. If the cred id
214 /// matches, but the credential is not changed, Some(false) is returned.
215 pub fn update_credential(&mut self, res: &AuthenticationResult) -> Option<bool> {
216 if res.cred_id() == self.cred_id() {
217 let mut changed = false;
218 if res.counter() > self.cred.counter {
219 self.cred.counter = res.counter();
220 changed = true;
221 }
222
223 if res.backup_state() != self.cred.backup_state {
224 self.cred.backup_state = res.backup_state();
225 changed = true;
226 }
227
228 Some(changed)
229 } else {
230 None
231 }
232 }
233
234 /// Re-verify this Credential's attestation chain. This re-applies the same process
235 /// for certificate authority verification that occured at registration. This can
236 /// be useful if you want to re-assert your credentials match an updated or changed
237 /// ca_list from the time that registration occured. This can also be useful to
238 /// re-determine certain properties of your device that may exist.
239 pub fn verify_attestation<'a>(
240 &'_ self,
241 ca_list: &'a AttestationCaList,
242 ) -> Result<&'a AttestationCa, WebauthnError> {
243 self.cred
244 .verify_attestation(ca_list)
245 .and_then(|maybe_att_ca| {
246 if let Some(att_ca) = maybe_att_ca {
247 Ok(att_ca)
248 } else {
249 Err(WebauthnError::AttestationNotVerifiable)
250 }
251 })
252 }
253}
254
255#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
256impl std::borrow::Borrow<CredentialID> for AttestedPasskey {
257 fn borrow(&self) -> &CredentialID {
258 &self.cred.cred_id
259 }
260}
261
262#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
263impl PartialEq for AttestedPasskey {
264 fn eq(&self, other: &Self) -> bool {
265 self.cred.cred_id == other.cred.cred_id
266 }
267}
268
269#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
270impl Eq for AttestedPasskey {}
271
272#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
273impl PartialOrd for AttestedPasskey {
274 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
275 Some(self.cmp(other))
276 }
277}
278
279#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
280impl Ord for AttestedPasskey {
281 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
282 self.cred.cred_id.cmp(&other.cred.cred_id)
283 }
284}
285
286#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
287impl From<&AttestedPasskey> for Passkey {
288 fn from(k: &AttestedPasskey) -> Self {
289 Passkey {
290 cred: k.cred.clone(),
291 }
292 }
293}
294
295#[cfg(any(all(doc, not(doctest)), feature = "attestation"))]
296impl From<AttestedPasskey> for Passkey {
297 fn from(k: AttestedPasskey) -> Self {
298 Passkey { cred: k.cred }
299 }
300}
301
302#[cfg(all(feature = "danger-credential-internals", feature = "attestation"))]
303impl From<AttestedPasskey> for Credential {
304 fn from(pk: AttestedPasskey) -> Self {
305 pk.cred
306 }
307}
308
309#[cfg(all(feature = "danger-credential-internals", feature = "attestation"))]
310impl From<Credential> for AttestedPasskey {
311 /// Convert a generic webauthn credential into an [AttestedPasskey]
312 fn from(cred: Credential) -> Self {
313 AttestedPasskey { cred }
314 }
315}
316
317/// An in progress registration session for a [SecurityKey].
318///
319/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
320///
321/// Failure to do so *may* open you to replay attacks which can significantly weaken the
322/// security of this system.
323///
324/// In some cases you *may* wish to serialise this value. For details on how to achieve this
325/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
326#[derive(Debug, Clone)]
327#[cfg_attr(
328 feature = "danger-allow-state-serialisation",
329 derive(Serialize, Deserialize)
330)]
331pub struct SecurityKeyRegistration {
332 pub(crate) rs: RegistrationState,
333 pub(crate) ca_list: Option<AttestationCaList>,
334}
335
336/// An in progress authentication session for a [SecurityKey].
337///
338/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
339///
340/// Failure to do so *may* open you to replay attacks which can significantly weaken the
341/// security of this system.
342///
343/// In some cases you *may* wish to serialise this value. For details on how to achieve this
344/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
345#[derive(Debug, Clone)]
346#[cfg_attr(
347 feature = "danger-allow-state-serialisation",
348 derive(Serialize, Deserialize)
349)]
350pub struct SecurityKeyAuthentication {
351 pub(crate) ast: AuthenticationState,
352}
353
354/// A Security Key for a user. These are the legacy "second factor" method of security tokens.
355///
356/// You should avoid this type in favour of [Passkey] or [AttestedPasskey]
357///
358/// These can be safely serialised and deserialised from a database for use.
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct SecurityKey {
361 pub(crate) cred: Credential,
362}
363
364impl SecurityKey {
365 /// Retrieve a reference to this Security Key's credential ID.
366 pub fn cred_id(&self) -> &CredentialID {
367 &self.cred.cred_id
368 }
369
370 /// Retrieve the type of cryptographic algorithm used by this key
371 pub fn cred_algorithm(&self) -> &COSEAlgorithm {
372 &self.cred.cred.type_
373 }
374
375 /// Retrieve a reference to the attestation used during this [`Credential`]'s
376 /// registration. This can tell you information about the manufacturer and
377 /// what type of credential it is.
378 pub fn attestation(&self) -> &ParsedAttestation {
379 &self.cred.attestation
380 }
381
382 /// Post authentication, update this credential's properties.
383 ///
384 /// To determine if this is required, you can inspect the result of
385 /// `authentication_result.needs_update()`. Generally this will always
386 /// be true as this class of key will maintain an activation counter which
387 /// allows (limited) protection against device cloning.
388 ///
389 /// If the credential_id does not match, None is returned. If the cred id matches
390 /// and the credential is updated, Some(true) is returned. If the cred id
391 /// matches, but the credential is not changed, Some(false) is returned.
392 pub fn update_credential(&mut self, res: &AuthenticationResult) -> Option<bool> {
393 if res.cred_id() == self.cred_id() {
394 let mut changed = false;
395 if res.counter() > self.cred.counter {
396 self.cred.counter = res.counter();
397 changed = true;
398 }
399
400 if res.backup_state() != self.cred.backup_state {
401 self.cred.backup_state = res.backup_state();
402 changed = true;
403 }
404
405 Some(changed)
406 } else {
407 None
408 }
409 }
410}
411
412impl PartialEq for SecurityKey {
413 fn eq(&self, other: &Self) -> bool {
414 self.cred.cred_id == other.cred.cred_id
415 }
416}
417
418#[cfg(feature = "danger-credential-internals")]
419impl From<SecurityKey> for Credential {
420 fn from(sk: SecurityKey) -> Self {
421 sk.cred
422 }
423}
424
425#[cfg(feature = "danger-credential-internals")]
426impl From<Credential> for SecurityKey {
427 /// Convert a generic webauthn credential into a security key
428 fn from(cred: Credential) -> Self {
429 SecurityKey { cred }
430 }
431}
432
433/// An in progress registration session for an [AttestedResidentKey].
434///
435/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
436///
437/// Failure to do so *may* open you to replay attacks which can significantly weaken the
438/// security of this system.
439///
440/// In some cases you *may* wish to serialise this value. For details on how to achieve this
441/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
442#[derive(Debug, Clone)]
443#[cfg_attr(
444 feature = "danger-allow-state-serialisation",
445 derive(Serialize, Deserialize)
446)]
447#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
448pub struct AttestedResidentKeyRegistration {
449 pub(crate) rs: RegistrationState,
450 pub(crate) ca_list: AttestationCaList,
451}
452
453/// An in progress authentication session for a [AttestedResidentKey].
454///
455/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
456///
457/// Failure to do so *may* open you to replay attacks which can significantly weaken the
458/// security of this system.
459///
460/// In some cases you *may* wish to serialise this value. For details on how to achieve this
461/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
462#[derive(Debug, Clone)]
463#[cfg_attr(
464 feature = "danger-allow-state-serialisation",
465 derive(Serialize, Deserialize)
466)]
467#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
468pub struct AttestedResidentKeyAuthentication {
469 pub(crate) ast: AuthenticationState,
470}
471
472/// An attested resident key belonging to a user. These are a specialisation of [AttestedPasskey] where
473/// the devices in use can be attested. In addition this type enforces keys to be resident on the
474/// authenticator.
475///
476/// Since most authenticators have very limited key residence support, this should only be used in
477/// tightly controlled enterprise environments where you have strict access over the makes and models
478/// of keys in use.
479///
480/// Key residence is *not* a security property. The general reason for the usage of key residence is
481/// to allow the device to identify the user in addition to authenticating them.
482///
483/// These can be safely serialised and deserialised from a database for use.
484#[derive(Debug, Clone, Serialize, Deserialize)]
485#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
486pub struct AttestedResidentKey {
487 pub(crate) cred: Credential,
488}
489
490#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
491impl AttestedResidentKey {
492 /// Retrieve a reference to this Resident Key's credential ID.
493 pub fn cred_id(&self) -> &CredentialID {
494 &self.cred.cred_id
495 }
496
497 /// Retrieve the type of cryptographic algorithm used by this key
498 pub fn cred_algorithm(&self) -> &COSEAlgorithm {
499 &self.cred.cred.type_
500 }
501
502 /// Retrieve a reference to the attestation used during this [`Credential`]'s
503 /// registration. This can tell you information about the manufacturer and
504 /// what type of credential it is.
505 pub fn attestation(&self) -> &ParsedAttestation {
506 &self.cred.attestation
507 }
508
509 /// Post authentication, update this credential'ds properties.
510 ///
511 /// To determine if this is required, you can inspect the result of
512 /// `authentication_result.needs_update()`. Generally this will always
513 /// be true as this class of key will maintain an activation counter which
514 /// allows (limited) protection against device cloning.
515 ///
516 /// If the credential_id does not match, None is returned. If the cred id matches
517 /// and the credential is updated, Some(true) is returned. If the cred id
518 /// matches, but the credential is not changed, Some(false) is returned.
519 pub fn update_credential(&mut self, res: &AuthenticationResult) -> Option<bool> {
520 if res.cred_id() == self.cred_id() {
521 let mut changed = false;
522 if res.counter() > self.cred.counter {
523 self.cred.counter = res.counter();
524 changed = true;
525 }
526
527 if res.backup_state() != self.cred.backup_state {
528 self.cred.backup_state = res.backup_state();
529 changed = true;
530 }
531
532 Some(changed)
533 } else {
534 None
535 }
536 }
537
538 /// Re-verify this Credential's attestation chain. This re-applies the same process
539 /// for certificate authority verification that occured at registration. This can
540 /// be useful if you want to re-assert your credentials match an updated or changed
541 /// ca_list from the time that registration occured. This can also be useful to
542 /// re-determine certain properties of your device that may exist.
543 pub fn verify_attestation<'a>(
544 &'_ self,
545 ca_list: &'a AttestationCaList,
546 ) -> Result<&'a AttestationCa, WebauthnError> {
547 self.cred
548 .verify_attestation(ca_list)
549 .and_then(|maybe_att_ca| {
550 if let Some(att_ca) = maybe_att_ca {
551 Ok(att_ca)
552 } else {
553 Err(WebauthnError::AttestationNotVerifiable)
554 }
555 })
556 }
557}
558
559#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
560impl PartialEq for AttestedResidentKey {
561 fn eq(&self, other: &Self) -> bool {
562 self.cred.cred_id == other.cred.cred_id
563 }
564}
565
566#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
567impl From<&AttestedResidentKey> for Passkey {
568 fn from(k: &AttestedResidentKey) -> Self {
569 Passkey {
570 cred: k.cred.clone(),
571 }
572 }
573}
574
575#[cfg(any(all(doc, not(doctest)), feature = "resident-key-support"))]
576impl From<AttestedResidentKey> for Passkey {
577 fn from(k: AttestedResidentKey) -> Self {
578 Passkey { cred: k.cred }
579 }
580}
581
582#[cfg(all(
583 feature = "danger-credential-internals",
584 feature = "resident-key-support"
585))]
586impl From<AttestedResidentKey> for Credential {
587 fn from(dk: AttestedResidentKey) -> Self {
588 dk.cred
589 }
590}
591
592#[cfg(all(
593 feature = "danger-credential-internals",
594 feature = "resident-key-support"
595))]
596impl From<Credential> for AttestedResidentKey {
597 /// Convert a generic webauthn credential into a security key
598 fn from(cred: Credential) -> Self {
599 AttestedResidentKey { cred }
600 }
601}
602
603/// An in progress authentication session for a [DiscoverableKey]. [Passkey] and [AttestedResidentKey]
604/// can be used with these workflows.
605///
606/// WARNING ⚠️ YOU MUST STORE THIS VALUE SERVER SIDE.
607///
608/// Failure to do so *may* open you to replay attacks which can significantly weaken the
609/// security of this system.
610///
611/// In some cases you *may* wish to serialise this value. For details on how to achieve this
612/// see the [crate#allow-serialising-registration-and-authentication-state] level documentation.
613#[derive(Debug, Clone)]
614#[cfg_attr(
615 feature = "danger-allow-state-serialisation",
616 derive(Serialize, Deserialize)
617)]
618#[cfg(any(all(doc, not(doctest)), feature = "conditional-ui"))]
619pub struct DiscoverableAuthentication {
620 pub(crate) ast: AuthenticationState,
621}
622
623/// A key that can be used in discoverable workflows. Within this library [Passkey]s may be
624/// discoverable on an opportunistic bases, and [AttestedResidentKey]s will always be discoverable.
625///
626/// Generally this is used as part of conditional ui which allows autofill of discovered
627/// credentials in username fields.
628#[derive(Debug, Clone, Serialize, Deserialize)]
629#[cfg(any(all(doc, not(doctest)), feature = "conditional-ui"))]
630pub struct DiscoverableKey {
631 pub(crate) cred: Credential,
632}
633
634#[cfg(any(
635 all(doc, not(doctest)),
636 all(feature = "conditional-ui", feature = "resident-key-support")
637))]
638impl From<&AttestedResidentKey> for DiscoverableKey {
639 fn from(k: &AttestedResidentKey) -> Self {
640 DiscoverableKey {
641 cred: k.cred.clone(),
642 }
643 }
644}
645
646#[cfg(any(
647 all(doc, not(doctest)),
648 all(feature = "conditional-ui", feature = "resident-key-support")
649))]
650impl From<AttestedResidentKey> for DiscoverableKey {
651 fn from(k: AttestedResidentKey) -> Self {
652 DiscoverableKey { cred: k.cred }
653 }
654}
655
656#[cfg(any(
657 all(doc, not(doctest)),
658 all(feature = "conditional-ui", feature = "attestation")
659))]
660impl From<&AttestedPasskey> for DiscoverableKey {
661 fn from(k: &AttestedPasskey) -> Self {
662 DiscoverableKey {
663 cred: k.cred.clone(),
664 }
665 }
666}
667
668#[cfg(any(
669 all(doc, not(doctest)),
670 all(feature = "conditional-ui", feature = "attestation")
671))]
672impl From<AttestedPasskey> for DiscoverableKey {
673 fn from(k: AttestedPasskey) -> Self {
674 DiscoverableKey { cred: k.cred }
675 }
676}
677
678#[cfg(any(all(doc, not(doctest)), feature = "conditional-ui"))]
679impl From<&Passkey> for DiscoverableKey {
680 fn from(k: &Passkey) -> Self {
681 DiscoverableKey {
682 cred: k.cred.clone(),
683 }
684 }
685}
686
687#[cfg(any(all(doc, not(doctest)), feature = "conditional-ui"))]
688impl From<Passkey> for DiscoverableKey {
689 fn from(k: Passkey) -> Self {
690 DiscoverableKey { cred: k.cred }
691 }
692}