1use std::{collections::BTreeMap, fmt::Debug, mem::size_of};
2
3#[cfg(feature = "ctap2-management")]
4use crate::util::check_pin;
5use crate::{
6 authenticator_hashed::AuthenticatorBackendHashedClientData,
7 ctap2::{commands::*, pin_uv::*, Ctap21Authenticator},
8 error::{CtapError, WebauthnCError},
9 transport::Token,
10 ui::UiCallback,
11 SHA256Hash, BASE64_ENGINE,
12};
13
14use base64::Engine;
15use base64urlsafedata::Base64UrlSafeData;
16use futures::executor::block_on;
17
18use webauthn_rs_proto::{
19 AuthenticationExtensionsClientOutputs, AuthenticatorAssertionResponseRaw,
20 AuthenticatorAttestationResponseRaw, PubKeyCredParams, PublicKeyCredential,
21 RegisterPublicKeyCredential, RegistrationExtensionsClientOutputs, RelyingParty, User,
22 UserVerificationPolicy,
23};
24
25use super::internal::CtapAuthenticatorVersion;
26
27#[derive(Debug, Clone)]
28pub(super) enum AuthToken {
29 None,
31 ProtocolToken(u32, Vec<u8>),
33 UvTrue,
35}
36
37impl AuthToken {
38 pub fn into_pin_uv_params(self) -> (Option<u32>, Option<Vec<u8>>) {
39 match self {
40 Self::ProtocolToken(p, t) => (Some(p), Some(t)),
41 _ => (None, None),
42 }
43 }
44}
45
46#[derive(Debug)]
47pub(super) enum AuthSession {
48 None,
49 InterfaceToken(PinUvPlatformInterface, Vec<u8>),
51 UvTrue,
52}
53
54#[derive(Debug)]
56pub struct Ctap20Authenticator<'a, T: Token, U: UiCallback> {
57 pub(super) info: GetInfoResponse,
58 pub(super) token: T,
59 pub(super) ui_callback: &'a U,
60}
61
62impl<'a, T: Token, U: UiCallback> CtapAuthenticatorVersion<'a, T, U>
63 for Ctap20Authenticator<'a, T, U>
64{
65 const VERSION: &'static str = "FIDO_2_0";
66 fn new_with_info(info: GetInfoResponse, token: T, ui_callback: &'a U) -> Self {
67 Self {
68 info,
69 token,
70 ui_callback,
71 }
72 }
73}
74
75impl<'a, T: Token, U: UiCallback> Ctap20Authenticator<'a, T, U> {
76 pub fn get_info(&self) -> &GetInfoResponse {
80 &self.info
81 }
82
83 #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
84 pub async fn factory_reset(&mut self) -> Result<(), WebauthnCError> {
86 let ui_callback = self.ui_callback;
87 self.token
88 .transmit(ResetRequest {}, ui_callback)
89 .await
90 .map(|_| ())
91 }
92
93 #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
94 pub(super) async fn refresh_info(&mut self) -> Result<(), WebauthnCError> {
99 let ui_callback = self.ui_callback;
100 self.info = self.token.transmit(GetInfoRequest {}, ui_callback).await?;
101 Ok(())
102 }
103
104 #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
105 pub fn validate_pin(&self, pin: &str) -> Result<String, WebauthnCError> {
108 check_pin(pin, self.info.get_min_pin_length())
109 }
110
111 #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
112 pub async fn set_new_pin(&mut self, pin: &str) -> Result<(), WebauthnCError> {
116 let ui_callback = self.ui_callback;
117 let pin = self.validate_pin(pin)?;
118
119 let mut padded_pin: [u8; 64] = [0; 64];
120 padded_pin[..pin.len()].copy_from_slice(pin.as_bytes());
121
122 let iface = PinUvPlatformInterface::select_protocol(self.info.pin_protocols.as_ref())?;
123
124 let p = iface.get_key_agreement_cmd();
125 let ret = self.token.transmit(p, ui_callback).await?;
126 let key_agreement = ret.key_agreement.ok_or(WebauthnCError::Internal)?;
127 trace!(?key_agreement);
128
129 let shared_secret = iface.encapsulate(key_agreement)?;
132 trace!(?shared_secret);
133
134 let set_pin = iface.set_pin_cmd(padded_pin, shared_secret.as_slice())?;
135 let ret = self.token.transmit(set_pin, ui_callback).await?;
136 trace!(?ret);
137
138 self.refresh_info().await?;
140 Ok(())
141 }
142
143 #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))]
144 pub async fn change_pin(&mut self, old_pin: &str, new_pin: &str) -> Result<(), WebauthnCError> {
148 let ui_callback = self.ui_callback;
149
150 let old_pin = self.validate_pin(old_pin)?;
152 let new_pin = self.validate_pin(new_pin)?;
153 let mut padded_pin: [u8; 64] = [0; 64];
154 padded_pin[..new_pin.len()].copy_from_slice(new_pin.as_bytes());
155
156 let iface = PinUvPlatformInterface::select_protocol(self.info.pin_protocols.as_ref())?;
157
158 let p = iface.get_key_agreement_cmd();
159 let ret = self.token.transmit(p, ui_callback).await?;
160 let key_agreement = ret.key_agreement.ok_or(WebauthnCError::Internal)?;
161 let shared_secret = iface.encapsulate(key_agreement)?;
162
163 let change_pin = iface.change_pin_cmd(&old_pin, padded_pin, &shared_secret)?;
164 let ret = self.token.transmit(change_pin, ui_callback).await?;
165 trace!(?ret);
166
167 self.refresh_info().await?;
169 Ok(())
170 }
171
172 pub(super) async fn get_pin_uv_auth_token(
229 &mut self,
230 client_data_hash: &[u8],
231 permissions: Permissions,
232 rp_id: Option<String>,
233 user_verification_policy: UserVerificationPolicy,
234 ) -> Result<AuthToken, WebauthnCError> {
235 let session = self
236 .get_pin_uv_auth_session(permissions, rp_id, user_verification_policy)
237 .await?;
238
239 Ok(match session {
240 AuthSession::InterfaceToken(iface, pin_token) => {
241 let pin_uv_auth_param =
242 iface.authenticate(pin_token.as_slice(), client_data_hash)?;
243 AuthToken::ProtocolToken(iface.get_pin_uv_protocol(), pin_uv_auth_param)
244 }
245
246 AuthSession::None => AuthToken::None,
247 AuthSession::UvTrue => AuthToken::UvTrue,
248 })
249 }
250
251 pub(super) async fn get_pin_uv_auth_session(
252 &mut self,
253 permissions: Permissions,
254 rp_id: Option<String>,
255 user_verification_policy: UserVerificationPolicy,
256 ) -> Result<AuthSession, WebauthnCError> {
257 #[derive(Debug)]
258 enum PlannedOperation {
259 UvAuthTokenUsingUvWithPermissions,
260 UvAuthTokenUsingPinWithPermissions,
261 Token,
262 }
263
264 if permissions.intersects(Permissions::MAKE_CREDENTIAL | Permissions::GET_ASSERTION)
265 && rp_id.is_none()
266 {
267 error!("rp_id is required for MakeCredential and GetAssertion requests");
268 return Err(WebauthnCError::Internal);
269 }
270
271 if rp_id.is_some() && permissions.is_empty() {
272 error!("rp_id specified with no permissions");
273 return Err(WebauthnCError::Internal);
274 }
275
276 trace!("Authenticator options: {:?}", self.info.options);
277 let ui_callback = self.ui_callback;
278 let client_pin = self.info.get_option("clientPin").unwrap_or_default();
279 let mut always_uv = self.info.get_option("alwaysUv").unwrap_or_default();
280 let make_cred_uv_not_required = self.info.make_cred_uv_not_required();
281 let pin_uv_auth_token = self.info.get_option("pinUvAuthToken").unwrap_or_default();
282 let uv = self.info.get_option("uv").unwrap_or_default();
283 let uv_acfg = self.info.get_option("uvAcfg").unwrap_or_default();
286 let uv_bio_enroll = self.info.get_option("uvBioEnroll").unwrap_or_default();
289 if permissions == Permissions::AUTHENTICATOR_CONFIGURATION
297 && user_verification_policy == UserVerificationPolicy::Discouraged_DO_NOT_USE
298 && !client_pin
299 && !uv
300 && always_uv
301 {
302 trace!(
303 "Pretending alwaysUv = false to allow for initial configuration of toggleAlwaysUv"
304 );
305 always_uv = false;
306 }
307
308 let requires_pin = (permissions.intersects(Permissions::BIO_ENROLLMENT) && !uv_bio_enroll)
309 || (permissions.intersects(Permissions::AUTHENTICATOR_CONFIGURATION) && !uv_acfg)
310 || permissions.intersects(Permissions::CREDENTIAL_MANAGEMENT);
311 trace!("Permissions: {permissions:?}, uvBioEnroll: {uv_bio_enroll:?}, uvAcfg: {uv_acfg:?}, requiresPin: {requires_pin:?}");
312 trace!("uvPolicy: {user_verification_policy:?}, clientPin: {client_pin:?}, pinUvAuthToken: {pin_uv_auth_token:?}, uv: {uv:?}, alwaysUv: {always_uv:?}, makeCredUvNotRequired: {make_cred_uv_not_required:?}");
320 if user_verification_policy == UserVerificationPolicy::Required
321 || user_verification_policy == UserVerificationPolicy::Preferred
322 || client_pin
323 || uv
324 || always_uv
326 {
328 let planned_operation = if uv && !requires_pin {
333 if pin_uv_auth_token {
335 PlannedOperation::UvAuthTokenUsingUvWithPermissions
340 } else {
341 trace!("pinUvAuthToken not supported, planning to use uv=true");
342 return Ok(AuthSession::UvTrue);
343 }
344 } else {
345 if pin_uv_auth_token {
348 if !client_pin {
355 error!(
356 "Client PIN not set, and user verification is preferred or required"
357 );
358 return Err(WebauthnCError::UserVerificationRequired);
359 }
360
361 PlannedOperation::UvAuthTokenUsingPinWithPermissions
362 } else {
363 if !client_pin {
368 error!(
369 "Client PIN not set, and user verification is preferred or required"
370 );
371 return Err(WebauthnCError::UserVerificationRequired);
372 }
373 PlannedOperation::Token
374 }
375 };
376
377 trace!(?planned_operation);
378
379 let iface = PinUvPlatformInterface::select_protocol(self.info.pin_protocols.as_ref())?;
382
383 let p = iface.get_key_agreement_cmd();
386 let ret = self.token.transmit(p, ui_callback).await?;
387 let key_agreement = ret.key_agreement.ok_or(WebauthnCError::Internal)?;
388 trace!(?key_agreement);
389
390 let shared_secret = iface.encapsulate(key_agreement)?;
393 trace!(?shared_secret);
394
395 let p = match planned_operation {
402 PlannedOperation::UvAuthTokenUsingUvWithPermissions => {
403 iface.get_pin_uv_auth_token_using_uv_with_permissions_cmd(permissions, rp_id)
405 }
406 PlannedOperation::UvAuthTokenUsingPinWithPermissions => {
407 let pin = self.request_pin(iface.get_pin_uv_protocol()).await?;
409 iface.get_pin_uv_auth_token_using_pin_with_permissions_cmd(
410 &pin,
411 shared_secret.as_slice(),
412 permissions,
413 rp_id,
414 )?
415 }
416 PlannedOperation::Token => {
417 let pin = self.request_pin(iface.get_pin_uv_protocol()).await?;
419 iface.get_pin_token_cmd(&pin, shared_secret.as_slice())?
420 }
421 };
422
423 let ret = self.token.transmit(p, ui_callback).await?;
424 trace!(?ret);
425 let pin_token = ret
426 .pin_uv_auth_token
427 .ok_or(WebauthnCError::MissingRequiredField)?;
428 let pin_token = iface.decrypt(shared_secret.as_slice(), pin_token.as_slice())?;
430 trace!(?pin_token);
431 Ok(AuthSession::InterfaceToken(iface, pin_token))
432 } else {
433 trace!("User verification disabled");
443 Ok(AuthSession::None)
444 }
445 }
446
447 async fn request_pin(&mut self, pin_uv_protocol: u32) -> Result<String, WebauthnCError> {
448 let p = ClientPinRequest {
449 pin_uv_protocol: Some(pin_uv_protocol),
450 sub_command: ClientPinSubCommand::GetPinRetries,
451 ..Default::default()
452 };
453
454 let ui_callback = self.ui_callback;
455 let ret = self.token.transmit(p, ui_callback).await?;
456 trace!(?ret);
457
458 ui_callback.request_pin().ok_or(WebauthnCError::Cancelled)
461 }
462
463 pub async fn selection(&mut self) -> Result<(), WebauthnCError> {
482 if !self.token.has_button() {
483 return Ok(());
486 }
487
488 if self
489 .info
490 .versions
491 .contains(Ctap21Authenticator::<'a, T, U>::VERSION)
492 {
493 let ui_callback = self.ui_callback;
494 return self
495 .token
496 .transmit(SelectionRequest {}, ui_callback)
497 .await
498 .map(|_| ());
499 }
500
501 let mc = MakeCredentialRequest {
502 client_data_hash: vec![0; size_of::<SHA256Hash>()],
503 rp: RelyingParty {
504 id: "SELECTION".to_string(),
505 name: "SELECTION".to_string(),
506 },
507 user: User {
508 id: Base64UrlSafeData::from(vec![0]),
509 name: "SELECTION".to_string(),
510 display_name: "SELECTION".to_string(),
511 },
512 pub_key_cred_params: vec![
513 PubKeyCredParams {
514 type_: "public-key".to_owned(),
515 alg: -7,
516 },
517 PubKeyCredParams {
518 type_: "public-key".to_owned(),
519 alg: -257,
520 },
521 PubKeyCredParams {
522 type_: "public-key".to_owned(),
523 alg: -37,
524 },
525 ],
526 exclude_list: vec![],
527 options: None,
528 pin_uv_auth_param: Some(vec![]),
529 pin_uv_auth_proto: Some(1),
535 enterprise_attest: None,
536 };
537
538 let ret = self.token.transmit(mc, self.ui_callback).await;
539
540 if let Err(WebauthnCError::Ctap(e)) = ret {
541 if e == CtapError::Ctap2PinAuthInvalid
545 || e == CtapError::Ctap2PinNotSet
546 || e == CtapError::Ctap2PinInvalid
547 {
548 return Ok(());
550 }
551
552 error!("unexpected error from authenticator: {e:?}");
553 return Err(WebauthnCError::Ctap(e));
554 } else {
555 ret?;
557 }
558
559 error!("got unexpected OK response from authenticator");
560 Err(WebauthnCError::Internal)
561 }
562}
563
564impl<T: Token, U: UiCallback> AuthenticatorBackendHashedClientData
565 for Ctap20Authenticator<'_, T, U>
566{
567 fn perform_register(
568 &mut self,
569 client_data_hash: Vec<u8>,
570 options: webauthn_rs_proto::PublicKeyCredentialCreationOptions,
571 _timeout_ms: u32,
572 ) -> Result<webauthn_rs_proto::RegisterPublicKeyCredential, crate::prelude::WebauthnCError>
573 {
574 let authenticator_selection = options.authenticator_selection.unwrap_or_default();
575 let auth_token = block_on(self.get_pin_uv_auth_token(
576 client_data_hash.as_slice(),
577 Permissions::MAKE_CREDENTIAL,
578 Some(options.rp.id.clone()),
579 authenticator_selection.user_verification,
580 ))?;
581
582 let req_options = if let AuthToken::UvTrue = auth_token {
583 Some(BTreeMap::from([("uv".to_owned(), true)]))
585 } else {
586 None
587 };
588 let (pin_uv_auth_proto, pin_uv_auth_param) = auth_token.into_pin_uv_params();
589
590 let mc = MakeCredentialRequest {
591 client_data_hash,
592 rp: options.rp,
593 user: options.user,
594 pub_key_cred_params: options.pub_key_cred_params,
595 exclude_list: options.exclude_credentials.unwrap_or_default(),
596
597 options: req_options,
598 pin_uv_auth_param,
599 pin_uv_auth_proto,
600 enterprise_attest: None,
601 };
602
603 let ret = block_on(self.token.transmit(mc, self.ui_callback))?;
604 trace!(?ret);
605
606 let raw = serde_cbor_2::to_vec(&ret).map_err(|e| {
614 error!("MakeCredentialResponse re-serialization: {:?}", e);
615 WebauthnCError::Cbor
616 })?;
617
618 let cred_id = vec![];
621 let id = String::new();
622
623 let type_ = ret.fmt.ok_or(WebauthnCError::InvalidAlgorithm)?;
624
625 Ok(RegisterPublicKeyCredential {
626 id,
627 raw_id: Base64UrlSafeData::from(cred_id),
628 type_,
629 extensions: RegistrationExtensionsClientOutputs::default(), response: AuthenticatorAttestationResponseRaw {
631 attestation_object: Base64UrlSafeData::from(raw),
632 client_data_json: Base64UrlSafeData::new(),
633 transports: self.info.get_transports(),
636 },
637 })
638 }
639
640 fn perform_auth(
641 &mut self,
642 client_data_hash: Vec<u8>,
643 options: webauthn_rs_proto::PublicKeyCredentialRequestOptions,
644 _timeout_ms: u32,
645 ) -> Result<webauthn_rs_proto::PublicKeyCredential, crate::prelude::WebauthnCError> {
646 trace!("trying to authenticate...");
647 let auth_token = block_on(self.get_pin_uv_auth_token(
648 client_data_hash.as_slice(),
649 Permissions::GET_ASSERTION,
650 Some(options.rp_id.clone()),
651 options.user_verification,
652 ))?;
653
654 let req_options = if let AuthToken::UvTrue = auth_token {
655 Some(BTreeMap::from([("uv".to_owned(), true)]))
657 } else {
658 None
659 };
660 let (pin_uv_auth_proto, pin_uv_auth_param) = auth_token.into_pin_uv_params();
661
662 let ga = GetAssertionRequest {
663 rp_id: options.rp_id,
664 client_data_hash,
665 allow_list: options.allow_credentials,
666 options: req_options,
667 pin_uv_auth_param,
668 pin_uv_auth_proto,
669 };
670
671 trace!(?ga);
672 let ret = block_on(self.token.transmit(ga, self.ui_callback))?;
673 trace!(?ret);
674
675 let raw_id = ret
676 .credential
677 .as_ref()
678 .map(|c| c.id.to_owned())
679 .ok_or(WebauthnCError::Cbor)?;
680 let type_ = ret
681 .credential
682 .map(|c| c.type_)
683 .ok_or(WebauthnCError::Cbor)?;
684 let signature = Base64UrlSafeData::from(ret.signature.ok_or(WebauthnCError::Cbor)?);
685 let authenticator_data =
686 Base64UrlSafeData::from(ret.auth_data.ok_or(WebauthnCError::Cbor)?);
687
688 Ok(PublicKeyCredential {
689 id: BASE64_ENGINE.encode(&raw_id),
690 raw_id,
691 response: AuthenticatorAssertionResponseRaw {
692 authenticator_data,
693 client_data_json: Base64UrlSafeData::new(),
694 signature,
695 user_handle: None,
697 },
698 extensions: AuthenticationExtensionsClientOutputs::default(),
700 type_,
701 })
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 }