summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/examples
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/authenticator/examples
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/authenticator/examples')
-rw-r--r--third_party/rust/authenticator/examples/ctap2.rs285
-rw-r--r--third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs380
-rw-r--r--third_party/rust/authenticator/examples/interactive_management.rs810
-rw-r--r--third_party/rust/authenticator/examples/reset.rs133
-rw-r--r--third_party/rust/authenticator/examples/set_pin.rs146
-rw-r--r--third_party/rust/authenticator/examples/test_exclude_list.rs309
6 files changed, 2063 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/examples/ctap2.rs b/third_party/rust/authenticator/examples/ctap2.rs
new file mode 100644
index 0000000000..be742a5e0e
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap2.rs
@@ -0,0 +1,285 @@
+/* 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, RegisterArgs, SignArgs},
+ crypto::COSEAlgorithm,
+ ctap2::server::{
+ AuthenticationExtensionsClientInputs, CredentialProtectionPolicy,
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
+ PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
+ UserVerificationRequirement,
+ },
+ statecallback::StateCallback,
+ Pin, StatusPinUv, StatusUpdate,
+};
+use getopts::Options;
+use rand::{thread_rng, RngCore};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = env::args().collect();
+ let program = args[0].clone();
+
+ let rp_id = "example.com".to_string();
+ let app_id = "https://fido.example.com/myApp".to_string();
+
+ let mut opts = Options::new();
+ opts.optflag("h", "help", "print this help menu").optopt(
+ "t",
+ "timeout",
+ "timeout in seconds",
+ "SEC",
+ );
+ opts.optflag(
+ "a",
+ "app_id",
+ &format!("Using App ID {app_id} from origin 'https://{rp_id}'"),
+ );
+ 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()),
+ };
+ if matches.opt_present("help") {
+ print_usage(&program, opts);
+ return;
+ }
+
+ let using_app_id = matches.opt_present("app_id");
+
+ let mut manager =
+ AuthenticatorService::new().expect("The auth service should initialize safely");
+ 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);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ println!("Asking a security key to register now...");
+ let mut chall_bytes = [0u8; 32];
+ thread_rng().fill_bytes(&mut chall_bytes);
+
+ 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::SelectDeviceNotice) => {
+ println!("STATUS: Please select a device by touching one of them.");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ 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::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Ok(StatusUpdate::SelectResultNotice(_, _)) => {
+ panic!("Unexpected select device notice")
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let user = PublicKeyCredentialUserEntity {
+ id: "user_id".as_bytes().to_vec(),
+ name: Some("A. User".to_string()),
+ display_name: None,
+ };
+ // If we're testing AppID support, then register with an RP ID that isn't valid for the origin.
+ let relying_party = RelyingParty {
+ id: if using_app_id {
+ app_id.clone()
+ } else {
+ rp_id.clone()
+ },
+ name: None,
+ };
+ let ctap_args = RegisterArgs {
+ client_data_hash: chall_bytes,
+ relying_party,
+ origin: format!("https://{rp_id}"),
+ user,
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![PublicKeyCredentialDescriptor {
+ id: vec![
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f,
+ ],
+ transports: vec![Transport::USB, Transport::NFC],
+ }],
+ user_verification_req: UserVerificationRequirement::Preferred,
+ resident_key_req: ResidentKeyRequirement::Discouraged,
+ extensions: AuthenticationExtensionsClientInputs {
+ cred_props: Some(true),
+ hmac_create_secret: Some(matches.opt_present("hmac_secret")),
+ min_pin_length: Some(true),
+ credential_protection_policy: Some(
+ CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList,
+ ),
+ ..Default::default()
+ },
+ pin: None,
+ use_ctap1_fallback: fallback,
+ };
+
+ let attestation_object;
+ loop {
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx.clone(), callback) {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match register_result {
+ Ok(a) => {
+ println!("Ok!");
+ attestation_object = a;
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ println!("Register result: {:?}", &attestation_object);
+
+ println!();
+ println!("*********************************************************************");
+ println!("Asking a security key to sign now, with the data from the register...");
+ println!("*********************************************************************");
+
+ let allow_list;
+ if let Some(cred_data) = attestation_object.att_obj.auth_data.credential_data {
+ allow_list = vec![PublicKeyCredentialDescriptor {
+ id: cred_data.credential_id,
+ transports: vec![Transport::USB],
+ }];
+ } else {
+ allow_list = Vec::new();
+ }
+
+ let ctap_args = SignArgs {
+ client_data_hash: chall_bytes,
+ origin: format!("https://{rp_id}"),
+ relying_party_id: rp_id,
+ allow_list,
+ user_verification_req: UserVerificationRequirement::Preferred,
+ user_presence_req: true,
+ extensions: AuthenticationExtensionsClientInputs {
+ app_id: using_app_id.then(|| app_id.clone()),
+ ..Default::default()
+ },
+ pin: None,
+ use_ctap1_fallback: fallback,
+ };
+
+ loop {
+ let (sign_tx, sign_rx) = channel();
+
+ let callback = StateCallback::new(Box::new(move |rv| {
+ sign_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.sign(timeout_ms, ctap_args, status_tx, callback) {
+ panic!("Couldn't sign: {:?}", e);
+ }
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+
+ match sign_result {
+ Ok(assertion_object) => {
+ println!("Assertion Object: {assertion_object:?}");
+ if using_app_id {
+ println!(
+ "Used AppID: {}",
+ assertion_object
+ .extensions
+ .app_id
+ .expect("app id extension should be set")
+ );
+ }
+ println!("Done.");
+ break;
+ }
+ Err(e) => panic!("Signing failed: {:?}", e),
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs
new file mode 100644
index 0000000000..d19ccc6f9e
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs
@@ -0,0 +1,380 @@
+/* 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, RegisterArgs, SignArgs},
+ crypto::COSEAlgorithm,
+ ctap2::server::{
+ AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
+ PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
+ ResidentKeyRequirement, Transport, UserVerificationRequirement,
+ },
+ statecallback::StateCallback,
+ Pin, StatusPinUv, StatusUpdate,
+};
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, io, thread};
+use std::io::Write;
+
+fn print_usage(program: &str, opts: Options) {
+ println!("------------------------------------------------------------------------");
+ println!("This program registers 3x the same origin with different users and");
+ println!("requests 'discoverable credentials' for them.");
+ println!("After that, we try to log in to that origin and list all credentials found.");
+ println!("------------------------------------------------------------------------");
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn ask_user_choice(choices: &[PublicKeyCredentialUserEntity]) -> Option<usize> {
+ for (idx, op) in choices.iter().enumerate() {
+ println!("({idx}) \"{}\"", op.name.as_ref().unwrap());
+ }
+ println!("({}) Cancel", choices.len());
+
+ 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 Some(idx);
+ } else if idx == choices.len() {
+ println!();
+ return None;
+ }
+ println!("invalid input");
+ }
+ }
+}
+
+fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: u64) {
+ println!();
+ println!("*********************************************************************");
+ println!("Asking a security key to register now with user: {username}");
+ println!("*********************************************************************");
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com", "username": ""#,
+ username,
+ r#""}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().into();
+
+ 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::SelectDeviceNotice) => {
+ println!("STATUS: Please select a device by touching one of them.");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ 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::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Ok(StatusUpdate::SelectResultNotice(_, _)) => {
+ panic!("Unexpected select result notice")
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let user = PublicKeyCredentialUserEntity {
+ id: username.as_bytes().to_vec(),
+ name: Some(username.to_string()),
+ display_name: None,
+ };
+ let origin = "https://example.com".to_string();
+ let ctap_args = RegisterArgs {
+ client_data_hash: chall_bytes,
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ },
+ origin,
+ user,
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![PublicKeyCredentialDescriptor {
+ id: vec![],
+ transports: vec![Transport::USB, Transport::NFC],
+ }],
+ user_verification_req: UserVerificationRequirement::Required,
+ resident_key_req: ResidentKeyRequirement::Required,
+ extensions: AuthenticationExtensionsClientInputs {
+ cred_props: Some(true),
+ ..Default::default()
+ },
+ pin: None,
+ use_ctap1_fallback: false,
+ };
+
+ let attestation_object;
+ loop {
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx, callback) {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match register_result {
+ Ok(a) => {
+ println!("Ok!");
+ attestation_object = a;
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ println!("Register result: {:?}", &attestation_object);
+}
+
+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(
+ "s",
+ "skip_reg",
+ "Skip registration");
+
+ 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", 15) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ if !matches.opt_present("skip_reg") {
+ for username in &["A. User", "A. Nother", "Dr. Who"] {
+ register_user(&mut manager, username, timeout_ms)
+ }
+ }
+
+ println!();
+ println!("*********************************************************************");
+ println!("Asking a security key to sign now, with the data from the register...");
+ println!("*********************************************************************");
+
+ // Discovering creds:
+ let allow_list = Vec::new();
+ let origin = "https://example.com".to_string();
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com" "#,
+ );
+
+ 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::SelectDeviceNotice) => {
+ println!("STATUS: Please select a device by touching one of them.");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ 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::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Ok(StatusUpdate::SelectResultNotice(index_sender, users)) => {
+ println!("Multiple signatures returned. Select one or cancel.");
+ let idx = ask_user_choice(&users);
+ index_sender.send(idx).expect("Failed to send choice");
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().into();
+ let ctap_args = SignArgs {
+ client_data_hash: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list,
+ user_verification_req: UserVerificationRequirement::Required,
+ user_presence_req: true,
+ extensions: Default::default(),
+ pin: None,
+ use_ctap1_fallback: false,
+ };
+
+ loop {
+ let (sign_tx, sign_rx) = channel();
+
+ let callback = StateCallback::new(Box::new(move |rv| {
+ sign_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.sign(timeout_ms, ctap_args, status_tx, callback) {
+ panic!("Couldn't sign: {:?}", e);
+ }
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+
+ match sign_result {
+ Ok(assertion_object) => {
+ println!("Assertion Object: {assertion_object:?}");
+ println!("-----------------------------------------------------------------");
+ println!("Found credentials:");
+ println!(
+ "{:?}",
+ assertion_object.assertion.user.clone().unwrap().name.unwrap() // Unwrapping here, as these shouldn't fail
+ );
+ println!("-----------------------------------------------------------------");
+ println!("Done.");
+ break;
+ }
+ Err(e) => panic!("Signing failed: {:?}", e),
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/examples/interactive_management.rs b/third_party/rust/authenticator/examples/interactive_management.rs
new file mode 100644
index 0000000000..6ef0e9dcc3
--- /dev/null
+++ b/third_party/rust/authenticator/examples/interactive_management.rs
@@ -0,0 +1,810 @@
+/* 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");
+}
diff --git a/third_party/rust/authenticator/examples/reset.rs b/third_party/rust/authenticator/examples/reset.rs
new file mode 100644
index 0000000000..867ab5448f
--- /dev/null
+++ b/third_party/rust/authenticator/examples/reset.rs
@@ -0,0 +1,133 @@
+/* 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::StatusCode,
+ errors::{AuthenticatorError, CommandError, HIDError},
+ statecallback::StateCallback,
+ StatusUpdate,
+};
+use getopts::Options;
+use std::env;
+use std::sync::mpsc::{channel, RecvError};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+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", 25) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ println!(
+ "NOTE: Please unplug all devices, type in 'yes' and plug in the device that should be reset."
+ );
+ loop {
+ let mut s = String::new();
+ println!("ATTENTION: Resetting a device will wipe all credentials! Do you wish to continue? [yes/N]");
+ std::io::stdin()
+ .read_line(&mut s)
+ .expect("Did not enter a correct string");
+ let trimmed = s.trim();
+ if trimmed.is_empty() || trimmed == "N" || trimmed == "n" {
+ println!("Exiting without reset.");
+ return;
+ }
+ if trimmed == "y" {
+ println!("Please type in the whole word 'yes'");
+ continue;
+ }
+ if trimmed == "yes" {
+ break;
+ }
+ }
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ let (reset_tx, reset_rx) = channel();
+ let rs_tx = reset_tx;
+ let callback = StateCallback::new(Box::new(move |rv| {
+ let _ = rs_tx.send(rv);
+ }));
+
+ if let Err(e) = manager.reset(timeout_ms, status_tx, callback) {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ loop {
+ match status_rx.recv() {
+ Ok(StatusUpdate::SelectDeviceNotice) => {
+ println!("ERROR: Please unplug all other tokens that should not be reset!");
+ // Needed to give the tokens enough time to start blinking
+ // otherwise we may cancel pre-maturely and this binary will hang
+ std::thread::sleep(std::time::Duration::from_millis(200));
+ manager.cancel().unwrap();
+ return;
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ break;
+ }
+ Ok(StatusUpdate::PinUvError(..)) => panic!("Reset should never ask for a PIN!"),
+ Ok(_) => { /* Ignore all other updates */ }
+ Err(RecvError) => {
+ println!("RecvError");
+ return;
+ }
+ }
+ }
+
+ let reset_result = reset_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match reset_result {
+ Ok(()) => {
+ println!("Token successfully reset!");
+ }
+ Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
+ StatusCode::NotAllowed,
+ _,
+ )))) => {
+ println!("Resetting is only allowed within the first 10 seconds after powering up.");
+ println!("Please unplug your device, plug it back in and try again.");
+ }
+ Err(e) => panic!("Reset failed: {:?}", e),
+ };
+}
diff --git a/third_party/rust/authenticator/examples/set_pin.rs b/third_party/rust/authenticator/examples/set_pin.rs
new file mode 100644
index 0000000000..5534ca08fd
--- /dev/null
+++ b/third_party/rust/authenticator/examples/set_pin.rs
@@ -0,0 +1,146 @@
+/* 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, statecallback::StateCallback, Pin, StatusPinUv,
+ StatusUpdate,
+};
+use getopts::Options;
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+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", 25) {
+ 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 new_pin = rpassword::prompt_password_stderr("Enter new PIN: ").expect("Failed to read PIN");
+ let repeat_new_pin =
+ rpassword::prompt_password_stderr("Enter it again: ").expect("Failed to read PIN");
+ if new_pin != repeat_new_pin {
+ println!("PINs did not match!");
+ return;
+ }
+
+ 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::SelectDeviceNotice) => {
+ println!("STATUS: Please select a device by touching one of them.");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ 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::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Ok(StatusUpdate::SelectResultNotice(_, _)) => {
+ panic!("Unexpected select device notice")
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let (reset_tx, reset_rx) = channel();
+ let rs_tx = reset_tx;
+ let callback = StateCallback::new(Box::new(move |rv| {
+ let _ = rs_tx.send(rv);
+ }));
+
+ if let Err(e) = manager.set_pin(timeout_ms, Pin::new(&new_pin), status_tx, callback) {
+ panic!("Couldn't call set_pin: {:?}", e);
+ };
+
+ let reset_result = reset_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match reset_result {
+ Ok(()) => {
+ println!("PIN successfully set!");
+ }
+ Err(e) => panic!("Setting PIN failed: {:?}", e),
+ };
+}
diff --git a/third_party/rust/authenticator/examples/test_exclude_list.rs b/third_party/rust/authenticator/examples/test_exclude_list.rs
new file mode 100644
index 0000000000..e24c49d05a
--- /dev/null
+++ b/third_party/rust/authenticator/examples/test_exclude_list.rs
@@ -0,0 +1,309 @@
+/* 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, RegisterArgs, SignArgs},
+ crypto::COSEAlgorithm,
+ ctap2::commands::StatusCode,
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
+ PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
+ UserVerificationRequirement,
+ },
+ errors::{AuthenticatorError, CommandError, HIDError, UnsupportedOption},
+ statecallback::StateCallback,
+ Pin, StatusPinUv, StatusUpdate,
+};
+
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+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", 25) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com"}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().into();
+
+ 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::SelectDeviceNotice) => {
+ println!("STATUS: Please select a device by touching one of them.");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ 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::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Ok(StatusUpdate::SelectResultNotice(_, _)) => {
+ panic!("Unexpected select device notice")
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let user = PublicKeyCredentialUserEntity {
+ id: "user_id".as_bytes().to_vec(),
+ name: Some("A. User".to_string()),
+ display_name: None,
+ };
+ let origin = "https://example.com".to_string();
+ let mut ctap_args = RegisterArgs {
+ client_data_hash: chall_bytes,
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ },
+ origin: origin.clone(),
+ user,
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![],
+ user_verification_req: UserVerificationRequirement::Preferred,
+ resident_key_req: ResidentKeyRequirement::Discouraged,
+ extensions: Default::default(),
+ pin: None,
+ use_ctap1_fallback: false,
+ };
+
+ let mut registered_key_handle = None;
+ loop {
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.register(timeout_ms, ctap_args.clone(), status_tx.clone(), callback)
+ {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match register_result {
+ Ok(a) => {
+ println!("Ok!");
+ println!("Registering again with the key_handle we just got back. This should result in a 'already registered' error.");
+ let key_handle = a
+ .att_obj
+ .auth_data
+ .credential_data
+ .unwrap()
+ .credential_id
+ .clone();
+ let pub_key = PublicKeyCredentialDescriptor {
+ id: key_handle,
+ transports: vec![Transport::USB],
+ };
+ ctap_args.exclude_list = vec![pub_key.clone()];
+ registered_key_handle = Some(pub_key);
+ continue;
+ }
+ Err(AuthenticatorError::CredentialExcluded) => {
+ println!("Got an 'already registered' error, as expected.");
+ if ctap_args.exclude_list.len() > 1 {
+ println!("Quitting.");
+ break;
+ }
+ println!("Extending the list to contain more invalid handles.");
+ let registered_handle = ctap_args.exclude_list[0].clone();
+ ctap_args.exclude_list = vec![];
+ for ii in 0..10 {
+ ctap_args.exclude_list.push(PublicKeyCredentialDescriptor {
+ id: vec![ii; 50],
+ transports: vec![Transport::USB],
+ });
+ }
+ ctap_args.exclude_list.push(registered_handle);
+ continue;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ // Signing
+ let mut ctap_args = SignArgs {
+ client_data_hash: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list: vec![],
+ extensions: Default::default(),
+ pin: None,
+ use_ctap1_fallback: false,
+ user_verification_req: UserVerificationRequirement::Preferred,
+ user_presence_req: true,
+ };
+
+ let mut no_cred_errors_done = false;
+ loop {
+ let (sign_tx, sign_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ sign_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.sign(timeout_ms, ctap_args.clone(), status_tx.clone(), callback) {
+ panic!("Couldn't sign: {:?}", e);
+ };
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match sign_result {
+ Ok(_) => {
+ if !no_cred_errors_done {
+ panic!("Should have errored out with NoCredentials, but it succeeded.");
+ }
+ println!("Successfully signed!");
+ if ctap_args.allow_list.len() > 1 {
+ println!("Quitting.");
+ break;
+ }
+ println!("Signing again with a long allow_list that needs pre-flighting.");
+ let registered_handle = registered_key_handle.as_ref().unwrap().clone();
+ ctap_args.allow_list = vec![];
+ for ii in 0..10 {
+ ctap_args.allow_list.push(PublicKeyCredentialDescriptor {
+ id: vec![ii; 50],
+ transports: vec![Transport::USB],
+ });
+ }
+ ctap_args.allow_list.push(registered_handle);
+ continue;
+ }
+ Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
+ StatusCode::NoCredentials,
+ None,
+ ))))
+ | Err(AuthenticatorError::UnsupportedOption(UnsupportedOption::EmptyAllowList)) => {
+ if ctap_args.allow_list.is_empty() {
+ // Try again with a list of false creds. We should end up here again.
+ println!(
+ "Got an 'no credentials' error, as expected with an empty allow-list."
+ );
+ println!("Extending the list to contain only fake handles.");
+ ctap_args.allow_list = vec![];
+ for ii in 0..10 {
+ ctap_args.allow_list.push(PublicKeyCredentialDescriptor {
+ id: vec![ii; 50],
+ transports: vec![Transport::USB],
+ });
+ }
+ } else {
+ println!(
+ "Got an 'no credentials' error, as expected with an all-fake allow-list."
+ );
+ println!("Extending the list to contain one valid handle.");
+ let registered_handle = registered_key_handle.as_ref().unwrap().clone();
+ ctap_args.allow_list = vec![registered_handle];
+ no_cred_errors_done = true;
+ }
+ continue;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+ println!("Done")
+}