1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::fmt::Debug;

use super::u2f::{AuthenticationRequest, RegisterRequest, RegisterResponse};
use crate::{ctap2::make_credential as ctap2, webauthn, Bytes};
use coset::CoseKey;

/// The private WebAuthn credential containing all relevant required and optional information for an
/// authentication ceremony.
///
/// The WebAuthn term for this is a [Public Key Credential Source][cred-src].
///
/// # Personally Identifying Information (PII) considerations
/// While this struct implements [`Debug`], it only prints the following fields:
/// * [`CoseKey::kty`] enum from the [`Self::key`] field,
/// * [`Self::counter`] which is the number of times this was used to authenticate.
///
/// The rest of this struct should be considered secret, either for cryptographic security, or because
/// its value could be used as PII.
///
/// [cred-src]: https://w3c.github.io/webauthn/#public-key-credential-source
// TODO: Implement Zeroize on this if/when rolling our own CoseKey type
// TODO: use `#[non_exhaustive]` here with a builder pattern for building new passkeys
#[derive(Clone)]
pub struct Passkey {
    /// The private key in COSE key format.
    ///
    /// # PII considerations
    /// This value should be considered secret and never printed out as it is a secret cryptographic
    /// key. The only thing that get printed in the `Debug` implementation is the key type,
    /// e.g: EC2, RSA, etc.
    pub key: CoseKey,

    /// A probabilistically-unique byte sequence identifying this [`Passkey`]. It must be at most 1023
    /// bytes long.
    ///
    /// Credential IDs are generated by authenticators in two forms:
    /// 1. At least 16 bytes that include at least 100 bits of entropy, or
    /// 2. The [`Passkey`] item, without its `credential_id`, encrypted so only its managing
    /// authenticator can decrypt it. This form allows the authenticator to be nearly stateless, by
    /// having the Relying Party store any necessary state.
    ///
    /// Relying Parties do not need to distinguish these two `credential id` forms.
    ///
    ///
    /// # PII considerations
    /// This value should be considered secret as it is the user's credential ID for the associated
    /// Relying Party. See [Privacy leak via credential IDs][privacy] for more information.
    ///
    /// [privacy]: https://w3c.github.io/webauthn/#sctn-credential-id-privacy-leak
    pub credential_id: Bytes,

    /// The [Relying Party ID][RP_ID] for which the [`Passkey`] is associated. This value mirrors the
    /// [`webauthn::PublicKeyCredentialRpEntity::id`] value passed during the creation of this credential.
    ///
    /// # PII considerations
    /// This should be handled similarly to a URL. Since this is a user credential for a Relying
    /// Party, this would leak the fact that a user has an account for this particular Relying Party.
    ///
    /// [RP_ID]: https://w3c.github.io/webauthn/#relying-party-identifier
    pub rp_id: String,

    /// This is the [`webauthn::PublicKeyCredentialUserEntity::id`] passed in during the creation of
    /// this credential. An Authenticator can choose to store this or not. If it stores this value,
    /// this [`Passkey`] will become a [Discoverable Credential] and will be returned during authentication
    /// Ceremonies.
    ///
    /// # PII Considerations
    /// This is the identifier a Relying party uses on their side to personally identify a user. This
    /// value is analogous to a username.
    ///
    /// [Discoverable Credential]: https://w3c.github.io/webauthn/#client-side-discoverable-credential
    pub user_handle: Option<Bytes>,

    /// Value tracks the number of times an authentication ceremony has been successfully completed.
    /// If the value is `None` then it will be sent as the constant `0`.
    /// See [Signature counter considerations][signCount] for more information.
    ///
    /// # PII considerations
    /// This value, if populated, is used by the Relying Party as an indicator of a cloned
    /// authenticator. If this [`Passkey`] is to be synced, consider leaving this value empty unless
    /// you can guarantee the value to always be increased for every use of this passkey across its
    /// distribution.
    ///
    /// [signCount]: https://w3c.github.io/webauthn/#signature-counter
    pub counter: Option<u32>,
}

impl Passkey {
    /// Standardised way to "upgrade" a U2F register request into a passkey
    pub fn from_u2f_register_response(
        request: &RegisterRequest,
        response: &RegisterResponse,
        private_key: &CoseKey,
    ) -> Self {
        let app_id: Bytes = request.application.to_vec().into();
        Self {
            key: private_key.clone(),
            credential_id: response.key_handle.clone().to_vec().into(),
            rp_id: app_id.into(),
            user_handle: None,
            counter: Some(0),
        }
    }

    /// Updgrade a U2F Authentication Request into a Passkey
    pub fn from_u2f_auth_request(
        request: &AuthenticationRequest,
        counter: u32,
        private_key: &CoseKey,
    ) -> Self {
        let app_id: Bytes = request.application.to_vec().into();
        Self {
            key: private_key.clone(),
            credential_id: request.key_handle.clone().to_vec().into(),
            rp_id: app_id.into(),
            user_handle: None,
            counter: Some(counter),
        }
    }

    /// This function wraps up a U2F registration request as a Passkey for storing
    /// in a CredentialStore.
    pub fn wrap_u2f_registration_request(
        request: &RegisterRequest,
        response: &RegisterResponse,
        key_handle: &[u8],
        private_key: &CoseKey,
    ) -> (
        Passkey,
        ctap2::PublicKeyCredentialUserEntity,
        ctap2::PublicKeyCredentialRpEntity,
    ) {
        let passkey = Passkey::from_u2f_register_response(request, response, private_key);

        let user_entity = ctap2::PublicKeyCredentialUserEntity {
            id: key_handle.to_vec().into(),
            display_name: None,
            name: None,
            icon_url: None,
        };

        let app_id: Bytes = request.application.to_vec().into();
        let rp = ctap2::PublicKeyCredentialRpEntity {
            id: app_id.into(),
            name: None,
        };

        (passkey, user_entity, rp)
    }
}

impl From<Passkey> for webauthn::PublicKeyCredentialDescriptor {
    fn from(value: Passkey) -> Self {
        Self {
            ty: webauthn::PublicKeyCredentialType::PublicKey,
            id: value.credential_id,
            transports: None,
        }
    }
}

impl From<&Passkey> for webauthn::PublicKeyCredentialDescriptor {
    fn from(value: &Passkey) -> Self {
        Self {
            ty: webauthn::PublicKeyCredentialType::PublicKey,
            id: value.credential_id.clone(),
            transports: None,
        }
    }
}

impl Debug for Passkey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Passkey")
            .field("key_type", &self.key.kty)
            .field("counter", &self.counter)
            .finish()
    }
}