Bug 1530373 - vendor the latest version of authenticator. r=dveditz,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D163864
This commit is contained in:
John Schanck 2022-12-06 18:09:53 +00:00
Родитель d7afdc2f9a
Коммит 4832b7d0f1
184 изменённых файлов: 33321 добавлений и 1937 удалений

48
Cargo.lock сгенерированный
Просмотреть файл

@ -378,18 +378,29 @@ dependencies = [
[[package]]
name = "authenticator"
version = "0.3.1"
version = "0.4.0-alpha.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08cee7a0952628fde958e149507c2bb321ab4fccfafd225da0b20adc956ef88a"
checksum = "671c5d49eab8c93b8aea310cef8a7fd0846eb9417e3c31e4f4d6ec7012aae842"
dependencies = [
"base64",
"bitflags",
"cfg-if 1.0.0",
"core-foundation",
"devd-rs",
"libc",
"libudev",
"log",
"rand 0.7.999",
"memoffset 0.6.5",
"nom 7.1.1",
"nss-gk-api",
"pkcs11-bindings",
"rand 0.8.5",
"runloop",
"serde",
"serde_bytes",
"serde_cbor",
"serde_json",
"sha2",
"winapi",
]
@ -2461,6 +2472,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.11.999"
@ -3770,6 +3787,21 @@ dependencies = [
"nsstring",
]
[[package]]
name = "nss-gk-api"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352"
dependencies = [
"bindgen 0.63.0",
"mozbuild",
"once_cell",
"pkcs11-bindings",
"serde",
"serde_derive",
"toml",
]
[[package]]
name = "nss_build_common"
version = "0.1.0"
@ -4763,6 +4795,16 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.144"

Просмотреть файл

@ -243,10 +243,6 @@ criteria = "safe-to-deploy"
version = "0.26.1"
criteria = "safe-to-deploy"
[[exemptions.authenticator]]
version = "0.3.1"
criteria = "safe-to-deploy"
[[exemptions.base64]]
version = "0.13.0"
criteria = "safe-to-deploy"

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1800
third_party/rust/authenticator/Cargo.lock сгенерированный поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

130
third_party/rust/authenticator/Cargo.toml поставляемый
Просмотреть файл

@ -3,26 +3,39 @@
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "authenticator"
version = "0.3.1"
authors = ["J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>", "Kyle Machulis <kyle@nonpolynomial.com>"]
version = "0.4.0-alpha.3"
authors = [
"J.C. Jones <jc@mozilla.com>",
"Tim Taubert <ttaubert@mozilla.com>",
"Kyle Machulis <kyle@nonpolynomial.com>",
]
description = "Library for interacting with CTAP1/2 security keys for Web Authentication. Used by Firefox."
keywords = ["ctap2", "u2f", "fido", "webauthn"]
categories = ["cryptography", "hardware-support", "os"]
readme = "README.md"
keywords = [
"ctap2",
"u2f",
"fido",
"webauthn",
]
categories = [
"cryptography",
"hardware-support",
"os",
]
license = "MPL-2.0"
repository = "https://github.com/mozilla/authenticator-rs/"
[dependencies.base64]
version = "^0.10"
optional = true
version = "^0.13"
[dependencies.bitflags]
version = "1.0"
@ -32,14 +45,42 @@ version = "0.5"
features = ["serde"]
optional = true
[dependencies.cfg-if]
version = "1.0"
[dependencies.libc]
version = "0.2"
[dependencies.log]
version = "0.4"
[dependencies.nom]
version = "^7.1.1"
features = ["std"]
default-features = false
[dependencies.nss-gk-api]
version = "0.2.1"
optional = true
[dependencies.openssl]
version = "0.10"
optional = true
[dependencies.openssl-sys]
version = "0.9"
optional = true
[dependencies.pkcs11-bindings]
version = "0.1.4"
optional = true
[dependencies.rand]
version = "0.7"
version = "0.8"
[dependencies.ring]
version = "0.16"
optional = true
[dependencies.runloop]
version = "0.1.0"
@ -47,50 +88,89 @@ version = "0.1.0"
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.serde_bytes]
version = "0.11"
[dependencies.serde_cbor]
version = "0.11"
[dependencies.serde_json]
version = "1.0"
optional = true
[dependencies.sha2]
version = "^0.10.0"
[dependencies.tokio]
version = "0.2"
features = ["macros"]
version = "1.17"
features = [
"macros",
"rt-multi-thread",
]
optional = true
[dependencies.warp]
version = "0.2.4"
version = "0.3.2"
optional = true
[dev-dependencies.assert_matches]
version = "1.2"
[dev-dependencies.base64]
version = "^0.10"
[dev-dependencies.env_logger]
version = "^0.6"
[dev-dependencies.getopts]
version = "^0.2"
[dev-dependencies.sha2]
version = "^0.8.2"
[dev-dependencies.rpassword]
version = "5.0"
[build-dependencies.bindgen]
version = "^0.51"
version = "^0.58.1"
optional = true
[features]
binding-recompile = ["bindgen"]
webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"]
crypto_dummy = []
crypto_nss = [
"nss-gk-api",
"pkcs11-bindings",
]
crypto_openssl = [
"openssl",
"openssl-sys",
]
crypto_ring = ["ring"]
default = ["crypto_nss"]
gecko = ["nss-gk-api/gecko"]
webdriver = [
"bytes",
"warp",
"tokio",
]
[target."cfg(target_os = \"freebsd\")".dependencies.devd-rs]
version = "0.3"
[target."cfg(target_os = \"linux\")".dependencies.libudev]
version = "^0.2"
[target."cfg(target_os = \"macos\")".dependencies.core-foundation]
version = "0.9"
[target."cfg(target_os = \"windows\")".dependencies.memoffset]
version = "0.6"
[target."cfg(target_os = \"windows\")".dependencies.winapi]
version = "^0.3"
features = ["handleapi", "hidclass", "hidpi", "hidusage", "setupapi"]
features = [
"handleapi",
"hidclass",
"hidpi",
"hidusage",
"setupapi",
]
[badges.maintenance]
status = "actively-developed"

2
third_party/rust/authenticator/build.rs поставляемый
Просмотреть файл

@ -45,6 +45,8 @@ fn main() {
"ioctl_aarch64be.rs"
} else if cfg!(all(target_arch = "s390x", target_endian = "big")) {
"ioctl_s390xbe.rs"
} else if cfg!(all(target_arch = "riscv64", target_endian = "little")) {
"ioctl_riscv64.rs"
} else {
panic!("architecture not supported");
};

203
third_party/rust/authenticator/examples/ctap1.rs поставляемый Normal file
Просмотреть файл

@ -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(&register_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(&register_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!");
}
}

290
third_party/rust/authenticator/examples/ctap2.rs поставляемый Normal file
Просмотреть файл

@ -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),
}
}
}

Просмотреть файл

@ -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),
}
}
}

Просмотреть файл

@ -3,8 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use authenticator::{
authenticatorservice::AuthenticatorService, statecallback::StateCallback,
AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate,
authenticatorservice::{AuthenticatorService, CtapVersion, RegisterArgsCtap1, SignArgsCtap1},
statecallback::StateCallback,
AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult,
StatusUpdate,
};
use getopts::Options;
use sha2::{Digest, Sha256};
@ -53,15 +55,15 @@ fn main() {
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => panic!(f.to_string()),
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");
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();
@ -92,13 +94,13 @@ fn main() {
r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"#
);
let mut challenge = Sha256::default();
challenge.input(challenge_str.as_bytes());
let chall_bytes = challenge.result().to_vec();
let mut challenge = Sha256::new();
challenge.update(challenge_str.as_bytes());
let chall_bytes = challenge.finalize().to_vec();
let mut application = Sha256::default();
application.input(b"http://demo.yubico.com");
let app_bytes = application.result().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();
@ -114,6 +116,11 @@ fn main() {
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;
@ -126,22 +133,24 @@ fn main() {
register_tx.send(rv).unwrap();
}));
let ctap_args = RegisterArgsCtap1 {
flags,
challenge: chall_bytes.clone(),
application: app_bytes.clone(),
key_handles: vec![],
};
manager
.register(
flags,
timeout_ms,
chall_bytes.clone(),
app_bytes.clone(),
vec![],
status_tx.clone(),
callback,
)
.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) = register_result.expect("Registration failed");
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(&register_data));
println!("Device info: {}", &device_info);
@ -160,11 +169,14 @@ fn main() {
}));
if let Err(e) = manager.sign(
flags,
timeout_ms,
chall_bytes,
vec![app_bytes],
vec![key_handle],
SignArgsCtap1 {
flags,
challenge: chall_bytes,
app_ids: vec![app_bytes],
key_handles: vec![key_handle],
}
.into(),
status_tx,
callback,
) {
@ -174,10 +186,14 @@ fn main() {
let sign_result = sign_rx
.recv()
.expect("Problem receiving, unable to continue");
let (_, 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.");
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!");
}
}

137
third_party/rust/authenticator/examples/reset.rs поставляемый Normal file
Просмотреть файл

@ -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),
};
}

144
third_party/rust/authenticator/examples/set_pin.rs поставляемый Normal file
Просмотреть файл

@ -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),
};
}

211
third_party/rust/authenticator/examples/test_exclude_list.rs поставляемый Normal file
Просмотреть файл

@ -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")
}

Просмотреть файл

@ -2,22 +2,118 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::consts::PARAMETER_SIZE;
use crate::ctap2::commands::client_pin::Pin;
pub use crate::ctap2::commands::get_assertion::{
GetAssertionExtensions, GetAssertionOptions, HmacSecretExtension,
};
pub use crate::ctap2::commands::make_credentials::{
MakeCredentialsExtensions, MakeCredentialsOptions,
};
use crate::ctap2::server::{
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User,
};
use crate::errors::*;
use crate::manager::Manager;
use crate::statecallback::StateCallback;
use std::sync::{mpsc::Sender, Arc, Mutex};
use crate::consts::PARAMETER_SIZE;
use crate::errors::*;
use crate::statecallback::StateCallback;
// TODO(MS): Once U2FManager gets completely removed, this can be removed as well
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CtapVersion {
CTAP1,
CTAP2,
}
#[derive(Debug, Clone)]
pub struct RegisterArgsCtap1 {
pub flags: crate::RegisterFlags,
pub challenge: Vec<u8>,
pub application: crate::AppId,
pub key_handles: Vec<crate::KeyHandle>,
}
#[derive(Debug, Clone)]
pub struct RegisterArgsCtap2 {
pub challenge: Vec<u8>,
pub relying_party: RelyingParty,
pub origin: String,
pub user: User,
pub pub_cred_params: Vec<PublicKeyCredentialParameters>,
pub exclude_list: Vec<PublicKeyCredentialDescriptor>,
pub options: MakeCredentialsOptions,
pub extensions: MakeCredentialsExtensions,
pub pin: Option<Pin>,
}
#[derive(Debug)]
pub enum RegisterArgs {
CTAP1(RegisterArgsCtap1),
CTAP2(RegisterArgsCtap2),
}
impl From<RegisterArgsCtap1> for RegisterArgs {
fn from(args: RegisterArgsCtap1) -> Self {
RegisterArgs::CTAP1(args)
}
}
impl From<RegisterArgsCtap2> for RegisterArgs {
fn from(args: RegisterArgsCtap2) -> Self {
RegisterArgs::CTAP2(args)
}
}
#[derive(Debug, Clone)]
pub struct SignArgsCtap1 {
pub flags: crate::SignFlags,
pub challenge: Vec<u8>,
pub app_ids: Vec<crate::AppId>,
pub key_handles: Vec<crate::KeyHandle>,
}
#[derive(Debug, Clone)]
pub struct SignArgsCtap2 {
pub challenge: Vec<u8>,
pub origin: String,
pub relying_party_id: String,
pub allow_list: Vec<PublicKeyCredentialDescriptor>,
pub options: GetAssertionOptions,
pub extensions: GetAssertionExtensions,
pub pin: Option<Pin>,
// Todo: Extensions
}
#[derive(Debug)]
pub enum SignArgs {
CTAP1(SignArgsCtap1),
CTAP2(SignArgsCtap2),
}
impl From<SignArgsCtap1> for SignArgs {
fn from(args: SignArgsCtap1) -> Self {
SignArgs::CTAP1(args)
}
}
impl From<SignArgsCtap2> for SignArgs {
fn from(args: SignArgsCtap2) -> Self {
SignArgs::CTAP2(args)
}
}
#[derive(Debug, Clone, Default)]
pub struct AssertionExtensions {
pub hmac_secret: Option<HmacSecretExtension>,
}
pub trait AuthenticatorTransport {
/// The implementation of this method must return quickly and should
/// report its status via the status and callback methods
fn register(
&mut self,
flags: crate::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: crate::AppId,
key_handles: Vec<crate::KeyHandle>,
ctap_args: RegisterArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()>;
@ -26,20 +122,31 @@ pub trait AuthenticatorTransport {
/// report its status via the status and callback methods
fn sign(
&mut self,
flags: crate::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<crate::AppId>,
key_handles: Vec<crate::KeyHandle>,
ctap_args: SignArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()>;
fn cancel(&mut self) -> crate::Result<()>;
fn reset(
&mut self,
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()>;
fn set_pin(
&mut self,
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()>;
}
pub struct AuthenticatorService {
transports: Vec<Arc<Mutex<Box<dyn AuthenticatorTransport + Send>>>>,
ctap_version: CtapVersion,
}
fn clone_and_configure_cancellation_callback<T>(
@ -62,9 +169,10 @@ fn clone_and_configure_cancellation_callback<T>(
}
impl AuthenticatorService {
pub fn new() -> crate::Result<Self> {
pub fn new(ctap_version: CtapVersion) -> crate::Result<Self> {
Ok(Self {
transports: Vec::new(),
ctap_version,
})
}
@ -78,9 +186,16 @@ impl AuthenticatorService {
}
pub fn add_u2f_usb_hid_platform_transports(&mut self) {
match crate::U2FManager::new() {
Ok(token) => self.add_transport(Box::new(token)),
Err(e) => error!("Could not add U2F HID transport: {}", e),
if self.ctap_version == CtapVersion::CTAP1 {
match crate::U2FManager::new() {
Ok(token) => self.add_transport(Box::new(token)),
Err(e) => error!("Could not add U2F HID transport: {}", e),
}
} else {
match Manager::new() {
Ok(token) => self.add_transport(Box::new(token)),
Err(e) => error!("Could not add CTAP2 HID transport: {}", e),
}
}
}
@ -97,20 +212,30 @@ impl AuthenticatorService {
pub fn register(
&mut self,
flags: crate::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: crate::AppId,
key_handles: Vec<crate::KeyHandle>,
ctap_args: RegisterArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()> {
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
match ctap_args {
RegisterArgs::CTAP1(a) => self.register_ctap1(timeout, a, status, callback),
RegisterArgs::CTAP2(a) => self.register_ctap2(timeout, a, status, callback),
}
}
fn register_ctap1(
&mut self,
timeout: u64,
args: RegisterArgsCtap1,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()> {
if args.challenge.len() != PARAMETER_SIZE || args.application.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
for key_handle in &args.key_handles {
if key_handle.credential.len() >= 256 {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
}
@ -136,11 +261,46 @@ impl AuthenticatorService {
);
transport_mutex.lock().unwrap().register(
flags,
timeout,
challenge.clone(),
application.clone(),
key_handles.clone(),
RegisterArgs::CTAP1(args.clone()),
status.clone(),
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
)?;
}
Ok(())
}
fn register_ctap2(
&mut self,
timeout: u64,
args: RegisterArgsCtap2,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()> {
let iterable_transports = self.transports.clone();
if iterable_transports.is_empty() {
return Err(AuthenticatorError::NoConfiguredTransports);
}
debug!(
"register called with {} transports, iterable is {}",
self.transports.len(),
iterable_transports.len()
);
for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
let mut transports_to_cancel = iterable_transports.clone();
transports_to_cancel.remove(idx);
debug!(
"register transports_to_cancel {}",
transports_to_cancel.len()
);
transport_mutex.lock().unwrap().register(
timeout,
RegisterArgs::CTAP2(args.clone()),
status.clone(),
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
)?;
@ -151,30 +311,40 @@ impl AuthenticatorService {
pub fn sign(
&mut self,
flags: crate::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<crate::AppId>,
key_handles: Vec<crate::KeyHandle>,
ctap_args: SignArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
if challenge.len() != PARAMETER_SIZE {
match ctap_args {
SignArgs::CTAP1(a) => self.sign_ctap1(timeout, a, status, callback),
SignArgs::CTAP2(a) => self.sign_ctap2(timeout, a, status, callback),
}
}
pub fn sign_ctap1(
&mut self,
timeout: u64,
args: SignArgsCtap1,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
if args.challenge.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
if app_ids.is_empty() {
if args.app_ids.is_empty() {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
for app_id in &app_ids {
for app_id in &args.app_ids {
if app_id.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
for key_handle in &args.key_handles {
if key_handle.credential.len() >= 256 {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
}
@ -189,11 +359,35 @@ impl AuthenticatorService {
transports_to_cancel.remove(idx);
transport_mutex.lock().unwrap().sign(
flags,
timeout,
challenge.clone(),
app_ids.clone(),
key_handles.clone(),
SignArgs::CTAP1(args.clone()),
status.clone(),
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
)?;
}
Ok(())
}
pub fn sign_ctap2(
&mut self,
timeout: u64,
args: SignArgsCtap2,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
let iterable_transports = self.transports.clone();
if iterable_transports.is_empty() {
return Err(AuthenticatorError::NoConfiguredTransports);
}
for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
let mut transports_to_cancel = iterable_transports.clone();
transports_to_cancel.remove(idx);
transport_mutex.lock().unwrap().sign(
timeout,
SignArgs::CTAP2(args.clone()),
status.clone(),
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
)?;
@ -213,6 +407,74 @@ impl AuthenticatorService {
Ok(())
}
pub fn reset(
&mut self,
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
let iterable_transports = self.transports.clone();
if iterable_transports.is_empty() {
return Err(AuthenticatorError::NoConfiguredTransports);
}
debug!(
"reset called with {} transports, iterable is {}",
self.transports.len(),
iterable_transports.len()
);
for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
let mut transports_to_cancel = iterable_transports.clone();
transports_to_cancel.remove(idx);
debug!("reset transports_to_cancel {}", transports_to_cancel.len());
transport_mutex.lock().unwrap().reset(
timeout,
status.clone(),
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
)?;
}
Ok(())
}
pub fn set_pin(
&mut self,
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
let iterable_transports = self.transports.clone();
if iterable_transports.is_empty() {
return Err(AuthenticatorError::NoConfiguredTransports);
}
debug!(
"reset called with {} transports, iterable is {}",
self.transports.len(),
iterable_transports.len()
);
for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
let mut transports_to_cancel = iterable_transports.clone();
transports_to_cancel.remove(idx);
debug!("reset transports_to_cancel {}", transports_to_cancel.len());
transport_mutex.lock().unwrap().set_pin(
timeout,
new_pin.clone(),
status.clone(),
clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
)?;
}
Ok(())
}
}
////////////////////////////////////////////////////////////////////////
@ -221,10 +483,17 @@ impl AuthenticatorService {
#[cfg(test)]
mod tests {
use super::{AuthenticatorService, AuthenticatorTransport};
use super::{
AuthenticatorService, AuthenticatorTransport, CtapVersion, Pin,
PublicKeyCredentialDescriptor, RegisterArgs, RegisterArgsCtap1, RegisterArgsCtap2,
SignArgs, SignArgsCtap1, SignArgsCtap2, User,
};
use crate::consts::Capability;
use crate::consts::PARAMETER_SIZE;
use crate::ctap2::server::RelyingParty;
use crate::statecallback::StateCallback;
use crate::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate};
use crate::{RegisterResult, SignResult};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Sender};
use std::sync::Arc;
@ -257,7 +526,7 @@ mod tests {
version_major: 1,
version_minor: 2,
version_build: 3,
cap_flags: 0,
cap_flags: Capability::empty(),
}
}
}
@ -265,16 +534,13 @@ mod tests {
impl AuthenticatorTransport for TestTransportDriver {
fn register(
&mut self,
_flags: crate::RegisterFlags,
_timeout: u64,
_challenge: Vec<u8>,
_application: crate::AppId,
_key_handles: Vec<crate::KeyHandle>,
_args: RegisterArgs,
_status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()> {
if self.consent {
let rv = Ok((vec![0u8; 16], self.dev_info()));
let rv = Ok(RegisterResult::CTAP1(vec![0u8; 16], self.dev_info()));
thread::spawn(move || callback.call(rv));
}
Ok(())
@ -282,16 +548,18 @@ mod tests {
fn sign(
&mut self,
_flags: crate::SignFlags,
_timeout: u64,
_challenge: Vec<u8>,
_app_ids: Vec<crate::AppId>,
_key_handles: Vec<crate::KeyHandle>,
_ctap_args: SignArgs,
_status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
if self.consent {
let rv = Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info()));
let rv = Ok(SignResult::CTAP1(
vec![0u8; 0],
vec![0u8; 0],
vec![0u8; 0],
self.dev_info(),
));
thread::spawn(move || callback.call(rv));
}
Ok(())
@ -307,6 +575,25 @@ mod tests {
|_| Ok(()),
)
}
fn reset(
&mut self,
_timeout: u64,
_status: Sender<crate::StatusUpdate>,
_callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
unimplemented!();
}
fn set_pin(
&mut self,
_timeout: u64,
_new_pin: Pin,
_status: Sender<crate::StatusUpdate>,
_callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
unimplemented!();
}
}
fn mk_key() -> KeyHandle {
@ -329,16 +616,19 @@ mod tests {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
assert_matches!(
s.register(
RegisterFlags::empty(),
1_000,
vec![],
mk_appid(),
vec![mk_key()],
RegisterArgsCtap1 {
challenge: vec![],
flags: RegisterFlags::empty(),
application: mk_appid(),
key_handles: vec![mk_key()],
}
.into(),
status_tx.clone(),
StateCallback::new(Box::new(move |_rv| {})),
)
@ -348,11 +638,14 @@ mod tests {
assert_matches!(
s.sign(
SignFlags::empty(),
1_000,
vec![],
vec![mk_appid()],
vec![mk_key()],
SignArgsCtap1 {
flags: SignFlags::empty(),
challenge: vec![],
app_ids: vec![mk_appid()],
key_handles: vec![mk_key()]
}
.into(),
status_tx,
StateCallback::new(Box::new(move |_rv| {})),
)
@ -366,16 +659,19 @@ mod tests {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
assert_matches!(
s.register(
RegisterFlags::empty(),
1_000,
mk_challenge(),
vec![],
vec![mk_key()],
RegisterArgsCtap1 {
challenge: mk_challenge(),
flags: RegisterFlags::empty(),
application: vec![],
key_handles: vec![mk_key()],
}
.into(),
status_tx.clone(),
StateCallback::new(Box::new(move |_rv| {})),
)
@ -385,11 +681,14 @@ mod tests {
assert_matches!(
s.sign(
SignFlags::empty(),
1_000,
mk_challenge(),
vec![],
vec![mk_key()],
SignArgsCtap1 {
flags: SignFlags::empty(),
challenge: mk_challenge(),
app_ids: vec![],
key_handles: vec![mk_key()]
}
.into(),
status_tx,
StateCallback::new(Box::new(move |_rv| {})),
)
@ -406,16 +705,19 @@ mod tests {
// This test yields OKs.
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
assert_matches!(
s.register(
RegisterFlags::empty(),
100,
mk_challenge(),
mk_appid(),
vec![],
RegisterArgsCtap1 {
challenge: mk_challenge(),
flags: RegisterFlags::empty(),
application: mk_appid(),
key_handles: vec![],
}
.into(),
status_tx.clone(),
StateCallback::new(Box::new(move |_rv| {})),
),
@ -424,11 +726,14 @@ mod tests {
assert_matches!(
s.sign(
SignFlags::empty(),
100,
mk_challenge(),
vec![mk_appid()],
vec![],
SignArgsCtap1 {
flags: SignFlags::empty(),
challenge: mk_challenge(),
app_ids: vec![mk_appid()],
key_handles: vec![]
}
.into(),
status_tx,
StateCallback::new(Box::new(move |_rv| {})),
),
@ -446,16 +751,19 @@ mod tests {
transports: AuthenticatorTransports::USB,
};
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
assert_matches!(
s.register(
RegisterFlags::empty(),
1_000,
mk_challenge(),
mk_appid(),
vec![large_key.clone()],
RegisterArgsCtap1 {
challenge: mk_challenge(),
flags: RegisterFlags::empty(),
application: mk_appid(),
key_handles: vec![large_key.clone()],
}
.into(),
status_tx.clone(),
StateCallback::new(Box::new(move |_rv| {})),
)
@ -465,11 +773,14 @@ mod tests {
assert_matches!(
s.sign(
SignFlags::empty(),
1_000,
mk_challenge(),
vec![mk_appid()],
vec![large_key],
SignArgsCtap1 {
flags: SignFlags::empty(),
challenge: mk_challenge(),
app_ids: vec![mk_appid()],
key_handles: vec![large_key]
}
.into(),
status_tx,
StateCallback::new(Box::new(move |_rv| {})),
)
@ -478,19 +789,84 @@ mod tests {
);
}
#[test]
fn test_large_keys_ctap2() {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let large_key = KeyHandle {
credential: vec![0; 1000],
transports: AuthenticatorTransports::USB,
};
let mut s = AuthenticatorService::new(CtapVersion::CTAP2).unwrap();
s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
let ctap2_register_args = RegisterArgsCtap2 {
challenge: mk_challenge(),
relying_party: RelyingParty {
id: "example.com".to_string(),
name: None,
icon: None,
},
origin: "example.com".to_string(),
user: User {
id: "user_id".as_bytes().to_vec(),
icon: None,
name: Some("A. User".to_string()),
display_name: None,
},
pub_cred_params: vec![],
exclude_list: vec![(&large_key).into()],
options: Default::default(),
extensions: Default::default(),
pin: None,
};
assert!(s
.register(
1_000,
ctap2_register_args.into(),
status_tx.clone(),
StateCallback::new(Box::new(move |_rv| {})),
)
.is_ok(),);
let ctap2_sign_args = SignArgsCtap2 {
challenge: mk_challenge(),
origin: "example.com".to_string(),
relying_party_id: "example.com".to_string(),
allow_list: vec![(&large_key).into()],
options: Default::default(),
extensions: Default::default(),
pin: None,
};
assert!(s
.sign(
1_000,
ctap2_sign_args.into(),
status_tx,
StateCallback::new(Box::new(move |_rv| {})),
)
.is_ok(),);
}
#[test]
fn test_no_transports() {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
assert_matches!(
s.register(
RegisterFlags::empty(),
1_000,
mk_challenge(),
mk_appid(),
vec![mk_key()],
RegisterArgsCtap1 {
challenge: mk_challenge(),
flags: RegisterFlags::empty(),
application: mk_appid(),
key_handles: vec![mk_key()],
}
.into(),
status_tx.clone(),
StateCallback::new(Box::new(move |_rv| {})),
)
@ -500,11 +876,14 @@ mod tests {
assert_matches!(
s.sign(
SignFlags::empty(),
1_000,
mk_challenge(),
vec![mk_appid()],
vec![mk_key()],
SignArgsCtap1 {
flags: SignFlags::empty(),
challenge: mk_challenge(),
app_ids: vec![mk_appid()],
key_handles: vec![mk_key()]
}
.into(),
status_tx,
StateCallback::new(Box::new(move |_rv| {})),
)
@ -523,7 +902,7 @@ mod tests {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
let ttd_one = TestTransportDriver::new(true).unwrap();
let ttd_two = TestTransportDriver::new(false).unwrap();
let ttd_three = TestTransportDriver::new(false).unwrap();
@ -539,11 +918,14 @@ mod tests {
let callback = StateCallback::new(Box::new(move |_rv| {}));
assert!(s
.register(
RegisterFlags::empty(),
1_000,
mk_challenge(),
mk_appid(),
vec![],
RegisterArgsCtap1 {
challenge: mk_challenge(),
flags: RegisterFlags::empty(),
application: mk_appid(),
key_handles: vec![],
}
.into(),
status_tx,
callback.clone(),
)
@ -560,7 +942,7 @@ mod tests {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
let ttd_one = TestTransportDriver::new(true).unwrap();
let ttd_two = TestTransportDriver::new(false).unwrap();
let ttd_three = TestTransportDriver::new(false).unwrap();
@ -576,11 +958,14 @@ mod tests {
let callback = StateCallback::new(Box::new(move |_rv| {}));
assert!(s
.sign(
SignFlags::empty(),
1_000,
mk_challenge(),
vec![mk_appid()],
vec![mk_key()],
SignArgsCtap1 {
flags: SignFlags::empty(),
challenge: mk_challenge(),
app_ids: vec![mk_appid()],
key_handles: vec![mk_key()]
}
.into(),
status_tx,
callback.clone(),
)
@ -597,7 +982,7 @@ mod tests {
init();
let (status_tx, _) = channel::<StatusUpdate>();
let mut s = AuthenticatorService::new().unwrap();
let mut s = AuthenticatorService::new(CtapVersion::CTAP1).unwrap();
// Let both of these race which one provides consent.
let ttd_one = TestTransportDriver::new(true).unwrap();
let ttd_two = TestTransportDriver::new(true).unwrap();
@ -611,11 +996,14 @@ mod tests {
let callback = StateCallback::new(Box::new(move |_rv| {}));
assert!(s
.register(
RegisterFlags::empty(),
1_000,
mk_challenge(),
mk_appid(),
vec![],
RegisterArgsCtap1 {
challenge: mk_challenge(),
flags: RegisterFlags::empty(),
application: mk_appid(),
key_handles: vec![],
}
.into(),
status_tx,
callback.clone(),
)

61
third_party/rust/authenticator/src/capi.rs поставляемый
Просмотреть файл

@ -2,7 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::authenticatorservice::AuthenticatorService;
use crate::authenticatorservice::{
AuthenticatorService, CtapVersion, RegisterArgsCtap1, SignArgsCtap1,
};
use crate::errors;
use crate::statecallback::StateCallback;
use crate::{RegisterResult, SignResult};
@ -46,7 +48,7 @@ unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
/// The handle returned by this method must be freed by the caller.
#[no_mangle]
pub extern "C" fn rust_u2f_mgr_new() -> *mut AuthenticatorService {
if let Ok(mut mgr) = AuthenticatorService::new() {
if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP1) {
mgr.add_detected_transports();
Box::into_raw(Box::new(mgr))
} else {
@ -61,7 +63,7 @@ pub extern "C" fn rust_u2f_mgr_new() -> *mut AuthenticatorService {
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut AuthenticatorService) {
if !mgr.is_null() {
Box::from_raw(mgr);
drop(Box::from_raw(mgr));
}
}
@ -92,7 +94,7 @@ pub unsafe extern "C" fn rust_u2f_app_ids_add(
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) {
if !ids.is_null() {
Box::from_raw(ids);
drop(Box::from_raw(ids));
}
}
@ -127,7 +129,7 @@ pub unsafe extern "C" fn rust_u2f_khs_add(
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
if !khs.is_null() {
Box::from_raw(khs);
drop(Box::from_raw(khs));
}
}
@ -147,6 +149,22 @@ pub unsafe extern "C" fn rust_u2f_result_error(res: *const U2FResult) -> u8 {
0 /* No error, the request succeeded. */
}
/// # Safety
///
/// This method must be used before rust_u2f_resbuf_copy
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_resbuf_contains(res: *const U2FResult, bid: u8) -> bool {
if res.is_null() {
return false;
}
if let U2FResult::Success(ref bufs) = *res {
return bufs.contains_key(&bid);
}
false
}
/// # Safety
///
/// This method must be used before rust_u2f_resbuf_copy
@ -201,7 +219,7 @@ pub unsafe extern "C" fn rust_u2f_resbuf_copy(
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) {
if !res.is_null() {
Box::from_raw(res);
drop(Box::from_raw(res));
}
}
@ -249,7 +267,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_register(
let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| {
let result = match rv {
Ok((registration, dev_info)) => {
Ok(RegisterResult::CTAP1(registration, dev_info)) => {
let mut bufs = HashMap::new();
bufs.insert(RESBUF_ID_REGISTRATION, registration);
bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name);
@ -259,21 +277,22 @@ pub unsafe extern "C" fn rust_u2f_mgr_register(
bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]);
U2FResult::Success(bufs)
}
Ok(RegisterResult::CTAP2(..)) => U2FResult::Error(
errors::AuthenticatorError::VersionMismatch("rust_u2f_mgr_register", 1),
),
Err(e) => U2FResult::Error(e),
};
callback(tid, Box::into_raw(Box::new(result)));
}));
let res = (*mgr).register(
let ctap_args = RegisterArgsCtap1 {
flags,
timeout,
challenge,
application,
key_handles,
status_tx,
state_callback,
);
};
let res = (*mgr).register(timeout, ctap_args.into(), status_tx, state_callback);
if res.is_ok() {
tid
@ -329,7 +348,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
let tid = new_tid();
let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| {
let result = match rv {
Ok((app_id, key_handle, signature, dev_info)) => {
Ok(SignResult::CTAP1(app_id, key_handle, signature, dev_info)) => {
let mut bufs = HashMap::new();
bufs.insert(RESBUF_ID_KEYHANDLE, key_handle);
bufs.insert(RESBUF_ID_SIGNATURE, signature);
@ -341,6 +360,9 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]);
U2FResult::Success(bufs)
}
Ok(SignResult::CTAP2(..)) => U2FResult::Error(
errors::AuthenticatorError::VersionMismatch("rust_u2f_mgr_sign", 1),
),
Err(e) => U2FResult::Error(e),
};
@ -348,11 +370,14 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
}));
let res = (*mgr).sign(
flags,
timeout,
challenge,
app_ids,
key_handles,
SignArgsCtap1 {
flags,
challenge,
app_ids,
key_handles,
}
.into(),
status_tx,
state_callback,
);

98
third_party/rust/authenticator/src/consts.rs поставляемый
Просмотреть файл

@ -5,8 +5,18 @@
// Allow dead code in this module, since it's all packet consts anyways.
#![allow(dead_code)]
use serde::Serialize;
pub const MAX_HID_RPT_SIZE: usize = 64;
/// Minimum size of the U2F Raw Message header (FIDO v1.x) in extended mode,
/// including expected response length (L<sub>e</sub>).
///
/// Fields `CLA`, `INS`, `P1` and `P2` are 1 byte each, and L<sub>e</sub> is 3
/// bytes. If there is a data payload, add 2 bytes (L<sub>c</sub> is 3 bytes,
/// and L<sub>e</sub> is 2 bytes).
pub const U2FAPDUHEADER_SIZE: usize = 7;
pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
pub const TYPE_MASK: u8 = 0x80;
pub const TYPE_INIT: u8 = 0x80;
@ -30,13 +40,65 @@ pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation versio
pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms
pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms
// U2FHID native commands
pub const U2FHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only
pub const U2FHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame
pub const U2FHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command
pub const U2FHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization
pub const U2FHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink
pub const U2FHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response
// CTAPHID native commands
const CTAPHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only
const CTAPHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame
const CTAPHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command
const CTAPHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization
const CTAPHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink
const CTAPHID_CBOR: u8 = TYPE_INIT | 0x10; // Encapsulated CBOR encoded message
const CTAPHID_CANCEL: u8 = TYPE_INIT | 0x11; // Cancel outstanding requests
const CTAPHID_KEEPALIVE: u8 = TYPE_INIT | 0x3b; // Keepalive sent to authenticator every 100ms and whenever a status changes
const CTAPHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum HIDCmd {
Ping,
Msg,
Lock,
Init,
Wink,
Cbor,
Cancel,
Keepalive,
Error,
Unknown(u8),
}
impl Into<u8> for HIDCmd {
fn into(self) -> u8 {
match self {
HIDCmd::Ping => CTAPHID_PING,
HIDCmd::Msg => CTAPHID_MSG,
HIDCmd::Lock => CTAPHID_LOCK,
HIDCmd::Init => CTAPHID_INIT,
HIDCmd::Wink => CTAPHID_WINK,
HIDCmd::Cbor => CTAPHID_CBOR,
HIDCmd::Cancel => CTAPHID_CANCEL,
HIDCmd::Keepalive => CTAPHID_KEEPALIVE,
HIDCmd::Error => CTAPHID_ERROR,
HIDCmd::Unknown(v) => v,
}
}
}
impl From<u8> for HIDCmd {
fn from(v: u8) -> HIDCmd {
match v {
CTAPHID_PING => HIDCmd::Ping,
CTAPHID_MSG => HIDCmd::Msg,
CTAPHID_LOCK => HIDCmd::Lock,
CTAPHID_INIT => HIDCmd::Init,
CTAPHID_WINK => HIDCmd::Wink,
CTAPHID_CBOR => HIDCmd::Cbor,
CTAPHID_CANCEL => HIDCmd::Cancel,
CTAPHID_KEEPALIVE => HIDCmd::Keepalive,
CTAPHID_ERROR => HIDCmd::Error,
v => HIDCmd::Unknown(v),
}
}
}
// U2FHID_MSG commands
pub const U2F_VENDOR_FIRST: u8 = TYPE_INIT | 0x40; // First vendor defined command
@ -57,8 +119,26 @@ pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is regi
// U2FHID_INIT command defines
pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge
pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command
pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command
bitflags! {
#[derive(Serialize)]
pub struct Capability: u8 {
const WINK = 0x01;
const LOCK = 0x02;
const CBOR = 0x04;
const NMSG = 0x08;
}
}
impl Capability {
pub fn has_fido1(self) -> bool {
!self.contains(Capability::NMSG)
}
pub fn has_fido2(self) -> bool {
self.contains(Capability::CBOR)
}
}
// Low-level error codes. Return as negatives.

42
third_party/rust/authenticator/src/crypto/dummy.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,42 @@
use super::{ByteBuf, COSEKey, ECDHSecret, ECDSACurve};
use serde::Serialize;
/*
This is a dummy implementation for CI, to avoid having to install NSS or openSSL in the CI-pipeline
*/
pub type Result<T> = std::result::Result<T, BackendError>;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum BackendError {}
pub(crate) fn serialize_key(_curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> {
// Copy from NSS
let length = key[1..].len() / 2;
let chunks: Vec<_> = key[1..].chunks_exact(length).collect();
Ok((
ByteBuf::from(chunks[0].to_vec()),
ByteBuf::from(chunks[1].to_vec()),
))
}
pub(crate) fn encapsulate(_key: &COSEKey) -> Result<ECDHSecret> {
unimplemented!()
}
pub(crate) fn encrypt(
_key: &[u8],
_plain_text: &[u8], /*PlainText*/
) -> Result<Vec<u8> /*CypherText*/> {
unimplemented!()
}
pub(crate) fn decrypt(
_key: &[u8],
_cypher_text: &[u8], /*CypherText*/
) -> Result<Vec<u8> /*PlainText*/> {
unimplemented!()
}
pub(crate) fn authenticate(_token: &[u8], _input: &[u8]) -> Result<Vec<u8>> {
unimplemented!()
}

924
third_party/rust/authenticator/src/crypto/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,924 @@
/* 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 crate::errors::AuthenticatorError;
use crate::{ctap2::commands::CommandError, transport::errors::HIDError};
use serde::{
de::{Error as SerdeError, MapAccess, Unexpected, Visitor},
ser::SerializeMap,
Deserialize, Deserializer, Serialize, Serializer,
};
use serde_bytes::ByteBuf;
use serde_cbor::Value;
use std::convert::TryFrom;
use std::fmt;
cfg_if::cfg_if! {
if #[cfg(feature = "crypto_ring")] {
#[path = "ring.rs"]
pub mod imp;
} else if #[cfg(feature = "crypto_openssl")] {
#[path = "openssl.rs"]
pub mod imp;
} else if #[cfg(feature = "crypto_dummy")] {
#[path = "dummy.rs"]
pub mod imp;
} else {
#[path = "nss.rs"]
pub mod imp;
}
}
pub(crate) use imp::{authenticate, decrypt, encapsulate, encrypt, serialize_key, BackendError};
/// An ECDSACurve identifier. You probably will never need to alter
/// or use this value, as it is set inside the Credential for you.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ECDSACurve {
// +---------+-------+----------+------------------------------------+
// | Name | Value | Key Type | Description |
// +---------+-------+----------+------------------------------------+
// | P-256 | 1 | EC2 | NIST P-256 also known as secp256r1 |
// | P-384 | 2 | EC2 | NIST P-384 also known as secp384r1 |
// | P-521 | 3 | EC2 | NIST P-521 also known as secp521r1 |
// | X25519 | 4 | OKP | X25519 for use w/ ECDH only |
// | X448 | 5 | OKP | X448 for use w/ ECDH only |
// | Ed25519 | 6 | OKP | Ed25519 for use w/ EdDSA only |
// | Ed448 | 7 | OKP | Ed448 for use w/ EdDSA only |
// +---------+-------+----------+------------------------------------+
/// Identifies this curve as SECP256R1 (X9_62_PRIME256V1 in OpenSSL)
SECP256R1 = 1,
/// Identifies this curve as SECP384R1
SECP384R1 = 2,
/// Identifies this curve as SECP521R1
SECP521R1 = 3,
/// Identifieds this as OKP X25519 for use w/ ECDH only
X25519 = 4,
/// Identifieds this as OKP X448 for use w/ ECDH only
X448 = 5,
/// Identifieds this as OKP Ed25519 for use w/ EdDSA only
Ed25519 = 6,
/// Identifieds this as OKP Ed448 for use w/ EdDSA only
Ed448 = 7,
}
impl TryFrom<u64> for ECDSACurve {
type Error = CryptoError;
fn try_from(i: u64) -> Result<Self, Self::Error> {
match i {
1 => Ok(ECDSACurve::SECP256R1),
2 => Ok(ECDSACurve::SECP384R1),
3 => Ok(ECDSACurve::SECP521R1),
4 => Ok(ECDSACurve::X25519),
5 => Ok(ECDSACurve::X448),
6 => Ok(ECDSACurve::Ed25519),
7 => Ok(ECDSACurve::Ed448),
_ => Err(CryptoError::UnknownKeyType),
}
}
}
/// A COSE signature algorithm, indicating the type of key and hash type
/// that should be used.
/// see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum COSEAlgorithm {
// /// Identifies this key as ECDSA (recommended SECP256R1) with SHA256 hashing
// //#[serde(alias = "ECDSA_SHA256")]
// ES256 = -7, // recommends curve SECP256R1
// /// Identifies this key as ECDSA (recommended SECP384R1) with SHA384 hashing
// //#[serde(alias = "ECDSA_SHA384")]
// ES384 = -35, // recommends curve SECP384R1
// /// Identifies this key as ECDSA (recommended SECP521R1) with SHA512 hashing
// //#[serde(alias = "ECDSA_SHA512")]
// ES512 = -36, // recommends curve SECP521R1
// /// Identifies this key as RS256 aka RSASSA-PKCS1-v1_5 w/ SHA-256
// RS256 = -257,
// /// Identifies this key as RS384 aka RSASSA-PKCS1-v1_5 w/ SHA-384
// RS384 = -258,
// /// Identifies this key as RS512 aka RSASSA-PKCS1-v1_5 w/ SHA-512
// RS512 = -259,
// /// Identifies this key as PS256 aka RSASSA-PSS w/ SHA-256
// PS256 = -37,
// /// Identifies this key as PS384 aka RSASSA-PSS w/ SHA-384
// PS384 = -38,
// /// Identifies this key as PS512 aka RSASSA-PSS w/ SHA-512
// PS512 = -39,
// /// Identifies this key as EdDSA (likely curve ed25519)
// EDDSA = -8,
// /// Identifies this as an INSECURE RS1 aka RSASSA-PKCS1-v1_5 using SHA-1. This is not
// /// used by validators, but can exist in some windows hello tpm's
// INSECURE_RS1 = -65535,
INSECURE_RS1 = -65535, // RSASSA-PKCS1-v1_5 using SHA-1
RS512 = -259, // RSASSA-PKCS1-v1_5 using SHA-512
RS384 = -258, // RSASSA-PKCS1-v1_5 using SHA-384
RS256 = -257, // RSASSA-PKCS1-v1_5 using SHA-256
ES256K = -47, // ECDSA using secp256k1 curve and SHA-256
HSS_LMS = -46, // HSS/LMS hash-based digital signature
SHAKE256 = -45, // SHAKE-256 512-bit Hash Value
SHA512 = -44, // SHA-2 512-bit Hash
SHA384 = -43, // SHA-2 384-bit Hash
RSAES_OAEP_SHA_512 = -42, // RSAES-OAEP w/ SHA-512
RSAES_OAEP_SHA_256 = -41, // RSAES-OAEP w/ SHA-256
RSAES_OAEP_RFC_8017_default = -40, // RSAES-OAEP w/ SHA-1
PS512 = -39, // RSASSA-PSS w/ SHA-512
PS384 = -38, // RSASSA-PSS w/ SHA-384
PS256 = -37, // RSASSA-PSS w/ SHA-256
ES512 = -36, // ECDSA w/ SHA-512
ES384 = -35, // ECDSA w/ SHA-384
ECDH_SS_A256KW = -34, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key
ECDH_SS_A192KW = -33, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key
ECDH_SS_A128KW = -32, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key
ECDH_ES_A256KW = -31, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key
ECDH_ES_A192KW = -30, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key
ECDH_ES_A128KW = -29, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key
ECDH_SS_HKDF512 = -28, // ECDH SS w/ HKDF - generate key directly
ECDH_SS_HKDF256 = -27, // ECDH SS w/ HKDF - generate key directly
ECDH_ES_HKDF512 = -26, // ECDH ES w/ HKDF - generate key directly
ECDH_ES_HKDF256 = -25, // ECDH ES w/ HKDF - generate key directly
SHAKE128 = -18, // SHAKE-128 256-bit Hash Value
SHA512_256 = -17, // SHA-2 512-bit Hash truncated to 256-bits
SHA256 = -16, // SHA-2 256-bit Hash
SHA256_64 = -15, // SHA-2 256-bit Hash truncated to 64-bits
SHA1 = -14, // SHA-1 Hash
Direct_HKDF_AES256 = -13, // Shared secret w/ AES-MAC 256-bit key
Direct_HKDF_AES128 = -12, // Shared secret w/ AES-MAC 128-bit key
Direct_HKDF_SHA512 = -11, // Shared secret w/ HKDF and SHA-512
Direct_HKDF_SHA256 = -10, // Shared secret w/ HKDF and SHA-256
EDDSA = -8, // EdDSA
ES256 = -7, // ECDSA w/ SHA-256
Direct = -6, // Direct use of CEK
A256KW = -5, // AES Key Wrap w/ 256-bit key
A192KW = -4, // AES Key Wrap w/ 192-bit key
A128KW = -3, // AES Key Wrap w/ 128-bit key
A128GCM = 1, // AES-GCM mode w/ 128-bit key, 128-bit tag
A192GCM = 2, // AES-GCM mode w/ 192-bit key, 128-bit tag
A256GCM = 3, // AES-GCM mode w/ 256-bit key, 128-bit tag
HMAC256_64 = 4, // HMAC w/ SHA-256 truncated to 64 bits
HMAC256_256 = 5, // HMAC w/ SHA-256
HMAC384_384 = 6, // HMAC w/ SHA-384
HMAC512_512 = 7, // HMAC w/ SHA-512
AES_CCM_16_64_128 = 10, // AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce
AES_CCM_16_64_256 = 11, // AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce
AES_CCM_64_64_128 = 12, // AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce
AES_CCM_64_64_256 = 13, // AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce
AES_MAC_128_64 = 14, // AES-MAC 128-bit key, 64-bit tag
AES_MAC_256_64 = 15, // AES-MAC 256-bit key, 64-bit tag
ChaCha20_Poly1305 = 24, // ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag
AES_MAC_128_128 = 25, // AES-MAC 128-bit key, 128-bit tag
AES_MAC_256_128 = 26, // AES-MAC 256-bit key, 128-bit tag
AES_CCM_16_128_128 = 30, // AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce
AES_CCM_16_128_256 = 31, // AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce
AES_CCM_64_128_128 = 32, // AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce
AES_CCM_64_128_256 = 33, // AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce
IV_GENERATION = 34, // For doing IV generation for symmetric algorithms.
}
impl Serialize for COSEAlgorithm {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
COSEAlgorithm::RS512 => serializer.serialize_i16(-259),
COSEAlgorithm::RS384 => serializer.serialize_i16(-258),
COSEAlgorithm::RS256 => serializer.serialize_i16(-257),
COSEAlgorithm::ES256K => serializer.serialize_i8(-47),
COSEAlgorithm::HSS_LMS => serializer.serialize_i8(-46),
COSEAlgorithm::SHAKE256 => serializer.serialize_i8(-45),
COSEAlgorithm::SHA512 => serializer.serialize_i8(-44),
COSEAlgorithm::SHA384 => serializer.serialize_i8(-43),
COSEAlgorithm::RSAES_OAEP_SHA_512 => serializer.serialize_i8(-42),
COSEAlgorithm::RSAES_OAEP_SHA_256 => serializer.serialize_i8(-41),
COSEAlgorithm::RSAES_OAEP_RFC_8017_default => serializer.serialize_i8(-40),
COSEAlgorithm::PS512 => serializer.serialize_i8(-39),
COSEAlgorithm::PS384 => serializer.serialize_i8(-38),
COSEAlgorithm::PS256 => serializer.serialize_i8(-37),
COSEAlgorithm::ES512 => serializer.serialize_i8(-36),
COSEAlgorithm::ES384 => serializer.serialize_i8(-35),
COSEAlgorithm::ECDH_SS_A256KW => serializer.serialize_i8(-34),
COSEAlgorithm::ECDH_SS_A192KW => serializer.serialize_i8(-33),
COSEAlgorithm::ECDH_SS_A128KW => serializer.serialize_i8(-32),
COSEAlgorithm::ECDH_ES_A256KW => serializer.serialize_i8(-31),
COSEAlgorithm::ECDH_ES_A192KW => serializer.serialize_i8(-30),
COSEAlgorithm::ECDH_ES_A128KW => serializer.serialize_i8(-29),
COSEAlgorithm::ECDH_SS_HKDF512 => serializer.serialize_i8(-28),
COSEAlgorithm::ECDH_SS_HKDF256 => serializer.serialize_i8(-27),
COSEAlgorithm::ECDH_ES_HKDF512 => serializer.serialize_i8(-26),
COSEAlgorithm::ECDH_ES_HKDF256 => serializer.serialize_i8(-25),
COSEAlgorithm::SHAKE128 => serializer.serialize_i8(-18),
COSEAlgorithm::SHA512_256 => serializer.serialize_i8(-17),
COSEAlgorithm::SHA256 => serializer.serialize_i8(-16),
COSEAlgorithm::SHA256_64 => serializer.serialize_i8(-15),
COSEAlgorithm::SHA1 => serializer.serialize_i8(-14),
COSEAlgorithm::Direct_HKDF_AES256 => serializer.serialize_i8(-13),
COSEAlgorithm::Direct_HKDF_AES128 => serializer.serialize_i8(-12),
COSEAlgorithm::Direct_HKDF_SHA512 => serializer.serialize_i8(-11),
COSEAlgorithm::Direct_HKDF_SHA256 => serializer.serialize_i8(-10),
COSEAlgorithm::EDDSA => serializer.serialize_i8(-8),
COSEAlgorithm::ES256 => serializer.serialize_i8(-7),
COSEAlgorithm::Direct => serializer.serialize_i8(-6),
COSEAlgorithm::A256KW => serializer.serialize_i8(-5),
COSEAlgorithm::A192KW => serializer.serialize_i8(-4),
COSEAlgorithm::A128KW => serializer.serialize_i8(-3),
COSEAlgorithm::A128GCM => serializer.serialize_i8(1),
COSEAlgorithm::A192GCM => serializer.serialize_i8(2),
COSEAlgorithm::A256GCM => serializer.serialize_i8(3),
COSEAlgorithm::HMAC256_64 => serializer.serialize_i8(4),
COSEAlgorithm::HMAC256_256 => serializer.serialize_i8(5),
COSEAlgorithm::HMAC384_384 => serializer.serialize_i8(6),
COSEAlgorithm::HMAC512_512 => serializer.serialize_i8(7),
COSEAlgorithm::AES_CCM_16_64_128 => serializer.serialize_i8(10),
COSEAlgorithm::AES_CCM_16_64_256 => serializer.serialize_i8(11),
COSEAlgorithm::AES_CCM_64_64_128 => serializer.serialize_i8(12),
COSEAlgorithm::AES_CCM_64_64_256 => serializer.serialize_i8(13),
COSEAlgorithm::AES_MAC_128_64 => serializer.serialize_i8(14),
COSEAlgorithm::AES_MAC_256_64 => serializer.serialize_i8(15),
COSEAlgorithm::ChaCha20_Poly1305 => serializer.serialize_i8(24),
COSEAlgorithm::AES_MAC_128_128 => serializer.serialize_i8(25),
COSEAlgorithm::AES_MAC_256_128 => serializer.serialize_i8(26),
COSEAlgorithm::AES_CCM_16_128_128 => serializer.serialize_i8(30),
COSEAlgorithm::AES_CCM_16_128_256 => serializer.serialize_i8(31),
COSEAlgorithm::AES_CCM_64_128_128 => serializer.serialize_i8(32),
COSEAlgorithm::AES_CCM_64_128_256 => serializer.serialize_i8(33),
COSEAlgorithm::IV_GENERATION => serializer.serialize_i8(34),
COSEAlgorithm::INSECURE_RS1 => serializer.serialize_i32(-65535),
}
}
}
impl<'de> Deserialize<'de> for COSEAlgorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct COSEAlgorithmVisitor;
impl<'de> Visitor<'de> for COSEAlgorithmVisitor {
type Value = COSEAlgorithm;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a signed integer")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: SerdeError,
{
COSEAlgorithm::try_from(v).map_err(|_| {
SerdeError::invalid_value(Unexpected::Signed(v), &"valid COSEAlgorithm")
})
}
}
deserializer.deserialize_any(COSEAlgorithmVisitor)
}
}
impl TryFrom<i64> for COSEAlgorithm {
type Error = CryptoError;
fn try_from(i: i64) -> Result<Self, Self::Error> {
match i {
-259 => Ok(COSEAlgorithm::RS512),
-258 => Ok(COSEAlgorithm::RS384),
-257 => Ok(COSEAlgorithm::RS256),
-47 => Ok(COSEAlgorithm::ES256K),
-46 => Ok(COSEAlgorithm::HSS_LMS),
-45 => Ok(COSEAlgorithm::SHAKE256),
-44 => Ok(COSEAlgorithm::SHA512),
-43 => Ok(COSEAlgorithm::SHA384),
-42 => Ok(COSEAlgorithm::RSAES_OAEP_SHA_512),
-41 => Ok(COSEAlgorithm::RSAES_OAEP_SHA_256),
-40 => Ok(COSEAlgorithm::RSAES_OAEP_RFC_8017_default),
-39 => Ok(COSEAlgorithm::PS512),
-38 => Ok(COSEAlgorithm::PS384),
-37 => Ok(COSEAlgorithm::PS256),
-36 => Ok(COSEAlgorithm::ES512),
-35 => Ok(COSEAlgorithm::ES384),
-34 => Ok(COSEAlgorithm::ECDH_SS_A256KW),
-33 => Ok(COSEAlgorithm::ECDH_SS_A192KW),
-32 => Ok(COSEAlgorithm::ECDH_SS_A128KW),
-31 => Ok(COSEAlgorithm::ECDH_ES_A256KW),
-30 => Ok(COSEAlgorithm::ECDH_ES_A192KW),
-29 => Ok(COSEAlgorithm::ECDH_ES_A128KW),
-28 => Ok(COSEAlgorithm::ECDH_SS_HKDF512),
-27 => Ok(COSEAlgorithm::ECDH_SS_HKDF256),
-26 => Ok(COSEAlgorithm::ECDH_ES_HKDF512),
-25 => Ok(COSEAlgorithm::ECDH_ES_HKDF256),
-18 => Ok(COSEAlgorithm::SHAKE128),
-17 => Ok(COSEAlgorithm::SHA512_256),
-16 => Ok(COSEAlgorithm::SHA256),
-15 => Ok(COSEAlgorithm::SHA256_64),
-14 => Ok(COSEAlgorithm::SHA1),
-13 => Ok(COSEAlgorithm::Direct_HKDF_AES256),
-12 => Ok(COSEAlgorithm::Direct_HKDF_AES128),
-11 => Ok(COSEAlgorithm::Direct_HKDF_SHA512),
-10 => Ok(COSEAlgorithm::Direct_HKDF_SHA256),
-8 => Ok(COSEAlgorithm::EDDSA),
-7 => Ok(COSEAlgorithm::ES256),
-6 => Ok(COSEAlgorithm::Direct),
-5 => Ok(COSEAlgorithm::A256KW),
-4 => Ok(COSEAlgorithm::A192KW),
-3 => Ok(COSEAlgorithm::A128KW),
1 => Ok(COSEAlgorithm::A128GCM),
2 => Ok(COSEAlgorithm::A192GCM),
3 => Ok(COSEAlgorithm::A256GCM),
4 => Ok(COSEAlgorithm::HMAC256_64),
5 => Ok(COSEAlgorithm::HMAC256_256),
6 => Ok(COSEAlgorithm::HMAC384_384),
7 => Ok(COSEAlgorithm::HMAC512_512),
10 => Ok(COSEAlgorithm::AES_CCM_16_64_128),
11 => Ok(COSEAlgorithm::AES_CCM_16_64_256),
12 => Ok(COSEAlgorithm::AES_CCM_64_64_128),
13 => Ok(COSEAlgorithm::AES_CCM_64_64_256),
14 => Ok(COSEAlgorithm::AES_MAC_128_64),
15 => Ok(COSEAlgorithm::AES_MAC_256_64),
24 => Ok(COSEAlgorithm::ChaCha20_Poly1305),
25 => Ok(COSEAlgorithm::AES_MAC_128_128),
26 => Ok(COSEAlgorithm::AES_MAC_256_128),
30 => Ok(COSEAlgorithm::AES_CCM_16_128_128),
31 => Ok(COSEAlgorithm::AES_CCM_16_128_256),
32 => Ok(COSEAlgorithm::AES_CCM_64_128_128),
33 => Ok(COSEAlgorithm::AES_CCM_64_128_256),
34 => Ok(COSEAlgorithm::IV_GENERATION),
-65535 => Ok(COSEAlgorithm::INSECURE_RS1),
_ => Err(CryptoError::UnknownAlgorithm),
}
}
}
/// A COSE Elliptic Curve Public Key. This is generally the provided credential
/// that an authenticator registers, and is used to authenticate the user.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct COSEEC2Key {
/// The curve that this key references.
pub curve: ECDSACurve,
/// The key's public X coordinate.
pub x: Vec<u8>,
/// The key's public Y coordinate.
pub y: Vec<u8>,
}
/// A Octet Key Pair (OKP).
/// The other version uses only the x-coordinate as the y-coordinate is
/// either to be recomputed or not needed for the key agreement operation ('OKP').
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct COSEOKPKey {
/// The curve that this key references.
pub curve: ECDSACurve,
/// The key's public X coordinate.
pub x: Vec<u8>,
}
/// A COSE RSA PublicKey. This is a provided credential from a registered
/// authenticator.
/// You will likely never need to interact with this value, as it is part of the Credential
/// API.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct COSERSAKey {
/// An RSA modulus
pub n: Vec<u8>,
/// An RSA exponent
pub e: Vec<u8>,
}
/// A Octet Key Pair (OKP).
/// The other version uses only the x-coordinate as the y-coordinate is
/// either to be recomputed or not needed for the key agreement operation ('OKP').
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct COSESymmetricKey {
/// The key
pub key: Vec<u8>,
}
// https://tools.ietf.org/html/rfc8152#section-13
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[repr(i64)]
pub enum COSEKeyTypeId {
// Reserved is invalid
// Reserved = 0,
/// Octet Key Pair
OKP = 1,
/// Elliptic Curve Keys w/ x- and y-coordinate
EC2 = 2,
/// RSA
RSA = 3,
/// Symmetric
Symmetric = 4,
}
impl TryFrom<u64> for COSEKeyTypeId {
type Error = CryptoError;
fn try_from(i: u64) -> Result<Self, Self::Error> {
match i {
1 => Ok(COSEKeyTypeId::OKP),
2 => Ok(COSEKeyTypeId::EC2),
3 => Ok(COSEKeyTypeId::RSA),
4 => Ok(COSEKeyTypeId::Symmetric),
_ => Err(CryptoError::UnknownKeyType),
}
}
}
/// The type of Key contained within a COSE value. You should never need
/// to alter or change this type.
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum COSEKeyType {
// +-----------+-------+-----------------------------------------------+
// | Name | Value | Description |
// +-----------+-------+-----------------------------------------------+
// | OKP | 1 | Octet Key Pair |
// | EC2 | 2 | Elliptic Curve Keys w/ x- and y-coordinate |
// | | | pair |
// | Symmetric | 4 | Symmetric Keys |
// | Reserved | 0 | This value is reserved |
// +-----------+-------+-----------------------------------------------+
// Reserved, // should always be invalid.
/// Identifies this as an Elliptic Curve octet key pair
OKP(COSEOKPKey), // Not used here
/// Identifies this as an Elliptic Curve EC2 key
EC2(COSEEC2Key),
/// Identifies this as an RSA key
RSA(COSERSAKey), // Not used here
/// Identifies this as a Symmetric key
Symmetric(COSESymmetricKey), // Not used here
}
/// A COSE Key as provided by the Authenticator. You should never need
/// to alter or change these values.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct COSEKey {
/// COSE signature algorithm, indicating the type of key and hash type
/// that should be used.
pub alg: COSEAlgorithm,
/// The public key
pub key: COSEKeyType,
}
impl<'de> Deserialize<'de> for COSEKey {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct COSEKeyVisitor;
impl<'de> Visitor<'de> for COSEKeyVisitor {
type Value = COSEKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut curve: Option<ECDSACurve> = None;
let mut key_type: Option<COSEKeyTypeId> = None;
let mut alg: Option<COSEAlgorithm> = None;
let mut x: Option<Vec<u8>> = None;
let mut y: Option<Vec<u8>> = None;
while let Some(key) = map.next_key()? {
trace!("cose key {:?}", key);
match key {
1 => {
if key_type.is_some() {
return Err(SerdeError::duplicate_field("key_type"));
}
let value: u64 = map.next_value()?;
let val = COSEKeyTypeId::try_from(value).map_err(|_| {
SerdeError::custom(format!("unsupported key_type {}", value))
})?;
key_type = Some(val);
// key_type = Some(map.next_value()?);
}
-1 => {
let key_type = key_type.ok_or(SerdeError::missing_field("key_type"))?;
if key_type == COSEKeyTypeId::RSA {
if y.is_some() {
return Err(SerdeError::duplicate_field("y"));
}
let value: ByteBuf = map.next_value()?;
y = Some(value.to_vec());
} else {
if curve.is_some() {
return Err(SerdeError::duplicate_field("curve"));
}
let value: u64 = map.next_value()?;
let val = ECDSACurve::try_from(value).map_err(|_| {
SerdeError::custom(format!("unsupported curve {}", value))
})?;
curve = Some(val);
// curve = Some(map.next_value()?);
}
}
-2 => {
if x.is_some() {
return Err(SerdeError::duplicate_field("x"));
}
let value: ByteBuf = map.next_value()?;
x = Some(value.to_vec());
}
-3 => {
if y.is_some() {
return Err(SerdeError::duplicate_field("y"));
}
let value: ByteBuf = map.next_value()?;
y = Some(value.to_vec());
}
3 => {
if alg.is_some() {
return Err(SerdeError::duplicate_field("alg"));
}
let value: i64 = map.next_value()?;
let val = COSEAlgorithm::try_from(value).map_err(|_| {
SerdeError::custom(format!("unsupported algorithm {}", value))
})?;
alg = Some(val);
// alg = map.next_value()?;
}
_ => {
// This unknown field should raise an error, but
// there is a couple of field I(baloo) do not understand
// yet. I(baloo) chose to ignore silently the
// error instead because of that
let value: Value = map.next_value()?;
trace!("cose unknown value {:?}:{:?}", key, value);
}
};
}
let key_type = key_type.ok_or(SerdeError::missing_field("key_type"))?;
let x = x.ok_or(SerdeError::missing_field("x"))?;
let alg = alg.ok_or(SerdeError::missing_field("alg"))?;
let res = match key_type {
COSEKeyTypeId::OKP => {
let curve = curve.ok_or(SerdeError::missing_field("curve"))?;
COSEKeyType::OKP(COSEOKPKey { curve, x })
}
COSEKeyTypeId::EC2 => {
let curve = curve.ok_or(SerdeError::missing_field("curve"))?;
let y = y.ok_or(SerdeError::missing_field("y"))?;
COSEKeyType::EC2(COSEEC2Key { curve, x, y })
}
COSEKeyTypeId::RSA => {
let e = y.ok_or(SerdeError::missing_field("y"))?;
COSEKeyType::RSA(COSERSAKey { e, n: x })
}
COSEKeyTypeId::Symmetric => COSEKeyType::Symmetric(COSESymmetricKey { key: x }),
};
Ok(COSEKey { alg, key: res })
}
}
deserializer.deserialize_bytes(COSEKeyVisitor)
}
}
impl Serialize for COSEKey {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let map_len = match &self.key {
COSEKeyType::OKP(_) => 3,
COSEKeyType::EC2(_) => 5,
COSEKeyType::RSA(_) => 4,
COSEKeyType::Symmetric(_) => 3,
};
let mut map = serializer.serialize_map(Some(map_len))?;
match &self.key {
COSEKeyType::OKP(key) => {
map.serialize_entry(&1, &COSEKeyTypeId::OKP)?;
map.serialize_entry(&3, &self.alg)?;
map.serialize_entry(&-1, &key.curve)?;
map.serialize_entry(&-2, &key.x)?;
}
COSEKeyType::EC2(key) => {
map.serialize_entry(&1, &(COSEKeyTypeId::EC2 as u8))?;
map.serialize_entry(&3, &self.alg)?;
map.serialize_entry(&-1, &(key.curve as u8))?;
map.serialize_entry(&-2, &serde_bytes::Bytes::new(&key.x))?;
map.serialize_entry(&-3, &serde_bytes::Bytes::new(&key.y))?;
}
COSEKeyType::RSA(key) => {
map.serialize_entry(&1, &COSEKeyTypeId::RSA)?;
map.serialize_entry(&3, &self.alg)?;
map.serialize_entry(&-1, &key.n)?;
map.serialize_entry(&-2, &key.e)?;
}
COSEKeyType::Symmetric(key) => {
map.serialize_entry(&1, &COSEKeyTypeId::Symmetric)?;
map.serialize_entry(&3, &self.alg)?;
map.serialize_entry(&-1, &key.key)?;
}
}
map.end()
}
}
/// Errors that can be returned from COSE functions.
#[derive(Debug)]
pub enum CryptoError {
// DecodingFailure,
// LibraryFailure,
MalformedInput,
// MissingHeader,
// UnexpectedHeaderValue,
// UnexpectedTag,
// UnexpectedType,
// Unimplemented,
// VerificationFailed,
// SigningFailed,
// InvalidArgument,
UnknownKeyType,
UnknownSignatureScheme,
UnknownAlgorithm,
WrongSaltLength,
Backend(BackendError),
}
impl From<BackendError> for CryptoError {
fn from(e: BackendError) -> Self {
CryptoError::Backend(e)
}
}
impl From<CryptoError> for CommandError {
fn from(e: CryptoError) -> Self {
CommandError::Crypto(e)
}
}
impl From<CryptoError> for AuthenticatorError {
fn from(e: CryptoError) -> Self {
AuthenticatorError::HIDError(HIDError::Command(CommandError::Crypto(e)))
}
}
#[derive(Clone)]
pub struct ECDHSecret {
remote: COSEKey,
my: COSEKey,
shared_secret: Vec<u8>,
}
impl ECDHSecret {
pub fn my_public_key(&self) -> &COSEKey {
&self.my
}
pub fn shared_secret(&self) -> &[u8] {
&self.shared_secret
}
}
impl fmt::Debug for ECDHSecret {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"ECDHSecret(remote: {:?}, my: {:?})",
self.remote,
self.my_public_key()
)
}
}
pub struct U2FRegisterAnswer<'a> {
pub certificate: &'a [u8],
pub signature: &'a [u8],
}
// We will only return MalformedInput here
pub fn parse_u2f_der_certificate(data: &[u8]) -> Result<U2FRegisterAnswer, CryptoError> {
// So we don't panic below, when accessing individual bytes
if data.len() < 4 {
return Err(CryptoError::MalformedInput);
}
// Check if it is a SEQUENCE
if data[0] != 0x30 {
return Err(CryptoError::MalformedInput);
}
// This algorithm is taken from mozilla-central/security/nss/lib/mozpkix/lib/pkixder.cpp
// The short form of length is a single byte with the high order bit set
// to zero. The long form of length is one byte with the high order bit
// set, followed by N bytes, where N is encoded in the lowest 7 bits of
// the first byte.
let end = if (data[1] & 0x80) == 0 {
2 + data[1] as usize
} else if data[1] == 0x81 {
// The next byte specifies the length
if data[2] < 128 {
// Not shortest possible encoding
// Forbidden by DER-format
return Err(CryptoError::MalformedInput);
}
3 + data[2] as usize
} else if data[1] == 0x82 {
// The next 2 bytes specify the length
let l = u16::from_be_bytes([data[2], data[3]]);
if l < 256 {
// Not shortest possible encoding
// Forbidden by DER-format
return Err(CryptoError::MalformedInput);
}
4 + l as usize
} else {
// We don't support lengths larger than 2^16 - 1.
return Err(CryptoError::MalformedInput);
};
if data.len() < end {
return Err(CryptoError::MalformedInput);
}
Ok(U2FRegisterAnswer {
certificate: &data[0..end],
signature: &data[end..],
})
}
#[cfg(all(test, not(feature = "crypto_dummy")))]
mod test {
use super::{
authenticate, decrypt, encrypt, imp::parse_key, imp::test_encapsulate, serialize_key,
COSEAlgorithm, COSEKey, ECDSACurve,
};
use crate::crypto::{COSEEC2Key, COSEKeyType};
use crate::ctap2::commands::client_pin::Pin;
use crate::util::decode_hex;
use serde_cbor::de::from_slice;
#[test]
fn test_serialize_key() {
let x = [
0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75,
0x67, 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33,
0x05, 0xe3, 0x1a, 0x80,
];
let y = [
0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0, 0xac, 0xf9, 0xd8,
0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3, 0x2c, 0x9a, 0xad,
0x6d, 0xfa, 0x8b, 0x27,
];
let serialized_key = [
0x04, 0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0,
0x75, 0x67, 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d,
0x33, 0x05, 0xe3, 0x1a, 0x80, 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda,
0x8d, 0xe0, 0xac, 0xf9, 0xd8, 0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73,
0xd4, 0xd3, 0x2c, 0x9a, 0xad, 0x6d, 0xfa, 0x8b, 0x27,
];
let (res_x, res_y) =
serialize_key(ECDSACurve::SECP256R1, &serialized_key).expect("Failed to serialize key");
assert_eq!(res_x, x);
assert_eq!(res_y, y);
let res_key = parse_key(ECDSACurve::SECP256R1, &x, &y).expect("Failed to parse key");
assert_eq!(res_key, serialized_key)
}
#[test]
fn test_parse_es256_serialize_key() {
// Test values taken from https://github.com/Yubico/python-fido2/blob/master/test/test_cose.py
let key_data = decode_hex("A5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C");
let key: COSEKey = from_slice(&key_data).unwrap();
assert_eq!(key.alg, COSEAlgorithm::ES256);
if let COSEKeyType::EC2(ec2key) = &key.key {
assert_eq!(ec2key.curve, ECDSACurve::SECP256R1);
assert_eq!(
ec2key.x,
decode_hex("A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1")
);
assert_eq!(
ec2key.y,
decode_hex("FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C")
);
} else {
panic!("Wrong key type!");
}
let serialized = serde_cbor::to_vec(&key).expect("Failed to serialize key");
assert_eq!(key_data, serialized);
}
#[test]
#[allow(non_snake_case)]
fn test_shared_secret() {
// Test values taken from https://github.com/Yubico/python-fido2/blob/master/test/test_ctap2.py
let EC_PRIV =
decode_hex("7452E599FEE739D8A653F6A507343D12D382249108A651402520B72F24FE7684");
let EC_PUB_X =
decode_hex("44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F");
let EC_PUB_Y =
decode_hex("EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9");
let DEV_PUB_X =
decode_hex("0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168");
let DEV_PUB_Y =
decode_hex("D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47");
let SHARED = decode_hex("c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c");
let TOKEN_ENC = decode_hex("7A9F98E31B77BE90F9C64D12E9635040");
let TOKEN = decode_hex("aff12c6dcfbf9df52f7a09211e8865cd");
let PIN_HASH_ENC = decode_hex("afe8327ce416da8ee3d057589c2ce1a9");
// let peer_key = parse_key(ECDSACurve::SECP256R1, &DEV_PUB_X, &DEV_PUB_Y).unwrap();
let my_pub_key_data = parse_key(ECDSACurve::SECP256R1, &EC_PUB_X, &EC_PUB_Y).unwrap();
let peer_key = COSEEC2Key {
curve: ECDSACurve::SECP256R1,
x: DEV_PUB_X,
y: DEV_PUB_Y,
};
// let my_pub_key = COSEKey {
// alg: COSEAlgorithm::ES256,
// key: COSEKeyType::EC2(COSEEC2Key {
// curve: ECDSACurve::SECP256R1,
// x: EC_PUB_X,
// y: EC_PUB_Y,
// }),
// };
// We are using `test_encapsulate()` here, because we need a way to hand in the private key
// which would be generated on the fly otherwise (ephemeral keys), to predict the outputs
let shared_secret =
test_encapsulate(&peer_key, COSEAlgorithm::ES256, &my_pub_key_data, &EC_PRIV).unwrap();
assert_eq!(shared_secret.shared_secret, SHARED);
let token_enc = encrypt(&shared_secret.shared_secret(), &TOKEN).unwrap();
assert_eq!(token_enc, TOKEN_ENC);
let token = decrypt(&shared_secret.shared_secret(), &TOKEN_ENC).unwrap();
assert_eq!(token, TOKEN);
let pin = Pin::new("1234");
let pin_hash_enc =
encrypt(&shared_secret.shared_secret(), pin.for_pin_token().as_ref()).unwrap();
assert_eq!(pin_hash_enc, PIN_HASH_ENC);
}
#[test]
fn test_authenticate() {
let key = "key";
let message = "The quick brown fox jumps over the lazy dog";
let expected =
decode_hex("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
let result =
authenticate(key.as_bytes(), message.as_bytes()).expect("Failed to authenticate");
assert_eq!(result, expected);
let key = "The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog";
let message = "message";
let expected =
decode_hex("5597b93a2843078cbb0c920ae41dfe20f1685e10c67e423c11ab91adfc319d12");
let result =
authenticate(key.as_bytes(), message.as_bytes()).expect("Failed to authenticate");
assert_eq!(result, expected);
}
#[test]
fn test_pin_encryption_and_hashing() {
let pin = "1234";
let shared_secret = vec![
0x82, 0xE3, 0xD8, 0x41, 0xE2, 0x5C, 0x5C, 0x13, 0x46, 0x2C, 0x12, 0x3C, 0xC3, 0xD3,
0x98, 0x78, 0x65, 0xBA, 0x3D, 0x20, 0x46, 0x74, 0xFB, 0xED, 0xD4, 0x7E, 0xF5, 0xAB,
0xAB, 0x8D, 0x13, 0x72,
];
let expected_new_pin_enc = vec![
0x70, 0x66, 0x4B, 0xB5, 0x81, 0xE2, 0x57, 0x45, 0x1A, 0x3A, 0xB9, 0x1B, 0xF1, 0xAA,
0xD8, 0xE4, 0x5F, 0x6C, 0xE9, 0xB5, 0xC3, 0xB0, 0xF3, 0x2B, 0x5E, 0xCD, 0x62, 0xD0,
0xBA, 0x3B, 0x60, 0x5F, 0xD9, 0x18, 0x31, 0x66, 0xF6, 0xC5, 0xFA, 0xF3, 0xE4, 0xDA,
0x24, 0x81, 0x50, 0x2C, 0xD0, 0xCE, 0xE0, 0x15, 0x8B, 0x35, 0x1F, 0xC3, 0x92, 0x08,
0xA7, 0x7C, 0xB2, 0x74, 0x4B, 0xD4, 0x3C, 0xF9,
];
let expected_pin_auth = vec![
0x8E, 0x7F, 0x01, 0x69, 0x97, 0xF3, 0xB0, 0xA2, 0x7B, 0xA4, 0x34, 0x7A, 0x0E, 0x49,
0xFD, 0xF5,
];
// Padding to 64 bytes
let input: Vec<u8> = pin
.as_bytes()
.iter()
.chain(std::iter::repeat(&0x00))
.take(64)
.cloned()
.collect();
let new_pin_enc = encrypt(&shared_secret, &input).expect("Failed to encrypt pin");
assert_eq!(new_pin_enc, expected_new_pin_enc);
let pin_auth = authenticate(&shared_secret, &new_pin_enc).expect("Failed to authenticate");
assert_eq!(pin_auth[0..16], expected_pin_auth);
}
}

543
third_party/rust/authenticator/src/crypto/nss.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,543 @@
use super::{COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, ECDHSecret, ECDSACurve};
use nss_gk_api::p11::{
PK11Origin, PK11_CreateContextBySymKey, PK11_Decrypt, PK11_DigestFinal, PK11_DigestOp,
PK11_Encrypt, PK11_GenerateKeyPairWithOpFlags, PK11_HashBuf, PK11_ImportSymKey,
PK11_PubDeriveWithKDF, PrivateKey, PublicKey, SECKEY_DecodeDERSubjectPublicKeyInfo,
SECKEY_ExtractPublicKey, SECOidTag, Slot, SubjectPublicKeyInfo, AES_BLOCK_SIZE,
PK11_ATTR_SESSION, SHA256_LENGTH,
};
use nss_gk_api::{Error as NSSError, IntoResult, SECItem, SECItemBorrowed, PR_FALSE};
use pkcs11_bindings::{
CKA_DERIVE, CKA_ENCRYPT, CKA_SIGN, CKD_NULL, CKF_DERIVE, CKM_AES_CBC, CKM_ECDH1_DERIVE,
CKM_EC_KEY_PAIR_GEN, CKM_SHA256_HMAC, CKM_SHA512_HMAC,
};
use serde::Serialize;
use serde_bytes::ByteBuf;
use std::convert::{TryFrom, TryInto};
use std::num::TryFromIntError;
use std::os::raw::c_uint;
use std::ptr;
#[cfg(test)]
use nss_gk_api::p11::{PK11_ImportDERPrivateKeyInfoAndReturnKey, SECKEY_ConvertToPublicKey};
/// Errors that can be returned from COSE functions.
#[derive(Clone, Debug, Serialize)]
pub enum BackendError {
NSSError(String),
TryFromError,
UnsupportedAlgorithm(COSEAlgorithm),
UnsupportedCurve(ECDSACurve),
UnsupportedKeyType,
}
impl From<NSSError> for BackendError {
fn from(e: NSSError) -> Self {
BackendError::NSSError(format!("{}", e))
}
}
impl From<TryFromIntError> for BackendError {
fn from(_: TryFromIntError) -> Self {
BackendError::TryFromError
}
}
pub type Result<T> = std::result::Result<T, BackendError>;
// Object identifiers in DER tag-length-value form
const DER_OID_EC_PUBLIC_KEY_BYTES: &[u8] = &[
0x06, 0x07,
/* {iso(1) member-body(2) us(840) ansi-x962(10045) keyType(2) ecPublicKey(1)} */
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
];
const DER_OID_P256_BYTES: &[u8] = &[
0x06, 0x08,
/* {iso(1) member-body(2) us(840) ansi-x962(10045) curves(3) prime(1) prime256v1(7)} */
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
];
const DER_OID_P384_BYTES: &[u8] = &[
0x06, 0x05,
/* {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} */
0x2b, 0x81, 0x04, 0x00, 0x22,
];
const DER_OID_P521_BYTES: &[u8] = &[
0x06, 0x05,
/* {iso(1) identified-organization(3) certicom(132) curve(0) ansip521r1(35)} */
0x2b, 0x81, 0x04, 0x00, 0x23,
];
/* From CTAP2.1 spec:
initialize()
This is run by the platform when starting a series of transactions with a specific authenticator.
encapsulate(peerCoseKey) (coseKey, sharedSecret) | error
Generates an encapsulation for the authenticators public key and returns the message to transmit and the shared secret.
encrypt(key, demPlaintext) ciphertext
Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length.
decrypt(key, ciphertext) plaintext | error
Decrypts a ciphertext and returns the plaintext.
authenticate(key, message) signature
Computes a MAC of the given message.
*/
// TODO(MS): Maybe remove ByteBuf and return Vec<u8>'s instead for a cleaner interface
pub(crate) fn serialize_key(_curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> {
// TODO(MS): I actually have NO idea how to do this with NSS
let length = key[1..].len() / 2;
let chunks: Vec<_> = key[1..].chunks_exact(length).collect();
Ok((
ByteBuf::from(chunks[0].to_vec()),
ByteBuf::from(chunks[1].to_vec()),
))
}
pub(crate) fn parse_key(_curve: ECDSACurve, x: &[u8], y: &[u8]) -> Result<Vec<u8>> {
if x.len() != y.len() {
return Err(BackendError::NSSError(
"EC coordinates not equally long".to_string(),
));
}
let mut buf = Vec::with_capacity(2 * x.len() + 1);
// The uncompressed point format is defined in Section 2.3.3 of "SEC 1: Elliptic Curve
// Cryptography" https://www.secg.org/sec1-v2.pdf.
buf.push(0x04);
buf.extend_from_slice(x);
buf.extend_from_slice(y);
Ok(buf)
}
fn der_spki_from_cose(cose_key: &COSEKey) -> Result<Vec<u8>> {
let ec2key = match cose_key.key {
COSEKeyType::EC2(ref ec2key) => ec2key,
_ => return Err(BackendError::UnsupportedKeyType),
};
let (curve_oid, seq_len, alg_len, spk_len) = match ec2key.curve {
ECDSACurve::SECP256R1 => (
DER_OID_P256_BYTES,
[0x59].as_slice(),
[0x13].as_slice(),
[0x42].as_slice(),
),
ECDSACurve::SECP384R1 => (
DER_OID_P384_BYTES,
[0x76].as_slice(),
[0x10].as_slice(),
[0x62].as_slice(),
),
ECDSACurve::SECP521R1 => (
DER_OID_P521_BYTES,
[0x81, 0x9b].as_slice(),
[0x10].as_slice(),
[0x8a, 0xdf].as_slice(),
),
x => return Err(BackendError::UnsupportedCurve(x)),
};
let cose_key_sec1 = parse_key(ec2key.curve, &ec2key.x, &ec2key.y)?;
// [RFC 5280]
let mut spki: Vec<u8> = vec![];
// SubjectPublicKeyInfo
spki.push(0x30);
spki.extend_from_slice(seq_len);
// AlgorithmIdentifier
spki.push(0x30);
spki.extend_from_slice(alg_len);
// ObjectIdentifier
spki.extend_from_slice(DER_OID_EC_PUBLIC_KEY_BYTES);
// RFC 5480 ECParameters
spki.extend_from_slice(curve_oid);
// BIT STRING encoding uncompressed SEC1 public point
spki.push(0x03);
spki.extend_from_slice(spk_len);
spki.push(0x0); // no trailing zeros
spki.extend_from_slice(&cose_key_sec1);
Ok(spki)
}
/// This is run by the platform when starting a series of transactions with a specific authenticator.
//pub(crate) fn initialize() { }
/// Generates an encapsulation for the authenticator's public key and returns the message
/// to transmit and the shared secret.
///
/// `peer_cose_key` is the authenticator's (peer's) public key.
pub(crate) fn encapsulate(peer_cose_key: &COSEKey) -> Result<ECDHSecret> {
nss_gk_api::init();
// Generate an ephmeral keypair to do ECDH with the authenticator.
// This is "platformKeyAgreementKey".
let ec2key = match peer_cose_key.key {
COSEKeyType::EC2(ref ec2key) => ec2key,
_ => return Err(BackendError::UnsupportedKeyType),
};
let mut oid = match ec2key.curve {
ECDSACurve::SECP256R1 => SECItemBorrowed::wrap(DER_OID_P256_BYTES),
ECDSACurve::SECP384R1 => SECItemBorrowed::wrap(DER_OID_P384_BYTES),
ECDSACurve::SECP521R1 => SECItemBorrowed::wrap(DER_OID_P521_BYTES),
x => return Err(BackendError::UnsupportedCurve(x)),
};
let oid_ptr: *mut SECItem = oid.as_mut();
let slot = Slot::internal()?;
let mut client_public_ptr = ptr::null_mut();
// We have to be careful with error handling between the `PK11_GenerateKeyPairWithOpFlags` and
// `PublicKey::from_ptr` calls here, so I've wrapped them in the same unsafe block as a
// warning. TODO(jms) Replace this once there is a safer alternative.
// https://github.com/mozilla/nss-gk-api/issues/1
let (client_private, client_public) = unsafe {
let client_private =
// Type of `param` argument depends on mechanism. For EC keygen it is
// `SECKEYECParams *` which is a typedef for `SECItem *`.
PK11_GenerateKeyPairWithOpFlags(
*slot,
CKM_EC_KEY_PAIR_GEN,
oid_ptr.cast(),
&mut client_public_ptr,
PK11_ATTR_SESSION,
CKF_DERIVE,
CKF_DERIVE,
ptr::null_mut(),
)
.into_result()?;
let client_public = PublicKey::from_ptr(client_public_ptr)?;
(client_private, client_public)
};
let peer_spki = der_spki_from_cose(peer_cose_key)?;
let peer_public = nss_public_key_from_der_spki(&peer_spki)?;
let shared_secret = encapsulate_helper(peer_public, client_private)?;
let client_cose_key = cose_key_from_nss_public(peer_cose_key.alg, ec2key.curve, client_public)?;
Ok(ECDHSecret {
remote: COSEKey {
alg: peer_cose_key.alg,
key: COSEKeyType::EC2(ec2key.clone()),
},
my: client_cose_key,
shared_secret,
})
}
fn nss_public_key_from_der_spki(spki: &[u8]) -> Result<PublicKey> {
let mut spki_item = SECItemBorrowed::wrap(&spki);
let spki_item_ptr: *mut SECItem = spki_item.as_mut();
let nss_spki = unsafe {
SubjectPublicKeyInfo::from_ptr(SECKEY_DecodeDERSubjectPublicKeyInfo(spki_item_ptr))?
};
let public_key = unsafe { PublicKey::from_ptr(SECKEY_ExtractPublicKey(*nss_spki))? };
Ok(public_key)
}
fn cose_key_from_nss_public(
alg: COSEAlgorithm,
curve: ECDSACurve,
nss_public: PublicKey,
) -> Result<COSEKey> {
let public_data = nss_public.key_data()?;
let (public_x, public_y) = serialize_key(curve, &public_data)?;
Ok(COSEKey {
alg,
key: COSEKeyType::EC2(COSEEC2Key {
curve,
x: public_x.to_vec(),
y: public_y.to_vec(),
}),
})
}
/// `peer_public`: The authenticator's public key.
/// `client_private`: Our ephemeral private key.
fn encapsulate_helper(peer_public: PublicKey, client_private: PrivateKey) -> Result<Vec<u8>> {
let ecdh_x_coord = unsafe {
PK11_PubDeriveWithKDF(
*client_private,
*peer_public,
PR_FALSE,
std::ptr::null_mut(),
std::ptr::null_mut(),
CKM_ECDH1_DERIVE,
CKM_SHA512_HMAC, // unused
CKA_DERIVE, // unused
0,
CKD_NULL,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
.into_result()?
};
let ecdh_x_coord_bytes = ecdh_x_coord.as_bytes()?;
let mut shared_secret = [0u8; SHA256_LENGTH];
unsafe {
PK11_HashBuf(
SECOidTag::SEC_OID_SHA256,
shared_secret.as_mut_ptr(),
ecdh_x_coord_bytes.as_ptr(),
ecdh_x_coord_bytes.len() as i32,
)
};
Ok(shared_secret.to_vec())
}
/// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext.
/// The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length.
pub(crate) fn encrypt(key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
nss_gk_api::init();
if key.len() != 32 {
return Err(BackendError::NSSError(
"Invalid AES-256 key length".to_string(),
));
}
// The input must be a multiple of the AES block size, 16
if data.len() % AES_BLOCK_SIZE != 0 {
return Err(BackendError::NSSError(
"Input to encrypt is too long".to_string(),
));
}
let in_len = c_uint::try_from(data.len())?;
let slot = Slot::internal()?;
let sym_key = unsafe {
PK11_ImportSymKey(
*slot,
CKM_AES_CBC,
PK11Origin::PK11_OriginUnwrap,
CKA_ENCRYPT,
SECItemBorrowed::wrap(key).as_mut(),
ptr::null_mut(),
)
.into_result()?
};
let iv = [0u8; AES_BLOCK_SIZE];
let mut params = SECItemBorrowed::wrap(&iv);
let params_ptr: *mut SECItem = params.as_mut();
let mut out_len: c_uint = 0;
let mut out = vec![0; in_len as usize];
unsafe {
PK11_Encrypt(
*sym_key,
CKM_AES_CBC,
params_ptr,
out.as_mut_ptr(),
&mut out_len,
in_len,
data.as_ptr(),
in_len,
)
.into_result()?
}
// CKM_AES_CBC should have output length equal to input length.
debug_assert_eq!(out_len, in_len);
Ok(out)
}
/// Decrypts a ciphertext and returns the plaintext.
pub(crate) fn decrypt(key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
nss_gk_api::init();
let slot = Slot::internal()?;
if key.len() != 32 {
return Err(BackendError::NSSError(
"Invalid AES-256 key length".to_string(),
));
}
// The input must be a multiple of the AES block size, 16
if data.len() % AES_BLOCK_SIZE != 0 {
return Err(BackendError::NSSError(
"Invalid input to decrypt".to_string(),
));
}
let in_len = c_uint::try_from(data.len())?;
let sym_key = unsafe {
PK11_ImportSymKey(
*slot,
CKM_AES_CBC,
PK11Origin::PK11_OriginUnwrap,
CKA_ENCRYPT,
SECItemBorrowed::wrap(key).as_mut(),
ptr::null_mut(),
)
.into_result()?
};
let iv = [0u8; AES_BLOCK_SIZE];
let mut params = SECItemBorrowed::wrap(&iv);
let params_ptr: *mut SECItem = params.as_mut();
let mut out_len: c_uint = 0;
let mut out = vec![0; in_len as usize];
unsafe {
PK11_Decrypt(
*sym_key,
CKM_AES_CBC,
params_ptr,
out.as_mut_ptr(),
&mut out_len,
in_len,
data.as_ptr(),
in_len,
)
.into_result()?
}
// CKM_AES_CBC should have output length equal to input length.
debug_assert_eq!(out_len, in_len);
Ok(out)
}
/// Computes a MAC of the given message.
pub(crate) fn authenticate(token: &[u8], input: &[u8]) -> Result<Vec<u8>> {
nss_gk_api::init();
let slot = Slot::internal()?;
let sym_key = unsafe {
PK11_ImportSymKey(
*slot,
CKM_SHA256_HMAC,
PK11Origin::PK11_OriginUnwrap,
CKA_SIGN,
SECItemBorrowed::wrap(token).as_mut(),
ptr::null_mut(),
)
.into_result()?
};
let param = SECItemBorrowed::make_empty();
let context = unsafe {
PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, *sym_key, param.as_ref())
.into_result()?
};
unsafe { PK11_DigestOp(*context, input.as_ptr(), input.len().try_into()?).into_result()? };
let mut digest = vec![0u8; 32];
let mut digest_len = 0u32;
unsafe {
PK11_DigestFinal(
*context,
digest.as_mut_ptr(),
&mut digest_len,
digest.len() as u32,
)
.into_result()?
}
assert_eq!(digest_len, 32);
Ok(digest)
}
#[cfg(test)]
pub(crate) fn test_encapsulate(
peer_coseec2_key: &COSEEC2Key,
alg: COSEAlgorithm,
my_pub_key: &[u8],
my_priv_key: &[u8],
) -> Result<ECDHSecret> {
nss_gk_api::init();
let peer_cose_key = COSEKey {
alg: alg,
key: COSEKeyType::EC2(peer_coseec2_key.clone()),
};
let spki = der_spki_from_cose(&peer_cose_key)?;
let peer_public = nss_public_key_from_der_spki(&spki)?;
/* NSS has no mechanism to import a raw elliptic curve coordinate as a private key.
* We need to encode it in a key storage format such as PKCS#8. To avoid a dependency
* on an ASN.1 encoder for this test, we'll do it manually. */
let pkcs8_private_key_info_version = &[0x02, 0x01, 0x00];
let rfc5915_ec_private_key_version = &[0x02, 0x01, 0x01];
let (curve_oid, seq_len, alg_len, attr_len, ecpriv_len, param_len, spk_len) =
match peer_coseec2_key.curve {
ECDSACurve::SECP256R1 => (
DER_OID_P256_BYTES,
[0x81, 0x87].as_slice(),
[0x13].as_slice(),
[0x6d].as_slice(),
[0x6b].as_slice(),
[0x44].as_slice(),
[0x42].as_slice(),
),
x => return Err(BackendError::UnsupportedCurve(x)),
};
let priv_len = my_priv_key.len() as u8; // < 127
let mut pkcs8_priv: Vec<u8> = vec![];
// RFC 5208 PrivateKeyInfo
pkcs8_priv.push(0x30);
pkcs8_priv.extend_from_slice(seq_len);
// Integer (0)
pkcs8_priv.extend_from_slice(pkcs8_private_key_info_version);
// AlgorithmIdentifier
pkcs8_priv.push(0x30);
pkcs8_priv.extend_from_slice(alg_len);
// ObjectIdentifier
pkcs8_priv.extend_from_slice(DER_OID_EC_PUBLIC_KEY_BYTES);
// RFC 5480 ECParameters
pkcs8_priv.extend_from_slice(DER_OID_P256_BYTES);
// Attributes
pkcs8_priv.push(0x04);
pkcs8_priv.extend_from_slice(attr_len);
// RFC 5915 ECPrivateKey
pkcs8_priv.push(0x30);
pkcs8_priv.extend_from_slice(ecpriv_len);
pkcs8_priv.extend_from_slice(rfc5915_ec_private_key_version);
pkcs8_priv.push(0x04);
pkcs8_priv.push(priv_len);
pkcs8_priv.extend_from_slice(my_priv_key);
pkcs8_priv.push(0xa1);
pkcs8_priv.extend_from_slice(param_len);
pkcs8_priv.push(0x03);
pkcs8_priv.extend_from_slice(spk_len);
pkcs8_priv.push(0x0);
pkcs8_priv.extend_from_slice(&my_pub_key);
// Now we can import the private key.
let slot = Slot::internal()?;
let mut pkcs8_priv_item = SECItemBorrowed::wrap(&pkcs8_priv);
let pkcs8_priv_item_ptr: *mut SECItem = pkcs8_priv_item.as_mut();
let mut client_private_ptr = ptr::null_mut();
unsafe {
PK11_ImportDERPrivateKeyInfoAndReturnKey(
*slot,
pkcs8_priv_item_ptr,
ptr::null_mut(),
ptr::null_mut(),
PR_FALSE,
PR_FALSE,
255, /* todo: expose KU_ flags in nss-gk-api */
&mut client_private_ptr,
ptr::null_mut(),
)
};
let client_private = unsafe { PrivateKey::from_ptr(client_private_ptr) }?;
let client_public = unsafe { PublicKey::from_ptr(SECKEY_ConvertToPublicKey(*client_private))? };
let client_cose_key = cose_key_from_nss_public(alg, peer_coseec2_key.curve, client_public)?;
let shared_secret = encapsulate_helper(peer_public, client_private)?;
Ok(ECDHSecret {
remote: peer_cose_key,
my: client_cose_key,
shared_secret,
})
}

283
third_party/rust/authenticator/src/crypto/openssl.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,283 @@
use super::{
/*Signature,*/ COSEAlgorithm, COSEEC2Key, /*PlainText*/ COSEKey, COSEKeyType,
/*CypherText,*/ ECDHSecret, ECDSACurve,
};
use openssl::bn::{BigNum, BigNumContext};
use openssl::derive::Deriver;
#[cfg(test)]
use openssl::ec::PointConversionForm;
use openssl::ec::{EcGroup, EcKey, EcPoint};
use openssl::error::ErrorStack;
use openssl::hash::{hash, MessageDigest};
use openssl::nid::Nid;
use openssl::pkey::{PKey, Private};
use openssl::sign::{Signer, Verifier};
use openssl::symm::{Cipher, Crypter, Mode};
use openssl::x509::X509;
use serde::{Serialize, Serializer};
use serde_bytes::ByteBuf;
fn openssl_string<S>(_: &ErrorStack, s: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str("OpenSSLError")
}
/// Errors that can be returned from COSE functions.
#[derive(Clone, Debug, Serialize)]
pub enum BackendError {
#[serde(serialize_with = "openssl_string")]
OpenSSL(ErrorStack),
UnsupportedCurve(ECDSACurve),
UnsupportedKeyType,
}
impl From<ErrorStack> for BackendError {
fn from(e: ErrorStack) -> Self {
BackendError::OpenSSL(e)
}
}
impl From<&ErrorStack> for BackendError {
fn from(e: &ErrorStack) -> Self {
BackendError::OpenSSL(e.clone())
}
}
pub type Result<T> = std::result::Result<T, BackendError>;
/* From CTAP2.1 spec:
initialize()
This is run by the platform when starting a series of transactions with a specific authenticator.
encapsulate(peerCoseKey) (coseKey, sharedSecret) | error
Generates an encapsulation for the authenticators public key and returns the message to transmit and the shared secret.
encrypt(key, demPlaintext) ciphertext
Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length.
decrypt(key, ciphertext) plaintext | error
Decrypts a ciphertext and returns the plaintext.
authenticate(key, message) signature
Computes a MAC of the given message.
*/
fn to_openssl_name(curve: ECDSACurve) -> Result<Nid> {
match curve {
ECDSACurve::SECP256R1 => Ok(Nid::X9_62_PRIME256V1),
ECDSACurve::SECP384R1 => Ok(Nid::SECP384R1),
ECDSACurve::SECP521R1 => Ok(Nid::SECP521R1),
x => Err(BackendError::UnsupportedCurve(x)),
}
}
fn affine_coordinates(curve: ECDSACurve, bytes: &[u8]) -> Result<(ByteBuf, ByteBuf)> {
let name = to_openssl_name(curve)?;
let group = EcGroup::from_curve_name(name)?;
let mut ctx = BigNumContext::new()?;
let point = EcPoint::from_bytes(&group, bytes, &mut ctx)?;
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
point.affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?;
//point.affine_coordinates_gf2m(&group, &mut x, &mut y, &mut ctx)?;
Ok((ByteBuf::from(x.to_vec()), ByteBuf::from(y.to_vec())))
}
// TODO(MS): Maybe remove ByteBuf and return Vec<u8>'s instead for a cleaner interface
pub(crate) fn serialize_key(curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> {
affine_coordinates(curve, key)
}
#[cfg(test)]
pub(crate) fn parse_key(curve: ECDSACurve, x: &[u8], y: &[u8]) -> Result<Vec<u8>> {
let name = to_openssl_name(curve)?;
let group = EcGroup::from_curve_name(name)?;
let mut ctx = BigNumContext::new()?;
let x = BigNum::from_slice(x)?;
let y = BigNum::from_slice(y)?;
let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
// TODO(baloo): what is uncompressed?!
let pub_key = key.public_key();
Ok(pub_key.to_bytes(&group, PointConversionForm::UNCOMPRESSED, &mut ctx)?)
}
/// This is run by the platform when starting a series of transactions with a specific authenticator.
//pub(crate) fn initialize() {
//
//}
/// Generates an encapsulation for the authenticators public key and returns the message
/// to transmit and the shared secret.
pub(crate) fn encapsulate(key: &COSEKey) -> Result<ECDHSecret> {
if let COSEKeyType::EC2(ec2key) = &key.key {
let curve_name = to_openssl_name(ec2key.curve)?;
let group = EcGroup::from_curve_name(curve_name)?;
let my_key = EcKey::generate(&group)?;
encapsulate_helper(&ec2key, key.alg, group, my_key)
} else {
Err(BackendError::UnsupportedKeyType)
}
}
pub(crate) fn encapsulate_helper(
key: &COSEEC2Key,
alg: COSEAlgorithm,
group: EcGroup,
my_key: EcKey<Private>,
) -> Result<ECDHSecret> {
let mut ctx = BigNumContext::new()?;
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
my_key
.public_key()
.affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?;
let my_public_key = COSEKey {
alg,
key: COSEKeyType::EC2(COSEEC2Key {
curve: key.curve.clone(),
x: x.to_vec(),
y: y.to_vec(),
}),
};
// let point = EcPoint::from_bytes(&group, &key.key, &mut ctx)?;
let peer_public_key = PKey::from_ec_key(EcKey::from_public_key_affine_coordinates(
&group,
BigNum::from_slice(&key.x).as_ref()?,
BigNum::from_slice(&key.y).as_ref()?,
)?)?;
let my_ec_key = PKey::from_ec_key(my_key)?;
let mut deriver = Deriver::new(my_ec_key.as_ref())?;
deriver.set_peer(&peer_public_key)?;
let shared_sec = deriver.derive_to_vec()?;
// Hashing the key material
let digest = hash(MessageDigest::sha256(), &shared_sec)?;
Ok(ECDHSecret {
remote: COSEKey {
alg,
key: COSEKeyType::EC2(key.clone()),
},
my: my_public_key,
shared_secret: digest.as_ref().to_vec(),
})
}
#[cfg(test)]
pub(crate) fn test_encapsulate(
key: &COSEEC2Key,
alg: COSEAlgorithm,
my_pub_key: &[u8],
my_priv_key: &[u8],
) -> Result<ECDHSecret> {
let curve_name = to_openssl_name(key.curve)?;
let group = EcGroup::from_curve_name(curve_name)?;
let mut ctx = BigNumContext::new()?;
let my_pub_point = EcPoint::from_bytes(&group, &my_pub_key, &mut ctx)?;
let my_priv_bignum = BigNum::from_slice(my_priv_key)?;
let my_key = EcKey::from_private_components(&group, &my_priv_bignum, &my_pub_point)?;
encapsulate_helper(key, alg, group, my_key)
}
/// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext.
/// The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length.
pub(crate) fn encrypt(
key: &[u8],
plain_text: &[u8], /*PlainText*/
) -> Result<Vec<u8> /*CypherText*/> {
let cipher = Cipher::aes_256_cbc();
// TODO(baloo): This might trigger a panic if size is not big enough
let mut cypher_text = vec![0; plain_text.len() * 2];
cypher_text.resize(plain_text.len() * 2, 0);
// Spec says explicitly IV=0
let iv = [0u8; 16];
let mut encrypter = Crypter::new(cipher, Mode::Encrypt, key, Some(&iv))?;
encrypter.pad(false);
let mut out_size = 0;
out_size += encrypter.update(plain_text, cypher_text.as_mut_slice())?;
out_size += encrypter.finalize(cypher_text.as_mut_slice())?;
cypher_text.truncate(out_size);
Ok(cypher_text)
}
/// Decrypts a ciphertext and returns the plaintext.
pub(crate) fn decrypt(
key: &[u8],
cypher_text: &[u8], /*CypherText*/
) -> Result<Vec<u8> /*PlainText*/> {
let cipher = Cipher::aes_256_cbc();
// TODO(baloo): This might trigger a panic if size is not big enough
let mut plain_text = vec![0; cypher_text.len() * 2];
plain_text.resize(cypher_text.len() * 2, 0);
// Spec says explicitly IV=0
let iv = [0u8; 16];
let mut encrypter = Crypter::new(cipher, Mode::Decrypt, key, Some(&iv))?;
encrypter.pad(false);
let mut out_size = 0;
out_size += encrypter.update(cypher_text, plain_text.as_mut_slice())?;
out_size += encrypter.finalize(plain_text.as_mut_slice())?;
plain_text.truncate(out_size);
Ok(plain_text)
}
/// Computes a MAC of the given message.
pub(crate) fn authenticate(token: &[u8], input: &[u8]) -> Result<Vec<u8>> {
// Create a PKey
let key = PKey::hmac(token)?;
// Compute the HMAC
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
signer.update(input)?;
let hmac = signer.sign_to_vec()?;
Ok(hmac)
}
// Currently unsued, because rc_crypto does not expose PKCS12 of NSS, so we can't parse the cert there
// To use it in statemachine.rs for example, do:
// if let Ok(cdhash) = client_data.hash() {
// let verification_data: Vec<u8> = attestation
// .auth_data
// .to_vec()
// .iter()
// .chain(cdhash.as_ref().iter())
// .copied()
// .collect();
// let res = attestation.att_statement.verify(&verification_data);
// ...
// }
#[allow(dead_code)]
pub(crate) fn verify(
sig_alg: ECDSACurve,
pub_key: &[u8],
signature: &[u8],
data: &[u8],
) -> Result<bool> {
let _alg = to_openssl_name(sig_alg)?; // TODO(MS): Actually use this to determine the right MessageDigest below
let pkey = X509::from_der(&pub_key)?;
let pubkey = pkey.public_key()?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &pubkey)?;
verifier.update(data)?;
let res = verifier.verify(signature)?;
Ok(res)
}

168
third_party/rust/authenticator/src/crypto/ring.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,168 @@
use super::{
/*Signature,*/ COSEAlgorithm, COSEEC2Key, /*PlainText*/ COSEKey, COSEKeyType,
/*CypherText,*/ ECDHSecret, ECDSACurve,
};
use ring::agreement::{
agree_ephemeral, Algorithm, EphemeralPrivateKey, UnparsedPublicKey, ECDH_P256, ECDH_P384,
};
use ring::digest;
use ring::hmac;
use ring::rand::SystemRandom;
use ring::signature::KeyPair;
use serde::Serialize;
use serde_bytes::ByteBuf;
/*
initialize()
This is run by the platform when starting a series of transactions with a specific authenticator.
encapsulate(peerCoseKey) (coseKey, sharedSecret) | error
Generates an encapsulation for the authenticators public key and returns the message to transmit and the shared secret.
encrypt(key, demPlaintext) ciphertext
Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length.
decrypt(key, ciphertext) plaintext | error
Decrypts a ciphertext and returns the plaintext.
authenticate(key, message) signature
Computes a MAC of the given message.
*/
pub type Result<T> = std::result::Result<T, BackendError>;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum BackendError {
AgreementError,
UnspecifiedRingError,
KeyRejected,
UnsupportedKeyType,
UnsupportedCurve(ECDSACurve),
}
fn to_ring_curve(curve: ECDSACurve) -> Result<&'static Algorithm> {
match curve {
ECDSACurve::SECP256R1 => Ok(&ECDH_P256),
ECDSACurve::SECP384R1 => Ok(&ECDH_P384),
x => Err(BackendError::UnsupportedCurve(x)),
}
}
impl From<ring::error::Unspecified> for BackendError {
fn from(e: ring::error::Unspecified) -> Self {
BackendError::UnspecifiedRingError
}
}
impl From<ring::error::KeyRejected> for BackendError {
fn from(e: ring::error::KeyRejected) -> Self {
BackendError::KeyRejected
}
}
pub(crate) fn parse_key(curve: ECDSACurve, x: &[u8], y: &[u8]) -> Result<Vec<u8>> {
compile_error!(
"Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now."
)
}
// TODO(MS): Maybe remove ByteBuf and return Vec<u8>'s instead for a cleaner interface
pub(crate) fn serialize_key(curve: ECDSACurve, key: &[u8]) -> Result<(ByteBuf, ByteBuf)> {
compile_error!(
"Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now."
)
}
/// This is run by the platform when starting a series of transactions with a specific authenticator.
pub(crate) fn initialize() {
compile_error!(
"Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now."
)
}
/// Generates an encapsulation for the authenticators public key and returns the message
/// to transmit and the shared secret.
pub(crate) fn encapsulate(key: &COSEKey) -> Result<ECDHSecret> {
if let COSEKeyType::EC2(ec2key) = &key.key {
// let curve_name = to_openssl_name(ec2key.curve)?;
// let group = EcGroup::from_curve_name(curve_name)?;
// let my_key = EcKey::generate(&group)?;
// encapsulate_helper(&ec2key, key.alg, group, my_key)
let rng = SystemRandom::new();
let peer_public_key_alg = to_ring_curve(ec2key.curve)?;
let private_key = EphemeralPrivateKey::generate(peer_public_key_alg, &rng)?;
let my_public_key = private_key.compute_public_key()?;
let (x, y) = serialize_key(ec2key.curve, my_public_key.as_ref())?;
let my_public_key = COSEKey {
alg: key.alg,
key: COSEKeyType::EC2(COSEEC2Key {
curve: ec2key.curve,
x: x.to_vec(),
y: y.to_vec(),
}),
};
let key_bytes = parse_key(ec2key.curve, &ec2key.x, &ec2key.y)?;
let peer_public_key = UnparsedPublicKey::new(peer_public_key_alg, &key_bytes);
let shared_secret = agree_ephemeral(private_key, &peer_public_key, (), |key_material| {
let digest = digest::digest(&digest::SHA256, key_material);
Ok(Vec::from(digest.as_ref()))
})
.map_err(|_| BackendError::AgreementError)?;
Ok(ECDHSecret {
remote: COSEKey {
alg: key.alg,
key: COSEKeyType::EC2(ec2key.clone()),
},
my: my_public_key,
shared_secret,
})
} else {
Err(BackendError::UnsupportedKeyType)
}
}
#[cfg(test)]
pub(crate) fn test_encapsulate(
key: &COSEEC2Key,
alg: COSEAlgorithm,
my_pub_key: &[u8],
my_priv_key: &[u8],
) -> Result<ECDHSecret> {
compile_error!(
"Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now."
)
}
/// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext.
/// The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length.
pub(crate) fn encrypt(
key: &[u8],
plain_text: &[u8], /*PlainText*/
) -> Result<Vec<u8> /*CypherText*/> {
// Ring doesn't support AES-CBC yet
compile_error!(
"Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now."
)
}
/// Decrypts a ciphertext and returns the plaintext.
pub(crate) fn decrypt(
key: &[u8],
cypher_text: &[u8], /*CypherText*/
) -> Result<Vec<u8> /*PlainText*/> {
// Ring doesn't support AES-CBC yet
compile_error!(
"Ring-backend is not yet implemented. Compile with `--features crypto_openssl` for now."
)
}
/// Computes a MAC of the given message.
pub(crate) fn authenticate(token: &[u8], input: &[u8]) -> Result<Vec<u8>> {
let s_key = hmac::Key::new(hmac::HMAC_SHA256, token);
let tag = hmac::sign(&s_key, input);
Ok(tag.as_ref().to_vec())
}

254
third_party/rust/authenticator/src/ctap2-capi.h поставляемый Normal file
Просмотреть файл

@ -0,0 +1,254 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef __CTAP2_CAPI
#define __CTAP2_CAPI
#include <stdlib.h>
#include "nsString.h"
extern "C" {
const uint8_t CTAP2_SIGN_RESULT_PUBKEY_CRED_ID = 1;
const uint8_t CTAP2_SIGN_RESULT_AUTH_DATA = 2;
const uint8_t CTAP2_SIGN_RESULT_SIGNATURE = 3;
const uint8_t CTAP2_SIGN_RESULT_USER_ID = 4;
const uint8_t CTAP2_SIGN_RESULT_USER_NAME = 5;
typedef struct {
const uint8_t *id_ptr;
size_t id_len;
const char *name;
} AuthenticatorArgsUser;
typedef struct {
const uint8_t *ptr;
size_t len;
} AuthenticatorArgsChallenge;
typedef struct {
const int32_t *ptr;
size_t len;
} AuthenticatorArgsPubCred;
typedef struct {
bool resident_key;
bool user_verification;
bool user_presence;
bool force_none_attestation;
} AuthenticatorArgsOptions;
// NOTE: Preconditions
// * All rust_u2f_mgr* pointers must refer to pointers which are returned
// by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
// * All rust_u2f_khs* pointers must refer to pointers which are returned
// by rust_u2f_pkcd_new, and must be freed with rust_u2f_pkcd_free.
// * All rust_u2f_res* pointers must refer to pointers passed to the
// register() and sign() callbacks. They can be null on failure.
// The `rust_u2f_key_handles` opaque type is equivalent to the rust type
// `Ctap2PubKeyCredDescriptors`
struct rust_ctap2_pub_key_cred_descriptors;
/// Ctap2PubKeyCredDescriptors functions.
rust_ctap2_pub_key_cred_descriptors* rust_ctap2_pkcd_new();
void rust_ctap2_pkcd_add(rust_ctap2_pub_key_cred_descriptors* pkcd, const uint8_t* id_ptr,
size_t id_len, uint8_t transports);
/* unsafe */ void rust_ctap2_pkcd_free(rust_ctap2_pub_key_cred_descriptors* khs);
// The `rust_ctap2_mgr` opaque type is equivalent to the rust type `Ctap2Manager`
// struct rust_ctap_manager;
// The `rust_ctap2_result` opaque type is equivalent to the rust type `RegisterResult`
struct rust_ctap2_register_result;
// The `rust_ctap2_result` opaque type is equivalent to the rust type `RegisterResult`
struct rust_ctap2_sign_result;
// Ctap2 exposes the results directly without repackaging them. Use getter-functions.
typedef void (*rust_ctap2_register_callback)(uint64_t, rust_ctap2_register_result*);
typedef void (*rust_ctap2_sign_callback)(uint64_t, rust_ctap2_sign_result*);
// Status updates get sent, if a device needs a PIN, if a device needs to be selected, etc.
struct rust_ctap2_status_update_res;
// May be called with NULL, in case of an error
typedef void (*rust_ctap2_status_update_callback)(rust_ctap2_status_update_res*);
rust_ctap_manager* rust_ctap2_mgr_new();
/* unsafe */ void rust_ctap2_mgr_free(rust_ctap_manager* mgr);
/* unsafe */ void rust_ctap2_register_res_free(rust_ctap2_register_result* res);
/* unsafe */ void rust_ctap2_sign_res_free(rust_ctap2_sign_result* res);
uint64_t rust_ctap2_mgr_register(
rust_ctap_manager* mgr, uint64_t timeout, rust_ctap2_register_callback, rust_ctap2_status_update_callback,
AuthenticatorArgsChallenge challenge,
const char* relying_party_id, const char *origin_ptr,
AuthenticatorArgsUser user, AuthenticatorArgsPubCred pub_cred_params,
const rust_ctap2_pub_key_cred_descriptors* exclude_list, AuthenticatorArgsOptions options,
const char *pin
);
uint64_t rust_ctap2_mgr_sign(
rust_ctap_manager* mgr, uint64_t timeout, rust_ctap2_sign_callback, rust_ctap2_status_update_callback,
AuthenticatorArgsChallenge challenge,
const char* relying_party_id, const char *origin_ptr,
const rust_ctap2_pub_key_cred_descriptors* allow_list, AuthenticatorArgsOptions options,
const char *pin
);
void rust_ctap2_mgr_cancel(rust_ctap_manager* mgr);
// Returns 0 for success, or the U2F_ERROR error code >= 1.
uint8_t rust_ctap2_register_result_error(const rust_ctap2_register_result* res);
uint8_t rust_ctap2_sign_result_error(const rust_ctap2_sign_result* res);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_register_result_client_data_copy()
bool rust_ctap2_register_result_client_data_len(
const rust_ctap2_register_result *res,
size_t *len
);
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_register_result_client_data_len)
bool rust_ctap2_register_result_client_data_copy(
const rust_ctap2_register_result *res,
const char *dst
);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_register_result_item_copy()
bool rust_ctap2_register_result_attestation_len(
const rust_ctap2_register_result *res,
size_t *len
);
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_register_result_item_len)
bool rust_ctap2_register_result_attestation_copy(
const rust_ctap2_register_result* res,
uint8_t *dst
);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_register_result_client_data_copy()
bool rust_ctap2_sign_result_client_data_len(
const rust_ctap2_sign_result *res,
size_t *len
);
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_sign_result_client_data_len)
bool rust_ctap2_sign_result_client_data_copy(
const rust_ctap2_sign_result *res,
const char *dst
);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_register_result_client_data_copy()
bool rust_ctap2_sign_result_assertions_len(
const rust_ctap2_sign_result *res,
size_t *len
);
bool rust_ctap2_sign_result_item_contains(
const rust_ctap2_sign_result *res,
size_t assertion_idx,
uint8_t item_idx
);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_sign_result_item_copy()
bool rust_ctap2_sign_result_item_len(
const rust_ctap2_sign_result *res,
size_t assertion_idx,
uint8_t item_idx,
size_t *len
);
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_sign_result_item_len)
bool rust_ctap2_sign_result_item_copy(
const rust_ctap2_sign_result* res,
size_t assertion_idx,
uint8_t item_idx,
uint8_t *dst
);
bool rust_ctap2_sign_result_contains_username(
const rust_ctap2_sign_result *res,
size_t assertion_idx
);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_sign_result_username_copy()
bool rust_ctap2_sign_result_username_len(
const rust_ctap2_sign_result *res,
size_t assertion_idx,
size_t *len
);
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_sign_result_username_len)
bool rust_ctap2_sign_result_username_copy(
const rust_ctap2_sign_result* res,
size_t assertion_idx,
const char *dst
);
/// # Safety
///
/// This function is used to get the length, prior to calling
/// rust_ctap2_status_update_copy_json()
bool rust_ctap2_status_update_len(
const rust_ctap2_status_update_res *res,
size_t *len
);
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_status_update_len)
bool rust_ctap2_status_update_copy_json(
const rust_ctap2_status_update_res *res,
const char *dst
);
bool rust_ctap2_status_update_send_pin(
const rust_ctap2_status_update_res *res,
const char *pin
);
/// # Safety
/// This frees the memory of a status_update_res
bool rust_ctap2_destroy_status_update_res(
rust_ctap2_status_update_res *res
);
}
#endif // __CTAP2_CAPI

799
third_party/rust/authenticator/src/ctap2/attestation.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,799 @@
use super::utils::from_slice_stream;
use crate::crypto::COSEAlgorithm;
use crate::ctap2::commands::CommandError;
use crate::ctap2::server::RpIdHash;
use crate::{crypto::COSEKey, errors::AuthenticatorError};
use nom::{
bytes::complete::take,
combinator::{cond, map},
error::Error as NomError,
number::complete::{be_u16, be_u32, be_u8},
Err as NomErr, IResult,
};
use serde::ser::{Error as SerError, SerializeMap, Serializer};
use serde::{
de::{Error as SerdeError, MapAccess, Visitor},
Deserialize, Deserializer, Serialize,
};
use serde_bytes::ByteBuf;
use serde_cbor;
use std::fmt;
#[derive(Debug, PartialEq, Eq)]
pub enum HmacSecretResponse {
/// This is returned by MakeCredential calls to display if CredRandom was
/// successfully generated
Confirmed(bool),
/// This is returned by GetAssertion:
/// AES256-CBC(shared_secret, HMAC-SHA265(CredRandom, salt1) || HMAC-SHA265(CredRandom, salt2))
Secret(Vec<u8>),
}
impl Serialize for HmacSecretResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
HmacSecretResponse::Confirmed(x) => serializer.serialize_bool(*x),
HmacSecretResponse::Secret(x) => serializer.serialize_bytes(&x),
}
}
}
impl<'de> Deserialize<'de> for HmacSecretResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct HmacSecretResponseVisitor;
impl<'de> Visitor<'de> for HmacSecretResponseVisitor {
type Value = HmacSecretResponse;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array or a boolean")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(HmacSecretResponse::Secret(v.to_vec()))
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(HmacSecretResponse::Confirmed(v))
}
}
deserializer.deserialize_any(HmacSecretResponseVisitor)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Extension {
#[serde(rename = "pinMinLength", skip_serializing_if = "Option::is_none")]
pub pin_min_length: Option<u64>,
#[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")]
pub hmac_secret: Option<HmacSecretResponse>,
}
fn parse_extensions<'a>(input: &'a [u8]) -> IResult<&'a [u8], Extension, NomError<&'a [u8]>> {
serde_to_nom(input)
}
#[derive(Serialize, PartialEq, Default, Eq, Clone)]
pub struct AAGuid(pub [u8; 16]);
impl AAGuid {
pub fn from(src: &[u8]) -> Result<AAGuid, ()> {
let mut payload = [0u8; 16];
if src.len() != payload.len() {
Err(())
} else {
payload.copy_from_slice(src);
Ok(AAGuid(payload))
}
}
}
impl fmt::Debug for AAGuid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"AAGuid({:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x})",
self.0[0],
self.0[1],
self.0[2],
self.0[3],
self.0[4],
self.0[5],
self.0[6],
self.0[7],
self.0[8],
self.0[9],
self.0[10],
self.0[11],
self.0[12],
self.0[13],
self.0[14],
self.0[15]
)
}
}
impl<'de> Deserialize<'de> for AAGuid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct AAGuidVisitor;
impl<'de> Visitor<'de> for AAGuidVisitor {
type Value = AAGuid;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
let mut buf = [0u8; 16];
if v.len() != buf.len() {
return Err(E::invalid_length(v.len(), &"16"));
}
buf.copy_from_slice(v);
Ok(AAGuid(buf))
}
}
deserializer.deserialize_bytes(AAGuidVisitor)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct AttestedCredentialData {
pub aaguid: AAGuid,
pub credential_id: Vec<u8>,
pub credential_public_key: COSEKey,
}
fn serde_to_nom<'a, Output>(input: &'a [u8]) -> IResult<&'a [u8], Output>
where
Output: Deserialize<'a>,
{
from_slice_stream(input)
.map_err(|_e| nom::Err::Error(nom::error::make_error(input, nom::error::ErrorKind::NoneOf)))
// can't use custom errorkind because of error type mismatch in parse_attested_cred_data
//.map_err(|e| NomErr::Error(Context::Code(input, ErrorKind::Custom(e))))
// .map_err(|_| NomErr::Error(Context::Code(input, ErrorKind::Custom(42))))
}
fn parse_attested_cred_data<'a>(
input: &'a [u8],
) -> IResult<&'a [u8], AttestedCredentialData, NomError<&'a [u8]>> {
let (rest, aaguid_res) = map(take(16u8), AAGuid::from)(input)?;
// // We can unwrap here, since we _know_ the input will be 16 bytes error out before calling from()
let aaguid = aaguid_res.unwrap();
let (rest, cred_len) = be_u16(rest)?;
let (rest, credential_id) = map(take(cred_len), Vec::from)(rest)?;
let (rest, credential_public_key) = serde_to_nom(rest)?;
Ok((
rest,
(AttestedCredentialData {
aaguid,
credential_id,
credential_public_key: credential_public_key,
}),
))
}
bitflags! {
pub struct AuthenticatorDataFlags: u8 {
const USER_PRESENT = 0x01;
const USER_VERIFIED = 0x04;
const ATTESTED = 0x40;
const EXTENSION_DATA = 0x80;
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct AuthenticatorData {
pub rp_id_hash: RpIdHash,
pub flags: AuthenticatorDataFlags,
pub counter: u32,
pub credential_data: Option<AttestedCredentialData>,
pub extensions: Extension,
}
fn parse_ad<'a>(input: &'a [u8]) -> IResult<&'a [u8], AuthenticatorData, NomError<&'a [u8]>> {
let (rest, rp_id_hash_res) = map(take(32u8), RpIdHash::from)(input)?;
// We can unwrap here, since we _know_ the input to from() will be 32 bytes or error out before calling from()
let rp_id_hash = rp_id_hash_res.unwrap();
// be conservative, there is a couple of reserved values in
// AuthenticatorDataFlags, just truncate the one we don't know
let (rest, flags) = map(be_u8, AuthenticatorDataFlags::from_bits_truncate)(rest)?;
let (rest, counter) = be_u32(rest)?;
let (rest, credential_data) = cond(
flags.contains(AuthenticatorDataFlags::ATTESTED),
parse_attested_cred_data,
)(rest)?;
let (rest, extensions) = cond(
flags.contains(AuthenticatorDataFlags::EXTENSION_DATA),
parse_extensions,
)(rest)?;
// TODO(baloo): we should check for end of buffer and raise a parse
// parse error if data is still in the buffer
//eof!() >>
Ok((
rest,
AuthenticatorData {
rp_id_hash,
flags,
counter,
credential_data,
extensions: extensions.unwrap_or_default(),
},
))
}
impl<'de> Deserialize<'de> for AuthenticatorData {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct AuthenticatorDataVisitor;
impl<'de> Visitor<'de> for AuthenticatorDataVisitor {
type Value = AuthenticatorData;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
parse_ad(v)
.map(|(_input, value)| value)
.map_err(|e| match e {
NomErr::Incomplete(nom::Needed::Size(len)) => {
E::invalid_length(v.len(), &format!("{}", v.len() + len.get()).as_ref())
}
NomErr::Incomplete(nom::Needed::Unknown) => {
E::invalid_length(v.len(), &"unknown") // We don't know the expected value
}
// TODO(baloo): is that enough? should we be more
// specific on the error type?
e => E::custom(e.to_string()),
})
}
}
deserializer.deserialize_bytes(AuthenticatorDataVisitor)
}
}
impl AuthenticatorData {
pub fn to_vec(&self) -> Result<Vec<u8>, AuthenticatorError> {
let mut data = Vec::new();
data.extend(&self.rp_id_hash.0);
data.extend(&[self.flags.bits()]);
data.extend(&self.counter.to_be_bytes());
// TODO(baloo): need to yield credential_data and extensions, but that dependends on flags,
// should we consider another type system?
if let Some(cred) = &self.credential_data {
data.extend(&cred.aaguid.0);
data.extend(&(cred.credential_id.len() as u16).to_be_bytes());
data.extend(&cred.credential_id);
data.extend(
&serde_cbor::to_vec(&cred.credential_public_key)
.map_err(CommandError::Serializing)?,
);
}
Ok(data)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
/// x509 encoded attestation certificate
pub struct AttestationCertificate(#[serde(with = "serde_bytes")] pub(crate) Vec<u8>);
impl AsRef<[u8]> for AttestationCertificate {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub struct Signature(#[serde(with = "serde_bytes")] pub(crate) ByteBuf);
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD);
write!(f, "Signature({})", value)
}
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum AttestationStatement {
None,
Packed(AttestationStatementPacked),
// TODO(baloo): there is a couple other options than None and Packed:
// https://w3c.github.io/webauthn/#generating-an-attestation-object
// https://w3c.github.io/webauthn/#defined-attestation-formats
//TPM,
//AndroidKey,
//AndroidSafetyNet,
FidoU2F(AttestationStatementFidoU2F),
}
// Not all crypto-backends currently provide "crypto::verify()", so we do not implement it yet.
// Also not sure, if we really need it. Would be a sanity-check only, to verify the signature is valid,
// before sendig it out.
// impl AttestationStatement {
// pub fn verify(&self, data: &[u8]) -> Result<bool, AuthenticatorError> {
// match self {
// AttestationStatement::None => Ok(true),
// AttestationStatement::Unparsed(_) => Err(AuthenticatorError::Custom(
// "Unparsed attestation object can't be used to verify signature.".to_string(),
// )),
// AttestationStatement::FidoU2F(att) => {
// let res = crypto::verify(
// crypto::SignatureAlgorithm::ES256,
// &att.attestation_cert[0].as_ref(),
// att.sig.as_ref(),
// data,
// )?;
// Ok(res)
// }
// AttestationStatement::Packed(att) => {
// if att.alg != Alg::ES256 {
// return Err(AuthenticatorError::Custom(
// "Verification only supported for ES256".to_string(),
// ));
// }
// let res = crypto::verify(
// crypto::SignatureAlgorithm::ES256,
// att.attestation_cert[0].as_ref(),
// att.sig.as_ref(),
// data,
// )?;
// Ok(res)
// }
// }
// }
// }
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
// See https://www.w3.org/TR/webauthn/#fido-u2f-attestation
pub struct AttestationStatementFidoU2F {
pub sig: Signature,
#[serde(rename = "x5c")]
/// Certificate chain in x509 format
pub attestation_cert: Vec<AttestationCertificate>,
}
#[allow(dead_code)] // TODO(MS): Remove me, once we can parse AttestationStatements and use this function
impl AttestationStatementFidoU2F {
pub fn new(cert: &[u8], signature: &[u8]) -> Self {
AttestationStatementFidoU2F {
sig: Signature(ByteBuf::from(signature)),
attestation_cert: vec![AttestationCertificate(Vec::from(cert))],
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
// TODO(baloo): there is a couple other options than x5c:
// https://www.w3.org/TR/webauthn/#packed-attestation
pub struct AttestationStatementPacked {
pub alg: COSEAlgorithm,
pub sig: Signature,
#[serde(rename = "x5c")]
/// Certificate chain in x509 format
pub attestation_cert: Vec<AttestationCertificate>,
}
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
enum AttestationFormat {
#[serde(rename = "fido-u2f")]
FidoU2F,
Packed,
None,
// TOOD(baloo): only packed is implemented for now, see spec:
// https://www.w3.org/TR/webauthn/#defined-attestation-formats
//TPM,
//AndroidKey,
//AndroidSafetyNet,
}
#[derive(Debug, PartialEq, Eq)]
pub struct AttestationObject {
pub auth_data: AuthenticatorData,
pub att_statement: AttestationStatement,
}
impl<'de> Deserialize<'de> for AttestationObject {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct AttestationObjectVisitor;
impl<'de> Visitor<'de> for AttestationObjectVisitor {
type Value = AttestationObject;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a cbor map")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut format: Option<AttestationFormat> = None;
let mut auth_data = None;
let mut att_statement = None;
while let Some(key) = map.next_key()? {
match key {
// Spec for CTAP 2.0 is wrong and fmt should be numbered 1, and auth_data 2:
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential
// Corrected in CTAP 2.1 and Webauthn spec
1 => {
if format.is_some() {
return Err(SerdeError::duplicate_field("fmt"));
}
format = Some(map.next_value()?);
}
2 => {
if auth_data.is_some() {
return Err(SerdeError::duplicate_field("auth_data"));
}
auth_data = Some(map.next_value()?);
}
3 => {
let format = format
.as_ref()
.ok_or_else(|| SerdeError::missing_field("fmt"))?;
if att_statement.is_some() {
return Err(SerdeError::duplicate_field("att_statement"));
}
match format {
// This should not actually happen, but ...
AttestationFormat::None => {
att_statement = Some(AttestationStatement::None);
}
AttestationFormat::Packed => {
att_statement =
Some(AttestationStatement::Packed(map.next_value()?));
}
AttestationFormat::FidoU2F => {
att_statement =
Some(AttestationStatement::FidoU2F(map.next_value()?));
}
}
}
k => return Err(M::Error::custom(format!("unexpected key: {:?}", k))),
}
}
let auth_data =
auth_data.ok_or_else(|| M::Error::custom("found no auth_data".to_string()))?;
let att_statement = att_statement.unwrap_or(AttestationStatement::None);
Ok(AttestationObject {
auth_data,
att_statement,
})
}
}
deserializer.deserialize_bytes(AttestationObjectVisitor)
}
}
impl Serialize for AttestationObject {
/// Serialize can be used to repackage the CBOR answer we get from the token using CTAP-format
/// to webauthn-format (string-keys like "authData" instead of numbers). Yes, the specs are weird.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let map_len = 3;
let mut map = serializer.serialize_map(Some(map_len))?;
let auth_data = self
.auth_data
.to_vec()
.map(|v| serde_cbor::Value::Bytes(v))
.map_err(|_| SerError::custom("Failed to serialize auth_data"))?;
map.serialize_entry(&"authData", &auth_data)?;
match self.att_statement {
AttestationStatement::None => {
// Even with Att None, an empty map is returned in the cbor!
map.serialize_entry(&"fmt", &"none")?;
let v = serde_cbor::Value::Map(std::collections::BTreeMap::new());
map.serialize_entry(&"attStmt", &v)?;
}
AttestationStatement::Packed(ref v) => {
map.serialize_entry(&"fmt", &"packed")?;
map.serialize_entry(&"attStmt", v)?;
}
AttestationStatement::FidoU2F(ref v) => {
map.serialize_entry(&"fmt", &"fido-u2f")?;
map.serialize_entry(&"attStmt", v)?;
}
}
map.end()
}
}
#[cfg(test)]
mod test {
use super::super::utils::from_slice_stream;
use super::*;
use serde_cbor::from_slice;
const SAMPLE_ATTESTATION: [u8; 1006] = [
0xa3, 0x1, 0x66, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x2, 0x58, 0xc4, 0x49, 0x96, 0xd,
0xe5, 0x88, 0xe, 0x8c, 0x68, 0x74, 0x34, 0x17, 0xf, 0x64, 0x76, 0x60, 0x5b, 0x8f, 0xe4,
0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, 0x97, 0x63, 0x41,
0x0, 0x0, 0x0, 0x7, 0xcb, 0x69, 0x48, 0x1e, 0x8f, 0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27,
0x29, 0xa1, 0x54, 0xa8, 0x0, 0x40, 0xc3, 0xcf, 0x1, 0x3b, 0xc6, 0x26, 0x93, 0x28, 0xfb,
0x7f, 0xa9, 0x76, 0xef, 0xa8, 0x4b, 0x66, 0x71, 0xad, 0xa9, 0x64, 0xea, 0xcb, 0x58, 0x76,
0x54, 0x51, 0xa, 0xc8, 0x86, 0x4f, 0xbb, 0x53, 0x2d, 0xfb, 0x2, 0xfc, 0xdc, 0xa9, 0x84,
0xc2, 0x5c, 0x67, 0x8a, 0x3a, 0xab, 0x57, 0xf3, 0x71, 0x77, 0xd3, 0xd4, 0x41, 0x64, 0x1,
0x50, 0xca, 0x6c, 0x42, 0x73, 0x1c, 0x42, 0xcb, 0x81, 0xba, 0xa5, 0x1, 0x2, 0x3, 0x26,
0x20, 0x1, 0x21, 0x58, 0x20, 0x9, 0x2e, 0x34, 0xfe, 0xa7, 0xd7, 0x32, 0xc8, 0xae, 0x4c,
0xf6, 0x96, 0xbe, 0x7a, 0x12, 0xdc, 0x29, 0xd5, 0xf1, 0xd3, 0xf1, 0x55, 0x4d, 0xdc, 0x87,
0xc4, 0xc, 0x9b, 0xd0, 0x17, 0xba, 0xf, 0x22, 0x58, 0x20, 0xc9, 0xf0, 0x97, 0x33, 0x55,
0x36, 0x58, 0xd9, 0xdb, 0x76, 0xf5, 0xef, 0x95, 0xcf, 0x8a, 0xc7, 0xfc, 0xc1, 0xb6, 0x81,
0x25, 0x5f, 0x94, 0x6b, 0x62, 0x13, 0x7d, 0xd0, 0xc4, 0x86, 0x53, 0xdb, 0x3, 0xa3, 0x63,
0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, 0x69, 0x67, 0x58, 0x48, 0x30, 0x46, 0x2, 0x21, 0x0,
0xac, 0x2a, 0x78, 0xa8, 0xaf, 0x18, 0x80, 0x39, 0x73, 0x8d, 0x3, 0x5e, 0x4, 0x4d, 0x94,
0x4f, 0x3f, 0x57, 0xce, 0x88, 0x41, 0xfa, 0x81, 0x50, 0x40, 0xb6, 0xd1, 0x95, 0xb5, 0xeb,
0xe4, 0x6f, 0x2, 0x21, 0x0, 0x8f, 0xf4, 0x15, 0xc9, 0xb3, 0x6d, 0x1c, 0xd, 0x4c, 0xa3,
0xcf, 0x99, 0x8a, 0x46, 0xd4, 0x4c, 0x8b, 0x5c, 0x26, 0x3f, 0xdf, 0x22, 0x6c, 0x9b, 0x23,
0x83, 0x8b, 0x69, 0x47, 0x67, 0x48, 0x45, 0x63, 0x78, 0x35, 0x63, 0x81, 0x59, 0x2, 0xc1,
0x30, 0x82, 0x2, 0xbd, 0x30, 0x82, 0x1, 0xa5, 0xa0, 0x3, 0x2, 0x1, 0x2, 0x2, 0x4, 0x18,
0xac, 0x46, 0xc0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb,
0x5, 0x0, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a, 0x6, 0x3, 0x55, 0x4, 0x3, 0x13, 0x23, 0x59,
0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30,
0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0xd, 0x31, 0x34, 0x30, 0x38, 0x30, 0x31, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0xf, 0x32, 0x30, 0x35, 0x30, 0x30, 0x39, 0x30,
0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6e, 0x31, 0xb, 0x30, 0x9, 0x6, 0x3,
0x55, 0x4, 0x6, 0x13, 0x2, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, 0x6, 0x3, 0x55, 0x4, 0xa,
0xc, 0x9, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, 0x31, 0x22, 0x30, 0x20,
0x6, 0x3, 0x55, 0x4, 0xb, 0xc, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,
0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x31, 0x27, 0x30, 0x25, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0x1e, 0x59, 0x75, 0x62, 0x69,
0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61,
0x6c, 0x20, 0x34, 0x31, 0x33, 0x39, 0x34, 0x33, 0x34, 0x38, 0x38, 0x30, 0x59, 0x30, 0x13,
0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d,
0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x79, 0xea, 0x3b, 0x2c, 0x7c, 0x49, 0x70, 0x10, 0x62,
0x23, 0xc, 0xd2, 0x3f, 0xeb, 0x60, 0xe5, 0x29, 0x31, 0x71, 0xd4, 0x83, 0xf1, 0x0, 0xbe,
0x85, 0x9d, 0x6b, 0xf, 0x83, 0x97, 0x3, 0x1, 0xb5, 0x46, 0xcd, 0xd4, 0x6e, 0xcf, 0xca,
0xe3, 0xe3, 0xf3, 0xf, 0x81, 0xe9, 0xed, 0x62, 0xbd, 0x26, 0x8d, 0x4c, 0x1e, 0xbd, 0x37,
0xb3, 0xbc, 0xbe, 0x92, 0xa8, 0xc2, 0xae, 0xeb, 0x4e, 0x3a, 0xa3, 0x6c, 0x30, 0x6a, 0x30,
0x22, 0x6, 0x9, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xc4, 0xa, 0x2, 0x4, 0x15, 0x31, 0x2e,
0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34, 0x38, 0x32,
0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xe5, 0x1c,
0x2, 0x1, 0x1, 0x4, 0x4, 0x3, 0x2, 0x5, 0x20, 0x30, 0x21, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4,
0x1, 0x82, 0xe5, 0x1c, 0x1, 0x1, 0x4, 0x4, 0x12, 0x4, 0x10, 0xcb, 0x69, 0x48, 0x1e, 0x8f,
0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27, 0x29, 0xa1, 0x54, 0xa8, 0x30, 0xc, 0x6, 0x3, 0x55,
0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x97, 0x9d, 0x3, 0x97,
0xd8, 0x60, 0xf8, 0x2e, 0xe1, 0x5d, 0x31, 0x1c, 0x79, 0x6e, 0xba, 0xfb, 0x22, 0xfa, 0xa7,
0xe0, 0x84, 0xd9, 0xba, 0xb4, 0xc6, 0x1b, 0xbb, 0x57, 0xf3, 0xe6, 0xb4, 0xc1, 0x8a, 0x48,
0x37, 0xb8, 0x5c, 0x3c, 0x4e, 0xdb, 0xe4, 0x83, 0x43, 0xf4, 0xd6, 0xa5, 0xd9, 0xb1, 0xce,
0xda, 0x8a, 0xe1, 0xfe, 0xd4, 0x91, 0x29, 0x21, 0x73, 0x5, 0x8e, 0x5e, 0xe1, 0xcb, 0xdd,
0x6b, 0xda, 0xc0, 0x75, 0x57, 0xc6, 0xa0, 0xe8, 0xd3, 0x68, 0x25, 0xba, 0x15, 0x9e, 0x7f,
0xb5, 0xad, 0x8c, 0xda, 0xf8, 0x4, 0x86, 0x8c, 0xf9, 0xe, 0x8f, 0x1f, 0x8a, 0xea, 0x17,
0xc0, 0x16, 0xb5, 0x5c, 0x2a, 0x7a, 0xd4, 0x97, 0xc8, 0x94, 0xfb, 0x71, 0xd7, 0x53, 0xd7,
0x9b, 0x9a, 0x48, 0x4b, 0x6c, 0x37, 0x6d, 0x72, 0x3b, 0x99, 0x8d, 0x2e, 0x1d, 0x43, 0x6,
0xbf, 0x10, 0x33, 0xb5, 0xae, 0xf8, 0xcc, 0xa5, 0xcb, 0xb2, 0x56, 0x8b, 0x69, 0x24, 0x22,
0x6d, 0x22, 0xa3, 0x58, 0xab, 0x7d, 0x87, 0xe4, 0xac, 0x5f, 0x2e, 0x9, 0x1a, 0xa7, 0x15,
0x79, 0xf3, 0xa5, 0x69, 0x9, 0x49, 0x7d, 0x72, 0xf5, 0x4e, 0x6, 0xba, 0xc1, 0xc3, 0xb4,
0x41, 0x3b, 0xba, 0x5e, 0xaf, 0x94, 0xc3, 0xb6, 0x4f, 0x34, 0xf9, 0xeb, 0xa4, 0x1a, 0xcb,
0x6a, 0xe2, 0x83, 0x77, 0x6d, 0x36, 0x46, 0x53, 0x78, 0x48, 0xfe, 0xe8, 0x84, 0xbd, 0xdd,
0xf5, 0xb1, 0xba, 0x57, 0x98, 0x54, 0xcf, 0xfd, 0xce, 0xba, 0xc3, 0x44, 0x5, 0x95, 0x27,
0xe5, 0x6d, 0xd5, 0x98, 0xf8, 0xf5, 0x66, 0x71, 0x5a, 0xbe, 0x43, 0x1, 0xdd, 0x19, 0x11,
0x30, 0xe6, 0xb9, 0xf0, 0xc6, 0x40, 0x39, 0x12, 0x53, 0xe2, 0x29, 0x80, 0x3f, 0x3a, 0xef,
0x27, 0x4b, 0xed, 0xbf, 0xde, 0x3f, 0xcb, 0xbd, 0x42, 0xea, 0xd6, 0x79,
];
const SAMPLE_CERT_CHAIN: [u8; 709] = [
0x81, 0x59, 0x2, 0xc1, 0x30, 0x82, 0x2, 0xbd, 0x30, 0x82, 0x1, 0xa5, 0xa0, 0x3, 0x2, 0x1,
0x2, 0x2, 0x4, 0x18, 0xac, 0x46, 0xc0, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x30, 0x2e, 0x31, 0x2c, 0x30, 0x2a, 0x6, 0x3, 0x55, 0x4, 0x3,
0x13, 0x23, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x52, 0x6f,
0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x35,
0x37, 0x32, 0x30, 0x30, 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0xd, 0x31, 0x34, 0x30, 0x38,
0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0xf, 0x32, 0x30, 0x35, 0x30,
0x30, 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6e, 0x31, 0xb,
0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, 0x6,
0x3, 0x55, 0x4, 0xa, 0xc, 0x9, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, 0x31,
0x22, 0x30, 0x20, 0x6, 0x3, 0x55, 0x4, 0xb, 0xc, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e,
0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0x1e, 0x59,
0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65,
0x72, 0x69, 0x61, 0x6c, 0x20, 0x34, 0x31, 0x33, 0x39, 0x34, 0x33, 0x34, 0x38, 0x38, 0x30,
0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, 0x8, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x79, 0xea, 0x3b, 0x2c, 0x7c, 0x49,
0x70, 0x10, 0x62, 0x23, 0xc, 0xd2, 0x3f, 0xeb, 0x60, 0xe5, 0x29, 0x31, 0x71, 0xd4, 0x83,
0xf1, 0x0, 0xbe, 0x85, 0x9d, 0x6b, 0xf, 0x83, 0x97, 0x3, 0x1, 0xb5, 0x46, 0xcd, 0xd4, 0x6e,
0xcf, 0xca, 0xe3, 0xe3, 0xf3, 0xf, 0x81, 0xe9, 0xed, 0x62, 0xbd, 0x26, 0x8d, 0x4c, 0x1e,
0xbd, 0x37, 0xb3, 0xbc, 0xbe, 0x92, 0xa8, 0xc2, 0xae, 0xeb, 0x4e, 0x3a, 0xa3, 0x6c, 0x30,
0x6a, 0x30, 0x22, 0x6, 0x9, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xc4, 0xa, 0x2, 0x4, 0x15,
0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, 0x34,
0x38, 0x32, 0x2e, 0x31, 0x2e, 0x37, 0x30, 0x13, 0x6, 0xb, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82,
0xe5, 0x1c, 0x2, 0x1, 0x1, 0x4, 0x4, 0x3, 0x2, 0x5, 0x20, 0x30, 0x21, 0x6, 0xb, 0x2b, 0x6,
0x1, 0x4, 0x1, 0x82, 0xe5, 0x1c, 0x1, 0x1, 0x4, 0x4, 0x12, 0x4, 0x10, 0xcb, 0x69, 0x48,
0x1e, 0x8f, 0xf7, 0x40, 0x39, 0x93, 0xec, 0xa, 0x27, 0x29, 0xa1, 0x54, 0xa8, 0x30, 0xc,
0x6, 0x3, 0x55, 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xd, 0x6, 0x9, 0x2a,
0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x97, 0x9d,
0x3, 0x97, 0xd8, 0x60, 0xf8, 0x2e, 0xe1, 0x5d, 0x31, 0x1c, 0x79, 0x6e, 0xba, 0xfb, 0x22,
0xfa, 0xa7, 0xe0, 0x84, 0xd9, 0xba, 0xb4, 0xc6, 0x1b, 0xbb, 0x57, 0xf3, 0xe6, 0xb4, 0xc1,
0x8a, 0x48, 0x37, 0xb8, 0x5c, 0x3c, 0x4e, 0xdb, 0xe4, 0x83, 0x43, 0xf4, 0xd6, 0xa5, 0xd9,
0xb1, 0xce, 0xda, 0x8a, 0xe1, 0xfe, 0xd4, 0x91, 0x29, 0x21, 0x73, 0x5, 0x8e, 0x5e, 0xe1,
0xcb, 0xdd, 0x6b, 0xda, 0xc0, 0x75, 0x57, 0xc6, 0xa0, 0xe8, 0xd3, 0x68, 0x25, 0xba, 0x15,
0x9e, 0x7f, 0xb5, 0xad, 0x8c, 0xda, 0xf8, 0x4, 0x86, 0x8c, 0xf9, 0xe, 0x8f, 0x1f, 0x8a,
0xea, 0x17, 0xc0, 0x16, 0xb5, 0x5c, 0x2a, 0x7a, 0xd4, 0x97, 0xc8, 0x94, 0xfb, 0x71, 0xd7,
0x53, 0xd7, 0x9b, 0x9a, 0x48, 0x4b, 0x6c, 0x37, 0x6d, 0x72, 0x3b, 0x99, 0x8d, 0x2e, 0x1d,
0x43, 0x6, 0xbf, 0x10, 0x33, 0xb5, 0xae, 0xf8, 0xcc, 0xa5, 0xcb, 0xb2, 0x56, 0x8b, 0x69,
0x24, 0x22, 0x6d, 0x22, 0xa3, 0x58, 0xab, 0x7d, 0x87, 0xe4, 0xac, 0x5f, 0x2e, 0x9, 0x1a,
0xa7, 0x15, 0x79, 0xf3, 0xa5, 0x69, 0x9, 0x49, 0x7d, 0x72, 0xf5, 0x4e, 0x6, 0xba, 0xc1,
0xc3, 0xb4, 0x41, 0x3b, 0xba, 0x5e, 0xaf, 0x94, 0xc3, 0xb6, 0x4f, 0x34, 0xf9, 0xeb, 0xa4,
0x1a, 0xcb, 0x6a, 0xe2, 0x83, 0x77, 0x6d, 0x36, 0x46, 0x53, 0x78, 0x48, 0xfe, 0xe8, 0x84,
0xbd, 0xdd, 0xf5, 0xb1, 0xba, 0x57, 0x98, 0x54, 0xcf, 0xfd, 0xce, 0xba, 0xc3, 0x44, 0x5,
0x95, 0x27, 0xe5, 0x6d, 0xd5, 0x98, 0xf8, 0xf5, 0x66, 0x71, 0x5a, 0xbe, 0x43, 0x1, 0xdd,
0x19, 0x11, 0x30, 0xe6, 0xb9, 0xf0, 0xc6, 0x40, 0x39, 0x12, 0x53, 0xe2, 0x29, 0x80, 0x3f,
0x3a, 0xef, 0x27, 0x4b, 0xed, 0xbf, 0xde, 0x3f, 0xcb, 0xbd, 0x42, 0xea, 0xd6, 0x79,
];
const SAMPLE_AUTH_DATA_MAKE_CREDENTIAL: [u8; 164] = [
0x58, 0xA2, // bytes(162)
// authData
0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84,
0x27, // rp_id_hash
0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a,
0x87, // rp_id_hash
0x05, 0x1d, // rp_id_hash
0xC1, // authData Flags
0x00, 0x00, 0x00, 0x0b, // authData counter
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, // AAGUID
0x00, 0x10, // credential id length
0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c,
0x6f, // credential id
// credential public key
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1,
0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97,
0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20,
0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c,
0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e,
0xa6, 0x1c, // pub key end
// Extensions
0xA1, // map(1)
0x6B, // text(11)
0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret"
0xF5, // true
];
const SAMPLE_AUTH_DATA_GET_ASSERTION: [u8; 229] = [
0x58, 0xE3, // bytes(227)
// authData
0xc2, 0x89, 0xc5, 0xca, 0x9b, 0x04, 0x60, 0xf9, 0x34, 0x6a, 0xb4, 0xe4, 0x2d, 0x84,
0x27, // rp_id_hash
0x43, 0x40, 0x4d, 0x31, 0xf4, 0x84, 0x68, 0x25, 0xa6, 0xd0, 0x65, 0xbe, 0x59, 0x7a,
0x87, // rp_id_hash
0x05, 0x1d, // rp_id_hash
0xC1, // authData Flags
0x00, 0x00, 0x00, 0x0b, // authData counter
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, // AAGUID
0x00, 0x10, // credential id length
0x89, 0x59, 0xce, 0xad, 0x5b, 0x5c, 0x48, 0x16, 0x4e, 0x8a, 0xbc, 0xd6, 0xd9, 0x43, 0x5c,
0x6f, // credential id
// credential public key
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xa5, 0xfd, 0x5c, 0xe1, 0xb1,
0xc4, 0x58, 0xc5, 0x30, 0xa5, 0x4f, 0xa6, 0x1b, 0x31, 0xbf, 0x6b, 0x04, 0xbe, 0x8b, 0x97,
0xaf, 0xde, 0x54, 0xdd, 0x8c, 0xbb, 0x69, 0x27, 0x5a, 0x8a, 0x1b, 0xe1, 0x22, 0x58, 0x20,
0xfa, 0x3a, 0x32, 0x31, 0xdd, 0x9d, 0xee, 0xd9, 0xd1, 0x89, 0x7b, 0xe5, 0xa6, 0x22, 0x8c,
0x59, 0x50, 0x1e, 0x4b, 0xcd, 0x12, 0x97, 0x5d, 0x3d, 0xff, 0x73, 0x0f, 0x01, 0x27, 0x8e,
0xa6, 0x1c, // pub key end
// Extensions
0xA1, // map(1)
0x6B, // text(11)
0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret"
0x58, 0x40, // bytes(64)
0x1F, 0x91, 0x52, 0x6C, 0xAE, 0x45, 0x6E, 0x4C, 0xBB, 0x71, 0xC4, 0xDD, 0xE7, 0xBB, 0x87,
0x71, 0x57, 0xE6, 0xE5, 0x4D, 0xFE, 0xD3, 0x01, 0x5D, 0x7D, 0x4D, 0xBB, 0x22, 0x69, 0xAF,
0xCD, 0xE6, 0xA9, 0x1B, 0x8D, 0x26, 0x7E, 0xBB, 0xF8, 0x48, 0xEB, 0x95, 0xA6, 0x8E, 0x79,
0xC7, 0xAC, 0x70, 0x5E, 0x35, 0x1D, 0x54, 0x3D, 0xB0, 0x16, 0x58, 0x87, 0xD6, 0x29, 0x0F,
0xD4, 0x7A, 0x40, 0xC4,
];
#[test]
fn parse_cert_chain() {
let cert: AttestationCertificate = from_slice(&SAMPLE_CERT_CHAIN[1..]).unwrap();
assert_eq!(&cert.0, &SAMPLE_CERT_CHAIN[4..]);
let _cert: Vec<AttestationCertificate> = from_slice(&SAMPLE_CERT_CHAIN).unwrap();
}
#[test]
fn parse_attestation_object() {
let value: AttestationObject = from_slice(&SAMPLE_ATTESTATION).unwrap();
println!("{:?}", value);
//assert_eq!(true, false);
}
#[test]
fn parse_reader() {
let v: Vec<u8> = vec![
0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72,
];
let (rest, value): (&[u8], String) = from_slice_stream(&v).unwrap();
assert_eq!(value, "foobar");
assert_eq!(rest, &[0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]);
let (rest, value): (&[u8], String) = from_slice_stream(rest).unwrap();
assert_eq!(value, "foobar");
assert!(rest.is_empty());
}
#[test]
fn parse_extensions() {
let auth_make: AuthenticatorData = from_slice(&SAMPLE_AUTH_DATA_MAKE_CREDENTIAL).unwrap();
assert_eq!(
auth_make.extensions.hmac_secret,
Some(HmacSecretResponse::Confirmed(true))
);
let auth_get: AuthenticatorData = from_slice(&SAMPLE_AUTH_DATA_GET_ASSERTION).unwrap();
assert_eq!(
auth_get.extensions.hmac_secret,
Some(HmacSecretResponse::Secret(vec![
0x1F, 0x91, 0x52, 0x6C, 0xAE, 0x45, 0x6E, 0x4C, 0xBB, 0x71, 0xC4, 0xDD, 0xE7, 0xBB,
0x87, 0x71, 0x57, 0xE6, 0xE5, 0x4D, 0xFE, 0xD3, 0x01, 0x5D, 0x7D, 0x4D, 0xBB, 0x22,
0x69, 0xAF, 0xCD, 0xE6, 0xA9, 0x1B, 0x8D, 0x26, 0x7E, 0xBB, 0xF8, 0x48, 0xEB, 0x95,
0xA6, 0x8E, 0x79, 0xC7, 0xAC, 0x70, 0x5E, 0x35, 0x1D, 0x54, 0x3D, 0xB0, 0x16, 0x58,
0x87, 0xD6, 0x29, 0x0F, 0xD4, 0x7A, 0x40, 0xC4,
]))
);
}
/// See: https://github.com/mozilla/authenticator-rs/issues/187
#[test]
fn test_aaguid_output() {
let input = [
0xcb, 0x69, 0x48, 0x1e, 0x8f, 0xf0, 0x00, 0x39, 0x93, 0xec, 0x0a, 0x27, 0x29, 0xa1,
0x54, 0xa8,
];
let expected = "AAGuid(cb69481e-8ff0-0039-93ec-0a2729a154a8)";
let result = AAGuid::from(&input).expect("Failed to parse AAGuid");
let res_str = format!("{:?}", result);
assert_eq!(expected, &res_str);
}
}

399
third_party/rust/authenticator/src/ctap2/client_data.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,399 @@
use super::commands::CommandError;
use crate::transport::errors::HIDError;
use serde::de::{self, Deserializer, Error as SerdeError, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
use serde_json as json;
use sha2::{Digest, Sha256};
use std::fmt;
/// https://w3c.github.io/webauthn/#dom-collectedclientdata-tokenbinding
// tokenBinding, of type TokenBinding
//
// This OPTIONAL member contains information about the state of the Token
// Binding protocol [TokenBinding] used when communicating with the Relying
// Party. Its absence indicates that the client doesnt support token
// binding.
//
// status, of type TokenBindingStatus
//
// This member is one of the following:
//
// supported
//
// Indicates the client supports token binding, but it was not
// negotiated when communicating with the Relying Party.
//
// present
//
// Indicates token binding was used when communicating with the
// Relying Party. In this case, the id member MUST be present.
//
// id, of type DOMString
//
// This member MUST be present if status is present, and MUST be a
// base64url encoding of the Token Binding ID that was used when
// communicating with the Relying Party.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TokenBinding {
Present(String),
Supported,
}
impl Serialize for TokenBinding {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(2))?;
match *self {
TokenBinding::Supported => {
map.serialize_entry(&"status", &"supported")?;
}
TokenBinding::Present(ref v) => {
map.serialize_entry(&"status", "present")?;
// Verify here, that `v` is valid base64 encoded?
// base64::decode_config(&v, base64::URL_SAFE_NO_PAD);
// For now: Let the token do that.
map.serialize_entry(&"id", &v)?;
}
}
map.end()
}
}
impl<'de> Deserialize<'de> for TokenBinding {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct TokenBindingVisitor;
impl<'de> Visitor<'de> for TokenBindingVisitor {
type Value = TokenBinding;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte string")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut id = None;
let mut status = None;
while let Some(key) = map.next_key()? {
match key {
"status" => {
status = Some(map.next_value()?);
}
"id" => {
id = Some(map.next_value()?);
}
k => {
return Err(M::Error::custom(format!("unexpected key: {:?}", k)));
}
}
}
if let Some(stat) = status {
match stat {
"present" => {
if let Some(id) = id {
Ok(TokenBinding::Present(id))
} else {
Err(SerdeError::missing_field("id"))
}
}
"supported" => Ok(TokenBinding::Supported),
k => {
return Err(M::Error::custom(format!(
"unexpected status key: {:?}",
k
)));
}
}
} else {
Err(SerdeError::missing_field("status"))
}
}
}
deserializer.deserialize_map(TokenBindingVisitor)
}
}
/// https://w3c.github.io/webauthn/#dom-collectedclientdata-type
// type, of type DOMString
//
// This member contains the string "webauthn.create" when creating new
// credentials, and "webauthn.get" when getting an assertion from an
// existing credential. The purpose of this member is to prevent certain
// types of signature confusion attacks (where an attacker substitutes one
// legitimate signature for another).
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum WebauthnType {
Create,
Get,
}
impl Serialize for WebauthnType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
WebauthnType::Create => serializer.serialize_str(&"webauthn.create"),
WebauthnType::Get => serializer.serialize_str(&"webauthn.get"),
}
}
}
impl<'de> Deserialize<'de> for WebauthnType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct WebauthnTypeVisitor;
impl<'de> Visitor<'de> for WebauthnTypeVisitor {
type Value = WebauthnType;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match v {
"webauthn.create" => Ok(WebauthnType::Create),
"webauthn.get" => Ok(WebauthnType::Get),
_ => Err(E::custom("unexpected webauthn_type")),
}
}
}
deserializer.deserialize_str(WebauthnTypeVisitor)
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Challenge(pub String);
impl Challenge {
pub fn new(input: Vec<u8>) -> Self {
let value = base64::encode_config(&input, base64::URL_SAFE_NO_PAD);
Challenge(value)
}
}
impl From<Vec<u8>> for Challenge {
fn from(v: Vec<u8>) -> Challenge {
Challenge::new(v)
}
}
impl AsRef<[u8]> for Challenge {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
pub type Origin = String;
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct CollectedClientData {
#[serde(rename = "type")]
pub webauthn_type: WebauthnType,
pub challenge: Challenge,
pub origin: Origin,
// It is optional, according to https://www.w3.org/TR/webauthn/#collectedclientdata-hash-of-the-serialized-client-data
// But we are serializing it, so we *have to* set crossOrigin (if not given, we have to set it to false)
// Thus, on our side, it is not optional. For deserializing, we provide a default (bool's default == False)
#[serde(rename = "crossOrigin", default)]
pub cross_origin: bool,
#[serde(rename = "tokenBinding", skip_serializing_if = "Option::is_none")]
pub token_binding: Option<TokenBinding>,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ClientDataHash([u8; 32]);
impl PartialEq<[u8]> for ClientDataHash {
fn eq(&self, other: &[u8]) -> bool {
self.0.eq(other)
}
}
impl AsRef<[u8]> for ClientDataHash {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Serialize for ClientDataHash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.0)
}
}
#[cfg(test)]
impl<'de> Deserialize<'de> for ClientDataHash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ClientDataHashVisitor;
impl<'de> Visitor<'de> for ClientDataHashVisitor {
type Value = ClientDataHash;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte string")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let mut out = [0u8; 32];
if out.len() != v.len() {
return Err(E::invalid_length(v.len(), &"32"));
}
out.copy_from_slice(v);
Ok(ClientDataHash(out))
}
}
deserializer.deserialize_bytes(ClientDataHashVisitor)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CollectedClientDataWrapper {
pub client_data: CollectedClientData,
pub serialized_data: Vec<u8>,
}
impl CollectedClientDataWrapper {
pub fn new(client_data: CollectedClientData) -> Result<Self, HIDError> {
let serialized_data = json::to_vec(&client_data).map_err(CommandError::Json)?;
Ok(CollectedClientDataWrapper {
client_data,
serialized_data,
})
}
pub fn hash(&self) -> ClientDataHash {
// WebIDL's dictionary definition specifies that the order of the struct
// is exactly as the WebIDL specification declares it, with an algorithm
// for partial dictionaries, so that's how interop works for these
// things.
// See: https://heycam.github.io/webidl/#dfn-dictionary
let mut hasher = Sha256::new();
hasher.update(&self.serialized_data);
let mut output = [0u8; 32];
output.copy_from_slice(hasher.finalize().as_slice());
ClientDataHash(output)
}
}
#[cfg(test)]
mod test {
use crate::CollectedClientDataWrapper;
use super::{Challenge, ClientDataHash, CollectedClientData, TokenBinding, WebauthnType};
use serde_json as json;
#[test]
fn test_token_binding_status() {
let tok = TokenBinding::Present("AAECAw".to_string());
let json_value = json::to_string(&tok).unwrap();
assert_eq!(json_value, "{\"status\":\"present\",\"id\":\"AAECAw\"}");
let tok = TokenBinding::Supported;
let json_value = json::to_string(&tok).unwrap();
assert_eq!(json_value, "{\"status\":\"supported\"}");
}
#[test]
fn test_webauthn_type() {
let t = WebauthnType::Create;
let json_value = json::to_string(&t).unwrap();
assert_eq!(json_value, "\"webauthn.create\"");
let t = WebauthnType::Get;
let json_value = json::to_string(&t).unwrap();
assert_eq!(json_value, "\"webauthn.get\"");
}
#[test]
fn test_collected_client_data_parsing() {
let original_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"crossOrigin\":false,\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}";
let parsed: CollectedClientData = serde_json::from_str(&original_str).unwrap();
let expected = CollectedClientData {
webauthn_type: WebauthnType::Create,
challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]),
origin: String::from("example.com"),
cross_origin: false,
token_binding: Some(TokenBinding::Present("AAECAw".to_string())),
};
assert_eq!(parsed, expected);
let back_again = serde_json::to_string(&expected).unwrap();
assert_eq!(back_again, original_str);
}
#[test]
fn test_collected_client_data_defaults() {
let cross_origin_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"crossOrigin\":false,\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}";
let no_cross_origin_str = "{\"type\":\"webauthn.create\",\"challenge\":\"AAECAw\",\"origin\":\"example.com\",\"tokenBinding\":{\"status\":\"present\",\"id\":\"AAECAw\"}}";
let parsed: CollectedClientData = serde_json::from_str(&no_cross_origin_str).unwrap();
let expected = CollectedClientData {
webauthn_type: WebauthnType::Create,
challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]),
origin: String::from("example.com"),
cross_origin: false,
token_binding: Some(TokenBinding::Present("AAECAw".to_string())),
};
assert_eq!(parsed, expected);
let back_again = serde_json::to_string(&expected).unwrap();
assert_eq!(back_again, cross_origin_str);
}
#[test]
fn test_collected_client_data() {
let client_data = CollectedClientData {
webauthn_type: WebauthnType::Create,
challenge: Challenge::new(vec![0x00, 0x01, 0x02, 0x03]),
origin: String::from("example.com"),
cross_origin: false,
token_binding: Some(TokenBinding::Present("AAECAw".to_string())),
};
let c =
CollectedClientDataWrapper::new(client_data).expect("Failed to serialize client_data");
assert_eq!(
c.hash(),
// echo -n '{"type":"webauthn.create","challenge":"AAECAw","origin":"example.com","crossOrigin":false,"tokenBinding":{"status":"present","id":"AAECAw"}}' | sha256sum -t
ClientDataHash {
0: [
0x75, 0x35, 0x35, 0x7d, 0x49, 0x6e, 0x33, 0xc8, 0x18, 0x7f, 0xea, 0x8d, 0x11,
0x32, 0x64, 0xaa, 0xa4, 0x52, 0x3e, 0x13, 0x40, 0x14, 0x9f, 0xbe, 0x00, 0x3f,
0x10, 0x87, 0x54, 0xc3, 0x2d, 0x80
]
}
);
}
}

784
third_party/rust/authenticator/src/ctap2/commands/client_pin.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,784 @@
use super::{get_info::AuthenticatorInfo, Command, CommandError, RequestCtap2, StatusCode};
use crate::crypto::{
authenticate, decrypt, encapsulate, encrypt, BackendError, COSEKey, CryptoError, ECDHSecret,
};
use crate::transport::errors::HIDError;
use crate::u2ftypes::U2FDevice;
use serde::{
de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor},
ser::SerializeMap,
Deserialize, Deserializer, Serialize, Serializer,
};
use serde_bytes::ByteBuf;
use serde_cbor::de::from_slice;
use serde_cbor::ser::to_vec;
use serde_cbor::Value;
use sha2::{Digest, Sha256};
use std::error::Error as StdErrorT;
use std::fmt;
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum PINSubcommand {
GetRetries = 0x01,
GetKeyAgreement = 0x02,
SetPIN = 0x03,
ChangePIN = 0x04,
GetPINToken = 0x05, // superseded by GetPinUvAuth*
GetPinUvAuthTokenUsingUvWithPermissions = 0x06,
GetUVRetries = 0x07,
GetPinUvAuthTokenUsingPinWithPermissions = 0x09, // Yes, 0x08 is missing
}
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum PinUvAuthTokenPermission {
MakeCredential = 0x01, // rp_id required
GetAssertion = 0x02, // rp_id required
CredentialManagement = 0x04, // rp_id optional
BioEnrollment = 0x08, // rp_id ignored
LargeBlobWrite = 0x10, // rp_id ignored
AuthenticatorConfiguration = 0x20, // rp_id ignored
}
#[derive(Debug)]
pub struct ClientPIN {
pin_protocol: Option<u8>,
subcommand: PINSubcommand,
key_agreement: Option<COSEKey>,
pin_auth: Option<PinAuth>,
new_pin_enc: Option<ByteBuf>,
pin_hash_enc: Option<ByteBuf>,
permissions: Option<u8>,
rp_id: Option<String>,
}
impl Default for ClientPIN {
fn default() -> Self {
ClientPIN {
pin_protocol: None,
subcommand: PINSubcommand::GetRetries,
key_agreement: None,
pin_auth: None,
new_pin_enc: None,
pin_hash_enc: None,
permissions: None,
rp_id: None,
}
}
}
impl Serialize for ClientPIN {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Need to define how many elements are going to be in the map
// beforehand
let mut map_len = 1;
if self.pin_protocol.is_some() {
map_len += 1;
}
if self.key_agreement.is_some() {
map_len += 1;
}
if self.pin_auth.is_some() {
map_len += 1;
}
if self.new_pin_enc.is_some() {
map_len += 1;
}
if self.pin_hash_enc.is_some() {
map_len += 1;
}
if self.permissions.is_some() {
map_len += 1;
}
if self.rp_id.is_some() {
map_len += 1;
}
let mut map = serializer.serialize_map(Some(map_len))?;
if let Some(ref pin_protocol) = self.pin_protocol {
map.serialize_entry(&1, pin_protocol)?;
}
let command: u8 = self.subcommand as u8;
map.serialize_entry(&2, &command)?;
if let Some(ref key_agreement) = self.key_agreement {
map.serialize_entry(&3, key_agreement)?;
}
if let Some(ref pin_auth) = self.pin_auth {
map.serialize_entry(&4, &ByteBuf::from(pin_auth.as_ref()))?;
}
if let Some(ref new_pin_enc) = self.new_pin_enc {
map.serialize_entry(&5, new_pin_enc)?;
}
if let Some(ref pin_hash_enc) = self.pin_hash_enc {
map.serialize_entry(&6, pin_hash_enc)?;
}
if let Some(ref permissions) = self.permissions {
map.serialize_entry(&9, permissions)?;
}
if let Some(ref rp_id) = self.rp_id {
map.serialize_entry(&0x0A, rp_id)?;
}
map.end()
}
}
pub trait ClientPINSubCommand {
type Output;
fn as_client_pin(&self) -> Result<ClientPIN, CommandError>;
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError>;
}
struct ClientPinResponse {
key_agreement: Option<COSEKey>,
pin_token: Option<EncryptedPinToken>,
/// Number of PIN attempts remaining before lockout.
pin_retries: Option<u8>,
power_cycle_state: Option<bool>,
uv_retries: Option<u8>,
}
impl<'de> Deserialize<'de> for ClientPinResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ClientPinResponseVisitor;
impl<'de> Visitor<'de> for ClientPinResponseVisitor {
type Value = ClientPinResponse;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut key_agreement = None;
let mut pin_token = None;
let mut pin_retries = None;
let mut power_cycle_state = None;
let mut uv_retries = None;
while let Some(key) = map.next_key()? {
match key {
0x01 => {
if key_agreement.is_some() {
return Err(SerdeError::duplicate_field("key_agreement"));
}
key_agreement = map.next_value()?;
}
0x02 => {
if pin_token.is_some() {
return Err(SerdeError::duplicate_field("pin_token"));
}
pin_token = map.next_value()?;
}
0x03 => {
if pin_retries.is_some() {
return Err(SerdeError::duplicate_field("pin_retries"));
}
pin_retries = Some(map.next_value()?);
}
0x04 => {
if power_cycle_state.is_some() {
return Err(SerdeError::duplicate_field("power_cycle_state"));
}
power_cycle_state = Some(map.next_value()?);
}
0x05 => {
if uv_retries.is_some() {
return Err(SerdeError::duplicate_field("uv_retries"));
}
uv_retries = Some(map.next_value()?);
}
k => {
warn!("ClientPinResponse: unexpected key: {:?}", k);
let _ = map.next_value::<IgnoredAny>()?;
continue;
}
}
}
Ok(ClientPinResponse {
key_agreement,
pin_token,
pin_retries,
power_cycle_state,
uv_retries,
})
}
}
deserializer.deserialize_bytes(ClientPinResponseVisitor)
}
}
#[derive(Debug)]
pub struct GetKeyAgreement {
pin_protocol: u8,
}
impl GetKeyAgreement {
pub fn new(info: &AuthenticatorInfo) -> Result<Self, CommandError> {
if info.pin_protocols.contains(&1) {
Ok(GetKeyAgreement { pin_protocol: 1 })
} else {
Err(CommandError::UnsupportedPinProtocol)
}
}
}
impl ClientPINSubCommand for GetKeyAgreement {
type Output = KeyAgreement;
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
Ok(ClientPIN {
pin_protocol: Some(self.pin_protocol),
subcommand: PINSubcommand::GetKeyAgreement,
..ClientPIN::default()
})
}
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
let value: Value = from_slice(input).map_err(CommandError::Deserializing)?;
debug!("GetKeyAgreement::parse_response_payload {:?}", value);
let get_pin_response: ClientPinResponse =
from_slice(input).map_err(CommandError::Deserializing)?;
if let Some(key_agreement) = get_pin_response.key_agreement {
Ok(KeyAgreement(key_agreement))
} else {
Err(CommandError::MissingRequiredField("key_agreement"))
}
}
}
#[derive(Debug)]
/// Superseded by GetPinUvAuthTokenUsingUvWithPermissions or GetPinUvAuthTokenUsingPinWithPermissions,
/// thus for backwards compatibility only
pub struct GetPinToken<'sc, 'pin> {
pin_protocol: u8,
shared_secret: &'sc ECDHSecret,
pin: &'pin Pin,
}
impl<'sc, 'pin> GetPinToken<'sc, 'pin> {
pub fn new(
info: &AuthenticatorInfo,
shared_secret: &'sc ECDHSecret,
pin: &'pin Pin,
) -> Result<Self, CommandError> {
if info.pin_protocols.contains(&1) {
Ok(GetPinToken {
pin_protocol: 1,
shared_secret,
pin,
})
} else {
Err(CommandError::UnsupportedPinProtocol)
}
}
}
impl<'sc, 'pin> ClientPINSubCommand for GetPinToken<'sc, 'pin> {
type Output = PinToken;
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
let input = self.pin.for_pin_token();
trace!("pin_hash = {:#04X?}", &input.as_ref());
let pin_hash_enc = encrypt(self.shared_secret.shared_secret(), input.as_ref())
.map_err(|e| CryptoError::Backend(e))?;
trace!("pin_hash_enc = {:#04X?}", &pin_hash_enc);
Ok(ClientPIN {
pin_protocol: Some(self.pin_protocol),
subcommand: PINSubcommand::GetPINToken,
key_agreement: Some(self.shared_secret.my_public_key().clone()),
pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)),
..ClientPIN::default()
})
}
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
let value: Value = from_slice(input).map_err(CommandError::Deserializing)?;
debug!("GetKeyAgreement::parse_response_payload {:?}", value);
let get_pin_response: ClientPinResponse =
from_slice(input).map_err(CommandError::Deserializing)?;
match get_pin_response.pin_token {
Some(encrypted_pin_token) => {
let pin_token = decrypt(
self.shared_secret.shared_secret(),
encrypted_pin_token.as_ref(),
)
.map_err(|e| CryptoError::Backend(e))?;
let pin_token = PinToken(pin_token);
Ok(pin_token)
}
None => Err(CommandError::MissingRequiredField("key_agreement")),
}
}
}
#[derive(Debug)]
pub struct GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
pin_protocol: u8,
shared_secret: &'sc ECDHSecret,
pin: &'pin Pin,
permissions: PinUvAuthTokenPermission,
rp_id: Option<String>,
}
impl<'sc, 'pin> GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
pub fn new(
info: &AuthenticatorInfo,
shared_secret: &'sc ECDHSecret,
pin: &'pin Pin,
permissions: PinUvAuthTokenPermission,
rp_id: Option<String>,
) -> Result<Self, CommandError> {
// TODO(MS): Actually handle protocol 2!
if info.pin_protocols.contains(&1) {
Ok(GetPinUvAuthTokenUsingPinWithPermissions {
pin_protocol: 1,
shared_secret,
pin,
permissions,
rp_id,
})
} else {
Err(CommandError::UnsupportedPinProtocol)
}
}
}
impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
type Output = PinToken;
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
let input = self.pin.for_pin_token();
let pin_hash_enc = encrypt(self.shared_secret.shared_secret(), input.as_ref())
.map_err(|e| CryptoError::Backend(e))?;
Ok(ClientPIN {
pin_protocol: Some(self.pin_protocol),
subcommand: PINSubcommand::GetPinUvAuthTokenUsingPinWithPermissions,
key_agreement: Some(self.shared_secret.my_public_key().clone()),
pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)),
permissions: Some(self.permissions as u8),
rp_id: self.rp_id.clone(), // TODO: This could probably be done less wasteful with &str all the way
..ClientPIN::default()
})
}
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
let value: Value = from_slice(input).map_err(CommandError::Deserializing)?;
debug!("GetKeyAgreement::parse_response_payload {:?}", value);
let get_pin_response: ClientPinResponse =
from_slice(input).map_err(CommandError::Deserializing)?;
match get_pin_response.pin_token {
Some(encrypted_pin_token) => {
let pin_token = decrypt(
self.shared_secret.shared_secret(),
encrypted_pin_token.as_ref(),
)
.map_err(|e| CryptoError::Backend(e))?;
let pin_token = PinToken(pin_token);
Ok(pin_token)
}
None => Err(CommandError::MissingRequiredField("key_agreement")),
}
}
}
#[derive(Debug)]
pub struct GetRetries {}
impl GetRetries {
pub fn new() -> Self {
GetRetries {}
}
}
impl ClientPINSubCommand for GetRetries {
type Output = u8;
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
Ok(ClientPIN {
subcommand: PINSubcommand::GetRetries,
..ClientPIN::default()
})
}
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
let value: Value = from_slice(input).map_err(CommandError::Deserializing)?;
debug!("GetKeyAgreement::parse_response_payload {:?}", value);
let get_pin_response: ClientPinResponse =
from_slice(input).map_err(CommandError::Deserializing)?;
match get_pin_response.pin_retries {
Some(pin_retries) => Ok(pin_retries),
None => Err(CommandError::MissingRequiredField("pin_retries")),
}
}
}
#[derive(Debug)]
pub struct SetNewPin<'sc, 'pin> {
pin_protocol: u8,
shared_secret: &'sc ECDHSecret,
new_pin: &'pin Pin,
}
impl<'sc, 'pin> SetNewPin<'sc, 'pin> {
pub fn new(
info: &AuthenticatorInfo,
shared_secret: &'sc ECDHSecret,
new_pin: &'pin Pin,
) -> Result<Self, CommandError> {
if info.pin_protocols.contains(&1) {
Ok(SetNewPin {
pin_protocol: 1,
shared_secret,
new_pin,
})
} else {
Err(CommandError::UnsupportedPinProtocol)
}
}
}
impl<'sc, 'pin> ClientPINSubCommand for SetNewPin<'sc, 'pin> {
type Output = ();
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
if self.new_pin.as_bytes().len() > 63 {
return Err(CommandError::StatusCode(
StatusCode::PinPolicyViolation,
None,
));
}
// Padding the PIN with trailing zeros, according to spec
let input: Vec<u8> = self
.new_pin
.as_bytes()
.iter()
.chain(std::iter::repeat(&0x00))
.take(64)
.cloned()
.collect();
let shared_secret = self.shared_secret.shared_secret();
// AES256-CBC(sharedSecret, IV=0, newPin)
let new_pin_enc =
encrypt(shared_secret, input.as_ref()).map_err(|e| CryptoError::Backend(e))?;
// LEFT(HMAC-SHA-265(sharedSecret, newPinEnc), 16)
let pin_auth = PinToken(shared_secret.to_vec())
.auth(&new_pin_enc)
.map_err(CommandError::Crypto)?;
Ok(ClientPIN {
pin_protocol: Some(self.pin_protocol),
subcommand: PINSubcommand::SetPIN,
key_agreement: Some(self.shared_secret.my_public_key().clone()),
new_pin_enc: Some(ByteBuf::from(new_pin_enc)),
pin_auth: Some(pin_auth),
..ClientPIN::default()
})
}
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
// Should be an empty response or a valid cbor-value (which we ignore)
if input.is_empty() {
Ok(())
} else {
let _: Value = from_slice(input).map_err(CommandError::Deserializing)?;
Ok(())
}
}
}
#[derive(Debug)]
pub struct ChangeExistingPin<'sc, 'pin> {
pin_protocol: u8,
shared_secret: &'sc ECDHSecret,
current_pin: &'pin Pin,
new_pin: &'pin Pin,
}
impl<'sc, 'pin> ChangeExistingPin<'sc, 'pin> {
pub fn new(
info: &AuthenticatorInfo,
shared_secret: &'sc ECDHSecret,
current_pin: &'pin Pin,
new_pin: &'pin Pin,
) -> Result<Self, CommandError> {
if info.pin_protocols.contains(&1) {
Ok(ChangeExistingPin {
pin_protocol: 1,
shared_secret,
current_pin,
new_pin,
})
} else {
Err(CommandError::UnsupportedPinProtocol)
}
}
}
impl<'sc, 'pin> ClientPINSubCommand for ChangeExistingPin<'sc, 'pin> {
type Output = ();
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
if self.new_pin.as_bytes().len() > 63 {
return Err(CommandError::StatusCode(
StatusCode::PinPolicyViolation,
None,
));
}
// Padding the PIN with trailing zeros, according to spec
let input: Vec<u8> = self
.new_pin
.as_bytes()
.iter()
.chain(std::iter::repeat(&0x00))
.take(64)
.cloned()
.collect();
let shared_secret = self.shared_secret.shared_secret();
// AES256-CBC(sharedSecret, IV=0, newPin)
let new_pin_enc =
encrypt(shared_secret, input.as_ref()).map_err(|e| CryptoError::Backend(e))?;
// AES256-CBC(sharedSecret, IV=0, LEFT(SHA-256(oldPin), 16))
let input = self.current_pin.for_pin_token();
let pin_hash_enc = encrypt(self.shared_secret.shared_secret(), input.as_ref())
.map_err(|e| CryptoError::Backend(e))?;
// LEFT(HMAC-SHA-265(sharedSecret, newPinEnc), 16)
let pin_auth = PinToken(shared_secret.to_vec())
.auth(&[new_pin_enc.as_slice(), pin_hash_enc.as_slice()].concat())
.map_err(CommandError::Crypto)?;
Ok(ClientPIN {
pin_protocol: Some(self.pin_protocol),
subcommand: PINSubcommand::ChangePIN,
key_agreement: Some(self.shared_secret.my_public_key().clone()),
new_pin_enc: Some(ByteBuf::from(new_pin_enc)),
pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)),
pin_auth: Some(pin_auth),
permissions: None,
rp_id: None,
})
}
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
// Should be an empty response or a valid cbor-value (which we ignore)
if input.is_empty() {
Ok(())
} else {
let _: Value = from_slice(input).map_err(CommandError::Deserializing)?;
Ok(())
}
}
}
impl<T> RequestCtap2 for T
where
T: ClientPINSubCommand,
T: fmt::Debug,
{
type Output = <T as ClientPINSubCommand>::Output;
fn command() -> Command {
Command::ClientPin
}
fn wire_format<Dev>(&self, _dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: U2FDevice,
{
let client_pin = self.as_client_pin()?;
let output = to_vec(&client_pin).map_err(CommandError::Serializing)?;
trace!("client subcommmand: {:04X?}", &output);
Ok(output)
}
fn handle_response_ctap2<Dev>(
&self,
_dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: U2FDevice,
{
trace!("Client pin subcomand response:{:04X?}", &input);
if input.is_empty() {
return Err(CommandError::InputTooSmall.into());
}
let status: StatusCode = input[0].into();
debug!("response status code: {:?}", status);
if status.is_ok() {
let res = <T as ClientPINSubCommand>::parse_response_payload(self, &input[1..])
.map_err(HIDError::Command);
res
} else {
let add_data = if input.len() > 1 {
let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Some(data)
} else {
None
};
Err(CommandError::StatusCode(status, add_data).into())
}
}
}
#[derive(Debug)]
pub struct KeyAgreement(COSEKey);
impl KeyAgreement {
pub fn shared_secret(&self) -> Result<ECDHSecret, CommandError> {
encapsulate(&self.0).map_err(|e| CommandError::Crypto(CryptoError::Backend(e)))
}
}
#[derive(Debug, Deserialize)]
pub struct EncryptedPinToken(ByteBuf);
impl AsRef<[u8]> for EncryptedPinToken {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Debug)]
pub struct PinToken(Vec<u8>);
impl PinToken {
pub fn auth(&self, payload: &[u8]) -> Result<PinAuth, CryptoError> {
let hmac = authenticate(self.as_ref(), payload)?;
let mut out = [0u8; 16];
out.copy_from_slice(&hmac[0..16]);
Ok(PinAuth(out.to_vec()))
}
}
impl AsRef<[u8]> for PinToken {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Deserialize))]
pub struct PinAuth(Vec<u8>);
impl PinAuth {
pub(crate) fn empty_pin_auth() -> Self {
PinAuth(vec![])
}
}
impl AsRef<[u8]> for PinAuth {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Serialize for PinAuth {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serde_bytes::serialize(&self.0[..], serializer)
}
}
#[derive(Clone)]
pub struct Pin(String);
impl fmt::Debug for Pin {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Pin(redacted)")
}
}
impl Pin {
pub fn new(value: &str) -> Pin {
Pin(String::from(value))
}
pub fn for_pin_token(&self) -> PinAuth {
let mut hasher = Sha256::new();
hasher.update(&self.0.as_bytes());
let mut output = [0u8; 16];
let len = output.len();
output.copy_from_slice(&hasher.finalize().as_slice()[..len]);
PinAuth(output.to_vec())
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
#[derive(Clone, Debug, Serialize)]
pub enum PinError {
PinRequired,
PinIsTooShort,
PinIsTooLong(usize),
InvalidKeyLen,
InvalidPin(Option<u8>),
PinAuthBlocked,
PinBlocked,
Backend(BackendError),
}
impl fmt::Display for PinError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PinError::PinRequired => write!(f, "PinError: Pin required."),
PinError::PinIsTooShort => write!(f, "PinError: pin is too short"),
PinError::PinIsTooLong(len) => write!(f, "PinError: pin is too long ({})", len),
PinError::InvalidKeyLen => write!(f, "PinError: invalid key len"),
PinError::InvalidPin(ref e) => {
let mut res = write!(f, "PinError: Invalid Pin.");
if let Some(pin_retries) = e {
res = write!(f, " Retries left: {:?}", pin_retries)
}
res
}
PinError::PinAuthBlocked => write!(
f,
"PinError: Pin authentication blocked. Device needs power cycle."
),
PinError::PinBlocked => write!(
f,
"PinError: No retries left. Pin blocked. Device needs reset."
),
PinError::Backend(ref e) => write!(f, "PinError: Crypto backend error: {:?}", e),
}
}
}
impl StdErrorT for PinError {}
impl From<BackendError> for PinError {
fn from(e: BackendError) -> Self {
PinError::Backend(e)
}
}

1282
third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

733
third_party/rust/authenticator/src/ctap2/commands/get_info.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,733 @@
use super::{Command, CommandError, RequestCtap2, StatusCode};
use crate::ctap2::attestation::AAGuid;
use crate::ctap2::server::PublicKeyCredentialParameters;
use crate::transport::errors::HIDError;
use crate::u2ftypes::U2FDevice;
use serde::{
de::{Error as SError, IgnoredAny, MapAccess, Visitor},
Deserialize, Deserializer,
};
use serde_cbor::{de::from_slice, Value};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug)]
pub struct GetInfo {}
impl Default for GetInfo {
fn default() -> GetInfo {
GetInfo {}
}
}
impl RequestCtap2 for GetInfo {
type Output = AuthenticatorInfo;
fn command() -> Command {
Command::GetInfo
}
fn wire_format<Dev>(&self, _dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: U2FDevice,
{
Ok(Vec::new())
}
fn handle_response_ctap2<Dev>(
&self,
_dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: U2FDevice,
{
if input.is_empty() {
return Err(CommandError::InputTooSmall.into());
}
let status: StatusCode = input[0].into();
if input.len() > 1 {
if status.is_ok() {
trace!("parsing authenticator info data: {:#04X?}", &input);
let authenticator_info =
from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Ok(authenticator_info)
} else {
let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Err(CommandError::StatusCode(status, Some(data)).into())
}
} else {
Err(CommandError::InputTooSmall.into())
}
}
}
fn true_val() -> bool {
true
}
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
pub(crate) struct AuthenticatorOptions {
/// Indicates that the device is attached to the client and therefore cant
/// be removed and used on another client.
#[serde(rename = "plat", default)]
pub(crate) platform_device: bool,
/// Indicates that the device is capable of storing keys on the device
/// itself and therefore can satisfy the authenticatorGetAssertion request
/// with allowList parameter not specified or empty.
#[serde(rename = "rk", default)]
pub(crate) resident_key: bool,
/// Client PIN:
/// If present and set to true, it indicates that the device is capable of
/// accepting a PIN from the client and PIN has been set.
/// If present and set to false, it indicates that the device is capable of
/// accepting a PIN from the client and PIN has not been set yet.
/// If absent, it indicates that the device is not capable of accepting a
/// PIN from the client.
/// Client PIN is one of the ways to do user verification.
#[serde(rename = "clientPin")]
pub(crate) client_pin: Option<bool>,
/// Indicates that the device is capable of testing user presence.
#[serde(rename = "up", default = "true_val")]
pub(crate) user_presence: bool,
/// Indicates that the device is capable of verifying the user within
/// itself. For example, devices with UI, biometrics fall into this
/// category.
/// If present and set to true, it indicates that the device is capable of
/// user verification within itself and has been configured.
/// If present and set to false, it indicates that the device is capable of
/// user verification within itself and has not been yet configured. For
/// example, a biometric device that has not yet been configured will
/// return this parameter set to false.
/// If absent, it indicates that the device is not capable of user
/// verification within itself.
/// A device that can only do Client PIN will not return the "uv" parameter.
/// If a device is capable of verifying the user within itself as well as
/// able to do Client PIN, it will return both "uv" and the Client PIN
/// option.
// TODO(MS): My Token (key-ID FIDO2) does return Some(false) here, even though
// it has no built-in verification method. Not to be trusted...
#[serde(rename = "uv")]
pub(crate) user_verification: Option<bool>,
}
impl Default for AuthenticatorOptions {
fn default() -> Self {
AuthenticatorOptions {
platform_device: false,
resident_key: false,
client_pin: None,
user_presence: true,
user_verification: None,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AuthenticatorInfo {
pub(crate) versions: Vec<String>,
pub(crate) extensions: Vec<String>,
pub(crate) aaguid: AAGuid,
pub(crate) options: AuthenticatorOptions,
pub(crate) max_msg_size: Option<usize>,
pub(crate) pin_protocols: Vec<u64>,
// CTAP 2.1
pub(crate) max_credential_count_in_list: Option<usize>,
pub(crate) max_credential_id_length: Option<usize>,
pub(crate) transports: Option<Vec<String>>,
pub(crate) algorithms: Option<Vec<PublicKeyCredentialParameters>>,
pub(crate) max_ser_large_blob_array: Option<u64>,
pub(crate) force_pin_change: Option<bool>,
pub(crate) min_pin_length: Option<u64>,
pub(crate) firmware_version: Option<u64>,
pub(crate) max_cred_blob_length: Option<u64>,
pub(crate) max_rpids_for_set_min_pin_length: Option<u64>,
pub(crate) preferred_platform_uv_attempts: Option<u64>,
pub(crate) uv_modality: Option<u64>,
pub(crate) certifications: Option<BTreeMap<String, u64>>,
pub(crate) remaining_discoverable_credentials: Option<u64>,
pub(crate) vendor_prototype_config_commands: Option<Vec<u64>>,
}
impl AuthenticatorInfo {
pub fn supports_hmac_secret(&self) -> bool {
self.extensions.contains(&"hmac-secret".to_string())
}
}
macro_rules! parse_next_optional_value {
($name:expr, $map:expr) => {
if $name.is_some() {
return Err(serde::de::Error::duplicate_field("$name"));
}
$name = Some($map.next_value()?);
};
}
impl<'de> Deserialize<'de> for AuthenticatorInfo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct AuthenticatorInfoVisitor;
impl<'de> Visitor<'de> for AuthenticatorInfoVisitor {
type Value = AuthenticatorInfo;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut versions = Vec::new();
let mut extensions = Vec::new();
let mut aaguid = None;
let mut options = AuthenticatorOptions::default();
let mut max_msg_size = None;
let mut pin_protocols = Vec::new();
let mut max_credential_count_in_list = None;
let mut max_credential_id_length = None;
let mut transports = None;
let mut algorithms = None;
let mut max_ser_large_blob_array = None;
let mut force_pin_change = None;
let mut min_pin_length = None;
let mut firmware_version = None;
let mut max_cred_blob_length = None;
let mut max_rpids_for_set_min_pin_length = None;
let mut preferred_platform_uv_attempts = None;
let mut uv_modality = None;
let mut certifications = None;
let mut remaining_discoverable_credentials = None;
let mut vendor_prototype_config_commands = None;
while let Some(key) = map.next_key()? {
match key {
0x01 => {
if !versions.is_empty() {
return Err(serde::de::Error::duplicate_field("versions"));
}
versions = map.next_value()?;
}
0x02 => {
if !extensions.is_empty() {
return Err(serde::de::Error::duplicate_field("extensions"));
}
extensions = map.next_value()?;
}
0x03 => {
parse_next_optional_value!(aaguid, map);
}
0x04 => {
options = map.next_value()?;
}
0x05 => {
parse_next_optional_value!(max_msg_size, map);
}
0x06 => {
if !pin_protocols.is_empty() {
return Err(serde::de::Error::duplicate_field("pin_protocols"));
}
pin_protocols = map.next_value()?;
}
0x07 => {
parse_next_optional_value!(max_credential_count_in_list, map);
}
0x08 => {
parse_next_optional_value!(max_credential_id_length, map);
}
0x09 => {
parse_next_optional_value!(transports, map);
}
0x0a => {
parse_next_optional_value!(algorithms, map);
}
0x0b => {
parse_next_optional_value!(max_ser_large_blob_array, map);
}
0x0c => {
parse_next_optional_value!(force_pin_change, map);
}
0x0d => {
parse_next_optional_value!(min_pin_length, map);
}
0x0e => {
parse_next_optional_value!(firmware_version, map);
}
0x0f => {
parse_next_optional_value!(max_cred_blob_length, map);
}
0x10 => {
parse_next_optional_value!(max_rpids_for_set_min_pin_length, map);
}
0x11 => {
parse_next_optional_value!(preferred_platform_uv_attempts, map);
}
0x12 => {
parse_next_optional_value!(uv_modality, map);
}
0x13 => {
parse_next_optional_value!(certifications, map);
}
0x14 => {
parse_next_optional_value!(remaining_discoverable_credentials, map);
}
0x15 => {
parse_next_optional_value!(vendor_prototype_config_commands, map);
}
k => {
warn!("GetInfo: unexpected key: {:?}", k);
let _ = map.next_value::<IgnoredAny>()?;
continue;
}
}
}
if versions.is_empty() {
return Err(M::Error::custom(
"expected at least one version, got none".to_string(),
));
}
if let Some(aaguid) = aaguid {
Ok(AuthenticatorInfo {
versions,
extensions,
aaguid,
options,
max_msg_size,
pin_protocols,
max_credential_count_in_list,
max_credential_id_length,
transports,
algorithms,
max_ser_large_blob_array,
force_pin_change,
min_pin_length,
firmware_version,
max_cred_blob_length,
max_rpids_for_set_min_pin_length,
preferred_platform_uv_attempts,
uv_modality,
certifications,
remaining_discoverable_credentials,
vendor_prototype_config_commands,
})
} else {
Err(M::Error::custom("No AAGuid specified".to_string()))
}
}
}
deserializer.deserialize_bytes(AuthenticatorInfoVisitor)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::consts::{Capability, HIDCmd, CID_BROADCAST};
use crate::crypto::COSEAlgorithm;
use crate::transport::device_selector::Device;
use crate::transport::platform::device::IN_HID_RPT_SIZE;
use crate::transport::{hid::HIDDevice, FidoDevice, Nonce};
use crate::u2ftypes::U2FDevice;
use rand::{thread_rng, RngCore};
use serde_cbor::de::from_slice;
// Raw data take from https://github.com/Yubico/python-fido2/blob/master/test/test_ctap2.py
pub const AAGUID_RAW: [u8; 16] = [
0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1F, 0x9E, 0xDC,
0x7D,
];
pub const AUTHENTICATOR_INFO_PAYLOAD: [u8; 89] = [
0xa6, // map(6)
0x01, // unsigned(1)
0x82, // array(2)
0x66, // text(6)
0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, // "U2F_V2"
0x68, // text(8)
0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, // "FIDO_2_0"
0x02, // unsigned(2)
0x82, // array(2)
0x63, // text(3)
0x75, 0x76, 0x6d, // "uvm"
0x6b, // text(11)
0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret"
0x03, // unsigned(3)
0x50, // bytes(16)
0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc,
0x7d, // "\xF8\xA0\u0011\xF3\x8C\nM\u0015\x80\u0006\u0017\u0011\u001F\x9E\xDC}"
0x04, // unsigned(4)
0xa4, // map(4)
0x62, // text(2)
0x72, 0x6b, // "rk"
0xf5, // primitive(21)
0x62, // text(2)
0x75, 0x70, // "up"
0xf5, // primitive(21)
0x64, // text(4)
0x70, 0x6c, 0x61, 0x74, // "plat"
0xf4, // primitive(20)
0x69, // text(9)
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x69, 0x6e, // "clientPin"
0xf4, // primitive(20)
0x05, // unsigned(5)
0x19, 0x04, 0xb0, // unsigned(1200)
0x06, // unsigned(6)
0x81, // array(1)
0x01, // unsigned(1)
];
// Real world example from Yubikey Bio
pub const AUTHENTICATOR_INFO_PAYLOAD_YK_BIO_5C: [u8; 409] = [
0xB3, // map(19)
0x01, // unsigned(1)
0x84, // array(4)
0x66, // text(6)
0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, // "U2F_V2"
0x68, // text(8)
0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, // "FIDO_2_0"
0x6C, // text(12)
0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x31, 0x5F, 0x50, 0x52,
0x45, // "FIDO_2_1_PRE"
0x68, // text(8)
0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x31, // "FIDO_2_1"
0x02, // unsigned(2)
0x85, // array(5)
0x6B, // text(11)
0x63, 0x72, 0x65, 0x64, 0x50, 0x72, 0x6F, 0x74, 0x65, 0x63, 0x74, // "credProtect"
0x6B, // text(11)
0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "hmac-secret"
0x6C, // text(12)
0x6C, 0x61, 0x72, 0x67, 0x65, 0x42, 0x6C, 0x6F, 0x62, 0x4B, 0x65,
0x79, // "largeBlobKey"
0x68, // text(8)
0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, // "credBlob"
0x6C, // text(12)
0x6D, 0x69, 0x6E, 0x50, 0x69, 0x6E, 0x4C, 0x65, 0x6E, 0x67, 0x74,
0x68, // "minPinLength"
0x03, // unsigned(3)
0x50, // bytes(16)
0xD8, 0x52, 0x2D, 0x9F, 0x57, 0x5B, 0x48, 0x66, 0x88, 0xA9, 0xBA, 0x99, 0xFA, 0x02, 0xF3,
0x5B, // "\xD8R-\x9FW[Hf\x88\xA9\xBA\x99\xFA\u0002\xF3["
0x04, // unsigned(4)
0xB0, // map(16)
0x62, // text(2)
0x72, 0x6B, // "rk"
0xF5, // primitive(21)
0x62, // text(2)
0x75, 0x70, // "up"
0xF5, // primitive(21)
0x62, // text(2)
0x75, 0x76, // "uv"
0xF5, // primitive(21)
0x64, // text(4)
0x70, 0x6C, 0x61, 0x74, // "plat"
0xF4, // primitive(20)
0x67, // text(7)
0x75, 0x76, 0x54, 0x6F, 0x6B, 0x65, 0x6E, // "uvToken"
0xF5, // primitive(21)
0x68, // text(8)
0x61, 0x6C, 0x77, 0x61, 0x79, 0x73, 0x55, 0x76, // "alwaysUv"
0xF5, // primitive(21)
0x68, // text(8)
0x63, 0x72, 0x65, 0x64, 0x4D, 0x67, 0x6D, 0x74, // "credMgmt"
0xF5, // primitive(21)
0x69, // text(9)
0x61, 0x75, 0x74, 0x68, 0x6E, 0x72, 0x43, 0x66, 0x67, // "authnrCfg"
0xF5, // primitive(21)
0x69, // text(9)
0x62, 0x69, 0x6F, 0x45, 0x6E, 0x72, 0x6F, 0x6C, 0x6C, // "bioEnroll"
0xF5, // primitive(21)
0x69, // text(9)
0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, // "clientPin"
0xF5, // primitive(21)
0x6A, // text(10)
0x6C, 0x61, 0x72, 0x67, 0x65, 0x42, 0x6C, 0x6F, 0x62, 0x73, // "largeBlobs"
0xF5, // primitive(21)
0x6E, // text(14)
0x70, 0x69, 0x6E, 0x55, 0x76, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6F, 0x6B, 0x65,
0x6E, // "pinUvAuthToken"
0xF5, // primitive(21)
0x6F, // text(15)
0x73, 0x65, 0x74, 0x4D, 0x69, 0x6E, 0x50, 0x49, 0x4E, 0x4C, 0x65, 0x6E, 0x67, 0x74,
0x68, // "setMinPINLength"
0xF5, // primitive(21)
0x70, // text(16)
0x6D, 0x61, 0x6B, 0x65, 0x43, 0x72, 0x65, 0x64, 0x55, 0x76, 0x4E, 0x6F, 0x74, 0x52, 0x71,
0x64, // "makeCredUvNotRqd"
0xF4, // primitive(20)
0x75, // text(21)
0x63, 0x72, 0x65, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x61, 0x6C, 0x4D, 0x67, 0x6D, 0x74, 0x50,
0x72, 0x65, 0x76, 0x69, 0x65, 0x77, // "credentialMgmtPreview"
0xF5, // primitive(21)
0x78, 0x1B, // text(27)
0x75, 0x73, 0x65, 0x72, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F,
0x6E, 0x4D, 0x67, 0x6D, 0x74, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65,
0x77, // "userVerificationMgmtPreview"
0xF5, // primitive(21)
0x05, // unsigned(5)
0x19, 0x04, 0xB0, // unsigned(1200)
0x06, // unsigned(6)
0x82, // array(2)
0x02, // unsigned(2)
0x01, // unsigned(1)
0x07, // unsigned(7)
0x08, // unsigned(8)
0x08, // unsigned(8)
0x18, 0x80, // unsigned(128)
0x09, // unsigned(9)
0x81, // array(1)
0x63, // text(3)
0x75, 0x73, 0x62, // "usb"
0x0A, // unsigned(10)
0x82, // array(2)
0xA2, // map(2)
0x63, // text(3)
0x61, 0x6C, 0x67, // "alg"
0x26, // negative(6)
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6A, // text(10)
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key"
0xA2, // map(2)
0x63, // text(3)
0x61, 0x6C, 0x67, // "alg"
0x27, // negative(7)
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6A, // text(10)
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, // "public-key"
0x0B, // unsigned(11)
0x19, 0x04, 0x00, // unsigned(1024)
0x0C, // unsigned(12)
0xF4, // primitive(20)
0x0D, // unsigned(13)
0x04, // unsigned(4)
0x0E, // unsigned(14)
0x1A, 0x00, 0x05, 0x05, 0x06, // unsigned(328966)
0x0F, // unsigned(15)
0x18, 0x20, // unsigned(32)
0x10, // unsigned(16)
0x01, // unsigned(1)
0x11, // unsigned(17)
0x03, // unsigned(3)
0x12, // unsigned(18)
0x02, // unsigned(2)
0x14, // unsigned(20)
0x18, 0x18, // unsigned(24)
];
#[test]
fn parse_authenticator_info() {
let authenticator_info: AuthenticatorInfo =
from_slice(&AUTHENTICATOR_INFO_PAYLOAD).unwrap();
let expected = AuthenticatorInfo {
versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()],
extensions: vec!["uvm".to_string(), "hmac-secret".to_string()],
aaguid: AAGuid(AAGUID_RAW),
options: AuthenticatorOptions {
platform_device: false,
resident_key: true,
client_pin: Some(false),
user_presence: true,
user_verification: None,
},
max_msg_size: Some(1200),
pin_protocols: vec![1],
max_credential_count_in_list: None,
max_credential_id_length: None,
transports: None,
algorithms: None,
max_ser_large_blob_array: None,
force_pin_change: None,
min_pin_length: None,
firmware_version: None,
max_cred_blob_length: None,
max_rpids_for_set_min_pin_length: None,
preferred_platform_uv_attempts: None,
uv_modality: None,
certifications: None,
remaining_discoverable_credentials: None,
vendor_prototype_config_commands: None,
};
assert_eq!(authenticator_info, expected);
// Test broken auth info
let mut broken_payload = AUTHENTICATOR_INFO_PAYLOAD.to_vec();
// Have one more entry in the map
broken_payload[0] += 1;
// Add the additional entry at the back with an invalid key
broken_payload.extend_from_slice(&[
0x17, // unsigned(23) -> invalid key-number. CTAP2.1 goes only to 0x15
0x6B, // text(11)
0x69, 0x6E, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x5F, 0x6B, 0x65, 0x79, // "invalid_key"
]);
let authenticator_info: AuthenticatorInfo = from_slice(&broken_payload).unwrap();
assert_eq!(authenticator_info, expected);
}
#[test]
fn parse_authenticator_info_yk_bio_5c() {
let authenticator_info: AuthenticatorInfo =
from_slice(&AUTHENTICATOR_INFO_PAYLOAD_YK_BIO_5C).unwrap();
let expected = AuthenticatorInfo {
versions: vec![
"U2F_V2".to_string(),
"FIDO_2_0".to_string(),
"FIDO_2_1_PRE".to_string(),
"FIDO_2_1".to_string(),
],
extensions: vec![
"credProtect".to_string(),
"hmac-secret".to_string(),
"largeBlobKey".to_string(),
"credBlob".to_string(),
"minPinLength".to_string(),
],
aaguid: AAGuid([
0xd8, 0x52, 0x2d, 0x9f, 0x57, 0x5b, 0x48, 0x66, 0x88, 0xa9, 0xba, 0x99, 0xfa, 0x02,
0xf3, 0x5b,
]),
options: AuthenticatorOptions {
platform_device: false,
resident_key: true,
client_pin: Some(true),
user_presence: true,
user_verification: Some(true),
},
max_msg_size: Some(1200),
pin_protocols: vec![2, 1],
max_credential_count_in_list: Some(8),
max_credential_id_length: Some(128),
transports: Some(vec!["usb".to_string()]),
algorithms: Some(vec![
PublicKeyCredentialParameters {
alg: COSEAlgorithm::ES256,
},
PublicKeyCredentialParameters {
alg: COSEAlgorithm::EDDSA,
},
]),
max_ser_large_blob_array: Some(1024),
force_pin_change: Some(false),
min_pin_length: Some(4),
firmware_version: Some(328966),
max_cred_blob_length: Some(32),
max_rpids_for_set_min_pin_length: Some(1),
preferred_platform_uv_attempts: Some(3),
uv_modality: Some(2),
certifications: None,
remaining_discoverable_credentials: Some(24),
vendor_prototype_config_commands: None,
};
assert_eq!(authenticator_info, expected);
}
#[test]
fn test_get_info_ctap2_only() {
let mut device = Device::new("commands/get_info").unwrap();
let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
// channel id
let mut cid = [0u8; 4];
thread_rng().fill_bytes(&mut cid);
// init packet
let mut msg = CID_BROADCAST.to_vec();
msg.extend(vec![HIDCmd::Init.into(), 0x00, 0x08]); // cmd + bcnt
msg.extend_from_slice(&nonce);
device.add_write(&msg, 0);
// init_resp packet
let mut msg = CID_BROADCAST.to_vec();
msg.extend(vec![
0x06, /*HIDCmd::Init without TYPE_INIT*/
0x00, 0x11,
]); // cmd + bcnt
msg.extend_from_slice(&nonce);
msg.extend_from_slice(&cid); // new channel id
// We are setting NMSG, to signal that the device does not support CTAP1
msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01 | 0x04 | 0x08]); // versions + flags (wink+cbor+nmsg)
device.add_read(&msg, 0);
// ctap2 request
let mut msg = cid.to_vec();
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt
msg.extend(vec![0x04]); // authenticatorGetInfo
device.add_write(&msg, 0);
// ctap2 response
let mut msg = cid.to_vec();
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x5A]); // cmd + bcnt
msg.extend(vec![0]); // Status code: Success
msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[0..(IN_HID_RPT_SIZE - 8)]);
device.add_read(&msg, 0);
// Continuation package
let mut msg = cid.to_vec();
msg.extend(vec![0x00]); // SEQ
msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[(IN_HID_RPT_SIZE - 8)..]);
device.add_read(&msg, 0);
device
.init(Nonce::Use(nonce))
.expect("Failed to init device");
assert_eq!(device.get_cid(), &cid);
let dev_info = device.get_device_info();
assert_eq!(
dev_info.cap_flags,
Capability::WINK | Capability::CBOR | Capability::NMSG
);
let result = device
.get_authenticator_info()
.expect("Didn't get any authenticator_info");
let expected = AuthenticatorInfo {
versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()],
extensions: vec!["uvm".to_string(), "hmac-secret".to_string()],
aaguid: AAGuid(AAGUID_RAW),
options: AuthenticatorOptions {
platform_device: false,
resident_key: true,
client_pin: Some(false),
user_presence: true,
user_verification: None,
},
max_msg_size: Some(1200),
pin_protocols: vec![1],
max_credential_count_in_list: None,
max_credential_id_length: None,
transports: None,
algorithms: None,
max_ser_large_blob_array: None,
force_pin_change: None,
min_pin_length: None,
firmware_version: None,
max_cred_blob_length: None,
max_rpids_for_set_min_pin_length: None,
preferred_platform_uv_attempts: None,
uv_modality: None,
certifications: None,
remaining_discoverable_credentials: None,
vendor_prototype_config_commands: None,
};
assert_eq!(result, &expected);
}
}

Просмотреть файл

@ -0,0 +1,53 @@
use super::{Command, CommandError, RequestCtap2, StatusCode};
use crate::ctap2::commands::get_assertion::GetAssertionResponse;
use crate::transport::errors::HIDError;
use crate::u2ftypes::U2FDevice;
use serde_cbor::{de::from_slice, Value};
#[derive(Debug)]
pub(crate) struct GetNextAssertion;
impl RequestCtap2 for GetNextAssertion {
type Output = GetAssertionResponse;
fn command() -> Command {
Command::GetNextAssertion
}
fn wire_format<Dev>(&self, _dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: U2FDevice,
{
Ok(Vec::new())
}
fn handle_response_ctap2<Dev>(
&self,
_dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: U2FDevice,
{
if input.is_empty() {
return Err(CommandError::InputTooSmall.into());
}
let status: StatusCode = input[0].into();
debug!("response status code: {:?}", status);
if input.len() > 1 {
if status.is_ok() {
let assertion = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
// TODO(baloo): check assertion response does not have numberOfCredentials
Ok(assertion)
} else {
let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Err(CommandError::StatusCode(status, Some(data)).into())
}
} else if status.is_ok() {
Err(CommandError::InputTooSmall.into())
} else {
Err(CommandError::StatusCode(status, None).into())
}
}
}

118
third_party/rust/authenticator/src/ctap2/commands/get_version.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,118 @@
use super::{CommandError, RequestCtap1, Retryable};
use crate::consts::U2F_VERSION;
use crate::transport::errors::{ApduErrorStatus, HIDError};
use crate::u2ftypes::CTAP1RequestAPDU;
use crate::u2ftypes::U2FDevice;
#[allow(non_camel_case_types)]
pub enum U2FInfo {
U2F_V2,
}
#[derive(Debug)]
// TODO(baloo): if one does not issue U2F_VERSION before makecredentials or getassertion, token
// will return error (ConditionsNotSatified), test this in unit tests
pub struct GetVersion {}
impl Default for GetVersion {
fn default() -> GetVersion {
GetVersion {}
}
}
impl RequestCtap1 for GetVersion {
type Output = U2FInfo;
fn handle_response_ctap1(
&self,
_status: Result<(), ApduErrorStatus>,
input: &[u8],
) -> Result<Self::Output, Retryable<HIDError>> {
if input.is_empty() {
return Err(Retryable::Error(HIDError::Command(
CommandError::InputTooSmall,
)));
}
let expected = String::from("U2F_V2");
let result = String::from_utf8_lossy(input);
match result {
ref data if data == &expected => Ok(U2FInfo::U2F_V2),
_ => Err(Retryable::Error(HIDError::UnexpectedVersion)),
}
}
fn ctap1_format<Dev>(&self, _dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: U2FDevice,
{
let flags = 0;
let cmd = U2F_VERSION;
let data = CTAP1RequestAPDU::serialize(cmd, flags, &[])?;
Ok(data)
}
}
#[cfg(test)]
pub mod tests {
use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR};
use crate::transport::device_selector::Device;
use crate::transport::{hid::HIDDevice, FidoDevice, Nonce};
use crate::u2ftypes::U2FDevice;
use rand::{thread_rng, RngCore};
#[test]
fn test_get_version_ctap1_only() {
let mut device = Device::new("commands/get_version").unwrap();
let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
// channel id
let mut cid = [0u8; 4];
thread_rng().fill_bytes(&mut cid);
// init packet
let mut msg = CID_BROADCAST.to_vec();
msg.extend(&[HIDCmd::Init.into(), 0x00, 0x08]); // cmd + bcnt
msg.extend_from_slice(&nonce);
device.add_write(&msg, 0);
// init_resp packet
let mut msg = CID_BROADCAST.to_vec();
msg.extend(vec![
0x06, /*HIDCmd::Init without !TYPE_INIT*/
0x00, 0x11,
]); // cmd + bcnt
msg.extend_from_slice(&nonce);
msg.extend_from_slice(&cid); // new channel id
// We are not setting CBOR, to signal that the device does not support CTAP1
msg.extend(&[0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags (wink)
device.add_read(&msg, 0);
// ctap1 U2F_VERSION request
let mut msg = cid.to_vec();
msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x7]); // cmd + bcnt
msg.extend(&[0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0]);
device.add_write(&msg, 0);
// fido response
let mut msg = cid.to_vec();
msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x08]); // cmd + bcnt
msg.extend(&[0x55, 0x32, 0x46, 0x5f, 0x56, 0x32]); // 'U2F_V2'
msg.extend(&SW_NO_ERROR);
device.add_read(&msg, 0);
device
.init(Nonce::Use(nonce))
.expect("Failed to init device");
assert_eq!(device.get_cid(), &cid);
let dev_info = device.get_device_info();
assert_eq!(dev_info.cap_flags, Capability::WINK);
let result = device.get_authenticator_info();
assert!(result.is_none());
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

461
third_party/rust/authenticator/src/ctap2/commands/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,461 @@
use crate::crypto;
use crate::ctap2::client_data::ClientDataHash;
use crate::ctap2::commands::client_pin::{GetPinToken, GetRetries, Pin, PinAuth, PinError};
use crate::errors::AuthenticatorError;
use crate::transport::errors::{ApduErrorStatus, HIDError};
use crate::transport::FidoDevice;
use serde_cbor::{error::Error as CborError, Value};
use serde_json as json;
use std::error::Error as StdErrorT;
use std::fmt;
use std::io::{Read, Write};
pub(crate) mod client_pin;
pub(crate) mod get_assertion;
pub(crate) mod get_info;
pub(crate) mod get_next_assertion;
pub(crate) mod get_version;
pub(crate) mod make_credentials;
pub(crate) mod reset;
pub(crate) mod selection;
pub trait Request<T>
where
Self: fmt::Debug,
Self: RequestCtap1<Output = T>,
Self: RequestCtap2<Output = T>,
{
fn is_ctap2_request(&self) -> bool;
}
/// Retryable wraps an error type and may ask manager to retry sending a
/// command, this is useful for ctap1 where token will reply with "condition not
/// sufficient" because user needs to press the button.
#[derive(Debug)]
pub enum Retryable<T> {
Retry,
Error(T),
}
impl<T> Retryable<T> {
pub fn is_retry(&self) -> bool {
matches!(*self, Retryable::Retry)
}
pub fn is_error(&self) -> bool {
!self.is_retry()
}
}
impl<T> From<T> for Retryable<T> {
fn from(e: T) -> Self {
Retryable::Error(e)
}
}
pub trait RequestCtap1: fmt::Debug {
type Output;
/// Serializes a request into FIDO v1.x / CTAP1 / U2F format.
///
/// See [`crate::u2ftypes::CTAP1RequestAPDU::serialize()`]
fn ctap1_format<Dev>(&self, dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: FidoDevice + Read + Write + fmt::Debug;
/// Deserializes a response from FIDO v1.x / CTAP1 / U2Fv2 format.
fn handle_response_ctap1(
&self,
status: Result<(), ApduErrorStatus>,
input: &[u8],
) -> Result<Self::Output, Retryable<HIDError>>;
}
pub trait RequestCtap2: fmt::Debug {
type Output;
fn command() -> Command;
fn wire_format<Dev>(&self, dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: FidoDevice + Read + Write + fmt::Debug;
fn handle_response_ctap2<Dev>(
&self,
dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: FidoDevice + Read + Write + fmt::Debug;
}
pub(crate) trait PinAuthCommand {
fn pin(&self) -> &Option<Pin>;
fn set_pin(&mut self, pin: Option<Pin>);
fn pin_auth(&self) -> &Option<PinAuth>;
fn set_pin_auth(&mut self, pin_auth: Option<PinAuth>, pin_auth_protocol: Option<u64>);
fn client_data_hash(&self) -> ClientDataHash;
fn unset_uv_option(&mut self);
fn determine_pin_auth<D: FidoDevice>(&mut self, dev: &mut D) -> Result<(), AuthenticatorError> {
if !dev.supports_ctap2() {
self.set_pin_auth(None, None);
return Ok(());
}
let client_data_hash = self.client_data_hash();
let pin_auth = match calculate_pin_auth(dev, &client_data_hash, &self.pin()) {
Ok(pin_auth) => pin_auth,
Err(e) => {
return Err(repackage_pin_errors(dev, e));
}
};
self.set_pin_auth(pin_auth, Some(1)); // TODO(MS): Currently, we only support version 1
Ok(())
}
}
pub(crate) fn repackage_pin_errors<D: FidoDevice>(
dev: &mut D,
error: AuthenticatorError,
) -> AuthenticatorError {
match error {
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
StatusCode::PinInvalid,
_,
))) => {
// If the given PIN was wrong, determine no. of left retries
let cmd = GetRetries::new();
let retries = dev.send_cbor(&cmd).ok(); // If we got retries, wrap it in Some, otherwise ignore err
return AuthenticatorError::PinError(PinError::InvalidPin(retries));
}
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
StatusCode::PinAuthBlocked,
_,
))) => {
return AuthenticatorError::PinError(PinError::PinAuthBlocked);
}
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
StatusCode::PinBlocked,
_,
))) => {
return AuthenticatorError::PinError(PinError::PinBlocked);
}
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
StatusCode::PinRequired,
_,
))) => {
return AuthenticatorError::PinError(PinError::PinRequired);
}
// TODO(MS): Add "PinNotSet"
// TODO(MS): Add "PinPolicyViolated"
err => {
return err;
}
}
}
// Spec: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api
// and: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticator-api
#[repr(u8)]
#[derive(Debug)]
pub enum Command {
MakeCredentials = 0x01,
GetAssertion = 0x02,
GetInfo = 0x04,
ClientPin = 0x06,
Reset = 0x07,
GetNextAssertion = 0x08,
Selection = 0x0B,
}
impl Command {
#[cfg(test)]
pub fn from_u8(v: u8) -> Option<Command> {
match v {
0x01 => Some(Command::MakeCredentials),
0x02 => Some(Command::GetAssertion),
0x04 => Some(Command::GetInfo),
0x06 => Some(Command::ClientPin),
0x07 => Some(Command::Reset),
0x08 => Some(Command::GetNextAssertion),
_ => None,
}
}
}
#[derive(Debug)]
pub enum StatusCode {
/// Indicates successful response.
OK,
/// The command is not a valid CTAP command.
InvalidCommand,
/// The command included an invalid parameter.
InvalidParameter,
/// Invalid message or item length.
InvalidLength,
/// Invalid message sequencing.
InvalidSeq,
/// Message timed out.
Timeout,
/// Channel busy.
ChannelBusy,
/// Command requires channel lock.
LockRequired,
/// Command not allowed on this cid.
InvalidChannel,
/// Invalid/unexpected CBOR error.
CBORUnexpectedType,
/// Error when parsing CBOR.
InvalidCBOR,
/// Missing non-optional parameter.
MissingParameter,
/// Limit for number of items exceeded.
LimitExceeded,
/// Unsupported extension.
UnsupportedExtension,
/// Valid credential found in the exclude list.
CredentialExcluded,
/// Processing (Lengthy operation is in progress).
Processing,
/// Credential not valid for the authenticator.
InvalidCredential,
/// Authentication is waiting for user interaction.
UserActionPending,
/// Processing, lengthy operation is in progress.
OperationPending,
/// No request is pending.
NoOperations,
/// Authenticator does not support requested algorithm.
UnsupportedAlgorithm,
/// Not authorized for requested operation.
OperationDenied,
/// Internal key storage is full.
KeyStoreFull,
/// No outstanding operations.
NoOperationPending,
/// Unsupported option.
UnsupportedOption,
/// Not a valid option for current operation.
InvalidOption,
/// Pending keep alive was cancelled.
KeepaliveCancel,
/// No valid credentials provided.
NoCredentials,
/// Timeout waiting for user interaction.
UserActionTimeout,
/// Continuation command, such as, authenticatorGetNextAssertion not
/// allowed.
NotAllowed,
/// PIN Invalid.
PinInvalid,
/// PIN Blocked.
PinBlocked,
/// PIN authentication,pinAuth, verification failed.
PinAuthInvalid,
/// PIN authentication,pinAuth, blocked. Requires power recycle to reset.
PinAuthBlocked,
/// No PIN has been set.
PinNotSet,
/// PIN is required for the selected operation.
PinRequired,
/// PIN policy violation. Currently only enforces minimum length.
PinPolicyViolation,
/// pinToken expired on authenticator.
PinTokenExpired,
/// Authenticator cannot handle this request due to memory constraints.
RequestTooLarge,
/// The current operation has timed out.
ActionTimeout,
/// User presence is required for the requested operation.
UpRequired,
/// Unknown status.
Unknown(u8),
}
impl StatusCode {
fn is_ok(&self) -> bool {
matches!(*self, StatusCode::OK)
}
fn device_busy(&self) -> bool {
matches!(*self, StatusCode::ChannelBusy)
}
}
impl From<u8> for StatusCode {
fn from(value: u8) -> StatusCode {
match value {
0x00 => StatusCode::OK,
0x01 => StatusCode::InvalidCommand,
0x02 => StatusCode::InvalidParameter,
0x03 => StatusCode::InvalidLength,
0x04 => StatusCode::InvalidSeq,
0x05 => StatusCode::Timeout,
0x06 => StatusCode::ChannelBusy,
0x0A => StatusCode::LockRequired,
0x0B => StatusCode::InvalidChannel,
0x11 => StatusCode::CBORUnexpectedType,
0x12 => StatusCode::InvalidCBOR,
0x14 => StatusCode::MissingParameter,
0x15 => StatusCode::LimitExceeded,
0x16 => StatusCode::UnsupportedExtension,
0x19 => StatusCode::CredentialExcluded,
0x21 => StatusCode::Processing,
0x22 => StatusCode::InvalidCredential,
0x23 => StatusCode::UserActionPending,
0x24 => StatusCode::OperationPending,
0x25 => StatusCode::NoOperations,
0x26 => StatusCode::UnsupportedAlgorithm,
0x27 => StatusCode::OperationDenied,
0x28 => StatusCode::KeyStoreFull,
0x2A => StatusCode::NoOperationPending,
0x2B => StatusCode::UnsupportedOption,
0x2C => StatusCode::InvalidOption,
0x2D => StatusCode::KeepaliveCancel,
0x2E => StatusCode::NoCredentials,
0x2f => StatusCode::UserActionTimeout,
0x30 => StatusCode::NotAllowed,
0x31 => StatusCode::PinInvalid,
0x32 => StatusCode::PinBlocked,
0x33 => StatusCode::PinAuthInvalid,
0x34 => StatusCode::PinAuthBlocked,
0x35 => StatusCode::PinNotSet,
0x36 => StatusCode::PinRequired,
0x37 => StatusCode::PinPolicyViolation,
0x38 => StatusCode::PinTokenExpired,
0x39 => StatusCode::RequestTooLarge,
0x3A => StatusCode::ActionTimeout,
0x3B => StatusCode::UpRequired,
othr => StatusCode::Unknown(othr),
}
}
}
#[cfg(test)]
impl Into<u8> for StatusCode {
fn into(self) -> u8 {
match self {
StatusCode::OK => 0x00,
StatusCode::InvalidCommand => 0x01,
StatusCode::InvalidParameter => 0x02,
StatusCode::InvalidLength => 0x03,
StatusCode::InvalidSeq => 0x04,
StatusCode::Timeout => 0x05,
StatusCode::ChannelBusy => 0x06,
StatusCode::LockRequired => 0x0A,
StatusCode::InvalidChannel => 0x0B,
StatusCode::CBORUnexpectedType => 0x11,
StatusCode::InvalidCBOR => 0x12,
StatusCode::MissingParameter => 0x14,
StatusCode::LimitExceeded => 0x15,
StatusCode::UnsupportedExtension => 0x16,
StatusCode::CredentialExcluded => 0x19,
StatusCode::Processing => 0x21,
StatusCode::InvalidCredential => 0x22,
StatusCode::UserActionPending => 0x23,
StatusCode::OperationPending => 0x24,
StatusCode::NoOperations => 0x25,
StatusCode::UnsupportedAlgorithm => 0x26,
StatusCode::OperationDenied => 0x27,
StatusCode::KeyStoreFull => 0x28,
StatusCode::NoOperationPending => 0x2A,
StatusCode::UnsupportedOption => 0x2B,
StatusCode::InvalidOption => 0x2C,
StatusCode::KeepaliveCancel => 0x2D,
StatusCode::NoCredentials => 0x2E,
StatusCode::UserActionTimeout => 0x2f,
StatusCode::NotAllowed => 0x30,
StatusCode::PinInvalid => 0x31,
StatusCode::PinBlocked => 0x32,
StatusCode::PinAuthInvalid => 0x33,
StatusCode::PinAuthBlocked => 0x34,
StatusCode::PinNotSet => 0x35,
StatusCode::PinRequired => 0x36,
StatusCode::PinPolicyViolation => 0x37,
StatusCode::PinTokenExpired => 0x38,
StatusCode::RequestTooLarge => 0x39,
StatusCode::ActionTimeout => 0x3A,
StatusCode::UpRequired => 0x3B,
StatusCode::Unknown(othr) => othr,
}
}
}
#[derive(Debug)]
pub enum CommandError {
InputTooSmall,
MissingRequiredField(&'static str),
Deserializing(CborError),
Serializing(CborError),
StatusCode(StatusCode, Option<Value>),
Json(json::Error),
Crypto(crypto::CryptoError),
UnsupportedPinProtocol,
}
impl fmt::Display for CommandError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CommandError::InputTooSmall => write!(f, "CommandError: Input is too small"),
CommandError::MissingRequiredField(field) => {
write!(f, "CommandError: Missing required field {}", field)
}
CommandError::Deserializing(ref e) => {
write!(f, "CommandError: Error while parsing: {}", e)
}
CommandError::Serializing(ref e) => {
write!(f, "CommandError: Error while serializing: {}", e)
}
CommandError::StatusCode(ref code, ref value) => {
write!(f, "CommandError: Unexpected code: {:?} ({:?})", code, value)
}
CommandError::Json(ref e) => write!(f, "CommandError: Json serializing error: {}", e),
CommandError::Crypto(ref e) => write!(f, "CommandError: Crypto error: {:?}", e),
CommandError::UnsupportedPinProtocol => {
write!(f, "CommandError: Pin protocol is not supported")
}
}
}
}
impl StdErrorT for CommandError {}
pub(crate) fn calculate_pin_auth<Dev>(
dev: &mut Dev,
client_data_hash: &ClientDataHash,
pin: &Option<Pin>,
) -> Result<Option<PinAuth>, AuthenticatorError>
where
Dev: FidoDevice,
{
// Not reusing the shared secret here, if it exists, since we might start again
// with a different PIN (e.g. if the last one was wrong)
let (shared_secret, info) = dev.establish_shared_secret()?;
// TODO(MS): What to do if token supports client_pin, but none has been set: Some(false)
// AND a Pin is not None?
let pin_auth = if info.options.client_pin == Some(true) {
let pin = pin
.as_ref()
.ok_or(HIDError::Command(CommandError::StatusCode(
StatusCode::PinRequired,
None,
)))?;
let pin_command = GetPinToken::new(&info, &shared_secret, &pin)?;
let pin_token = dev.send_cbor(&pin_command)?;
Some(
pin_token
.auth(client_data_hash.as_ref())
.map_err(CommandError::Crypto)?,
)
} else {
None
};
Ok(pin_auth)
}

129
third_party/rust/authenticator/src/ctap2/commands/reset.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,129 @@
use super::{Command, CommandError, RequestCtap2, StatusCode};
use crate::transport::errors::HIDError;
use crate::u2ftypes::U2FDevice;
use serde_cbor::{de::from_slice, Value};
#[derive(Debug)]
pub struct Reset {}
impl Default for Reset {
fn default() -> Reset {
Reset {}
}
}
impl RequestCtap2 for Reset {
type Output = ();
fn command() -> Command {
Command::Reset
}
fn wire_format<Dev>(&self, _dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: U2FDevice,
{
Ok(Vec::new())
}
fn handle_response_ctap2<Dev>(
&self,
_dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: U2FDevice,
{
if input.is_empty() {
return Err(CommandError::InputTooSmall.into());
}
let status: StatusCode = input[0].into();
if status.is_ok() {
Ok(())
} else {
let msg = if input.len() > 1 {
let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Some(data)
} else {
None
};
Err(CommandError::StatusCode(status, msg).into())
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::consts::HIDCmd;
use crate::transport::device_selector::Device;
use crate::transport::{hid::HIDDevice, FidoDevice};
use crate::u2ftypes::U2FDevice;
use rand::{thread_rng, RngCore};
use serde_cbor::{de::from_slice, Value};
fn issue_command_and_get_response(cmd: u8, add: &[u8]) -> Result<(), HIDError> {
let mut device = Device::new("commands/Reset").unwrap();
// ctap2 request
let mut cid = [0u8; 4];
thread_rng().fill_bytes(&mut cid);
device.set_cid(cid);
let mut msg = cid.to_vec();
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt
msg.extend(vec![0x07]); // authenticatorReset
device.add_write(&msg, 0);
// ctap2 response
let len = 0x1 + add.len() as u8;
let mut msg = cid.to_vec();
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, len]); // cmd + bcnt
msg.push(cmd); // Status code
msg.extend(add); // + maybe additional data
device.add_read(&msg, 0);
device.send_cbor(&Reset {})
}
#[test]
fn test_select_ctap2_only() {
// Test, if we can parse the status codes specified by the spec
// Ok()
let response = issue_command_and_get_response(0, &vec![]).expect("Unexpected error");
assert_eq!(response, ());
// Denied by the user
let response = issue_command_and_get_response(0x27, &vec![]).expect_err("Not an error!");
assert!(matches!(
response,
HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, None))
));
// Timeout
let response = issue_command_and_get_response(0x2F, &vec![]).expect_err("Not an error!");
assert!(matches!(
response,
HIDError::Command(CommandError::StatusCode(
StatusCode::UserActionTimeout,
None
))
));
// Unexpected error with more random CBOR-data
let add_data = vec![
0x63, // text(3)
0x61, 0x6c, 0x67, // "alg"
];
let response = issue_command_and_get_response(0x02, &add_data).expect_err("Not an error!");
match response {
HIDError::Command(CommandError::StatusCode(StatusCode::InvalidParameter, Some(d))) => {
let expected: Value = from_slice(&add_data).unwrap();
assert_eq!(d, expected)
}
e => panic!("Not the expected response: {:?}", e),
}
}
}

129
third_party/rust/authenticator/src/ctap2/commands/selection.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,129 @@
use super::{Command, CommandError, RequestCtap2, StatusCode};
use crate::transport::errors::HIDError;
use crate::u2ftypes::U2FDevice;
use serde_cbor::{de::from_slice, Value};
#[derive(Debug)]
pub struct Selection {}
impl Default for Selection {
fn default() -> Selection {
Selection {}
}
}
impl RequestCtap2 for Selection {
type Output = ();
fn command() -> Command {
Command::Selection
}
fn wire_format<Dev>(&self, _dev: &mut Dev) -> Result<Vec<u8>, HIDError>
where
Dev: U2FDevice,
{
Ok(Vec::new())
}
fn handle_response_ctap2<Dev>(
&self,
_dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>
where
Dev: U2FDevice,
{
if input.is_empty() {
return Err(CommandError::InputTooSmall.into());
}
let status: StatusCode = input[0].into();
if status.is_ok() {
Ok(())
} else {
let msg = if input.len() > 1 {
let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Some(data)
} else {
None
};
Err(CommandError::StatusCode(status, msg).into())
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::consts::HIDCmd;
use crate::transport::device_selector::Device;
use crate::transport::{hid::HIDDevice, FidoDevice};
use crate::u2ftypes::U2FDevice;
use rand::{thread_rng, RngCore};
use serde_cbor::{de::from_slice, Value};
fn issue_command_and_get_response(cmd: u8, add: &[u8]) -> Result<(), HIDError> {
let mut device = Device::new("commands/selection").unwrap();
// ctap2 request
let mut cid = [0u8; 4];
thread_rng().fill_bytes(&mut cid);
device.set_cid(cid);
let mut msg = cid.to_vec();
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt
msg.extend(vec![0x0B]); // authenticatorSelection
device.add_write(&msg, 0);
// ctap2 response
let len = 0x1 + add.len() as u8;
let mut msg = cid.to_vec();
msg.extend(vec![HIDCmd::Cbor.into(), 0x00, len]); // cmd + bcnt
msg.push(cmd); // Status code
msg.extend(add); // + maybe additional data
device.add_read(&msg, 0);
device.send_cbor(&Selection {})
}
#[test]
fn test_select_ctap2_only() {
// Test, if we can parse the status codes specified by the spec
// Ok()
let response = issue_command_and_get_response(0, &vec![]).expect("Unexpected error");
assert_eq!(response, ());
// Denied by the user
let response = issue_command_and_get_response(0x27, &vec![]).expect_err("Not an error!");
assert!(matches!(
response,
HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, None))
));
// Timeout
let response = issue_command_and_get_response(0x2F, &vec![]).expect_err("Not an error!");
assert!(matches!(
response,
HIDError::Command(CommandError::StatusCode(
StatusCode::UserActionTimeout,
None
))
));
// Unexpected error with more random CBOR-data
let add_data = vec![
0x63, // text(3)
0x61, 0x6c, 0x67, // "alg"
];
let response = issue_command_and_get_response(0x02, &add_data).expect_err("Not an error!");
match response {
HIDError::Command(CommandError::StatusCode(StatusCode::InvalidParameter, Some(d))) => {
let expected: Value = from_slice(&add_data).unwrap();
assert_eq!(d, expected)
}
e => panic!("Not the expected response: {:?}", e),
}
}
}

9
third_party/rust/authenticator/src/ctap2/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
#[allow(dead_code)] // TODO(MS): Remove me asap
pub mod commands;
pub use commands::get_assertion::AssertionObject;
pub(crate) mod attestation;
pub mod client_data;
pub mod server;
pub(crate) mod utils;

510
third_party/rust/authenticator/src/ctap2/server.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,510 @@
use crate::crypto::COSEAlgorithm;
use crate::{errors::AuthenticatorError, AuthenticatorTransports, KeyHandle};
use serde::de::MapAccess;
use serde::{
de::{Error as SerdeError, Visitor},
ser::SerializeMap,
Deserialize, Deserializer, Serialize, Serializer,
};
use serde_bytes::ByteBuf;
use sha2::{Digest, Sha256};
use std::convert::{Into, TryFrom};
use std::fmt;
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct RpIdHash(pub [u8; 32]);
impl fmt::Debug for RpIdHash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD);
write!(f, "RpIdHash({})", value)
}
}
impl AsRef<[u8]> for RpIdHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl RpIdHash {
pub fn from(src: &[u8]) -> Result<RpIdHash, AuthenticatorError> {
let mut payload = [0u8; 32];
if src.len() != payload.len() {
Err(AuthenticatorError::InvalidRelyingPartyInput)
} else {
payload.copy_from_slice(src);
Ok(RpIdHash(payload))
}
}
}
#[derive(Debug, Serialize, Clone, Default)]
#[cfg_attr(test, derive(Deserialize))]
pub struct RelyingParty {
// TODO(baloo): spec is wrong !!!!111
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands
// in the example "A PublicKeyCredentialRpEntity DOM object defined as follows:"
// inconsistent with https://w3c.github.io/webauthn/#sctn-rp-credential-params
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
}
// Note: This enum is provided to make old CTAP1/U2F API work. This should be deprecated at some point
#[derive(Debug, Clone)]
pub enum RelyingPartyWrapper {
Data(RelyingParty),
// CTAP1 hash can be derived from full object, see RelyingParty::hash below,
// but very old backends might still provide application IDs.
Hash(RpIdHash),
}
impl RelyingPartyWrapper {
pub fn hash(&self) -> RpIdHash {
match *self {
RelyingPartyWrapper::Data(ref d) => {
let mut hasher = Sha256::new();
hasher.update(&d.id);
let mut output = [0u8; 32];
output.copy_from_slice(&hasher.finalize().as_slice());
RpIdHash(output)
}
RelyingPartyWrapper::Hash(ref d) => d.clone(),
}
}
}
// TODO(baloo): should we rename this PublicKeyCredentialUserEntity ?
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Deserialize, Default)]
pub struct User {
#[serde(with = "serde_bytes")]
pub id: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>, // This has been removed from Webauthn-2
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "displayName")]
pub display_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKeyCredentialParameters {
pub alg: COSEAlgorithm,
}
impl TryFrom<i32> for PublicKeyCredentialParameters {
type Error = AuthenticatorError;
fn try_from(arg: i32) -> Result<Self, Self::Error> {
let alg = COSEAlgorithm::try_from(arg as i64)?;
Ok(PublicKeyCredentialParameters { alg })
}
}
impl Serialize for PublicKeyCredentialParameters {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("alg", &self.alg)?;
map.serialize_entry("type", "public-key")?;
map.end()
}
}
impl<'de> Deserialize<'de> for PublicKeyCredentialParameters {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct PublicKeyCredentialParametersVisitor;
impl<'de> Visitor<'de> for PublicKeyCredentialParametersVisitor {
type Value = PublicKeyCredentialParameters;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut found_type = false;
let mut alg = None;
while let Some(key) = map.next_key()? {
match key {
"alg" => {
if alg.is_some() {
return Err(SerdeError::duplicate_field("alg"));
}
alg = Some(map.next_value()?);
}
"type" => {
if found_type {
return Err(SerdeError::duplicate_field("type"));
}
let v: &str = map.next_value()?;
if v != "public-key" {
return Err(SerdeError::custom(format!("invalid value: {}", v)));
}
found_type = true;
}
v => {
return Err(SerdeError::unknown_field(v, &[]));
}
}
}
if !found_type {
return Err(SerdeError::missing_field("type"));
}
let alg = alg.ok_or(SerdeError::missing_field("alg"))?;
Ok(PublicKeyCredentialParameters { alg })
}
}
deserializer.deserialize_bytes(PublicKeyCredentialParametersVisitor)
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Transport {
USB,
NFC,
BLE,
Internal,
}
impl From<AuthenticatorTransports> for Vec<Transport> {
fn from(t: AuthenticatorTransports) -> Self {
let mut transports = Vec::new();
if t.contains(AuthenticatorTransports::USB) {
transports.push(Transport::USB);
}
if t.contains(AuthenticatorTransports::NFC) {
transports.push(Transport::NFC);
}
if t.contains(AuthenticatorTransports::BLE) {
transports.push(Transport::BLE);
}
transports
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKeyCredentialDescriptor {
pub id: Vec<u8>,
pub transports: Vec<Transport>,
}
impl Serialize for PublicKeyCredentialDescriptor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// TODO(MS): Transports is OPTIONAL, but some older tokens don't understand it
// and return a CBOR-Parsing error. It is only a hint for the token,
// so we'll leave it out for the moment
let mut map = serializer.serialize_map(Some(2))?;
// let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("type", "public-key")?;
map.serialize_entry("id", &ByteBuf::from(self.id.clone()))?;
// map.serialize_entry("transports", &self.transports)?;
map.end()
}
}
impl<'de> Deserialize<'de> for PublicKeyCredentialDescriptor {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct PublicKeyCredentialDescriptorVisitor;
impl<'de> Visitor<'de> for PublicKeyCredentialDescriptorVisitor {
type Value = PublicKeyCredentialDescriptor;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut found_type = false;
let mut id = None;
let mut transports = None;
while let Some(key) = map.next_key()? {
match key {
"id" => {
if id.is_some() {
return Err(SerdeError::duplicate_field("id"));
}
let id_bytes: ByteBuf = map.next_value()?;
id = Some(id_bytes.into_vec());
}
"transports" => {
if transports.is_some() {
return Err(SerdeError::duplicate_field("transports"));
}
transports = Some(map.next_value()?);
}
"type" => {
if found_type {
return Err(SerdeError::duplicate_field("type"));
}
let v: &str = map.next_value()?;
if v != "public-key" {
return Err(SerdeError::custom(format!("invalid value: {}", v)));
}
found_type = true;
}
v => {
return Err(SerdeError::unknown_field(v, &[]));
}
}
}
if !found_type {
return Err(SerdeError::missing_field("type"));
}
let id = id.ok_or(SerdeError::missing_field("id"))?;
let transports = transports.unwrap_or(Vec::new());
Ok(PublicKeyCredentialDescriptor { id, transports })
}
}
deserializer.deserialize_bytes(PublicKeyCredentialDescriptorVisitor)
}
}
impl From<&KeyHandle> for PublicKeyCredentialDescriptor {
fn from(kh: &KeyHandle) -> Self {
Self {
id: kh.credential.clone(),
transports: kh.transports.into(),
}
}
}
#[cfg(test)]
mod test {
use super::{
COSEAlgorithm, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
Transport, User,
};
#[test]
fn serialize_rp() {
let rp = RelyingParty {
id: String::from("Acme"),
name: None,
icon: None,
};
let payload = ser::to_vec(&rp).unwrap();
assert_eq!(
&payload,
&[
0xa1, // map(1)
0x62, // text(2)
0x69, 0x64, // "id"
0x64, // text(4)
0x41, 0x63, 0x6d, 0x65
]
);
}
#[test]
fn serialize_user() {
let user = User {
id: vec![
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30,
0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82,
0x01, 0x93, 0x30, 0x82,
],
icon: Some(String::from("https://pics.example.com/00/p/aBjjjpqPb.png")),
name: Some(String::from("johnpsmith@example.com")),
display_name: Some(String::from("John P. Smith")),
};
let payload = ser::to_vec(&user).unwrap();
println!("payload = {:?}", payload);
assert_eq!(
payload,
vec![
0xa4, // map(4)
0x62, // text(2)
0x69, 0x64, // "id"
0x58, 0x20, // bytes(32)
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, // userid
0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, // ...
0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, // ...
0x30, 0x82, // ...
0x64, // text(4)
0x69, 0x63, 0x6f, 0x6e, // "icon"
0x78, 0x2b, // text(43)
0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70,
0x69, // "https://pics.example.com/00/p/aBjjjpqPb.png"
0x63, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, // ...
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x30, 0x2f, 0x70, 0x2f, // ...
0x61, 0x42, 0x6a, 0x6a, 0x6a, 0x70, 0x71, 0x50, 0x62, 0x2e, // ...
0x70, 0x6e, 0x67, // ...
0x64, // text(4)
0x6e, 0x61, 0x6d, 0x65, // "name"
0x76, // text(22)
0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74,
0x68, // "johnpsmith@example.com"
0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, // ...
0x6f, 0x6d, // ...
0x6b, // text(11)
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, // "displayName"
0x65, // ...
0x6d, // text(13)
0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x50, 0x2e, 0x20, 0x53, 0x6d, // "John P. Smith"
0x69, 0x74, 0x68, // ...
]
);
}
#[test]
fn serialize_user_noicon_nodisplayname() {
let user = User {
id: vec![
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30,
0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82,
0x01, 0x93, 0x30, 0x82,
],
icon: None,
name: Some(String::from("johnpsmith@example.com")),
display_name: None,
};
let payload = ser::to_vec(&user).unwrap();
println!("payload = {:?}", payload);
assert_eq!(
payload,
vec![
0xa2, // map(2)
0x62, // text(2)
0x69, 0x64, // "id"
0x58, 0x20, // bytes(32)
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, // userid
0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, // ...
0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82, 0x01, 0x93, // ...
0x30, 0x82, // ...
0x64, // text(4)
0x6e, 0x61, 0x6d, 0x65, // "name"
0x76, // text(22)
0x6a, 0x6f, 0x68, 0x6e, 0x70, 0x73, 0x6d, 0x69, 0x74,
0x68, // "johnpsmith@example.com"
0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, // ...
0x6f, 0x6d, // ...
]
);
}
use serde_cbor::ser;
#[test]
fn public_key() {
let keys = vec![
PublicKeyCredentialParameters {
alg: COSEAlgorithm::ES256,
},
PublicKeyCredentialParameters {
alg: COSEAlgorithm::RS256,
},
];
let payload = ser::to_vec(&keys);
println!("payload = {:?}", payload);
let payload = payload.unwrap();
assert_eq!(
payload,
vec![
0x82, // array(2)
0xa2, // map(2)
0x63, // text(3)
0x61, 0x6c, 0x67, // "alg"
0x26, // -7 (ES256)
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6a, // text(10)
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, // "public-key"
0x2D, 0x6B, 0x65, 0x79, // ...
0xa2, // map(2)
0x63, // text(3)
0x61, 0x6c, 0x67, // "alg"
0x39, 0x01, 0x00, // -257 (RS256)
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6a, // text(10)
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, // "public-key"
0x2D, 0x6B, 0x65, 0x79 // ...
]
);
}
#[test]
fn public_key_desc() {
let key = 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::BLE, Transport::USB],
};
let payload = ser::to_vec(&key);
println!("payload = {:?}", payload);
let payload = payload.unwrap();
assert_eq!(
payload,
vec![
// 0xa3, // map(3)
0xa2, // map(2)
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6a, // text(10)
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, // "public-key"
0x2D, 0x6B, 0x65, 0x79, // ...
0x62, // text(2)
0x69, 0x64, // "id"
0x58, 0x20, // bytes(32)
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, // key id
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, // ...
// Deactivated for now
//0x6a, // text(10)
//0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, // "transports"
//0x6f, 0x72, 0x74, 0x73, // ...
//0x82, // array(2)
//0x63, // text(3)
//0x62, 0x6c, 0x65, // "ble"
//0x63, // text(3)
//0x75, 0x73, 0x62 // "usb"
]
);
}
}

14
third_party/rust/authenticator/src/ctap2/utils.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
use serde::de;
use serde_cbor::error::Result;
use serde_cbor::Deserializer;
pub fn from_slice_stream<'a, T>(slice: &'a [u8]) -> Result<(&'a [u8], T)>
where
T: de::Deserialize<'a>,
{
let mut deserializer = Deserializer::from_slice(slice);
let value = de::Deserialize::deserialize(&mut deserializer)?;
let rest = &slice[deserializer.byte_offset()..];
Ok((rest, value))
}

945
third_party/rust/authenticator/src/ctap2_capi.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,945 @@
/* 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 crate::authenticatorservice::{
AuthenticatorService, CtapVersion, RegisterArgsCtap2, SignArgsCtap2,
};
use crate::ctap2::attestation::AttestationStatement;
use crate::ctap2::commands::get_assertion::{Assertion, AssertionObject, GetAssertionOptions};
use crate::ctap2::commands::make_credentials::MakeCredentialsOptions;
use crate::ctap2::server::{
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User,
};
use crate::errors::{AuthenticatorError, U2FTokenError};
use crate::statecallback::StateCallback;
use crate::{AttestationObject, CollectedClientDataWrapper, Pin, StatusUpdate};
use crate::{RegisterResult, SignResult};
use libc::size_t;
use rand::{thread_rng, Rng};
use serde_cbor;
use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::mpsc::channel;
use std::thread;
use std::{ptr, slice};
type Ctap2RegisterResult = Result<(AttestationObject, CString), AuthenticatorError>;
type Ctap2PubKeyCredDescriptors = Vec<PublicKeyCredentialDescriptor>;
type Ctap2RegisterCallback = extern "C" fn(u64, *mut Ctap2RegisterResult);
type Ctap2SignResult = Result<(AssertionObject, CString), AuthenticatorError>;
type Ctap2SignCallback = extern "C" fn(u64, *mut Ctap2SignResult);
type Ctap2StatusUpdateCallback = extern "C" fn(*mut StatusUpdate);
const SIGN_RESULT_PUBKEY_CRED_ID: u8 = 1;
const SIGN_RESULT_AUTH_DATA: u8 = 2;
const SIGN_RESULT_SIGNATURE: u8 = 3;
const SIGN_RESULT_USER_ID: u8 = 4;
const SIGN_RESULT_USER_NAME: u8 = 5;
#[repr(C)]
pub struct AuthenticatorArgsUser {
id_ptr: *const u8,
id_len: usize,
name: *const c_char,
}
#[repr(C)]
pub struct AuthenticatorArgsChallenge {
ptr: *const u8,
len: usize,
}
#[repr(C)]
pub struct AuthenticatorArgsPubCred {
ptr: *const i32,
len: usize,
}
#[repr(C)]
pub struct AuthenticatorArgsOptions {
resident_key: bool,
user_verification: bool,
user_presence: bool,
force_none_attestation: bool,
}
// Generates a new 64-bit transaction id with collision probability 2^-32.
fn new_tid() -> u64 {
thread_rng().gen::<u64>()
}
unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
slice::from_raw_parts(ptr, len).to_vec()
}
/// # Safety
///
/// This method must not be called on a handle twice, and the handle is unusable
/// after.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_mgr_free(mgr: *mut AuthenticatorService) {
if !mgr.is_null() {
drop(Box::from_raw(mgr));
}
}
/// # Safety
///
/// The handle returned by this method must be freed by the caller.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_pkcd_new() -> *mut Ctap2PubKeyCredDescriptors {
Box::into_raw(Box::new(vec![]))
}
/// # Safety
///
/// This method must be used on an actual Ctap2PubKeyCredDescriptors CredDescriptorse
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_pkcd_add(
pkcd: *mut Ctap2PubKeyCredDescriptors,
id_ptr: *const u8,
id_len: usize,
transports: u8,
) {
(*pkcd).push(PublicKeyCredentialDescriptor {
id: from_raw(id_ptr, id_len),
transports: crate::AuthenticatorTransports::from_bits_truncate(transports).into(),
});
}
/// # Safety
///
/// This method must not be called on a handle twice, and the handle is unusable
/// after.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_pkcd_free(khs: *mut Ctap2PubKeyCredDescriptors) {
if !khs.is_null() {
drop(Box::from_raw(khs));
}
}
/// # Safety
///
/// The handle returned by this method must be freed by the caller.
/// The returned handle can be used with all rust_u2f_mgr_*-functions as well
/// but uses CTAP2 as the underlying protocol. CTAP1 requests will be repackaged
/// into CTAP2 (if the device supports it)
#[no_mangle]
pub extern "C" fn rust_ctap2_mgr_new() -> *mut AuthenticatorService {
if let Ok(mut mgr) = AuthenticatorService::new(CtapVersion::CTAP2) {
mgr.add_detected_transports();
Box::into_raw(Box::new(mgr))
} else {
ptr::null_mut()
}
}
fn rewrap_client_data(
client_data: CollectedClientDataWrapper,
) -> Result<CString, AuthenticatorError> {
let s = CString::new(client_data.serialized_data.clone()).map_err(|_| {
AuthenticatorError::Custom("Failed to transform client_data to C String".to_string())
})?;
Ok(s)
}
fn rewrap_register_result(
attestation_object: AttestationObject,
client_data: CollectedClientDataWrapper,
) -> Ctap2RegisterResult {
let s = rewrap_client_data(client_data)?;
Ok((attestation_object, s))
}
fn rewrap_sign_result(
assertion_object: AssertionObject,
client_data: CollectedClientDataWrapper,
) -> Ctap2SignResult {
let s = rewrap_client_data(client_data)?;
Ok((assertion_object, s))
}
/// # Safety
///
/// This method should not be called on AuthenticatorService handles after
/// they've been freed
/// All input is copied and it is the callers responsibility to free appropriately.
/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list
/// to keep the API smaller, as they are essentially the same thing.
/// `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with
/// their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_mgr_register(
mgr: *mut AuthenticatorService,
timeout: u64,
callback: Ctap2RegisterCallback,
status_callback: Ctap2StatusUpdateCallback,
challenge: AuthenticatorArgsChallenge,
relying_party_id: *const c_char,
origin_ptr: *const c_char,
user: AuthenticatorArgsUser,
pub_cred_params: AuthenticatorArgsPubCred,
exclude_list: *const Ctap2PubKeyCredDescriptors,
options: AuthenticatorArgsOptions,
pin_ptr: *const c_char,
) -> u64 {
if mgr.is_null() {
return 0;
}
// Check buffers.
if challenge.ptr.is_null()
|| origin_ptr.is_null()
|| relying_party_id.is_null()
|| user.id_ptr.is_null()
|| user.name.is_null()
|| exclude_list.is_null()
{
return 0;
}
let pub_cred_params = match slice::from_raw_parts(pub_cred_params.ptr, pub_cred_params.len)
.iter()
.map(|x| PublicKeyCredentialParameters::try_from(*x))
.collect()
{
Ok(x) => x,
Err(_) => {
return 0;
}
};
let pin = if pin_ptr.is_null() {
None
} else {
Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy()))
};
let user = User {
id: from_raw(user.id_ptr, user.id_len),
name: Some(CStr::from_ptr(user.name).to_string_lossy().to_string()), // TODO(MS): Use to_str() and error out on failure?
display_name: None,
icon: None,
};
let rp = RelyingParty {
id: CStr::from_ptr(relying_party_id)
.to_string_lossy()
.to_string(),
name: None,
icon: None,
};
let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string();
let challenge = from_raw(challenge.ptr, challenge.len);
let exclude_list = (*exclude_list).clone();
let force_none_attestation = options.force_none_attestation;
let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
thread::spawn(move || loop {
match status_rx.recv() {
Ok(r) => {
let rb = Box::new(r);
status_callback(Box::into_raw(rb));
}
Err(e) => {
status_callback(ptr::null_mut());
error!("Error when receiving status update: {:?}", e);
return;
}
}
});
let tid = new_tid();
let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| {
let res = match rv {
Ok(RegisterResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch(
"rust_ctap2_mgr_register",
2,
)),
Ok(RegisterResult::CTAP2(mut attestation_object, client_data)) => {
if force_none_attestation {
attestation_object.att_statement = AttestationStatement::None;
}
rewrap_register_result(attestation_object, client_data)
}
Err(e) => Err(e),
};
callback(tid, Box::into_raw(Box::new(res)));
}));
let ctap_args = RegisterArgsCtap2 {
challenge,
relying_party: rp,
origin,
user,
pub_cred_params,
exclude_list,
options: MakeCredentialsOptions {
resident_key: options.resident_key.then(|| true),
user_verification: options.user_verification.then(|| true),
},
extensions: Default::default(),
pin,
};
let res = (*mgr).register(timeout, ctap_args.into(), status_tx, state_callback);
if res.is_ok() {
tid
} else {
0
}
}
/// # Safety
///
/// This method should not be called on AuthenticatorService handles after
/// they've been freed
/// Note: `KeyHandles` are used as `PublicKeyCredentialDescriptor`s for the exclude_list
/// to keep the API smaller, as they are essentially the same thing.
/// `PublicKeyCredentialParameters` in pub_cred_params are represented as i32 with
/// their COSE value (see: https://www.iana.org/assignments/cose/cose.xhtml#table-algorithms)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_mgr_sign(
mgr: *mut AuthenticatorService,
timeout: u64,
callback: Ctap2SignCallback,
status_callback: Ctap2StatusUpdateCallback,
challenge: AuthenticatorArgsChallenge,
relying_party_id: *const c_char,
origin_ptr: *const c_char,
allow_list: *const Ctap2PubKeyCredDescriptors,
options: AuthenticatorArgsOptions,
pin_ptr: *const c_char,
) -> u64 {
if mgr.is_null() {
return 0;
}
// Check buffers.
if challenge.ptr.is_null()
|| origin_ptr.is_null()
|| relying_party_id.is_null()
|| allow_list.is_null()
{
return 0;
}
let pin = if pin_ptr.is_null() {
None
} else {
Some(Pin::new(&CStr::from_ptr(pin_ptr).to_string_lossy()))
};
let rpid = CStr::from_ptr(relying_party_id)
.to_string_lossy()
.to_string();
let origin = CStr::from_ptr(origin_ptr).to_string_lossy().to_string();
let challenge = from_raw(challenge.ptr, challenge.len);
let allow_list: Vec<_> = (*allow_list).clone();
let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
thread::spawn(move || loop {
match status_rx.recv() {
Ok(r) => {
let rb = Box::new(r);
status_callback(Box::into_raw(rb));
}
Err(e) => {
status_callback(ptr::null_mut());
error!("Error when receiving status update: {:?}", e);
return;
}
}
});
let single_key_handle = if allow_list.len() == 1 {
Some(allow_list.first().unwrap().clone())
} else {
None
};
let tid = new_tid();
let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| {
let res = match rv {
Ok(SignResult::CTAP1(..)) => Err(AuthenticatorError::VersionMismatch(
"rust_ctap2_mgr_register",
2,
)),
Ok(SignResult::CTAP2(mut assertion_object, client_data)) => {
// The token can omit sending back credentials, if the allow_list had only one
// entry. Thus we re-add that here now for all found assertions before handing it out.
assertion_object.0.iter_mut().for_each(|x| {
x.credentials = x.credentials.clone().or(single_key_handle.clone());
});
rewrap_sign_result(assertion_object, client_data)
}
Err(e) => Err(e),
};
callback(tid, Box::into_raw(Box::new(res)));
}));
let ctap_args = SignArgsCtap2 {
challenge,
origin,
relying_party_id: rpid,
allow_list,
options: GetAssertionOptions {
user_presence: options.user_presence.then(|| true),
user_verification: options.user_verification.then(|| true),
},
extensions: Default::default(),
pin,
};
let res = (*mgr).sign(timeout, ctap_args.into(), status_tx, state_callback);
if res.is_ok() {
tid
} else {
0
}
}
// /// # Safety
// ///
// /// This method must be used on an actual U2FResult handle
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_register_result_error(res: *const Ctap2RegisterResult) -> u8 {
if res.is_null() {
return U2FTokenError::Unknown as u8;
}
match &*res {
Ok(..) => 0, // No error, the request succeeded.
Err(e) => e.as_u2f_errorcode(),
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_error(res: *const Ctap2SignResult) -> u8 {
if res.is_null() {
return U2FTokenError::Unknown as u8;
}
match &*res {
Ok(..) => 0, // No error, the request succeeded.
Err(e) => e.as_u2f_errorcode(),
}
}
/// # Safety
///
/// This method should not be called on RegisterResult handles after they've been
/// freed or a double-free will occur
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_register_res_free(res: *mut Ctap2RegisterResult) {
if !res.is_null() {
drop(Box::from_raw(res));
}
}
/// # Safety
///
/// This method should not be called on SignResult handles after they've been
/// freed or a double-free will occur
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_res_free(res: *mut Ctap2SignResult) {
if !res.is_null() {
drop(Box::from_raw(res));
}
}
/// # Safety
///
/// This method should not be called AuthenticatorService handles after they've
/// been freed
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_mgr_cancel(mgr: *mut AuthenticatorService) {
if !mgr.is_null() {
// Ignore return value.
let _ = (*mgr).cancel();
}
}
unsafe fn client_data_len<T>(
res: *const Result<(T, CString), AuthenticatorError>,
len: *mut size_t,
) -> bool {
if res.is_null() || len.is_null() {
return false;
}
match &*res {
Ok((_, client_data)) => {
*len = client_data.as_bytes().len();
true
}
Err(_) => false,
}
}
/// This function is used to get the length, prior to calling
/// rust_ctap2_register_result_client_data_copy()
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_register_result_client_data_len(
res: *const Ctap2RegisterResult,
len: *mut size_t,
) -> bool {
client_data_len(res, len)
}
/// This function is used to get the length, prior to calling
/// rust_ctap2_sign_result_client_data_copy()
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_len(
res: *const Ctap2SignResult,
len: *mut size_t,
) -> bool {
client_data_len(res, len)
}
unsafe fn client_data_copy<T>(
res: *const Result<(T, CString), AuthenticatorError>,
dst: *mut c_char,
) -> bool {
if dst.is_null() || res.is_null() {
return false;
}
match &*res {
Ok((_, client_data)) => {
ptr::copy_nonoverlapping(client_data.as_ptr(), dst, client_data.as_bytes().len());
return true;
}
Err(_) => false,
}
}
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_register_result_client_data_len)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_register_result_client_data_copy(
res: *const Ctap2RegisterResult,
dst: *mut c_char,
) -> bool {
client_data_copy(res, dst)
}
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_register_result_client_data_len)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_client_data_copy(
res: *const Ctap2SignResult,
dst: *mut c_char,
) -> bool {
client_data_copy(res, dst)
}
/// # Safety
///
/// This function is used to get how long the specific item is.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_register_result_attestation_len(
res: *const Ctap2RegisterResult,
len: *mut size_t,
) -> bool {
if res.is_null() || len.is_null() {
return false;
}
if let Ok((attestation, _)) = &*res {
if let Some(item_len) = serde_cbor::to_vec(&attestation).ok().map(|x| x.len()) {
*len = item_len;
return true;
}
}
false
}
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_register_result_item_len)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_register_result_attestation_copy(
res: *const Ctap2RegisterResult,
dst: *mut u8,
) -> bool {
if res.is_null() || dst.is_null() {
return false;
}
if let Ok((attestation, _)) = &*res {
if let Ok(item) = serde_cbor::to_vec(&attestation) {
ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len());
return true;
}
}
false
}
/// This function is used to get how many assertions there are in total
/// The returned number can be used as index-maximum to access individual
/// fields
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_assertions_len(
res: *const Ctap2SignResult,
len: *mut size_t,
) -> bool {
if res.is_null() || len.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
*len = assertions.0.len();
return true;
}
Err(_) => false,
}
}
fn sign_result_item_len(assertion: &Assertion, item_idx: u8) -> Option<usize> {
match item_idx {
SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.len()),
// This is inefficent! Converting twice here. Once for len, once for copy
SIGN_RESULT_AUTH_DATA => assertion.auth_data.to_vec().ok().map(|x| x.len()),
SIGN_RESULT_SIGNATURE => Some(assertion.signature.len()),
SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.len()),
SIGN_RESULT_USER_NAME => assertion
.user
.as_ref()
.map(|u| {
u.display_name
.as_ref()
.or(u.name.as_ref())
.map(|n| n.as_bytes().len())
})
.flatten(),
_ => None,
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_item_contains(
res: *const Ctap2SignResult,
assertion_idx: usize,
item_idx: u8,
) -> bool {
if res.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
if assertion_idx >= assertions.0.len() {
return false;
}
if item_idx == SIGN_RESULT_AUTH_DATA {
// Short-cut to avoid serializing auth_data
return true;
}
sign_result_item_len(&assertions.0[assertion_idx], item_idx).is_some()
}
Err(_) => false,
}
}
/// # Safety
///
/// This function is used to get how long the specific item is.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_item_len(
res: *const Ctap2SignResult,
assertion_idx: usize,
item_idx: u8,
len: *mut size_t,
) -> bool {
if res.is_null() || len.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
if assertion_idx >= assertions.0.len() {
return false;
}
if let Some(item_len) = sign_result_item_len(&assertions.0[assertion_idx], item_idx) {
*len = item_len;
true
} else {
false
}
}
Err(_) => false,
}
}
unsafe fn sign_result_item_copy(assertion: &Assertion, item_idx: u8, dst: *mut u8) -> bool {
if dst.is_null() {
return false;
}
let tmp_val;
let item = match item_idx {
SIGN_RESULT_PUBKEY_CRED_ID => assertion.credentials.as_ref().map(|x| x.id.as_ref()),
// This is inefficent! Converting twice here. Once for len, once for copy
SIGN_RESULT_AUTH_DATA => {
tmp_val = assertion.auth_data.to_vec().ok();
tmp_val.as_ref().map(|x| x.as_ref())
}
SIGN_RESULT_SIGNATURE => Some(assertion.signature.as_ref()),
SIGN_RESULT_USER_ID => assertion.user.as_ref().map(|u| u.id.as_ref()),
SIGN_RESULT_USER_NAME => assertion
.user
.as_ref()
.map(|u| {
u.display_name
.as_ref()
.or(u.name.as_ref())
.map(|n| n.as_bytes().as_ref())
})
.flatten(),
_ => None,
};
if let Some(item) = item {
ptr::copy_nonoverlapping(item.as_ptr(), dst, item.len());
true
} else {
false
}
}
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_sign_result_item_len)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_item_copy(
res: *const Ctap2SignResult,
assertion_idx: usize,
item_idx: u8,
dst: *mut u8,
) -> bool {
if res.is_null() || dst.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
if assertion_idx >= assertions.0.len() {
return false;
}
sign_result_item_copy(&assertions.0[assertion_idx], item_idx, dst)
}
Err(_) => false,
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_contains_username(
res: *const Ctap2SignResult,
assertion_idx: usize,
) -> bool {
if res.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
if assertion_idx >= assertions.0.len() {
return false;
}
assertions.0[assertion_idx]
.user
.as_ref()
.map(|u| u.display_name.as_ref().or(u.name.as_ref()))
.is_some()
}
Err(_) => false,
}
}
/// # Safety
///
/// This function is used to get how long the specific username is.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_username_len(
res: *const Ctap2SignResult,
assertion_idx: usize,
len: *mut size_t,
) -> bool {
if res.is_null() || len.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
if assertion_idx >= assertions.0.len() {
return false;
}
if let Some(name_len) = assertions.0[assertion_idx]
.user
.as_ref()
.map(|u| u.display_name.as_ref().or(u.name.as_ref()))
.flatten()
.map(|x| x.as_bytes().len())
{
*len = name_len;
true
} else {
false
}
}
Err(_) => false,
}
}
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_sign_result_username_len)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_sign_result_username_copy(
res: *const Ctap2SignResult,
assertion_idx: usize,
dst: *mut c_char,
) -> bool {
if res.is_null() || dst.is_null() {
return false;
}
match &*res {
Ok((assertions, _)) => {
if assertion_idx >= assertions.0.len() {
return false;
}
if let Some(name) = assertions.0[assertion_idx]
.user
.as_ref()
.map(|u| u.display_name.as_ref().or(u.name.as_ref()))
.flatten()
.map(|u| CString::new(u.clone()).ok())
.flatten()
{
ptr::copy_nonoverlapping(name.as_ptr(), dst, name.as_bytes().len());
true
} else {
false
}
}
Err(_) => false,
}
}
/// # Safety
///
/// This function is used to get how long the JSON-representation of a status update is.
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_status_update_len(
res: *const StatusUpdate,
len: *mut size_t,
) -> bool {
if res.is_null() || len.is_null() {
return false;
}
match serde_json::to_string(&*res) {
Ok(s) => {
*len = s.len();
true
}
Err(e) => {
error!("Failed to parse {:?} into json: {:?}", &*res, e);
false
}
}
}
/// # Safety
///
/// This method does not ensure anything about dst before copying, so
/// ensure it is long enough (using rust_ctap2_status_update_len)
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_status_update_copy_json(
res: *const StatusUpdate,
dst: *mut c_char,
) -> bool {
if res.is_null() || dst.is_null() {
return false;
}
match serde_json::to_string(&*res) {
Ok(s) => {
if let Ok(cs) = CString::new(s) {
ptr::copy_nonoverlapping(cs.as_ptr(), dst, cs.as_bytes().len());
true
} else {
error!("Failed to convert String to CString");
false
}
}
Err(e) => {
error!("Failed to parse {:?} into json: {:?}", &*res, e);
false
}
}
}
/// # Safety
///
/// We copy the pin, so it is the callers responsibility to free the argument
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_status_update_send_pin(
res: *const StatusUpdate,
c_pin: *mut c_char,
) -> bool {
if res.is_null() || c_pin.is_null() {
return false;
}
match &*res {
StatusUpdate::PinError(_, sender) => {
if let Ok(pin) = CStr::from_ptr(c_pin).to_str() {
sender
.send(Pin::new(pin))
.map_err(|e| {
error!("Failed to send PIN to device-thread");
e
})
.is_ok()
} else {
error!("Failed to convert PIN from c_char to String");
false
}
}
_ => {
error!("Wrong state!");
false
}
}
}
/// # Safety
///
/// This function frees the memory of res!
#[no_mangle]
pub unsafe extern "C" fn rust_ctap2_destroy_status_update_res(res: *mut StatusUpdate) -> bool {
if res.is_null() {
return false;
}
// Dropping it when we go out of scope
drop(Box::from_raw(res));
true
}

44
third_party/rust/authenticator/src/errors.rs поставляемый
Просмотреть файл

@ -2,6 +2,8 @@
* 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/. */
pub use crate::ctap2::commands::{client_pin::PinError, CommandError};
pub use crate::transport::errors::HIDError;
use std::fmt;
use std::io;
use std::sync::mpsc;
@ -9,6 +11,12 @@ use std::sync::mpsc;
// This composite error type is patterned from Phil Daniels' blog:
// https://www.philipdaniels.com/blog/2019/defining-rust-error-types/
#[derive(Debug)]
pub enum UnsupportedOption {
MaxPinLength,
HmacSecret,
}
#[derive(Debug)]
pub enum AuthenticatorError {
// Errors from external libraries...
@ -20,12 +28,23 @@ pub enum AuthenticatorError {
InternalError(String),
U2FToken(U2FTokenError),
Custom(String),
VersionMismatch(&'static str, u32),
HIDError(HIDError),
CryptoError,
PinError(PinError),
UnsupportedOption(UnsupportedOption),
}
impl AuthenticatorError {
pub fn as_u2f_errorcode(&self) -> u8 {
match *self {
AuthenticatorError::U2FToken(ref err) => *err as u8,
// TODO: This is somewhat ugly, as we hardcode the error code here, instead of using the
// const defined in `u2fhid-capi.h`, which we should.
AuthenticatorError::PinError(PinError::PinRequired) => 6u8,
AuthenticatorError::PinError(PinError::InvalidPin(_)) => 7u8,
AuthenticatorError::PinError(PinError::PinAuthBlocked) => 8u8,
AuthenticatorError::PinError(PinError::PinBlocked) => 9u8,
_ => U2FTokenError::Unknown as u8,
}
}
@ -50,6 +69,19 @@ impl fmt::Display for AuthenticatorError {
write!(f, "A u2f token error occurred {:?}", err)
}
AuthenticatorError::Custom(ref err) => write!(f, "A custom error occurred {:?}", err),
AuthenticatorError::VersionMismatch(manager, version) => write!(
f,
"{} expected arguments of version CTAP{}",
manager, version
),
AuthenticatorError::HIDError(ref e) => write!(f, "Device error: {}", e),
AuthenticatorError::CryptoError => {
write!(f, "The cryptography implementation encountered an error")
}
AuthenticatorError::PinError(ref e) => write!(f, "PIN Error: {}", e),
AuthenticatorError::UnsupportedOption(ref e) => {
write!(f, "Unsupported option: {:?}", e)
}
}
}
}
@ -60,6 +92,18 @@ impl From<io::Error> for AuthenticatorError {
}
}
impl From<HIDError> for AuthenticatorError {
fn from(err: HIDError) -> AuthenticatorError {
AuthenticatorError::HIDError(err)
}
}
impl From<CommandError> for AuthenticatorError {
fn from(err: CommandError) -> AuthenticatorError {
AuthenticatorError::HIDError(HIDError::Command(err))
}
}
impl<T> From<mpsc::SendError<T>> for AuthenticatorError {
fn from(err: mpsc::SendError<T>) -> AuthenticatorError {
AuthenticatorError::InternalError(err.to_string())

77
third_party/rust/authenticator/src/lib.rs поставляемый
Просмотреть файл

@ -5,53 +5,15 @@
#[macro_use]
mod util;
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
pub mod hidproto;
#[cfg(any(target_os = "linux"))]
extern crate libudev;
#[cfg(any(target_os = "linux"))]
#[path = "linux/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "freebsd"))]
extern crate devd_rs;
#[cfg(any(target_os = "freebsd"))]
#[path = "freebsd/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "netbsd"))]
#[path = "netbsd/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "openbsd"))]
#[path = "openbsd/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "macos"))]
extern crate core_foundation;
#[cfg(any(target_os = "macos"))]
#[path = "macos/mod.rs"]
pub mod platform;
#[cfg(any(target_os = "windows"))]
#[path = "windows/mod.rs"]
pub mod platform;
#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "windows"
)))]
#[path = "stub/mod.rs"]
pub mod platform;
extern crate libc;
#[macro_use]
extern crate log;
@ -73,10 +35,26 @@ pub use crate::manager::U2FManager;
mod capi;
pub use crate::capi::*;
pub mod ctap2;
pub use ctap2::attestation::AttestationObject;
pub use ctap2::client_data::{CollectedClientData, CollectedClientDataWrapper};
pub use ctap2::commands::client_pin::{Pin, PinError};
pub use ctap2::AssertionObject;
mod ctap2_capi;
pub use crate::ctap2_capi::*;
pub mod errors;
pub mod statecallback;
mod transport;
mod virtualdevices;
mod status_update;
pub use status_update::*;
mod crypto;
pub use crypto::COSEAlgorithm;
// Keep this in sync with the constants in u2fhid-capi.h.
bitflags! {
pub struct RegisterFlags: u64 {
@ -98,25 +76,28 @@ bitflags! {
}
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct KeyHandle {
pub credential: Vec<u8>,
pub transports: AuthenticatorTransports,
}
pub type AppId = Vec<u8>;
pub type RegisterResult = (Vec<u8>, u2ftypes::U2FDeviceInfo);
pub type SignResult = (AppId, Vec<u8>, Vec<u8>, u2ftypes::U2FDeviceInfo);
pub enum RegisterResult {
CTAP1(Vec<u8>, u2ftypes::U2FDeviceInfo),
CTAP2(AttestationObject, CollectedClientDataWrapper),
}
pub enum SignResult {
CTAP1(AppId, Vec<u8>, Vec<u8>, u2ftypes::U2FDeviceInfo),
CTAP2(AssertionObject, CollectedClientDataWrapper),
}
pub type ResetResult = ();
pub type Result<T> = std::result::Result<T, errors::AuthenticatorError>;
#[derive(Debug, Clone)]
pub enum StatusUpdate {
DeviceAvailable { dev_info: u2ftypes::U2FDeviceInfo },
DeviceUnavailable { dev_info: u2ftypes::U2FDeviceInfo },
Success { dev_info: u2ftypes::U2FDeviceInfo },
}
#[cfg(test)]
#[macro_use]
extern crate assert_matches;

Просмотреть файл

@ -1,112 +0,0 @@
/* 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/. */
extern crate libc;
use std::ffi::{CString, OsString};
use std::io;
use std::io::{Read, Write};
use std::os::unix::prelude::*;
use crate::consts::CID_BROADCAST;
use crate::platform::{hidraw, monitor};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use crate::util::from_unix_result;
#[derive(Debug)]
pub struct Device {
path: OsString,
fd: libc::c_int,
in_rpt_size: usize,
out_rpt_size: usize,
cid: [u8; 4],
dev_info: Option<U2FDeviceInfo>,
}
impl Device {
pub fn new(path: OsString) -> io::Result<Self> {
let cstr = CString::new(path.as_bytes())?;
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
let fd = from_unix_result(fd)?;
let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd);
Ok(Self {
path,
fd,
in_rpt_size,
out_rpt_size,
cid: CID_BROADCAST,
dev_info: None,
})
}
pub fn is_u2f(&self) -> bool {
hidraw::is_u2f_device(self.fd)
}
}
impl Drop for Device {
fn drop(&mut self) {
// Close the fd, ignore any errors.
let _ = unsafe { libc::close(self.fd) };
}
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.path == other.path
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
from_unix_result(rv as usize)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bufp = buf.as_ptr() as *const libc::c_void;
let rv = unsafe { libc::write(self.fd, bufp, buf.len()) };
from_unix_result(rv as usize)
}
// USB HID writes don't buffer, so this will be a nop.
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl U2FDevice for Device {
fn get_cid(&self) -> &[u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
fn in_rpt_size(&self) -> usize {
self.in_rpt_size
}
fn out_rpt_size(&self) -> usize {
self.out_rpt_size
}
fn get_property(&self, prop_name: &str) -> io::Result<String> {
monitor::get_property_linux(&self.path, prop_name)
}
fn get_device_info(&self) -> U2FDeviceInfo {
// unwrap is okay, as dev_info must have already been set, else
// a programmer error
self.dev_info.clone().unwrap()
}
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
self.dev_info = Some(dev_info);
}
}

477
third_party/rust/authenticator/src/manager.rs поставляемый
Просмотреть файл

@ -2,28 +2,40 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::authenticatorservice::AuthenticatorTransport;
use crate::authenticatorservice::{RegisterArgs, RegisterArgsCtap1, SignArgs};
use crate::consts::PARAMETER_SIZE;
use crate::crypto::COSEAlgorithm;
use crate::ctap2::client_data::{CollectedClientData, WebauthnType};
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionOptions};
use crate::ctap2::commands::make_credentials::MakeCredentials;
use crate::ctap2::commands::make_credentials::MakeCredentialsOptions;
use crate::ctap2::server::{
PublicKeyCredentialParameters, RelyingParty, RelyingPartyWrapper, RpIdHash,
};
use crate::errors::*;
use crate::statecallback::StateCallback;
use crate::statemachine::{StateMachine, StateMachineCtap2};
use crate::{Pin, SignFlags};
use runloop::RunLoop;
use std::io;
use std::sync::mpsc::{channel, RecvTimeoutError, Sender};
use std::time::Duration;
use crate::authenticatorservice::AuthenticatorTransport;
use crate::consts::PARAMETER_SIZE;
use crate::errors::*;
use crate::statecallback::StateCallback;
use crate::statemachine::StateMachine;
use runloop::RunLoop;
enum QueueAction {
Register {
flags: crate::RegisterFlags,
RegisterCtap1 {
timeout: u64,
challenge: Vec<u8>,
application: crate::AppId,
key_handles: Vec<crate::KeyHandle>,
ctap_args: RegisterArgsCtap1,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
},
Sign {
RegisterCtap2 {
timeout: u64,
make_credentials: MakeCredentials,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
},
SignCtap1 {
flags: crate::SignFlags,
timeout: u64,
challenge: Vec<u8>,
@ -32,7 +44,24 @@ enum QueueAction {
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
},
SignCtap2 {
timeout: u64,
get_assertion: GetAssertion,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
},
Cancel,
Reset {
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
},
SetPin {
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
},
}
pub struct U2FManager {
@ -50,27 +79,24 @@ impl U2FManager {
while alive() {
match rx.recv_timeout(Duration::from_millis(50)) {
Ok(QueueAction::Register {
flags,
Ok(QueueAction::RegisterCtap1 {
timeout,
challenge,
application,
key_handles,
ctap_args,
status,
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.register(
flags,
ctap_args.flags,
timeout,
challenge,
application,
key_handles,
ctap_args.challenge,
ctap_args.application,
ctap_args.key_handles,
status,
callback,
);
}
Ok(QueueAction::Sign {
Ok(QueueAction::SignCtap1 {
flags,
timeout,
challenge,
@ -95,6 +121,17 @@ impl U2FManager {
// polling thread before the old one has shut down.
sm.cancel();
}
Ok(QueueAction::RegisterCtap2 { .. }) => {
// TODO(MS): What to do here? Error out? Silently ignore?
unimplemented!();
}
Ok(QueueAction::SignCtap2 { .. }) => {
// TODO(MS): What to do here? Error out? Silently ignore?
unimplemented!();
}
Ok(QueueAction::Reset { .. }) | Ok(QueueAction::SetPin { .. }) => {
unimplemented!();
}
Err(RecvTimeoutError::Disconnected) => {
break;
}
@ -113,30 +150,30 @@ impl U2FManager {
impl AuthenticatorTransport for U2FManager {
fn register(
&mut self,
flags: crate::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: crate::AppId,
key_handles: Vec<crate::KeyHandle>,
ctap_args: RegisterArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> crate::Result<()> {
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
let args = match ctap_args {
RegisterArgs::CTAP1(args) => args,
RegisterArgs::CTAP2(_) => {
return Err(AuthenticatorError::VersionMismatch("U2FManager", 1));
}
};
if args.challenge.len() != PARAMETER_SIZE || args.application.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
for key_handle in &args.key_handles {
if key_handle.credential.len() >= 256 {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
}
let action = QueueAction::Register {
flags,
let action = QueueAction::RegisterCtap1 {
timeout,
challenge,
application,
key_handles,
ctap_args: args,
status,
callback,
};
@ -145,40 +182,44 @@ impl AuthenticatorTransport for U2FManager {
fn sign(
&mut self,
flags: crate::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<crate::AppId>,
key_handles: Vec<crate::KeyHandle>,
ctap_args: SignArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
if challenge.len() != PARAMETER_SIZE {
let args = match ctap_args {
SignArgs::CTAP1(args) => args,
SignArgs::CTAP2(_) => {
return Err(AuthenticatorError::VersionMismatch("U2FManager", 1));
}
};
if args.challenge.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
if app_ids.is_empty() {
if args.app_ids.is_empty() {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
for app_id in &app_ids {
for app_id in &args.app_ids {
if app_id.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
for key_handle in &args.key_handles {
if key_handle.credential.len() >= 256 {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
}
let action = QueueAction::Sign {
flags,
let action = QueueAction::SignCtap1 {
flags: args.flags,
timeout,
challenge,
app_ids,
key_handles,
challenge: args.challenge,
app_ids: args.app_ids,
key_handles: args.key_handles,
status,
callback,
};
@ -188,6 +229,34 @@ impl AuthenticatorTransport for U2FManager {
fn cancel(&mut self) -> crate::Result<()> {
Ok(self.tx.send(QueueAction::Cancel)?)
}
fn reset(
&mut self,
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
Ok(self.tx.send(QueueAction::Reset {
timeout,
status,
callback,
})?)
}
fn set_pin(
&mut self,
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
Ok(self.tx.send(QueueAction::SetPin {
timeout,
new_pin,
status,
callback,
})?)
}
}
impl Drop for U2FManager {
@ -195,3 +264,313 @@ impl Drop for U2FManager {
self.queue.cancel();
}
}
pub struct Manager {
queue: RunLoop,
tx: Sender<QueueAction>,
}
impl Manager {
pub fn new() -> io::Result<Self> {
let (tx, rx) = channel();
// Start a new work queue thread.
let queue = RunLoop::new(move |alive| {
let mut sm = StateMachineCtap2::new();
while alive() {
match rx.recv_timeout(Duration::from_millis(50)) {
Ok(QueueAction::RegisterCtap2 {
timeout,
make_credentials,
status,
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.register(timeout, make_credentials, status, callback);
}
Ok(QueueAction::SignCtap2 {
timeout,
get_assertion,
status,
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.sign(timeout, get_assertion, status, callback);
}
Ok(QueueAction::Cancel) => {
// Cancelling must block so that we don't start a new
// polling thread before the old one has shut down.
sm.cancel();
}
Ok(QueueAction::Reset {
timeout,
status,
callback,
}) => {
// Reset the token: Delete all keypairs, reset PIN
sm.reset(timeout, status, callback);
}
Ok(QueueAction::SetPin {
timeout,
new_pin,
status,
callback,
}) => {
// This must not block, otherwise we can't cancel.
sm.set_pin(timeout, new_pin, status, callback);
}
Ok(QueueAction::RegisterCtap1 {
timeout: _,
ctap_args: _,
status: _,
callback: _,
}) => {
// TODO(MS): Remove QueueAction::RegisterCtap1 once U2FManager is deleted.
// The repackaging from CTAP1 to CTAP2 happens in self.register()
unimplemented!();
}
Ok(QueueAction::SignCtap1 {
timeout: _,
callback: _,
flags: _,
challenge: _,
app_ids: _,
key_handles: _,
status: _,
}) => {
// TODO(MS): Remove QueueAction::SignCtap1 once U2FManager is deleted.
// The repackaging from CTAP1 to CTAP2 happens in self.sign()
unimplemented!()
}
Err(RecvTimeoutError::Disconnected) => {
break;
}
_ => { /* continue */ }
}
}
// Cancel any ongoing activity.
sm.cancel();
})?;
Ok(Self { queue, tx })
}
}
impl Drop for Manager {
fn drop(&mut self) {
self.queue.cancel();
}
}
impl AuthenticatorTransport for Manager {
fn register(
&mut self,
timeout: u64,
ctap_args: RegisterArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) -> Result<(), AuthenticatorError> {
let make_credentials = match ctap_args {
RegisterArgs::CTAP2(args) => {
let client_data = CollectedClientData {
webauthn_type: WebauthnType::Create,
challenge: args.challenge.into(),
origin: args.origin,
cross_origin: false,
token_binding: None,
};
MakeCredentials::new(
client_data,
RelyingPartyWrapper::Data(args.relying_party),
Some(args.user),
args.pub_cred_params,
args.exclude_list,
args.options,
args.extensions,
args.pin,
// pin_auth will be filled in Statemachine, once we have a device
)
}
RegisterArgs::CTAP1(args) => {
let client_data = CollectedClientData {
webauthn_type: WebauthnType::Create,
challenge: args.challenge.into(),
origin: String::new(),
cross_origin: false,
token_binding: None,
};
MakeCredentials::new(
client_data,
RelyingPartyWrapper::Hash(RpIdHash::from(&args.application)?),
None,
vec![PublicKeyCredentialParameters {
alg: COSEAlgorithm::ES256,
}],
args.key_handles
.iter()
.map(|k| k.into())
.collect::<Vec<_>>(),
MakeCredentialsOptions {
resident_key: None,
user_verification: None,
},
Default::default(),
None,
)
}
}?;
let action = QueueAction::RegisterCtap2 {
timeout,
make_credentials,
status,
callback,
};
Ok(self.tx.send(action)?)
}
fn sign(
&mut self,
timeout: u64,
ctap_args: SignArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) -> crate::Result<()> {
match ctap_args {
SignArgs::CTAP1(args) => {
if args.challenge.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
if args.app_ids.is_empty() {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
let client_data = CollectedClientData {
webauthn_type: WebauthnType::Get,
challenge: args.challenge.into(),
origin: String::new(),
cross_origin: false,
token_binding: None,
};
let options = if args.flags == SignFlags::empty() {
GetAssertionOptions::default()
} else {
GetAssertionOptions {
user_verification: Some(
args.flags.contains(SignFlags::REQUIRE_USER_VERIFICATION),
),
..GetAssertionOptions::default()
}
};
for app_id in &args.app_ids {
if app_id.len() != PARAMETER_SIZE {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
for key_handle in &args.key_handles {
if key_handle.credential.len() >= 256 {
return Err(AuthenticatorError::InvalidRelyingPartyInput);
}
let rp = RelyingPartyWrapper::Hash(RpIdHash::from(&app_id)?);
let allow_list = vec![key_handle.into()];
let get_assertion = GetAssertion::new(
client_data.clone(),
rp,
allow_list,
options,
Default::default(),
None,
)?;
let action = QueueAction::SignCtap2 {
timeout,
get_assertion,
status: status.clone(),
callback: callback.clone(),
};
self.tx.send(action)?;
}
}
}
SignArgs::CTAP2(args) => {
let client_data = CollectedClientData {
webauthn_type: WebauthnType::Get,
challenge: args.challenge.into(),
origin: args.origin,
cross_origin: false,
token_binding: None,
};
let get_assertion = GetAssertion::new(
client_data.clone(),
RelyingPartyWrapper::Data(RelyingParty {
id: args.relying_party_id,
name: None,
icon: None,
}),
args.allow_list,
args.options,
args.extensions,
args.pin,
)?;
let action = QueueAction::SignCtap2 {
timeout,
get_assertion,
status,
callback,
};
self.tx.send(action)?;
}
};
Ok(())
}
fn cancel(&mut self) -> Result<(), AuthenticatorError> {
Ok(self.tx.send(QueueAction::Cancel)?)
}
fn reset(
&mut self,
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> Result<(), AuthenticatorError> {
Ok(self.tx.send(QueueAction::Reset {
timeout,
status,
callback,
})?)
}
fn set_pin(
&mut self,
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) -> crate::Result<()> {
Ok(self.tx.send(QueueAction::SetPin {
timeout,
new_pin,
status,
callback,
})?)
}
}

Просмотреть файл

@ -3,15 +3,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::consts::PARAMETER_SIZE;
use crate::errors;
use crate::platform::device::Device;
use crate::platform::transaction::Transaction;
use crate::ctap2::commands::client_pin::{ChangeExistingPin, Pin, PinError, SetNewPin};
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult};
use crate::ctap2::commands::make_credentials::{MakeCredentials, MakeCredentialsResult};
use crate::ctap2::commands::reset::Reset;
use crate::ctap2::commands::{
repackage_pin_errors, CommandError, PinAuthCommand, Request, StatusCode,
};
use crate::errors::{self, AuthenticatorError, UnsupportedOption};
use crate::statecallback::StateCallback;
use crate::transport::device_selector::{
BlinkResult, Device, DeviceBuildParameters, DeviceCommand, DeviceSelectorEvent,
};
use crate::transport::platform::transaction::Transaction;
use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, Nonce};
use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
use crate::u2ftypes::U2FDevice;
use std::sync::mpsc::Sender;
use std::sync::Mutex;
use crate::{send_status, RegisterResult, SignResult, StatusUpdate};
use std::sync::mpsc::{channel, Sender};
use std::thread;
use std::time::Duration;
@ -44,18 +53,6 @@ where
(&app_ids[0], vec![])
}
fn send_status(status_mutex: &Mutex<Sender<crate::StatusUpdate>>, msg: crate::StatusUpdate) {
match status_mutex.lock() {
Ok(s) => match s.send(msg) {
Ok(_) => {}
Err(e) => error!("Couldn't send status: {:?}", e),
},
Err(e) => {
error!("Couldn't obtain status mutex: {:?}", e);
}
};
}
#[derive(Default)]
pub struct StateMachine {
transaction: Option<Transaction>,
@ -80,79 +77,88 @@ impl StateMachine {
self.cancel();
let cbc = callback.clone();
let status_mutex = Mutex::new(status);
let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
let transaction = Transaction::new(
timeout,
cbc.clone(),
status,
move |info, _, status, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// We currently support none of the authenticator selection
// criteria because we can't ask tokens whether they do support
// those features. If flags are set, ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
// Iterate the exclude list and see if there are any matches.
// If so, we'll keep polling the device anyway to test for user
// consent, to be consistent with CTAP2 device behavior.
let excluded = key_handles.iter().any(|key_handle| {
is_valid_transport(key_handle.transports)
&& u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
});
while alive() {
if excluded {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::InvalidState,
)));
break;
}
} else if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
let dev_info = dev.get_device_info();
send_status(
&status_mutex,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok((bytes, dev_info)));
break;
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
// We currently support none of the authenticator selection
// criteria because we can't ask tokens whether they do support
// those features. If flags are set, ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
});
send_status(
&status,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
// Iterate the exclude list and see if there are any matches.
// If so, we'll keep polling the device anyway to test for user
// consent, to be consistent with CTAP2 device behavior.
let excluded = key_handles.iter().any(|key_handle| {
is_valid_transport(key_handle.transports)
&& u2f_is_keyhandle_valid(
dev,
&challenge,
&application,
&key_handle.credential,
)
.unwrap_or(false) /* no match on failure */
});
while alive() {
if excluded {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::InvalidState,
)));
break;
}
} else if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
let dev_info = dev.get_device_info();
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok(RegisterResult::CTAP1(bytes, dev_info)));
break;
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
send_status(
&status,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
},
);
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
@ -172,104 +178,108 @@ impl StateMachine {
let cbc = callback.clone();
let status_mutex = Mutex::new(status);
let transaction = Transaction::new(
timeout,
cbc.clone(),
status,
move |info, _, status, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// We currently don't support user verification because we can't
// ask tokens whether they do support that. If the flag is set,
// ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
// For each appId, try all key handles. If there's at least one
// valid key handle for an appId, we'll use that appId below.
let (app_id, valid_handles) =
find_valid_key_handles(&app_ids, &key_handles, |app_id, key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, app_id, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
});
// Aggregate distinct transports from all given credentials.
let transports = key_handles
.iter()
.fold(crate::AuthenticatorTransports::empty(), |t, k| {
t | k.transports
});
// We currently only support USB. If the RP specifies transports
// and doesn't include USB it's probably lying.
if !is_valid_transport(transports) {
return;
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
'outer: while alive() {
// If the device matches none of the given key handles
// then just make it blink with bogus data.
if valid_handles.is_empty() {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::InvalidState,
)));
break;
}
} else {
// Otherwise, try to sign.
for key_handle in &valid_handles {
if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential)
{
let dev_info = dev.get_device_info();
send_status(
&status_mutex,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok((
app_id.clone(),
key_handle.credential.clone(),
bytes,
dev_info,
)));
break 'outer;
}
}
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
// We currently don't support user verification because we can't
// ask tokens whether they do support that. If the flag is set,
// ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
});
// For each appId, try all key handles. If there's at least one
// valid key handle for an appId, we'll use that appId below.
let (app_id, valid_handles) =
find_valid_key_handles(&app_ids, &key_handles, |app_id, key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, app_id, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
});
// Aggregate distinct transports from all given credentials.
let transports = key_handles
.iter()
.fold(crate::AuthenticatorTransports::empty(), |t, k| {
t | k.transports
});
// We currently only support USB. If the RP specifies transports
// and doesn't include USB it's probably lying.
if !is_valid_transport(transports) {
return;
}
send_status(
&status,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
'outer: while alive() {
// If the device matches none of the given key handles
// then just make it blink with bogus data.
if valid_handles.is_empty() {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::InvalidState,
)));
break;
}
} else {
// Otherwise, try to sign.
for key_handle in &valid_handles {
if let Ok(bytes) =
u2f_sign(dev, &challenge, app_id, &key_handle.credential)
{
let dev_info = dev.get_device_info();
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok(SignResult::CTAP1(
app_id.clone(),
key_handle.credential.clone(),
bytes,
dev_info,
)));
break 'outer;
}
}
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
send_status(
&status,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
},
);
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
@ -281,3 +291,539 @@ impl StateMachine {
}
}
}
#[derive(Default)]
// TODO(MS): To be renamed to `StateMachine` once U2FManager and the original StateMachine can be removed.
pub struct StateMachineCtap2 {
transaction: Option<Transaction>,
}
impl StateMachineCtap2 {
pub fn new() -> Self {
Default::default()
}
fn init_and_select(
info: DeviceBuildParameters,
selector: &Sender<DeviceSelectorEvent>,
ctap2_only: bool,
) -> Option<Device> {
// Create a new device.
let mut dev = match Device::new(info) {
Ok(dev) => dev,
Err((e, id)) => {
info!("error happened with device: {}", e);
selector.send(DeviceSelectorEvent::NotAToken(id)).ok()?;
return None;
}
};
// Try initializing it.
if let Err(e) = dev.init(Nonce::CreateRandom) {
warn!("error while initializing device: {}", e);
selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok();
return None;
}
if ctap2_only && dev.get_authenticator_info().is_none() {
info!("Device does not support CTAP2");
selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok();
return None;
}
let write_only_clone = match dev.clone_device_as_write_only() {
Ok(x) => x,
Err(_) => {
// There is probably something seriously wrong here, if this happens.
// So `NotAToken()` is probably too weak a response here.
warn!("error while cloning device: {:?}", dev.id());
selector
.send(DeviceSelectorEvent::NotAToken(dev.id()))
.ok()?;
return None;
}
};
let (tx, rx) = channel();
selector
.send(DeviceSelectorEvent::ImAToken((write_only_clone, tx)))
.ok()?;
// Blocking recv. DeviceSelector will tell us what to do
loop {
match rx.recv() {
Ok(DeviceCommand::Blink) => match dev.block_and_blink() {
BlinkResult::DeviceSelected => {
// User selected us. Let DeviceSelector know, so it can cancel all other
// outstanding open blink-requests.
selector
.send(DeviceSelectorEvent::SelectedToken(dev.id()))
.ok()?;
break;
}
BlinkResult::Cancelled => {
info!("Device {:?} was not selected", dev.id());
return None;
}
},
Ok(DeviceCommand::Removed) => {
info!("Device {:?} was removed", dev.id());
return None;
}
Ok(DeviceCommand::Continue) => {
break;
}
Err(_) => {
warn!("Error when trying to receive messages from DeviceSelector! Exiting.");
return None;
}
}
}
Some(dev)
}
fn ask_user_for_pin<U>(
error: PinError,
status: &Sender<StatusUpdate>,
callback: &StateCallback<crate::Result<U>>,
) -> Result<Pin, ()> {
info!("PIN Error that requires user interaction detected. Sending it back and waiting for a reply");
let (tx, rx) = channel();
send_status(status, crate::StatusUpdate::PinError(error.clone(), tx));
match rx.recv() {
Ok(pin) => Ok(pin),
Err(_) => {
// recv() can only fail, if the other side is dropping the Sender. We are using this as a trick
// to let the callback decide if this PinError is recoverable (e.g. with User input) or not (e.g.
// locked token). If it is deemed unrecoverable, we error out the 'normal' way with the same error.
error!("Callback dropped the channel, so we forward the error to the results-callback: {:?}", error);
callback.call(Err(AuthenticatorError::PinError(error)));
return Err(());
}
}
}
fn determine_pin_auth<T: PinAuthCommand, U>(
cmd: &mut T,
dev: &mut Device,
status: &Sender<StatusUpdate>,
callback: &StateCallback<crate::Result<U>>,
) -> Result<(), ()> {
loop {
match cmd.determine_pin_auth(dev) {
Ok(_) => {
break;
}
Err(AuthenticatorError::PinError(e)) => {
let pin = Self::ask_user_for_pin(e, status, callback)?;
cmd.set_pin(Some(pin));
continue;
}
Err(e) => {
error!("Error when determining pinAuth: {:?}", e);
callback.call(Err(e));
return Err(());
}
};
}
// CTAP 2.0 spec is a bit vague here, but CTAP 2.1 is very specific, that the request
// should either include pinAuth OR uv=true, but not both at the same time.
// Do not set user_verification, if pinAuth is provided
if cmd.pin_auth().is_some() {
cmd.unset_uv_option();
}
Ok(())
}
pub fn register(
&mut self,
timeout: u64,
params: MakeCredentials,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
cbc.clone(),
status,
move |info, selector, status, _alive| {
let mut dev = match Self::init_and_select(info, &selector, false) {
None => {
return;
}
Some(dev) => dev,
};
info!("Device {:?} continues with the register process", dev.id());
// TODO(baloo): not sure about this, have to ask
// We currently support none of the authenticator selection
// criteria because we can't ask tokens whether they do support
// those features. If flags are set, ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
//if !flags.is_empty() {
// return;
//}
// TODO(baloo): not sure about this, have to ask
// Iterate the exclude list and see if there are any matches.
// If so, we'll keep polling the device anyway to test for user
// consent, to be consistent with CTAP2 device behavior.
//let excluded = key_handles.iter().any(|key_handle| {
// is_valid_transport(key_handle.transports)
// && u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
// .unwrap_or(false) /* no match on failure */
//});
// TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me
// to modify "params" directly.
let mut makecred = params.clone();
if params.is_ctap2_request() {
// First check if extensions have been requested that are not supported by the device
if let Some(true) = params.extensions.hmac_secret {
if let Some(auth) = dev.get_authenticator_info() {
if !auth.supports_hmac_secret() {
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::HmacSecret,
)));
return;
}
}
}
// Second, ask for PIN and get the shared secret
if Self::determine_pin_auth(&mut makecred, &mut dev, &status, &callback)
.is_err()
{
return;
}
}
debug!("------------------------------------------------------------------");
debug!("{:?}", makecred);
debug!("------------------------------------------------------------------");
let resp = dev.send_msg(&makecred);
if resp.is_ok() {
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
// The DeviceSelector could already be dead, but it might also wait
// for us to respond, in order to cancel all other tokens in case
// we skipped the "blinking"-action and went straight for the actual
// request.
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
}
match resp {
Ok(MakeCredentialsResult::CTAP2(attestation, client_data)) => {
callback.call(Ok(RegisterResult::CTAP2(attestation, client_data)))
}
Ok(MakeCredentialsResult::CTAP1(data)) => {
callback.call(Ok(RegisterResult::CTAP1(data, dev.get_device_info())))
}
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
Err(HIDError::Command(CommandError::StatusCode(
StatusCode::ChannelBusy,
_,
))) => {}
Err(e) => {
warn!("error happened: {}", e);
callback.call(Err(AuthenticatorError::HIDError(e)));
}
}
},
);
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
pub fn sign(
&mut self,
timeout: u64,
params: GetAssertion,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
callback.clone(),
status,
move |info, selector, status, _alive| {
let mut dev = match Self::init_and_select(info, &selector, false) {
None => {
return;
}
Some(dev) => dev,
};
info!("Device {:?} continues with the signing process", dev.id());
// TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me
// to modify "params" directly.
let mut getassertion = params.clone();
if params.is_ctap2_request() {
// First check if extensions have been requested that are not supported by the device
if params.extensions.hmac_secret.is_some() {
if let Some(auth) = dev.get_authenticator_info() {
if !auth.supports_hmac_secret() {
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::HmacSecret,
)));
return;
}
}
}
// Second, ask for PIN and get the shared secret
if Self::determine_pin_auth(&mut getassertion, &mut dev, &status, &callback)
.is_err()
{
return;
}
// Third, use the shared secret in the extensions, if requested
if let Some(extension) = getassertion.extensions.hmac_secret.as_mut() {
if let Some(secret) = dev.get_shared_secret() {
match extension.calculate(secret) {
Ok(x) => x,
Err(e) => {
callback.call(Err(e));
return;
}
}
}
}
}
debug!("------------------------------------------------------------------");
debug!("{:?}", getassertion);
debug!("------------------------------------------------------------------");
let resp = dev.send_msg(&getassertion);
if resp.is_ok() {
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
// The DeviceSelector could already be dead, but it might also wait
// for us to respond, in order to cancel all other tokens in case
// we skipped the "blinking"-action and went straight for the actual
// request.
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
}
match resp {
Ok(GetAssertionResult::CTAP1(resp)) => {
let app_id = getassertion.rp.hash().as_ref().to_vec();
let key_handle = getassertion.allow_list[0].id.clone();
callback.call(Ok(SignResult::CTAP1(
app_id,
key_handle,
resp,
dev.get_device_info(),
)))
}
Ok(GetAssertionResult::CTAP2(assertion, client_data)) => {
callback.call(Ok(SignResult::CTAP2(assertion, client_data)))
}
// TODO(baloo): if key_handle is invalid for this device, it
// should reply something like:
// CTAP2_ERR_INVALID_CREDENTIAL
// have to check
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
Err(HIDError::Command(CommandError::StatusCode(
StatusCode::ChannelBusy,
_,
))) => {}
Err(e) => {
warn!("error happened: {}", e);
callback.call(Err(AuthenticatorError::HIDError(e)));
}
}
},
);
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
}
// This blocks.
pub fn cancel(&mut self) {
if let Some(mut transaction) = self.transaction.take() {
info!("Statemachine was cancelled. Cancelling transaction now.");
transaction.cancel();
}
}
pub fn reset(
&mut self,
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
callback.clone(),
status,
move |info, selector, status, _alive| {
let reset = Reset {};
let mut dev = match Self::init_and_select(info, &selector, true) {
None => {
return;
}
Some(dev) => dev,
};
info!("Device {:?} continues with the reset process", dev.id());
debug!("------------------------------------------------------------------");
debug!("{:?}", reset);
debug!("------------------------------------------------------------------");
let resp = dev.send_cbor(&reset);
if resp.is_ok() {
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
// The DeviceSelector could already be dead, but it might also wait
// for us to respond, in order to cancel all other tokens in case
// we skipped the "blinking"-action and went straight for the actual
// request.
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
}
match resp {
Ok(()) => callback.call(Ok(())),
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
Err(HIDError::Command(CommandError::StatusCode(
StatusCode::ChannelBusy,
_,
))) => {}
Err(e) => {
warn!("error happened: {}", e);
callback.call(Err(AuthenticatorError::HIDError(e)));
}
}
},
);
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
}
pub fn set_pin(
&mut self,
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
callback.clone(),
status,
move |info, selector, status, _alive| {
let mut dev = match Self::init_and_select(info, &selector, true) {
None => {
return;
}
Some(dev) => dev,
};
let (mut shared_secret, authinfo) = match dev.establish_shared_secret() {
Ok(s) => s,
Err(e) => {
callback.call(Err(AuthenticatorError::HIDError(e)));
return;
}
};
// With CTAP2.1 we will have an adjustable required length for PINs
if new_pin.as_bytes().len() < 4 {
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
return;
}
if new_pin.as_bytes().len() > 64 {
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong(
new_pin.as_bytes().len(),
))));
return;
}
// Check if a client-pin is already set, or if a new one should be created
let res = if authinfo.options.client_pin.unwrap_or_default() {
let mut res;
let mut error = PinError::PinRequired;
loop {
let current_pin = match Self::ask_user_for_pin(error, &status, &callback) {
Ok(pin) => pin,
_ => {
return;
}
};
res = ChangeExistingPin::new(
&authinfo,
&shared_secret,
&current_pin,
&new_pin,
)
.map_err(HIDError::Command)
.and_then(|msg| dev.send_cbor(&msg))
.map_err(AuthenticatorError::HIDError)
.map_err(|e| repackage_pin_errors(&mut dev, e));
if let Err(AuthenticatorError::PinError(e)) = res {
error = e;
// We need to re-establish the shared secret for the next round.
match dev.establish_shared_secret() {
Ok((s, _)) => {
shared_secret = s;
}
Err(e) => {
callback.call(Err(AuthenticatorError::HIDError(e)));
return;
}
};
continue;
} else {
break;
}
}
res
} else {
SetNewPin::new(&authinfo, &shared_secret, &new_pin)
.map_err(HIDError::Command)
.and_then(|msg| dev.send_cbor(&msg))
.map_err(AuthenticatorError::HIDError)
};
callback.call(res);
},
);
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
}
}

101
third_party/rust/authenticator/src/status_update.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,101 @@
use super::{u2ftypes, Pin, PinError};
use serde::ser::{Serialize, SerializeStruct};
use std::sync::mpsc::Sender;
#[derive(Debug)]
pub enum StatusUpdate {
/// Device found
DeviceAvailable { dev_info: u2ftypes::U2FDeviceInfo },
/// Device got removed
DeviceUnavailable { dev_info: u2ftypes::U2FDeviceInfo },
/// We successfully finished the register or sign request
Success { dev_info: u2ftypes::U2FDeviceInfo },
/// Sent if a PIN is needed (or was wrong), or some other kind of PIN-related
/// error occurred. The Sender is for sending back a PIN (if needed).
PinError(PinError, Sender<Pin>),
/// Sent, if multiple devices are found and the user has to select one
SelectDeviceNotice,
/// Sent, once a device was selected (either automatically or by user-interaction)
/// and the register or signing process continues with this device
DeviceSelected(u2ftypes::U2FDeviceInfo),
}
impl Serialize for StatusUpdate {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_struct("StatusUpdate", 1)?;
match &*self {
StatusUpdate::DeviceAvailable { dev_info } => {
map.serialize_field("DeviceAvailable", &dev_info)?
}
StatusUpdate::DeviceUnavailable { dev_info } => {
map.serialize_field("DeviceUnavailable", &dev_info)?
}
StatusUpdate::Success { dev_info } => map.serialize_field("Success", &dev_info)?,
StatusUpdate::PinError(e, _) => map.serialize_field("PinError", &e)?,
StatusUpdate::SelectDeviceNotice => map.serialize_field("SelectDeviceNotice", &())?,
StatusUpdate::DeviceSelected(dev_info) => {
map.serialize_field("DeviceSelected", &dev_info)?
}
}
map.end()
}
}
pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {
match status.send(msg) {
Ok(_) => {}
Err(e) => error!("Couldn't send status: {:?}", e),
};
}
#[cfg(test)]
pub mod tests {
use crate::consts::U2F_AUTHENTICATE;
use super::*;
use crate::consts::Capability;
use serde_json::to_string;
use std::sync::mpsc::channel;
#[test]
fn serialize_select() {
let st = StatusUpdate::SelectDeviceNotice;
let json = to_string(&st).expect("Failed to serialize");
assert_eq!(&json, r#"{"SelectDeviceNotice":null}"#);
}
#[test]
fn serialize_invalid_pin() {
let (tx, _rx) = channel();
let st = StatusUpdate::PinError(PinError::InvalidPin(Some(3)), tx.clone());
let json = to_string(&st).expect("Failed to serialize");
assert_eq!(&json, r#"{"PinError":{"InvalidPin":3}}"#);
let st = StatusUpdate::PinError(PinError::InvalidPin(None), tx);
let json = to_string(&st).expect("Failed to serialize");
assert_eq!(&json, r#"{"PinError":{"InvalidPin":null}}"#);
}
#[test]
fn serialize_success() {
let cap = Capability::WINK | Capability::CBOR;
let dev = u2ftypes::U2FDeviceInfo {
vendor_name: String::from("ABC").into_bytes(),
device_name: String::from("DEF").into_bytes(),
version_interface: 2,
version_major: 5,
version_minor: 4,
version_build: 3,
cap_flags: cap,
};
let st = StatusUpdate::Success { dev_info: dev };
let json = to_string(&st).expect("Failed to serialize");
assert_eq!(
&json,
r#"{"Success":{"vendor_name":[65,66,67],"device_name":[68,69,70],"version_interface":2,"version_major":5,"version_minor":4,"version_build":3,"cap_flags":{"bits":5}}}"#
);
}
}

Просмотреть файл

@ -1,31 +0,0 @@
/* 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 crate::errors;
use crate::statecallback::StateCallback;
pub struct Transaction {}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: StateCallback<crate::Result<T>>,
new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn(String, &dyn Fn() -> bool),
{
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::NotSupported,
)));
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::NotSupported,
))
}
pub fn cancel(&mut self) {
/* No-op. */
}
}

514
third_party/rust/authenticator/src/transport/device_selector.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,514 @@
use crate::send_status;
use crate::transport::hid::HIDDevice;
pub use crate::transport::platform::device::Device;
use crate::u2ftypes::U2FDevice;
use runloop::RunLoop;
use std::collections::{HashMap, HashSet};
use std::sync::mpsc::{channel, RecvTimeoutError, Sender};
use std::time::Duration;
pub type DeviceID = <Device as HIDDevice>::Id;
pub type DeviceBuildParameters = <Device as HIDDevice>::BuildParameters;
trait DeviceSelectorEventMarker {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlinkResult {
DeviceSelected,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DeviceCommand {
Blink,
Continue,
Removed,
}
#[derive(Debug)]
pub enum DeviceSelectorEvent {
Cancel,
Timeout,
DevicesAdded(Vec<DeviceID>),
DeviceRemoved(DeviceID),
NotAToken(DeviceID),
ImAToken((Device, Sender<DeviceCommand>)),
SelectedToken(DeviceID),
}
pub struct DeviceSelector {
/// How to send a message to the event loop
sender: Sender<DeviceSelectorEvent>,
/// Thread of the event loop
runloop: RunLoop,
}
impl DeviceSelector {
pub fn run(status: Sender<crate::StatusUpdate>) -> Self {
let (selector_send, selector_rec) = channel();
// let new_device_callback = Arc::new(new_device_cb);
let runloop = RunLoop::new(move |alive| {
let mut blinking = false;
// Device was added, but we wait for its response, if it is a token or not
// We save both a write-only copy of the device (for cancellation) and it's thread
let mut waiting_for_response = HashSet::new();
// All devices that responded with "ImAToken"
let mut tokens = HashMap::new();
while alive() {
let d = Duration::from_secs(100);
let res = match selector_rec.recv_timeout(d) {
Err(RecvTimeoutError::Disconnected) => {
break;
}
Err(RecvTimeoutError::Timeout) => DeviceSelectorEvent::Timeout,
Ok(res) => res,
};
match res {
DeviceSelectorEvent::Timeout | DeviceSelectorEvent::Cancel => {
/* TODO */
Self::cancel_all(tokens, None);
break;
}
DeviceSelectorEvent::SelectedToken(id) => {
if let Some(dev) = tokens.keys().find(|d| d.id() == id) {
send_status(
&status,
crate::StatusUpdate::DeviceSelected(dev.get_device_info()),
);
}
Self::cancel_all(tokens, Some(&id));
break; // We are done here. The selected device continues without us.
}
DeviceSelectorEvent::DevicesAdded(ids) => {
for id in ids {
debug!("Device added event: {:?}", id);
waiting_for_response.insert(id);
}
continue;
}
DeviceSelectorEvent::DeviceRemoved(id) => {
debug!("Device removed event: {:?}", id);
if !waiting_for_response.remove(&id) {
// Note: We _could_ check here if we had multiple tokens and are already blinking
// and the removal of this one leads to only one token left. So we could in theory
// stop blinking and select it right away. At the moment, I think this is a
// too surprising behavior and therefore, we let the remaining device keep on blinking
// since the user could add yet another device, instead of using the remaining one.
tokens.iter().for_each(|(dev, send)| {
if dev.id() == id {
let _ = send.send(DeviceCommand::Removed);
send_status(
&status,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
}
});
tokens.retain(|dev, _| dev.id() != id);
if tokens.is_empty() {
blinking = false;
continue;
}
}
// We are already blinking, so no need to run the code below this match
// that figures out if we should blink or not. In fact, currently, we do
// NOT want to run this code again, because if you have 2 blinking tokens
// and one got removed, we WANT the remaining one to continue blinking.
// This is a design choice, because I currently think it is the "less surprising"
// option to the user.
if blinking {
continue;
}
}
DeviceSelectorEvent::NotAToken(id) => {
debug!("Device not a token event: {:?}", id);
waiting_for_response.remove(&id);
}
DeviceSelectorEvent::ImAToken((dev, tx)) => {
send_status(
&status,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
let id = dev.id();
let _ = waiting_for_response.remove(&id);
tokens.insert(dev, tx.clone());
if blinking {
// We are already blinking, so this new device should blink too.
if tx.send(DeviceCommand::Blink).is_err() {
// Device thread died in the meantime (which shouldn't happen)
tokens.retain(|dev, _| dev.id() != id);
}
continue;
}
}
}
// All known devices told us, whether they are tokens or not and we have at least one token
if waiting_for_response.is_empty() && !tokens.is_empty() {
if tokens.len() == 1 {
let (dev, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty
if tx.send(DeviceCommand::Continue).is_err() {
// Device thread died in the meantime (which shouldn't happen).
// Tokens is empty, so we just start over again
continue;
}
send_status(
&status,
crate::StatusUpdate::DeviceSelected(dev.get_device_info()),
);
Self::cancel_all(tokens, Some(&dev.id()));
break; // We are done here
} else {
blinking = true;
tokens.iter().for_each(|(_dev, tx)| {
// A send operation can only fail if the receiving end of a channel is disconnected, implying that the data could never be received.
// We ignore errors here for now, but should probably remove the device in such a case (even though it theoretically can't happen)
let _ = tx.send(DeviceCommand::Blink);
});
send_status(&status, crate::StatusUpdate::SelectDeviceNotice);
}
}
}
});
Self {
runloop: runloop.unwrap(), // TODO
sender: selector_send,
}
}
pub fn clone_sender(&self) -> Sender<DeviceSelectorEvent> {
self.sender.clone()
}
fn cancel_all(tokens: HashMap<Device, Sender<DeviceCommand>>, exclude: Option<&DeviceID>) {
tokens
.into_keys()
.filter(|x| exclude.map_or(true, |y| y != &x.id()))
.for_each(|mut dev| dev.cancel().unwrap()); // TODO
}
pub fn stop(&mut self) {
// We ignore a possible error here, since we don't really care
let _ = self.sender.send(DeviceSelectorEvent::Cancel);
self.runloop.cancel();
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::{
consts::Capability,
ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorOptions},
u2ftypes::U2FDeviceInfo,
StatusUpdate,
};
use std::sync::mpsc::Receiver;
enum ExpectedUpdate {
DeviceAvailable,
DeviceUnavailable,
SelectDeviceNotice,
DeviceSelected,
}
fn gen_info(id: String) -> U2FDeviceInfo {
U2FDeviceInfo {
vendor_name: String::from("ExampleVendor").into_bytes(),
device_name: id.into_bytes(),
version_interface: 1,
version_major: 3,
version_minor: 2,
version_build: 1,
cap_flags: Capability::WINK | Capability::CBOR | Capability::NMSG,
}
}
fn make_device_simple_u2f(dev: &mut Device) {
dev.set_device_info(gen_info(dev.id()));
dev.create_channel();
}
fn make_device_with_pin(dev: &mut Device) {
dev.set_device_info(gen_info(dev.id()));
dev.create_channel();
let info = AuthenticatorInfo {
options: AuthenticatorOptions {
client_pin: Some(true),
..Default::default()
},
..Default::default()
};
dev.set_authenticator_info(info.clone());
}
fn send_i_am_token(dev: &Device, selector: &DeviceSelector) {
selector
.sender
.send(DeviceSelectorEvent::ImAToken((
dev.clone_device_as_write_only().unwrap(),
dev.sender.clone().unwrap(),
)))
.unwrap();
}
fn send_no_token(dev: &Device, selector: &DeviceSelector) {
selector
.sender
.send(DeviceSelectorEvent::NotAToken(dev.id()))
.unwrap()
}
fn remove_device(dev: &Device, selector: &DeviceSelector) {
selector
.sender
.send(DeviceSelectorEvent::DeviceRemoved(dev.id()))
.unwrap();
assert_eq!(
dev.receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Removed
);
}
fn recv_status(dev: &Device, status_rx: &Receiver<StatusUpdate>, expected: ExpectedUpdate) {
let res = status_rx.recv().unwrap();
// Marking it with _, as cargo warns about "unused exp", as it doesn't view the assert below as a usage
let _exp = match expected {
ExpectedUpdate::DeviceAvailable => StatusUpdate::DeviceUnavailable {
dev_info: gen_info(dev.id()),
},
ExpectedUpdate::DeviceUnavailable => StatusUpdate::DeviceUnavailable {
dev_info: gen_info(dev.id()),
},
ExpectedUpdate::DeviceSelected => StatusUpdate::DeviceSelected(gen_info(dev.id())),
ExpectedUpdate::SelectDeviceNotice => StatusUpdate::SelectDeviceNotice,
};
assert!(matches!(res, _exp));
}
fn add_devices<'a, T>(iter: T, selector: &DeviceSelector)
where
T: Iterator<Item = &'a Device>,
{
selector
.sender
.send(DeviceSelectorEvent::DevicesAdded(
iter.map(|f| f.id()).collect(),
))
.unwrap();
}
#[test]
fn test_device_selector_one_token_no_late_adds() {
let mut devices = vec![
Device::new("device selector 1").unwrap(),
Device::new("device selector 2").unwrap(),
Device::new("device selector 3").unwrap(),
Device::new("device selector 4").unwrap(),
];
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
let (status_tx, status_rx) = channel();
let selector = DeviceSelector::run(status_tx);
// Adding all
add_devices(devices.iter(), &selector);
devices.iter().filter(|d| !d.is_u2f()).for_each(|d| {
send_no_token(d, &selector);
});
send_i_am_token(&devices[2], &selector);
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable);
assert_eq!(
devices[2].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Continue
);
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceSelected);
}
#[test]
fn test_device_selector_all_pins_with_late_add() {
let mut devices = vec![
Device::new("device selector 1").unwrap(),
Device::new("device selector 2").unwrap(),
Device::new("device selector 3").unwrap(),
Device::new("device selector 4").unwrap(),
Device::new("device selector 5").unwrap(),
Device::new("device selector 6").unwrap(),
];
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
make_device_with_pin(&mut devices[4]);
make_device_with_pin(&mut devices[5]);
let (status_tx, status_rx) = channel();
let selector = DeviceSelector::run(status_tx);
// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter().take(5), &selector);
// Interleave tokens and non-tokens
send_i_am_token(&devices[2], &selector);
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable);
devices.iter().filter(|d| !d.is_u2f()).for_each(|d| {
send_no_token(d, &selector);
});
send_i_am_token(&devices[4], &selector);
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable);
// We added 2 devices that are tokens. They should get the blink-command now
assert_eq!(
devices[2].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_eq!(
devices[4].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice);
// Plug in late device
send_i_am_token(&devices[5], &selector);
recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable);
assert_eq!(
devices[5].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
}
#[test]
fn test_device_selector_no_pins_late_mixed_adds() {
// Multiple tokes, none of them support a PIN
let mut devices = vec![
Device::new("device selector 1").unwrap(),
Device::new("device selector 2").unwrap(),
Device::new("device selector 3").unwrap(),
Device::new("device selector 4").unwrap(),
Device::new("device selector 5").unwrap(),
Device::new("device selector 6").unwrap(),
Device::new("device selector 7").unwrap(),
];
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_simple_u2f(&mut devices[2]);
make_device_simple_u2f(&mut devices[4]);
make_device_simple_u2f(&mut devices[5]);
let (status_tx, status_rx) = channel();
let selector = DeviceSelector::run(status_tx);
// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter().take(5), &selector);
// Interleave tokens and non-tokens
send_i_am_token(&devices[2], &selector);
recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable);
devices.iter().filter(|d| !d.is_u2f()).for_each(|d| {
send_no_token(d, &selector);
});
send_i_am_token(&devices[4], &selector);
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable);
// We added 2 devices that are tokens. They should get the blink-command now
assert_eq!(
devices[2].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_eq!(
devices[4].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice);
// Plug in late device
send_i_am_token(&devices[5], &selector);
recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable);
assert_eq!(
devices[5].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
// Remove device again
remove_device(&devices[5], &selector);
recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceUnavailable);
// Now we add a token that has a PIN, it should not get "Continue" but "Blink"
make_device_with_pin(&mut devices[6]);
send_i_am_token(&devices[6], &selector);
recv_status(&devices[6], &status_rx, ExpectedUpdate::DeviceAvailable);
assert_eq!(
devices[6].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
}
#[test]
fn test_device_selector_mixed_pins_remove_all() {
// Multiple tokes, none of them support a PIN, so we should get Continue-commands
// for all of them
let mut devices = vec![
Device::new("device selector 1").unwrap(),
Device::new("device selector 2").unwrap(),
Device::new("device selector 3").unwrap(),
Device::new("device selector 4").unwrap(),
Device::new("device selector 5").unwrap(),
Device::new("device selector 6").unwrap(),
];
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
make_device_with_pin(&mut devices[4]);
make_device_with_pin(&mut devices[5]);
let (status_tx, status_rx) = channel();
let selector = DeviceSelector::run(status_tx);
// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter(), &selector);
devices.iter().for_each(|d| {
if d.is_u2f() {
send_i_am_token(d, &selector);
} else {
send_no_token(d, &selector);
}
});
for idx in [2, 4, 5] {
recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceAvailable);
assert_eq!(
devices[idx].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
}
recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice);
// Remove all tokens
for idx in [2, 4, 5] {
remove_device(&devices[idx], &selector);
recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceUnavailable);
}
// Adding one again
send_i_am_token(&devices[4], &selector);
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable);
// This should now get a "Continue" instead of "Blinking", because it's the only device
assert_eq!(
devices[4].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Continue
);
recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceSelected);
}
}

98
third_party/rust/authenticator/src/transport/errors.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,98 @@
use crate::consts::{SW_CONDITIONS_NOT_SATISFIED, SW_NO_ERROR, SW_WRONG_DATA, SW_WRONG_LENGTH};
use crate::ctap2::commands::CommandError;
use std::fmt;
use std::io;
use std::path;
#[allow(unused)]
#[derive(Debug, PartialEq, Eq)]
pub enum ApduErrorStatus {
ConditionsNotSatisfied,
WrongData,
WrongLength,
Unknown([u8; 2]),
}
impl ApduErrorStatus {
pub fn from(status: [u8; 2]) -> Result<(), ApduErrorStatus> {
match status {
s if s == SW_NO_ERROR => Ok(()),
s if s == SW_CONDITIONS_NOT_SATISFIED => Err(ApduErrorStatus::ConditionsNotSatisfied),
s if s == SW_WRONG_DATA => Err(ApduErrorStatus::WrongData),
s if s == SW_WRONG_LENGTH => Err(ApduErrorStatus::WrongLength),
other => Err(ApduErrorStatus::Unknown(other)),
}
}
}
impl fmt::Display for ApduErrorStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ApduErrorStatus::ConditionsNotSatisfied => write!(f, "Apdu: condition not satisfied"),
ApduErrorStatus::WrongData => write!(f, "Apdu: wrong data"),
ApduErrorStatus::WrongLength => write!(f, "Apdu: wrong length"),
ApduErrorStatus::Unknown(ref u) => write!(f, "Apdu: unknown error: {:?}", u),
}
}
}
#[allow(unused)]
#[derive(Debug)]
pub enum HIDError {
/// Transport replied with a status not expected
DeviceError,
UnexpectedInitReplyLen,
NonceMismatch,
DeviceNotInitialized,
DeviceNotSupported,
UnsupportedCommand,
UnexpectedVersion,
IO(Option<path::PathBuf>, io::Error),
UnexpectedCmd(u8),
Command(CommandError),
ApduStatus(ApduErrorStatus),
}
impl From<io::Error> for HIDError {
fn from(e: io::Error) -> HIDError {
HIDError::IO(None, e)
}
}
impl From<CommandError> for HIDError {
fn from(e: CommandError) -> HIDError {
HIDError::Command(e)
}
}
impl From<ApduErrorStatus> for HIDError {
fn from(e: ApduErrorStatus) -> HIDError {
HIDError::ApduStatus(e)
}
}
impl fmt::Display for HIDError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HIDError::UnexpectedInitReplyLen => {
write!(f, "Error: Unexpected reply len when initilizaling")
}
HIDError::NonceMismatch => write!(f, "Error: Nonce mismatch"),
HIDError::DeviceError => write!(f, "Error: device returned error"),
HIDError::DeviceNotInitialized => write!(f, "Error: using not initiliazed device"),
HIDError::DeviceNotSupported => {
write!(f, "Error: requested operation is not available on device")
}
HIDError::UnexpectedVersion => write!(f, "Error: Unexpected protocol version"),
HIDError::UnsupportedCommand => {
write!(f, "Error: command is not supported on this device")
}
HIDError::IO(ref p, ref e) => write!(f, "Error: Ioerror({:?}): {}", p, e),
HIDError::Command(ref e) => write!(f, "Error: Error issuing command: {}", e),
HIDError::UnexpectedCmd(s) => write!(f, "Error: Unexpected status: {}", s),
HIDError::ApduStatus(ref status) => {
write!(f, "Error: Unexpected apdu status: {:?}", status)
}
}
}
}

Просмотреть файл

@ -10,9 +10,10 @@ use std::io::{Read, Write};
use std::os::unix::prelude::*;
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
use crate::platform::uhid;
use crate::transport::platform::uhid;
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use crate::util::from_unix_result;
use crate::util::io_err;
#[derive(Debug)]
pub struct Device {
@ -35,8 +36,51 @@ impl Device {
})
}
pub fn is_u2f(&self) -> bool {
uhid::is_u2f_device(self.fd)
pub fn is_u2f(&mut self) -> bool {
if !uhid::is_u2f_device(self.fd) {
return false;
}
if let Err(_) = self.ping() {
return false;
}
true
}
fn ping(&mut self) -> io::Result<()> {
for i in 0..10 {
let mut buf = vec![0u8; 1 + MAX_HID_RPT_SIZE];
buf[0] = 0; // report number
buf[1] = 0xff; // CID_BROADCAST
buf[2] = 0xff;
buf[3] = 0xff;
buf[4] = 0xff;
buf[5] = 0x81; // ping
buf[6] = 0;
buf[7] = 1; // one byte
self.write(&buf[..])?;
// Wait for response
let mut pfd: libc::pollfd = unsafe { std::mem::zeroed() };
pfd.fd = self.fd;
pfd.events = libc::POLLIN;
let nfds = unsafe { libc::poll(&mut pfd, 1, 100) };
if nfds == -1 {
return Err(io::Error::last_os_error());
}
if nfds == 0 {
debug!("device timeout {}", i);
continue;
}
// Read response
self.read(&mut buf[..])?;
return Ok(());
}
Err(io_err("no response from device"))
}
}

Просмотреть файл

@ -3,8 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::errors;
use crate::platform::monitor::Monitor;
use crate::statecallback::StateCallback;
use crate::transport::platformmonitor::Monitor;
use runloop::RunLoop;
use std::ffi::OsString;

136
third_party/rust/authenticator/src/transport/hid.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,136 @@
use crate::consts::{HIDCmd, CID_BROADCAST};
use crate::crypto::ECDHSecret;
use crate::ctap2::commands::get_info::AuthenticatorInfo;
use crate::transport::{errors::HIDError, Nonce};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo, U2FHIDCont, U2FHIDInit, U2FHIDInitResp};
use rand::{thread_rng, RngCore};
use std::cmp::Eq;
use std::fmt;
use std::hash::Hash;
use std::io;
pub trait HIDDevice
where
Self: io::Read,
Self: io::Write,
Self: U2FDevice,
Self: Sized,
Self: fmt::Debug,
{
type BuildParameters: Sized;
type Id: fmt::Debug + PartialEq + Eq + Hash + Sized;
// Open device, verify that it is indeed a CTAP device and potentially read initial values
fn new(parameters: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)>;
fn id(&self) -> Self::Id;
fn initialized(&self) -> bool;
// Check if the device is actually a token
fn is_u2f(&self) -> bool;
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>;
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo);
fn set_shared_secret(&mut self, secret: ECDHSecret);
fn get_shared_secret(&self) -> Option<&ECDHSecret>;
fn clone_device_as_write_only(&self) -> Result<Self, HIDError>;
// Initialize on a protocol-level
fn initialize(&mut self, noncecmd: Nonce) -> Result<(), HIDError> {
if self.initialized() {
return Ok(());
}
let nonce = match noncecmd {
Nonce::Use(x) => x,
Nonce::CreateRandom => {
let mut nonce = [0u8; 8];
thread_rng().fill_bytes(&mut nonce);
nonce
}
};
// Send Init to broadcast address to create a new channel
self.set_cid(CID_BROADCAST);
let (cmd, raw) = self.sendrecv(HIDCmd::Init, &nonce)?;
if cmd != HIDCmd::Init {
return Err(HIDError::DeviceError);
}
let rsp = U2FHIDInitResp::read(&raw, &nonce)?;
// Get the new Channel ID
self.set_cid(rsp.cid);
let vendor = self
.get_property("Manufacturer")
.unwrap_or_else(|_| String::from("Unknown Vendor"));
let product = self
.get_property("Product")
.unwrap_or_else(|_| String::from("Unknown Device"));
self.set_device_info(U2FDeviceInfo {
vendor_name: vendor.as_bytes().to_vec(),
device_name: product.as_bytes().to_vec(),
version_interface: rsp.version_interface,
version_major: rsp.version_major,
version_minor: rsp.version_minor,
version_build: rsp.version_build,
cap_flags: rsp.cap_flags,
});
// A CTAPHID host SHALL accept a response size that is longer than the
// anticipated size to allow for future extensions of the protocol, yet
// maintaining backwards compatibility. Future versions will maintain
// the response structure of the current version, but additional fields
// may be added.
Ok(())
}
fn sendrecv(&mut self, cmd: HIDCmd, send: &[u8]) -> io::Result<(HIDCmd, Vec<u8>)> {
let cmd: u8 = cmd.into();
self.u2f_write(cmd, send)?;
loop {
let (cmd, data) = self.u2f_read()?;
if cmd != HIDCmd::Keepalive {
break Ok((cmd, data));
}
}
}
fn u2f_write(&mut self, cmd: u8, send: &[u8]) -> io::Result<()> {
let mut count = U2FHIDInit::write(self, cmd, send)?;
// Send continuation packets.
let mut sequence = 0u8;
while count < send.len() {
count += U2FHIDCont::write(self, sequence, &send[count..])?;
sequence += 1;
}
Ok(())
}
fn u2f_read(&mut self) -> io::Result<(HIDCmd, Vec<u8>)> {
// Now we read. This happens in 2 chunks: The initial packet, which has
// the size we expect overall, then continuation packets, which will
// fill in data until we have everything.
let (cmd, data) = {
let (cmd, mut data) = U2FHIDInit::read(self)?;
trace!("init frame data read: {:04X?}", &data);
let mut sequence = 0u8;
while data.len() < data.capacity() {
let max = data.capacity() - data.len();
data.extend_from_slice(&U2FHIDCont::read(self, sequence, max)?);
sequence += 1;
}
(cmd, data)
};
trace!("u2f_read({:?}) cmd={:?}: {:04X?}", self.id(), cmd, &data);
Ok((cmd, data))
}
fn cancel(&mut self) -> Result<(), HIDError> {
let cancel: u8 = HIDCmd::Cancel.into();
self.u2f_write(cancel, &[])?;
Ok(())
}
}

Просмотреть файл

182
third_party/rust/authenticator/src/transport/linux/device.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,182 @@
/* 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/. */
extern crate libc;
use crate::consts::CID_BROADCAST;
use crate::transport::hid::HIDDevice;
use crate::transport::platform::{hidraw, monitor};
use crate::transport::{AuthenticatorInfo, ECDHSecret, FidoDevice, HIDError};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use crate::util::from_unix_result;
use std::fs::OpenOptions;
use std::hash::{Hash, Hasher};
use std::io;
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
#[derive(Debug)]
pub struct Device {
path: PathBuf,
fd: std::fs::File,
in_rpt_size: usize,
out_rpt_size: usize,
cid: [u8; 4],
dev_info: Option<U2FDeviceInfo>,
secret: Option<ECDHSecret>,
authenticator_info: Option<AuthenticatorInfo>,
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
// The path should be the only identifying member for a device
// If the path is the same, its the same device
self.path == other.path
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
// The path should be the only identifying member for a device
// If the path is the same, its the same device
self.path.hash(state);
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bufp = buf.as_mut_ptr() as *mut libc::c_void;
let rv = unsafe { libc::read(self.fd.as_raw_fd(), bufp, buf.len()) };
from_unix_result(rv as usize)
}
}
impl Write for Device {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bufp = buf.as_ptr() as *const libc::c_void;
let rv = unsafe { libc::write(self.fd.as_raw_fd(), bufp, buf.len()) };
from_unix_result(rv as usize)
}
// USB HID writes don't buffer, so this will be a nop.
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl U2FDevice for Device {
fn get_cid(&self) -> &[u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
fn in_rpt_size(&self) -> usize {
self.in_rpt_size
}
fn out_rpt_size(&self) -> usize {
self.out_rpt_size
}
fn get_property(&self, prop_name: &str) -> io::Result<String> {
monitor::get_property_linux(&self.path, prop_name)
}
fn get_device_info(&self) -> U2FDeviceInfo {
// unwrap is okay, as dev_info must have already been set, else
// a programmer error
self.dev_info.clone().unwrap()
}
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
self.dev_info = Some(dev_info);
}
}
impl HIDDevice for Device {
type BuildParameters = PathBuf;
type Id = PathBuf;
fn new(path: PathBuf) -> Result<Self, (HIDError, Self::Id)> {
debug!("Opening device {:?}", path);
let fd = OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.map_err(|e| (HIDError::IO(Some(path.clone()), e), path.clone()))?;
let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd.as_raw_fd());
let res = Self {
path,
fd,
in_rpt_size,
out_rpt_size,
cid: CID_BROADCAST,
dev_info: None,
secret: None,
authenticator_info: None,
};
if res.is_u2f() {
info!("new device {:?}", res.path);
Ok(res)
} else {
Err((HIDError::DeviceNotSupported, res.path.clone()))
}
}
fn initialized(&self) -> bool {
// During successful init, the broadcast channel id gets repplaced by an actual one
self.cid != CID_BROADCAST
}
fn id(&self) -> Self::Id {
self.path.clone()
}
fn is_u2f(&self) -> bool {
hidraw::is_u2f_device(self.fd.as_raw_fd())
}
fn get_shared_secret(&self) -> Option<&ECDHSecret> {
self.secret.as_ref()
}
fn set_shared_secret(&mut self, secret: ECDHSecret) {
self.secret = Some(secret);
}
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
self.authenticator_info.as_ref()
}
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
self.authenticator_info = Some(authenticator_info);
}
/// This is used for cancellation of blocking read()-requests.
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
let fd = OpenOptions::new()
.write(true)
.open(&self.path)
.map_err(|e| (HIDError::IO(Some(self.path.clone()), e)))?;
Ok(Self {
path: self.path.clone(),
fd,
in_rpt_size: self.in_rpt_size,
out_rpt_size: self.out_rpt_size,
cid: self.cid,
dev_info: self.dev_info.clone(),
secret: self.secret.clone(),
authenticator_info: self.authenticator_info.clone(),
})
}
}
impl FidoDevice for Device {}

Просмотреть файл

@ -10,7 +10,7 @@ use std::os::unix::io::RawFd;
use super::hidwrapper::{_HIDIOCGRDESC, _HIDIOCGRDESCSIZE};
use crate::consts::MAX_HID_RPT_SIZE;
use crate::hidproto::*;
use crate::transport::hidproto::*;
use crate::util::{from_unix_result, io_err};
#[allow(non_camel_case_types)]

Просмотреть файл

@ -46,3 +46,6 @@ include!("ioctl_aarch64be.rs");
#[cfg(all(target_arch = "s390x", target_endian = "big"))]
include!("ioctl_s390xbe.rs");
#[cfg(all(target_arch = "riscv64", target_endian = "little"))]
include!("ioctl_riscv64.rs");

Просмотреть файл

@ -0,0 +1,5 @@
/* automatically generated by rust-bindgen */
pub type __u32 = ::std::os::raw::c_uint;
pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
pub const _HIDIOCGRDESC: __u32 = 2416199682;

Просмотреть файл

Просмотреть файл

@ -2,14 +2,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::transport::device_selector::DeviceSelectorEvent;
use libc::{c_int, c_short, c_ulong};
use libudev::EventType;
use runloop::RunLoop;
use std::collections::HashMap;
use std::ffi::OsString;
use std::error::Error;
use std::io;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use std::path::PathBuf;
use std::sync::{mpsc::Sender, Arc};
const UDEV_SUBSYSTEM: &str = "hidraw";
const POLLIN: c_short = 0x0001;
@ -29,34 +31,54 @@ fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> {
pub struct Monitor<F>
where
F: Fn(OsString, &dyn Fn() -> bool) + Sync,
F: Fn(PathBuf, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ Sync,
{
runloops: HashMap<OsString, RunLoop>,
runloops: HashMap<PathBuf, RunLoop>,
new_device_cb: Arc<F>,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
}
impl<F> Monitor<F>
where
F: Fn(OsString, &dyn Fn() -> bool) + Send + Sync + 'static,
F: Fn(PathBuf, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ Send
+ Sync
+ 'static,
{
pub fn new(new_device_cb: F) -> Self {
pub fn new(
new_device_cb: F,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
) -> Self {
Self {
runloops: HashMap::new(),
new_device_cb: Arc::new(new_device_cb),
selector_sender,
status_sender,
}
}
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
let ctx = libudev::Context::new()?;
let mut enumerator = libudev::Enumerator::new(&ctx)?;
enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
// Iterate all existing devices.
for dev in enumerator.scan_devices()? {
if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) {
self.add_device(path);
}
let paths: Vec<PathBuf> = enumerator
.scan_devices()?
.filter_map(|dev| dev.devnode().map(|p| p.to_owned()))
.collect();
// Add them all in one go to avoid race conditions in DeviceSelector
// (8 devices should be added, but the first returns already before all
// others are known to DeviceSelector)
self.selector_sender
.send(DeviceSelectorEvent::DevicesAdded(paths.clone()))?;
for path in paths {
self.add_device(path);
}
let mut monitor = libudev::Monitor::new(&ctx)?;
@ -86,13 +108,13 @@ where
}
fn process_event(&mut self, event: &libudev::Event) {
let path = event
.device()
.devnode()
.map(|dn| dn.to_owned().into_os_string());
let path = event.device().devnode().map(|dn| dn.to_owned());
match (event.event_type(), path) {
(EventType::Add, Some(path)) => {
let _ = self
.selector_sender
.send(DeviceSelectorEvent::DevicesAdded(vec![path.clone()]));
self.add_device(path);
}
(EventType::Remove, Some(path)) => {
@ -102,15 +124,16 @@ where
}
}
fn add_device(&mut self, path: OsString) {
fn add_device(&mut self, path: PathBuf) {
let f = self.new_device_cb.clone();
let key = path.clone();
let selector_sender = self.selector_sender.clone();
let status_sender = self.status_sender.clone();
debug!("Adding device {}", path.to_string_lossy());
let runloop = RunLoop::new(move |alive| {
if alive() {
f(path, alive);
f(path, selector_sender, status_sender, alive);
}
});
@ -119,9 +142,12 @@ where
}
}
fn remove_device(&mut self, path: &OsString) {
debug!("Removing device {}", path.to_string_lossy());
fn remove_device(&mut self, path: &PathBuf) {
let _ = self
.selector_sender
.send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
debug!("Removing device {}", path.to_string_lossy());
if let Some(runloop) = self.runloops.remove(path) {
runloop.cancel();
}
@ -135,7 +161,7 @@ where
}
}
pub fn get_property_linux(path: &OsString, prop_name: &str) -> io::Result<String> {
pub fn get_property_linux(path: &PathBuf, prop_name: &str) -> io::Result<String> {
let ctx = libudev::Context::new()?;
let mut enumerator = libudev::Enumerator::new(&ctx)?;

Просмотреть файл

@ -3,30 +3,45 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::errors;
use crate::platform::fd::Fd;
use crate::platform::monitor::Monitor;
use crate::statecallback::StateCallback;
use crate::transport::device_selector::{
DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
};
use crate::transport::platform::monitor::Monitor;
use runloop::RunLoop;
use std::sync::mpsc::Sender;
pub struct Transaction {
// Handle to the thread loop.
thread: Option<RunLoop>,
thread: RunLoop,
device_selector: DeviceSelector,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: StateCallback<crate::Result<T>>,
status: Sender<crate::StatusUpdate>,
new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn(Fd, &dyn Fn() -> bool) + Sync + Send + 'static,
F: Fn(
DeviceBuildParameters,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync
+ Send
+ 'static,
T: 'static,
{
let status_sender = status.clone();
let device_selector = DeviceSelector::run(status);
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
// Create a new device monitor.
let mut monitor = Monitor::new(new_device_cb);
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
// Start polling for new devices.
try_or!(monitor.run(alive), |_| callback
@ -42,12 +57,14 @@ impl Transaction {
.map_err(|_| errors::AuthenticatorError::Platform)?;
Ok(Self {
thread: Some(thread),
thread,
device_selector,
})
}
pub fn cancel(&mut self) {
// This must never be None.
self.thread.take().unwrap().cancel();
info!("Transaction was cancelled.");
self.device_selector.stop();
self.thread.cancel();
}
}

Просмотреть файл

@ -5,11 +5,15 @@
extern crate log;
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
use crate::platform::iokit::*;
use crate::transport::hid::HIDDevice;
use crate::transport::platform::iokit::*;
use crate::transport::{AuthenticatorInfo, ECDHSecret, FidoDevice, HIDError};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use core_foundation::base::*;
use core_foundation::string::*;
use std::convert::TryInto;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::io;
use std::io::{Read, Write};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
@ -20,25 +24,13 @@ const READ_TIMEOUT: u64 = 15;
pub struct Device {
device_ref: IOHIDDeviceRef,
cid: [u8; 4],
report_rx: Receiver<Vec<u8>>,
report_rx: Option<Receiver<Vec<u8>>>,
dev_info: Option<U2FDeviceInfo>,
secret: Option<ECDHSecret>,
authenticator_info: Option<AuthenticatorInfo>,
}
impl Device {
pub fn new(dev_ids: (IOHIDDeviceRef, Receiver<Vec<u8>>)) -> io::Result<Self> {
let (device_ref, report_rx) = dev_ids;
Ok(Self {
device_ref,
cid: CID_BROADCAST,
report_rx,
dev_info: None,
})
}
pub fn is_u2f(&self) -> bool {
true
}
unsafe fn get_property_macos(&self, prop_name: &str) -> io::Result<String> {
let prop_ref = IOHIDDeviceGetProperty(
self.device_ref,
@ -68,25 +60,45 @@ impl Device {
}
}
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Device").field("cid", &self.cid).finish()
}
}
impl PartialEq for Device {
fn eq(&self, other_device: &Device) -> bool {
self.device_ref == other_device.device_ref
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
// The path should be the only identifying member for a device
// If the path is the same, its the same device
self.device_ref.hash(state);
}
}
impl Read for Device {
fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> {
let timeout = Duration::from_secs(READ_TIMEOUT);
let data = match self.report_rx.recv_timeout(timeout) {
Ok(v) => v,
Err(e) if e == RecvTimeoutError::Timeout => {
return Err(io::Error::new(io::ErrorKind::TimedOut, e));
}
Err(e) => {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e));
}
};
bytes.write(&data)
if let Some(rx) = &self.report_rx {
let timeout = Duration::from_secs(READ_TIMEOUT);
let data = match rx.recv_timeout(timeout) {
Ok(v) => v,
Err(e) if e == RecvTimeoutError::Timeout => {
return Err(io::Error::new(io::ErrorKind::TimedOut, e));
}
Err(e) => {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e));
}
};
bytes.write(&data)
} else {
Err(io::Error::from(io::ErrorKind::Unsupported))
}
}
}
@ -154,3 +166,62 @@ impl U2FDevice for Device {
self.dev_info = Some(dev_info);
}
}
impl HIDDevice for Device {
type BuildParameters = (IOHIDDeviceRef, Receiver<Vec<u8>>);
type Id = IOHIDDeviceRef;
fn new(dev_ids: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
let (device_ref, report_rx) = dev_ids;
Ok(Self {
device_ref,
cid: CID_BROADCAST,
report_rx: Some(report_rx),
dev_info: None,
secret: None,
authenticator_info: None,
})
}
fn initialized(&self) -> bool {
self.cid != CID_BROADCAST
}
fn id(&self) -> Self::Id {
self.device_ref
}
fn is_u2f(&self) -> bool {
true
}
fn get_shared_secret(&self) -> Option<&ECDHSecret> {
self.secret.as_ref()
}
fn set_shared_secret(&mut self, secret: ECDHSecret) {
self.secret = Some(secret);
}
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
self.authenticator_info.as_ref()
}
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
self.authenticator_info = Some(authenticator_info);
}
/// This is used for cancellation of blocking read()-requests.
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
Ok(Self {
device_ref: self.device_ref,
cid: self.cid,
report_rx: None,
dev_info: self.dev_info.clone(),
secret: self.secret.clone(),
authenticator_info: self.authenticator_info.clone(),
})
}
}
impl FidoDevice for Device {}

Просмотреть файл

Просмотреть файл

@ -5,7 +5,8 @@
extern crate libc;
extern crate log;
use crate::platform::iokit::*;
use crate::transport::device_selector::DeviceSelectorEvent;
use crate::transport::platform::iokit::*;
use crate::util::io_err;
use core_foundation::base::*;
use core_foundation::runloop::*;
@ -22,20 +23,40 @@ struct DeviceData {
pub struct Monitor<F>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync,
F: Fn(
(IOHIDDeviceRef, Receiver<Vec<u8>>),
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Send
+ Sync
+ 'static,
{
manager: IOHIDManagerRef,
// Keep alive until the monitor goes away.
_matcher: IOHIDDeviceMatcher,
map: HashMap<IOHIDDeviceRef, DeviceData>,
new_device_cb: F,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
}
impl<F> Monitor<F>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync + 'static,
F: Fn(
(IOHIDDeviceRef, Receiver<Vec<u8>>),
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Send
+ Sync
+ 'static,
{
pub fn new(new_device_cb: F) -> Self {
pub fn new(
new_device_cb: F,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
) -> Self {
let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
// Match FIDO devices only.
@ -47,6 +68,8 @@ where
_matcher,
new_device_cb,
map: HashMap::new(),
selector_sender,
status_sender,
}
}
@ -98,6 +121,9 @@ where
fn remove_device(&mut self, device_ref: IOHIDDeviceRef) {
if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) {
let _ = self
.selector_sender
.send(DeviceSelectorEvent::DeviceRemoved(device_ref));
// Dropping `tx` will make Device::read() fail eventually.
drop(tx);
@ -137,7 +163,11 @@ where
device_ref: IOHIDDeviceRef,
) {
let this = unsafe { &mut *(context as *mut Self) };
let _ = this
.selector_sender
.send(DeviceSelectorEvent::DevicesAdded(vec![device_ref]));
let selector_sender = this.selector_sender.clone();
let status_sender = this.status_sender.clone();
let (tx, rx) = channel();
let f = &this.new_device_cb;
@ -145,7 +175,7 @@ where
let runloop = RunLoop::new(move |alive| {
// Ensure that the runloop is still alive.
if alive() {
f((device_ref, rx), alive);
f((device_ref, rx), selector_sender, status_sender, alive);
}
});
@ -167,7 +197,14 @@ where
impl<F> Drop for Monitor<F>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync,
F: Fn(
(IOHIDDeviceRef, Receiver<Vec<u8>>),
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Send
+ Sync
+ 'static,
{
fn drop(&mut self) {
unsafe { CFRelease(self.manager as *mut c_void) };

Просмотреть файл

@ -5,12 +5,15 @@
extern crate libc;
use crate::errors;
use crate::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop};
use crate::platform::monitor::Monitor;
use crate::statecallback::StateCallback;
use crate::transport::device_selector::{
DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
};
use crate::transport::platform::iokit::{CFRunLoopEntryObserver, SendableRunLoop};
use crate::transport::platform::monitor::Monitor;
use core_foundation::runloop::*;
use std::os::raw::c_void;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::mpsc::{channel, Sender};
use std::thread;
// A transaction will run the given closure in a new thread, thereby using a
@ -20,21 +23,32 @@ use std::thread;
pub struct Transaction {
runloop: Option<SendableRunLoop>,
thread: Option<thread::JoinHandle<()>>,
device_selector: DeviceSelector,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: StateCallback<crate::Result<T>>,
status: Sender<crate::StatusUpdate>,
new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync + Send + 'static,
F: Fn(
DeviceBuildParameters,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync
+ Send
+ 'static,
T: 'static,
{
let (tx, rx) = channel();
let timeout = (timeout as f64) / 1000.0;
let status_sender = status.clone();
let device_selector = DeviceSelector::run(status);
let selector_sender = device_selector.clone_sender();
let builder = thread::Builder::new();
let thread = builder
.spawn(move || {
@ -47,7 +61,7 @@ impl Transaction {
obs.add_to_current_runloop();
// Create a new HID device monitor and start polling.
let mut monitor = Monitor::new(new_device_cb);
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
try_or!(monitor.start(), |_| callback
.call(Err(errors::AuthenticatorError::Platform)));
@ -72,6 +86,7 @@ impl Transaction {
Ok(Self {
runloop: Some(runloop),
thread: Some(thread),
device_selector,
})
}
@ -86,6 +101,7 @@ impl Transaction {
// This must never be None. This won't block.
unsafe { CFRunLoopStop(*self.runloop.take().unwrap()) };
self.device_selector.stop();
// This must never be None. Ignore return value.
let _ = self.thread.take().unwrap().join();
}

194
third_party/rust/authenticator/src/transport/mock/device.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,194 @@
/* 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 crate::consts::CID_BROADCAST;
use crate::crypto::ECDHSecret;
use crate::ctap2::commands::get_info::AuthenticatorInfo;
use crate::transport::device_selector::DeviceCommand;
use crate::transport::{hid::HIDDevice, FidoDevice, HIDError};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use std::hash::{Hash, Hasher};
use std::io::{self, Read, Write};
use std::sync::mpsc::{channel, Receiver, Sender};
pub(crate) const IN_HID_RPT_SIZE: usize = 64;
const OUT_HID_RPT_SIZE: usize = 64;
#[derive(Debug)]
pub struct Device {
pub id: String,
pub cid: [u8; 4],
pub reads: Vec<[u8; IN_HID_RPT_SIZE]>,
pub writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>,
pub dev_info: Option<U2FDeviceInfo>,
pub authenticator_info: Option<AuthenticatorInfo>,
pub sender: Option<Sender<DeviceCommand>>,
pub receiver: Option<Receiver<DeviceCommand>>,
}
impl Device {
pub fn add_write(&mut self, packet: &[u8], fill_value: u8) {
// Add one to deal with record index check
let mut write = [fill_value; OUT_HID_RPT_SIZE + 1];
// Make sure we start with a 0, for HID record index
write[0] = 0;
// Clone packet data in at 1, since front is padded with HID record index
write[1..=packet.len()].clone_from_slice(packet);
self.writes.push(write);
}
pub fn add_read(&mut self, packet: &[u8], fill_value: u8) {
let mut read = [fill_value; IN_HID_RPT_SIZE];
read[..packet.len()].clone_from_slice(packet);
self.reads.push(read);
}
pub fn create_channel(&mut self) {
let (tx, rx) = channel();
self.sender = Some(tx);
self.receiver = Some(rx);
}
}
impl Write for Device {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
// Pop a vector from the expected writes, check for quality
// against bytes array.
assert!(
!self.writes.is_empty(),
"Ran out of expected write values! Wanted to write {:?}",
bytes
);
let check = self.writes.remove(0);
assert_eq!(check.len(), bytes.len());
assert_eq!(&check, bytes);
Ok(bytes.len())
}
// nop
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Read for Device {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
assert!(!self.reads.is_empty(), "Ran out of read values!");
let check = self.reads.remove(0);
assert_eq!(check.len(), bytes.len());
bytes.clone_from_slice(&check);
Ok(check.len())
}
}
impl Drop for Device {
fn drop(&mut self) {
if !std::thread::panicking() {
assert!(self.reads.is_empty());
assert!(self.writes.is_empty());
}
}
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.id == other.id
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl U2FDevice for Device {
fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
fn in_rpt_size(&self) -> usize {
IN_HID_RPT_SIZE
}
fn out_rpt_size(&self) -> usize {
OUT_HID_RPT_SIZE
}
fn get_property(&self, prop_name: &str) -> io::Result<String> {
Ok(format!("{} not implemented", prop_name))
}
fn get_device_info(&self) -> U2FDeviceInfo {
self.dev_info.clone().unwrap()
}
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
self.dev_info = Some(dev_info);
}
}
impl HIDDevice for Device {
type Id = String;
type BuildParameters = &'static str; // None used
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
self.authenticator_info.as_ref()
}
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
self.authenticator_info = Some(authenticator_info);
}
fn set_shared_secret(&mut self, _: ECDHSecret) {
// Nothing
}
fn get_shared_secret(&self) -> std::option::Option<&ECDHSecret> {
None
}
fn new(id: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
Ok(Device {
id: id.to_string(),
cid: CID_BROADCAST,
reads: vec![],
writes: vec![],
dev_info: None,
authenticator_info: None,
sender: None,
receiver: None,
})
}
fn initialized(&self) -> bool {
self.get_cid() != &CID_BROADCAST
}
fn id(&self) -> Self::Id {
self.id.clone()
}
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
Ok(Device {
id: self.id.clone(),
cid: self.cid,
reads: self.reads.clone(),
writes: self.writes.clone(),
dev_info: self.dev_info.clone(),
authenticator_info: self.authenticator_info.clone(),
sender: self.sender.clone(),
receiver: None,
})
}
fn is_u2f(&self) -> bool {
self.sender.is_some()
}
}
impl FidoDevice for Device {}

6
third_party/rust/authenticator/src/transport/mock/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
/* 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/. */
pub mod device;
pub mod transaction;

Просмотреть файл

@ -0,0 +1,35 @@
/* 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 crate::statecallback::StateCallback;
use crate::transport::device_selector::{DeviceBuildParameters, DeviceSelectorEvent};
use std::sync::mpsc::Sender;
pub struct Transaction {}
impl Transaction {
pub fn new<F, T>(
_timeout: u64,
_callback: StateCallback<crate::Result<T>>,
_status: Sender<crate::StatusUpdate>,
_new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn(
DeviceBuildParameters,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync
+ Send
+ 'static,
T: 'static,
{
Ok(Self {})
}
pub fn cancel(&mut self) {
info!("Transaction was cancelled.");
}
}

263
third_party/rust/authenticator/src/transport/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,263 @@
use crate::consts::{Capability, HIDCmd};
use crate::crypto::ECDHSecret;
use crate::ctap2::commands::client_pin::{GetKeyAgreement, PinAuth};
use crate::ctap2::commands::get_info::{AuthenticatorInfo, GetInfo};
use crate::ctap2::commands::get_version::GetVersion;
use crate::ctap2::commands::make_credentials::dummy_make_credentials_cmd;
use crate::ctap2::commands::selection::Selection;
use crate::ctap2::commands::{
CommandError, PinAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable, StatusCode,
};
use crate::transport::device_selector::BlinkResult;
use crate::transport::errors::{ApduErrorStatus, HIDError};
use crate::transport::hid::HIDDevice;
use crate::util::io_err;
use std::thread;
use std::time::Duration;
pub mod device_selector;
pub mod errors;
pub mod hid;
#[cfg(all(
any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"),
not(test)
))]
pub mod hidproto;
#[cfg(all(target_os = "linux", not(test)))]
#[path = "linux/mod.rs"]
pub mod platform;
#[cfg(all(target_os = "freebsd", not(test)))]
#[path = "freebsd/mod.rs"]
pub mod platform;
#[cfg(all(target_os = "netbsd", not(test)))]
#[path = "netbsd/mod.rs"]
pub mod platform;
#[cfg(all(target_os = "openbsd", not(test)))]
#[path = "openbsd/mod.rs"]
pub mod platform;
#[cfg(all(target_os = "macos", not(test)))]
#[path = "macos/mod.rs"]
pub mod platform;
#[cfg(all(target_os = "windows", not(test)))]
#[path = "windows/mod.rs"]
pub mod platform;
#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "windows",
test
)))]
#[path = "stub/mod.rs"]
pub mod platform;
#[cfg(test)]
#[path = "mock/mod.rs"]
pub mod platform;
#[derive(Debug)]
pub enum Nonce {
CreateRandom,
Use([u8; 8]),
}
// TODO(MS): This is the lazy way: FidoDevice currently only extends HIDDevice by more functions,
// but the goal is to remove U2FDevice entirely and copy over the trait-definition here
pub trait FidoDevice: HIDDevice {
fn send_msg<'msg, Out, Req: Request<Out>>(&mut self, msg: &'msg Req) -> Result<Out, HIDError> {
if !self.initialized() {
return Err(HIDError::DeviceNotInitialized);
}
if self.supports_ctap2() && msg.is_ctap2_request() {
self.send_cbor(msg)
} else {
self.send_ctap1(msg)
}
}
fn send_cbor<'msg, Req: RequestCtap2>(
&mut self,
msg: &'msg Req,
) -> Result<Req::Output, HIDError> {
debug!("sending {:?} to {:?}", msg, self);
let mut data = msg.wire_format(self)?;
let mut buf: Vec<u8> = Vec::with_capacity(data.len() + 1);
// CTAP2 command
buf.push(Req::command() as u8);
// payload
buf.append(&mut data);
let buf = buf;
let (cmd, resp) = self.sendrecv(HIDCmd::Cbor, &buf)?;
debug!(
"got from Device {:?} status={:?}: {:?}",
self.id(),
cmd,
resp
);
if cmd == HIDCmd::Cbor {
Ok(msg.handle_response_ctap2(self, &resp)?)
} else {
Err(HIDError::UnexpectedCmd(cmd.into()))
}
}
fn send_ctap1<'msg, Req: RequestCtap1>(
&mut self,
msg: &'msg Req,
) -> Result<Req::Output, HIDError> {
debug!("sending {:?} to {:?}", msg, self);
let data = msg.ctap1_format(self)?;
loop {
let (cmd, mut data) = self.sendrecv(HIDCmd::Msg, &data)?;
debug!(
"got from Device {:?} status={:?}: {:?}",
self.id(),
cmd,
data
);
if cmd == HIDCmd::Msg {
if data.len() < 2 {
return Err(io_err("Unexpected Response: shorter than expected").into());
}
let split_at = data.len() - 2;
let status = data.split_off(split_at);
// This will bubble up error if status != no error
let status = ApduErrorStatus::from([status[0], status[1]]);
match msg.handle_response_ctap1(status, &data) {
Ok(out) => return Ok(out),
Err(Retryable::Retry) => {
// sleep 100ms then loop again
// TODO(baloo): meh, use tokio instead?
thread::sleep(Duration::from_millis(100));
}
Err(Retryable::Error(e)) => return Err(e),
}
} else {
return Err(HIDError::UnexpectedCmd(cmd.into()));
}
}
}
// This is ugly as we have 2 init-functions now, but the fastest way currently.
fn init(&mut self, nonce: Nonce) -> Result<(), HIDError> {
let resp = <Self as HIDDevice>::initialize(self, nonce)?;
// TODO(baloo): this logic should be moved to
// transport/mod.rs::Device trait
if self.supports_ctap2() {
let command = GetInfo::default();
let info = self.send_cbor(&command)?;
debug!("{:?} infos: {:?}", self.id(), info);
self.set_authenticator_info(info);
}
if self.supports_ctap1() {
let command = GetVersion::default();
// We don't really use the result here
self.send_ctap1(&command)?;
}
Ok(resp)
}
fn block_and_blink(&mut self) -> BlinkResult {
let resp;
let supports_select_cmd = self
.get_authenticator_info()
.map_or(false, |i| i.versions.contains(&String::from("FIDO_2_1")));
if supports_select_cmd {
let msg = Selection {};
resp = self.send_cbor(&msg);
} else {
// We need to fake a blink-request, because FIDO2.0 forgot to specify one
// See: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorMakeCredential
let mut msg = match dummy_make_credentials_cmd() {
Ok(m) => m,
Err(_) => {
return BlinkResult::Cancelled;
}
};
// Using a zero-length pinAuth will trigger the device to blink
// For CTAP1, this gets ignored anyways and we do a 'normal' register
// command, which also just blinks.
msg.set_pin_auth(Some(PinAuth::empty_pin_auth()), None);
info!("Trying to blink: {:?}", &msg);
// We don't care about the Ok-value, just if it is Ok or not
resp = self.send_msg(&msg).map(|_| ());
}
match resp {
// Spec only says PinInvalid or PinNotSet should be returned on the fake touch-request,
// but Yubikeys for example return PinAuthInvalid. A successful return is also possible
// for CTAP1-tokens so we catch those here as well.
Ok(_)
| Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinInvalid, _)))
| Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _)))
| Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinNotSet, _))) => {
BlinkResult::DeviceSelected
}
// We cancelled the receive, because another device was selected.
Err(HIDError::Command(CommandError::StatusCode(StatusCode::KeepaliveCancel, _)))
| Err(HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, _)))
| Err(HIDError::Command(CommandError::StatusCode(StatusCode::UserActionTimeout, _))) => {
// TODO: Repeat the request, if it is a UserActionTimeout?
debug!("Device {:?} got cancelled", &self);
BlinkResult::Cancelled
}
// Something unexpected happened, so we assume this device is not usable and interpreting
// this equivalent to being cancelled.
e => {
info!("Device {:?} received unexpected answer, so we assume an error occurred and we are NOT using this device (assuming the request was cancelled): {:?}", &self, e);
BlinkResult::Cancelled
}
}
}
fn supports_ctap1(&self) -> bool {
// CAPABILITY_NMSG:
// If set to 1, authenticator DOES NOT implement U2FHID_MSG function
!self.get_device_info().cap_flags.contains(Capability::NMSG)
}
fn supports_ctap2(&self) -> bool {
self.get_device_info().cap_flags.contains(Capability::CBOR)
}
fn establish_shared_secret(&mut self) -> Result<(ECDHSecret, AuthenticatorInfo), HIDError> {
if !self.supports_ctap2() {
return Err(HIDError::UnsupportedCommand);
}
let info = if let Some(authenticator_info) = self.get_authenticator_info().cloned() {
authenticator_info
} else {
// We should already have it, since it is queried upon `init()`, but just to be safe
let info_command = GetInfo::default();
let info = self.send_cbor(&info_command)?;
debug!("infos: {:?}", info);
self.set_authenticator_info(info.clone());
info
};
// Not reusing the shared secret here, if it exists, since we might start again
// with a different PIN (e.g. if the last one was wrong)
let pin_command = GetKeyAgreement::new(&info)?;
let device_key_agreement = self.send_cbor(&pin_command)?;
let shared_secret = device_key_agreement.shared_secret()?;
self.set_shared_secret(shared_secret.clone());
Ok((shared_secret, info))
}
}

Просмотреть файл

@ -11,8 +11,8 @@ use std::mem;
use crate::consts::CID_BROADCAST;
use crate::consts::MAX_HID_RPT_SIZE;
use crate::platform::fd::Fd;
use crate::platform::uhid;
use crate::transport::platform::fd::Fd;
use crate::transport::platform::uhid;
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use crate::util::io_err;
@ -97,6 +97,16 @@ impl PartialEq for Device {
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
// The path should be the only identifying member for a device
// If the path is the same, its the same device
self.fd.hash(state);
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bufp = buf.as_mut_ptr() as *mut libc::c_void;

Просмотреть файл

@ -45,3 +45,17 @@ impl PartialEq for Fd {
(st.st_dev == sto.st_dev) & (st.st_ino == sto.st_ino)
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut st: libc::stat = unsafe { mem::zeroed() };
if unsafe { libc::fstat(self.fileno, &mut st) } == -1 {
return;
}
st.st_dev.hash(state);
st.st_ino.hash(state);
}
}

Просмотреть файл

Просмотреть файл

@ -2,16 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::transport::device_selector::DeviceSelectorEvent;
use std::collections::HashMap;
use std::ffi::OsString;
use std::io;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use std::sync::{mpsc::Sender, Arc};
use runloop::RunLoop;
use crate::platform::fd::Fd;
use crate::transport::platform::fd::Fd;
// XXX Should use drvctl, but it doesn't do pubsub properly yet so
// DRVGETEVENT requires write access to /dev/drvctl. Instead, for now,
@ -20,24 +21,44 @@ const POLL_TIMEOUT: u64 = 500;
pub struct Monitor<F>
where
F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static,
F: Fn(
Fd,
OsString,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync,
{
runloops: HashMap<OsString, RunLoop>,
new_device_cb: Arc<F>,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
}
impl<F> Monitor<F>
where
F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static,
F: Fn(
Fd,
OsString,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync,
{
pub fn new(new_device_cb: F) -> Self {
pub fn new(
new_device_cb: F,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
) -> Self {
Self {
runloops: HashMap::new(),
new_device_cb: Arc::new(new_device_cb),
selector_sender,
status_sender,
}
}
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
while alive() {
for n in 0..100 {
let uhidpath = format!("/dev/uhid{}", n);
@ -60,10 +81,13 @@ where
fn add_device(&mut self, fd: Fd, path: OsString) {
let f = self.new_device_cb.clone();
let selector_sender = self.selector_sender.clone();
let status_sender = self.status_sender.clone();
debug!("Adding device {}", path.to_string_lossy());
let runloop = RunLoop::new(move |alive| {
if alive() {
f(fd, alive);
f(fd.clone(), path.clone(), selector_sender, status_sender, alive);
}
});
@ -73,6 +97,11 @@ where
}
fn remove_device(&mut self, path: OsString) {
let _ = self
.selector_sender
.send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
debug!("Removing device {}", path.to_string_lossy());
if let Some(runloop) = self.runloops.remove(&path) {
runloop.cancel();
}

Просмотреть файл

@ -0,0 +1,72 @@
/* 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 crate::errors;
use crate::statecallback::StateCallback;
use crate::transport::platform::fd::Fd;
use crate::transport::device_selector::{
DeviceBuildParameters, DeviceID, DeviceSelector, DeviceSelectorEvent,
};
use crate::transport::platform::monitor::Monitor;
use runloop::RunLoop;
use std::sync::mpsc::Sender;
pub struct Transaction {
// Handle to the thread loop.
thread: RunLoop,
device_selector: DeviceSelector,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: StateCallback<crate::Result<T>>,
status: Sender<crate::StatusUpdate>,
new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn(
DeviceBuildParameters,
DeviceID,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync
+ Send
+ 'static,
T: 'static,
{
let status_sender = status.clone();
let device_selector = DeviceSelector::run(status);
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
// Create a new device monitor.
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
// Start polling for new devices.
try_or!(monitor.run(alive), |_| callback
.call(Err(errors::AuthenticatorError::Platform)));
// Send an error, if the callback wasn't called already.
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::NotAllowed,
)));
},
timeout,
)
.map_err(|_| errors::AuthenticatorError::Platform)?;
Ok(Self {
thread,
device_selector,
})
}
pub fn cancel(&mut self) {
info!("Transaction was cancelled.");
self.thread.cancel();
self.device_selector.stop();
}
}

Просмотреть файл

@ -11,7 +11,7 @@ use std::os::raw::c_uchar;
use crate::hidproto::has_fido_usage;
use crate::hidproto::ReportDescriptor;
use crate::platform::fd::Fd;
use crate::transport::platform::fd::Fd;
use crate::util::io_err;
/* sys/ioccom.h */

Просмотреть файл

@ -10,7 +10,7 @@ use std::io::{Read, Result, Write};
use std::mem;
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
use crate::platform::monitor::FidoDev;
use crate::transport::platform::monitor::FidoDev;
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use crate::util::{from_unix_result, io_err};

Просмотреть файл

@ -3,8 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::errors;
use crate::platform::monitor::{FidoDev, Monitor};
use crate::statecallback::StateCallback;
use crate::transport::platform::monitor::{FidoDev, Monitor};
use runloop::RunLoop;
pub struct Transaction {

Просмотреть файл

@ -2,22 +2,18 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::transport::hid::HIDDevice;
use crate::transport::FidoDevice;
use crate::transport::{AuthenticatorInfo, ECDHSecret, HIDError};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use std::hash::{Hash, Hasher};
use std::io;
use std::io::{Read, Write};
use std::path::PathBuf;
#[derive(Debug, PartialEq, Eq)]
pub struct Device {}
impl Device {
pub fn new(path: String) -> io::Result<Self> {
panic!("not implemented");
}
pub fn is_u2f(&self) -> bool {
panic!("not implemented");
}
}
impl Read for Device {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
panic!("not implemented");
@ -63,3 +59,52 @@ impl U2FDevice for Device {
panic!("not implemented")
}
}
impl HIDDevice for Device {
type BuildParameters = PathBuf;
type Id = PathBuf;
fn new(parameters: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
unimplemented!();
}
fn initialized(&self) -> bool {
unimplemented!();
}
fn id(&self) -> Self::Id {
unimplemented!()
}
fn is_u2f(&self) -> bool {
unimplemented!()
}
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
unimplemented!()
}
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
unimplemented!()
}
fn set_shared_secret(&mut self, secret: ECDHSecret) {
unimplemented!()
}
fn get_shared_secret(&self) -> Option<&ECDHSecret> {
unimplemented!()
}
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
unimplemented!()
}
}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
unimplemented!()
}
}
impl FidoDevice for Device {}

Просмотреть файл

Просмотреть файл

@ -0,0 +1,52 @@
/* 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 crate::errors;
use crate::statecallback::StateCallback;
use crate::transport::device_selector::{
DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
};
use std::path::PathBuf;
use std::sync::mpsc::Sender;
pub struct Transaction {}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: StateCallback<crate::Result<T>>,
status: Sender<crate::StatusUpdate>,
new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn(
DeviceBuildParameters,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync
+ Send
+ 'static,
T: 'static,
{
// Just to silence "unused"-warnings
let mut device_selector = DeviceSelector::run(status);
let _ = DeviceSelectorEvent::DevicesAdded(vec![]);
let _ = DeviceSelectorEvent::DeviceRemoved(PathBuf::new());
let _ = device_selector.clone_sender();
device_selector.stop();
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::NotSupported,
)));
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::NotSupported,
))
}
pub fn cancel(&mut self) {
/* No-op. */
}
}

171
third_party/rust/authenticator/src/transport/windows/device.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,171 @@
/* 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 super::winapi::DeviceCapabilities;
use crate::consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE};
use crate::transport::hid::HIDDevice;
use crate::transport::{AuthenticatorInfo, ECDHSecret, FidoDevice, HIDError};
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
use std::fs::{File, OpenOptions};
use std::hash::{Hash, Hasher};
use std::io::{self, Read, Write};
use std::os::windows::io::AsRawHandle;
#[derive(Debug)]
pub struct Device {
path: String,
file: File,
cid: [u8; 4],
dev_info: Option<U2FDeviceInfo>,
secret: Option<ECDHSecret>,
authenticator_info: Option<AuthenticatorInfo>,
}
impl PartialEq for Device {
fn eq(&self, other: &Device) -> bool {
self.path == other.path
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
// The path should be the only identifying member for a device
// If the path is the same, its the same device
self.path.hash(state);
}
}
impl Read for Device {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
// Windows always includes the report ID.
let mut input = [0u8; MAX_HID_RPT_SIZE + 1];
let _ = self.file.read(&mut input)?;
bytes.clone_from_slice(&input[1..]);
Ok(bytes.len() as usize)
}
}
impl Write for Device {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.file.write(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl U2FDevice for Device {
fn get_cid(&self) -> &[u8; 4] {
&self.cid
}
fn set_cid(&mut self, cid: [u8; 4]) {
self.cid = cid;
}
fn in_rpt_size(&self) -> usize {
MAX_HID_RPT_SIZE
}
fn out_rpt_size(&self) -> usize {
MAX_HID_RPT_SIZE
}
fn get_property(&self, _prop_name: &str) -> io::Result<String> {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
}
fn get_device_info(&self) -> U2FDeviceInfo {
// unwrap is okay, as dev_info must have already been set, else
// a programmer error
self.dev_info.clone().unwrap()
}
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
self.dev_info = Some(dev_info);
}
}
impl HIDDevice for Device {
type BuildParameters = String;
type Id = String;
fn new(path: String) -> Result<Self, (HIDError, Self::Id)> {
debug!("Opening device {:?}", path);
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.map_err(|e| (HIDError::IO(Some(path.clone().into()), e), path.clone()))?;
let res = Self {
path,
file,
cid: CID_BROADCAST,
dev_info: None,
secret: None,
authenticator_info: None,
};
if res.is_u2f() {
info!("new device {:?}", res.path);
Ok(res)
} else {
Err((HIDError::DeviceNotSupported, res.path.clone()))
}
}
fn initialized(&self) -> bool {
// During successful init, the broadcast channel id gets repplaced by an actual one
self.cid != CID_BROADCAST
}
fn id(&self) -> Self::Id {
self.path.clone()
}
fn is_u2f(&self) -> bool {
match DeviceCapabilities::new(self.file.as_raw_handle()) {
Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE,
_ => false,
}
}
fn get_shared_secret(&self) -> Option<&ECDHSecret> {
self.secret.as_ref()
}
fn set_shared_secret(&mut self, secret: ECDHSecret) {
self.secret = Some(secret);
}
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
self.authenticator_info.as_ref()
}
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
self.authenticator_info = Some(authenticator_info);
}
/// This is used for cancellation of blocking read()-requests.
/// With this, we can clone the Device, pass it to another thread and call "cancel()" on that.
fn clone_device_as_write_only(&self) -> Result<Self, HIDError> {
let file = OpenOptions::new()
.write(true)
.open(&self.path)
.map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?;
Ok(Self {
path: self.path.clone(),
file,
cid: self.cid,
dev_info: self.dev_info.clone(),
secret: self.secret.clone(),
authenticator_info: self.authenticator_info.clone(),
})
}
}
impl FidoDevice for Device {}

Просмотреть файл

@ -2,35 +2,48 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::platform::winapi::DeviceInfoSet;
use crate::transport::device_selector::DeviceSelectorEvent;
use crate::transport::platform::winapi::DeviceInfoSet;
use runloop::RunLoop;
use std::collections::{HashMap, HashSet};
use std::io;
use std::error::Error;
use std::iter::FromIterator;
use std::sync::Arc;
use std::sync::{mpsc::Sender, Arc};
use std::thread;
use std::time::Duration;
pub struct Monitor<F>
where
F: Fn(String, &dyn Fn() -> bool) + Sync,
F: Fn(String, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ Sync,
{
runloops: HashMap<String, RunLoop>,
new_device_cb: Arc<F>,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
}
impl<F> Monitor<F>
where
F: Fn(String, &dyn Fn() -> bool) + Send + Sync + 'static,
F: Fn(String, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ Send
+ Sync
+ 'static,
{
pub fn new(new_device_cb: F) -> Self {
pub fn new(
new_device_cb: F,
selector_sender: Sender<DeviceSelectorEvent>,
status_sender: Sender<crate::StatusUpdate>,
) -> Self {
Self {
runloops: HashMap::new(),
new_device_cb: Arc::new(new_device_cb),
selector_sender,
status_sender,
}
}
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
let mut stored = HashSet::new();
while alive() {
@ -42,9 +55,12 @@ where
self.remove_device(path);
}
let paths: Vec<_> = devices.difference(&stored).cloned().collect();
self.selector_sender
.send(DeviceSelectorEvent::DevicesAdded(paths.clone()))?;
// Add devices that were plugged in.
for path in devices.difference(&stored) {
self.add_device(path);
for path in paths {
self.add_device(&path);
}
// Remember the new set.
@ -64,10 +80,13 @@ where
let f = self.new_device_cb.clone();
let path = path.clone();
let key = path.clone();
let selector_sender = self.selector_sender.clone();
let status_sender = self.status_sender.clone();
debug!("Adding device {}", path);
let runloop = RunLoop::new(move |alive| {
if alive() {
f(path, alive);
f(path, selector_sender, status_sender, alive);
}
});
@ -77,6 +96,11 @@ where
}
fn remove_device(&mut self, path: &String) {
let _ = self
.selector_sender
.send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
debug!("Removing device {}", path);
if let Some(runloop) = self.runloops.remove(path) {
runloop.cancel();
}

Просмотреть файл

@ -3,30 +3,45 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::errors;
use crate::platform::monitor::Monitor;
use crate::statecallback::StateCallback;
use crate::transport::device_selector::{
DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
};
use crate::transport::platform::monitor::Monitor;
use runloop::RunLoop;
use std::ffi::OsString;
use std::sync::mpsc::Sender;
pub struct Transaction {
// Handle to the thread loop.
thread: Option<RunLoop>,
thread: RunLoop,
device_selector: DeviceSelector,
}
impl Transaction {
pub fn new<F, T>(
timeout: u64,
callback: StateCallback<crate::Result<T>>,
status: Sender<crate::StatusUpdate>,
new_device_cb: F,
) -> crate::Result<Self>
where
F: Fn(OsString, &dyn Fn() -> bool) + Sync + Send + 'static,
F: Fn(
DeviceBuildParameters,
Sender<DeviceSelectorEvent>,
Sender<crate::StatusUpdate>,
&dyn Fn() -> bool,
) + Sync
+ Send
+ 'static,
T: 'static,
{
let status_sender = status.clone();
let device_selector = DeviceSelector::run(status);
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
// Create a new device monitor.
let mut monitor = Monitor::new(new_device_cb);
let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender);
// Start polling for new devices.
try_or!(monitor.run(alive), |_| callback
@ -42,12 +57,14 @@ impl Transaction {
.map_err(|_| errors::AuthenticatorError::Platform)?;
Ok(Self {
thread: Some(thread),
thread,
device_selector,
})
}
pub fn cancel(&mut self) {
// This must never be None.
self.thread.take().unwrap().cancel();
info!("Transaction was cancelled.");
self.device_selector.stop();
self.thread.cancel();
}
}

Просмотреть файл

@ -15,9 +15,9 @@ use crate::util::io_err;
extern crate libc;
extern crate winapi;
use crate::platform::winapi::winapi::shared::{guiddef, minwindef, ntdef, windef};
use crate::platform::winapi::winapi::shared::{hidclass, hidpi, hidusage};
use crate::platform::winapi::winapi::um::{handleapi, setupapi};
use winapi::shared::{guiddef, minwindef, ntdef, windef};
use winapi::shared::{hidclass, hidpi, hidusage};
use winapi::um::{handleapi, setupapi};
#[link(name = "setupapi")]
extern "system" {
@ -63,12 +63,6 @@ extern "system" {
) -> ntdef::NTSTATUS;
}
macro_rules! offset_of {
($ty:ty, $field:ident) => {
unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
};
}
fn from_wide_ptr(ptr: *const u16, len: usize) -> String {
assert!(!ptr.is_null() && len % 2 == 0);
let slice = unsafe { slice::from_raw_parts(ptr, len / 2) };
@ -214,7 +208,7 @@ impl DeviceInterfaceDetailData {
unsafe { (*data).cbSize = cb_size as minwindef::UINT };
// Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`.
let offset = offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
let offset = memoffset::offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
Some(Self {
data,

Просмотреть файл

@ -30,11 +30,16 @@ const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2;
const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
const uint8_t CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL = 8;
const uint8_t U2F_OK = 0;
const uint8_t U2F_ERROR_UKNOWN = 1;
const uint8_t U2F_ERROR_NOT_SUPPORTED = 2;
const uint8_t U2F_ERROR_INVALID_STATE = 3;
const uint8_t U2F_ERROR_CONSTRAINT = 4;
const uint8_t U2F_ERROR_NOT_ALLOWED = 5;
const uint8_t CTAP_ERROR_PIN_REQUIRED = 6;
const uint8_t CTAP_ERROR_PIN_INVALID = 7;
const uint8_t CTAP_ERROR_PIN_AUTH_BLOCKED = 8;
const uint8_t CTAP_ERROR_PIN_BLOCKED = 9;
// NOTE: Preconditions
// * All rust_u2f_mgr* pointers must refer to pointers which are returned
@ -45,7 +50,8 @@ const uint8_t U2F_ERROR_NOT_ALLOWED = 5;
// register() and sign() callbacks. They can be null on failure.
// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
struct rust_u2f_manager;
// TODO(MS): Once CTAP2 support is added, this should probably be renamed.
struct rust_ctap_manager;
// The `rust_u2f_app_ids` opaque type is equivalent to the rust type `U2FAppIds`
struct rust_u2f_app_ids;
@ -62,10 +68,10 @@ typedef void (*rust_u2f_callback)(uint64_t, rust_u2f_result*);
/// U2FManager functions.
rust_u2f_manager* rust_u2f_mgr_new();
/* unsafe */ void rust_u2f_mgr_free(rust_u2f_manager* mgr);
rust_ctap_manager* rust_u2f_mgr_new();
/* unsafe */ void rust_u2f_mgr_free(rust_ctap_manager* mgr);
uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr, uint64_t flags,
uint64_t rust_u2f_mgr_register(rust_ctap_manager* mgr, uint64_t flags,
uint64_t timeout, rust_u2f_callback,
const uint8_t* challenge_ptr,
size_t challenge_len,
@ -73,13 +79,13 @@ uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr, uint64_t flags,
size_t application_len,
const rust_u2f_key_handles* khs);
uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr, uint64_t flags,
uint64_t rust_u2f_mgr_sign(rust_ctap_manager* mgr, uint64_t flags,
uint64_t timeout, rust_u2f_callback,
const uint8_t* challenge_ptr, size_t challenge_len,
const rust_u2f_app_ids* app_ids,
const rust_u2f_key_handles* khs);
void rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
void rust_u2f_mgr_cancel(rust_ctap_manager* mgr);
/// U2FAppIds functions.
@ -103,9 +109,11 @@ uint8_t rust_u2f_result_error(const rust_u2f_result* res);
// Call this before `[..]_copy()` to allocate enough space.
bool rust_u2f_resbuf_length(const rust_u2f_result* res, uint8_t bid,
size_t* len);
bool rust_u2f_resbuf_contains(const rust_u2f_result* res, uint8_t bid);
bool rust_u2f_resbuf_copy(const rust_u2f_result* res, uint8_t bid,
uint8_t* dst);
/* unsafe */ void rust_u2f_res_free(rust_u2f_result* res);
}
#endif // __U2FHID_CAPI

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше