diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/authenticator/examples | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
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/ctap1.rs | 203 | ||||
-rw-r--r-- | third_party/rust/authenticator/examples/ctap2.rs | 290 | ||||
-rw-r--r-- | third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs | 354 | ||||
-rw-r--r-- | third_party/rust/authenticator/examples/main.rs | 199 | ||||
-rw-r--r-- | third_party/rust/authenticator/examples/reset.rs | 137 | ||||
-rw-r--r-- | third_party/rust/authenticator/examples/set_pin.rs | 144 | ||||
-rw-r--r-- | third_party/rust/authenticator/examples/test_exclude_list.rs | 211 |
7 files changed, 1538 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/examples/ctap1.rs b/third_party/rust/authenticator/examples/ctap1.rs new file mode 100644 index 0000000000..444a2ef573 --- /dev/null +++ b/third_party/rust/authenticator/examples/ctap1.rs @@ -0,0 +1,203 @@ +/* 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, CtapVersion, RegisterArgsCtap1, SignArgsCtap1}, + statecallback::StateCallback, + AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult, + StatusUpdate, +}; +use getopts::Options; +use sha2::{Digest, Sha256}; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, io, thread}; + +fn u2f_get_key_handle_from_register_response(register_response: &[u8]) -> io::Result<Vec<u8>> { + if register_response[0] != 0x05 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Reserved byte not set correctly", + )); + } + + let key_handle_len = register_response[66] as usize; + let mut public_key = register_response.to_owned(); + let mut key_handle = public_key.split_off(67); + let _attestation = key_handle.split_off(key_handle_len); + + Ok(key_handle) +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options]", program); + 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("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms"); + #[cfg(feature = "webdriver")] + opts.optflag("w", "webdriver", "enable WebDriver virtual bus"); + + 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(CtapVersion::CTAP2) + .expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + #[cfg(feature = "webdriver")] + { + if matches.opt_present("webdriver") { + manager.add_webdriver_virtual_bus(); + } + } + + 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; + } + }; + + println!("Asking a security key to register now..."); + let challenge_str = format!( + "{}{}", + r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#, + r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"# + ); + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + + let mut application = Sha256::new(); + application.update(b"http://demo.yubico.com"); + let app_bytes = application.finalize().to_vec(); + + let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::<StatusUpdate>(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(StatusUpdate::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device unavailable: {}", dev_info) + } + Ok(StatusUpdate::Success { dev_info }) => { + println!("STATUS: success using device: {}", dev_info); + } + Ok(StatusUpdate::PinError(..)) + | Ok(StatusUpdate::SelectDeviceNotice) + | Ok(StatusUpdate::DeviceSelected(..)) => { + panic!("STATUS: This can't happen for CTAP1!"); + } + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let (register_tx, register_rx) = channel(); + let callback = StateCallback::new(Box::new(move |rv| { + register_tx.send(rv).unwrap(); + })); + + let ctap_args = RegisterArgsCtap1 { + flags, + challenge: chall_bytes.clone(), + application: app_bytes.clone(), + key_handles: vec![], + }; + manager + .register(timeout_ms, ctap_args.into(), status_tx.clone(), callback) + .expect("Couldn't register"); + + let register_result = register_rx + .recv() + .expect("Problem receiving, unable to continue"); + let (register_data, device_info) = match register_result { + Ok(RegisterResult::CTAP1(r, d)) => (r, d), + Ok(RegisterResult::CTAP2(..)) => panic!("Did not request CTAP2, but got CTAP2 results!"), + Err(e) => panic!("Registration failed {:?}", e), + }; + + println!("Register result: {}", base64::encode(®ister_data)); + println!("Device info: {}", &device_info); + println!(""); + println!("*********************************************************************"); + println!("Asking a security key to sign now, with the data from the register..."); + println!("*********************************************************************"); + + let credential = u2f_get_key_handle_from_register_response(®ister_data).unwrap(); + let key_handle = KeyHandle { + credential, + transports: AuthenticatorTransports::empty(), + }; + + let flags = SignFlags::empty(); + 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, + SignArgsCtap1 { + flags, + challenge: chall_bytes, + app_ids: vec![app_bytes], + key_handles: vec![key_handle], + } + .into(), + status_tx, + callback, + ) { + panic!("Couldn't register: {:?}", e); + } + + let sign_result = sign_rx + .recv() + .expect("Problem receiving, unable to continue"); + if let SignResult::CTAP1(_, handle_used, sign_data, device_info) = + sign_result.expect("Sign failed") + { + println!("Sign result: {}", base64::encode(&sign_data)); + println!("Key handle used: {}", base64::encode(&handle_used)); + println!("Device info: {}", &device_info); + println!("Done."); + } else { + panic!("Expected CTAP version 1 for sign result!"); + } +} diff --git a/third_party/rust/authenticator/examples/ctap2.rs b/third_party/rust/authenticator/examples/ctap2.rs new file mode 100644 index 0000000000..2067a0e921 --- /dev/null +++ b/third_party/rust/authenticator/examples/ctap2.rs @@ -0,0 +1,290 @@ +/* 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, CtapVersion, GetAssertionExtensions, GetAssertionOptions, + HmacSecretExtension, MakeCredentialsExtensions, MakeCredentialsOptions, RegisterArgsCtap2, + SignArgsCtap2, + }, + ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + }, + errors::PinError, + statecallback::StateCallback, + COSEAlgorithm, Pin, RegisterResult, SignResult, 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: {} [options]", program); + 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("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("s", "hmac_secret", "With hmac-secret"); + 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(CtapVersion::CTAP2) + .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", 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().to_vec(); + + // 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::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device 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)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let user = User { + id: "user_id".as_bytes().to_vec(), + icon: None, + name: Some("A. User".to_string()), + display_name: None, + }; + let origin = "https://example.com".to_string(); + let ctap_args = RegisterArgsCtap2 { + challenge: chall_bytes.clone(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: origin.clone(), + user: user.clone(), + 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], + }], + options: MakeCredentialsOptions { + resident_key: None, + user_verification: None, + }, + extensions: MakeCredentialsExtensions { + hmac_secret: if matches.opt_present("hmac_secret") { + Some(true) + } else { + None + }, + ..Default::default() + }, + pin: None, + }; + + let attestation_object; + let client_data; + 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().into(), + 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(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"), + Ok(RegisterResult::CTAP2(a, c)) => { + println!("Ok!"); + attestation_object = a; + client_data = c; + break; + } + Err(e) => panic!("Registration failed: {:?}", e), + }; + } + + println!("Register result: {:?}", &attestation_object); + println!("Collected client data: {:?}", &client_data); + + 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.auth_data.credential_data { + allow_list = vec![PublicKeyCredentialDescriptor { + id: cred_data.credential_id.clone(), + transports: vec![Transport::USB], + }]; + } else { + allow_list = Vec::new(); + } + + let ctap_args = SignArgsCtap2 { + challenge: chall_bytes, + origin, + relying_party_id: "example.com".to_string(), + allow_list, + options: GetAssertionOptions::default(), + extensions: GetAssertionExtensions { + hmac_secret: if matches.opt_present("hmac_secret") { + Some(HmacSecretExtension::new( + vec![ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34, + ], + None, + )) + } else { + None + }, + }, + pin: None, + }; + + 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().into(), + 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(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 sign results!"), + Ok(SignResult::CTAP2(assertion_object, _client_data)) => { + println!("Assertion Object: {:?}", assertion_object); + 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..ebbbd766ed --- /dev/null +++ b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs @@ -0,0 +1,354 @@ +/* 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, CtapVersion, GetAssertionOptions, MakeCredentialsOptions, + RegisterArgsCtap2, SignArgsCtap2, + }, + ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + }, + errors::PinError, + statecallback::StateCallback, + COSEAlgorithm, Pin, RegisterResult, SignResult, 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) { + 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: {} [options]", program); + print!("{}", opts.usage(&brief)); +} + +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().to_vec(); + + // 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::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device 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)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let user = User { + id: username.as_bytes().to_vec(), + icon: None, + name: Some(username.to_string()), + display_name: None, + }; + let origin = "https://example.com".to_string(); + let ctap_args = RegisterArgsCtap2 { + challenge: chall_bytes.clone(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: origin.clone(), + user: user.clone(), + pub_cred_params: vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + exclude_list: vec![PublicKeyCredentialDescriptor { + id: vec![], + transports: vec![Transport::USB, Transport::NFC], + }], + options: MakeCredentialsOptions { + resident_key: Some(true), + user_verification: Some(true), + }, + extensions: Default::default(), + pin: None, + }; + + let attestation_object; + let client_data; + 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().into(), + 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(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"), + Ok(RegisterResult::CTAP2(a, c)) => { + println!("Ok!"); + attestation_object = a; + client_data = c; + break; + } + Err(e) => panic!("Registration failed: {:?}", e), + }; + } + + println!("Register result: {:?}", &attestation_object); + println!("Collected client data: {:?}", &client_data); +} + +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(CtapVersion::CTAP2) + .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", 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; + } + }; + + for username in vec!["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::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device 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)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + let ctap_args = SignArgsCtap2 { + challenge: chall_bytes, + origin, + relying_party_id: "example.com".to_string(), + allow_list, + options: GetAssertionOptions::default(), + extensions: Default::default(), + pin: None, + }; + + 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().into(), + 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(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 sign results!"), + Ok(SignResult::CTAP2(assertion_object, _client_data)) => { + println!("Assertion Object: {:?}", assertion_object); + println!("-----------------------------------------------------------------"); + println!("Found credentials:"); + println!( + "{:?}", + assertion_object + .0 + .iter() + .map(|x| x.user.clone().unwrap().name.unwrap()) // Unwrapping here, as these shouldn't fail + .collect::<Vec<_>>() + ); + println!("-----------------------------------------------------------------"); + println!("Done."); + break; + } + Err(e) => panic!("Signing failed: {:?}", e), + } + } +} diff --git a/third_party/rust/authenticator/examples/main.rs b/third_party/rust/authenticator/examples/main.rs new file mode 100644 index 0000000000..decc877b34 --- /dev/null +++ b/third_party/rust/authenticator/examples/main.rs @@ -0,0 +1,199 @@ +/* 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, CtapVersion, RegisterArgsCtap1, SignArgsCtap1}, + statecallback::StateCallback, + AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult, + StatusUpdate, +}; +use getopts::Options; +use sha2::{Digest, Sha256}; +use std::sync::mpsc::{channel, RecvError}; +use std::{env, io, thread}; + +fn u2f_get_key_handle_from_register_response(register_response: &[u8]) -> io::Result<Vec<u8>> { + if register_response[0] != 0x05 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Reserved byte not set correctly", + )); + } + + let key_handle_len = register_response[66] as usize; + let mut public_key = register_response.to_owned(); + let mut key_handle = public_key.split_off(67); + let _attestation = key_handle.split_off(key_handle_len); + + Ok(key_handle) +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options]", program); + 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("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms"); + #[cfg(feature = "webdriver")] + opts.optflag("w", "webdriver", "enable WebDriver virtual bus"); + + 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(CtapVersion::CTAP1) + .expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + #[cfg(feature = "webdriver")] + { + if matches.opt_present("webdriver") { + manager.add_webdriver_virtual_bus(); + } + } + + 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; + } + }; + + println!("Asking a security key to register now..."); + let challenge_str = format!( + "{}{}", + r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#, + r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"# + ); + let mut challenge = Sha256::new(); + challenge.update(challenge_str.as_bytes()); + let chall_bytes = challenge.finalize().to_vec(); + + let mut application = Sha256::new(); + application.update(b"http://demo.yubico.com"); + let app_bytes = application.finalize().to_vec(); + + let flags = RegisterFlags::empty(); + + let (status_tx, status_rx) = channel::<StatusUpdate>(); + thread::spawn(move || loop { + match status_rx.recv() { + Ok(StatusUpdate::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device unavailable: {}", dev_info) + } + Ok(StatusUpdate::Success { dev_info }) => { + println!("STATUS: success using device: {}", dev_info); + } + Ok(StatusUpdate::PinError(..)) + | Ok(StatusUpdate::SelectDeviceNotice) + | Ok(StatusUpdate::DeviceSelected(..)) => { + panic!("STATUS: This can't happen for CTAP1!"); + } + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let (register_tx, register_rx) = channel(); + let callback = StateCallback::new(Box::new(move |rv| { + register_tx.send(rv).unwrap(); + })); + + let ctap_args = RegisterArgsCtap1 { + flags, + challenge: chall_bytes.clone(), + application: app_bytes.clone(), + key_handles: vec![], + }; + manager + .register(timeout_ms, ctap_args.into(), status_tx.clone(), callback) + .expect("Couldn't register"); + + let register_result = register_rx + .recv() + .expect("Problem receiving, unable to continue"); + let (register_data, device_info) = match register_result { + Ok(RegisterResult::CTAP1(r, d)) => (r, d), + Ok(RegisterResult::CTAP2(..)) => panic!("Did not request CTAP2, but got CTAP2 results!"), + Err(_) => panic!("Registration failed"), + }; + + println!("Register result: {}", base64::encode(®ister_data)); + println!("Device info: {}", &device_info); + println!("Asking a security key to sign now, with the data from the register..."); + let credential = u2f_get_key_handle_from_register_response(®ister_data).unwrap(); + let key_handle = KeyHandle { + credential, + transports: AuthenticatorTransports::empty(), + }; + + let flags = SignFlags::empty(); + 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, + SignArgsCtap1 { + flags, + challenge: chall_bytes, + app_ids: vec![app_bytes], + key_handles: vec![key_handle], + } + .into(), + status_tx, + callback, + ) { + panic!("Couldn't register: {:?}", e); + } + + let sign_result = sign_rx + .recv() + .expect("Problem receiving, unable to continue"); + if let SignResult::CTAP1(_, handle_used, sign_data, device_info) = + sign_result.expect("Sign failed") + { + println!("Sign result: {}", base64::encode(&sign_data)); + println!("Key handle used: {}", base64::encode(&handle_used)); + println!("Device info: {}", &device_info); + println!("Done."); + } else { + panic!("Expected CTAP version 1 for sign result!"); + } +} diff --git a/third_party/rust/authenticator/examples/reset.rs b/third_party/rust/authenticator/examples/reset.rs new file mode 100644 index 0000000000..96bc6e8535 --- /dev/null +++ b/third_party/rust/authenticator/examples/reset.rs @@ -0,0 +1,137 @@ +/* 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, CtapVersion}, + 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: {} [options]", program); + 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("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(CtapVersion::CTAP2) + .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", 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.clone(); + let callback = StateCallback::new(Box::new(move |rv| { + let _ = rs_tx.send(rv); + })); + + if let Err(e) = manager.reset(timeout_ms, status_tx.clone(), 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::DeviceSelected(dev_info)) => { + println!("STATUS: Continuing with device: {}", dev_info); + break; + } + Ok(StatusUpdate::PinError(..)) => 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..b5fd810e09 --- /dev/null +++ b/third_party/rust/authenticator/examples/set_pin.rs @@ -0,0 +1,144 @@ +/* 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, CtapVersion}, + statecallback::StateCallback, + Pin, PinError, 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: {} [options]", program); + 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("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(CtapVersion::CTAP2) + .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", 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::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device 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)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter current PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter current PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let (reset_tx, reset_rx) = channel(); + let rs_tx = reset_tx.clone(); + 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.clone(), 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..c5b019287b --- /dev/null +++ b/third_party/rust/authenticator/examples/test_exclude_list.rs @@ -0,0 +1,211 @@ +/* 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, CtapVersion, MakeCredentialsOptions, RegisterArgsCtap2, + }, + ctap2::commands::StatusCode, + ctap2::server::{ + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + }, + errors::{AuthenticatorError, CommandError, HIDError, PinError}, + statecallback::StateCallback, + COSEAlgorithm, Pin, RegisterResult, 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: {} [options]", program); + 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(CtapVersion::CTAP2) + .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().to_vec(); + + // 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::DeviceAvailable { dev_info }) => { + println!("STATUS: device available: {}", dev_info) + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + println!("STATUS: device 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)) => { + println!("STATUS: Continuing with device: {}", dev_info); + } + Ok(StatusUpdate::PinError(error, sender)) => match error { + PinError::PinRequired => { + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::InvalidPin(attempts) => { + println!( + "Wrong PIN! {}", + attempts.map_or(format!("Try again."), |a| format!( + "You have {} attempts left.", + a + )) + ); + let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ") + .expect("Failed to read PIN"); + sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN"); + continue; + } + PinError::PinAuthBlocked => { + panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.") + } + PinError::PinBlocked => { + panic!("Too many failed attempts. Your device has been blocked. Reset it.") + } + e => { + panic!("Unexpected error: {:?}", e) + } + }, + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + }); + + let user = User { + id: "user_id".as_bytes().to_vec(), + icon: None, + name: Some("A. User".to_string()), + display_name: None, + }; + let origin = "https://example.com".to_string(); + let mut ctap_args = RegisterArgsCtap2 { + challenge: chall_bytes.clone(), + relying_party: RelyingParty { + id: "example.com".to_string(), + name: None, + icon: None, + }, + origin: origin.clone(), + user: user.clone(), + pub_cred_params: vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256, + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::RS256, + }, + ], + exclude_list: vec![], + options: MakeCredentialsOptions { + resident_key: None, + user_verification: None, + }, + extensions: Default::default(), + pin: 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().into(), + 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(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"), + Ok(RegisterResult::CTAP2(a, _c)) => { + println!("Ok!"); + println!("Registering again with the key_handle we just got back. This should result in a 'already registered' error."); + let registered_key_handle = + a.auth_data.credential_data.unwrap().credential_id.clone(); + ctap_args.exclude_list = vec![PublicKeyCredentialDescriptor { + id: registered_key_handle.clone(), + transports: vec![Transport::USB], + }]; + continue; + } + Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode( + StatusCode::CredentialExcluded, + None, + )))) => { + println!("Got an 'already registered' error. Quitting."); + break; + } + Err(e) => panic!("Registration failed: {:?}", e), + }; + } + println!("Done") +} |