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}