diff --git a/webauthn-authenticator-rs/Cargo.toml b/webauthn-authenticator-rs/Cargo.toml index 4ef5aa9a..07804228 100644 --- a/webauthn-authenticator-rs/Cargo.toml +++ b/webauthn-authenticator-rs/Cargo.toml @@ -86,10 +86,9 @@ openssl = { workspace = true, optional = true } rpassword = { version = "5.0", optional = true } fido-hid-rs = { workspace = true, optional = true } -# authenticator = { path = "../../../authenticator-rs", optional = true, default-features = false, features = ["crypto_openssl"], package = "authenticator-ctap2-2021" } -authenticator = { version = "0.3.2-dev.1", optional = true, default-features = false, features = [ +authenticator = { version = "0.4.1", optional = true, default-features = false, features = [ "crypto_openssl", -], package = "authenticator-ctap2-2021" } +] } # pcsc = { git = "https://github.com/bluetech/pcsc-rust.git", rev = "13e24649be96989cdffb7e73ca3a994b9534ddff", optional = true } pcsc = { version = "2.8.2", optional = true } diff --git a/webauthn-authenticator-rs/src/error.rs b/webauthn-authenticator-rs/src/error.rs index 1b5a14cd..6c0e9418 100644 --- a/webauthn-authenticator-rs/src/error.rs +++ b/webauthn-authenticator-rs/src/error.rs @@ -17,6 +17,7 @@ pub enum WebauthnCError { ApduTransmission, InvalidAlgorithm, InvalidAssertion, + InvalidRegistration, MessageTooLarge, MessageTooShort, /// Message was an unexpected length diff --git a/webauthn-authenticator-rs/src/mozilla.rs b/webauthn-authenticator-rs/src/mozilla.rs index fc621f2f..6dfdc887 100644 --- a/webauthn-authenticator-rs/src/mozilla.rs +++ b/webauthn-authenticator-rs/src/mozilla.rs @@ -26,17 +26,16 @@ use authenticator::{authenticatorservice::AuthenticatorService, StatusUpdate}; #[cfg(feature = "mozilla")] use authenticator::{ - authenticatorservice::{ - CtapVersion, GetAssertionExtensions, GetAssertionOptions, MakeCredentialsExtensions, - MakeCredentialsOptions, RegisterArgsCtap2, SignArgsCtap2, - }, + authenticatorservice::{RegisterArgs, SignArgs}, + crypto::COSEAlgorithm, + ctap2::client_data::{ClientDataHash, CollectedClientData, WebauthnType}, ctap2::server::{ - PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor, + PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty, + ResidentKeyRequirement, Transport, UserVerificationRequirement, }, - ctap2::AssertionObject, - errors::PinError, statecallback::StateCallback, - COSEAlgorithm, Pin, RegisterResult, SignResult, + Pin, StatusPinUv, }; use std::sync::mpsc::{channel, RecvError, Sender}; @@ -50,8 +49,8 @@ pub struct MozillaAuthenticator { impl MozillaAuthenticator { pub fn new() -> Self { - let mut manager = AuthenticatorService::new(CtapVersion::CTAP2) - .expect("The auth service should initialize safely"); + let mut manager = + AuthenticatorService::new().expect("The auth service should initialize safely"); manager.add_u2f_usb_hid_platform_transports(); @@ -59,51 +58,67 @@ impl MozillaAuthenticator { let _thread_handle = thread::spawn(move || loop { match status_rx.recv() { - Ok(StatusUpdate::DeviceAvailable { dev_info }) => { - debug!("STATUS: device available: {}", dev_info) + Ok(StatusUpdate::SelectDeviceNotice) => { + info!("STATUS: Please select a device by touching one of them."); } - Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { - debug!("STATUS: device unavailable: {}", dev_info) + Ok(StatusUpdate::PresenceRequired) => { + info!("STATUS: Please touch your device."); } - Ok(StatusUpdate::Success { dev_info }) => { - debug!("STATUS: success using device: {}", dev_info); + + Ok(StatusUpdate::SelectResultNotice(_, _)) => { + error!("Unexpected State - SelectResultNotice"); + return; } - Ok(StatusUpdate::SelectDeviceNotice) => { - info!("STATUS: Please select a device by touching one of them."); + + Ok(StatusUpdate::InteractiveManagement(..)) => { + error!("Unexpected State - InteractiveManagement"); + return; } - Ok(StatusUpdate::DeviceSelected(dev_info)) => { - debug!("STATUS: Continuing with device: {}", dev_info); + + Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; } - Ok(StatusUpdate::PinError(error, sender)) => match error { - PinError::PinRequired => { - let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") - .expect("Failed to read PIN"); - sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); - continue; - } - PinError::InvalidPin(attempts) => { - error!( - "Wrong PIN! {}", - attempts.map_or("Try again.".to_string(), |a| format!( - "You have {} attempts left.", - a - )) - ); - let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") - .expect("Failed to read PIN"); - sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); - continue; - } - PinError::PinAuthBlocked => { - error!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") - } - PinError::PinBlocked => { - error!("Too many failed attempts. Your device has been blocked. Reset it.") - } - e => { - error!("Unexpected error: {:?}", e) - } - }, + + Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => { + error!( + "Wrong PIN! {}", + attempts.map_or("Try again.".to_string(), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + + Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => { + error!( + "Invalid User Verification! {}", + attempts.map_or("Try again.".to_string(), |a| format!( + "You have {} attempts left.", + a + )) + ); + continue; + } + + Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => { + error!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) + | Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => { + error!("Too many failed attempts. Your device has been blocked. Reset it.") + } + + Ok(StatusUpdate::PinUvError(e)) => { + error!("Unexpected error: {:?}", e) + } + Err(RecvError) => { debug!("STATUS: end"); return; @@ -132,6 +147,18 @@ impl AuthenticatorBackend for MozillaAuthenticator { options: PublicKeyCredentialCreationOptions, timeout_ms: u32, ) -> Result { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Create, + challenge: options.challenge.to_vec().into(), + origin: origin.to_string(), + cross_origin: false, + token_binding: None, + }; + + let ClientDataHash(client_data_hash) = client_data + .hash() + .map_err(|_| WebauthnCError::InvalidRegistration)?; + let pub_cred_params = options .pub_key_cred_params .into_iter() @@ -145,30 +172,28 @@ impl AuthenticatorBackend for MozillaAuthenticator { }) .collect::, _>>()?; - let ctap_args = RegisterArgsCtap2 { - challenge: options.challenge.into(), + let ctap_args = RegisterArgs { + client_data_hash, relying_party: RelyingParty { id: options.rp.id, name: Some(options.rp.name), - icon: None, }, origin: origin.to_string(), - user: User { + user: PublicKeyCredentialUserEntity { id: options.user.id.into(), name: Some(options.user.name), display_name: Some(options.user.display_name), - icon: None, }, pub_cred_params, exclude_list: vec![], - options: MakeCredentialsOptions { - resident_key: None, - user_verification: None, - }, - extensions: MakeCredentialsExtensions { + user_verification_req: UserVerificationRequirement::Required, + resident_key_req: ResidentKeyRequirement::Discouraged, + + pin: None, + extensions: AuthenticationExtensionsClientInputs { ..Default::default() }, - pin: None, + use_ctap1_fallback: false, }; /* Actually call the library. */ @@ -191,16 +216,12 @@ impl AuthenticatorBackend for MozillaAuthenticator { let register_result = register_rx .recv() - .map_err(|_| WebauthnCError::PlatformAuthenticator)?; + // If the channel closes, the platform goes away. + .map_err(|_| WebauthnCError::PlatformAuthenticator)? + // If the registration failed + .map_err(|_| WebauthnCError::InvalidRegistration)?; - let (attestation_object, client_data) = match register_result { - Ok(RegisterResult::CTAP1(_, _)) => return Err(WebauthnCError::PlatformAuthenticator), - Ok(RegisterResult::CTAP2(a, c)) => { - trace!("Ok!"); - (a, c) - } - Err(_e) => return Err(WebauthnCError::PlatformAuthenticator), - }; + let attestation_object = register_result.att_obj; trace!(?attestation_object); trace!(?client_data); @@ -243,6 +264,18 @@ impl AuthenticatorBackend for MozillaAuthenticator { options: PublicKeyCredentialRequestOptions, timeout_ms: u32, ) -> Result { + let client_data = CollectedClientData { + webauthn_type: WebauthnType::Get, + challenge: options.challenge.to_vec().into(), + origin: origin.to_string(), + cross_origin: false, + token_binding: None, + }; + + let ClientDataHash(client_data_hash) = client_data + .hash() + .map_err(|_| WebauthnCError::InvalidRegistration)?; + let allow_list = options .allow_credentials .iter() @@ -256,16 +289,20 @@ impl AuthenticatorBackend for MozillaAuthenticator { }) .collect(); - let ctap_args = SignArgsCtap2 { - challenge: options.challenge.into(), + let ctap_args = SignArgs { + client_data_hash, origin: origin.to_string(), relying_party_id: options.rp_id, allow_list, - options: GetAssertionOptions::default(), - extensions: GetAssertionExtensions { + + user_verification_req: UserVerificationRequirement::Required, + user_presence_req: true, + + extensions: AuthenticationExtensionsClientInputs { ..Default::default() }, pin: None, + use_ctap1_fallback: false, }; let (sign_tx, sign_rx) = channel(); @@ -287,38 +324,21 @@ impl AuthenticatorBackend for MozillaAuthenticator { let sign_result = sign_rx .recv() - .map_err(|_| WebauthnCError::PlatformAuthenticator)?; + .map_err(|_| WebauthnCError::PlatformAuthenticator)? + .map_err(|_| WebauthnCError::InvalidAssertion)?; - let (assertion_object, client_data) = match sign_result { - Ok(SignResult::CTAP1(..)) => return Err(WebauthnCError::PlatformAuthenticator), - Ok(SignResult::CTAP2(assertion_object, client_data)) => (assertion_object, client_data), - Err(_e) => return Err(WebauthnCError::PlatformAuthenticator), - }; + let assertion = sign_result.assertion; - trace!(?assertion_object); + trace!(?assertion); trace!(?client_data); - let AssertionObject(mut assertions) = assertion_object; - let assertion = if let Some(a) = assertions.pop() { - if assertions.is_empty() { - a - } else { - return Err(WebauthnCError::InvalidAssertion); - } - } else { - return Err(WebauthnCError::InvalidAssertion); - }; - let raw_id = assertion .credentials .map(|pkdesc| Base64UrlSafeData::from(pkdesc.id)) .ok_or(WebauthnCError::Internal)?; // let authenticator_data = serde_cbor_2::to_vec(&assertion.auth_data) - let authenticator_data = assertion - .auth_data - .to_vec() - .map_err(|_| WebauthnCError::Cbor)?; + let authenticator_data = assertion.auth_data.to_vec(); let client_data_json = serde_json::to_vec(&client_data).map_err(|_| WebauthnCError::Json)?;