webauthn_rs_proto/
auth.rs

1//! Types related to authentication (Assertion)
2
3#[cfg(feature = "wasm")]
4use base64::Engine;
5use base64urlsafedata::Base64UrlSafeData;
6use serde::{Deserialize, Serialize};
7
8use crate::extensions::{AuthenticationExtensionsClientOutputs, RequestAuthenticationExtensions};
9use crate::options::*;
10#[cfg(feature = "wasm")]
11use crate::BASE64_ENGINE;
12
13/// The requested options for the authentication
14#[derive(Debug, Serialize, Clone, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct PublicKeyCredentialRequestOptions {
17    /// The challenge that should be signed by the authenticator.
18    pub challenge: Base64UrlSafeData,
19    /// The timeout for the authenticator in case of no interaction.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub timeout: Option<u32>,
22    /// The relying party ID.
23    pub rp_id: String,
24    /// The set of credentials that are allowed to sign this challenge.
25    pub allow_credentials: Vec<AllowCredentials>,
26    /// The verification policy the browser will request.
27    pub user_verification: UserVerificationPolicy,
28
29    /// Hints defining which types credentials may be used in this operation.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub hints: Option<Vec<PublicKeyCredentialHints>>,
32
33    /// extensions.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub extensions: Option<RequestAuthenticationExtensions>,
36}
37
38/// Request in residentkey workflows that conditional mediation should be used
39/// in the UI, or not.
40#[derive(Debug, Serialize, Clone, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub enum Mediation {
43    // /// No mediation is provided - This is represented by "None" on the Option
44    // below. We can't use None here as a variant because it confuses serde-wasm-bindgen :(
45    // None,
46    // /// Silent, try to do things without the user being involved. Probably a bad idea.
47    // Silent,
48    // /// If we can get creds without the user having to do anything, great, other wise ask the user. Probably a bad idea.
49    // Optional,
50    /// Discovered credentials are presented to the user in a dialog.
51    /// Conditional UI is used. See <https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-Conditional-UI>
52    /// <https://w3c.github.io/webappsec-credential-management/#enumdef-credentialmediationrequirement>
53    Conditional,
54    // /// The user needs to do something.
55    // Required
56}
57
58/// A JSON serializable challenge which is issued to the user's webbrowser
59/// for handling. This is meant to be opaque, that is, you should not need
60/// to inspect or alter the content of the struct - you should serialise it
61/// and transmit it to the client only.
62#[derive(Debug, Serialize, Clone, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct RequestChallengeResponse {
65    /// The options.
66    pub public_key: PublicKeyCredentialRequestOptions,
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    /// The mediation requested
69    pub mediation: Option<Mediation>,
70}
71
72#[cfg(feature = "wasm")]
73impl From<RequestChallengeResponse> for web_sys::CredentialRequestOptions {
74    fn from(rcr: RequestChallengeResponse) -> Self {
75        use js_sys::{Array, Object, Uint8Array};
76        use wasm_bindgen::JsValue;
77
78        let jsv = serde_wasm_bindgen::to_value(&rcr).unwrap();
79        let pkcco = js_sys::Reflect::get(&jsv, &"publicKey".into()).unwrap();
80
81        let chal = Uint8Array::from(rcr.public_key.challenge.as_slice());
82        js_sys::Reflect::set(&pkcco, &"challenge".into(), &chal).unwrap();
83
84        if let Some(extensions) = rcr.public_key.extensions {
85            let obj: Object = (&extensions).into();
86            js_sys::Reflect::set(&pkcco, &"extensions".into(), &obj).unwrap();
87        }
88
89        let allow_creds: Array = rcr
90            .public_key
91            .allow_credentials
92            .iter()
93            .map(|ac| {
94                let obj = Object::new();
95                js_sys::Reflect::set(&obj, &"type".into(), &JsValue::from_str(ac.type_.as_str()))
96                    .unwrap();
97
98                js_sys::Reflect::set(&obj, &"id".into(), &Uint8Array::from(ac.id.as_slice()))
99                    .unwrap();
100
101                if let Some(transports) = &ac.transports {
102                    let tarray: Array = transports
103                        .iter()
104                        .map(|trs| serde_wasm_bindgen::to_value(trs).unwrap())
105                        .collect();
106
107                    js_sys::Reflect::set(&obj, &"transports".into(), &tarray).unwrap();
108                }
109
110                obj
111            })
112            .collect();
113        js_sys::Reflect::set(&pkcco, &"allowCredentials".into(), &allow_creds).unwrap();
114
115        web_sys::CredentialRequestOptions::from(jsv)
116    }
117}
118
119/// <https://w3c.github.io/webauthn/#authenticatorassertionresponse>
120#[derive(Debug, Deserialize, Serialize, Clone)]
121pub struct AuthenticatorAssertionResponseRaw {
122    /// Raw authenticator data.
123    #[serde(rename = "authenticatorData")]
124    pub authenticator_data: Base64UrlSafeData,
125
126    /// Signed client data.
127    #[serde(rename = "clientDataJSON")]
128    pub client_data_json: Base64UrlSafeData,
129
130    /// Signature
131    pub signature: Base64UrlSafeData,
132
133    /// Optional userhandle.
134    #[serde(rename = "userHandle")]
135    pub user_handle: Option<Base64UrlSafeData>,
136}
137
138/// A client response to an authentication challenge. This contains all required
139/// information to asses and assert trust in a credentials legitimacy, followed
140/// by authentication to a user.
141///
142/// You should not need to handle the inner content of this structure - you should
143/// provide this to the correctly handling function of Webauthn only.
144#[derive(Debug, Deserialize, Serialize, Clone)]
145pub struct PublicKeyCredential {
146    /// The credential Id, likely base64
147    pub id: String,
148    /// The binary of the credential id.
149    #[serde(rename = "rawId")]
150    pub raw_id: Base64UrlSafeData,
151    /// The authenticator response.
152    pub response: AuthenticatorAssertionResponseRaw,
153    /// Unsigned Client processed extensions.
154    #[serde(default, alias = "clientExtensionResults")]
155    pub extensions: AuthenticationExtensionsClientOutputs,
156    /// The authenticator type.
157    #[serde(rename = "type")]
158    pub type_: String,
159}
160
161impl PublicKeyCredential {
162    /// Retrieve the user uniqueid that *may* have been provided by the authenticator during this
163    /// authentication.
164    pub fn get_user_unique_id(&self) -> Option<&[u8]> {
165        self.response.user_handle.as_ref().map(|b| b.as_ref())
166    }
167
168    /// Retrieve the credential id that was provided in this authentication
169    pub fn get_credential_id(&self) -> &[u8] {
170        self.raw_id.as_slice()
171    }
172}
173
174#[cfg(feature = "wasm")]
175impl From<web_sys::PublicKeyCredential> for PublicKeyCredential {
176    fn from(data: web_sys::PublicKeyCredential) -> PublicKeyCredential {
177        use js_sys::Uint8Array;
178
179        let data_raw_id =
180            Uint8Array::new(&js_sys::Reflect::get(&data, &"rawId".into()).unwrap()).to_vec();
181
182        let data_response = js_sys::Reflect::get(&data, &"response".into()).unwrap();
183
184        let data_response_authenticator_data = Uint8Array::new(
185            &js_sys::Reflect::get(&data_response, &"authenticatorData".into()).unwrap(),
186        )
187        .to_vec();
188
189        let data_response_signature =
190            Uint8Array::new(&js_sys::Reflect::get(&data_response, &"signature".into()).unwrap())
191                .to_vec();
192
193        let data_response_user_handle =
194            &js_sys::Reflect::get(&data_response, &"userHandle".into()).unwrap();
195        let data_response_user_handle = if data_response_user_handle.is_undefined() {
196            None
197        } else {
198            Some(Uint8Array::new(data_response_user_handle).to_vec())
199        };
200
201        let data_response_client_data_json = Uint8Array::new(
202            &js_sys::Reflect::get(&data_response, &"clientDataJSON".into()).unwrap(),
203        )
204        .to_vec();
205
206        let data_extensions = data.get_client_extension_results();
207        web_sys::console::log_1(&data_extensions);
208
209        PublicKeyCredential {
210            id: BASE64_ENGINE.encode(&data_raw_id),
211            raw_id: data_raw_id.into(),
212            response: AuthenticatorAssertionResponseRaw {
213                authenticator_data: data_response_authenticator_data.into(),
214                client_data_json: data_response_client_data_json.into(),
215                signature: data_response_signature.into(),
216                user_handle: data_response_user_handle.map(Into::into),
217            },
218            extensions: data_extensions.into(),
219            type_: "public-key".to_string(),
220        }
221    }
222}