forked from mirrors/gecko-dev
Bug 1830944 - vendor authenticator-rs v0.4.0-alpha14. r=keeler,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D176938
This commit is contained in:
parent
7e261edb97
commit
92fb6d7ad4
44 changed files with 1074 additions and 833 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -320,9 +320,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.13"
|
||||
version = "0.4.0-alpha.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7571904a2c563ed99aac46a7d990186f76a8568f0fd1a769a78214dd385946bf"
|
||||
checksum = "9f3aaf9a8213bf1a4cfafca1a38befe0873a2a4d8013136d2dd12227e14bb2ba"
|
||||
dependencies = [
|
||||
"base64 0.13.999",
|
||||
"bitflags 1.3.2",
|
||||
|
|
|
|||
|
|
@ -7609,6 +7609,14 @@ var WebAuthnPromptHelper = {
|
|||
"deviceBlocked",
|
||||
"webauthn.deviceBlockedPrompt"
|
||||
);
|
||||
} else if (data.action == "pin-not-set") {
|
||||
this.show_info(
|
||||
mgr,
|
||||
data.origin,
|
||||
data.tid,
|
||||
"pinNotSet",
|
||||
"webauthn.pinNotSetPrompt"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -462,6 +462,8 @@ webauthn.selectDevicePrompt=Multiple devices found for %S. Please select one.
|
|||
webauthn.deviceBlockedPrompt=User verification failed on %S. There are no attempts left and your device has been locked, because the wrong PIN was provided too many times. The device needs a reset.
|
||||
# LOCALIZATION NOTE (webauthn.pinAuthBlockedPrompt): %S is hostname
|
||||
webauthn.pinAuthBlockedPrompt=User verification failed on %S. There were too many failed attempts in a row and PIN authentication has been temporarily blocked. Your device needs a power cycle (unplug and re-insert).
|
||||
# LOCALIZATION NOTE (webauthn.pinNotSetPrompt): %S is hostname
|
||||
webauthn.pinNotSetPrompt=User verification failed on %S. You may need to set a PIN on your device.
|
||||
webauthn.cancel=Cancel
|
||||
webauthn.cancel.accesskey=c
|
||||
webauthn.proceed=Proceed
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
authors = ["Martin Sirringhaus", "John Schanck"]
|
||||
|
||||
[dependencies]
|
||||
authenticator = { version = "0.4.0-alpha.13", features = ["gecko"] }
|
||||
authenticator = { version = "0.4.0-alpha.14", features = ["gecko"] }
|
||||
log = "0.4"
|
||||
moz_task = { path = "../../../xpcom/rust/moz_task" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ extern crate log;
|
|||
extern crate xpcom;
|
||||
|
||||
use authenticator::{
|
||||
authenticatorservice::{
|
||||
AuthenticatorService, GetAssertionOptions, MakeCredentialsOptions,
|
||||
RegisterArgs, SignArgs,
|
||||
},
|
||||
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
|
||||
ctap2::attestation::AttestationStatement,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User,
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, User, UserVerificationRequirement,
|
||||
},
|
||||
errors::{AuthenticatorError, PinError, U2FTokenError},
|
||||
statecallback::StateCallback,
|
||||
|
|
@ -85,6 +83,7 @@ fn authrs_to_nserror(e: &AuthenticatorError) -> nsresult {
|
|||
AuthenticatorError::PinError(PinError::InvalidPin(_)) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinAuthBlocked) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinBlocked) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinNotSet) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
_ => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
}
|
||||
}
|
||||
|
|
@ -376,9 +375,20 @@ fn status_callback(
|
|||
make_pin_error_prompt("device-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinNotSet)) => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("pin-not-set", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
warn!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Ok(StatusUpdate::InteractiveManagement((_, dev_info, auth_info))) => {
|
||||
debug!(
|
||||
"STATUS: interactive management: {}, {:?}",
|
||||
dev_info, auth_info
|
||||
);
|
||||
}
|
||||
Err(RecvError) => {
|
||||
debug!("STATUS: end");
|
||||
return;
|
||||
|
|
@ -482,22 +492,26 @@ impl AuthrsTransport {
|
|||
|
||||
let mut resident_key = nsString::new();
|
||||
unsafe { args.GetResidentKey(&mut *resident_key) }.to_result()?;
|
||||
let resident_key = if resident_key.eq("required") {
|
||||
Some(true)
|
||||
let resident_key_req = if resident_key.eq("required") {
|
||||
ResidentKeyRequirement::Required
|
||||
} else if resident_key.eq("preferred") {
|
||||
ResidentKeyRequirement::Preferred
|
||||
} else if resident_key.eq("discouraged") {
|
||||
Some(false)
|
||||
ResidentKeyRequirement::Discouraged
|
||||
} else {
|
||||
None
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
};
|
||||
|
||||
let mut user_verification = nsString::new();
|
||||
unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
|
||||
let user_verification = if user_verification.eq("required") {
|
||||
Some(true)
|
||||
let user_verification_req = if user_verification.eq("required") {
|
||||
UserVerificationRequirement::Required
|
||||
} else if user_verification.eq("preferred") {
|
||||
UserVerificationRequirement::Preferred
|
||||
} else if user_verification.eq("discouraged") {
|
||||
Some(false)
|
||||
UserVerificationRequirement::Discouraged
|
||||
} else {
|
||||
None
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
};
|
||||
|
||||
let mut attestation_conveyance_preference = nsString::new();
|
||||
|
|
@ -529,10 +543,8 @@ impl AuthrsTransport {
|
|||
},
|
||||
pub_cred_params,
|
||||
exclude_list,
|
||||
options: MakeCredentialsOptions {
|
||||
resident_key,
|
||||
user_verification,
|
||||
},
|
||||
user_verification_req,
|
||||
resident_key_req,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
use_ctap1_fallback: static_prefs::pref!("security.webauthn.ctap2") == false,
|
||||
|
|
@ -625,12 +637,14 @@ impl AuthrsTransport {
|
|||
|
||||
let mut user_verification = nsString::new();
|
||||
unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
|
||||
let user_verification = if user_verification.eq("required") {
|
||||
Some(true)
|
||||
let user_verification_req = if user_verification.eq("required") {
|
||||
UserVerificationRequirement::Required
|
||||
} else if user_verification.eq("preferred") {
|
||||
UserVerificationRequirement::Preferred
|
||||
} else if user_verification.eq("discouraged") {
|
||||
Some(false)
|
||||
UserVerificationRequirement::Discouraged
|
||||
} else {
|
||||
None
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
};
|
||||
|
||||
let mut alternate_rp_id = None;
|
||||
|
|
@ -695,10 +709,8 @@ impl AuthrsTransport {
|
|||
relying_party_id: relying_party_id.to_string(),
|
||||
origin: origin.to_string(),
|
||||
allow_list,
|
||||
options: GetAssertionOptions {
|
||||
user_presence: Some(true),
|
||||
user_verification,
|
||||
},
|
||||
user_verification_req,
|
||||
user_presence_req: true,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
alternate_rp_id,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ user-id = 1258
|
|||
user-login = "padenot"
|
||||
user-name = "Paul Adenot"
|
||||
|
||||
[[publisher.authenticator]]
|
||||
version = "0.4.0-alpha.14"
|
||||
when = "2023-05-02"
|
||||
user-id = 175410
|
||||
user-login = "jschanck"
|
||||
user-name = "John Schanck"
|
||||
|
||||
[[publisher.bhttp]]
|
||||
version = "0.3.1"
|
||||
when = "2023-02-23"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
2
third_party/rust/authenticator/Cargo.lock
generated
vendored
2
third_party/rust/authenticator/Cargo.lock
generated
vendored
|
|
@ -39,7 +39,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.13"
|
||||
version = "0.4.0-alpha.14"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64",
|
||||
|
|
|
|||
2
third_party/rust/authenticator/Cargo.toml
vendored
2
third_party/rust/authenticator/Cargo.toml
vendored
|
|
@ -12,7 +12,7 @@
|
|||
[package]
|
||||
edition = "2018"
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.13"
|
||||
version = "0.4.0-alpha.14"
|
||||
authors = [
|
||||
"J.C. Jones <jc@mozilla.com>",
|
||||
"Tim Taubert <ttaubert@mozilla.com>",
|
||||
|
|
|
|||
39
third_party/rust/authenticator/examples/ctap2.rs
vendored
39
third_party/rust/authenticator/examples/ctap2.rs
vendored
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
use authenticator::{
|
||||
authenticatorservice::{
|
||||
AuthenticatorService, GetAssertionExtensions, GetAssertionOptions,
|
||||
HmacSecretExtension, MakeCredentialsExtensions, MakeCredentialsOptions, RegisterArgs,
|
||||
SignArgs,
|
||||
AuthenticatorService, GetAssertionExtensions, HmacSecretExtension,
|
||||
MakeCredentialsExtensions, RegisterArgs, SignArgs,
|
||||
},
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
},
|
||||
statecallback::StateCallback,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
|
|
@ -40,6 +40,7 @@ fn main() {
|
|||
);
|
||||
opts.optflag("s", "hmac_secret", "With hmac-secret");
|
||||
opts.optflag("h", "help", "print this help menu");
|
||||
opts.optflag("f", "fallback", "Use CTAP1 fallback implementation");
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => panic!("{}", f.to_string()),
|
||||
|
|
@ -49,13 +50,15 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut manager = AuthenticatorService::new()
|
||||
.expect("The auth service should initialize safely");
|
||||
let mut manager =
|
||||
AuthenticatorService::new().expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
|
||||
let fallback = matches.opt_present("fallback");
|
||||
|
||||
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 25) {
|
||||
Ok(timeout_s) => {
|
||||
println!("Using {}s as the timeout", &timeout_s);
|
||||
|
|
@ -78,12 +81,12 @@ fn main() {
|
|||
challenge.update(challenge_str.as_bytes());
|
||||
let chall_bytes: [u8; 32] = challenge.finalize().into();
|
||||
|
||||
// TODO(MS): Needs to be added to RegisterArgsCtap2
|
||||
// let flags = RegisterFlags::empty();
|
||||
|
||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
|
|
@ -153,7 +156,7 @@ fn main() {
|
|||
display_name: None,
|
||||
};
|
||||
let origin = "https://example.com".to_string();
|
||||
let ctap_args = RegisterArgs{
|
||||
let ctap_args = RegisterArgs {
|
||||
client_data_hash: chall_bytes,
|
||||
relying_party: RelyingParty {
|
||||
id: "example.com".to_string(),
|
||||
|
|
@ -178,10 +181,8 @@ fn main() {
|
|||
],
|
||||
transports: vec![Transport::USB, Transport::NFC],
|
||||
}],
|
||||
options: MakeCredentialsOptions {
|
||||
resident_key: None,
|
||||
user_verification: None,
|
||||
},
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
resident_key_req: ResidentKeyRequirement::Discouraged,
|
||||
extensions: MakeCredentialsExtensions {
|
||||
hmac_secret: if matches.opt_present("hmac_secret") {
|
||||
Some(true)
|
||||
|
|
@ -191,7 +192,7 @@ fn main() {
|
|||
..Default::default()
|
||||
},
|
||||
pin: None,
|
||||
use_ctap1_fallback: false,
|
||||
use_ctap1_fallback: fallback,
|
||||
};
|
||||
|
||||
let attestation_object;
|
||||
|
|
@ -201,8 +202,7 @@ fn main() {
|
|||
register_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx.clone(), callback)
|
||||
{
|
||||
if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx.clone(), callback) {
|
||||
panic!("Couldn't register: {:?}", e);
|
||||
};
|
||||
|
||||
|
|
@ -242,7 +242,8 @@ fn main() {
|
|||
origin,
|
||||
relying_party_id: "example.com".to_string(),
|
||||
allow_list,
|
||||
options: GetAssertionOptions::default(),
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
user_presence_req: true,
|
||||
extensions: GetAssertionExtensions {
|
||||
hmac_secret: if matches.opt_present("hmac_secret") {
|
||||
Some(HmacSecretExtension::new(
|
||||
|
|
@ -259,7 +260,7 @@ fn main() {
|
|||
},
|
||||
pin: None,
|
||||
alternate_rp_id: None,
|
||||
use_ctap1_fallback: false,
|
||||
use_ctap1_fallback: fallback,
|
||||
};
|
||||
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use authenticator::{
|
||||
authenticatorservice::{
|
||||
AuthenticatorService, GetAssertionOptions, MakeCredentialsOptions,
|
||||
RegisterArgs, SignArgs,
|
||||
},
|
||||
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
},
|
||||
statecallback::StateCallback,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
|
|
@ -49,6 +47,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
|||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
|
|
@ -139,10 +140,8 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
|||
id: vec![],
|
||||
transports: vec![Transport::USB, Transport::NFC],
|
||||
}],
|
||||
options: MakeCredentialsOptions {
|
||||
resident_key: Some(true),
|
||||
user_verification: Some(true),
|
||||
},
|
||||
user_verification_req: UserVerificationRequirement::Required,
|
||||
resident_key_req: ResidentKeyRequirement::Required,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
use_ctap1_fallback: false,
|
||||
|
|
@ -201,8 +200,8 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut manager = AuthenticatorService::new()
|
||||
.expect("The auth service should initialize safely");
|
||||
let mut manager =
|
||||
AuthenticatorService::new().expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
|
|
@ -241,6 +240,9 @@ fn main() {
|
|||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
|
|
@ -311,7 +313,8 @@ fn main() {
|
|||
origin,
|
||||
relying_party_id: "example.com".to_string(),
|
||||
allow_list,
|
||||
options: GetAssertionOptions::default(),
|
||||
user_verification_req: UserVerificationRequirement::Required,
|
||||
user_presence_req: true,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
alternate_rp_id: None,
|
||||
|
|
|
|||
201
third_party/rust/authenticator/examples/interactive_management.rs
vendored
Normal file
201
third_party/rust/authenticator/examples/interactive_management.rs
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use authenticator::{
|
||||
authenticatorservice::AuthenticatorService, errors::AuthenticatorError,
|
||||
statecallback::StateCallback, InteractiveRequest, Pin, ResetResult, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use log::debug;
|
||||
use std::{env, io, thread};
|
||||
use std::{
|
||||
io::Write,
|
||||
sync::mpsc::{channel, Receiver, RecvError},
|
||||
};
|
||||
|
||||
fn print_usage(program: &str, opts: Options) {
|
||||
let brief = format!("Usage: {program} [options]");
|
||||
print!("{}", opts.usage(&brief));
|
||||
}
|
||||
|
||||
fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
|
||||
let mut num_of_devices = 0;
|
||||
loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement((tx, dev_info, auth_info))) => {
|
||||
debug!(
|
||||
"STATUS: interactive management: {:#}, {:#?}",
|
||||
dev_info, auth_info
|
||||
);
|
||||
println!("Device info {:#}", dev_info);
|
||||
let mut change_pin = false;
|
||||
if let Some(info) = auth_info {
|
||||
println!("Authenticator Info {:#?}", info);
|
||||
println!();
|
||||
println!("What do you wish to do?");
|
||||
|
||||
let mut choices = vec!["0", "1"];
|
||||
println!("(0) Quit");
|
||||
println!("(1) Reset token");
|
||||
match info.options.client_pin {
|
||||
None => {}
|
||||
Some(true) => {
|
||||
println!("(2) Change PIN");
|
||||
choices.push("2");
|
||||
change_pin = true;
|
||||
}
|
||||
Some(false) => {
|
||||
println!("(2) Set PIN");
|
||||
choices.push("2");
|
||||
}
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
while !choices.contains(&input.trim()) {
|
||||
input.clear();
|
||||
print!("Your choice: ");
|
||||
io::stdout()
|
||||
.lock()
|
||||
.flush()
|
||||
.expect("Failed to flush stdout!");
|
||||
io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("error: unable to read user input");
|
||||
}
|
||||
input = input.trim().to_string();
|
||||
|
||||
match input.as_str() {
|
||||
"0" => {
|
||||
return;
|
||||
}
|
||||
"1" => {
|
||||
tx.send(InteractiveRequest::Reset)
|
||||
.expect("Failed to send Reset request.");
|
||||
}
|
||||
"2" => {
|
||||
let raw_new_pin = rpassword::prompt_password_stderr("Enter new PIN: ")
|
||||
.expect("Failed to read PIN");
|
||||
let new_pin = Pin::new(&raw_new_pin);
|
||||
if change_pin {
|
||||
let raw_curr_pin =
|
||||
rpassword::prompt_password_stderr("Enter current PIN: ")
|
||||
.expect("Failed to read PIN");
|
||||
let curr_pin = Pin::new(&raw_curr_pin);
|
||||
tx.send(InteractiveRequest::ChangePIN(curr_pin, new_pin))
|
||||
.expect("Failed to send PIN-change request");
|
||||
} else {
|
||||
tx.send(InteractiveRequest::SetPIN(new_pin))
|
||||
.expect("Failed to send PIN-set request");
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
panic!("Can't happen");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Device only supports CTAP1 and can't be managed.");
|
||||
}
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
num_of_devices += 1;
|
||||
debug!(
|
||||
"STATUS: New device #{} available: {}",
|
||||
num_of_devices, dev_info
|
||||
);
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
num_of_devices -= 1;
|
||||
if num_of_devices <= 0 {
|
||||
println!("No more devices left. Please plug in a device!");
|
||||
}
|
||||
debug!("STATUS: Device became unavailable: {}", dev_info)
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {}", dev_info);
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(_dev_info)) => {}
|
||||
Ok(StatusUpdate::PinUvError(..)) => {
|
||||
println!("STATUS: Pin Error!");
|
||||
}
|
||||
Err(RecvError) => {
|
||||
println!("STATUS: end");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let program = args[0].clone();
|
||||
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms");
|
||||
opts.optflag("h", "help", "print this help menu").optopt(
|
||||
"t",
|
||||
"timeout",
|
||||
"timeout in seconds",
|
||||
"SEC",
|
||||
);
|
||||
opts.optflag("h", "help", "print this help menu");
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => panic!("{}", f.to_string()),
|
||||
};
|
||||
if matches.opt_present("help") {
|
||||
print_usage(&program, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut manager =
|
||||
AuthenticatorService::new().expect("The auth service should initialize safely");
|
||||
|
||||
if !matches.opt_present("no-u2f-usb-hid") {
|
||||
manager.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
|
||||
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 120) {
|
||||
Ok(timeout_s) => {
|
||||
println!("Using {}s as the timeout", &timeout_s);
|
||||
timeout_s * 1_000
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
print_usage(&program, opts);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || interactive_status_callback(status_rx));
|
||||
|
||||
let (manage_tx, manage_rx) = channel();
|
||||
let state_callback =
|
||||
StateCallback::<Result<ResetResult, AuthenticatorError>>::new(Box::new(move |rv| {
|
||||
manage_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
match manager.manage(timeout_ms, status_tx, state_callback) {
|
||||
Ok(_) => {
|
||||
debug!("Started management")
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error! Failed to start interactive management: {:?}", e)
|
||||
}
|
||||
}
|
||||
let manage_result = manage_rx
|
||||
.recv()
|
||||
.expect("Problem receiving, unable to continue");
|
||||
match manage_result {
|
||||
Ok(_) => println!("Success!"),
|
||||
Err(e) => println!("Error! {:?}", e),
|
||||
};
|
||||
println!("Done");
|
||||
}
|
||||
|
|
@ -70,6 +70,9 @@ fn main() {
|
|||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use authenticator::{
|
||||
authenticatorservice::{
|
||||
AuthenticatorService, MakeCredentialsOptions, RegisterArgs,
|
||||
},
|
||||
authenticatorservice::{AuthenticatorService, RegisterArgs},
|
||||
ctap2::commands::StatusCode,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
},
|
||||
errors::{AuthenticatorError, CommandError, HIDError},
|
||||
statecallback::StateCallback,
|
||||
|
|
@ -48,8 +47,8 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut manager = AuthenticatorService::new()
|
||||
.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();
|
||||
|
||||
|
|
@ -75,12 +74,12 @@ fn main() {
|
|||
challenge.update(challenge_str.as_bytes());
|
||||
let chall_bytes = challenge.finalize().into();
|
||||
|
||||
// TODO(MS): Needs to be added to RegisterArgsCtap2
|
||||
// let flags = RegisterFlags::empty();
|
||||
|
||||
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||
thread::spawn(move || loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
|
|
@ -168,10 +167,8 @@ fn main() {
|
|||
},
|
||||
],
|
||||
exclude_list: vec![],
|
||||
options: MakeCredentialsOptions {
|
||||
resident_key: None,
|
||||
user_verification: None,
|
||||
},
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
resident_key_req: ResidentKeyRequirement::Discouraged,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
use_ctap1_fallback: false,
|
||||
|
|
@ -183,12 +180,8 @@ fn main() {
|
|||
register_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
if let Err(e) = manager.register(
|
||||
timeout_ms,
|
||||
ctap_args.clone(),
|
||||
status_tx.clone(),
|
||||
callback,
|
||||
) {
|
||||
if let Err(e) = manager.register(timeout_ms, ctap_args.clone(), status_tx.clone(), callback)
|
||||
{
|
||||
panic!("Couldn't register: {:?}", e);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::ctap2::commands::client_pin::Pin;
|
||||
pub use crate::ctap2::commands::get_assertion::{
|
||||
GetAssertionExtensions, GetAssertionOptions, HmacSecretExtension,
|
||||
};
|
||||
pub use crate::ctap2::commands::make_credentials::{
|
||||
MakeCredentialsExtensions, MakeCredentialsOptions,
|
||||
};
|
||||
pub use crate::ctap2::commands::get_assertion::{GetAssertionExtensions, HmacSecretExtension};
|
||||
pub use crate::ctap2::commands::make_credentials::MakeCredentialsExtensions;
|
||||
use crate::ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User,
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, User, UserVerificationRequirement,
|
||||
};
|
||||
use crate::errors::*;
|
||||
use crate::manager::Manager;
|
||||
|
|
@ -25,7 +22,8 @@ pub struct RegisterArgs {
|
|||
pub user: User,
|
||||
pub pub_cred_params: Vec<PublicKeyCredentialParameters>,
|
||||
pub exclude_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
pub options: MakeCredentialsOptions,
|
||||
pub user_verification_req: UserVerificationRequirement,
|
||||
pub resident_key_req: ResidentKeyRequirement,
|
||||
pub extensions: MakeCredentialsExtensions,
|
||||
pub pin: Option<Pin>,
|
||||
pub use_ctap1_fallback: bool,
|
||||
|
|
@ -37,7 +35,8 @@ pub struct SignArgs {
|
|||
pub origin: String,
|
||||
pub relying_party_id: String,
|
||||
pub allow_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
pub options: GetAssertionOptions,
|
||||
pub user_verification_req: UserVerificationRequirement,
|
||||
pub user_presence_req: bool,
|
||||
pub extensions: GetAssertionExtensions,
|
||||
pub pin: Option<Pin>,
|
||||
pub alternate_rp_id: Option<String>,
|
||||
|
|
@ -85,6 +84,12 @@ pub trait AuthenticatorTransport {
|
|||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
) -> crate::Result<()>;
|
||||
fn manage(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
pub struct AuthenticatorService {
|
||||
|
|
@ -288,6 +293,39 @@ impl AuthenticatorService {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn manage(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
) -> crate::Result<()> {
|
||||
let iterable_transports = self.transports.clone();
|
||||
if iterable_transports.is_empty() {
|
||||
return Err(AuthenticatorError::NoConfiguredTransports);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Manage called with {} transports, iterable is {}",
|
||||
self.transports.len(),
|
||||
iterable_transports.len()
|
||||
);
|
||||
|
||||
for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
|
||||
let mut transports_to_cancel = iterable_transports.clone();
|
||||
transports_to_cancel.remove(idx);
|
||||
|
||||
debug!("reset transports_to_cancel {}", transports_to_cancel.len());
|
||||
|
||||
transport_mutex.lock().unwrap().manage(
|
||||
timeout,
|
||||
status.clone(),
|
||||
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -298,7 +336,9 @@ impl AuthenticatorService {
|
|||
mod tests {
|
||||
use super::{AuthenticatorService, AuthenticatorTransport, Pin, RegisterArgs, SignArgs};
|
||||
use crate::consts::{Capability, PARAMETER_SIZE};
|
||||
use crate::ctap2::server::{RelyingParty, User};
|
||||
use crate::ctap2::server::{
|
||||
RelyingParty, ResidentKeyRequirement, User, UserVerificationRequirement,
|
||||
};
|
||||
use crate::statecallback::StateCallback;
|
||||
use crate::{RegisterResult, SignResult, StatusUpdate};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
|
@ -401,6 +441,15 @@ mod tests {
|
|||
) -> crate::Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn manage(
|
||||
&mut self,
|
||||
_timeout: u64,
|
||||
_status: Sender<crate::StatusUpdate>,
|
||||
_callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
) -> crate::Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_challenge() -> [u8; PARAMETER_SIZE] {
|
||||
|
|
@ -432,7 +481,8 @@ mod tests {
|
|||
},
|
||||
pub_cred_params: vec![],
|
||||
exclude_list: vec![],
|
||||
options: Default::default(),
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
resident_key_req: ResidentKeyRequirement::Preferred,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
use_ctap1_fallback: false,
|
||||
|
|
@ -453,7 +503,8 @@ mod tests {
|
|||
origin: "example.com".to_string(),
|
||||
relying_party_id: "example.com".to_string(),
|
||||
allow_list: vec![],
|
||||
options: Default::default(),
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
user_presence_req: true,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
alternate_rp_id: None,
|
||||
|
|
@ -511,7 +562,8 @@ mod tests {
|
|||
},
|
||||
pub_cred_params: vec![],
|
||||
exclude_list: vec![],
|
||||
options: Default::default(),
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
resident_key_req: ResidentKeyRequirement::Preferred,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
use_ctap1_fallback: false,
|
||||
|
|
@ -555,7 +607,8 @@ mod tests {
|
|||
origin: "example.com".to_string(),
|
||||
relying_party_id: "example.com".to_string(),
|
||||
allow_list: vec![],
|
||||
options: Default::default(),
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
user_presence_req: true,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
alternate_rp_id: None,
|
||||
|
|
@ -609,7 +662,8 @@ mod tests {
|
|||
},
|
||||
pub_cred_params: vec![],
|
||||
exclude_list: vec![],
|
||||
options: Default::default(),
|
||||
user_verification_req: UserVerificationRequirement::Preferred,
|
||||
resident_key_req: ResidentKeyRequirement::Preferred,
|
||||
extensions: Default::default(),
|
||||
pin: None,
|
||||
use_ctap1_fallback: false,
|
||||
|
|
|
|||
|
|
@ -362,6 +362,15 @@ pub struct PinUvAuthParam {
|
|||
pin_auth: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PinUvAuthParam {
|
||||
pub(crate) fn create_empty() -> Self {
|
||||
Self {
|
||||
pin_protocol: PinUvAuthProtocol(Box::new(PinUvAuth1 {})),
|
||||
pin_auth: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PinUvAuthParam {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ impl AsRef<[u8]> for EncryptedPinToken {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Pin(String);
|
||||
|
||||
impl fmt::Debug for Pin {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ use crate::ctap2::client_data::ClientDataHash;
|
|||
use crate::ctap2::commands::client_pin::Pin;
|
||||
use crate::ctap2::commands::get_next_assertion::GetNextAssertion;
|
||||
use crate::ctap2::commands::make_credentials::UserVerification;
|
||||
use crate::ctap2::server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper, RpIdHash, User};
|
||||
use crate::ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement,
|
||||
};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::FidoDevice;
|
||||
|
|
@ -218,7 +220,6 @@ pub struct GetAssertion {
|
|||
|
||||
// This is used to implement the FIDO AppID extension.
|
||||
pub(crate) alternate_rp_id: Option<String>,
|
||||
pub(crate) use_ctap1_fallback: bool,
|
||||
}
|
||||
|
||||
impl GetAssertion {
|
||||
|
|
@ -230,9 +231,8 @@ impl GetAssertion {
|
|||
extensions: GetAssertionExtensions,
|
||||
pin: Option<Pin>,
|
||||
alternate_rp_id: Option<String>,
|
||||
use_ctap1_fallback: bool,
|
||||
) -> Result<Self, HIDError> {
|
||||
Ok(Self {
|
||||
) -> Self {
|
||||
Self {
|
||||
client_data_hash,
|
||||
rp,
|
||||
allow_list,
|
||||
|
|
@ -241,8 +241,7 @@ impl GetAssertion {
|
|||
pin,
|
||||
pin_uv_auth_param: None,
|
||||
alternate_rp_id,
|
||||
use_ctap1_fallback,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -275,10 +274,6 @@ impl PinUvAuthCommand for GetAssertion {
|
|||
self.options.user_verification = uv;
|
||||
}
|
||||
|
||||
fn get_uv_option(&mut self) -> Option<bool> {
|
||||
self.options.user_verification
|
||||
}
|
||||
|
||||
fn get_rp_id(&self) -> Option<&String> {
|
||||
match &self.rp {
|
||||
// CTAP1 case: We only have the hash, not the entire RpID
|
||||
|
|
@ -287,24 +282,18 @@ impl PinUvAuthCommand for GetAssertion {
|
|||
}
|
||||
}
|
||||
|
||||
fn can_skip_user_verification(&mut self, info: &AuthenticatorInfo) -> bool {
|
||||
fn can_skip_user_verification(
|
||||
&mut self,
|
||||
info: &AuthenticatorInfo,
|
||||
uv_req: UserVerificationRequirement,
|
||||
) -> bool {
|
||||
let supports_uv = info.options.user_verification == Some(true);
|
||||
let pin_configured = info.options.client_pin == Some(true);
|
||||
let device_protected = supports_uv || pin_configured;
|
||||
let uv_preferred_or_required = self.get_uv_option() != Some(false);
|
||||
let uv_discouraged = uv_req == UserVerificationRequirement::Discouraged;
|
||||
let always_uv = info.options.always_uv == Some(true);
|
||||
|
||||
if always_uv || (device_protected && uv_preferred_or_required) {
|
||||
// If the token is protected AND the RP doesn't specifically discourage UV, we have to use it
|
||||
self.set_uv_option(Some(true));
|
||||
false
|
||||
} else {
|
||||
// "[..] the Relying Party does not wish to require user verification (e.g., by setting options.userVerification
|
||||
// to "discouraged" in the WebAuthn API), the platform invokes the authenticatorGetAssertion operation using
|
||||
// the marshalled input parameters along with an absent "uv" option key."
|
||||
self.set_uv_option(None);
|
||||
true
|
||||
}
|
||||
!always_uv && (!device_protected || uv_discouraged)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -730,9 +719,7 @@ pub mod test {
|
|||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("Failed to create GetAssertion");
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap();
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
|
|
@ -933,9 +920,7 @@ pub mod test {
|
|||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("Failed to create GetAssertion");
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it)
|
||||
// channel id
|
||||
let mut cid = [0u8; 4];
|
||||
|
|
@ -1022,9 +1007,7 @@ pub mod test {
|
|||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("Failed to create GetAssertion");
|
||||
);
|
||||
|
||||
let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it)
|
||||
// channel id
|
||||
|
|
|
|||
|
|
@ -62,17 +62,17 @@ fn true_val() -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub(crate) struct AuthenticatorOptions {
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)]
|
||||
pub struct AuthenticatorOptions {
|
||||
/// Indicates that the device is attached to the client and therefore can’t
|
||||
/// be removed and used on another client.
|
||||
#[serde(rename = "plat", default)]
|
||||
pub(crate) platform_device: bool,
|
||||
pub platform_device: bool,
|
||||
/// Indicates that the device is capable of storing keys on the device
|
||||
/// itself and therefore can satisfy the authenticatorGetAssertion request
|
||||
/// with allowList parameter not specified or empty.
|
||||
#[serde(rename = "rk", default)]
|
||||
pub(crate) resident_key: bool,
|
||||
pub resident_key: bool,
|
||||
|
||||
/// Client PIN:
|
||||
/// If present and set to true, it indicates that the device is capable of
|
||||
|
|
@ -83,11 +83,11 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// PIN from the client.
|
||||
/// Client PIN is one of the ways to do user verification.
|
||||
#[serde(rename = "clientPin")]
|
||||
pub(crate) client_pin: Option<bool>,
|
||||
pub client_pin: Option<bool>,
|
||||
|
||||
/// Indicates that the device is capable of testing user presence.
|
||||
#[serde(rename = "up", default = "true_val")]
|
||||
pub(crate) user_presence: bool,
|
||||
pub user_presence: bool,
|
||||
|
||||
/// Indicates that the device is capable of verifying the user within
|
||||
/// itself. For example, devices with UI, biometrics fall into this
|
||||
|
|
@ -107,7 +107,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
// TODO(MS): My Token (key-ID FIDO2) does return Some(false) here, even though
|
||||
// it has no built-in verification method. Not to be trusted...
|
||||
#[serde(rename = "uv")]
|
||||
pub(crate) user_verification: Option<bool>,
|
||||
pub user_verification: Option<bool>,
|
||||
|
||||
// ----------------------------------------------------
|
||||
// CTAP 2.1 options
|
||||
|
|
@ -124,7 +124,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// getPinUvAuthTokenUsingPinWithPermissions and getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// subcommands.
|
||||
#[serde(rename = "pinUvAuthToken")]
|
||||
pub(crate) pin_uv_auth_token: Option<bool>,
|
||||
pub pin_uv_auth_token: Option<bool>,
|
||||
|
||||
/// If this noMcGaPermissionsWithClientPin is:
|
||||
/// present and set to true
|
||||
|
|
@ -141,7 +141,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// Note: noMcGaPermissionsWithClientPin MUST only be present if the
|
||||
/// clientPin option ID is present.
|
||||
#[serde(rename = "noMcGaPermissionsWithClientPin")]
|
||||
pub(crate) no_mc_ga_permissions_with_client_pin: Option<bool>,
|
||||
pub no_mc_ga_permissions_with_client_pin: Option<bool>,
|
||||
|
||||
/// If largeBlobs is:
|
||||
/// present and set to true
|
||||
|
|
@ -149,7 +149,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// present and set to false, or absent.
|
||||
/// The authenticatorLargeBlobs command is NOT supported.
|
||||
#[serde(rename = "largeBlobs")]
|
||||
pub(crate) large_blobs: Option<bool>,
|
||||
pub large_blobs: Option<bool>,
|
||||
|
||||
/// Enterprise Attestation feature support:
|
||||
/// If ep is:
|
||||
|
|
@ -162,7 +162,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// Absent
|
||||
/// The Enterprise Attestation feature is NOT supported.
|
||||
#[serde(rename = "ep")]
|
||||
pub(crate) ep: Option<bool>,
|
||||
pub ep: Option<bool>,
|
||||
|
||||
/// If bioEnroll is:
|
||||
/// present and set to true
|
||||
|
|
@ -174,7 +174,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// absent
|
||||
/// the authenticatorBioEnrollment commands are NOT supported.
|
||||
#[serde(rename = "bioEnroll")]
|
||||
pub(crate) bio_enroll: Option<bool>,
|
||||
pub bio_enroll: Option<bool>,
|
||||
|
||||
/// "FIDO_2_1_PRE" Prototype Credential management support:
|
||||
/// If userVerificationMgmtPreview is:
|
||||
|
|
@ -187,7 +187,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// absent
|
||||
/// the Prototype authenticatorBioEnrollment (0x41) commands are not supported.
|
||||
#[serde(rename = "userVerificationMgmtPreview")]
|
||||
pub(crate) user_verification_mgmt_preview: Option<bool>,
|
||||
pub user_verification_mgmt_preview: Option<bool>,
|
||||
|
||||
/// getPinUvAuthTokenUsingUvWithPermissions support for requesting the be permission:
|
||||
/// This option ID MUST only be present if bioEnroll is also present.
|
||||
|
|
@ -199,7 +199,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// requesting the be permission when invoking getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// is NOT supported.
|
||||
#[serde(rename = "uvBioEnroll")]
|
||||
pub(crate) uv_bio_enroll: Option<bool>,
|
||||
pub uv_bio_enroll: Option<bool>,
|
||||
|
||||
/// authenticatorConfig command support:
|
||||
/// If authnrCfg is:
|
||||
|
|
@ -208,7 +208,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// present and set to false, or absent.
|
||||
/// the authenticatorConfig command is NOT supported.
|
||||
#[serde(rename = "authnrCfg")]
|
||||
pub(crate) authnr_cfg: Option<bool>,
|
||||
pub authnr_cfg: Option<bool>,
|
||||
|
||||
/// getPinUvAuthTokenUsingUvWithPermissions support for requesting the acfg permission:
|
||||
/// This option ID MUST only be present if authnrCfg is also present.
|
||||
|
|
@ -220,7 +220,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// requesting the acfg permission when invoking getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// is NOT supported.
|
||||
#[serde(rename = "uvAcfg")]
|
||||
pub(crate) uv_acfg: Option<bool>,
|
||||
pub uv_acfg: Option<bool>,
|
||||
|
||||
/// Credential management support:
|
||||
/// If credMgmt is:
|
||||
|
|
@ -229,7 +229,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// present and set to false, or absent.
|
||||
/// the authenticatorCredentialManagement command is NOT supported.
|
||||
#[serde(rename = "credMgmt")]
|
||||
pub(crate) cred_mgmt: Option<bool>,
|
||||
pub cred_mgmt: Option<bool>,
|
||||
|
||||
/// "FIDO_2_1_PRE" Prototype Credential management support:
|
||||
/// If credentialMgmtPreview is:
|
||||
|
|
@ -238,7 +238,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// present and set to false, or absent.
|
||||
/// the Prototype authenticatorCredentialManagement (0x41) command is NOT supported.
|
||||
#[serde(rename = "credentialMgmtPreview")]
|
||||
pub(crate) credential_mgmt_preview: Option<bool>,
|
||||
pub credential_mgmt_preview: Option<bool>,
|
||||
|
||||
/// Support for the Set Minimum PIN Length feature.
|
||||
/// If setMinPINLength is:
|
||||
|
|
@ -248,7 +248,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// the setMinPINLength subcommand is NOT supported.
|
||||
/// Note: setMinPINLength MUST only be present if the clientPin option ID is present.
|
||||
#[serde(rename = "setMinPINLength")]
|
||||
pub(crate) set_min_pin_length: Option<bool>,
|
||||
pub set_min_pin_length: Option<bool>,
|
||||
|
||||
/// Support for making non-discoverable credentials without requiring User Verification.
|
||||
/// If makeCredUvNotRqd is:
|
||||
|
|
@ -261,7 +261,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// for the authenticatorMakeCredential command.
|
||||
/// Authenticators SHOULD include this option with the value true.
|
||||
#[serde(rename = "makeCredUvNotRqd")]
|
||||
pub(crate) make_cred_uv_not_rqd: Option<bool>,
|
||||
pub make_cred_uv_not_rqd: Option<bool>,
|
||||
|
||||
/// Support for the Always Require User Verification feature:
|
||||
/// If alwaysUv is
|
||||
|
|
@ -274,7 +274,7 @@ pub(crate) struct AuthenticatorOptions {
|
|||
/// Note: If the alwaysUv option ID is present and true the authenticator MUST set the value
|
||||
/// of makeCredUvNotRqd to false.
|
||||
#[serde(rename = "alwaysUv")]
|
||||
pub(crate) always_uv: Option<bool>,
|
||||
pub always_uv: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for AuthenticatorOptions {
|
||||
|
|
@ -312,30 +312,30 @@ pub enum AuthenticatorVersion {
|
|||
FIDO_2_1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct AuthenticatorInfo {
|
||||
pub(crate) versions: Vec<AuthenticatorVersion>,
|
||||
pub(crate) extensions: Vec<String>,
|
||||
pub(crate) aaguid: AAGuid,
|
||||
pub(crate) options: AuthenticatorOptions,
|
||||
pub(crate) max_msg_size: Option<usize>,
|
||||
pub(crate) pin_protocols: Vec<u64>,
|
||||
pub versions: Vec<AuthenticatorVersion>,
|
||||
pub extensions: Vec<String>,
|
||||
pub aaguid: AAGuid,
|
||||
pub options: AuthenticatorOptions,
|
||||
pub max_msg_size: Option<usize>,
|
||||
pub pin_protocols: Vec<u64>,
|
||||
// CTAP 2.1
|
||||
pub(crate) max_credential_count_in_list: Option<usize>,
|
||||
pub(crate) max_credential_id_length: Option<usize>,
|
||||
pub(crate) transports: Option<Vec<String>>,
|
||||
pub(crate) algorithms: Option<Vec<PublicKeyCredentialParameters>>,
|
||||
pub(crate) max_ser_large_blob_array: Option<u64>,
|
||||
pub(crate) force_pin_change: Option<bool>,
|
||||
pub(crate) min_pin_length: Option<u64>,
|
||||
pub(crate) firmware_version: Option<u64>,
|
||||
pub(crate) max_cred_blob_length: Option<u64>,
|
||||
pub(crate) max_rpids_for_set_min_pin_length: Option<u64>,
|
||||
pub(crate) preferred_platform_uv_attempts: Option<u64>,
|
||||
pub(crate) uv_modality: Option<u64>,
|
||||
pub(crate) certifications: Option<BTreeMap<String, u64>>,
|
||||
pub(crate) remaining_discoverable_credentials: Option<u64>,
|
||||
pub(crate) vendor_prototype_config_commands: Option<Vec<u64>>,
|
||||
pub max_credential_count_in_list: Option<usize>,
|
||||
pub max_credential_id_length: Option<usize>,
|
||||
pub transports: Option<Vec<String>>,
|
||||
pub algorithms: Option<Vec<PublicKeyCredentialParameters>>,
|
||||
pub max_ser_large_blob_array: Option<u64>,
|
||||
pub force_pin_change: Option<bool>,
|
||||
pub min_pin_length: Option<u64>,
|
||||
pub firmware_version: Option<u64>,
|
||||
pub max_cred_blob_length: Option<u64>,
|
||||
pub max_rpids_for_set_min_pin_length: Option<u64>,
|
||||
pub preferred_platform_uv_attempts: Option<u64>,
|
||||
pub uv_modality: Option<u64>,
|
||||
pub certifications: Option<BTreeMap<String, u64>>,
|
||||
pub remaining_discoverable_credentials: Option<u64>,
|
||||
pub vendor_prototype_config_commands: Option<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl AuthenticatorInfo {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::ctap2::commands::client_pin::Pin;
|
|||
use crate::ctap2::commands::get_assertion::CheckKeyHandle;
|
||||
use crate::ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
RelyingPartyWrapper, RpIdHash, User,
|
||||
RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement,
|
||||
};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::{
|
||||
|
|
@ -174,7 +174,6 @@ pub struct MakeCredentials {
|
|||
pub(crate) pin: Option<Pin>,
|
||||
pub(crate) pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
pub(crate) enterprise_attestation: Option<u64>,
|
||||
pub(crate) use_ctap1_fallback: bool,
|
||||
}
|
||||
|
||||
impl MakeCredentials {
|
||||
|
|
@ -188,9 +187,8 @@ impl MakeCredentials {
|
|||
options: MakeCredentialsOptions,
|
||||
extensions: MakeCredentialsExtensions,
|
||||
pin: Option<Pin>,
|
||||
use_ctap1_fallback: bool,
|
||||
) -> Result<Self, HIDError> {
|
||||
Ok(Self {
|
||||
) -> Self {
|
||||
Self {
|
||||
client_data_hash,
|
||||
rp,
|
||||
user,
|
||||
|
|
@ -201,8 +199,7 @@ impl MakeCredentials {
|
|||
pin,
|
||||
pin_uv_auth_param: None,
|
||||
enterprise_attestation: None,
|
||||
use_ctap1_fallback,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,10 +232,6 @@ impl PinUvAuthCommand for MakeCredentials {
|
|||
self.options.user_verification = uv;
|
||||
}
|
||||
|
||||
fn get_uv_option(&mut self) -> Option<bool> {
|
||||
self.options.user_verification
|
||||
}
|
||||
|
||||
fn get_rp_id(&self) -> Option<&String> {
|
||||
match &self.rp {
|
||||
// CTAP1 case: We only have the hash, not the entire RpID
|
||||
|
|
@ -247,7 +240,11 @@ impl PinUvAuthCommand for MakeCredentials {
|
|||
}
|
||||
}
|
||||
|
||||
fn can_skip_user_verification(&mut self, info: &AuthenticatorInfo) -> bool {
|
||||
fn can_skip_user_verification(
|
||||
&mut self,
|
||||
info: &AuthenticatorInfo,
|
||||
uv_req: UserVerificationRequirement,
|
||||
) -> bool {
|
||||
// TODO(MS): Handle here the case where we NEED a UV, the device supports PINs, but hasn't set a PIN.
|
||||
// For this, the user has to be prompted to set a PIN first (see https://github.com/mozilla/authenticator-rs/issues/223)
|
||||
|
||||
|
|
@ -260,27 +257,13 @@ impl PinUvAuthCommand for MakeCredentials {
|
|||
// For CTAP2.0, UV is always required when doing MakeCredential
|
||||
let always_uv = info.options.always_uv == Some(true)
|
||||
|| info.max_supported_version() == AuthenticatorVersion::FIDO_2_0;
|
||||
let uv_discouraged = self.get_uv_option() == Some(false);
|
||||
let uv_discouraged = uv_req == UserVerificationRequirement::Discouraged;
|
||||
|
||||
// CTAP 2.1 authenticators can allow MakeCredential without PinUvAuth,
|
||||
// but that is only relevant, if RP also discourages UV.
|
||||
let can_make_cred_without_uv = make_cred_uv_not_required && uv_discouraged;
|
||||
|
||||
if always_uv || (device_protected && !can_make_cred_without_uv) {
|
||||
// If the token is protected, we have to require UV anyways
|
||||
self.set_uv_option(Some(true));
|
||||
false
|
||||
} else {
|
||||
// "[..] the Relying Party wants to create a non-discoverable credential and not require user verification
|
||||
// (e.g., by setting options.authenticatorSelection.userVerification to "discouraged" in the WebAuthn API),
|
||||
// the platform invokes the authenticatorMakeCredential operation using the marshalled input parameters along
|
||||
// with the "uv" option key set to false and terminate these steps."
|
||||
// Note: This is basically a no-op right now, since we use `get_uv_option() == Some(false)`, to determine if
|
||||
// the RP is discouraging UV. But we may change that part of the API in the future, so better to be
|
||||
// explicit here.
|
||||
self.set_uv_option(Some(false));
|
||||
true
|
||||
}
|
||||
!always_uv && (!device_protected || can_make_cred_without_uv)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,14 +336,6 @@ impl RequestCtap1 for MakeCredentials {
|
|||
where
|
||||
Dev: io::Read + io::Write + fmt::Debug + FidoDevice,
|
||||
{
|
||||
// TODO(MS): Mandatory sanity checks are missing:
|
||||
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#u2f-authenticatorMakeCredential-interoperability
|
||||
// If any of the below conditions is not true, platform errors out with
|
||||
// CTAP2_ERR_UNSUPPORTED_OPTION.
|
||||
// * pubKeyCredParams must use the ES256 algorithm (-7).
|
||||
// * Options must not include "rk" set to true.
|
||||
// * Options must not include "uv" set to true.
|
||||
|
||||
let is_already_registered = self
|
||||
.exclude_list
|
||||
.iter()
|
||||
|
|
@ -469,7 +444,7 @@ impl RequestCtap2 for MakeCredentials {
|
|||
}
|
||||
|
||||
pub(crate) fn dummy_make_credentials_cmd() -> Result<MakeCredentials, HIDError> {
|
||||
MakeCredentials::new(
|
||||
let mut req = MakeCredentials::new(
|
||||
CollectedClientData {
|
||||
webauthn_type: WebauthnType::Create,
|
||||
challenge: Challenge::new(vec![0, 1, 2, 3, 4]),
|
||||
|
|
@ -477,8 +452,7 @@ pub(crate) fn dummy_make_credentials_cmd() -> Result<MakeCredentials, HIDError>
|
|||
cross_origin: false,
|
||||
token_binding: None,
|
||||
}
|
||||
.hash()
|
||||
.expect("failed to serialize client data"),
|
||||
.hash()?,
|
||||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: String::from("make.me.blink"),
|
||||
..Default::default()
|
||||
|
|
@ -495,8 +469,12 @@ pub(crate) fn dummy_make_credentials_cmd() -> Result<MakeCredentials, HIDError>
|
|||
MakeCredentialsOptions::default(),
|
||||
MakeCredentialsExtensions::default(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
);
|
||||
// Using a zero-length pinAuth will trigger the device to blink.
|
||||
// For CTAP1, this gets ignored anyways and we do a 'normal' register
|
||||
// command, which also just blinks.
|
||||
req.pin_uv_auth_param = Some(PinUvAuthParam::create_empty());
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -648,9 +626,7 @@ pub mod test {
|
|||
},
|
||||
Default::default(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("Failed to create MakeCredentials");
|
||||
);
|
||||
|
||||
let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it)
|
||||
let req_serialized = req
|
||||
|
|
@ -708,9 +684,7 @@ pub mod test {
|
|||
},
|
||||
Default::default(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.expect("Failed to create MakeCredentials");
|
||||
);
|
||||
|
||||
let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it)
|
||||
let (req_serialized, _) = req
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::crypto::{CryptoError, PinUvAuthToken};
|
|||
|
||||
use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, Pin, PinError};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::ctap2::server::UserVerificationRequirement;
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::FidoDevice;
|
||||
|
|
@ -121,9 +122,12 @@ pub(crate) trait PinUvAuthCommand: RequestCtap2 {
|
|||
pin_uv_auth_token: Option<PinUvAuthToken>,
|
||||
) -> Result<(), AuthenticatorError>;
|
||||
fn set_uv_option(&mut self, uv: Option<bool>);
|
||||
fn get_uv_option(&mut self) -> Option<bool>;
|
||||
fn get_rp_id(&self) -> Option<&String>;
|
||||
fn can_skip_user_verification(&mut self, info: &AuthenticatorInfo) -> bool;
|
||||
fn can_skip_user_verification(
|
||||
&mut self,
|
||||
info: &AuthenticatorInfo,
|
||||
uv_req: UserVerificationRequirement,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
pub(crate) fn repackage_pin_errors<D: FidoDevice>(
|
||||
|
|
|
|||
|
|
@ -300,6 +300,20 @@ impl From<&KeyHandle> for PublicKeyCredentialDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ResidentKeyRequirement {
|
||||
Discouraged,
|
||||
Preferred,
|
||||
Required,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum UserVerificationRequirement {
|
||||
Discouraged,
|
||||
Preferred,
|
||||
Required,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
|
|
|
|||
6
third_party/rust/authenticator/src/errors.rs
vendored
6
third_party/rust/authenticator/src/errors.rs
vendored
|
|
@ -13,8 +13,12 @@ use std::sync::mpsc;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum UnsupportedOption {
|
||||
MaxPinLength,
|
||||
EmptyAllowList,
|
||||
HmacSecret,
|
||||
MaxPinLength,
|
||||
PubCredParams,
|
||||
ResidentKey,
|
||||
UserVerification,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
1
third_party/rust/authenticator/src/lib.rs
vendored
1
third_party/rust/authenticator/src/lib.rs
vendored
|
|
@ -40,6 +40,7 @@ pub use ctap2::attestation::AttestationObject;
|
|||
pub use ctap2::client_data::CollectedClientData;
|
||||
pub use ctap2::commands::client_pin::{Pin, PinError};
|
||||
pub use ctap2::commands::get_assertion::Assertion;
|
||||
pub use ctap2::commands::get_info::AuthenticatorInfo;
|
||||
pub use ctap2::AssertionObject;
|
||||
|
||||
pub mod errors;
|
||||
|
|
|
|||
80
third_party/rust/authenticator/src/manager.rs
vendored
80
third_party/rust/authenticator/src/manager.rs
vendored
|
|
@ -4,11 +4,6 @@
|
|||
|
||||
use crate::authenticatorservice::AuthenticatorTransport;
|
||||
use crate::authenticatorservice::{RegisterArgs, SignArgs};
|
||||
|
||||
use crate::ctap2::client_data::ClientDataHash;
|
||||
use crate::ctap2::commands::get_assertion::GetAssertion;
|
||||
use crate::ctap2::commands::make_credentials::MakeCredentials;
|
||||
use crate::ctap2::server::{RelyingParty, RelyingPartyWrapper};
|
||||
use crate::errors::*;
|
||||
use crate::statecallback::StateCallback;
|
||||
use crate::statemachine::StateMachine;
|
||||
|
|
@ -21,13 +16,13 @@ use std::time::Duration;
|
|||
enum QueueAction {
|
||||
Register {
|
||||
timeout: u64,
|
||||
make_credentials: MakeCredentials,
|
||||
register_args: RegisterArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::RegisterResult>>,
|
||||
},
|
||||
Sign {
|
||||
timeout: u64,
|
||||
get_assertion: GetAssertion,
|
||||
sign_args: SignArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::SignResult>>,
|
||||
},
|
||||
|
|
@ -43,6 +38,11 @@ enum QueueAction {
|
|||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
},
|
||||
InteractiveManagement {
|
||||
timeout: u64,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Manager {
|
||||
|
|
@ -62,22 +62,22 @@ impl Manager {
|
|||
match rx.recv_timeout(Duration::from_millis(50)) {
|
||||
Ok(QueueAction::Register {
|
||||
timeout,
|
||||
make_credentials,
|
||||
register_args,
|
||||
status,
|
||||
callback,
|
||||
}) => {
|
||||
// This must not block, otherwise we can't cancel.
|
||||
sm.register(timeout, make_credentials, status, callback);
|
||||
sm.register(timeout, register_args, status, callback);
|
||||
}
|
||||
|
||||
Ok(QueueAction::Sign {
|
||||
timeout,
|
||||
get_assertion,
|
||||
sign_args,
|
||||
status,
|
||||
callback,
|
||||
}) => {
|
||||
// This must not block, otherwise we can't cancel.
|
||||
sm.sign(timeout, get_assertion, status, callback);
|
||||
sm.sign(timeout, sign_args, status, callback);
|
||||
}
|
||||
|
||||
Ok(QueueAction::Cancel) => {
|
||||
|
|
@ -105,6 +105,15 @@ impl Manager {
|
|||
sm.set_pin(timeout, new_pin, status, callback);
|
||||
}
|
||||
|
||||
Ok(QueueAction::InteractiveManagement {
|
||||
timeout,
|
||||
status,
|
||||
callback,
|
||||
}) => {
|
||||
// Manage token interactively
|
||||
sm.manage(timeout, status, callback);
|
||||
}
|
||||
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
break;
|
||||
}
|
||||
|
|
@ -131,26 +140,13 @@ impl AuthenticatorTransport for Manager {
|
|||
fn register(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
args: RegisterArgs,
|
||||
register_args: RegisterArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::RegisterResult>>,
|
||||
) -> Result<(), AuthenticatorError> {
|
||||
let make_credentials = MakeCredentials::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(args.relying_party),
|
||||
Some(args.user),
|
||||
args.pub_cred_params,
|
||||
args.exclude_list,
|
||||
args.options,
|
||||
args.extensions,
|
||||
args.pin,
|
||||
args.use_ctap1_fallback,
|
||||
// pin_auth will be filled in Statemachine, once we have a device
|
||||
)?;
|
||||
|
||||
let action = QueueAction::Register {
|
||||
timeout,
|
||||
make_credentials,
|
||||
register_args,
|
||||
status,
|
||||
callback,
|
||||
};
|
||||
|
|
@ -160,28 +156,13 @@ impl AuthenticatorTransport for Manager {
|
|||
fn sign(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
args: SignArgs,
|
||||
sign_args: SignArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::SignResult>>,
|
||||
) -> crate::Result<()> {
|
||||
let get_assertion = GetAssertion::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: args.relying_party_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
}),
|
||||
args.allow_list,
|
||||
args.options,
|
||||
args.extensions,
|
||||
args.pin,
|
||||
args.alternate_rp_id,
|
||||
args.use_ctap1_fallback,
|
||||
)?;
|
||||
|
||||
let action = QueueAction::Sign {
|
||||
timeout,
|
||||
get_assertion,
|
||||
sign_args,
|
||||
status,
|
||||
callback,
|
||||
};
|
||||
|
|
@ -221,4 +202,17 @@ impl AuthenticatorTransport for Manager {
|
|||
callback,
|
||||
})?)
|
||||
}
|
||||
|
||||
fn manage(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
) -> Result<(), AuthenticatorError> {
|
||||
Ok(self.tx.send(QueueAction::InteractiveManagement {
|
||||
timeout,
|
||||
status,
|
||||
callback,
|
||||
})?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
714
third_party/rust/authenticator/src/statemachine.rs
vendored
714
third_party/rust/authenticator/src/statemachine.rs
vendored
|
|
@ -2,19 +2,26 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::authenticatorservice::{RegisterArgs, SignArgs};
|
||||
use crate::consts::PARAMETER_SIZE;
|
||||
use crate::crypto::COSEAlgorithm;
|
||||
use crate::ctap2::client_data::ClientDataHash;
|
||||
use crate::ctap2::commands::client_pin::{
|
||||
ChangeExistingPin, Pin, PinError, PinUvAuthTokenPermission, SetNewPin,
|
||||
};
|
||||
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult};
|
||||
use crate::ctap2::commands::make_credentials::{MakeCredentials, MakeCredentialsResult};
|
||||
use crate::ctap2::commands::get_assertion::{
|
||||
GetAssertion, GetAssertionOptions, GetAssertionResult,
|
||||
};
|
||||
use crate::ctap2::commands::make_credentials::{
|
||||
MakeCredentials, MakeCredentialsOptions, MakeCredentialsResult,
|
||||
};
|
||||
use crate::ctap2::commands::reset::Reset;
|
||||
use crate::ctap2::commands::{
|
||||
repackage_pin_errors, CommandError, PinUvAuthCommand, PinUvAuthResult, Request, StatusCode,
|
||||
};
|
||||
use crate::ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, RpIdHash,
|
||||
PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, ResidentKeyRequirement,
|
||||
RpIdHash, UserVerificationRequirement,
|
||||
};
|
||||
use crate::errors::{self, AuthenticatorError, UnsupportedOption};
|
||||
use crate::statecallback::StateCallback;
|
||||
|
|
@ -26,10 +33,10 @@ use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, Nonce};
|
|||
use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use crate::{
|
||||
send_status, AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags,
|
||||
SignResult, StatusPinUv, StatusUpdate,
|
||||
send_status, AuthenticatorTransports, InteractiveRequest, KeyHandle, RegisterFlags,
|
||||
RegisterResult, SignFlags, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::sync::mpsc::{channel, RecvError, RecvTimeoutError, Sender};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -75,6 +82,7 @@ impl StateMachine {
|
|||
fn init_and_select(
|
||||
info: DeviceBuildParameters,
|
||||
selector: &Sender<DeviceSelectorEvent>,
|
||||
status: &Sender<crate::StatusUpdate>,
|
||||
ctap2_only: bool,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Option<Device> {
|
||||
|
|
@ -101,22 +109,16 @@ impl StateMachine {
|
|||
return None;
|
||||
}
|
||||
|
||||
let write_only_clone = match dev.clone_device_as_write_only() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
// There is probably something seriously wrong here, if this happens.
|
||||
// So `NotAToken()` is probably too weak a response here.
|
||||
warn!("error while cloning device: {:?}", dev.id());
|
||||
selector
|
||||
.send(DeviceSelectorEvent::NotAToken(dev.id()))
|
||||
.ok()?;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let (tx, rx) = channel();
|
||||
selector
|
||||
.send(DeviceSelectorEvent::ImAToken((write_only_clone, tx)))
|
||||
.send(DeviceSelectorEvent::ImAToken((dev.id(), tx)))
|
||||
.ok()?;
|
||||
send_status(
|
||||
status,
|
||||
crate::StatusUpdate::DeviceAvailable {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
|
||||
// We can be cancelled from the user (through keep_alive()) or from the device selector
|
||||
// (through a DeviceCommand::Cancel on rx). We'll combine those signals into a single
|
||||
|
|
@ -125,29 +127,50 @@ impl StateMachine {
|
|||
|
||||
// Blocking recv. DeviceSelector will tell us what to do
|
||||
match rx.recv() {
|
||||
Ok(DeviceCommand::Blink) => match dev.block_and_blink(&keep_blinking) {
|
||||
BlinkResult::DeviceSelected => {
|
||||
// User selected us. Let DeviceSelector know, so it can cancel all other
|
||||
// outstanding open blink-requests.
|
||||
selector
|
||||
.send(DeviceSelectorEvent::SelectedToken(dev.id()))
|
||||
.ok()?;
|
||||
Ok(DeviceCommand::Blink) => {
|
||||
// Inform the user that there are multiple devices available.
|
||||
// NOTE: We'll send this once per device, so the recipient should be prepared
|
||||
// to receive this message multiple times.
|
||||
send_status(status, crate::StatusUpdate::SelectDeviceNotice);
|
||||
match dev.block_and_blink(&keep_blinking) {
|
||||
BlinkResult::DeviceSelected => {
|
||||
// User selected us. Let DeviceSelector know, so it can cancel all other
|
||||
// outstanding open blink-requests.
|
||||
selector
|
||||
.send(DeviceSelectorEvent::SelectedToken(dev.id()))
|
||||
.ok()?;
|
||||
|
||||
send_status(
|
||||
status,
|
||||
crate::StatusUpdate::DeviceSelected(dev.get_device_info()),
|
||||
);
|
||||
}
|
||||
BlinkResult::Cancelled => {
|
||||
info!("Device {:?} was not selected", dev.id());
|
||||
return None;
|
||||
}
|
||||
}
|
||||
BlinkResult::Cancelled => {
|
||||
info!("Device {:?} was not selected", dev.id());
|
||||
return None;
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(DeviceCommand::Cancel) => {
|
||||
info!("Device {:?} was not selected", dev.id());
|
||||
return None;
|
||||
}
|
||||
Ok(DeviceCommand::Removed) => {
|
||||
info!("Device {:?} was removed", dev.id());
|
||||
send_status(
|
||||
status,
|
||||
crate::StatusUpdate::DeviceUnavailable {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Ok(DeviceCommand::Continue) => {
|
||||
// Just continue
|
||||
send_status(
|
||||
status,
|
||||
crate::StatusUpdate::DeviceSelected(dev.get_device_info()),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Error when trying to receive messages from DeviceSelector! Exiting.");
|
||||
|
|
@ -178,9 +201,9 @@ impl StateMachine {
|
|||
}
|
||||
match rx.recv() {
|
||||
Ok(pin) => Ok(pin),
|
||||
Err(_) => {
|
||||
Err(RecvError) => {
|
||||
// recv() can only fail, if the other side is dropping the Sender.
|
||||
error!("Callback dropped the channel. Aborting.");
|
||||
info!("Callback dropped the channel. Aborting.");
|
||||
callback.call(Err(AuthenticatorError::CancelledByUser));
|
||||
Err(())
|
||||
}
|
||||
|
|
@ -195,7 +218,23 @@ impl StateMachine {
|
|||
dev: &mut Device,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
skip_uv: bool,
|
||||
uv_req: UserVerificationRequirement,
|
||||
) -> Result<PinUvAuthResult, AuthenticatorError> {
|
||||
// CTAP 2.1 is very specific that the request should either include pinUvAuthParam
|
||||
// OR uv=true, but not both at the same time. We now have to decide which (if either)
|
||||
// to send. We may omit both values. Will never send an explicit uv=false, because
|
||||
// a) this is the default, and
|
||||
// b) some CTAP 2.0 authenticators return UnsupportedOption when uv=false.
|
||||
|
||||
// We ensure both pinUvAuthParam and uv are not set to start.
|
||||
cmd.set_pin_uv_auth_param(None)?;
|
||||
cmd.set_uv_option(None);
|
||||
|
||||
// CTAP1/U2F-only devices do not support user verification, so we skip it
|
||||
if !dev.supports_ctap2() {
|
||||
return Ok(PinUvAuthResult::DeviceIsCtap1);
|
||||
}
|
||||
|
||||
let info = dev
|
||||
.get_authenticator_info()
|
||||
.ok_or(AuthenticatorError::HIDError(HIDError::DeviceNotInitialized))?;
|
||||
|
|
@ -213,16 +252,30 @@ impl StateMachine {
|
|||
|
||||
// Check if the combination of device-protection and request-options
|
||||
// are allowing for 'discouraged', meaning no auth required.
|
||||
if cmd.can_skip_user_verification(info) {
|
||||
if cmd.can_skip_user_verification(info, uv_req) {
|
||||
return Ok(PinUvAuthResult::NoAuthRequired);
|
||||
}
|
||||
|
||||
// Device does not support any auth-method
|
||||
if !pin_configured && !supports_uv {
|
||||
// We'll send it to the device anyways, and let it error out (or magically work)
|
||||
// Device does not support any (remaining) auth-method
|
||||
if (skip_uv || !supports_uv) && !supports_pin {
|
||||
if supports_uv && uv_req == UserVerificationRequirement::Required {
|
||||
// We should always set the uv option in the Required case, but the CTAP 2.1 spec
|
||||
// says 'Platforms MUST NOT include the "uv" option key if the authenticator does
|
||||
// not support built-in user verification.' This is to work around some CTAP 2.0
|
||||
// authenticators which incorrectly error out with CTAP2_ERR_UNSUPPORTED_OPTION
|
||||
// when the "uv" option is set. The RP that requested UV will (hopefully) reject our
|
||||
// response in the !supports_uv case.
|
||||
cmd.set_uv_option(Some(true));
|
||||
}
|
||||
return Ok(PinUvAuthResult::NoAuthTypeSupported);
|
||||
}
|
||||
|
||||
// Device supports PINs, but a PIN is not configured. Signal that we
|
||||
// can complete the operation if the user sets a PIN first.
|
||||
if (skip_uv || !supports_uv) && !pin_configured {
|
||||
return Err(AuthenticatorError::PinError(PinError::PinNotSet));
|
||||
}
|
||||
|
||||
let (res, pin_auth_token) = if info.options.pin_uv_auth_token == Some(true) {
|
||||
if !skip_uv && supports_uv {
|
||||
// CTAP 2.1 - UV
|
||||
|
|
@ -232,8 +285,12 @@ impl StateMachine {
|
|||
PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions,
|
||||
pin_auth_token,
|
||||
)
|
||||
} else if supports_pin && pin_configured {
|
||||
} else {
|
||||
// CTAP 2.1 - PIN
|
||||
// We did not take the `!skip_uv && supports_uv` branch, so we have
|
||||
// `(skip_uv || !supports_uv)`. Moreover we did not exit early in the
|
||||
// `(skip_uv || !supports_uv) && !pin_configured` case. So we have
|
||||
// `pin_configured`.
|
||||
let pin_auth_token = dev.get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
cmd.pin(),
|
||||
permission,
|
||||
|
|
@ -243,8 +300,6 @@ impl StateMachine {
|
|||
PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions,
|
||||
pin_auth_token,
|
||||
)
|
||||
} else {
|
||||
return Ok(PinUvAuthResult::NoAuthTypeSupported);
|
||||
}
|
||||
} else {
|
||||
// CTAP 2.0 fallback
|
||||
|
|
@ -257,6 +312,8 @@ impl StateMachine {
|
|||
if info.supports_hmac_secret() {
|
||||
let _shared_secret = dev.establish_shared_secret()?;
|
||||
}
|
||||
// CTAP 2.1, Section 6.1.1, Step 1.1.2.1.2.
|
||||
cmd.set_uv_option(Some(true));
|
||||
return Ok(PinUvAuthResult::UsingInternalUv);
|
||||
}
|
||||
|
||||
|
|
@ -266,10 +323,6 @@ impl StateMachine {
|
|||
|
||||
let pin_auth_token = pin_auth_token.map_err(|e| repackage_pin_errors(dev, e))?;
|
||||
cmd.set_pin_uv_auth_param(Some(pin_auth_token))?;
|
||||
// CTAP 2.0 spec is a bit vague here, but CTAP 2.1 is very specific, that the request
|
||||
// should either include pinAuth OR uv=true, but not both at the same time.
|
||||
// Do not set user_verification, if pinAuth is provided
|
||||
cmd.set_uv_option(None);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
|
@ -286,22 +339,15 @@ impl StateMachine {
|
|||
dev: &mut Device,
|
||||
mut skip_uv: bool,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
uv_req: UserVerificationRequirement,
|
||||
status: &Sender<StatusUpdate>,
|
||||
callback: &StateCallback<crate::Result<U>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
) -> Result<PinUvAuthResult, ()> {
|
||||
// Starting from a blank slate.
|
||||
cmd.set_pin_uv_auth_param(None).map_err(|_| ())?;
|
||||
|
||||
// CTAP1/U2F-only devices do not support PinUvAuth, so we skip it
|
||||
if !dev.supports_ctap2() {
|
||||
return Ok(PinUvAuthResult::DeviceIsCtap1);
|
||||
}
|
||||
|
||||
while alive() {
|
||||
debug!("-----------------------------------------------------------------");
|
||||
debug!("Getting pinUvAuthParam");
|
||||
match Self::get_pin_uv_auth_param(cmd, dev, permission, skip_uv) {
|
||||
match Self::get_pin_uv_auth_param(cmd, dev, permission, skip_uv, uv_req) {
|
||||
Ok(r) => {
|
||||
return Ok(r);
|
||||
}
|
||||
|
|
@ -378,21 +424,23 @@ impl StateMachine {
|
|||
pub fn register(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
params: MakeCredentials,
|
||||
args: RegisterArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::RegisterResult>>,
|
||||
) {
|
||||
if params.use_ctap1_fallback {
|
||||
if args.use_ctap1_fallback {
|
||||
/* Firefox uses this when security.webauthn.ctap2 is false. */
|
||||
let mut flags = RegisterFlags::empty();
|
||||
if params.options.resident_key == Some(true) {
|
||||
if args.resident_key_req == ResidentKeyRequirement::Required {
|
||||
flags |= RegisterFlags::REQUIRE_RESIDENT_KEY;
|
||||
}
|
||||
if params.options.user_verification == Some(true) {
|
||||
if args.user_verification_req == UserVerificationRequirement::Required {
|
||||
flags |= RegisterFlags::REQUIRE_USER_VERIFICATION;
|
||||
}
|
||||
let application = params.rp.hash().0.to_vec();
|
||||
let key_handles = params
|
||||
|
||||
let rp = RelyingPartyWrapper::Data(args.relying_party);
|
||||
let application = rp.hash().as_ref().to_vec();
|
||||
let key_handles = args
|
||||
.exclude_list
|
||||
.iter()
|
||||
.map(|cred_desc| KeyHandle {
|
||||
|
|
@ -400,7 +448,7 @@ impl StateMachine {
|
|||
transports: AuthenticatorTransports::empty(),
|
||||
})
|
||||
.collect();
|
||||
let challenge = params.client_data_hash;
|
||||
let challenge = ClientDataHash(args.client_data_hash);
|
||||
|
||||
self.legacy_register(
|
||||
flags,
|
||||
|
|
@ -422,7 +470,7 @@ impl StateMachine {
|
|||
cbc.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, false, alive) {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, false, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
|
|
@ -430,33 +478,72 @@ impl StateMachine {
|
|||
};
|
||||
|
||||
info!("Device {:?} continues with the register process", dev.id());
|
||||
// TODO(baloo): not sure about this, have to ask
|
||||
// We currently support none of the authenticator selection
|
||||
// criteria because we can't ask tokens whether they do support
|
||||
// those features. If flags are set, ignore all tokens for now.
|
||||
//
|
||||
// Technically, this is a ConstraintError because we shouldn't talk
|
||||
// to this authenticator in the first place. But the result is the
|
||||
// same anyway.
|
||||
//if !flags.is_empty() {
|
||||
// return;
|
||||
//}
|
||||
|
||||
// TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me
|
||||
// to modify "params" directly.
|
||||
let mut makecred = params.clone();
|
||||
// First check if extensions have been requested that are not supported by the device
|
||||
if let Some(true) = params.extensions.hmac_secret {
|
||||
if let Some(auth) = dev.get_authenticator_info() {
|
||||
if !auth.supports_hmac_secret() {
|
||||
// We need a copy of the arguments for this device
|
||||
let args = args.clone();
|
||||
|
||||
let mut options = MakeCredentialsOptions::default();
|
||||
|
||||
if let Some(info) = dev.get_authenticator_info() {
|
||||
// Check if extensions have been requested that are not supported by the device
|
||||
if let Some(true) = args.extensions.hmac_secret {
|
||||
if !info.supports_hmac_secret() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::HmacSecret,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set options based on the arguments and the device info.
|
||||
// The user verification option will be set in `determine_puap_if_needed`.
|
||||
options.resident_key = match args.resident_key_req {
|
||||
ResidentKeyRequirement::Required => Some(true),
|
||||
ResidentKeyRequirement::Preferred => {
|
||||
// Use a resident key if the authenticator supports it
|
||||
Some(info.options.resident_key)
|
||||
}
|
||||
ResidentKeyRequirement::Discouraged => Some(false),
|
||||
}
|
||||
} else {
|
||||
// Check that the request can be processed by a CTAP1 device.
|
||||
// See CTAP 2.1 Section 10.2. Some additional checks are performed in
|
||||
// MakeCredentials::RequestCtap1
|
||||
if args.resident_key_req == ResidentKeyRequirement::Required {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::ResidentKey,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
if args.user_verification_req == UserVerificationRequirement::Required {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::UserVerification,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
if !args
|
||||
.pub_cred_params
|
||||
.iter()
|
||||
.any(|x| x.alg == COSEAlgorithm::ES256)
|
||||
{
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::PubCredParams,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut makecred = MakeCredentials::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(args.relying_party),
|
||||
Some(args.user),
|
||||
args.pub_cred_params,
|
||||
args.exclude_list,
|
||||
options,
|
||||
args.extensions,
|
||||
args.pin,
|
||||
);
|
||||
|
||||
let mut skip_uv = false;
|
||||
while alive() {
|
||||
let pin_uv_auth_result = match Self::determine_puap_if_needed(
|
||||
|
|
@ -464,6 +551,7 @@ impl StateMachine {
|
|||
&mut dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::MakeCredential,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
|
|
@ -552,29 +640,30 @@ impl StateMachine {
|
|||
pub fn sign(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
params: GetAssertion,
|
||||
args: SignArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::SignResult>>,
|
||||
) {
|
||||
if params.use_ctap1_fallback {
|
||||
if args.use_ctap1_fallback {
|
||||
/* Firefox uses this when security.webauthn.ctap2 is false. */
|
||||
let flags = match params.options.user_verification {
|
||||
Some(true) => SignFlags::REQUIRE_USER_VERIFICATION,
|
||||
_ => SignFlags::empty(),
|
||||
};
|
||||
let mut app_ids = vec![params.rp.hash().0.to_vec()];
|
||||
if let Some(app_id) = params.alternate_rp_id {
|
||||
app_ids.push(
|
||||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: app_id,
|
||||
..Default::default()
|
||||
})
|
||||
.hash()
|
||||
.0
|
||||
.to_vec(),
|
||||
);
|
||||
let mut flags = SignFlags::empty();
|
||||
if args.user_verification_req == UserVerificationRequirement::Required {
|
||||
flags |= SignFlags::REQUIRE_USER_VERIFICATION;
|
||||
}
|
||||
let key_handles = params
|
||||
let mut app_ids = vec![];
|
||||
let rp_id = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: args.relying_party_id,
|
||||
..Default::default()
|
||||
});
|
||||
app_ids.push(rp_id.hash().as_ref().to_vec());
|
||||
if let Some(app_id) = args.alternate_rp_id {
|
||||
let app_id = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: app_id,
|
||||
..Default::default()
|
||||
});
|
||||
app_ids.push(app_id.hash().as_ref().to_vec());
|
||||
}
|
||||
let key_handles = args
|
||||
.allow_list
|
||||
.iter()
|
||||
.map(|cred_desc| KeyHandle {
|
||||
|
|
@ -582,7 +671,7 @@ impl StateMachine {
|
|||
transports: AuthenticatorTransports::empty(),
|
||||
})
|
||||
.collect();
|
||||
let challenge = params.client_data_hash;
|
||||
let challenge = ClientDataHash(args.client_data_hash);
|
||||
|
||||
self.legacy_sign(
|
||||
flags,
|
||||
|
|
@ -605,7 +694,7 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, false, alive) {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, false, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
|
|
@ -613,27 +702,61 @@ impl StateMachine {
|
|||
};
|
||||
|
||||
info!("Device {:?} continues with the signing process", dev.id());
|
||||
// TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me
|
||||
// to modify "params" directly.
|
||||
let mut getassertion = params.clone();
|
||||
// First check if extensions have been requested that are not supported by the device
|
||||
if params.extensions.hmac_secret.is_some() {
|
||||
if let Some(auth) = dev.get_authenticator_info() {
|
||||
if !auth.supports_hmac_secret() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::HmacSecret,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
|
||||
// We need a copy of the arguments for this device
|
||||
let args = args.clone();
|
||||
|
||||
if let Some(info) = dev.get_authenticator_info() {
|
||||
// Check if extensions have been requested that are not supported by the device
|
||||
if args.extensions.hmac_secret.is_some() && !info.supports_hmac_secret() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::HmacSecret,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Check that the request can be processed by a CTAP1 device.
|
||||
// See CTAP 2.1 Section 10.3. Some additional checks are performed in
|
||||
// GetAssertion::RequestCtap1
|
||||
if args.user_verification_req == UserVerificationRequirement::Required {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::UserVerification,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
if args.allow_list.is_empty() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::EmptyAllowList,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut get_assertion = GetAssertion::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: args.relying_party_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
}),
|
||||
args.allow_list,
|
||||
GetAssertionOptions {
|
||||
user_presence: Some(args.user_presence_req),
|
||||
user_verification: None,
|
||||
},
|
||||
args.extensions,
|
||||
args.pin,
|
||||
args.alternate_rp_id,
|
||||
);
|
||||
|
||||
let mut skip_uv = false;
|
||||
while alive() {
|
||||
let pin_uv_auth_result = match Self::determine_puap_if_needed(
|
||||
&mut getassertion,
|
||||
&mut get_assertion,
|
||||
&mut dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::GetAssertion,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
|
|
@ -645,7 +768,7 @@ impl StateMachine {
|
|||
};
|
||||
|
||||
// Third, use the shared secret in the extensions, if requested
|
||||
if let Some(extension) = getassertion.extensions.hmac_secret.as_mut() {
|
||||
if let Some(extension) = get_assertion.extensions.hmac_secret.as_mut() {
|
||||
if let Some(secret) = dev.get_shared_secret() {
|
||||
match extension.calculate(secret) {
|
||||
Ok(x) => x,
|
||||
|
|
@ -658,20 +781,20 @@ impl StateMachine {
|
|||
}
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{getassertion:?} using {pin_uv_auth_result:?}");
|
||||
debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
||||
let mut resp = dev.send_msg_cancellable(&getassertion, alive);
|
||||
let mut resp = dev.send_msg_cancellable(&get_assertion, alive);
|
||||
if resp.is_err() {
|
||||
// Retry with a different RP ID if one was supplied. This is intended to be
|
||||
// used with the AppID provided in the WebAuthn FIDO AppID extension.
|
||||
if let Some(alternate_rp_id) = getassertion.alternate_rp_id {
|
||||
getassertion.rp = RelyingPartyWrapper::Data(RelyingParty {
|
||||
if let Some(alternate_rp_id) = get_assertion.alternate_rp_id {
|
||||
get_assertion.rp = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: alternate_rp_id,
|
||||
..Default::default()
|
||||
});
|
||||
getassertion.alternate_rp_id = None;
|
||||
resp = dev.send_msg_cancellable(&getassertion, alive);
|
||||
get_assertion.alternate_rp_id = None;
|
||||
resp = dev.send_msg_cancellable(&get_assertion, alive);
|
||||
}
|
||||
}
|
||||
if resp.is_ok() {
|
||||
|
|
@ -754,6 +877,45 @@ impl StateMachine {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reset_helper(
|
||||
dev: &mut Device,
|
||||
selector: Sender<DeviceSelectorEvent>,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) {
|
||||
let reset = Reset {};
|
||||
info!("Device {:?} continues with the reset process", dev.id());
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{:?}", reset);
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
||||
let resp = dev.send_cbor_cancellable(&reset, keep_alive);
|
||||
if resp.is_ok() {
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::Success {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
// The DeviceSelector could already be dead, but it might also wait
|
||||
// for us to respond, in order to cancel all other tokens in case
|
||||
// we skipped the "blinking"-action and went straight for the actual
|
||||
// request.
|
||||
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
|
||||
}
|
||||
|
||||
match resp {
|
||||
Ok(()) => callback.call(Ok(())),
|
||||
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
|
||||
Err(HIDError::Command(CommandError::StatusCode(StatusCode::ChannelBusy, _))) => {}
|
||||
Err(e) => {
|
||||
warn!("error happened: {}", e);
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
|
|
@ -769,52 +931,112 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let reset = Reset {};
|
||||
let mut dev = match Self::init_and_select(info, &selector, true, alive) {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
Some(dev) => dev,
|
||||
};
|
||||
|
||||
info!("Device {:?} continues with the reset process", dev.id());
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{:?}", reset);
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
||||
let resp = dev.send_cbor_cancellable(&reset, alive);
|
||||
if resp.is_ok() {
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::Success {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
// The DeviceSelector could already be dead, but it might also wait
|
||||
// for us to respond, in order to cancel all other tokens in case
|
||||
// we skipped the "blinking"-action and went straight for the actual
|
||||
// request.
|
||||
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
|
||||
}
|
||||
|
||||
match resp {
|
||||
Ok(()) => callback.call(Ok(())),
|
||||
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::ChannelBusy,
|
||||
_,
|
||||
))) => {}
|
||||
Err(e) => {
|
||||
warn!("error happened: {}", e);
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
}
|
||||
}
|
||||
Self::reset_helper(&mut dev, selector, status, callback.clone(), alive);
|
||||
},
|
||||
);
|
||||
|
||||
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
|
||||
}
|
||||
|
||||
pub fn set_or_change_pin_helper(
|
||||
dev: &mut Device,
|
||||
mut current_pin: Option<Pin>,
|
||||
new_pin: Pin,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
) {
|
||||
let mut shared_secret = match dev.establish_shared_secret() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let authinfo = match dev.get_authenticator_info() {
|
||||
Some(i) => i.clone(),
|
||||
None => {
|
||||
callback.call(Err(HIDError::DeviceNotInitialized.into()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// If the device has a min PIN use that, otherwise default to 4 according to Spec
|
||||
if new_pin.as_bytes().len() < authinfo.min_pin_length.unwrap_or(4) as usize {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
|
||||
return;
|
||||
}
|
||||
|
||||
// As per Spec: "Maximum PIN Length: UTF-8 representation MUST NOT exceed 63 bytes"
|
||||
if new_pin.as_bytes().len() >= 64 {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong(
|
||||
new_pin.as_bytes().len(),
|
||||
))));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a client-pin is already set, or if a new one should be created
|
||||
let res = if Some(true) == authinfo.options.client_pin {
|
||||
let mut res;
|
||||
let mut was_invalid = false;
|
||||
let mut retries = None;
|
||||
loop {
|
||||
// current_pin will only be Some() in the interactive mode (running `manage()`)
|
||||
// In case that PIN is wrong, we want to avoid an endless-loop here with re-trying
|
||||
// that wrong PIN all the time. So we `take()` it, and only test it once.
|
||||
// If that PIN is wrong, we fall back to the "ask_user_for_pin"-method.
|
||||
let curr_pin = match current_pin.take() {
|
||||
None => {
|
||||
match Self::ask_user_for_pin(was_invalid, retries, &status, &callback) {
|
||||
Ok(pin) => pin,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(pin) => pin,
|
||||
};
|
||||
|
||||
res = ChangeExistingPin::new(&authinfo, &shared_secret, &curr_pin, &new_pin)
|
||||
.map_err(HIDError::Command)
|
||||
.and_then(|msg| dev.send_cbor_cancellable(&msg, alive))
|
||||
.map_err(|e| repackage_pin_errors(dev, e));
|
||||
|
||||
if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res {
|
||||
was_invalid = true;
|
||||
retries = r;
|
||||
// We need to re-establish the shared secret for the next round.
|
||||
match dev.establish_shared_secret() {
|
||||
Ok(s) => {
|
||||
shared_secret = s;
|
||||
}
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
res
|
||||
} else {
|
||||
dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive)
|
||||
.map_err(AuthenticatorError::HIDError)
|
||||
};
|
||||
|
||||
callback.call(res);
|
||||
}
|
||||
|
||||
pub fn set_pin(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
|
|
@ -832,95 +1054,21 @@ impl StateMachine {
|
|||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, true, alive) {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
Some(dev) => dev,
|
||||
};
|
||||
|
||||
let mut shared_secret = match dev.establish_shared_secret() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let authinfo = match dev.get_authenticator_info() {
|
||||
Some(i) => i.clone(),
|
||||
None => {
|
||||
callback.call(Err(HIDError::DeviceNotInitialized.into()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// With CTAP2.1 we will have an adjustable required length for PINs
|
||||
if new_pin.as_bytes().len() < 4 {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
|
||||
return;
|
||||
}
|
||||
|
||||
if new_pin.as_bytes().len() > 64 {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong(
|
||||
new_pin.as_bytes().len(),
|
||||
))));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a client-pin is already set, or if a new one should be created
|
||||
let res = if authinfo.options.client_pin.unwrap_or_default() {
|
||||
let mut res;
|
||||
let mut was_invalid = false;
|
||||
let mut retries = None;
|
||||
loop {
|
||||
let current_pin = match Self::ask_user_for_pin(
|
||||
was_invalid,
|
||||
retries,
|
||||
&status,
|
||||
&callback,
|
||||
) {
|
||||
Ok(pin) => pin,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
res = ChangeExistingPin::new(
|
||||
&authinfo,
|
||||
&shared_secret,
|
||||
¤t_pin,
|
||||
&new_pin,
|
||||
)
|
||||
.map_err(HIDError::Command)
|
||||
.and_then(|msg| dev.send_cbor_cancellable(&msg, alive))
|
||||
.map_err(|e| repackage_pin_errors(&mut dev, e));
|
||||
|
||||
if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res {
|
||||
was_invalid = true;
|
||||
retries = r;
|
||||
// We need to re-establish the shared secret for the next round.
|
||||
match dev.establish_shared_secret() {
|
||||
Ok(s) => {
|
||||
shared_secret = s;
|
||||
}
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
res
|
||||
} else {
|
||||
dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive)
|
||||
.map_err(AuthenticatorError::HIDError)
|
||||
};
|
||||
callback.call(res);
|
||||
Self::set_or_change_pin_helper(
|
||||
&mut dev,
|
||||
None,
|
||||
new_pin.clone(),
|
||||
status,
|
||||
callback.clone(),
|
||||
alive,
|
||||
);
|
||||
},
|
||||
);
|
||||
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
|
||||
|
|
@ -938,7 +1086,6 @@ impl StateMachine {
|
|||
) {
|
||||
// Abort any prior register/sign calls.
|
||||
self.cancel();
|
||||
|
||||
let cbc = callback.clone();
|
||||
|
||||
let transaction = Transaction::new(
|
||||
|
|
@ -1166,4 +1313,95 @@ impl StateMachine {
|
|||
|
||||
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
|
||||
}
|
||||
|
||||
// Function to interactively manage a specific token.
|
||||
// Difference to register/sign: These want to do something and don't care
|
||||
// with which token they do it.
|
||||
// This function wants to manipulate a specific token. For this, we first
|
||||
// have to select one and then do something with it, based on what it
|
||||
// supports (Set PIN, Change PIN, Reset, etc.).
|
||||
// Hence, we first go through the discovery-phase, then provide the user
|
||||
// with the AuthenticatorInfo and then let them interactively decide what to do
|
||||
pub fn manage(
|
||||
&mut self,
|
||||
timeout: u64,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
) {
|
||||
// Abort any prior register/sign calls.
|
||||
self.cancel();
|
||||
let cbc = callback.clone();
|
||||
|
||||
let transaction = Transaction::new(
|
||||
timeout,
|
||||
callback.clone(),
|
||||
status,
|
||||
move |info, selector, status, alive| {
|
||||
let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) {
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
Some(dev) => dev,
|
||||
};
|
||||
|
||||
info!("Device {:?} selected for interactive management.", dev.id());
|
||||
|
||||
// Sending the user the info about the token
|
||||
let (tx, rx) = channel();
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::InteractiveManagement((
|
||||
tx,
|
||||
dev.get_device_info(),
|
||||
dev.get_authenticator_info().cloned(),
|
||||
)),
|
||||
);
|
||||
while alive() {
|
||||
match rx.recv_timeout(Duration::from_millis(400)) {
|
||||
Ok(InteractiveRequest::Reset) => {
|
||||
Self::reset_helper(&mut dev, selector, status, callback.clone(), alive);
|
||||
}
|
||||
Ok(InteractiveRequest::ChangePIN(curr_pin, new_pin)) => {
|
||||
Self::set_or_change_pin_helper(
|
||||
&mut dev,
|
||||
Some(curr_pin),
|
||||
new_pin,
|
||||
status,
|
||||
callback.clone(),
|
||||
alive,
|
||||
);
|
||||
}
|
||||
Ok(InteractiveRequest::SetPIN(pin)) => {
|
||||
Self::set_or_change_pin_helper(
|
||||
&mut dev,
|
||||
None,
|
||||
pin,
|
||||
status,
|
||||
callback.clone(),
|
||||
alive,
|
||||
);
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
if !alive() {
|
||||
// We got stopped at some point
|
||||
callback.call(Err(AuthenticatorError::CancelledByUser));
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
// recv() failed, because the other side is dropping the Sender.
|
||||
info!(
|
||||
"Callback dropped the channel, so we abort the interactive session"
|
||||
);
|
||||
callback.call(Err(AuthenticatorError::CancelledByUser));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
use super::{u2ftypes, Pin};
|
||||
use serde::{
|
||||
ser::{Serialize, SerializeStruct},
|
||||
Serialize as DeriveSer, Serializer,
|
||||
};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use serde::{Deserialize, Serialize as DeriveSer, Serializer};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
#[derive(Debug, Deserialize, DeriveSer)]
|
||||
pub enum InteractiveRequest {
|
||||
Reset,
|
||||
ChangePIN(Pin, Pin),
|
||||
SetPIN(Pin),
|
||||
}
|
||||
|
||||
// Simply ignoring the Sender when serializing
|
||||
pub(crate) fn serialize_pin_required<S>(_: &Sender<Pin>, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
|
@ -64,30 +69,14 @@ pub enum StatusUpdate {
|
|||
/// Sent, once a device was selected (either automatically or by user-interaction)
|
||||
/// and the register or signing process continues with this device
|
||||
DeviceSelected(u2ftypes::U2FDeviceInfo),
|
||||
}
|
||||
|
||||
impl Serialize for StatusUpdate {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_struct("StatusUpdate", 1)?;
|
||||
match self {
|
||||
StatusUpdate::DeviceAvailable { dev_info } => {
|
||||
map.serialize_field("DeviceAvailable", &dev_info)?
|
||||
}
|
||||
StatusUpdate::DeviceUnavailable { dev_info } => {
|
||||
map.serialize_field("DeviceUnavailable", &dev_info)?
|
||||
}
|
||||
StatusUpdate::Success { dev_info } => map.serialize_field("Success", &dev_info)?,
|
||||
StatusUpdate::PinUvError(e) => map.serialize_field("PinUvError", &e)?,
|
||||
StatusUpdate::SelectDeviceNotice => map.serialize_field("SelectDeviceNotice", &())?,
|
||||
StatusUpdate::DeviceSelected(dev_info) => {
|
||||
map.serialize_field("DeviceSelected", &dev_info)?
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
/// Sent when a token was selected for interactive management
|
||||
InteractiveManagement(
|
||||
(
|
||||
Sender<InteractiveRequest>,
|
||||
u2ftypes::U2FDeviceInfo,
|
||||
Option<AuthenticatorInfo>,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {
|
||||
|
|
@ -96,54 +85,3 @@ pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {
|
|||
Err(e) => error!("Couldn't send status: {:?}", e),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::consts::Capability;
|
||||
use serde_json::to_string;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn serialize_select() {
|
||||
let st = StatusUpdate::SelectDeviceNotice;
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"SelectDeviceNotice":null}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_status_pin_uv() {
|
||||
let (tx, _rx) = channel();
|
||||
let st = StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx.clone(), Some(3)));
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"PinUvError":{"InvalidPin":3}}"#);
|
||||
|
||||
let st = StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx, None));
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"PinUvError":{"InvalidPin":null}}"#);
|
||||
|
||||
let st = StatusUpdate::PinUvError(StatusPinUv::PinBlocked);
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"PinUvError":"PinBlocked"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_success() {
|
||||
let cap = Capability::WINK | Capability::CBOR;
|
||||
let dev = u2ftypes::U2FDeviceInfo {
|
||||
vendor_name: String::from("ABC").into_bytes(),
|
||||
device_name: String::from("DEF").into_bytes(),
|
||||
version_interface: 2,
|
||||
version_major: 5,
|
||||
version_minor: 4,
|
||||
version_build: 3,
|
||||
cap_flags: cap,
|
||||
};
|
||||
let st = StatusUpdate::Success { dev_info: dev };
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(
|
||||
&json,
|
||||
r#"{"Success":{"vendor_name":[65,66,67],"device_name":[68,69,70],"version_interface":2,"version_major":5,"version_minor":4,"version_build":3,"cap_flags":{"bits":5}}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use crate::send_status;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
pub use crate::transport::platform::device::Device;
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use runloop::RunLoop;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::mpsc::{channel, RecvTimeoutError, Sender};
|
||||
use std::time::Duration;
|
||||
|
||||
// This import is used, but Rust 1.68 gives a warning
|
||||
#[allow(unused_imports)]
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
|
||||
pub type DeviceID = <Device as HIDDevice>::Id;
|
||||
pub type DeviceBuildParameters = <Device as HIDDevice>::BuildParameters;
|
||||
|
||||
|
|
@ -33,7 +35,7 @@ pub enum DeviceSelectorEvent {
|
|||
DevicesAdded(Vec<DeviceID>),
|
||||
DeviceRemoved(DeviceID),
|
||||
NotAToken(DeviceID),
|
||||
ImAToken((Device, Sender<DeviceCommand>)),
|
||||
ImAToken((DeviceID, Sender<DeviceCommand>)),
|
||||
SelectedToken(DeviceID),
|
||||
}
|
||||
|
||||
|
|
@ -45,11 +47,7 @@ pub struct DeviceSelector {
|
|||
}
|
||||
|
||||
impl DeviceSelector {
|
||||
// Devices are hashed according to their DeviceID (usually a path),
|
||||
// all other members (which can be mutable) are not used, but clippy
|
||||
// can't check this.
|
||||
#![allow(clippy::mutable_key_type)]
|
||||
pub fn run(status: Sender<crate::StatusUpdate>) -> Self {
|
||||
pub fn run() -> Self {
|
||||
let (selector_send, selector_rec) = channel();
|
||||
// let new_device_callback = Arc::new(new_device_cb);
|
||||
let runloop = RunLoop::new(move |alive| {
|
||||
|
|
@ -57,7 +55,8 @@ impl DeviceSelector {
|
|||
// Device was added, but we wait for its response, if it is a token or not
|
||||
// We save both a write-only copy of the device (for cancellation) and it's thread
|
||||
let mut waiting_for_response = HashSet::new();
|
||||
// All devices that responded with "ImAToken"
|
||||
// Device IDs of devices that responded with "ImAToken" mapping to channels that are
|
||||
// waiting to receive a DeviceCommand
|
||||
let mut tokens = HashMap::new();
|
||||
while alive() {
|
||||
let d = Duration::from_secs(100);
|
||||
|
|
@ -75,14 +74,8 @@ impl DeviceSelector {
|
|||
Self::cancel_all(tokens, None);
|
||||
break;
|
||||
}
|
||||
DeviceSelectorEvent::SelectedToken(id) => {
|
||||
if let Some(dev) = tokens.keys().find(|d| d.id() == id) {
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::DeviceSelected(dev.get_device_info()),
|
||||
);
|
||||
}
|
||||
Self::cancel_all(tokens, Some(&id));
|
||||
DeviceSelectorEvent::SelectedToken(ref id) => {
|
||||
Self::cancel_all(tokens, Some(id));
|
||||
break; // We are done here. The selected device continues without us.
|
||||
}
|
||||
DeviceSelectorEvent::DevicesAdded(ids) => {
|
||||
|
|
@ -92,26 +85,20 @@ impl DeviceSelector {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
DeviceSelectorEvent::DeviceRemoved(id) => {
|
||||
DeviceSelectorEvent::DeviceRemoved(ref id) => {
|
||||
debug!("Device removed event: {:?}", id);
|
||||
if !waiting_for_response.remove(&id) {
|
||||
if !waiting_for_response.remove(id) {
|
||||
// Note: We _could_ check here if we had multiple tokens and are already blinking
|
||||
// and the removal of this one leads to only one token left. So we could in theory
|
||||
// stop blinking and select it right away. At the moment, I think this is a
|
||||
// too surprising behavior and therefore, we let the remaining device keep on blinking
|
||||
// since the user could add yet another device, instead of using the remaining one.
|
||||
tokens.iter().for_each(|(dev, send)| {
|
||||
if dev.id() == id {
|
||||
let _ = send.send(DeviceCommand::Removed);
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::DeviceUnavailable {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
tokens.iter().for_each(|(dev_id, tx)| {
|
||||
if dev_id == id {
|
||||
let _ = tx.send(DeviceCommand::Removed);
|
||||
}
|
||||
});
|
||||
tokens.retain(|dev, _| dev.id() != id);
|
||||
tokens.retain(|dev_id, _| dev_id != id);
|
||||
if tokens.is_empty() {
|
||||
blinking = false;
|
||||
continue;
|
||||
|
|
@ -127,27 +114,20 @@ impl DeviceSelector {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
DeviceSelectorEvent::NotAToken(id) => {
|
||||
DeviceSelectorEvent::NotAToken(ref id) => {
|
||||
debug!("Device not a token event: {:?}", id);
|
||||
waiting_for_response.remove(&id);
|
||||
waiting_for_response.remove(id);
|
||||
}
|
||||
DeviceSelectorEvent::ImAToken((dev, tx)) => {
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::DeviceAvailable {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
let id = dev.id();
|
||||
DeviceSelectorEvent::ImAToken((id, tx)) => {
|
||||
let _ = waiting_for_response.remove(&id);
|
||||
tokens.insert(dev, tx.clone());
|
||||
if blinking {
|
||||
// We are already blinking, so this new device should blink too.
|
||||
if tx.send(DeviceCommand::Blink).is_err() {
|
||||
// Device thread died in the meantime (which shouldn't happen)
|
||||
tokens.retain(|dev, _| dev.id() != id);
|
||||
if tx.send(DeviceCommand::Blink).is_ok() {
|
||||
tokens.insert(id, tx.clone());
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
tokens.insert(id, tx.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -155,17 +135,13 @@ impl DeviceSelector {
|
|||
// All known devices told us, whether they are tokens or not and we have at least one token
|
||||
if waiting_for_response.is_empty() && !tokens.is_empty() {
|
||||
if tokens.len() == 1 {
|
||||
let (dev, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty
|
||||
let (dev_id, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty
|
||||
if tx.send(DeviceCommand::Continue).is_err() {
|
||||
// Device thread died in the meantime (which shouldn't happen).
|
||||
// Tokens is empty, so we just start over again
|
||||
continue;
|
||||
}
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::DeviceSelected(dev.get_device_info()),
|
||||
);
|
||||
Self::cancel_all(tokens, Some(&dev.id()));
|
||||
Self::cancel_all(tokens, Some(&dev_id));
|
||||
break; // We are done here
|
||||
} else {
|
||||
blinking = true;
|
||||
|
|
@ -175,7 +151,6 @@ impl DeviceSelector {
|
|||
// We ignore errors here for now, but should probably remove the device in such a case (even though it theoretically can't happen)
|
||||
let _ = tx.send(DeviceCommand::Blink);
|
||||
});
|
||||
send_status(&status, crate::StatusUpdate::SelectDeviceNotice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,9 +165,9 @@ impl DeviceSelector {
|
|||
self.sender.clone()
|
||||
}
|
||||
|
||||
fn cancel_all(tokens: HashMap<Device, Sender<DeviceCommand>>, exclude: Option<&DeviceID>) {
|
||||
for (dev, tx) in tokens.iter() {
|
||||
if Some(&dev.id()) != exclude {
|
||||
fn cancel_all(tokens: HashMap<DeviceID, Sender<DeviceCommand>>, exclude: Option<&DeviceID>) {
|
||||
for (dev_id, tx) in tokens.iter() {
|
||||
if Some(dev_id) != exclude {
|
||||
let _ = tx.send(DeviceCommand::Cancel);
|
||||
}
|
||||
}
|
||||
|
|
@ -212,16 +187,7 @@ pub mod tests {
|
|||
consts::Capability,
|
||||
ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorOptions},
|
||||
u2ftypes::U2FDeviceInfo,
|
||||
StatusUpdate,
|
||||
};
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
enum ExpectedUpdate {
|
||||
DeviceAvailable,
|
||||
DeviceUnavailable,
|
||||
SelectDeviceNotice,
|
||||
DeviceSelected,
|
||||
}
|
||||
|
||||
fn gen_info(id: String) -> U2FDeviceInfo {
|
||||
U2FDeviceInfo {
|
||||
|
|
@ -257,7 +223,7 @@ pub mod tests {
|
|||
selector
|
||||
.sender
|
||||
.send(DeviceSelectorEvent::ImAToken((
|
||||
dev.clone_device_as_write_only().unwrap(),
|
||||
dev.id(),
|
||||
dev.sender.clone().unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
|
|
@ -281,22 +247,6 @@ pub mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn recv_status(dev: &Device, status_rx: &Receiver<StatusUpdate>, expected: ExpectedUpdate) {
|
||||
let res = status_rx.recv().unwrap();
|
||||
// Marking it with _, as cargo warns about "unused exp", as it doesn't view the assert below as a usage
|
||||
let _exp = match expected {
|
||||
ExpectedUpdate::DeviceAvailable => StatusUpdate::DeviceUnavailable {
|
||||
dev_info: gen_info(dev.id()),
|
||||
},
|
||||
ExpectedUpdate::DeviceUnavailable => StatusUpdate::DeviceUnavailable {
|
||||
dev_info: gen_info(dev.id()),
|
||||
},
|
||||
ExpectedUpdate::DeviceSelected => StatusUpdate::DeviceSelected(gen_info(dev.id())),
|
||||
ExpectedUpdate::SelectDeviceNotice => StatusUpdate::SelectDeviceNotice,
|
||||
};
|
||||
assert!(matches!(res, _exp));
|
||||
}
|
||||
|
||||
fn add_devices<'a, T>(iter: T, selector: &DeviceSelector)
|
||||
where
|
||||
T: Iterator<Item = &'a Device>,
|
||||
|
|
@ -320,8 +270,7 @@ pub mod tests {
|
|||
|
||||
// Make those actual tokens. The rest is interpreted as non-u2f-devices
|
||||
make_device_with_pin(&mut devices[2]);
|
||||
let (status_tx, status_rx) = channel();
|
||||
let selector = DeviceSelector::run(status_tx);
|
||||
let selector = DeviceSelector::run();
|
||||
|
||||
// Adding all
|
||||
add_devices(devices.iter(), &selector);
|
||||
|
|
@ -333,12 +282,10 @@ pub mod tests {
|
|||
|
||||
send_i_am_token(&devices[2], &selector);
|
||||
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
assert_eq!(
|
||||
devices[2].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Continue
|
||||
);
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceSelected);
|
||||
}
|
||||
|
||||
// This test is mostly for testing stop() and clone_sender()
|
||||
|
|
@ -346,8 +293,7 @@ pub mod tests {
|
|||
fn test_device_selector_stop() {
|
||||
let device = Device::new("device selector 1").unwrap();
|
||||
|
||||
let (status_tx, _) = channel();
|
||||
let mut selector = DeviceSelector::run(status_tx);
|
||||
let mut selector = DeviceSelector::run();
|
||||
|
||||
// Adding all
|
||||
selector
|
||||
|
|
@ -378,15 +324,13 @@ pub mod tests {
|
|||
make_device_with_pin(&mut devices[4]);
|
||||
make_device_with_pin(&mut devices[5]);
|
||||
|
||||
let (status_tx, status_rx) = channel();
|
||||
let selector = DeviceSelector::run(status_tx);
|
||||
let selector = DeviceSelector::run();
|
||||
|
||||
// Adding all, except the last one (we simulate that this one is not yet plugged in)
|
||||
add_devices(devices.iter().take(5), &selector);
|
||||
|
||||
// Interleave tokens and non-tokens
|
||||
send_i_am_token(&devices[2], &selector);
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
|
||||
devices.iter_mut().for_each(|d| {
|
||||
if !d.is_u2f() {
|
||||
|
|
@ -395,7 +339,6 @@ pub mod tests {
|
|||
});
|
||||
|
||||
send_i_am_token(&devices[4], &selector);
|
||||
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
|
||||
// We added 2 devices that are tokens. They should get the blink-command now
|
||||
assert_eq!(
|
||||
|
|
@ -406,11 +349,9 @@ pub mod tests {
|
|||
devices[4].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Blink
|
||||
);
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice);
|
||||
|
||||
// Plug in late device
|
||||
send_i_am_token(&devices[5], &selector);
|
||||
recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
assert_eq!(
|
||||
devices[5].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Blink
|
||||
|
|
@ -435,15 +376,13 @@ pub mod tests {
|
|||
make_device_simple_u2f(&mut devices[4]);
|
||||
make_device_simple_u2f(&mut devices[5]);
|
||||
|
||||
let (status_tx, status_rx) = channel();
|
||||
let selector = DeviceSelector::run(status_tx);
|
||||
let selector = DeviceSelector::run();
|
||||
|
||||
// Adding all, except the last one (we simulate that this one is not yet plugged in)
|
||||
add_devices(devices.iter().take(5), &selector);
|
||||
|
||||
// Interleave tokens and non-tokens
|
||||
send_i_am_token(&devices[2], &selector);
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
|
||||
devices.iter_mut().for_each(|d| {
|
||||
if !d.is_u2f() {
|
||||
|
|
@ -452,7 +391,6 @@ pub mod tests {
|
|||
});
|
||||
|
||||
send_i_am_token(&devices[4], &selector);
|
||||
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
|
||||
// We added 2 devices that are tokens. They should get the blink-command now
|
||||
assert_eq!(
|
||||
|
|
@ -463,23 +401,19 @@ pub mod tests {
|
|||
devices[4].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Blink
|
||||
);
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice);
|
||||
|
||||
// Plug in late device
|
||||
send_i_am_token(&devices[5], &selector);
|
||||
recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
assert_eq!(
|
||||
devices[5].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Blink
|
||||
);
|
||||
// Remove device again
|
||||
remove_device(&devices[5], &selector);
|
||||
recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceUnavailable);
|
||||
|
||||
// Now we add a token that has a PIN, it should not get "Continue" but "Blink"
|
||||
make_device_with_pin(&mut devices[6]);
|
||||
send_i_am_token(&devices[6], &selector);
|
||||
recv_status(&devices[6], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
assert_eq!(
|
||||
devices[6].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Blink
|
||||
|
|
@ -504,8 +438,7 @@ pub mod tests {
|
|||
make_device_with_pin(&mut devices[4]);
|
||||
make_device_with_pin(&mut devices[5]);
|
||||
|
||||
let (status_tx, status_rx) = channel();
|
||||
let selector = DeviceSelector::run(status_tx);
|
||||
let selector = DeviceSelector::run();
|
||||
|
||||
// Adding all, except the last one (we simulate that this one is not yet plugged in)
|
||||
add_devices(devices.iter(), &selector);
|
||||
|
|
@ -519,29 +452,24 @@ pub mod tests {
|
|||
});
|
||||
|
||||
for idx in [2, 4, 5] {
|
||||
recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
assert_eq!(
|
||||
devices[idx].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Blink
|
||||
);
|
||||
}
|
||||
recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice);
|
||||
|
||||
// Remove all tokens
|
||||
for idx in [2, 4, 5] {
|
||||
remove_device(&devices[idx], &selector);
|
||||
recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceUnavailable);
|
||||
}
|
||||
|
||||
// Adding one again
|
||||
send_i_am_token(&devices[4], &selector);
|
||||
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable);
|
||||
|
||||
// This should now get a "Continue" instead of "Blinking", because it's the only device
|
||||
assert_eq!(
|
||||
devices[4].receiver.as_ref().unwrap().recv().unwrap(),
|
||||
DeviceCommand::Continue
|
||||
);
|
||||
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceSelected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,25 +212,6 @@ impl HIDDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
/// This is used for cancellation of blocking read()-requests.
|
||||
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
// Try to open the device.
|
||||
// This can't really error out as we already did this conversion
|
||||
let cstr = CString::new(self.path.as_bytes()).map_err(|_| (HIDError::DeviceError))?;
|
||||
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_WRONLY) };
|
||||
let fd =
|
||||
from_unix_result(fd).map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?;
|
||||
Ok(Self {
|
||||
path: self.path.clone(),
|
||||
fd,
|
||||
cid: self.cid,
|
||||
dev_info: self.dev_info.clone(),
|
||||
secret: self.secret.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ impl Transaction {
|
|||
+ 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let status_sender = status.clone();
|
||||
let device_selector = DeviceSelector::run(status);
|
||||
let device_selector = DeviceSelector::run();
|
||||
let selector_sender = device_selector.clone_sender();
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
// Create a new device monitor.
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
|
||||
|
||||
// Start polling for new devices.
|
||||
try_or!(monitor.run(alive), |_| callback
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ where
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo);
|
||||
fn set_shared_secret(&mut self, secret: SharedSecret);
|
||||
fn get_shared_secret(&self) -> Option<&SharedSecret>;
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError>;
|
||||
|
||||
fn supports_ctap1(&self) -> bool {
|
||||
// CAPABILITY_NMSG:
|
||||
|
|
|
|||
|
|
@ -158,26 +158,6 @@ impl HIDDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
/// This is used for cancellation of blocking read()-requests.
|
||||
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
let fd = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&self.path)
|
||||
.map_err(|e| (HIDError::IO(Some(self.path.clone()), e)))?;
|
||||
|
||||
Ok(Self {
|
||||
path: self.path.clone(),
|
||||
fd,
|
||||
in_rpt_size: self.in_rpt_size,
|
||||
out_rpt_size: self.out_rpt_size,
|
||||
cid: self.cid,
|
||||
dev_info: self.dev_info.clone(),
|
||||
secret: self.secret.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ impl Transaction {
|
|||
+ 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let status_sender = status.clone();
|
||||
let device_selector = DeviceSelector::run(status);
|
||||
let device_selector = DeviceSelector::run();
|
||||
let selector_sender = device_selector.clone_sender();
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
// Create a new device monitor.
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
|
||||
|
||||
// Start polling for new devices.
|
||||
try_or!(monitor.run(alive), |_| callback
|
||||
|
|
|
|||
|
|
@ -204,19 +204,6 @@ impl HIDDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
/// This is used for cancellation of blocking read()-requests.
|
||||
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
Ok(Self {
|
||||
device_ref: self.device_ref,
|
||||
cid: self.cid,
|
||||
report_rx: None,
|
||||
dev_info: self.dev_info.clone(),
|
||||
secret: self.secret.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ impl Transaction {
|
|||
{
|
||||
let (tx, rx) = channel();
|
||||
let timeout = (timeout as f64) / 1000.0;
|
||||
let status_sender = status.clone();
|
||||
let device_selector = DeviceSelector::run(status);
|
||||
let device_selector = DeviceSelector::run();
|
||||
let selector_sender = device_selector.clone_sender();
|
||||
let builder = thread::Builder::new();
|
||||
let thread = builder
|
||||
|
|
@ -61,7 +60,7 @@ impl Transaction {
|
|||
obs.add_to_current_runloop();
|
||||
|
||||
// Create a new HID device monitor and start polling.
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
|
||||
try_or!(monitor.start(), |_| callback
|
||||
.call(Err(errors::AuthenticatorError::Platform)));
|
||||
|
||||
|
|
|
|||
|
|
@ -173,19 +173,6 @@ impl HIDDevice for Device {
|
|||
self.id.clone()
|
||||
}
|
||||
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
Ok(Device {
|
||||
id: self.id.clone(),
|
||||
cid: self.cid,
|
||||
reads: self.reads.clone(),
|
||||
writes: self.writes.clone(),
|
||||
dev_info: self.dev_info.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
sender: self.sender.clone(),
|
||||
receiver: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
self.sender.is_some()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,21 +225,6 @@ impl HIDDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
/// This is used for cancellation of blocking read()-requests.
|
||||
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
// Try to open the device.
|
||||
let fd = Fd::open(&self.path, libc::O_WRONLY)?;
|
||||
Ok(Self {
|
||||
path: self.path.clone(),
|
||||
fd,
|
||||
cid: self.cid,
|
||||
dev_info: self.dev_info.clone(),
|
||||
secret: self.secret.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ impl Transaction {
|
|||
+ 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let status_sender = status.clone();
|
||||
let device_selector = DeviceSelector::run(status);
|
||||
let device_selector = DeviceSelector::run();
|
||||
let selector_sender = device_selector.clone_sender();
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
// Create a new device monitor.
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
|
||||
|
||||
// Start polling for new devices.
|
||||
try_or!(monitor.run(alive), |_| callback
|
||||
|
|
|
|||
|
|
@ -201,27 +201,6 @@ impl HIDDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
/// This is used for cancellation of blocking read()-requests.
|
||||
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
// Try to open the device.
|
||||
// This can't really error out as we already did this conversion
|
||||
let cstr = CString::new(self.path.as_bytes()).map_err(|_| (HIDError::DeviceError))?;
|
||||
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_WRONLY) };
|
||||
let fd =
|
||||
from_unix_result(fd).map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?;
|
||||
Ok(Self {
|
||||
path: self.path.clone(),
|
||||
fd,
|
||||
in_rpt_size: self.in_rpt_size,
|
||||
out_rpt_size: self.out_rpt_size,
|
||||
cid: self.cid,
|
||||
dev_info: self.dev_info.clone(),
|
||||
secret: self.secret.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ impl Transaction {
|
|||
+ 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let status_sender = status.clone();
|
||||
let device_selector = DeviceSelector::run(status);
|
||||
let device_selector = DeviceSelector::run();
|
||||
let selector_sender = device_selector.clone_sender();
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
// Create a new device monitor.
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
|
||||
|
||||
// Start polling for new devices.
|
||||
try_or!(monitor.run(alive), |_| callback
|
||||
|
|
|
|||
|
|
@ -96,10 +96,6 @@ impl HIDDevice for Device {
|
|||
fn get_shared_secret(&self) -> Option<&SharedSecret> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Transaction {
|
|||
pub fn new<F, T>(
|
||||
timeout: u64,
|
||||
callback: StateCallback<crate::Result<T>>,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
_status: Sender<crate::StatusUpdate>,
|
||||
new_device_cb: F,
|
||||
) -> crate::Result<Self>
|
||||
where
|
||||
|
|
@ -31,7 +31,7 @@ impl Transaction {
|
|||
T: 'static,
|
||||
{
|
||||
// Just to silence "unused"-warnings
|
||||
let mut device_selector = DeviceSelector::run(status);
|
||||
let mut device_selector = DeviceSelector::run();
|
||||
let _ = DeviceSelectorEvent::DevicesAdded(vec![]);
|
||||
let _ = DeviceSelectorEvent::DeviceRemoved(PathBuf::new());
|
||||
let _ = device_selector.clone_sender();
|
||||
|
|
|
|||
|
|
@ -149,24 +149,6 @@ impl HIDDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
/// This is used for cancellation of blocking read()-requests.
|
||||
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
|
||||
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&self.path)
|
||||
.map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?;
|
||||
|
||||
Ok(Self {
|
||||
path: self.path.clone(),
|
||||
file,
|
||||
cid: self.cid,
|
||||
dev_info: self.dev_info.clone(),
|
||||
secret: self.secret.clone(),
|
||||
authenticator_info: self.authenticator_info.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ impl Transaction {
|
|||
+ 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let status_sender = status.clone();
|
||||
let device_selector = DeviceSelector::run(status);
|
||||
let device_selector = DeviceSelector::run();
|
||||
let selector_sender = device_selector.clone_sender();
|
||||
let thread = RunLoop::new_with_timeout(
|
||||
move |alive| {
|
||||
// Create a new device monitor.
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
|
||||
let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
|
||||
|
||||
// Start polling for new devices.
|
||||
try_or!(monitor.run(alive), |_| callback
|
||||
|
|
|
|||
Loading…
Reference in a new issue