mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
Bug 1827748 - vendor authenticator-rs v0.4.0-alpha.12. r=keeler,glandium,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D175294
This commit is contained in:
parent
74407531f5
commit
20bcb2f55f
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -320,9 +320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.11"
|
||||
version = "0.4.0-alpha.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e73e25e6ae754b553f930c48af8f0e5ca71e641c419151e95bafd5644ff6e21b"
|
||||
checksum = "c14dd8025f8c43525d3bcb5fba75838095ddfe99f2284a72cd1b2a9380c47ae2"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitflags 1.3.2",
|
||||
@ -332,7 +332,7 @@ dependencies = [
|
||||
"libc",
|
||||
"libudev",
|
||||
"log",
|
||||
"memoffset 0.6.99",
|
||||
"memoffset",
|
||||
"nom",
|
||||
"nss-gk-api",
|
||||
"pkcs11-bindings",
|
||||
@ -973,7 +973,7 @@ dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
"memoffset 0.8.0",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
@ -3145,13 +3145,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.99"
|
||||
dependencies = [
|
||||
"memoffset 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.8.0"
|
||||
@ -3246,7 +3239,7 @@ dependencies = [
|
||||
"libc",
|
||||
"mach2",
|
||||
"memmap2",
|
||||
"memoffset 0.8.0",
|
||||
"memoffset",
|
||||
"minidump-common",
|
||||
"nix 0.26.2",
|
||||
"scroll",
|
||||
|
@ -121,9 +121,6 @@ wasi = { path = "build/rust/wasi" }
|
||||
# Patch bindgen 0.63 to 0.64
|
||||
bindgen = { path = "build/rust/bindgen" }
|
||||
|
||||
# Patch memoffset 0.6 to 0.8
|
||||
memoffset = { path = "build/rust/memoffset" }
|
||||
|
||||
# Patch ntapi 0.3 to 0.4
|
||||
ntapi = { path = "build/rust/ntapi" }
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "memoffset"
|
||||
version = "0.6.99"
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.memoffset]
|
||||
version = "0.8"
|
@ -1,5 +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/. */
|
||||
|
||||
pub use memoffset::*;
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
authors = ["Martin Sirringhaus", "John Schanck"]
|
||||
|
||||
[dependencies]
|
||||
authenticator = { version = "0.4.0-alpha.11", features = ["gecko"] }
|
||||
authenticator = { version = "0.4.0-alpha.12", features = ["gecko"] }
|
||||
log = "0.4"
|
||||
moz_task = { path = "../../../xpcom/rust/moz_task" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
|
@ -19,7 +19,7 @@ use authenticator::{
|
||||
},
|
||||
errors::{AuthenticatorError, PinError, U2FTokenError},
|
||||
statecallback::StateCallback,
|
||||
Assertion, Pin, RegisterResult, SignResult, StatusUpdate,
|
||||
Assertion, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use moz_task::RunnableBuilder;
|
||||
use nserror::{
|
||||
@ -78,20 +78,13 @@ fn make_pin_required_prompt(
|
||||
|
||||
fn authrs_to_nserror(e: &AuthenticatorError) -> nsresult {
|
||||
match e {
|
||||
AuthenticatorError::U2FToken(U2FTokenError::Unknown) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::NotSupported) => NS_ERROR_DOM_NOT_SUPPORTED_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::InvalidState) => NS_ERROR_DOM_INVALID_STATE_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::ConstraintError) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::U2FToken(U2FTokenError::NotAllowed) => NS_ERROR_DOM_NOT_ALLOWED_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinRequired) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinIsTooShort) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinIsTooLong(_)) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::InvalidKeyLen) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::InvalidPin(_)) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinAuthBlocked) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinBlocked) => NS_ERROR_DOM_OPERATION_ERR,
|
||||
AuthenticatorError::PinError(PinError::PinNotSet) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
AuthenticatorError::PinError(PinError::Crypto(_)) => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
_ => NS_ERROR_DOM_UNKNOWN_ERR,
|
||||
}
|
||||
}
|
||||
@ -358,50 +351,46 @@ fn status_callback(
|
||||
};
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinError(error, sender)) => match error {
|
||||
PinError::PinRequired => {
|
||||
let guard = pin_receiver.lock();
|
||||
if let Ok(mut entry) = guard {
|
||||
entry.replace((tid, sender));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let notification_str =
|
||||
make_pin_required_prompt(tid, origin, browsing_context_id, false, -1);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
continue;
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
||||
let guard = pin_receiver.lock();
|
||||
if let Ok(mut entry) = guard {
|
||||
entry.replace((tid, sender));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
PinError::InvalidPin(attempts) => {
|
||||
let guard = pin_receiver.lock();
|
||||
if let Ok(mut entry) = guard {
|
||||
entry.replace((tid, sender));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let notification_str = make_pin_required_prompt(
|
||||
tid,
|
||||
origin,
|
||||
browsing_context_id,
|
||||
true,
|
||||
attempts.map_or(-1, |x| x as i64),
|
||||
);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
continue;
|
||||
let notification_str =
|
||||
make_pin_required_prompt(tid, origin, browsing_context_id, false, -1);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
||||
let guard = pin_receiver.lock();
|
||||
if let Ok(mut entry) = guard {
|
||||
entry.replace((tid, sender));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
PinError::PinAuthBlocked => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("pin-auth-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
PinError::PinBlocked => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("device-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
e => {
|
||||
warn!("Unexpected error: {:?}", e)
|
||||
}
|
||||
},
|
||||
let notification_str = make_pin_required_prompt(
|
||||
tid,
|
||||
origin,
|
||||
browsing_context_id,
|
||||
true,
|
||||
attempts.map_or(-1, |x| x as i64),
|
||||
);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("pin-auth-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
||||
let notification_str =
|
||||
make_pin_error_prompt("device-blocked", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
warn!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Err(RecvError) => {
|
||||
debug!("STATUS: end");
|
||||
return;
|
||||
|
@ -266,7 +266,7 @@ delta = "0.1.8 -> 0.1.9"
|
||||
[[audits.authenticator]]
|
||||
who = "John M. Schanck <jschanck@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
version = "0.4.0-alpha.11"
|
||||
version = "0.4.0-alpha.12"
|
||||
notes = "Maintained by the CryptoEng team at Mozilla."
|
||||
|
||||
[[audits.autocfg]]
|
||||
|
File diff suppressed because one or more lines are too long
58
third_party/rust/authenticator/Cargo.lock
generated
vendored
58
third_party/rust/authenticator/Cargo.lock
generated
vendored
@ -39,7 +39,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.11"
|
||||
version = "0.4.0-alpha.12"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64",
|
||||
@ -176,9 +176,9 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.76"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
|
||||
checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
@ -544,9 +544,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.20"
|
||||
version = "0.14.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
dependencies = [
|
||||
"bytes 1.2.1",
|
||||
"futures-channel",
|
||||
@ -615,15 +615,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.136"
|
||||
version = "0.2.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
@ -666,9 +666,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@ -768,9 +768,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
@ -778,9 +778,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.15.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
@ -876,15 +876,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
@ -951,9 +951,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -962,9 +962,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
@ -1020,9 +1020,9 @@ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@ -1088,9 +1088,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
||||
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@ -1248,9 +1248,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
4
third_party/rust/authenticator/Cargo.toml
vendored
4
third_party/rust/authenticator/Cargo.toml
vendored
@ -12,7 +12,7 @@
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.11"
|
||||
version = "0.4.0-alpha.12"
|
||||
authors = [
|
||||
"J.C. Jones <jc@mozilla.com>",
|
||||
"Tim Taubert <ttaubert@mozilla.com>",
|
||||
@ -154,7 +154,7 @@ version = "^0.2"
|
||||
version = "0.9"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.memoffset]
|
||||
version = "0.6"
|
||||
version = "0.8"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.winapi]
|
||||
version = "^0.3"
|
||||
|
@ -116,9 +116,10 @@ fn main() {
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PinError(..))
|
||||
| Ok(StatusUpdate::SelectDeviceNotice)
|
||||
| Ok(StatusUpdate::DeviceSelected(..)) => {
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(..)) | Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
panic!("STATUS: This can't happen for CTAP1!");
|
||||
}
|
||||
Err(RecvError) => {
|
||||
|
87
third_party/rust/authenticator/examples/ctap2.rs
vendored
87
third_party/rust/authenticator/examples/ctap2.rs
vendored
@ -11,9 +11,8 @@ use authenticator::{
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
|
||||
},
|
||||
errors::PinError,
|
||||
statecallback::StateCallback,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusUpdate,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use sha2::{Digest, Sha256};
|
||||
@ -100,35 +99,46 @@ fn main() {
|
||||
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("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ")
|
||||
.expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
||||
println!(
|
||||
"Wrong PIN! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
||||
panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
||||
panic!("Too many failed attempts. Your device has been blocked. Reset it.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
|
||||
println!(
|
||||
"Wrong UV! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
|
||||
println!("Too many failed UV-attempts.");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
panic!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Err(RecvError) => {
|
||||
println!("STATUS: end");
|
||||
return;
|
||||
@ -191,12 +201,8 @@ fn main() {
|
||||
register_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
if let Err(e) = manager.register(
|
||||
timeout_ms,
|
||||
ctap_args.into(),
|
||||
status_tx.clone(),
|
||||
callback,
|
||||
) {
|
||||
if let Err(e) = manager.register(timeout_ms, ctap_args.into(), status_tx.clone(), callback)
|
||||
{
|
||||
panic!("Couldn't register: {:?}", e);
|
||||
};
|
||||
|
||||
@ -264,12 +270,7 @@ fn main() {
|
||||
sign_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
if let Err(e) = manager.sign(
|
||||
timeout_ms,
|
||||
ctap_args.into(),
|
||||
status_tx,
|
||||
callback,
|
||||
) {
|
||||
if let Err(e) = manager.sign(timeout_ms, ctap_args.into(), status_tx, callback) {
|
||||
panic!("Couldn't sign: {:?}", e);
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,8 @@ use authenticator::{
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
|
||||
},
|
||||
errors::PinError,
|
||||
statecallback::StateCallback,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusUpdate,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use sha2::{Digest, Sha256};
|
||||
@ -32,9 +31,7 @@ fn print_usage(program: &str, opts: Options) {
|
||||
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!("Asking a security key to register now with user: {username}");
|
||||
println!("*********************************************************************");
|
||||
|
||||
println!("Asking a security key to register now...");
|
||||
@ -70,35 +67,46 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
||||
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("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ")
|
||||
.expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
||||
println!(
|
||||
"Wrong PIN! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
||||
panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
||||
panic!("Too many failed attempts. Your device has been blocked. Reset it.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
|
||||
println!(
|
||||
"Wrong UV! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
|
||||
println!("Too many failed UV-attempts.");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
panic!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Err(RecvError) => {
|
||||
println!("STATUS: end");
|
||||
return;
|
||||
@ -150,12 +158,7 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
||||
register_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
if let Err(e) = manager.register(
|
||||
timeout_ms,
|
||||
ctap_args.into(),
|
||||
status_tx,
|
||||
callback,
|
||||
) {
|
||||
if let Err(e) = manager.register(timeout_ms, ctap_args.into(), status_tx, callback) {
|
||||
panic!("Couldn't register: {:?}", e);
|
||||
};
|
||||
|
||||
@ -258,35 +261,46 @@ fn main() {
|
||||
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("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ")
|
||||
.expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
||||
println!(
|
||||
"Wrong PIN! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
||||
panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
||||
panic!("Too many failed attempts. Your device has been blocked. Reset it.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
|
||||
println!(
|
||||
"Wrong UV! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
|
||||
println!("Too many failed UV-attempts.");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
panic!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Err(RecvError) => {
|
||||
println!("STATUS: end");
|
||||
return;
|
||||
@ -315,12 +329,7 @@ fn main() {
|
||||
sign_tx.send(rv).unwrap();
|
||||
}));
|
||||
|
||||
if let Err(e) = manager.sign(
|
||||
timeout_ms,
|
||||
ctap_args.into(),
|
||||
status_tx,
|
||||
callback,
|
||||
) {
|
||||
if let Err(e) = manager.sign(timeout_ms, ctap_args.into(), status_tx, callback) {
|
||||
panic!("Couldn't sign: {:?}", e);
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ fn main() {
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PinError(..))
|
||||
Ok(StatusUpdate::PinUvError(..))
|
||||
| Ok(StatusUpdate::SelectDeviceNotice)
|
||||
| Ok(StatusUpdate::DeviceSelected(..)) => {
|
||||
panic!("STATUS: This can't happen for CTAP1!");
|
||||
|
@ -109,7 +109,7 @@ fn main() {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
break;
|
||||
}
|
||||
Ok(StatusUpdate::PinError(..)) => panic!("Reset should never ask for a PIN!"),
|
||||
Ok(StatusUpdate::PinUvError(..)) => panic!("Reset should never ask for a PIN!"),
|
||||
Ok(_) => { /* Ignore all other updates */ }
|
||||
Err(RecvError) => {
|
||||
println!("RecvError");
|
||||
|
@ -5,7 +5,7 @@
|
||||
use authenticator::{
|
||||
authenticatorservice::{AuthenticatorService, CtapVersion},
|
||||
statecallback::StateCallback,
|
||||
Pin, PinError, StatusUpdate,
|
||||
Pin, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use std::sync::mpsc::{channel, RecvError};
|
||||
@ -85,35 +85,46 @@ fn main() {
|
||||
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("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
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)
|
||||
}
|
||||
},
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
||||
println!(
|
||||
"Wrong PIN! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
||||
panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
||||
panic!("Too many failed attempts. Your device has been blocked. Reset it.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
|
||||
println!(
|
||||
"Wrong UV! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
|
||||
println!("Too many failed UV-attempts.");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
panic!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Err(RecvError) => {
|
||||
println!("STATUS: end");
|
||||
return;
|
||||
|
@ -10,9 +10,9 @@ use authenticator::{
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
|
||||
},
|
||||
errors::{AuthenticatorError, CommandError, HIDError, PinError},
|
||||
errors::{AuthenticatorError, CommandError, HIDError},
|
||||
statecallback::StateCallback,
|
||||
COSEAlgorithm, Pin, RegisterResult, StatusUpdate,
|
||||
COSEAlgorithm, Pin, RegisterResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
|
||||
use getopts::Options;
|
||||
@ -96,35 +96,46 @@ fn main() {
|
||||
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("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin = rpassword::prompt_password_stderr("Enter PIN: ")
|
||||
.expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
|
||||
println!(
|
||||
"Wrong PIN! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
let raw_pin =
|
||||
rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
|
||||
sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
|
||||
panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
|
||||
panic!("Too many failed attempts. Your device has been blocked. Reset it.")
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
|
||||
println!(
|
||||
"Wrong UV! {}",
|
||||
attempts.map_or("Try again.".to_string(), |a| format!(
|
||||
"You have {a} attempts left."
|
||||
))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
|
||||
println!("Too many failed UV-attempts.");
|
||||
continue;
|
||||
}
|
||||
Ok(StatusUpdate::PinUvError(e)) => {
|
||||
panic!("Unexpected error: {:?}", e)
|
||||
}
|
||||
Err(RecvError) => {
|
||||
println!("STATUS: end");
|
||||
return;
|
||||
|
17
third_party/rust/authenticator/src/crypto/mod.rs
vendored
17
third_party/rust/authenticator/src/crypto/mod.rs
vendored
@ -2,6 +2,7 @@
|
||||
* 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::ctap2::commands::client_pin::PinUvAuthTokenPermission;
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::{ctap2::commands::CommandError, transport::errors::HIDError};
|
||||
@ -314,11 +315,16 @@ impl SharedSecret {
|
||||
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
self.pin_protocol.0.decrypt(&self.key, ciphertext)
|
||||
}
|
||||
pub fn decrypt_pin_token(&self, encrypted_pin_token: &[u8]) -> Result<PinToken, CryptoError> {
|
||||
pub fn decrypt_pin_token(
|
||||
&self,
|
||||
permissions: PinUvAuthTokenPermission,
|
||||
encrypted_pin_token: &[u8],
|
||||
) -> Result<PinUvAuthToken, CryptoError> {
|
||||
let pin_token = self.decrypt(encrypted_pin_token)?;
|
||||
Ok(PinToken {
|
||||
Ok(PinUvAuthToken {
|
||||
pin_protocol: self.pin_protocol.clone(),
|
||||
pin_token,
|
||||
permissions,
|
||||
})
|
||||
}
|
||||
pub fn authenticate(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
@ -333,13 +339,14 @@ impl SharedSecret {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PinToken {
|
||||
pub struct PinUvAuthToken {
|
||||
pub pin_protocol: PinUvAuthProtocol,
|
||||
pin_token: Vec<u8>,
|
||||
// TODO(jms): add permissions
|
||||
#[allow(dead_code)] // Not yet used
|
||||
permissions: PinUvAuthTokenPermission,
|
||||
}
|
||||
|
||||
impl PinToken {
|
||||
impl PinUvAuthToken {
|
||||
pub fn derive(&self, message: &[u8]) -> Result<PinUvAuthParam, CryptoError> {
|
||||
let pin_auth = self.pin_protocol.0.authenticate(&self.pin_token, message)?;
|
||||
Ok(PinUvAuthParam {
|
||||
|
@ -1,5 +1,9 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
// Note: Needed for PinUvAuthTokenPermission
|
||||
// The current version of `bitflags` doesn't seem to allow
|
||||
// to set this for an individual bitflag-struct.
|
||||
use super::{get_info::AuthenticatorInfo, Command, CommandError, RequestCtap2, StatusCode};
|
||||
use crate::crypto::{COSEKey, CryptoError, PinToken, PinUvAuthProtocol, SharedSecret};
|
||||
use crate::crypto::{COSEKey, CryptoError, PinUvAuthProtocol, PinUvAuthToken, SharedSecret};
|
||||
use crate::transport::errors::HIDError;
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde::{
|
||||
@ -19,25 +23,34 @@ use std::fmt;
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum PINSubcommand {
|
||||
GetRetries = 0x01,
|
||||
GetPinRetries = 0x01,
|
||||
GetKeyAgreement = 0x02,
|
||||
SetPIN = 0x03,
|
||||
ChangePIN = 0x04,
|
||||
GetPINToken = 0x05, // superseded by GetPinUvAuth*
|
||||
GetPinUvAuthTokenUsingUvWithPermissions = 0x06,
|
||||
GetUVRetries = 0x07,
|
||||
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
|
||||
bitflags! {
|
||||
pub struct PinUvAuthTokenPermission: u8 {
|
||||
const MakeCredential = 0x01; // rp_id required
|
||||
const GetAssertion = 0x02; // rp_id required
|
||||
const CredentialManagement = 0x04; // rp_id optional
|
||||
const BioEnrollment = 0x08; // rp_id ignored
|
||||
const LargeBlobWrite = 0x10; // rp_id ignored
|
||||
const AuthenticatorConfiguration = 0x20; // rp_id ignored
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PinUvAuthTokenPermission {
|
||||
fn default() -> Self {
|
||||
// CTAP 2.1 spec:
|
||||
// If authenticatorClientPIN's getPinToken subcommand is invoked, default permissions
|
||||
// of `mc` and `ga` (value 0x03) are granted for the returned pinUvAuthToken.
|
||||
PinUvAuthTokenPermission::MakeCredential | PinUvAuthTokenPermission::GetAssertion
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -56,7 +69,7 @@ impl Default for ClientPIN {
|
||||
fn default() -> Self {
|
||||
ClientPIN {
|
||||
pin_protocol: None,
|
||||
subcommand: PINSubcommand::GetRetries,
|
||||
subcommand: PINSubcommand::GetPinRetries,
|
||||
key_agreement: None,
|
||||
pin_auth: None,
|
||||
new_pin_enc: None,
|
||||
@ -222,10 +235,8 @@ pub struct GetKeyAgreement {
|
||||
}
|
||||
|
||||
impl GetKeyAgreement {
|
||||
pub fn new(info: &AuthenticatorInfo) -> Result<Self, CommandError> {
|
||||
Ok(GetKeyAgreement {
|
||||
pin_protocol: PinUvAuthProtocol::try_from(info)?,
|
||||
})
|
||||
pub fn new(pin_protocol: PinUvAuthProtocol) -> Self {
|
||||
GetKeyAgreement { pin_protocol }
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +283,7 @@ impl<'sc, 'pin> GetPinToken<'sc, 'pin> {
|
||||
}
|
||||
|
||||
impl<'sc, 'pin> ClientPINSubCommand for GetPinToken<'sc, 'pin> {
|
||||
type Output = PinToken;
|
||||
type Output = PinUvAuthToken;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
let input = self.pin.for_pin_token();
|
||||
@ -297,9 +308,13 @@ impl<'sc, 'pin> ClientPINSubCommand for GetPinToken<'sc, 'pin> {
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
match get_pin_response.pin_token {
|
||||
Some(encrypted_pin_token) => {
|
||||
// CTAP 2.1 spec:
|
||||
// If authenticatorClientPIN's getPinToken subcommand is invoked, default permissions
|
||||
// of `mc` and `ga` (value 0x03) are granted for the returned pinUvAuthToken.
|
||||
let default_permissions = PinUvAuthTokenPermission::default();
|
||||
let pin_token = self
|
||||
.shared_secret
|
||||
.decrypt_pin_token(encrypted_pin_token.as_ref())?;
|
||||
.decrypt_pin_token(default_permissions, encrypted_pin_token.as_ref())?;
|
||||
Ok(pin_token)
|
||||
}
|
||||
None => Err(CommandError::MissingRequiredField("key_agreement")),
|
||||
@ -332,7 +347,7 @@ impl<'sc, 'pin> GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
|
||||
}
|
||||
|
||||
impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
|
||||
type Output = PinToken;
|
||||
type Output = PinUvAuthToken;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
let input = self.pin.for_pin_token();
|
||||
@ -343,7 +358,103 @@ impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions
|
||||
subcommand: PINSubcommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
||||
key_agreement: Some(self.shared_secret.client_input().clone()),
|
||||
pin_hash_enc: Some(ByteBuf::from(pin_hash_enc)),
|
||||
permissions: Some(self.permissions as u8),
|
||||
permissions: Some(self.permissions.bits()),
|
||||
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!(
|
||||
"GetPinUvAuthTokenUsingPinWithPermissions::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 = self
|
||||
.shared_secret
|
||||
.decrypt_pin_token(self.permissions, encrypted_pin_token.as_ref())?;
|
||||
Ok(pin_token)
|
||||
}
|
||||
None => Err(CommandError::MissingRequiredField("key_agreement")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! implementRetries {
|
||||
($name:ident, $getter:ident) => {
|
||||
#[derive(Debug)]
|
||||
pub struct $name {}
|
||||
|
||||
impl $name {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPINSubCommand for $name {
|
||||
type Output = u8;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
Ok(ClientPIN {
|
||||
subcommand: PINSubcommand::$name,
|
||||
..ClientPIN::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
|
||||
let value: Value = from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
debug!("{}::parse_response_payload {:?}", stringify!($name), value);
|
||||
|
||||
let get_pin_response: ClientPinResponse =
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
match get_pin_response.$getter {
|
||||
Some($getter) => Ok($getter),
|
||||
None => Err(CommandError::MissingRequiredField(stringify!($getter))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
implementRetries!(GetPinRetries, pin_retries);
|
||||
implementRetries!(GetUvRetries, uv_retries);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetPinUvAuthTokenUsingUvWithPermissions<'sc> {
|
||||
shared_secret: &'sc SharedSecret,
|
||||
permissions: PinUvAuthTokenPermission,
|
||||
rp_id: Option<String>,
|
||||
}
|
||||
|
||||
impl<'sc> GetPinUvAuthTokenUsingUvWithPermissions<'sc> {
|
||||
pub fn new(
|
||||
shared_secret: &'sc SharedSecret,
|
||||
permissions: PinUvAuthTokenPermission,
|
||||
rp_id: Option<String>,
|
||||
) -> Self {
|
||||
GetPinUvAuthTokenUsingUvWithPermissions {
|
||||
shared_secret,
|
||||
permissions,
|
||||
rp_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'sc> ClientPINSubCommand for GetPinUvAuthTokenUsingUvWithPermissions<'sc> {
|
||||
type Output = PinUvAuthToken;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
Ok(ClientPIN {
|
||||
pin_protocol: Some(self.shared_secret.pin_protocol.clone()),
|
||||
subcommand: PINSubcommand::GetPinUvAuthTokenUsingUvWithPermissions,
|
||||
key_agreement: Some(self.shared_secret.client_input().clone()),
|
||||
permissions: Some(self.permissions.bits()),
|
||||
rp_id: self.rp_id.clone(), /* TODO: This could probably be done less wasteful with
|
||||
* &str all the way */
|
||||
..ClientPIN::default()
|
||||
@ -360,7 +471,7 @@ impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions
|
||||
Some(encrypted_pin_token) => {
|
||||
let pin_token = self
|
||||
.shared_secret
|
||||
.decrypt_pin_token(encrypted_pin_token.as_ref())?;
|
||||
.decrypt_pin_token(self.permissions, encrypted_pin_token.as_ref())?;
|
||||
Ok(pin_token)
|
||||
}
|
||||
None => Err(CommandError::MissingRequiredField("key_agreement")),
|
||||
@ -368,38 +479,6 @@ impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions
|
||||
}
|
||||
}
|
||||
|
||||
#[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> {
|
||||
shared_secret: &'sc SharedSecret,
|
||||
@ -641,38 +720,45 @@ pub enum PinError {
|
||||
PinRequired,
|
||||
PinIsTooShort,
|
||||
PinIsTooLong(usize),
|
||||
InvalidKeyLen,
|
||||
InvalidPin(Option<u8>),
|
||||
InvalidUv(Option<u8>),
|
||||
PinAuthBlocked,
|
||||
PinBlocked,
|
||||
PinNotSet,
|
||||
UvBlocked,
|
||||
/// Used for CTAP2.0 UV (fingerprints)
|
||||
PinAuthInvalid,
|
||||
Crypto(CryptoError),
|
||||
}
|
||||
|
||||
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::PinRequired => write!(f, "Pin required."),
|
||||
PinError::PinIsTooShort => write!(f, "pin is too short"),
|
||||
PinError::PinIsTooLong(len) => write!(f, "pin is too long ({len})"),
|
||||
PinError::InvalidPin(ref e) => {
|
||||
let mut res = write!(f, "PinError: Invalid Pin.");
|
||||
let mut res = write!(f, "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::PinNotSet => write!(f, "PinError: Pin needed but not set on device."),
|
||||
PinError::Crypto(ref e) => write!(f, "PinError: Crypto backend error: {e:?}"),
|
||||
PinError::InvalidUv(ref e) => {
|
||||
let mut res = write!(f, "Invalid Uv.");
|
||||
if let Some(uv_retries) = e {
|
||||
res = write!(f, " Retries left: {uv_retries:?}")
|
||||
}
|
||||
res
|
||||
}
|
||||
PinError::PinAuthBlocked => {
|
||||
write!(f, "Pin authentication blocked. Device needs power cycle.")
|
||||
}
|
||||
PinError::PinBlocked => write!(f, "No retries left. Pin blocked. Device needs reset."),
|
||||
PinError::PinNotSet => write!(f, "Pin needed but not set on device."),
|
||||
PinError::UvBlocked => write!(f, "No retries left. Uv blocked. Device needs reset."),
|
||||
PinError::PinAuthInvalid => write!(f, "PinAuth invalid."),
|
||||
PinError::Crypto(ref e) => write!(f, "Crypto backend error: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use super::{
|
||||
Command, CommandError, PinAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable,
|
||||
Command, CommandError, PinUvAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable,
|
||||
StatusCode,
|
||||
};
|
||||
use crate::consts::{
|
||||
PARAMETER_SIZE, U2F_AUTHENTICATE, U2F_CHECK_IS_REGISTERED, U2F_REQUEST_USER_PRESENCE,
|
||||
};
|
||||
use crate::crypto::{COSEKey, CryptoError, PinUvAuthParam, SharedSecret};
|
||||
use crate::crypto::{COSEKey, CryptoError, PinUvAuthParam, PinUvAuthToken, SharedSecret};
|
||||
use crate::ctap2::attestation::{AuthenticatorData, AuthenticatorDataFlags};
|
||||
use crate::ctap2::client_data::{ClientDataHash, CollectedClientData, CollectedClientDataWrapper};
|
||||
use crate::ctap2::commands::client_pin::Pin;
|
||||
@ -176,7 +176,7 @@ pub struct GetAssertion {
|
||||
pub(crate) extensions: GetAssertionExtensions,
|
||||
pub(crate) options: GetAssertionOptions,
|
||||
pub(crate) pin: Option<Pin>,
|
||||
pub(crate) pin_auth: Option<PinUvAuthParam>,
|
||||
pub(crate) pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
|
||||
// This is used to implement the FIDO AppID extension.
|
||||
pub(crate) alternate_rp_id: Option<String>,
|
||||
@ -200,13 +200,13 @@ impl GetAssertion {
|
||||
extensions,
|
||||
options,
|
||||
pin,
|
||||
pin_auth: None,
|
||||
pin_uv_auth_param: None,
|
||||
alternate_rp_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PinAuthCommand for GetAssertion {
|
||||
impl PinUvAuthCommand for GetAssertion {
|
||||
fn pin(&self) -> &Option<Pin> {
|
||||
&self.pin
|
||||
}
|
||||
@ -215,20 +215,47 @@ impl PinAuthCommand for GetAssertion {
|
||||
self.pin = pin;
|
||||
}
|
||||
|
||||
fn pin_auth(&self) -> &Option<PinUvAuthParam> {
|
||||
&self.pin_auth
|
||||
}
|
||||
|
||||
fn set_pin_auth(&mut self, pin_auth: Option<PinUvAuthParam>) {
|
||||
self.pin_auth = pin_auth;
|
||||
fn set_pin_uv_auth_param(
|
||||
&mut self,
|
||||
pin_uv_auth_token: Option<PinUvAuthToken>,
|
||||
) -> Result<(), AuthenticatorError> {
|
||||
let mut param = None;
|
||||
if let Some(token) = pin_uv_auth_token {
|
||||
param = Some(
|
||||
token
|
||||
.derive(self.client_data_hash().as_ref())
|
||||
.map_err(CommandError::Crypto)?,
|
||||
);
|
||||
}
|
||||
self.pin_uv_auth_param = param;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn client_data_hash(&self) -> ClientDataHash {
|
||||
self.client_data_wrapper.hash()
|
||||
}
|
||||
|
||||
fn unset_uv_option(&mut self) {
|
||||
self.options.user_verification = None;
|
||||
fn set_uv_option(&mut self, uv: Option<bool>) {
|
||||
self.options.user_verification = uv;
|
||||
}
|
||||
|
||||
fn get_uv_option(&mut self) -> Option<bool> {
|
||||
self.options.user_verification
|
||||
}
|
||||
|
||||
fn get_rp_id(&self) -> Option<&String> {
|
||||
match &self.rp {
|
||||
// CTAP1 case: We only have the hash, not the entire RpID
|
||||
RelyingPartyWrapper::Hash(..) => None,
|
||||
RelyingPartyWrapper::Data(r) => Some(&r.id),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_discouraged_uv_option(&mut self) {
|
||||
// "[..] the Relying Party does not wish to require user verification (e.g., by setting options.userVerification
|
||||
// to "discouraged" in the WebAuthn API), the platform invokes the authenticatorGetAssertion operation using
|
||||
// the marshalled input parameters along with an absent "uv" option key."
|
||||
self.set_uv_option(None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +276,7 @@ impl Serialize for GetAssertion {
|
||||
if self.options.has_some() {
|
||||
map_len += 1;
|
||||
}
|
||||
if self.pin_auth.is_some() {
|
||||
if self.pin_uv_auth_param.is_some() {
|
||||
map_len += 2;
|
||||
}
|
||||
|
||||
@ -276,9 +303,9 @@ impl Serialize for GetAssertion {
|
||||
if self.options.has_some() {
|
||||
map.serialize_entry(&5, &self.options)?;
|
||||
}
|
||||
if let Some(pin_auth) = &self.pin_auth {
|
||||
map.serialize_entry(&6, &pin_auth)?;
|
||||
map.serialize_entry(&7, &pin_auth.pin_protocol.id())?;
|
||||
if let Some(pin_uv_auth_param) = &self.pin_uv_auth_param {
|
||||
map.serialize_entry(&6, &pin_uv_auth_param)?;
|
||||
map.serialize_entry(&7, &pin_uv_auth_param.pin_protocol.id())?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::transport::errors::HIDError;
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde::{
|
||||
de::{Error as SError, IgnoredAny, MapAccess, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use serde_cbor::{de::from_slice, Value};
|
||||
use std::collections::BTreeMap;
|
||||
@ -108,6 +108,173 @@ pub(crate) struct AuthenticatorOptions {
|
||||
// it has no built-in verification method. Not to be trusted...
|
||||
#[serde(rename = "uv")]
|
||||
pub(crate) user_verification: Option<bool>,
|
||||
|
||||
// ----------------------------------------------------
|
||||
// CTAP 2.1 options
|
||||
// ----------------------------------------------------
|
||||
/// If pinUvAuthToken is:
|
||||
/// present and set to true
|
||||
/// if the clientPin option id is present and set to true, then the
|
||||
/// authenticator supports authenticatorClientPIN's getPinUvAuthTokenUsingPinWithPermissions
|
||||
/// subcommand. If the uv option id is present and set to true, then
|
||||
/// the authenticator supports authenticatorClientPIN's getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// subcommand.
|
||||
/// present and set to false, or absent.
|
||||
/// the authenticator does not support authenticatorClientPIN's
|
||||
/// getPinUvAuthTokenUsingPinWithPermissions and getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// subcommands.
|
||||
#[serde(rename = "pinUvAuthToken")]
|
||||
pub(crate) pin_uv_auth_token: Option<bool>,
|
||||
|
||||
/// If this noMcGaPermissionsWithClientPin is:
|
||||
/// present and set to true
|
||||
/// A pinUvAuthToken obtained via getPinUvAuthTokenUsingPinWithPermissions
|
||||
/// (or getPinToken) cannot be used for authenticatorMakeCredential or
|
||||
/// authenticatorGetAssertion commands, because it will lack the necessary
|
||||
/// mc and ga permissions. In this situation, platforms SHOULD NOT attempt
|
||||
/// to use getPinUvAuthTokenUsingPinWithPermissions if using
|
||||
/// getPinUvAuthTokenUsingUvWithPermissions fails.
|
||||
/// present and set to false, or absent.
|
||||
/// A pinUvAuthToken obtained via getPinUvAuthTokenUsingPinWithPermissions
|
||||
/// (or getPinToken) can be used for authenticatorMakeCredential or
|
||||
/// authenticatorGetAssertion commands.
|
||||
/// Note: noMcGaPermissionsWithClientPin MUST only be present if the
|
||||
/// clientPin option ID is present.
|
||||
#[serde(rename = "noMcGaPermissionsWithClientPin")]
|
||||
pub(crate) no_mc_ga_permissions_with_client_pin: Option<bool>,
|
||||
|
||||
/// If largeBlobs is:
|
||||
/// present and set to true
|
||||
/// the authenticator supports the authenticatorLargeBlobs command.
|
||||
/// present and set to false, or absent.
|
||||
/// The authenticatorLargeBlobs command is NOT supported.
|
||||
#[serde(rename = "largeBlobs")]
|
||||
pub(crate) large_blobs: Option<bool>,
|
||||
|
||||
/// Enterprise Attestation feature support:
|
||||
/// If ep is:
|
||||
/// Present and set to true
|
||||
/// The authenticator is enterprise attestation capable, and enterprise
|
||||
/// attestation is enabled.
|
||||
/// Present and set to false
|
||||
/// The authenticator is enterprise attestation capable, and enterprise
|
||||
/// attestation is disabled.
|
||||
/// Absent
|
||||
/// The Enterprise Attestation feature is NOT supported.
|
||||
#[serde(rename = "ep")]
|
||||
pub(crate) ep: Option<bool>,
|
||||
|
||||
/// If bioEnroll is:
|
||||
/// present and set to true
|
||||
/// the authenticator supports the authenticatorBioEnrollment commands,
|
||||
/// and has at least one bio enrollment presently provisioned.
|
||||
/// present and set to false
|
||||
/// the authenticator supports the authenticatorBioEnrollment commands,
|
||||
/// and does not yet have any bio enrollments provisioned.
|
||||
/// absent
|
||||
/// the authenticatorBioEnrollment commands are NOT supported.
|
||||
#[serde(rename = "bioEnroll")]
|
||||
pub(crate) bio_enroll: Option<bool>,
|
||||
|
||||
/// "FIDO_2_1_PRE" Prototype Credential management support:
|
||||
/// If userVerificationMgmtPreview is:
|
||||
/// present and set to true
|
||||
/// the authenticator supports the Prototype authenticatorBioEnrollment (0x41)
|
||||
/// commands, and has at least one bio enrollment presently provisioned.
|
||||
/// present and set to false
|
||||
/// the authenticator supports the Prototype authenticatorBioEnrollment (0x41)
|
||||
/// commands, and does not yet have any bio enrollments provisioned.
|
||||
/// absent
|
||||
/// the Prototype authenticatorBioEnrollment (0x41) commands are not supported.
|
||||
#[serde(rename = "userVerificationMgmtPreview")]
|
||||
pub(crate) user_verification_mgmt_preview: Option<bool>,
|
||||
|
||||
/// getPinUvAuthTokenUsingUvWithPermissions support for requesting the be permission:
|
||||
/// This option ID MUST only be present if bioEnroll is also present.
|
||||
/// If uvBioEnroll is:
|
||||
/// present and set to true
|
||||
/// requesting the be permission when invoking getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// is supported.
|
||||
/// present and set to false, or absent.
|
||||
/// requesting the be permission when invoking getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// is NOT supported.
|
||||
#[serde(rename = "uvBioEnroll")]
|
||||
pub(crate) uv_bio_enroll: Option<bool>,
|
||||
|
||||
/// authenticatorConfig command support:
|
||||
/// If authnrCfg is:
|
||||
/// present and set to true
|
||||
/// the authenticatorConfig command is supported.
|
||||
/// present and set to false, or absent.
|
||||
/// the authenticatorConfig command is NOT supported.
|
||||
#[serde(rename = "authnrCfg")]
|
||||
pub(crate) authnr_cfg: Option<bool>,
|
||||
|
||||
/// getPinUvAuthTokenUsingUvWithPermissions support for requesting the acfg permission:
|
||||
/// This option ID MUST only be present if authnrCfg is also present.
|
||||
/// If uvAcfg is:
|
||||
/// present and set to true
|
||||
/// requesting the acfg permission when invoking getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// is supported.
|
||||
/// present and set to false, or absent.
|
||||
/// requesting the acfg permission when invoking getPinUvAuthTokenUsingUvWithPermissions
|
||||
/// is NOT supported.
|
||||
#[serde(rename = "uvAcfg")]
|
||||
pub(crate) uv_acfg: Option<bool>,
|
||||
|
||||
/// Credential management support:
|
||||
/// If credMgmt is:
|
||||
/// present and set to true
|
||||
/// the authenticatorCredentialManagement command is supported.
|
||||
/// present and set to false, or absent.
|
||||
/// the authenticatorCredentialManagement command is NOT supported.
|
||||
#[serde(rename = "credMgmt")]
|
||||
pub(crate) cred_mgmt: Option<bool>,
|
||||
|
||||
/// "FIDO_2_1_PRE" Prototype Credential management support:
|
||||
/// If credentialMgmtPreview is:
|
||||
/// present and set to true
|
||||
/// the Prototype authenticatorCredentialManagement (0x41) command is supported.
|
||||
/// present and set to false, or absent.
|
||||
/// the Prototype authenticatorCredentialManagement (0x41) command is NOT supported.
|
||||
#[serde(rename = "credentialMgmtPreview")]
|
||||
pub(crate) credential_mgmt_preview: Option<bool>,
|
||||
|
||||
/// Support for the Set Minimum PIN Length feature.
|
||||
/// If setMinPINLength is:
|
||||
/// present and set to true
|
||||
/// the setMinPINLength subcommand is supported.
|
||||
/// present and set to false, or absent.
|
||||
/// the setMinPINLength subcommand is NOT supported.
|
||||
/// Note: setMinPINLength MUST only be present if the clientPin option ID is present.
|
||||
#[serde(rename = "setMinPINLength")]
|
||||
pub(crate) set_min_pin_length: Option<bool>,
|
||||
|
||||
/// Support for making non-discoverable credentials without requiring User Verification.
|
||||
/// If makeCredUvNotRqd is:
|
||||
/// present and set to true
|
||||
/// the authenticator allows creation of non-discoverable credentials without
|
||||
/// requiring any form of user verification, if the platform requests this behaviour.
|
||||
/// present and set to false, or absent.
|
||||
/// the authenticator requires some form of user verification for creating
|
||||
/// non-discoverable credentials, regardless of the parameters the platform supplies
|
||||
/// for the authenticatorMakeCredential command.
|
||||
/// Authenticators SHOULD include this option with the value true.
|
||||
#[serde(rename = "makeCredUvNotRqd")]
|
||||
pub(crate) make_cred_uv_not_rqd: Option<bool>,
|
||||
|
||||
/// Support for the Always Require User Verification feature:
|
||||
/// If alwaysUv is
|
||||
/// present and set to true
|
||||
/// the authenticator supports the Always Require User Verification feature and it is enabled.
|
||||
/// present and set to false
|
||||
/// the authenticator supports the Always Require User Verification feature but it is disabled.
|
||||
/// absent
|
||||
/// the authenticator does not support the Always Require User Verification feature.
|
||||
/// Note: If the alwaysUv option ID is present and true the authenticator MUST set the value
|
||||
/// of makeCredUvNotRqd to false.
|
||||
#[serde(rename = "alwaysUv")]
|
||||
pub(crate) always_uv: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for AuthenticatorOptions {
|
||||
@ -118,13 +285,36 @@ impl Default for AuthenticatorOptions {
|
||||
client_pin: None,
|
||||
user_presence: true,
|
||||
user_verification: None,
|
||||
pin_uv_auth_token: None,
|
||||
no_mc_ga_permissions_with_client_pin: None,
|
||||
large_blobs: None,
|
||||
ep: None,
|
||||
bio_enroll: None,
|
||||
user_verification_mgmt_preview: None,
|
||||
uv_bio_enroll: None,
|
||||
authnr_cfg: None,
|
||||
uv_acfg: None,
|
||||
cred_mgmt: None,
|
||||
credential_mgmt_preview: None,
|
||||
set_min_pin_length: None,
|
||||
make_cred_uv_not_rqd: None,
|
||||
always_uv: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorVersion {
|
||||
U2F_V2,
|
||||
FIDO_2_0,
|
||||
FIDO_2_1_PRE,
|
||||
FIDO_2_1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct AuthenticatorInfo {
|
||||
pub(crate) versions: Vec<String>,
|
||||
pub(crate) versions: Vec<AuthenticatorVersion>,
|
||||
pub(crate) extensions: Vec<String>,
|
||||
pub(crate) aaguid: AAGuid,
|
||||
pub(crate) options: AuthenticatorOptions,
|
||||
@ -527,7 +717,7 @@ pub mod tests {
|
||||
from_slice(&AUTHENTICATOR_INFO_PAYLOAD).unwrap();
|
||||
|
||||
let expected = AuthenticatorInfo {
|
||||
versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()],
|
||||
versions: vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0],
|
||||
extensions: vec!["uvm".to_string(), "hmac-secret".to_string()],
|
||||
aaguid: AAGuid(AAGUID_RAW),
|
||||
options: AuthenticatorOptions {
|
||||
@ -536,6 +726,7 @@ pub mod tests {
|
||||
client_pin: Some(false),
|
||||
user_presence: true,
|
||||
user_verification: None,
|
||||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: vec![1],
|
||||
@ -580,10 +771,10 @@ pub mod tests {
|
||||
|
||||
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(),
|
||||
AuthenticatorVersion::U2F_V2,
|
||||
AuthenticatorVersion::FIDO_2_0,
|
||||
AuthenticatorVersion::FIDO_2_1_PRE,
|
||||
AuthenticatorVersion::FIDO_2_1,
|
||||
],
|
||||
extensions: vec![
|
||||
"credProtect".to_string(),
|
||||
@ -602,6 +793,20 @@ pub mod tests {
|
||||
client_pin: Some(true),
|
||||
user_presence: true,
|
||||
user_verification: Some(true),
|
||||
pin_uv_auth_token: Some(true),
|
||||
no_mc_ga_permissions_with_client_pin: None,
|
||||
large_blobs: Some(true),
|
||||
ep: None,
|
||||
bio_enroll: Some(true),
|
||||
user_verification_mgmt_preview: Some(true),
|
||||
uv_bio_enroll: None,
|
||||
authnr_cfg: Some(true),
|
||||
uv_acfg: None,
|
||||
cred_mgmt: Some(true),
|
||||
credential_mgmt_preview: Some(true),
|
||||
set_min_pin_length: Some(true),
|
||||
make_cred_uv_not_rqd: Some(false),
|
||||
always_uv: Some(true),
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: vec![2, 1],
|
||||
@ -693,7 +898,7 @@ pub mod tests {
|
||||
.get_authenticator_info()
|
||||
.expect("Didn't get any authenticator_info");
|
||||
let expected = AuthenticatorInfo {
|
||||
versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()],
|
||||
versions: vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0],
|
||||
extensions: vec!["uvm".to_string(), "hmac-secret".to_string()],
|
||||
aaguid: AAGuid(AAGUID_RAW),
|
||||
options: AuthenticatorOptions {
|
||||
@ -702,6 +907,7 @@ pub mod tests {
|
||||
client_pin: Some(false),
|
||||
user_presence: true,
|
||||
user_verification: None,
|
||||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: vec![1],
|
||||
|
@ -1,11 +1,11 @@
|
||||
use super::{
|
||||
Command, CommandError, PinAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable,
|
||||
Command, CommandError, PinUvAuthCommand, Request, RequestCtap1, RequestCtap2, Retryable,
|
||||
StatusCode,
|
||||
};
|
||||
use crate::consts::{PARAMETER_SIZE, U2F_REGISTER, U2F_REQUEST_USER_PRESENCE};
|
||||
use crate::crypto::{
|
||||
parse_u2f_der_certificate, COSEAlgorithm, COSEEC2Key, COSEKey, COSEKeyType, Curve,
|
||||
PinUvAuthParam,
|
||||
PinUvAuthParam, PinUvAuthToken,
|
||||
};
|
||||
use crate::ctap2::attestation::{
|
||||
AAGuid, AttestationObject, AttestationStatement, AttestationStatementFidoU2F,
|
||||
@ -20,6 +20,7 @@ use crate::ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
RelyingPartyWrapper, User,
|
||||
};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::{
|
||||
errors::{ApduErrorStatus, HIDError},
|
||||
FidoDevice,
|
||||
@ -110,7 +111,7 @@ pub struct MakeCredentials {
|
||||
pub(crate) extensions: MakeCredentialsExtensions,
|
||||
pub(crate) options: MakeCredentialsOptions,
|
||||
pub(crate) pin: Option<Pin>,
|
||||
pub(crate) pin_auth: Option<PinUvAuthParam>,
|
||||
pub(crate) pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
pub(crate) enterprise_attestation: Option<u64>,
|
||||
}
|
||||
|
||||
@ -135,13 +136,13 @@ impl MakeCredentials {
|
||||
extensions,
|
||||
options,
|
||||
pin,
|
||||
pin_auth: None,
|
||||
pin_uv_auth_param: None,
|
||||
enterprise_attestation: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PinAuthCommand for MakeCredentials {
|
||||
impl PinUvAuthCommand for MakeCredentials {
|
||||
fn pin(&self) -> &Option<Pin> {
|
||||
&self.pin
|
||||
}
|
||||
@ -150,20 +151,51 @@ impl PinAuthCommand for MakeCredentials {
|
||||
self.pin = pin;
|
||||
}
|
||||
|
||||
fn pin_auth(&self) -> &Option<PinUvAuthParam> {
|
||||
&self.pin_auth
|
||||
}
|
||||
|
||||
fn set_pin_auth(&mut self, pin_auth: Option<PinUvAuthParam>) {
|
||||
self.pin_auth = pin_auth;
|
||||
fn set_pin_uv_auth_param(
|
||||
&mut self,
|
||||
pin_uv_auth_token: Option<PinUvAuthToken>,
|
||||
) -> Result<(), AuthenticatorError> {
|
||||
let mut param = None;
|
||||
if let Some(token) = pin_uv_auth_token {
|
||||
param = Some(
|
||||
token
|
||||
.derive(self.client_data_hash().as_ref())
|
||||
.map_err(CommandError::Crypto)?,
|
||||
);
|
||||
}
|
||||
self.pin_uv_auth_param = param;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn client_data_hash(&self) -> ClientDataHash {
|
||||
self.client_data_wrapper.hash()
|
||||
}
|
||||
|
||||
fn unset_uv_option(&mut self) {
|
||||
self.options.user_verification = None;
|
||||
fn set_uv_option(&mut self, uv: Option<bool>) {
|
||||
self.options.user_verification = uv;
|
||||
}
|
||||
|
||||
fn get_uv_option(&mut self) -> Option<bool> {
|
||||
self.options.user_verification
|
||||
}
|
||||
|
||||
fn get_rp_id(&self) -> Option<&String> {
|
||||
match &self.rp {
|
||||
// CTAP1 case: We only have the hash, not the entire RpID
|
||||
RelyingPartyWrapper::Hash(..) => None,
|
||||
RelyingPartyWrapper::Data(r) => Some(&r.id),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_discouraged_uv_option(&mut self) {
|
||||
// "[..] the Relying Party wants to create a non-discoverable credential and not require user verification
|
||||
// (e.g., by setting options.authenticatorSelection.userVerification to "discouraged" in the WebAuthn API),
|
||||
// the platform invokes the authenticatorMakeCredential operation using the marshalled input parameters along
|
||||
// with the "uv" option key set to false and terminate these steps."
|
||||
// Note: This is basically a no-op right now, since we use `get_uv_option() == Some(false)`, to determine if
|
||||
// the RP is discouraging UV. But we may change that part of the API in the future, so better to be
|
||||
// explicit here.
|
||||
self.set_uv_option(Some(false))
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +217,7 @@ impl Serialize for MakeCredentials {
|
||||
if self.options.has_some() {
|
||||
map_len += 1;
|
||||
}
|
||||
if self.pin_auth.is_some() {
|
||||
if self.pin_uv_auth_param.is_some() {
|
||||
map_len += 2;
|
||||
}
|
||||
if self.enterprise_attestation.is_some() {
|
||||
@ -216,9 +248,9 @@ impl Serialize for MakeCredentials {
|
||||
if self.options.has_some() {
|
||||
map.serialize_entry(&0x07, &self.options)?;
|
||||
}
|
||||
if let Some(pin_auth) = &self.pin_auth {
|
||||
map.serialize_entry(&0x08, &pin_auth)?;
|
||||
map.serialize_entry(&0x09, &pin_auth.pin_protocol.id())?;
|
||||
if let Some(pin_uv_auth_param) = &self.pin_uv_auth_param {
|
||||
map.serialize_entry(&0x08, &pin_uv_auth_param)?;
|
||||
map.serialize_entry(&0x09, &pin_uv_auth_param.pin_protocol.id())?;
|
||||
}
|
||||
if let Some(enterprise_attestation) = self.enterprise_attestation {
|
||||
map.serialize_entry(&0x0a, &enterprise_attestation)?;
|
||||
@ -279,11 +311,7 @@ impl RequestCtap1 for MakeCredentials {
|
||||
)));
|
||||
}
|
||||
|
||||
let flags = if self.options.ask_user_verification() {
|
||||
U2F_REQUEST_USER_PRESENCE
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let flags = U2F_REQUEST_USER_PRESENCE;
|
||||
|
||||
let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE);
|
||||
if self.is_ctap2_request() {
|
||||
@ -944,7 +972,7 @@ pub mod test {
|
||||
// CBOR Header
|
||||
0x0, // CLA
|
||||
0x1, // INS U2F_Register
|
||||
0x0, // P1 Flags
|
||||
0x3, // P1 Flags
|
||||
0x0, // P2
|
||||
0x0, 0x0, 0x40, // Lc
|
||||
// NOTE: This has been taken from CTAP2.0 spec, but the clientDataHash has been replaced
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::crypto::{CryptoError, PinUvAuthParam};
|
||||
use crate::crypto::{CryptoError, PinUvAuthToken};
|
||||
use crate::ctap2::client_data::ClientDataHash;
|
||||
use crate::ctap2::commands::client_pin::{GetPinToken, GetRetries, Pin, PinError};
|
||||
use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, Pin, PinError};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::FidoDevice;
|
||||
@ -92,66 +92,85 @@ pub trait RequestCtap2: fmt::Debug {
|
||||
Dev: FidoDevice + Read + Write + fmt::Debug;
|
||||
}
|
||||
|
||||
pub(crate) trait PinAuthCommand {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) enum PinUvAuthResult {
|
||||
/// Request is CTAP1 and does not need PinUvAuth
|
||||
RequestIsCtap1,
|
||||
/// Device is not capable of CTAP2
|
||||
DeviceIsCtap1,
|
||||
/// Device does not support UV or PINs
|
||||
NoAuthTypeSupported,
|
||||
/// Request doesn't want user verification (uv = "discouraged")
|
||||
NoAuthRequired,
|
||||
/// Device is CTAP2.0 and has internal UV capability
|
||||
UsingInternalUv,
|
||||
/// Successfully established PinUvAuthToken via GetPinToken (CTAP2.0)
|
||||
SuccessGetPinToken,
|
||||
/// Successfully established PinUvAuthToken via UV (CTAP2.1)
|
||||
SuccessGetPinUvAuthTokenUsingUvWithPermissions,
|
||||
/// Successfully established PinUvAuthToken via Pin (CTAP2.1)
|
||||
SuccessGetPinUvAuthTokenUsingPinWithPermissions,
|
||||
}
|
||||
|
||||
/// Helper-trait to determine pin_uv_auth_param from PIN or UV.
|
||||
pub(crate) trait PinUvAuthCommand: RequestCtap2 {
|
||||
fn pin(&self) -> &Option<Pin>;
|
||||
fn set_pin(&mut self, pin: Option<Pin>);
|
||||
fn pin_auth(&self) -> &Option<PinUvAuthParam>;
|
||||
fn set_pin_auth(&mut self, pin_auth: Option<PinUvAuthParam>);
|
||||
fn set_pin_uv_auth_param(
|
||||
&mut self,
|
||||
pin_uv_auth_token: Option<PinUvAuthToken>,
|
||||
) -> Result<(), AuthenticatorError>;
|
||||
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);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let client_data_hash = self.client_data_hash();
|
||||
let pin_auth = calculate_pin_auth(dev, &client_data_hash, self.pin())
|
||||
.map_err(|e| repackage_pin_errors(dev, e))?;
|
||||
self.set_pin_auth(pin_auth);
|
||||
Ok(())
|
||||
}
|
||||
fn set_uv_option(&mut self, uv: Option<bool>);
|
||||
fn get_uv_option(&mut self) -> Option<bool>;
|
||||
fn get_rp_id(&self) -> Option<&String>;
|
||||
fn set_discouraged_uv_option(&mut self);
|
||||
}
|
||||
|
||||
pub(crate) fn repackage_pin_errors<D: FidoDevice>(
|
||||
dev: &mut D,
|
||||
error: AuthenticatorError,
|
||||
error: HIDError,
|
||||
) -> AuthenticatorError {
|
||||
match error {
|
||||
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinInvalid,
|
||||
_,
|
||||
))) => {
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinInvalid, _)) => {
|
||||
// If the given PIN was wrong, determine no. of left retries
|
||||
let cmd = GetRetries::new();
|
||||
let cmd = GetPinRetries::new();
|
||||
let retries = dev.send_cbor(&cmd).ok(); // If we got retries, wrap it in Some, otherwise ignore err
|
||||
AuthenticatorError::PinError(PinError::InvalidPin(retries))
|
||||
}
|
||||
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinAuthBlocked,
|
||||
_,
|
||||
))) => AuthenticatorError::PinError(PinError::PinAuthBlocked),
|
||||
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinBlocked,
|
||||
_,
|
||||
))) => AuthenticatorError::PinError(PinError::PinBlocked),
|
||||
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinRequired,
|
||||
_,
|
||||
))) => AuthenticatorError::PinError(PinError::PinRequired),
|
||||
AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinNotSet,
|
||||
_,
|
||||
))) => AuthenticatorError::PinError(PinError::PinNotSet),
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthBlocked, _)) => {
|
||||
AuthenticatorError::PinError(PinError::PinAuthBlocked)
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinBlocked, _)) => {
|
||||
AuthenticatorError::PinError(PinError::PinBlocked)
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinRequired, _)) => {
|
||||
AuthenticatorError::PinError(PinError::PinRequired)
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinNotSet, _)) => {
|
||||
AuthenticatorError::PinError(PinError::PinNotSet)
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _)) => {
|
||||
AuthenticatorError::PinError(PinError::PinAuthInvalid)
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::UvInvalid, _)) => {
|
||||
// If the internal UV failed, determine no. of left retries
|
||||
let cmd = GetUvRetries::new();
|
||||
let retries = dev.send_cbor(&cmd).ok(); // If we got retries, wrap it in Some, otherwise ignore err
|
||||
AuthenticatorError::PinError(PinError::InvalidUv(retries))
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::UvBlocked, _)) => {
|
||||
AuthenticatorError::PinError(PinError::UvBlocked)
|
||||
}
|
||||
// TODO(MS): Add "PinPolicyViolated"
|
||||
err => err,
|
||||
err => AuthenticatorError::HIDError(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)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Command {
|
||||
MakeCredentials = 0x01,
|
||||
GetAssertion = 0x02,
|
||||
@ -262,6 +281,18 @@ pub enum StatusCode {
|
||||
ActionTimeout,
|
||||
/// User presence is required for the requested operation.
|
||||
UpRequired,
|
||||
/// built-in user verification is disabled.
|
||||
UvBlocked,
|
||||
/// A checksum did not match.
|
||||
IntegrityFailure,
|
||||
/// The requested subcommand is either invalid or not implemented.
|
||||
InvalidSubcommand,
|
||||
/// built-in user verification unsuccessful. The platform SHOULD retry.
|
||||
UvInvalid,
|
||||
/// The permissions parameter contains an unauthorized permission.
|
||||
UnauthorizedPermission,
|
||||
/// Other unspecified error.
|
||||
Other,
|
||||
|
||||
/// Unknown status.
|
||||
Unknown(u8),
|
||||
@ -321,7 +352,12 @@ impl From<u8> for StatusCode {
|
||||
0x39 => StatusCode::RequestTooLarge,
|
||||
0x3A => StatusCode::ActionTimeout,
|
||||
0x3B => StatusCode::UpRequired,
|
||||
|
||||
0x3C => StatusCode::UvBlocked,
|
||||
0x3D => StatusCode::IntegrityFailure,
|
||||
0x3E => StatusCode::InvalidSubcommand,
|
||||
0x3F => StatusCode::UvInvalid,
|
||||
0x40 => StatusCode::UnauthorizedPermission,
|
||||
0x7F => StatusCode::Other,
|
||||
othr => StatusCode::Unknown(othr),
|
||||
}
|
||||
}
|
||||
@ -372,6 +408,12 @@ impl From<StatusCode> for u8 {
|
||||
StatusCode::RequestTooLarge => 0x39,
|
||||
StatusCode::ActionTimeout => 0x3A,
|
||||
StatusCode::UpRequired => 0x3B,
|
||||
StatusCode::UvBlocked => 0x3C,
|
||||
StatusCode::IntegrityFailure => 0x3D,
|
||||
StatusCode::InvalidSubcommand => 0x3E,
|
||||
StatusCode::UvInvalid => 0x3F,
|
||||
StatusCode::UnauthorizedPermission => 0x40,
|
||||
StatusCode::Other => 0x7F,
|
||||
|
||||
StatusCode::Unknown(othr) => othr,
|
||||
}
|
||||
@ -416,38 +458,3 @@ impl fmt::Display for CommandError {
|
||||
}
|
||||
|
||||
impl StdErrorT for CommandError {}
|
||||
|
||||
pub(crate) fn calculate_pin_auth<Dev>(
|
||||
dev: &mut Dev,
|
||||
client_data_hash: &ClientDataHash,
|
||||
pin: &Option<Pin>,
|
||||
) -> Result<Option<PinUvAuthParam>, 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?
|
||||
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(&shared_secret, pin);
|
||||
let pin_token = dev.send_cbor(&pin_command)?;
|
||||
|
||||
Ok(Some(
|
||||
pin_token
|
||||
.derive(client_data_hash.as_ref())
|
||||
.map_err(CommandError::Crypto)?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
11
third_party/rust/authenticator/src/ctap2_capi.rs
vendored
11
third_party/rust/authenticator/src/ctap2_capi.rs
vendored
@ -13,7 +13,7 @@ use crate::ctap2::server::{
|
||||
};
|
||||
use crate::errors::{AuthenticatorError, U2FTokenError};
|
||||
use crate::statecallback::StateCallback;
|
||||
use crate::{AttestationObject, CollectedClientDataWrapper, Pin, StatusUpdate};
|
||||
use crate::{AttestationObject, CollectedClientDataWrapper, Pin, StatusPinUv, StatusUpdate};
|
||||
use crate::{RegisterResult, SignResult};
|
||||
use libc::size_t;
|
||||
use rand::{thread_rng, Rng};
|
||||
@ -338,7 +338,11 @@ pub unsafe extern "C" fn rust_ctap2_mgr_sign(
|
||||
let alternate_rp_id = if alternate_relying_party_id.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CStr::from_ptr(alternate_relying_party_id).to_string_lossy().to_string())
|
||||
Some(
|
||||
CStr::from_ptr(alternate_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);
|
||||
@ -972,7 +976,8 @@ pub unsafe extern "C" fn rust_ctap2_status_update_send_pin(
|
||||
}
|
||||
|
||||
match &*res {
|
||||
StatusUpdate::PinError(_, sender) => {
|
||||
StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))
|
||||
| StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, _)) => {
|
||||
if let Ok(pin) = CStr::from_ptr(c_pin).to_str() {
|
||||
sender
|
||||
.send(Pin::new(pin))
|
||||
|
11
third_party/rust/authenticator/src/errors.rs
vendored
11
third_party/rust/authenticator/src/errors.rs
vendored
@ -33,6 +33,7 @@ pub enum AuthenticatorError {
|
||||
CryptoError,
|
||||
PinError(PinError),
|
||||
UnsupportedOption(UnsupportedOption),
|
||||
CancelledByUser,
|
||||
}
|
||||
|
||||
impl AuthenticatorError {
|
||||
@ -69,10 +70,9 @@ 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,
|
||||
"{manager} expected arguments of version CTAP{version}"
|
||||
),
|
||||
AuthenticatorError::VersionMismatch(manager, version) => {
|
||||
write!(f, "{manager} expected arguments of version CTAP{version}")
|
||||
}
|
||||
AuthenticatorError::HIDError(ref e) => write!(f, "Device error: {e}"),
|
||||
AuthenticatorError::CryptoError => {
|
||||
write!(f, "The cryptography implementation encountered an error")
|
||||
@ -81,6 +81,9 @@ impl fmt::Display for AuthenticatorError {
|
||||
AuthenticatorError::UnsupportedOption(ref e) => {
|
||||
write!(f, "Unsupported option: {e:?}")
|
||||
}
|
||||
AuthenticatorError::CancelledByUser => {
|
||||
write!(f, "Cancelled by user.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
574
third_party/rust/authenticator/src/statemachine.rs
vendored
574
third_party/rust/authenticator/src/statemachine.rs
vendored
@ -3,12 +3,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::consts::PARAMETER_SIZE;
|
||||
use crate::ctap2::commands::client_pin::{ChangeExistingPin, Pin, PinError, SetNewPin};
|
||||
use crate::ctap2::commands::client_pin::{
|
||||
ChangeExistingPin, Pin, PinError, PinUvAuthTokenPermission, SetNewPin,
|
||||
};
|
||||
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult};
|
||||
use crate::ctap2::commands::make_credentials::{MakeCredentials, MakeCredentialsResult};
|
||||
use crate::ctap2::commands::reset::Reset;
|
||||
use crate::ctap2::commands::{
|
||||
repackage_pin_errors, CommandError, PinAuthCommand, Request, StatusCode,
|
||||
repackage_pin_errors, CommandError, PinUvAuthCommand, PinUvAuthResult, Request, StatusCode,
|
||||
};
|
||||
use crate::ctap2::server::{RelyingParty, RelyingPartyWrapper};
|
||||
use crate::errors::{self, AuthenticatorError, UnsupportedOption};
|
||||
@ -20,7 +22,7 @@ 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 crate::{send_status, RegisterResult, SignResult, StatusUpdate};
|
||||
use crate::{send_status, RegisterResult, SignResult, StatusPinUv, StatusUpdate};
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
@ -353,9 +355,7 @@ impl StateMachineCtap2 {
|
||||
// We can be cancelled from the user (through keep_alive()) or from the device selector
|
||||
// (through a DeviceCommand::Cancel on rx). We'll combine those signals into a single
|
||||
// predicate to pass to Device::block_and_blink.
|
||||
let keep_blinking = || {
|
||||
keep_alive() && !matches!(rx.try_recv(), Ok(DeviceCommand::Cancel))
|
||||
};
|
||||
let keep_blinking = || keep_alive() && !matches!(rx.try_recv(), Ok(DeviceCommand::Cancel));
|
||||
|
||||
// Blocking recv. DeviceSelector will tell us what to do
|
||||
match rx.recv() {
|
||||
@ -392,58 +392,222 @@ impl StateMachineCtap2 {
|
||||
}
|
||||
|
||||
fn ask_user_for_pin<U>(
|
||||
error: PinError,
|
||||
was_invalid: bool,
|
||||
retries: Option<u8>,
|
||||
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));
|
||||
if was_invalid {
|
||||
send_status(
|
||||
status,
|
||||
crate::StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx, retries)),
|
||||
);
|
||||
} else {
|
||||
send_status(
|
||||
status,
|
||||
crate::StatusUpdate::PinUvError(StatusPinUv::PinRequired(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)));
|
||||
// recv() can only fail, if the other side is dropping the Sender.
|
||||
error!("Callback dropped the channel. Aborting.");
|
||||
callback.call(Err(AuthenticatorError::CancelledByUser));
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_pin_auth<T: PinAuthCommand, U>(
|
||||
/// Try to fetch PinUvAuthToken from the device and derive from it PinUvAuthParam.
|
||||
/// Prefer UV, fallback to PIN.
|
||||
/// Prefer newer pinUvAuth-methods, if supported by the device.
|
||||
fn get_pin_uv_auth_param<T: PinUvAuthCommand + Request<V>, V>(
|
||||
cmd: &mut T,
|
||||
dev: &mut Device,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
skip_uv: bool,
|
||||
) -> Result<PinUvAuthResult, AuthenticatorError> {
|
||||
let info = dev
|
||||
.get_authenticator_info()
|
||||
.ok_or(AuthenticatorError::HIDError(HIDError::DeviceNotInitialized))?;
|
||||
|
||||
// Only use UV, if the device supports it and we don't skip it
|
||||
// which happens as a fallback, if UV-usage failed too many times
|
||||
// Note: In theory, we could also repeatedly query GetInfo here and check
|
||||
// if uv is set to Some(true), as tokens should set it to Some(false)
|
||||
// if UV is blocked (too many failed attempts). But the CTAP2.0-spec is
|
||||
// vague and I don't trust all tokens to implement it that way. So we
|
||||
// keep track of it ourselves, using `skip_uv`.
|
||||
let supports_uv = info.options.user_verification == Some(true);
|
||||
let supports_pin = info.options.client_pin.is_some();
|
||||
let pin_configured = info.options.client_pin == Some(true);
|
||||
|
||||
// Device does not support any auth-method
|
||||
if !pin_configured && !supports_uv {
|
||||
// We'll send it to the device anyways, and let it error out (or magically work)
|
||||
return Ok(PinUvAuthResult::NoAuthTypeSupported);
|
||||
}
|
||||
let (res, pin_auth_token) = if info.options.pin_uv_auth_token == Some(true) {
|
||||
if !skip_uv && supports_uv {
|
||||
// CTAP 2.1 - UV
|
||||
let pin_auth_token = dev
|
||||
.get_pin_uv_auth_token_using_uv_with_permissions(permission, cmd.get_rp_id());
|
||||
(
|
||||
PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions,
|
||||
pin_auth_token,
|
||||
)
|
||||
} else if supports_pin && pin_configured {
|
||||
// CTAP 2.1 - PIN
|
||||
let pin_auth_token = dev.get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
cmd.pin(),
|
||||
permission,
|
||||
cmd.get_rp_id(),
|
||||
);
|
||||
(
|
||||
PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions,
|
||||
pin_auth_token,
|
||||
)
|
||||
} else {
|
||||
return Ok(PinUvAuthResult::NoAuthTypeSupported);
|
||||
}
|
||||
} else {
|
||||
// CTAP 2.0 fallback
|
||||
if !skip_uv && supports_uv && cmd.pin().is_none() {
|
||||
// If the device supports internal user-verification (e.g. fingerprints),
|
||||
// skip PIN-stuff
|
||||
|
||||
// We may need the shared secret for HMAC-extension, so we
|
||||
// have to establish one
|
||||
let _shared_secret = dev.establish_shared_secret()?;
|
||||
return Ok(PinUvAuthResult::UsingInternalUv);
|
||||
}
|
||||
|
||||
let pin_auth_token = dev.get_pin_token(cmd.pin());
|
||||
(PinUvAuthResult::SuccessGetPinToken, pin_auth_token)
|
||||
};
|
||||
|
||||
let pin_auth_token = pin_auth_token.map_err(|e| repackage_pin_errors(dev, e))?;
|
||||
cmd.set_pin_uv_auth_param(Some(pin_auth_token))?;
|
||||
// CTAP 2.0 spec is a bit vague here, but CTAP 2.1 is very specific, that the request
|
||||
// should either include pinAuth OR uv=true, but not both at the same time.
|
||||
// Do not set user_verification, if pinAuth is provided
|
||||
cmd.set_uv_option(None);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// PUAP, as per spec: PinUvAuthParam
|
||||
/// Determines, if we need to establish a PinUvAuthParam, based on the
|
||||
/// capabilities of the device and the incoming request.
|
||||
/// If it is needed, tries to establish one and save it inside the Request.
|
||||
/// Returns Ok() if we can proceed with sending the actual Request to
|
||||
/// the device, Err() otherwise.
|
||||
/// Handles asking the user for a PIN, if needed and sending StatusUpdates
|
||||
/// regarding PIN and UV usage.
|
||||
fn determine_puap_if_needed<T: PinUvAuthCommand + Request<V>, U, V>(
|
||||
cmd: &mut T,
|
||||
dev: &mut Device,
|
||||
mut skip_uv: bool,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
status: &Sender<StatusUpdate>,
|
||||
callback: &StateCallback<crate::Result<U>>,
|
||||
) -> Result<(), ()> {
|
||||
loop {
|
||||
match cmd.determine_pin_auth(dev) {
|
||||
Ok(_) => {
|
||||
break;
|
||||
alive: &dyn Fn() -> bool,
|
||||
) -> Result<PinUvAuthResult, ()> {
|
||||
// Starting from a blank slate.
|
||||
cmd.set_pin_uv_auth_param(None).map_err(|_| ())?;
|
||||
|
||||
if !cmd.is_ctap2_request() {
|
||||
return Ok(PinUvAuthResult::RequestIsCtap1);
|
||||
}
|
||||
|
||||
// CTAP1/U2F-only devices do not support PinUvAuth, so we skip it
|
||||
if !dev.supports_ctap2() {
|
||||
return Ok(PinUvAuthResult::DeviceIsCtap1);
|
||||
}
|
||||
|
||||
// TODO: API needs a better way to express "uv = discouraged"
|
||||
if cmd.get_uv_option() == Some(false) {
|
||||
cmd.set_discouraged_uv_option();
|
||||
return Ok(PinUvAuthResult::NoAuthRequired);
|
||||
}
|
||||
|
||||
while alive() {
|
||||
debug!("-----------------------------------------------------------------");
|
||||
debug!("Getting pinUvAuthParam");
|
||||
match Self::get_pin_uv_auth_param(cmd, dev, permission, skip_uv) {
|
||||
Ok(r) => {
|
||||
return Ok(r);
|
||||
}
|
||||
Err(AuthenticatorError::PinError(e)) => {
|
||||
let pin = Self::ask_user_for_pin(e, status, callback)?;
|
||||
cmd.set_pin(Some(pin));
|
||||
continue;
|
||||
|
||||
Err(AuthenticatorError::PinError(PinError::PinRequired)) => {
|
||||
if let Ok(pin) = Self::ask_user_for_pin(false, None, status, callback) {
|
||||
cmd.set_pin(Some(pin));
|
||||
skip_uv = true;
|
||||
continue;
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
Err(AuthenticatorError::PinError(PinError::InvalidPin(retries))) => {
|
||||
if let Ok(pin) = Self::ask_user_for_pin(true, retries, status, callback) {
|
||||
cmd.set_pin(Some(pin));
|
||||
continue;
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
Err(AuthenticatorError::PinError(PinError::InvalidUv(retries))) => {
|
||||
if retries == Some(0) {
|
||||
skip_uv = true;
|
||||
}
|
||||
send_status(
|
||||
status,
|
||||
StatusUpdate::PinUvError(StatusPinUv::InvalidUv(retries)),
|
||||
)
|
||||
}
|
||||
Err(e @ AuthenticatorError::PinError(PinError::PinAuthBlocked)) => {
|
||||
send_status(
|
||||
status,
|
||||
StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked),
|
||||
);
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
}
|
||||
Err(e @ AuthenticatorError::PinError(PinError::PinBlocked)) => {
|
||||
send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinBlocked));
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
}
|
||||
Err(e @ AuthenticatorError::PinError(PinError::PinNotSet)) => {
|
||||
send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinNotSet));
|
||||
error!("Error when determining pinAuth: {:?}", e);
|
||||
callback.call(Err(e));
|
||||
return Err(());
|
||||
}
|
||||
Err(AuthenticatorError::PinError(PinError::UvBlocked)) => {
|
||||
skip_uv = true;
|
||||
send_status(status, StatusUpdate::PinUvError(StatusPinUv::UvBlocked))
|
||||
}
|
||||
// Used for CTAP2.0 UV (fingerprints)
|
||||
Err(AuthenticatorError::PinError(PinError::PinAuthInvalid)) => {
|
||||
skip_uv = true;
|
||||
send_status(
|
||||
status,
|
||||
StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
|
||||
)
|
||||
}
|
||||
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(())
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
@ -481,16 +645,6 @@ impl StateMachineCtap2 {
|
||||
// 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();
|
||||
@ -506,46 +660,96 @@ impl StateMachineCtap2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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_cancellable(&makecred, alive);
|
||||
if resp.is_ok() {
|
||||
send_status(
|
||||
|
||||
let mut skip_uv = false;
|
||||
while alive() {
|
||||
let pin_uv_auth_result = match Self::determine_puap_if_needed(
|
||||
&mut makecred,
|
||||
&mut dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::MakeCredential,
|
||||
&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())))
|
||||
}
|
||||
&callback,
|
||||
alive,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::ChannelBusy,
|
||||
_,
|
||||
))) => {}
|
||||
Err(e) => {
|
||||
warn!("error happened: {}", e);
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{makecred:?} using {pin_uv_auth_result:?}");
|
||||
debug!("------------------------------------------------------------------");
|
||||
let resp = dev.send_msg_cancellable(&makecred, alive);
|
||||
if resp.is_ok() {
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::Success {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
// The DeviceSelector could already be dead, but it might also wait
|
||||
// for us to respond, in order to cancel all other tokens in case
|
||||
// we skipped the "blinking"-action and went straight for the actual
|
||||
// request.
|
||||
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
|
||||
}
|
||||
match resp {
|
||||
Ok(MakeCredentialsResult::CTAP2(attestation, client_data)) => {
|
||||
callback.call(Ok(RegisterResult::CTAP2(attestation, client_data)));
|
||||
break;
|
||||
}
|
||||
Ok(MakeCredentialsResult::CTAP1(data)) => {
|
||||
callback.call(Ok(RegisterResult::CTAP1(data, dev.get_device_info())));
|
||||
break;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::ChannelBusy,
|
||||
_,
|
||||
))) => {
|
||||
// Channel busy. Client SHOULD retry the request after a short delay.
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinAuthInvalid,
|
||||
_,
|
||||
))) if pin_uv_auth_result == PinUvAuthResult::UsingInternalUv => {
|
||||
// This should only happen for CTAP2.0 tokens that use internal UV and
|
||||
// failed (e.g. wrong fingerprint used), while doing MakeCredentials
|
||||
send_status(
|
||||
&status,
|
||||
StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinRequired,
|
||||
_,
|
||||
))) if pin_uv_auth_result == PinUvAuthResult::UsingInternalUv => {
|
||||
// This should only happen for CTAP2.0 tokens that use internal UV and failed
|
||||
// repeatedly, so that we have to fall back to PINs
|
||||
skip_uv = true;
|
||||
continue;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::UvBlocked,
|
||||
_,
|
||||
))) if pin_uv_auth_result
|
||||
== PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions =>
|
||||
{
|
||||
// This should only happen for CTAP2.1 tokens that use internal UV and failed
|
||||
// repeatedly, so that we have to fall back to PINs
|
||||
skip_uv = true;
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error happened: {e}");
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -593,80 +797,132 @@ impl StateMachineCtap2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut skip_uv = false;
|
||||
while alive() {
|
||||
let pin_uv_auth_result = match Self::determine_puap_if_needed(
|
||||
&mut getassertion,
|
||||
&mut dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::GetAssertion,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
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;
|
||||
if params.is_ctap2_request() {
|
||||
// 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!("------------------------------------------------------------------");
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{getassertion:?} using {pin_uv_auth_result:?}");
|
||||
debug!("------------------------------------------------------------------");
|
||||
|
||||
let mut resp = dev.send_msg_cancellable(&getassertion, alive);
|
||||
if resp.is_err() {
|
||||
// Retry with a different RP ID if one was supplied. This is intended to be
|
||||
// used with the AppID provided in the WebAuthn FIDO AppID extension.
|
||||
if let Some(alternate_rp_id) = getassertion.alternate_rp_id {
|
||||
getassertion.rp = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: alternate_rp_id,
|
||||
..Default::default()
|
||||
});
|
||||
getassertion.alternate_rp_id = None;
|
||||
resp = dev.send_msg_cancellable(&getassertion, alive);
|
||||
let mut resp = dev.send_msg_cancellable(&getassertion, alive);
|
||||
if resp.is_err() {
|
||||
// Retry with a different RP ID if one was supplied. This is intended to be
|
||||
// used with the AppID provided in the WebAuthn FIDO AppID extension.
|
||||
if let Some(alternate_rp_id) = getassertion.alternate_rp_id {
|
||||
getassertion.rp = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: alternate_rp_id,
|
||||
..Default::default()
|
||||
});
|
||||
getassertion.alternate_rp_id = None;
|
||||
resp = dev.send_msg_cancellable(&getassertion, alive);
|
||||
}
|
||||
}
|
||||
}
|
||||
if resp.is_ok() {
|
||||
send_status(
|
||||
&status,
|
||||
crate::StatusUpdate::Success {
|
||||
dev_info: dev.get_device_info(),
|
||||
},
|
||||
);
|
||||
// The DeviceSelector could already be dead, but it might also wait
|
||||
// for us to respond, in order to cancel all other tokens in case
|
||||
// we skipped the "blinking"-action and went straight for the actual
|
||||
// request.
|
||||
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
|
||||
}
|
||||
match resp {
|
||||
Ok(GetAssertionResult::CTAP1(resp)) => {
|
||||
let app_id = getassertion.rp.hash().as_ref().to_vec();
|
||||
let key_handle = getassertion.allow_list[0].id.clone();
|
||||
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)))
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::ChannelBusy,
|
||||
_,
|
||||
))) => {}
|
||||
Err(e) => {
|
||||
warn!("error happened: {}", e);
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
callback.call(Ok(SignResult::CTAP1(
|
||||
app_id,
|
||||
key_handle,
|
||||
resp,
|
||||
dev.get_device_info(),
|
||||
)));
|
||||
break;
|
||||
}
|
||||
Ok(GetAssertionResult::CTAP2(assertion, client_data)) => {
|
||||
callback.call(Ok(SignResult::CTAP2(assertion, client_data)));
|
||||
break;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::ChannelBusy,
|
||||
_,
|
||||
))) => {
|
||||
// Channel busy. Client SHOULD retry the request after a short delay.
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::OperationDenied,
|
||||
_,
|
||||
))) if pin_uv_auth_result == PinUvAuthResult::UsingInternalUv => {
|
||||
// This should only happen for CTAP2.0 tokens that use internal UV and failed
|
||||
// (e.g. wrong fingerprint used), while doing GetAssertion
|
||||
// Yes, this is a different error code than for MakeCredential.
|
||||
send_status(
|
||||
&status,
|
||||
StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::PinRequired,
|
||||
_,
|
||||
))) if pin_uv_auth_result == PinUvAuthResult::UsingInternalUv => {
|
||||
// This should only happen for CTAP2.0 tokens that use internal UV and failed
|
||||
// repeatedly, so that we have to fall back to PINs
|
||||
skip_uv = true;
|
||||
continue;
|
||||
}
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::UvBlocked,
|
||||
_,
|
||||
))) if pin_uv_auth_result
|
||||
== PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions =>
|
||||
{
|
||||
// This should only happen for CTAP2.1 tokens that use internal UV and failed
|
||||
// repeatedly, so that we have to fall back to PINs
|
||||
skip_uv = true;
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error happened: {e}");
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -768,7 +1024,7 @@ impl StateMachineCtap2 {
|
||||
Some(dev) => dev,
|
||||
};
|
||||
|
||||
let (mut shared_secret, authinfo) = match dev.establish_shared_secret() {
|
||||
let mut shared_secret = match dev.establish_shared_secret() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
@ -776,6 +1032,14 @@ impl StateMachineCtap2 {
|
||||
}
|
||||
};
|
||||
|
||||
let authinfo = match dev.get_authenticator_info() {
|
||||
Some(i) => i.clone(),
|
||||
None => {
|
||||
callback.call(Err(HIDError::DeviceNotInitialized.into()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// With CTAP2.1 we will have an adjustable required length for PINs
|
||||
if new_pin.as_bytes().len() < 4 {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
|
||||
@ -792,9 +1056,15 @@ impl StateMachineCtap2 {
|
||||
// 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;
|
||||
let mut was_invalid = false;
|
||||
let mut retries = None;
|
||||
loop {
|
||||
let current_pin = match Self::ask_user_for_pin(error, &status, &callback) {
|
||||
let current_pin = match Self::ask_user_for_pin(
|
||||
was_invalid,
|
||||
retries,
|
||||
&status,
|
||||
&callback,
|
||||
) {
|
||||
Ok(pin) => pin,
|
||||
_ => {
|
||||
return;
|
||||
@ -809,14 +1079,14 @@ impl StateMachineCtap2 {
|
||||
)
|
||||
.map_err(HIDError::Command)
|
||||
.and_then(|msg| dev.send_cbor_cancellable(&msg, alive))
|
||||
.map_err(AuthenticatorError::HIDError)
|
||||
.map_err(|e| repackage_pin_errors(&mut dev, e));
|
||||
|
||||
if let Err(AuthenticatorError::PinError(e)) = res {
|
||||
error = e;
|
||||
if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res {
|
||||
was_invalid = true;
|
||||
retries = r;
|
||||
// We need to re-establish the shared secret for the next round.
|
||||
match dev.establish_shared_secret() {
|
||||
Ok((s, _)) => {
|
||||
Ok(s) => {
|
||||
shared_secret = s;
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -1,7 +1,53 @@
|
||||
use super::{u2ftypes, Pin, PinError};
|
||||
use serde::ser::{Serialize, SerializeStruct};
|
||||
use super::{u2ftypes, Pin};
|
||||
use serde::{
|
||||
ser::{Serialize, SerializeStruct},
|
||||
Serialize as DeriveSer, Serializer,
|
||||
};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
// Simply ignoring the Sender when serializing
|
||||
pub(crate) fn serialize_pin_required<S>(_: &Sender<Pin>, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_none()
|
||||
}
|
||||
|
||||
// Simply ignoring the Sender when serializing
|
||||
pub(crate) fn serialize_pin_invalid<S>(
|
||||
_: &Sender<Pin>,
|
||||
retries: &Option<u8>,
|
||||
s: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(r) = retries {
|
||||
s.serialize_u8(*r)
|
||||
} else {
|
||||
s.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, DeriveSer)]
|
||||
pub enum StatusPinUv {
|
||||
#[serde(serialize_with = "serialize_pin_required")]
|
||||
PinRequired(Sender<Pin>),
|
||||
#[serde(serialize_with = "serialize_pin_invalid")]
|
||||
InvalidPin(Sender<Pin>, Option<u8>),
|
||||
PinIsTooShort,
|
||||
PinIsTooLong(usize),
|
||||
InvalidUv(Option<u8>),
|
||||
// This SHOULD ever only happen for CTAP2.0 devices that
|
||||
// use internal UV (e.g. fingerprint sensors) and failed (e.g. wrong
|
||||
// finger used).
|
||||
// PinAuthInvalid, // Folded into InvalidUv
|
||||
PinAuthBlocked,
|
||||
PinBlocked,
|
||||
PinNotSet,
|
||||
UvBlocked,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StatusUpdate {
|
||||
/// Device found
|
||||
@ -12,7 +58,7 @@ pub enum StatusUpdate {
|
||||
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>),
|
||||
PinUvError(StatusPinUv),
|
||||
/// 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)
|
||||
@ -34,7 +80,7 @@ impl Serialize for StatusUpdate {
|
||||
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::PinUvError(e) => map.serialize_field("PinUvError", &e)?,
|
||||
StatusUpdate::SelectDeviceNotice => map.serialize_field("SelectDeviceNotice", &())?,
|
||||
StatusUpdate::DeviceSelected(dev_info) => {
|
||||
map.serialize_field("DeviceSelected", &dev_info)?
|
||||
@ -66,15 +112,19 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_invalid_pin() {
|
||||
fn serialize_status_pin_uv() {
|
||||
let (tx, _rx) = channel();
|
||||
let st = StatusUpdate::PinError(PinError::InvalidPin(Some(3)), tx.clone());
|
||||
let st = StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx.clone(), Some(3)));
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"PinError":{"InvalidPin":3}}"#);
|
||||
assert_eq!(&json, r#"{"PinUvError":{"InvalidPin":3}}"#);
|
||||
|
||||
let st = StatusUpdate::PinError(PinError::InvalidPin(None), tx);
|
||||
let st = StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx, None));
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"PinError":{"InvalidPin":null}}"#);
|
||||
assert_eq!(&json, r#"{"PinUvError":{"InvalidPin":null}}"#);
|
||||
|
||||
let st = StatusUpdate::PinUvError(StatusPinUv::PinBlocked);
|
||||
let json = to_string(&st).expect("Failed to serialize");
|
||||
assert_eq!(&json, r#"{"PinUvError":"PinBlocked"}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5,9 +5,10 @@
|
||||
extern crate libc;
|
||||
|
||||
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::platform::uhid;
|
||||
use crate::transport::{AuthenticatorInfo, FidoDevice, HIDError, SharedSecret};
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::from_unix_result;
|
||||
use crate::util::io_err;
|
||||
|
@ -4,9 +4,10 @@
|
||||
|
||||
extern crate libc;
|
||||
use crate::consts::CID_BROADCAST;
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::platform::{hidraw, monitor};
|
||||
use crate::transport::{AuthenticatorInfo, FidoDevice, HIDError, SharedSecret};
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::from_unix_result;
|
||||
use std::fs::OpenOptions;
|
||||
|
@ -5,9 +5,10 @@
|
||||
extern crate log;
|
||||
|
||||
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::platform::iokit::*;
|
||||
use crate::transport::{AuthenticatorInfo, FidoDevice, HIDError, SharedSecret};
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use core_foundation::base::*;
|
||||
use core_foundation::string::*;
|
||||
|
@ -1,8 +1,10 @@
|
||||
use crate::consts::HIDCmd;
|
||||
use crate::crypto::SharedSecret;
|
||||
|
||||
use crate::ctap2::commands::client_pin::GetKeyAgreement;
|
||||
use crate::ctap2::commands::get_info::{AuthenticatorInfo, GetInfo};
|
||||
use crate::crypto::{PinUvAuthProtocol, PinUvAuthToken, SharedSecret};
|
||||
use crate::ctap2::commands::client_pin::{
|
||||
GetKeyAgreement, GetPinToken, GetPinUvAuthTokenUsingPinWithPermissions,
|
||||
GetPinUvAuthTokenUsingUvWithPermissions, PinUvAuthTokenPermission,
|
||||
};
|
||||
use crate::ctap2::commands::get_info::{AuthenticatorVersion, GetInfo};
|
||||
use crate::ctap2::commands::get_version::GetVersion;
|
||||
use crate::ctap2::commands::make_credentials::dummy_make_credentials_cmd;
|
||||
use crate::ctap2::commands::selection::Selection;
|
||||
@ -13,6 +15,8 @@ use crate::transport::device_selector::BlinkResult;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::util::io_err;
|
||||
use crate::Pin;
|
||||
use std::convert::TryFrom;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -199,9 +203,9 @@ pub trait FidoDevice: HIDDevice {
|
||||
}
|
||||
|
||||
fn block_and_blink(&mut self, keep_alive: &dyn Fn() -> bool) -> BlinkResult {
|
||||
let supports_select_cmd = self
|
||||
.get_authenticator_info()
|
||||
.map_or(false, |i| i.versions.contains(&String::from("FIDO_2_1")));
|
||||
let supports_select_cmd = self.get_authenticator_info().map_or(false, |i| {
|
||||
i.versions.contains(&AuthenticatorVersion::FIDO_2_1)
|
||||
});
|
||||
let resp = if supports_select_cmd {
|
||||
let msg = Selection {};
|
||||
self.send_cbor_cancellable(&msg, keep_alive)
|
||||
@ -246,29 +250,82 @@ pub trait FidoDevice: HIDDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn establish_shared_secret(&mut self) -> Result<(SharedSecret, AuthenticatorInfo), HIDError> {
|
||||
fn establish_shared_secret(&mut self) -> Result<SharedSecret, 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
|
||||
};
|
||||
let info = self
|
||||
.get_authenticator_info()
|
||||
.ok_or(HIDError::DeviceNotInitialized)?;
|
||||
let pin_protocol = PinUvAuthProtocol::try_from(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 pin_command = GetKeyAgreement::new(pin_protocol);
|
||||
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))
|
||||
Ok(shared_secret)
|
||||
}
|
||||
|
||||
/// CTAP 2.0-only version:
|
||||
/// "Getting pinUvAuthToken using getPinToken (superseded)"
|
||||
fn get_pin_token(&mut self, pin: &Option<Pin>) -> Result<PinUvAuthToken, HIDError> {
|
||||
// Asking the user for PIN before establishing the shared secret
|
||||
let pin = pin
|
||||
.as_ref()
|
||||
.ok_or(CommandError::StatusCode(StatusCode::PinRequired, None))?;
|
||||
|
||||
// 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 = self.establish_shared_secret()?;
|
||||
|
||||
let pin_command = GetPinToken::new(&shared_secret, pin);
|
||||
let pin_token = self.send_cbor(&pin_command)?;
|
||||
|
||||
Ok(pin_token)
|
||||
}
|
||||
|
||||
fn get_pin_uv_auth_token_using_uv_with_permissions(
|
||||
&mut self,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
rp_id: Option<&String>,
|
||||
) -> Result<PinUvAuthToken, HIDError> {
|
||||
// Explicitly not reusing the shared secret here
|
||||
let shared_secret = self.establish_shared_secret()?;
|
||||
let pin_command = GetPinUvAuthTokenUsingUvWithPermissions::new(
|
||||
&shared_secret,
|
||||
permission,
|
||||
rp_id.cloned(),
|
||||
);
|
||||
let pin_auth_token = self.send_cbor(&pin_command)?;
|
||||
|
||||
Ok(pin_auth_token)
|
||||
}
|
||||
|
||||
fn get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
&mut self,
|
||||
pin: &Option<Pin>,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
rp_id: Option<&String>,
|
||||
) -> Result<PinUvAuthToken, HIDError> {
|
||||
// Asking the user for PIN before establishing the shared secret
|
||||
let pin = pin
|
||||
.as_ref()
|
||||
.ok_or(CommandError::StatusCode(StatusCode::PinRequired, None))?;
|
||||
|
||||
// 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 = self.establish_shared_secret()?;
|
||||
let pin_command = GetPinUvAuthTokenUsingPinWithPermissions::new(
|
||||
&shared_secret,
|
||||
pin,
|
||||
permission,
|
||||
rp_id.cloned(),
|
||||
);
|
||||
let pin_auth_token = self.send_cbor(&pin_command)?;
|
||||
|
||||
Ok(pin_auth_token)
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,12 @@
|
||||
|
||||
extern crate libc;
|
||||
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::platform::fd::Fd;
|
||||
use crate::transport::platform::monitor::WrappedOpenDevice;
|
||||
use crate::transport::platform::uhid;
|
||||
use crate::transport::{AuthenticatorInfo, FidoDevice, HIDError, SharedSecret};
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::io_err;
|
||||
use std::ffi::OsString;
|
||||
|
@ -4,9 +4,10 @@
|
||||
|
||||
extern crate libc;
|
||||
use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::platform::monitor::WrappedOpenDevice;
|
||||
use crate::transport::{AuthenticatorInfo, FidoDevice, HIDError, SharedSecret};
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::{from_unix_result, io_err};
|
||||
use std::ffi::{CString, OsString};
|
||||
|
@ -2,9 +2,10 @@
|
||||
* 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::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::FidoDevice;
|
||||
use crate::transport::{AuthenticatorInfo, HIDError, SharedSecret};
|
||||
use crate::transport::{HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use std::hash::Hash;
|
||||
use std::io;
|
||||
|
@ -4,8 +4,9 @@
|
||||
|
||||
use super::winapi::DeviceCapabilities;
|
||||
use crate::consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::{AuthenticatorInfo, FidoDevice, HIDError, SharedSecret};
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -216,7 +216,7 @@ impl DeviceInterfaceDetailData {
|
||||
}
|
||||
|
||||
fn path(&self) -> String {
|
||||
unsafe { from_wide_ptr((*self.data).DevicePath.as_ptr(), self.path_len - 2) }
|
||||
unsafe { from_wide_ptr(ptr::addr_of!((*self.data).DevicePath[0]), self.path_len - 2) }
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user