810 lines
29 KiB
Rust
810 lines
29 KiB
Rust
/* 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,
|
|
ctap2::commands::{
|
|
authenticator_config::{AuthConfigCommand, AuthConfigResult, SetMinPINLength},
|
|
bio_enrollment::EnrollmentInfo,
|
|
credential_management::CredentialList,
|
|
PinUvAuthResult,
|
|
},
|
|
errors::AuthenticatorError,
|
|
statecallback::StateCallback,
|
|
AuthenticatorInfo, BioEnrollmentCmd, BioEnrollmentResult, CredManagementCmd,
|
|
CredentialManagementResult, InteractiveRequest, InteractiveUpdate, ManageResult, Pin,
|
|
StatusPinUv, StatusUpdate,
|
|
};
|
|
use getopts::Options;
|
|
use log::debug;
|
|
use std::{env, io, sync::mpsc::Sender, thread};
|
|
use std::{
|
|
fmt::Display,
|
|
io::Write,
|
|
sync::mpsc::{channel, Receiver, RecvError},
|
|
};
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
enum PinOperation {
|
|
Set,
|
|
Change,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
enum ConfigureOperation {
|
|
ToggleAlwaysUV,
|
|
EnableEnterpriseAttestation,
|
|
SetMinPINLength,
|
|
}
|
|
|
|
impl ConfigureOperation {
|
|
fn ask_user(info: &AuthenticatorInfo) -> Self {
|
|
let sub_level = Self::parse_possible_operations(info);
|
|
println!();
|
|
println!("What do you wish to do?");
|
|
let choice = ask_user_choice(&sub_level);
|
|
sub_level[choice].clone()
|
|
}
|
|
|
|
fn parse_possible_operations(info: &AuthenticatorInfo) -> Vec<Self> {
|
|
let mut configure_ops = vec![];
|
|
if info.options.authnr_cfg == Some(true) && info.options.always_uv.is_some() {
|
|
configure_ops.push(ConfigureOperation::ToggleAlwaysUV);
|
|
}
|
|
if info.options.authnr_cfg == Some(true) && info.options.set_min_pin_length.is_some() {
|
|
configure_ops.push(ConfigureOperation::SetMinPINLength);
|
|
}
|
|
if info.options.ep.is_some() {
|
|
configure_ops.push(ConfigureOperation::EnableEnterpriseAttestation);
|
|
}
|
|
configure_ops
|
|
}
|
|
}
|
|
|
|
impl Display for ConfigureOperation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
ConfigureOperation::ToggleAlwaysUV => write!(f, "Toggle option 'Always UV'"),
|
|
ConfigureOperation::EnableEnterpriseAttestation => {
|
|
write!(f, "Enable Enterprise attestation")
|
|
}
|
|
ConfigureOperation::SetMinPINLength => write!(f, "Set min. PIN length"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
enum CredentialsOperation {
|
|
List,
|
|
Delete,
|
|
UpdateUser,
|
|
}
|
|
|
|
impl CredentialsOperation {
|
|
fn ask_user(info: &AuthenticatorInfo, creds: &CredentialList) -> Self {
|
|
let sub_level = Self::parse_possible_operations(info, creds);
|
|
println!();
|
|
println!("What do you wish to do?");
|
|
let choice = ask_user_choice(&sub_level);
|
|
sub_level[choice].clone()
|
|
}
|
|
|
|
fn parse_possible_operations(info: &AuthenticatorInfo, creds: &CredentialList) -> Vec<Self> {
|
|
let mut credentials_ops = vec![];
|
|
if info.options.cred_mgmt == Some(true)
|
|
|| info.options.credential_mgmt_preview == Some(true)
|
|
{
|
|
credentials_ops.push(CredentialsOperation::List);
|
|
}
|
|
if creds.existing_resident_credentials_count > 0 {
|
|
credentials_ops.push(CredentialsOperation::Delete);
|
|
// FIDO_2_1_PRE devices do not (all?) support UpdateUser.
|
|
// So we require devices to support full 2.1 for this.
|
|
if info
|
|
.versions
|
|
.contains(&authenticator::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1)
|
|
{
|
|
credentials_ops.push(CredentialsOperation::UpdateUser);
|
|
}
|
|
}
|
|
credentials_ops
|
|
}
|
|
}
|
|
|
|
impl Display for CredentialsOperation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
CredentialsOperation::List => write!(f, "List credentials"),
|
|
CredentialsOperation::Delete => write!(f, "Delete credentials"),
|
|
CredentialsOperation::UpdateUser => write!(f, "Update user info"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
enum BioOperation {
|
|
ShowInfo,
|
|
Add,
|
|
List,
|
|
Delete,
|
|
Rename,
|
|
}
|
|
|
|
impl BioOperation {
|
|
fn ask_user(info: &AuthenticatorInfo) -> Self {
|
|
let sub_level = Self::parse_possible_operations(info);
|
|
println!();
|
|
println!("What do you wish to do?");
|
|
let choice = ask_user_choice(&sub_level);
|
|
sub_level[choice].clone()
|
|
}
|
|
|
|
fn parse_possible_operations(info: &AuthenticatorInfo) -> Vec<Self> {
|
|
let mut bio_ops = vec![];
|
|
if info.options.bio_enroll.is_some()
|
|
|| info.options.user_verification_mgmt_preview.is_some()
|
|
{
|
|
bio_ops.extend([BioOperation::ShowInfo, BioOperation::Add]);
|
|
}
|
|
if info.options.bio_enroll == Some(true)
|
|
|| info.options.user_verification_mgmt_preview == Some(true)
|
|
{
|
|
bio_ops.extend([
|
|
BioOperation::Delete,
|
|
BioOperation::List,
|
|
BioOperation::Rename,
|
|
]);
|
|
}
|
|
bio_ops
|
|
}
|
|
}
|
|
|
|
impl Display for BioOperation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
BioOperation::ShowInfo => write!(f, "Show fingerprint sensor info"),
|
|
BioOperation::List => write!(f, "List enrollments"),
|
|
BioOperation::Add => write!(f, "Add enrollment"),
|
|
BioOperation::Delete => write!(f, "Delete enrollment"),
|
|
BioOperation::Rename => write!(f, "Rename enrollment"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
enum TopLevelOperation {
|
|
Quit,
|
|
Reset,
|
|
ShowFullInfo,
|
|
Pin(PinOperation),
|
|
Configure,
|
|
Credentials,
|
|
Bio,
|
|
}
|
|
|
|
impl Display for TopLevelOperation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
TopLevelOperation::Quit => write!(f, "Quit"),
|
|
TopLevelOperation::Reset => write!(f, "Reset"),
|
|
TopLevelOperation::ShowFullInfo => write!(f, "Show full info"),
|
|
TopLevelOperation::Pin(PinOperation::Change) => write!(f, "Change Pin"),
|
|
TopLevelOperation::Pin(PinOperation::Set) => write!(f, "Set Pin"),
|
|
TopLevelOperation::Configure => write!(f, "Configure Authenticator"),
|
|
TopLevelOperation::Credentials => write!(f, "Manage Credentials"),
|
|
TopLevelOperation::Bio => write!(f, "Manage BioEnrollments"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TopLevelOperation {
|
|
fn ask_user(info: &AuthenticatorInfo) -> TopLevelOperation {
|
|
let top_level = Self::parse_possible_operations(info);
|
|
println!();
|
|
println!("What do you wish to do?");
|
|
let choice = ask_user_choice(&top_level);
|
|
top_level[choice].clone()
|
|
}
|
|
|
|
fn parse_possible_operations(info: &AuthenticatorInfo) -> Vec<TopLevelOperation> {
|
|
let mut ops = vec![
|
|
TopLevelOperation::Quit,
|
|
TopLevelOperation::Reset,
|
|
TopLevelOperation::ShowFullInfo,
|
|
];
|
|
|
|
// PIN-related
|
|
match info.options.client_pin {
|
|
None => {}
|
|
Some(true) => ops.push(TopLevelOperation::Pin(PinOperation::Change)),
|
|
Some(false) => ops.push(TopLevelOperation::Pin(PinOperation::Set)),
|
|
}
|
|
|
|
// Authenticator-Configuration
|
|
if info.options.authnr_cfg == Some(true)
|
|
&& (info.options.always_uv.is_some()
|
|
|| info.options.set_min_pin_length.is_some()
|
|
|| info.options.ep.is_some())
|
|
{
|
|
ops.push(TopLevelOperation::Configure);
|
|
}
|
|
|
|
// Credential Management
|
|
if info.options.cred_mgmt == Some(true)
|
|
|| info.options.credential_mgmt_preview == Some(true)
|
|
{
|
|
ops.push(TopLevelOperation::Credentials);
|
|
}
|
|
|
|
// Bio Enrollment
|
|
if info.options.bio_enroll.is_some()
|
|
|| info.options.user_verification_mgmt_preview.is_some()
|
|
{
|
|
ops.push(TopLevelOperation::Bio);
|
|
}
|
|
|
|
ops
|
|
}
|
|
}
|
|
|
|
fn print_usage(program: &str, opts: Options) {
|
|
let brief = format!("Usage: {program} [options]");
|
|
print!("{}", opts.usage(&brief));
|
|
}
|
|
|
|
fn ask_user_choice<T: Display>(choices: &[T]) -> usize {
|
|
for (idx, op) in choices.iter().enumerate() {
|
|
println!("({idx}) {op}");
|
|
}
|
|
|
|
let mut input = String::new();
|
|
loop {
|
|
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");
|
|
if let Ok(idx) = input.trim().parse::<usize>() {
|
|
if idx < choices.len() {
|
|
// Add a newline in case of success for better separation of in/output
|
|
println!();
|
|
return idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_authenticator_config(
|
|
res: Option<AuthConfigResult>,
|
|
puat: Option<PinUvAuthResult>,
|
|
auth_info: &mut Option<AuthenticatorInfo>,
|
|
tx: &Sender<InteractiveRequest>,
|
|
) {
|
|
if let Some(AuthConfigResult::Success(info)) = res {
|
|
println!("{:#?}", info.options);
|
|
*auth_info = Some(info);
|
|
}
|
|
let choice = ConfigureOperation::ask_user(auth_info.as_ref().unwrap());
|
|
match choice {
|
|
ConfigureOperation::ToggleAlwaysUV => {
|
|
tx.send(InteractiveRequest::ChangeConfig(
|
|
AuthConfigCommand::ToggleAlwaysUv,
|
|
puat,
|
|
))
|
|
.expect("Failed to send ToggleAlwaysUV request.");
|
|
}
|
|
ConfigureOperation::EnableEnterpriseAttestation => {
|
|
tx.send(InteractiveRequest::ChangeConfig(
|
|
AuthConfigCommand::EnableEnterpriseAttestation,
|
|
puat,
|
|
))
|
|
.expect("Failed to send EnableEnterpriseAttestation request.");
|
|
}
|
|
ConfigureOperation::SetMinPINLength => {
|
|
let mut length = String::new();
|
|
while length.trim().parse::<u64>().is_err() {
|
|
length.clear();
|
|
print!("New minimum PIN length: ");
|
|
io::stdout()
|
|
.lock()
|
|
.flush()
|
|
.expect("Failed to flush stdout!");
|
|
io::stdin()
|
|
.read_line(&mut length)
|
|
.expect("error: unable to read user input");
|
|
}
|
|
let new_length = length.trim().parse::<u64>().unwrap();
|
|
let cmd = SetMinPINLength {
|
|
new_min_pin_length: Some(new_length),
|
|
min_pin_length_rpids: None,
|
|
force_change_pin: None,
|
|
};
|
|
|
|
tx.send(InteractiveRequest::ChangeConfig(
|
|
AuthConfigCommand::SetMinPINLength(cmd),
|
|
puat,
|
|
))
|
|
.expect("Failed to send SetMinPINLength request.");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_credential_management(
|
|
res: Option<CredentialManagementResult>,
|
|
puat: Option<PinUvAuthResult>,
|
|
auth_info: &mut Option<AuthenticatorInfo>,
|
|
tx: &Sender<InteractiveRequest>,
|
|
) {
|
|
match res {
|
|
Some(CredentialManagementResult::CredentialList(credlist)) => {
|
|
let mut creds = vec![];
|
|
for rp in &credlist.credential_list {
|
|
for cred in &rp.credentials {
|
|
creds.push((
|
|
rp.rp.name.clone(),
|
|
cred.user.clone(),
|
|
cred.credential_id.clone(),
|
|
));
|
|
}
|
|
}
|
|
let display_creds: Vec<_> = creds
|
|
.iter()
|
|
.map(|(rp, user, id)| format!("{:?} - {:?} - {:?}", rp, user, id))
|
|
.collect();
|
|
for (idx, op) in display_creds.iter().enumerate() {
|
|
println!("({idx}) {op}");
|
|
}
|
|
|
|
loop {
|
|
match CredentialsOperation::ask_user(auth_info.as_ref().unwrap(), &credlist) {
|
|
CredentialsOperation::List => {
|
|
let mut idx = 0;
|
|
for rp in credlist.credential_list.iter() {
|
|
for cred in &rp.credentials {
|
|
println!("({idx}) - {:?}: {:?}", rp.rp.name, cred);
|
|
idx += 1;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
CredentialsOperation::Delete => {
|
|
let choice = ask_user_choice(&display_creds);
|
|
tx.send(InteractiveRequest::CredentialManagement(
|
|
CredManagementCmd::DeleteCredential(creds[choice].2.clone()),
|
|
puat,
|
|
))
|
|
.expect("Failed to send DeleteCredentials request.");
|
|
break;
|
|
}
|
|
CredentialsOperation::UpdateUser => {
|
|
let choice = ask_user_choice(&display_creds);
|
|
// Updating username. Asking for the new one.
|
|
let mut input = String::new();
|
|
print!("New username: ");
|
|
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();
|
|
let name = if input.is_empty() { None } else { Some(input) };
|
|
let mut new_user = creds[choice].1.clone();
|
|
new_user.name = name;
|
|
tx.send(InteractiveRequest::CredentialManagement(
|
|
CredManagementCmd::UpdateUserInformation(
|
|
creds[choice].2.clone(),
|
|
new_user,
|
|
),
|
|
puat,
|
|
))
|
|
.expect("Failed to send UpdateUserinformation request.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
| Some(CredentialManagementResult::DeleteSucess)
|
|
| Some(CredentialManagementResult::UpdateSuccess) => {
|
|
tx.send(InteractiveRequest::CredentialManagement(
|
|
CredManagementCmd::GetCredentials,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetCredentials request.");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ask_user_bio_options(
|
|
biolist: Vec<EnrollmentInfo>,
|
|
puat: Option<PinUvAuthResult>,
|
|
auth_info: &mut Option<AuthenticatorInfo>,
|
|
tx: &Sender<InteractiveRequest>,
|
|
) {
|
|
let display_bios: Vec<_> = biolist
|
|
.iter()
|
|
.map(|x| format!("{:?}: {:?}", x.template_friendly_name, x.template_id))
|
|
.collect();
|
|
loop {
|
|
match BioOperation::ask_user(auth_info.as_ref().unwrap()) {
|
|
BioOperation::List => {
|
|
for (idx, bio) in display_bios.iter().enumerate() {
|
|
println!("({idx}) - {bio}");
|
|
}
|
|
continue;
|
|
}
|
|
BioOperation::ShowInfo => {
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::GetFingerprintSensorInfo,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetFingerprintSensorInfo request.");
|
|
break;
|
|
}
|
|
BioOperation::Delete => {
|
|
let choice = ask_user_choice(&display_bios);
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::DeleteEnrollment(biolist[choice].template_id.clone()),
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
break;
|
|
}
|
|
BioOperation::Rename => {
|
|
let choice = ask_user_choice(&display_bios);
|
|
let chosen_id = biolist[choice].template_id.clone();
|
|
// Updating enrollment name. Asking for the new one.
|
|
let mut input = String::new();
|
|
print!("New name: ");
|
|
io::stdout()
|
|
.lock()
|
|
.flush()
|
|
.expect("Failed to flush stdout!");
|
|
io::stdin()
|
|
.read_line(&mut input)
|
|
.expect("error: unable to read user input");
|
|
let name = input.trim().to_string();
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::ChangeName(chosen_id, name),
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
break;
|
|
}
|
|
BioOperation::Add => {
|
|
let mut input = String::new();
|
|
print!(
|
|
"The name of the new bio enrollment (leave empty if you don't want to name it): "
|
|
);
|
|
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();
|
|
let name = if input.is_empty() { None } else { Some(input) };
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::StartNewEnrollment(name),
|
|
puat,
|
|
))
|
|
.expect("Failed to send StartNewEnrollment request.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_bio_enrollments(
|
|
res: Option<BioEnrollmentResult>,
|
|
puat: Option<PinUvAuthResult>,
|
|
auth_info: &mut Option<AuthenticatorInfo>,
|
|
tx: &Sender<InteractiveRequest>,
|
|
) {
|
|
match res {
|
|
Some(BioEnrollmentResult::EnrollmentList(biolist)) => {
|
|
ask_user_bio_options(biolist, puat, auth_info, tx);
|
|
}
|
|
None => {
|
|
if BioOperation::parse_possible_operations(auth_info.as_ref().unwrap())
|
|
.contains(&BioOperation::List)
|
|
{
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::GetEnrollments,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
} else {
|
|
ask_user_bio_options(vec![], puat, auth_info, tx);
|
|
}
|
|
}
|
|
Some(BioEnrollmentResult::UpdateSuccess) => {
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::GetEnrollments,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
}
|
|
Some(BioEnrollmentResult::DeleteSuccess(info)) => {
|
|
*auth_info = Some(info.clone());
|
|
if BioOperation::parse_possible_operations(&info).contains(&BioOperation::List) {
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::GetEnrollments,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
} else {
|
|
ask_user_bio_options(vec![], puat, auth_info, tx);
|
|
}
|
|
}
|
|
Some(BioEnrollmentResult::AddSuccess(info)) => {
|
|
*auth_info = Some(info);
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::GetEnrollments,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
}
|
|
Some(BioEnrollmentResult::FingerprintSensorInfo(info)) => {
|
|
println!("{info:#?}");
|
|
if BioOperation::parse_possible_operations(auth_info.as_ref().unwrap())
|
|
.contains(&BioOperation::List)
|
|
{
|
|
tx.send(InteractiveRequest::BioEnrollment(
|
|
BioEnrollmentCmd::GetEnrollments,
|
|
puat,
|
|
))
|
|
.expect("Failed to send GetEnrollments request.");
|
|
} else {
|
|
ask_user_bio_options(vec![], puat, auth_info, tx);
|
|
}
|
|
}
|
|
Some(BioEnrollmentResult::SampleStatus(last_sample_status, remaining_samples)) => {
|
|
println!("Last sample status: {last_sample_status:?}, remaining samples: {remaining_samples:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
|
|
let mut tx = None;
|
|
let mut auth_info = None;
|
|
loop {
|
|
match status_rx.recv() {
|
|
Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::StartManagement((
|
|
tx_new,
|
|
auth_info_new,
|
|
)))) => {
|
|
let info = match auth_info_new {
|
|
Some(info) => info,
|
|
None => {
|
|
println!("Device only supports CTAP1 and can't be managed.");
|
|
return;
|
|
}
|
|
};
|
|
auth_info = Some(info.clone());
|
|
tx = Some(tx_new);
|
|
match TopLevelOperation::ask_user(&info) {
|
|
TopLevelOperation::Quit => {
|
|
tx.as_ref()
|
|
.unwrap()
|
|
.send(InteractiveRequest::Quit)
|
|
.expect("Failed to send PIN-set request");
|
|
}
|
|
TopLevelOperation::Reset => tx
|
|
.as_ref()
|
|
.unwrap()
|
|
.send(InteractiveRequest::Reset)
|
|
.expect("Failed to send Reset request."),
|
|
TopLevelOperation::ShowFullInfo => {
|
|
println!("Authenticator Info {:#?}", info);
|
|
return;
|
|
}
|
|
TopLevelOperation::Pin(op) => {
|
|
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 op == PinOperation::Change {
|
|
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.as_ref()
|
|
.unwrap()
|
|
.send(InteractiveRequest::ChangePIN(curr_pin, new_pin))
|
|
.expect("Failed to send PIN-change request");
|
|
} else {
|
|
tx.as_ref()
|
|
.unwrap()
|
|
.send(InteractiveRequest::SetPIN(new_pin))
|
|
.expect("Failed to send PIN-set request");
|
|
}
|
|
}
|
|
TopLevelOperation::Configure => handle_authenticator_config(
|
|
None,
|
|
None,
|
|
&mut auth_info,
|
|
tx.as_ref().unwrap(),
|
|
),
|
|
TopLevelOperation::Credentials => handle_credential_management(
|
|
None,
|
|
None,
|
|
&mut auth_info,
|
|
tx.as_ref().unwrap(),
|
|
),
|
|
TopLevelOperation::Bio => {
|
|
handle_bio_enrollments(None, None, &mut auth_info, tx.as_ref().unwrap())
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::AuthConfigUpdate((
|
|
cfg_result,
|
|
puat_res,
|
|
)))) => {
|
|
handle_authenticator_config(
|
|
Some(cfg_result),
|
|
puat_res,
|
|
&mut auth_info,
|
|
tx.as_ref().unwrap(),
|
|
);
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::InteractiveManagement(
|
|
InteractiveUpdate::CredentialManagementUpdate((cfg_result, puat_res)),
|
|
)) => {
|
|
handle_credential_management(
|
|
Some(cfg_result),
|
|
puat_res,
|
|
&mut auth_info,
|
|
tx.as_ref().unwrap(),
|
|
);
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::BioEnrollmentUpdate((
|
|
bio_res,
|
|
puat_res,
|
|
)))) => {
|
|
handle_bio_enrollments(
|
|
Some(bio_res),
|
|
puat_res,
|
|
&mut auth_info,
|
|
tx.as_ref().unwrap(),
|
|
);
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::SelectDeviceNotice) => {
|
|
println!("STATUS: Please select a device by touching one of them.");
|
|
}
|
|
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
|
let raw_pin =
|
|
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
|
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
|
println!(
|
|
"Wrong PIN! {}",
|
|
attempts.map_or("Try again.".to_string(), |a| format!(
|
|
"You have {a} attempts left."
|
|
))
|
|
);
|
|
let raw_pin =
|
|
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
|
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
|
panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
|
|
}
|
|
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
|
panic!("Too many failed attempts. Your device has been blocked. Reset it.")
|
|
}
|
|
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
|
|
println!(
|
|
"Wrong UV! {}",
|
|
attempts.map_or("Try again.".to_string(), |a| format!(
|
|
"You have {a} attempts left."
|
|
))
|
|
);
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
|
|
println!("Too many failed UV-attempts.");
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::PresenceRequired) => {
|
|
println!("Please touch your device!");
|
|
continue;
|
|
}
|
|
Ok(StatusUpdate::PinUvError(e)) => {
|
|
panic!("Unexpected error: {:?}", e)
|
|
}
|
|
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
|
|
panic!("Unexpected select device notice")
|
|
}
|
|
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("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");
|
|
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<ManageResult, 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);
|
|
return;
|
|
}
|
|
}
|
|
let manage_result = manage_rx
|
|
.recv()
|
|
.expect("Problem receiving, unable to continue");
|
|
match manage_result {
|
|
Ok(r) => {
|
|
println!("Success! Result = {r:?}");
|
|
}
|
|
Err(e) => {
|
|
println!("Error! {:?}", e);
|
|
}
|
|
};
|
|
println!("Done");
|
|
}
|