webauthn_authenticator_rs/
authenticator_hashed.rs

1//! Specialized alternative traits for authenticator backends.
2#[cfg(feature = "ctap2")]
3use std::collections::BTreeMap;
4
5#[cfg(any(feature = "ctap2", feature = "crypto"))]
6use base64urlsafedata::Base64UrlSafeData;
7
8#[cfg(feature = "ctap2")]
9use serde_cbor_2::{ser::to_vec_packed, Value};
10#[cfg(any(all(doc, not(doctest)), feature = "crypto"))]
11use url::Url;
12#[cfg(feature = "ctap2")]
13use webauthn_rs_proto::PublicKeyCredentialDescriptor;
14use webauthn_rs_proto::{
15    PublicKeyCredential, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions,
16    RegisterPublicKeyCredential,
17};
18
19#[cfg(any(all(doc, not(doctest)), feature = "ctap2"))]
20use crate::ctap2::commands::{
21    GetAssertionRequest, GetAssertionResponse, MakeCredentialRequest, MakeCredentialResponse,
22};
23use crate::error::WebauthnCError;
24#[cfg(any(all(doc, not(doctest)), feature = "crypto"))]
25use crate::{
26    crypto::compute_sha256,
27    util::{creation_to_clientdata, get_to_clientdata},
28    AuthenticatorBackend,
29};
30
31/// [AuthenticatorBackend] with a `client_data_hash` parameter, for proxying
32/// requests.
33///
34/// **Note:** unless you're proxying autentication requests, use the
35/// [AuthenticatorBackend] trait instead. There is an implementation of
36/// [AuthenticatorBackend] for `T: AuthenticatorBackendHashedClientData`.
37///
38/// Normally, [AuthenticatorBackend] takes the `origin` and `options.challenge`
39/// parameters, serialises it to JSON, and then hashes it to produce
40/// `client_data_hash`, which the authenticator signs. That JSON and the
41/// signature are returned to the relying party, which it can check contain
42/// expected values and are signed correctly.
43///
44/// This doesn't work when proxying an authenticator, where an initiator (web
45/// browser) has *already* produced a `client_data_hash` for the authenticator
46/// to sign, and changing it will cause the authenticator to sign something else
47/// (and fail verification).
48///
49/// This trait instead takes a `client_data_hash` directly, and ignores the
50/// `options.challenge` parameter. The downside is that this *can't* return a
51/// `client_data_json` (the value is unknown), because the authenticator
52/// wouldn't normally get a `client_data_json`.
53///
54/// This is similar to
55/// [`BrowserPublicKeyCredentialCreationOptions.Builder.setClientDataHash()`][0]
56/// on Android (Google Play Services FIDO API), which Chromium uses to proxy
57/// caBLE requests (which only contain `client_data_json`) to an authenticator
58/// stored in the device's secure element.
59///
60/// [AuthenticatorBackendHashedClientData] provides a [AuthenticatorBackend]
61/// implementation – so backends should only implement **one** of those APIs,
62/// preferring to implement [AuthenticatorBackendHashedClientData] if possible.
63///
64/// This interface won't be feasiable to implement on all platforms. For
65/// example, Windows' Webauthn API takes a `client_data_json` and always hashes
66/// it, and Apple's Passkey API takes `relyingPartyIdentifier` (origin) and
67/// `challenge` parameters and generates the `client_data_json` for you.
68///
69/// Most clients should prefer to use the [AuthenticatorBackend] trait.
70///
71/// **See also:** [perform_register_with_request], [perform_auth_with_request]
72///
73/// [0]: https://developers.google.com/android/reference/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.Builder#public-browserpublickeycredentialcreationoptions.builder-setclientdatahash-byte[]-clientdatahash
74pub trait AuthenticatorBackendHashedClientData {
75    fn perform_register(
76        &mut self,
77        client_data_hash: Vec<u8>,
78        options: PublicKeyCredentialCreationOptions,
79        timeout_ms: u32,
80    ) -> Result<RegisterPublicKeyCredential, WebauthnCError>;
81
82    fn perform_auth(
83        &mut self,
84        client_data_hash: Vec<u8>,
85        options: PublicKeyCredentialRequestOptions,
86        timeout_ms: u32,
87    ) -> Result<PublicKeyCredential, WebauthnCError>;
88}
89
90#[cfg(any(all(doc, not(doctest)), feature = "crypto"))]
91/// This provides a [AuthenticatorBackend] implementation for
92/// [AuthenticatorBackendHashedClientData] implementations.
93///
94/// This implementation creates and hashes the `client_data_json`, and inserts
95/// it back into the response type as normal.
96impl<T: AuthenticatorBackendHashedClientData> AuthenticatorBackend for T {
97    fn perform_register(
98        &mut self,
99        origin: Url,
100        options: PublicKeyCredentialCreationOptions,
101        timeout_ms: u32,
102    ) -> Result<RegisterPublicKeyCredential, WebauthnCError> {
103        let client_data = creation_to_clientdata(origin, options.challenge.clone());
104        let client_data: Vec<u8> = serde_json::to_string(&client_data)
105            .map_err(|_| WebauthnCError::Json)?
106            .into();
107        let client_data_hash = compute_sha256(&client_data).to_vec();
108        let mut cred = self.perform_register(client_data_hash, options, timeout_ms)?;
109        cred.response.client_data_json = Base64UrlSafeData::from(client_data);
110
111        Ok(cred)
112    }
113
114    fn perform_auth(
115        &mut self,
116        origin: Url,
117        options: PublicKeyCredentialRequestOptions,
118        timeout_ms: u32,
119    ) -> Result<PublicKeyCredential, WebauthnCError> {
120        let client_data = get_to_clientdata(origin, options.challenge.clone());
121        let client_data: Vec<u8> = serde_json::to_string(&client_data)
122            .map_err(|_| WebauthnCError::Json)?
123            .into();
124        let client_data_hash = compute_sha256(&client_data).to_vec();
125        let mut cred = self.perform_auth(client_data_hash, options, timeout_ms)?;
126        cred.response.client_data_json = Base64UrlSafeData::from(client_data);
127        Ok(cred)
128    }
129}
130
131#[cfg(any(all(doc, not(doctest)), feature = "ctap2"))]
132/// Performs a registration request, using a [MakeCredentialRequest].
133///
134/// All PIN/UV auth parameters will be ignored, and are processed by
135/// [AuthenticatorBackendHashedClientData] in the usual way.
136///
137/// Returns a [MakeCredentialResponse] as `Vec<u8>` on success. The message may
138/// not be identical to what the authenticator actually returned, as it is
139/// subject to deserialisation and conversion to and from another structure used
140/// by [AuthenticatorBackend].
141pub fn perform_register_with_request(
142    backend: &mut impl AuthenticatorBackendHashedClientData,
143    request: MakeCredentialRequest,
144    timeout_ms: u32,
145) -> Result<Vec<u8>, WebauthnCError> {
146    let options = PublicKeyCredentialCreationOptions {
147        rp: request.rp,
148        user: request.user,
149        challenge: Base64UrlSafeData::new(),
150        pub_key_cred_params: request.pub_key_cred_params,
151        timeout: Some(timeout_ms),
152        exclude_credentials: Some(request.exclude_list),
153        // TODO
154        hints: None,
155        attestation: None,
156        attestation_formats: None,
157        authenticator_selection: None,
158        extensions: None,
159    };
160    let client_data_hash = request.client_data_hash;
161
162    let cred: RegisterPublicKeyCredential =
163        backend.perform_register(client_data_hash, options, timeout_ms)?;
164
165    // attestation_object is a MakeCredentialResponse, with string keys
166    // rather than u32, we need to convert it.
167    let resp: MakeCredentialResponse =
168        serde_cbor_2::de::from_slice(cred.response.attestation_object.as_slice())
169            .map_err(|_| WebauthnCError::Cbor)?;
170
171    // Write value with u32 keys
172    let resp: BTreeMap<u32, Value> = resp.into();
173    to_vec_packed(&resp).map_err(|_| WebauthnCError::Cbor)
174}
175
176#[cfg(any(all(doc, not(doctest)), feature = "ctap2"))]
177/// Performs an authentication request, using a [GetAssertionRequest].
178///
179/// All PIN/UV auth parameters will be ignored, and are processed by
180/// [AuthenticatorBackendHashedClientData] in the usual way.
181///
182/// Returns a [GetAssertionResponse] as `Vec<u8>` on success. The message may
183/// not be identical to what the authenticator actually returned, as it is
184/// subject to deserialisation and conversion to and from another structure used
185/// by [AuthenticatorBackend].
186pub fn perform_auth_with_request(
187    backend: &mut impl AuthenticatorBackendHashedClientData,
188    request: GetAssertionRequest,
189    timeout_ms: u32,
190) -> Result<Vec<u8>, WebauthnCError> {
191    let options = PublicKeyCredentialRequestOptions {
192        challenge: Base64UrlSafeData::new(),
193        timeout: Some(timeout_ms),
194        rp_id: request.rp_id,
195        allow_credentials: request.allow_list,
196        // TODO
197        hints: None,
198        user_verification: webauthn_rs_proto::UserVerificationPolicy::Preferred,
199        extensions: None,
200    };
201
202    let cred = backend.perform_auth(request.client_data_hash, options, timeout_ms)?;
203    let resp = GetAssertionResponse {
204        credential: Some(PublicKeyCredentialDescriptor {
205            type_: cred.type_,
206            id: cred.raw_id,
207            transports: None,
208        }),
209        auth_data: Some(cred.response.authenticator_data.into()),
210        signature: Some(cred.response.signature.into()),
211        number_of_credentials: None,
212        user_selected: None,
213        large_blob_key: None,
214    };
215
216    // Write value with u32 keys
217    let resp: BTreeMap<u32, Value> = resp.into();
218    to_vec_packed(&resp).map_err(|_| WebauthnCError::Cbor)
219}