Skip to content

Commit

Permalink
Update to upstream mozilla which supports ctap2
Browse files Browse the repository at this point in the history
  • Loading branch information
Firstyear committed Dec 12, 2024
1 parent 5cb5205 commit 5fee2c1
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 99 deletions.
5 changes: 2 additions & 3 deletions webauthn-authenticator-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions webauthn-authenticator-rs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum WebauthnCError {
ApduTransmission,
InvalidAlgorithm,
InvalidAssertion,
InvalidRegistration,
MessageTooLarge,
MessageTooShort,
/// Message was an unexpected length
Expand Down
212 changes: 116 additions & 96 deletions webauthn-authenticator-rs/src/mozilla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -50,60 +49,76 @@ 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();

let (status_tx, status_rx) = channel::<StatusUpdate>();

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;
Expand Down Expand Up @@ -132,6 +147,18 @@ impl AuthenticatorBackend for MozillaAuthenticator {
options: PublicKeyCredentialCreationOptions,
timeout_ms: u32,
) -> Result<RegisterPublicKeyCredential, WebauthnCError> {
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()
Expand All @@ -145,30 +172,28 @@ impl AuthenticatorBackend for MozillaAuthenticator {
})
.collect::<Result<Vec<_>, _>>()?;

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. */
Expand All @@ -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);
Expand Down Expand Up @@ -243,6 +264,18 @@ impl AuthenticatorBackend for MozillaAuthenticator {
options: PublicKeyCredentialRequestOptions,
timeout_ms: u32,
) -> Result<PublicKeyCredential, WebauthnCError> {
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()
Expand All @@ -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();
Expand All @@ -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)?;
Expand Down

0 comments on commit 5fee2c1

Please sign in to comment.