webauthn_authenticator_rs/
mozilla.rs1#[cfg(doc)]
5use crate::stubs::*;
6
7use crate::error::WebauthnCError;
8use crate::AuthenticatorBackend;
9use crate::Url;
10use crate::BASE64_ENGINE;
11
12use base64::Engine;
13use base64urlsafedata::Base64UrlSafeData;
14use webauthn_rs_proto::PublicKeyCredentialCreationOptions;
15use webauthn_rs_proto::{
16 AuthenticatorAttestationResponseRaw, RegisterPublicKeyCredential,
17 RegistrationExtensionsClientOutputs,
18};
19
20use webauthn_rs_proto::PublicKeyCredentialRequestOptions;
21use webauthn_rs_proto::{
22 AuthenticationExtensionsClientOutputs, AuthenticatorAssertionResponseRaw, PublicKeyCredential,
23};
24
25use authenticator::{authenticatorservice::AuthenticatorService, StatusUpdate};
26
27#[cfg(feature = "mozilla")]
28use authenticator::{
29 authenticatorservice::{RegisterArgs, SignArgs},
30 crypto::COSEAlgorithm,
31 ctap2::client_data::{ClientDataHash, CollectedClientData, WebauthnType},
32 ctap2::server::{
33 AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
34 PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
35 ResidentKeyRequirement, Transport, UserVerificationRequirement,
36 },
37 statecallback::StateCallback,
38 Pin, StatusPinUv,
39};
40
41use std::sync::mpsc::{channel, RecvError, Sender};
42use std::thread;
43
44pub struct MozillaAuthenticator {
45 status_tx: Sender<StatusUpdate>,
46 _thread_handle: thread::JoinHandle<()>,
47 manager: AuthenticatorService,
48}
49
50impl MozillaAuthenticator {
51 pub fn new() -> Self {
52 let mut manager =
53 AuthenticatorService::new().expect("The auth service should initialize safely");
54
55 manager.add_u2f_usb_hid_platform_transports();
56
57 let (status_tx, status_rx) = channel::<StatusUpdate>();
58
59 let _thread_handle = thread::spawn(move || loop {
60 match status_rx.recv() {
61 Ok(StatusUpdate::SelectDeviceNotice) => {
62 info!("STATUS: Please select a device by touching one of them.");
63 }
64 Ok(StatusUpdate::PresenceRequired) => {
65 info!("STATUS: Please touch your device.");
66 }
67
68 Ok(StatusUpdate::SelectResultNotice(_, _)) => {
69 error!("Unexpected State - SelectResultNotice");
70 return;
71 }
72
73 Ok(StatusUpdate::InteractiveManagement(..)) => {
74 error!("Unexpected State - InteractiveManagement");
75 return;
76 }
77
78 Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
79 let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ")
80 .expect("Failed to read PIN");
81 sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
82 continue;
83 }
84
85 Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
86 error!(
87 "Wrong PIN! {}",
88 attempts.map_or("Try again.".to_string(), |a| format!(
89 "You have {} attempts left.",
90 a
91 ))
92 );
93 let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ")
94 .expect("Failed to read PIN");
95 sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
96 continue;
97 }
98
99 Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
100 error!(
101 "Invalid User Verification! {}",
102 attempts.map_or("Try again.".to_string(), |a| format!(
103 "You have {} attempts left.",
104 a
105 ))
106 );
107 continue;
108 }
109
110 Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
111 error!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
112 }
113 Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked))
114 | Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
115 error!("Too many failed attempts. Your device has been blocked. Reset it.")
116 }
117
118 Ok(StatusUpdate::PinUvError(e)) => {
119 error!("Unexpected error: {:?}", e)
120 }
121
122 Err(RecvError) => {
123 debug!("STATUS: end");
124 return;
125 }
126 }
127 });
128
129 MozillaAuthenticator {
130 status_tx,
131 _thread_handle,
132 manager,
133 }
134 }
135}
136
137impl Default for MozillaAuthenticator {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl AuthenticatorBackend for MozillaAuthenticator {
144 fn perform_register(
145 &mut self,
146 origin: Url,
147 options: PublicKeyCredentialCreationOptions,
148 timeout_ms: u32,
149 ) -> Result<RegisterPublicKeyCredential, WebauthnCError> {
150 let client_data = CollectedClientData {
151 webauthn_type: WebauthnType::Create,
152 challenge: options.challenge.to_vec().into(),
153 origin: origin.to_string(),
154 cross_origin: false,
155 token_binding: None,
156 };
157
158 let ClientDataHash(client_data_hash) = client_data
159 .hash()
160 .map_err(|_| WebauthnCError::InvalidRegistration)?;
161
162 let pub_cred_params = options
163 .pub_key_cred_params
164 .into_iter()
165 .map(|param| {
166 COSEAlgorithm::try_from(param.alg)
167 .map_err(|e| {
168 error!(?e, "error converting to COSEAlgorithm");
169 WebauthnCError::InvalidAlgorithm
170 })
171 .map(|alg| PublicKeyCredentialParameters { alg })
172 })
173 .collect::<Result<Vec<_>, _>>()?;
174
175 let ctap_args = RegisterArgs {
176 client_data_hash,
177 relying_party: RelyingParty {
178 id: options.rp.id,
179 name: Some(options.rp.name),
180 },
181 origin: origin.to_string(),
182 user: PublicKeyCredentialUserEntity {
183 id: options.user.id.into(),
184 name: Some(options.user.name),
185 display_name: Some(options.user.display_name),
186 },
187 pub_cred_params,
188 exclude_list: vec![],
189 user_verification_req: UserVerificationRequirement::Required,
190 resident_key_req: ResidentKeyRequirement::Discouraged,
191
192 pin: None,
193 extensions: AuthenticationExtensionsClientInputs {
194 ..Default::default()
195 },
196 use_ctap1_fallback: false,
197 };
198
199 let (register_tx, register_rx) = channel();
201
202 let callback = StateCallback::new(Box::new(move |rv| {
203 register_tx
204 .send(rv)
205 .expect("Unable to proceed - state callback channel closed!");
206 }));
207
208 if let Err(_e) = self.manager.register(
209 timeout_ms.into(),
210 ctap_args.into(),
211 self.status_tx.clone(),
212 callback,
213 ) {
214 return Err(WebauthnCError::PlatformAuthenticator);
215 };
216
217 let register_result = register_rx
218 .recv()
219 .map_err(|_| WebauthnCError::PlatformAuthenticator)?
221 .map_err(|_| WebauthnCError::InvalidRegistration)?;
223
224 let attestation_object = register_result.att_obj;
225
226 trace!(?attestation_object);
227 trace!(?client_data);
228
229 let raw_id = if let Some(cred_data) = &attestation_object.auth_data.credential_data {
234 Base64UrlSafeData::from(cred_data.credential_id.clone())
235 } else {
236 return Err(WebauthnCError::PlatformAuthenticator);
237 };
238
239 let attestation_object =
241 serde_cbor_2::to_vec(&attestation_object).map_err(|_| WebauthnCError::Cbor)?;
242
243 let client_data_json =
244 serde_json::to_vec(&client_data).map_err(|_| WebauthnCError::Json)?;
245
246 Ok(RegisterPublicKeyCredential {
247 id: BASE64_ENGINE.encode(&raw_id),
248 raw_id,
249 response: AuthenticatorAttestationResponseRaw {
250 attestation_object: attestation_object.into(),
251 client_data_json: client_data_json.into(),
252 transports: None,
253 },
254 type_: "public-key".to_string(),
255 extensions: RegistrationExtensionsClientOutputs {
256 ..Default::default()
257 },
258 })
259 }
260
261 fn perform_auth(
262 &mut self,
263 origin: Url,
264 options: PublicKeyCredentialRequestOptions,
265 timeout_ms: u32,
266 ) -> Result<PublicKeyCredential, WebauthnCError> {
267 let client_data = CollectedClientData {
268 webauthn_type: WebauthnType::Get,
269 challenge: options.challenge.to_vec().into(),
270 origin: origin.to_string(),
271 cross_origin: false,
272 token_binding: None,
273 };
274
275 let ClientDataHash(client_data_hash) = client_data
276 .hash()
277 .map_err(|_| WebauthnCError::InvalidRegistration)?;
278
279 let allow_list = options
280 .allow_credentials
281 .iter()
282 .map(|cred| {
283 PublicKeyCredentialDescriptor {
284 id: cred.id.clone().into(),
285 transports: vec![Transport::USB],
288 }
289 })
290 .collect();
291
292 let ctap_args = SignArgs {
293 client_data_hash,
294 origin: origin.to_string(),
295 relying_party_id: options.rp_id,
296 allow_list,
297
298 user_verification_req: UserVerificationRequirement::Required,
299 user_presence_req: true,
300
301 extensions: AuthenticationExtensionsClientInputs {
302 ..Default::default()
303 },
304 pin: None,
305 use_ctap1_fallback: false,
306 };
307
308 let (sign_tx, sign_rx) = channel();
309
310 let callback = StateCallback::new(Box::new(move |rv| {
311 sign_tx
312 .send(rv)
313 .expect("Unable to proceed - state callback channel closed!");
314 }));
315
316 if let Err(_e) = self.manager.sign(
317 timeout_ms.into(),
318 ctap_args.into(),
319 self.status_tx.clone(),
320 callback,
321 ) {
322 return Err(WebauthnCError::PlatformAuthenticator);
323 }
324
325 let sign_result = sign_rx
326 .recv()
327 .map_err(|_| WebauthnCError::PlatformAuthenticator)?
328 .map_err(|_| WebauthnCError::InvalidAssertion)?;
329
330 let assertion = sign_result.assertion;
331
332 trace!(?assertion);
333 trace!(?client_data);
334
335 let raw_id = assertion
336 .credentials
337 .map(|pkdesc| Base64UrlSafeData::from(pkdesc.id))
338 .ok_or(WebauthnCError::Internal)?;
339
340 let authenticator_data = assertion.auth_data.to_vec();
342
343 let client_data_json =
344 serde_json::to_vec(&client_data).map_err(|_| WebauthnCError::Json)?;
345
346 Ok(PublicKeyCredential {
347 id: BASE64_ENGINE.encode(&raw_id),
348 raw_id,
349 response: AuthenticatorAssertionResponseRaw {
350 authenticator_data: authenticator_data.into(),
351 client_data_json: client_data_json.into(),
352 signature: assertion.signature.into(),
353 user_handle: assertion.user.map(|u| u.id.into()),
354 },
355 type_: "public-key".to_string(),
356 extensions: AuthenticationExtensionsClientOutputs {
357 ..Default::default()
358 },
359 })
360 }
361}