зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset e637dc2466d4 (bug 1837473) for breaking CTAP2 PIN entry. a=backout
This commit is contained in:
Родитель
f36cc743ce
Коммит
bf80c8e256
|
@ -309,9 +309,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.16"
|
||||
version = "0.4.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80dfc2f20aa0c1135069b1076e0cb3372b9c051f928d3a3adebbd5c3dea40afe"
|
||||
checksum = "aa0e182b77b6b19eaf9c7b69fddf3be970169ec6d34eca3f5d682ab948727e57"
|
||||
dependencies = [
|
||||
"base64 0.13.999",
|
||||
"bitflags 1.3.2",
|
||||
|
@ -322,6 +322,7 @@ dependencies = [
|
|||
"libudev",
|
||||
"log",
|
||||
"memoffset",
|
||||
"nom",
|
||||
"nss-gk-api",
|
||||
"pkcs11-bindings",
|
||||
"rand",
|
||||
|
@ -3676,9 +3677,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nss-gk-api"
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c17aec6d4e1822c023689899f09311592a36cbf6de8f85dfaf5f01976790d8d"
|
||||
checksum = "1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352"
|
||||
dependencies = [
|
||||
"bindgen 0.64.0",
|
||||
"mozbuild",
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
authors = ["Martin Sirringhaus", "John Schanck"]
|
||||
|
||||
[dependencies]
|
||||
authenticator = { version = "0.4.0-alpha.16", features = ["gecko"] }
|
||||
authenticator = { version = "0.4.0-alpha.15", features = ["gecko"] }
|
||||
log = "0.4"
|
||||
moz_task = { path = "../../../xpcom/rust/moz_task" }
|
||||
nserror = { path = "../../../xpcom/rust/nserror" }
|
||||
|
|
|
@ -304,12 +304,24 @@ fn status_callback(
|
|||
) {
|
||||
loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
debug!("STATUS: device available: {}", dev_info)
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
debug!("STATUS: device unavailable: {}", dev_info)
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
debug!("STATUS: success using device: {}", dev_info);
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
debug!("STATUS: Please select a device by touching one of them.");
|
||||
let notification_str =
|
||||
make_prompt("select-device", tid, origin, browsing_context_id);
|
||||
controller.send_prompt(tid, ¬ification_str);
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
debug!("STATUS: Continuing with device: {}", dev_info);
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
debug!("STATUS: Waiting for user presence");
|
||||
let notification_str = make_prompt("presence", tid, origin, browsing_context_id);
|
||||
|
@ -374,8 +386,11 @@ fn status_callback(
|
|||
// These should never happen.
|
||||
warn!("STATUS: Got unexpected StatusPinUv-error.");
|
||||
}
|
||||
Ok(StatusUpdate::InteractiveManagement((_, auth_info))) => {
|
||||
debug!("STATUS: interactive management: {:?}", auth_info);
|
||||
Ok(StatusUpdate::InteractiveManagement((_, dev_info, auth_info))) => {
|
||||
debug!(
|
||||
"STATUS: interactive management: {}, {:?}",
|
||||
dev_info, auth_info
|
||||
);
|
||||
}
|
||||
Err(RecvError) => {
|
||||
debug!("STATUS: end");
|
||||
|
|
|
@ -285,14 +285,6 @@ start = "2020-11-03"
|
|||
end = "2024-03-31"
|
||||
notes = "Maintained by the DevTools team at Mozilla and has no unsafe code."
|
||||
|
||||
[[wildcard-audits.nss-gk-api]]
|
||||
who = "John M. Schanck <jschanck@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
user-id = 175410 # John Schanck (jschanck)
|
||||
start = "2022-11-14"
|
||||
end = "2024-06-14"
|
||||
notes = "Maintained by the CryptoEng team at Mozilla."
|
||||
|
||||
[[wildcard-audits.ohttp]]
|
||||
who = "Martin Thomson <mt@lowentropy.net>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -43,13 +43,6 @@ user-id = 175410
|
|||
user-login = "jschanck"
|
||||
user-name = "John Schanck"
|
||||
|
||||
[[publisher.authenticator]]
|
||||
version = "0.4.0-alpha.16"
|
||||
when = "2023-06-14"
|
||||
user-id = 175410
|
||||
user-login = "jschanck"
|
||||
user-name = "John Schanck"
|
||||
|
||||
[[publisher.bhttp]]
|
||||
version = "0.3.1"
|
||||
when = "2023-02-23"
|
||||
|
@ -302,13 +295,6 @@ user-id = 10
|
|||
user-login = "carllerche"
|
||||
user-name = "Carl Lerche"
|
||||
|
||||
[[publisher.nss-gk-api]]
|
||||
version = "0.3.0"
|
||||
when = "2023-06-14"
|
||||
user-id = 175410
|
||||
user-login = "jschanck"
|
||||
user-name = "John Schanck"
|
||||
|
||||
[[publisher.num_cpus]]
|
||||
version = "1.15.0"
|
||||
when = "2022-12-20"
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -39,7 +39,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.16"
|
||||
version = "0.4.0-alpha.15"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64",
|
||||
|
@ -55,6 +55,7 @@ dependencies = [
|
|||
"libudev",
|
||||
"log",
|
||||
"memoffset",
|
||||
"nom 7.1.1",
|
||||
"nss-gk-api",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
|
@ -175,9 +176,9 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.74"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
|
||||
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
|
@ -543,9 +544,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.23"
|
||||
version = "0.14.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
|
||||
dependencies = [
|
||||
"bytes 1.2.1",
|
||||
"futures-channel",
|
||||
|
@ -614,15 +615,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
version = "0.2.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
|
@ -752,9 +753,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nss-gk-api"
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c17aec6d4e1822c023689899f09311592a36cbf6de8f85dfaf5f01976790d8d"
|
||||
checksum = "1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352"
|
||||
dependencies = [
|
||||
"bindgen 0.61.0",
|
||||
"mozbuild",
|
||||
|
@ -767,9 +768,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.14.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
|
@ -777,9 +778,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
|
@ -875,15 +876,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.26"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
|
@ -950,9 +951,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -961,9 +962,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
|
@ -1019,9 +1020,9 @@ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
|||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
|
@ -1087,9 +1088,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.10.1"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
|
||||
checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
|
@ -1247,9 +1248,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.2"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[package]
|
||||
edition = "2018"
|
||||
name = "authenticator"
|
||||
version = "0.4.0-alpha.16"
|
||||
version = "0.4.0-alpha.15"
|
||||
authors = [
|
||||
"J.C. Jones <jc@mozilla.com>",
|
||||
"Tim Taubert <ttaubert@mozilla.com>",
|
||||
|
@ -54,8 +54,13 @@ version = "0.2"
|
|||
[dependencies.log]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.nom]
|
||||
version = "^7.1.1"
|
||||
features = ["std"]
|
||||
default-features = false
|
||||
|
||||
[dependencies.nss-gk-api]
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.openssl]
|
||||
|
|
|
@ -47,8 +47,6 @@ fn main() {
|
|||
"ioctl_s390xbe.rs"
|
||||
} else if cfg!(all(target_arch = "riscv64", target_endian = "little")) {
|
||||
"ioctl_riscv64.rs"
|
||||
} else if cfg!(all(target_arch = "loongarch64", target_endian = "little")) {
|
||||
"ioctl_loongarch64.rs"
|
||||
} else {
|
||||
panic!("architecture not supported");
|
||||
};
|
||||
|
|
|
@ -7,13 +7,12 @@ use authenticator::{
|
|||
AuthenticatorService, GetAssertionExtensions, HmacSecretExtension,
|
||||
MakeCredentialsExtensions, RegisterArgs, SignArgs,
|
||||
},
|
||||
crypto::COSEAlgorithm,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
},
|
||||
statecallback::StateCallback,
|
||||
Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
@ -88,9 +87,21 @@ fn main() {
|
|||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
println!("STATUS: device unavailable: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
}
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
|
||||
use authenticator::{
|
||||
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
|
||||
crypto::COSEAlgorithm,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
|
||||
},
|
||||
statecallback::StateCallback,
|
||||
Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
use getopts::Options;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
@ -51,9 +50,21 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
|
|||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
println!("STATUS: device unavailable: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
}
|
||||
|
@ -235,9 +246,21 @@ fn main() {
|
|||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
println!("STATUS: device unavailable: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
}
|
||||
|
|
|
@ -20,10 +20,15 @@ fn print_usage(program: &str, opts: Options) {
|
|||
}
|
||||
|
||||
fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
|
||||
let mut num_of_devices = 0;
|
||||
loop {
|
||||
match status_rx.recv() {
|
||||
Ok(StatusUpdate::InteractiveManagement((tx, auth_info))) => {
|
||||
debug!("STATUS: interactive management: {:#?}", auth_info);
|
||||
Ok(StatusUpdate::InteractiveManagement((tx, dev_info, auth_info))) => {
|
||||
debug!(
|
||||
"STATUS: interactive management: {:#}, {:#?}",
|
||||
dev_info, auth_info
|
||||
);
|
||||
println!("Device info {:#}", dev_info);
|
||||
let mut change_pin = false;
|
||||
if let Some(info) = auth_info {
|
||||
println!("Authenticator Info {:#?}", info);
|
||||
|
@ -93,9 +98,27 @@ fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
|
|||
println!("Device only supports CTAP1 and can't be managed.");
|
||||
}
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
num_of_devices += 1;
|
||||
debug!(
|
||||
"STATUS: New device #{} available: {}",
|
||||
num_of_devices, dev_info
|
||||
);
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
num_of_devices -= 1;
|
||||
if num_of_devices <= 0 {
|
||||
println!("No more devices left. Please plug in a device!");
|
||||
}
|
||||
debug!("STATUS: Device became unavailable: {}", dev_info)
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {}", dev_info);
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(_dev_info)) => {}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
}
|
||||
|
|
|
@ -105,6 +105,9 @@ fn main() {
|
|||
manager.cancel().unwrap();
|
||||
return;
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
break;
|
||||
|
|
|
@ -73,9 +73,21 @@ fn main() {
|
|||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
println!("STATUS: device unavailable: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
use authenticator::{
|
||||
authenticatorservice::{AuthenticatorService, GetAssertionExtensions, RegisterArgs, SignArgs},
|
||||
crypto::COSEAlgorithm,
|
||||
ctap2::commands::StatusCode,
|
||||
ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
|
@ -12,7 +11,7 @@ use authenticator::{
|
|||
},
|
||||
errors::{AuthenticatorError, CommandError, HIDError, UnsupportedOption},
|
||||
statecallback::StateCallback,
|
||||
Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
|
||||
};
|
||||
|
||||
use getopts::Options;
|
||||
|
@ -81,9 +80,21 @@ fn main() {
|
|||
Ok(StatusUpdate::InteractiveManagement(..)) => {
|
||||
panic!("STATUS: This can't happen when doing non-interactive usage");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
|
||||
println!("STATUS: device available: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
|
||||
println!("STATUS: device unavailable: {dev_info}")
|
||||
}
|
||||
Ok(StatusUpdate::Success { dev_info }) => {
|
||||
println!("STATUS: success using device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||
println!("STATUS: Please select a device by touching one of them.");
|
||||
}
|
||||
Ok(StatusUpdate::DeviceSelected(dev_info)) => {
|
||||
println!("STATUS: Continuing with device: {dev_info}");
|
||||
}
|
||||
Ok(StatusUpdate::PresenceRequired) => {
|
||||
println!("STATUS: waiting for user presence");
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ impl AuthenticatorService {
|
|||
self.add_u2f_usb_hid_platform_transports();
|
||||
}
|
||||
|
||||
pub fn add_transport(&mut self, boxed_token: Box<dyn AuthenticatorTransport + Send>) {
|
||||
fn add_transport(&mut self, boxed_token: Box<dyn AuthenticatorTransport + Send>) {
|
||||
self.transports.push(Arc::new(Mutex::new(boxed_token)))
|
||||
}
|
||||
|
||||
|
|
|
@ -37,21 +37,3 @@ pub fn sha256(_data: &[u8]) -> Result<Vec<u8>> {
|
|||
pub fn random_bytes(_count: usize) -> Result<Vec<u8>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn ecdsa_p256_sha256_sign_raw(_private: &[u8], _data: &[u8]) -> Result<Vec<u8>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
pub fn test_ecdsa_p256_sha256_verify_raw(
|
||||
_public: &[u8],
|
||||
_signature: &[u8],
|
||||
_data: &[u8],
|
||||
) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
|
@ -32,12 +32,10 @@ mod dummy;
|
|||
use dummy as backend;
|
||||
|
||||
use backend::{
|
||||
decrypt_aes_256_cbc_no_pad, ecdhe_p256_raw, encrypt_aes_256_cbc_no_pad, gen_p256, hmac_sha256,
|
||||
decrypt_aes_256_cbc_no_pad, ecdhe_p256_raw, encrypt_aes_256_cbc_no_pad, hmac_sha256,
|
||||
random_bytes, sha256,
|
||||
};
|
||||
|
||||
pub use backend::ecdsa_p256_sha256_sign_raw;
|
||||
|
||||
// Object identifiers in DER tag-length-value form
|
||||
const DER_OID_EC_PUBLIC_KEY_BYTES: &[u8] = &[
|
||||
0x06, 0x07,
|
||||
|
@ -152,26 +150,11 @@ impl TryFrom<&AuthenticatorInfo> for PinUvAuthProtocol {
|
|||
// "If there are multiple mutually supported protocols, and the platform
|
||||
// has no preference, it SHOULD select the one listed first in
|
||||
// pinUvAuthProtocols."
|
||||
if let Some(pin_protocols) = &info.pin_protocols {
|
||||
for proto_id in pin_protocols.iter() {
|
||||
match proto_id {
|
||||
1 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))),
|
||||
2 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match info.max_supported_version() {
|
||||
crate::ctap2::commands::get_info::AuthenticatorVersion::U2F_V2 => {
|
||||
return Err(CommandError::UnsupportedPinProtocol)
|
||||
}
|
||||
crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_0 => {
|
||||
return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {})))
|
||||
}
|
||||
crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1_PRE
|
||||
| crate::ctap2::commands::get_info::AuthenticatorVersion::FIDO_2_1 => {
|
||||
return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {})))
|
||||
}
|
||||
for proto_id in info.pin_protocols.iter() {
|
||||
match proto_id {
|
||||
1 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth1 {}))),
|
||||
2 => return Ok(PinUvAuthProtocol(Box::new(PinUvAuth2 {}))),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Err(CommandError::UnsupportedPinProtocol)
|
||||
|
@ -883,23 +866,6 @@ pub struct COSEKey {
|
|||
pub key: COSEKeyType,
|
||||
}
|
||||
|
||||
impl COSEKey {
|
||||
/// Generates a new key pair for the specified algorithm.
|
||||
/// Returns an PKCS#8 encoding of the private key, and the public key as a COSEKey.
|
||||
pub fn generate(alg: COSEAlgorithm) -> Result<(Vec<u8>, Self), CryptoError> {
|
||||
if alg != COSEAlgorithm::ES256 && alg != COSEAlgorithm::ECDH_ES_HKDF256 {
|
||||
return Err(CryptoError::UnsupportedAlgorithm(alg));
|
||||
}
|
||||
let (private, public) = gen_p256()?;
|
||||
let cose_ec2_key = COSEEC2Key::from_sec1_uncompressed(Curve::SECP256R1, &public)?;
|
||||
let public = COSEKey {
|
||||
alg,
|
||||
key: COSEKeyType::EC2(cose_ec2_key),
|
||||
};
|
||||
Ok((private, public))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for COSEKey {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
|
@ -1160,23 +1126,14 @@ pub fn parse_u2f_der_certificate(data: &[u8]) -> Result<U2FRegisterAnswer, Crypt
|
|||
|
||||
#[cfg(all(test, not(feature = "crypto_dummy")))]
|
||||
mod test {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[cfg(feature = "crypto_nss")]
|
||||
use super::backend::{ecdsa_p256_sha256_sign_raw, test_ecdsa_p256_sha256_verify_raw};
|
||||
use super::{
|
||||
backend::hmac_sha256, backend::sha256, backend::test_ecdh_p256_raw, COSEAlgorithm, COSEKey,
|
||||
Curve, PinProtocolImpl, PinUvAuth1, PinUvAuth2, PinUvAuthProtocol, PublicInputs,
|
||||
SharedSecret,
|
||||
};
|
||||
use crate::crypto::{COSEEC2Key, COSEKeyType};
|
||||
use crate::ctap2::attestation::AAGuid;
|
||||
use crate::ctap2::commands::client_pin::Pin;
|
||||
use crate::ctap2::commands::get_info::{
|
||||
tests::AAGUID_RAW, AuthenticatorOptions, AuthenticatorVersion,
|
||||
};
|
||||
use crate::util::decode_hex;
|
||||
use crate::AuthenticatorInfo;
|
||||
use serde_cbor::de::from_slice;
|
||||
|
||||
#[test]
|
||||
|
@ -1383,102 +1340,4 @@ mod test {
|
|||
.expect("HMAC-SHA256 failed");
|
||||
assert_eq!(pin_auth[0..16], expected_pin_auth);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pin_protocol() {
|
||||
let mut info = AuthenticatorInfo {
|
||||
versions: vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0],
|
||||
extensions: vec![],
|
||||
aaguid: AAGuid(AAGUID_RAW),
|
||||
options: AuthenticatorOptions {
|
||||
platform_device: false,
|
||||
resident_key: true,
|
||||
client_pin: Some(false),
|
||||
user_presence: true,
|
||||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Valid pin_protocols
|
||||
info.pin_protocols = Some(vec![1, 2]);
|
||||
let pin = PinUvAuthProtocol::try_from(&info).unwrap();
|
||||
assert_eq!(pin.id(), 1); // The one listed first
|
||||
|
||||
// Invalid pin_protocols
|
||||
info.pin_protocols = Some(vec![0, 10]);
|
||||
PinUvAuthProtocol::try_from(&info).unwrap_err();
|
||||
|
||||
info.pin_protocols = None;
|
||||
// No PIN protocols. CTAP1 - not supported
|
||||
info.versions = vec![AuthenticatorVersion::U2F_V2];
|
||||
PinUvAuthProtocol::try_from(&info).unwrap_err();
|
||||
|
||||
// No PIN protocols. CTAP2.0 - Fallback to 1
|
||||
info.versions = vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0];
|
||||
let pin = PinUvAuthProtocol::try_from(&info).unwrap();
|
||||
assert_eq!(pin.id(), 1);
|
||||
|
||||
// No PIN protocols. CTAP2.1 - Fallback to 2
|
||||
info.versions = vec![AuthenticatorVersion::FIDO_2_1];
|
||||
let pin = PinUvAuthProtocol::try_from(&info).unwrap();
|
||||
assert_eq!(pin.id(), 2);
|
||||
|
||||
// No PIN protocols. CTAP2.1_PRE - Fallback to 2
|
||||
info.versions = vec![
|
||||
AuthenticatorVersion::FIDO_2_0,
|
||||
AuthenticatorVersion::FIDO_2_1_PRE,
|
||||
];
|
||||
let pin = PinUvAuthProtocol::try_from(&info).unwrap();
|
||||
assert_eq!(pin.id(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "crypto_nss")]
|
||||
fn test_sign() {
|
||||
let (good_private, good_public) =
|
||||
COSEKey::generate(COSEAlgorithm::ES256).expect("could not generate a key pair");
|
||||
let good_spki = match good_public.key {
|
||||
COSEKeyType::EC2(ref x) => x.der_spki().expect("could not serialize public key"),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let good_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let good_signature =
|
||||
ecdsa_p256_sha256_sign_raw(&good_private, &good_data).expect("could not sign");
|
||||
let good_signature2 =
|
||||
ecdsa_p256_sha256_sign_raw(&good_private, &good_data).expect("could not sign");
|
||||
|
||||
// Signing is randomized
|
||||
assert_ne!(good_signature, good_signature2);
|
||||
|
||||
// Good signature verifies
|
||||
assert!(test_ecdsa_p256_sha256_verify_raw(&good_spki, &good_signature, &good_data).is_ok());
|
||||
|
||||
// Wrong data does not verify
|
||||
let other_data = vec![0, 0, 0, 0, 5, 6, 7, 8];
|
||||
assert!(
|
||||
test_ecdsa_p256_sha256_verify_raw(&good_spki, &good_signature, &other_data).is_err()
|
||||
);
|
||||
|
||||
// Wrong signature does not verify
|
||||
let other_signature =
|
||||
ecdsa_p256_sha256_sign_raw(&good_private, &other_data).expect("could not sign");
|
||||
assert!(
|
||||
test_ecdsa_p256_sha256_verify_raw(&good_spki, &other_signature, &good_data).is_err()
|
||||
);
|
||||
|
||||
// Wrong key does not verify
|
||||
let (_, other_public) =
|
||||
COSEKey::generate(COSEAlgorithm::ES256).expect("could not generate a key pair");
|
||||
let other_spki = match other_public.key {
|
||||
COSEKeyType::EC2(ref x) => x.der_spki().expect("could not serialize public key"),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
assert!(
|
||||
test_ecdsa_p256_sha256_verify_raw(&other_spki, &good_signature, &good_data).is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
use super::{CryptoError, DER_OID_P256_BYTES};
|
||||
use nss_gk_api::p11::{
|
||||
PK11Origin, PK11_CreateContextBySymKey, PK11_Decrypt, PK11_DigestFinal, PK11_DigestOp,
|
||||
PK11_Encrypt, PK11_ExportDERPrivateKeyInfo, PK11_GenerateKeyPairWithOpFlags,
|
||||
PK11_GenerateRandom, PK11_HashBuf, PK11_ImportDERPrivateKeyInfoAndReturnKey, PK11_ImportSymKey,
|
||||
PK11_PubDeriveWithKDF, PK11_SignWithMechanism, PrivateKey, PublicKey,
|
||||
PK11_Encrypt, PK11_GenerateKeyPairWithOpFlags, PK11_GenerateRandom, PK11_HashBuf,
|
||||
PK11_ImportSymKey, PK11_PubDeriveWithKDF, PrivateKey, PublicKey,
|
||||
SECKEY_DecodeDERSubjectPublicKeyInfo, SECKEY_ExtractPublicKey, SECOidTag, Slot,
|
||||
SubjectPublicKeyInfo, AES_BLOCK_SIZE, PK11_ATTR_EXTRACTABLE, PK11_ATTR_INSENSITIVE,
|
||||
PK11_ATTR_SESSION, SHA256_LENGTH,
|
||||
SubjectPublicKeyInfo, AES_BLOCK_SIZE, PK11_ATTR_SESSION, SHA256_LENGTH,
|
||||
};
|
||||
use nss_gk_api::{IntoResult, SECItem, SECItemBorrowed, ScopedSECItem, PR_FALSE};
|
||||
use nss_gk_api::{IntoResult, SECItem, SECItemBorrowed, PR_FALSE};
|
||||
use pkcs11_bindings::{
|
||||
CKA_DERIVE, CKA_ENCRYPT, CKA_SIGN, CKD_NULL, CKF_DERIVE, CKM_AES_CBC, CKM_ECDH1_DERIVE,
|
||||
CKM_ECDSA_SHA256, CKM_EC_KEY_PAIR_GEN, CKM_SHA256_HMAC, CKM_SHA512_HMAC,
|
||||
CKM_EC_KEY_PAIR_GEN, CKM_SHA256_HMAC, CKM_SHA512_HMAC,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::os::raw::{c_int, c_uint};
|
||||
use std::ptr;
|
||||
|
||||
const DER_TAG_INTEGER: u8 = 0x02;
|
||||
const DER_TAG_SEQUENCE: u8 = 0x30;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::DER_OID_EC_PUBLIC_KEY_BYTES;
|
||||
|
||||
#[cfg(test)]
|
||||
use nss_gk_api::p11::PK11_VerifyWithMechanism;
|
||||
use nss_gk_api::p11::PK11_ImportDERPrivateKeyInfoAndReturnKey;
|
||||
|
||||
impl From<nss_gk_api::Error> for CryptoError {
|
||||
fn from(e: nss_gk_api::Error) -> Self {
|
||||
|
@ -34,118 +29,6 @@ impl From<nss_gk_api::Error> for CryptoError {
|
|||
|
||||
pub type Result<T> = std::result::Result<T, CryptoError>;
|
||||
|
||||
// DER encode a pair of 32 byte unsigned integers as an RFC 3279 Ecdsa-Sig-Value.
|
||||
// Ecdsa-Sig-Value ::= SEQUENCE {
|
||||
// r INTEGER,
|
||||
// s INTEGER }.
|
||||
fn encode_der_p256_sig(r: &[u8], s: &[u8]) -> Result<Vec<u8>> {
|
||||
if r.len() != 32 || s.len() != 32 {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
// Each of the inputs is no more than 32 bytes as an unsigned integer. So each is no more than
|
||||
// 33 bytes as a signed integer and no more than 35 bytes with tag and length. The surrounding
|
||||
// tag and length for the SEQUENCE has length 2, so the output is no more than 72 bytes.
|
||||
let mut out = Vec::with_capacity(72);
|
||||
out.push(DER_TAG_SEQUENCE);
|
||||
out.push(0xaa); // placeholder for final length
|
||||
|
||||
let encode_u256 = |out: &mut Vec<u8>, r: &[u8]| {
|
||||
// trim leading zeros, leaving a single zero if the input is the zero vector.
|
||||
let mut r = r;
|
||||
while r.len() > 1 && r[0] == 0 {
|
||||
r = &r[1..];
|
||||
}
|
||||
out.push(DER_TAG_INTEGER);
|
||||
if r[0] & 0x80 != 0 {
|
||||
// Pad with a zero byte to avoid r being interpreted as a negative value.
|
||||
out.push((r.len() + 1) as u8);
|
||||
out.push(0x00);
|
||||
} else {
|
||||
out.push(r.len() as u8);
|
||||
}
|
||||
out.extend_from_slice(r);
|
||||
};
|
||||
|
||||
encode_u256(&mut out, r);
|
||||
encode_u256(&mut out, s);
|
||||
|
||||
// Write the length of the sequence
|
||||
out[1] = (out.len() - 2) as u8;
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
// Given "tag || len || value || rest" where tag and len are of length one, len is in [0, 127],
|
||||
// and value is of length len, returns (value, rest)
|
||||
#[cfg(test)]
|
||||
fn der_expect_tag_with_short_len(tag: u8, z: &[u8]) -> Result<(&[u8], &[u8])> {
|
||||
if z.is_empty() {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
let (h, z) = z.split_at(1);
|
||||
if h[0] != tag || z.is_empty() {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
let (h, z) = z.split_at(1);
|
||||
if h[0] >= 0x80 || h[0] as usize > z.len() {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
Ok(z.split_at(h[0] as usize))
|
||||
}
|
||||
|
||||
// Given a DER encoded RFC 3279 Ecdsa-Sig-Value,
|
||||
// Ecdsa-Sig-Value ::= SEQUENCE {
|
||||
// r INTEGER,
|
||||
// s INTEGER },
|
||||
// with r and s < 2^256, returns a 64 byte array containing
|
||||
// r and s encoded as 32 byte zero-padded big endian unsigned
|
||||
// integers
|
||||
#[cfg(test)]
|
||||
fn decode_der_p256_sig(z: &[u8]) -> Result<Vec<u8>> {
|
||||
// Strip the tag and length.
|
||||
let (z, rest) = der_expect_tag_with_short_len(DER_TAG_SEQUENCE, z)?;
|
||||
|
||||
// The input should not have any trailing data.
|
||||
if !rest.is_empty() {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
|
||||
let read_u256 = |z| -> Result<(&[u8], &[u8])> {
|
||||
let (r, z) = der_expect_tag_with_short_len(DER_TAG_INTEGER, z)?;
|
||||
// We're expecting r < 2^256, so no more than 33 bytes as a signed integer.
|
||||
if r.is_empty() || r.len() > 33 {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
// If it is 33 bytes the leading byte must be zero.
|
||||
if r.len() == 33 && r[0] != 0 {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
// Ensure r is no more than 32 bytes.
|
||||
if r.len() == 33 {
|
||||
Ok((&r[1..], z))
|
||||
} else {
|
||||
Ok((r, z))
|
||||
}
|
||||
};
|
||||
|
||||
let (r, z) = read_u256(z)?;
|
||||
let (s, z) = read_u256(z)?;
|
||||
|
||||
// We should have consumed the entire buffer
|
||||
if !z.is_empty() {
|
||||
return Err(CryptoError::MalformedInput);
|
||||
}
|
||||
|
||||
// Left pad each integer with zeros to length 32 and concatenate the results
|
||||
let mut out = vec![0u8; 64];
|
||||
{
|
||||
let (r_out, s_out) = out.split_at_mut(32);
|
||||
r_out[32 - r.len()..].copy_from_slice(r);
|
||||
s_out[32 - s.len()..].copy_from_slice(s);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn nss_public_key_from_der_spki(spki: &[u8]) -> Result<PublicKey> {
|
||||
// TODO: replace this with an nss-gk-api function
|
||||
// https://github.com/mozilla/nss-gk-api/issues/7
|
||||
|
@ -182,7 +65,15 @@ fn ecdh_nss_raw(client_private: PrivateKey, peer_public: PublicKey) -> Result<Ve
|
|||
Ok(ecdh_x_coord_bytes.to_vec())
|
||||
}
|
||||
|
||||
fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> {
|
||||
/// Ephemeral ECDH over P256. Takes a DER SubjectPublicKeyInfo that encodes a public key. Generates
|
||||
/// an ephemeral P256 key pair. Returns
|
||||
/// 1) the x coordinate of the shared point, and
|
||||
/// 2) the uncompressed SEC 1 encoding of the ephemeral public key.
|
||||
pub fn ecdhe_p256_raw(peer_spki: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
nss_gk_api::init();
|
||||
|
||||
let peer_public = nss_public_key_from_der_spki(peer_spki)?;
|
||||
|
||||
// Hard-coding the P256 OID here is easier than extracting a group name from peer_public and
|
||||
// comparing it with P256. We'll fail in `PK11_GenerateKeyPairWithOpFlags` if peer_public is on
|
||||
// the wrong curve.
|
||||
|
@ -197,7 +88,7 @@ fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> {
|
|||
// `PublicKey::from_ptr` calls here, so I've wrapped them in the same unsafe block as a
|
||||
// warning. TODO(jms) Replace this once there is a safer alternative.
|
||||
// https://github.com/mozilla/nss-gk-api/issues/1
|
||||
unsafe {
|
||||
let (client_private, client_public) = unsafe {
|
||||
let client_private =
|
||||
// Type of `param` argument depends on mechanism. For EC keygen it is
|
||||
// `SECKEYECParams *` which is a typedef for `SECItem *`.
|
||||
|
@ -206,7 +97,7 @@ fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> {
|
|||
CKM_EC_KEY_PAIR_GEN,
|
||||
oid_ptr.cast(),
|
||||
&mut client_public_ptr,
|
||||
PK11_ATTR_EXTRACTABLE | PK11_ATTR_INSENSITIVE | PK11_ATTR_SESSION,
|
||||
PK11_ATTR_SESSION,
|
||||
CKF_DERIVE,
|
||||
CKF_DERIVE,
|
||||
ptr::null_mut(),
|
||||
|
@ -215,75 +106,9 @@ fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> {
|
|||
|
||||
let client_public = PublicKey::from_ptr(client_public_ptr)?;
|
||||
|
||||
Ok((client_private, client_public))
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns a PKCS#8 ECPrivateKey and an uncompressed SEC1 public key.
|
||||
pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
nss_gk_api::init();
|
||||
|
||||
let (client_private, client_public) = generate_p256_nss()?;
|
||||
|
||||
let pkcs8_priv = unsafe {
|
||||
let pkcs8_priv_item: ScopedSECItem =
|
||||
PK11_ExportDERPrivateKeyInfo(*client_private, ptr::null_mut()).into_result()?;
|
||||
pkcs8_priv_item.into_vec()
|
||||
(client_private, client_public)
|
||||
};
|
||||
|
||||
let sec1_pub = client_public.key_data()?;
|
||||
|
||||
Ok((pkcs8_priv, sec1_pub))
|
||||
}
|
||||
|
||||
pub fn ecdsa_p256_sha256_sign_raw(private: &[u8], data: &[u8]) -> Result<Vec<u8>> {
|
||||
nss_gk_api::init();
|
||||
|
||||
let slot = Slot::internal()?;
|
||||
|
||||
let imported_private: PrivateKey = unsafe {
|
||||
let mut imported_private_ptr = ptr::null_mut();
|
||||
PK11_ImportDERPrivateKeyInfoAndReturnKey(
|
||||
*slot,
|
||||
SECItemBorrowed::wrap(private).as_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
PR_FALSE,
|
||||
PR_FALSE,
|
||||
255, /* todo: expose KU_ flags in nss-gk-api */
|
||||
&mut imported_private_ptr,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
imported_private_ptr.into_result()?
|
||||
};
|
||||
|
||||
let signature_buf = vec![0; 64];
|
||||
unsafe {
|
||||
PK11_SignWithMechanism(
|
||||
*imported_private,
|
||||
CKM_ECDSA_SHA256,
|
||||
ptr::null_mut(),
|
||||
SECItemBorrowed::wrap(&signature_buf).as_mut(),
|
||||
SECItemBorrowed::wrap(data).as_mut(),
|
||||
)
|
||||
.into_result()?;
|
||||
}
|
||||
|
||||
let (r, s) = signature_buf.split_at(32);
|
||||
encode_der_p256_sig(r, s)
|
||||
}
|
||||
|
||||
/// Ephemeral ECDH over P256. Takes a DER SubjectPublicKeyInfo that encodes a public key. Generates
|
||||
/// an ephemeral P256 key pair. Returns
|
||||
/// 1) the x coordinate of the shared point, and
|
||||
/// 2) the uncompressed SEC 1 encoding of the ephemeral public key.
|
||||
pub fn ecdhe_p256_raw(peer_spki: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
nss_gk_api::init();
|
||||
|
||||
let peer_public = nss_public_key_from_der_spki(peer_spki)?;
|
||||
|
||||
let (client_private, client_public) = generate_p256_nss()?;
|
||||
|
||||
let shared_point = ecdh_nss_raw(client_private, peer_public)?;
|
||||
|
||||
Ok((shared_point, client_public.key_data()?))
|
||||
|
@ -475,8 +300,6 @@ pub fn sha256(data: &[u8]) -> Result<Vec<u8>> {
|
|||
}
|
||||
|
||||
pub fn random_bytes(count: usize) -> Result<Vec<u8>> {
|
||||
nss_gk_api::init();
|
||||
|
||||
let count_cint: c_int = match c_int::try_from(count) {
|
||||
Ok(c) => c,
|
||||
_ => return Err(CryptoError::LibraryFailure),
|
||||
|
@ -572,27 +395,3 @@ pub fn test_ecdh_p256_raw(
|
|||
|
||||
Ok(shared_point)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_ecdsa_p256_sha256_verify_raw(
|
||||
public: &[u8],
|
||||
signature: &[u8],
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
nss_gk_api::init();
|
||||
|
||||
let signature = decode_der_p256_sig(signature)?;
|
||||
let public = nss_public_key_from_der_spki(public)?;
|
||||
unsafe {
|
||||
PK11_VerifyWithMechanism(
|
||||
*public,
|
||||
CKM_ECDSA_SHA256,
|
||||
ptr::null_mut(),
|
||||
SECItemBorrowed::wrap(&signature).as_mut(),
|
||||
SECItemBorrowed::wrap(data).as_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.into_result()?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -163,21 +163,3 @@ pub fn test_ecdh_p256_raw(
|
|||
|
||||
Ok(shared_point)
|
||||
}
|
||||
|
||||
pub fn gen_p256() -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn ecdsa_p256_sha256_sign_raw(_private: &[u8], _data: &[u8]) -> Result<Vec<u8>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
pub fn test_ecdsa_p256_sha256_verify_raw(
|
||||
_public: &[u8],
|
||||
_signature: &[u8],
|
||||
_data: &[u8],
|
||||
) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
use super::utils::{from_slice_stream, read_be_u16, read_be_u32, read_byte};
|
||||
use super::utils::from_slice_stream;
|
||||
use crate::crypto::COSEAlgorithm;
|
||||
use crate::ctap2::commands::CommandError;
|
||||
use crate::ctap2::server::RpIdHash;
|
||||
use crate::ctap2::utils::serde_parse_err;
|
||||
use crate::{crypto::COSEKey, errors::AuthenticatorError};
|
||||
use nom::{
|
||||
bytes::complete::take,
|
||||
combinator::{cond, map},
|
||||
error::Error as NomError,
|
||||
number::complete::{be_u16, be_u32, be_u8},
|
||||
Err as NomErr, IResult,
|
||||
};
|
||||
use serde::ser::{Error as SerError, SerializeMap, Serializer};
|
||||
use serde::{
|
||||
de::{Error as SerdeError, MapAccess, Visitor},
|
||||
|
@ -12,7 +18,6 @@ use serde::{
|
|||
use serde_bytes::ByteBuf;
|
||||
use serde_cbor;
|
||||
use std::fmt;
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum HmacSecretResponse {
|
||||
|
@ -81,6 +86,10 @@ impl Extension {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_extensions(input: &[u8]) -> IResult<&[u8], Extension, NomError<&[u8]>> {
|
||||
serde_to_nom(input)
|
||||
}
|
||||
|
||||
#[derive(Serialize, PartialEq, Default, Eq, Clone)]
|
||||
pub struct AAGuid(pub [u8; 16]);
|
||||
|
||||
|
@ -163,23 +172,34 @@ pub struct AttestedCredentialData {
|
|||
pub credential_public_key: COSEKey,
|
||||
}
|
||||
|
||||
fn parse_attested_cred_data<R: Read, E: SerdeError>(
|
||||
data: &mut R,
|
||||
) -> Result<AttestedCredentialData, E> {
|
||||
let mut aaguid_raw = [0u8; 16];
|
||||
data.read_exact(&mut aaguid_raw)
|
||||
.map_err(|_| serde_parse_err("AAGuid"))?;
|
||||
let aaguid = AAGuid(aaguid_raw);
|
||||
let cred_len = read_be_u16(data)?;
|
||||
let mut credential_id = vec![0u8; cred_len as usize];
|
||||
data.read_exact(&mut credential_id)
|
||||
.map_err(|_| serde_parse_err("CredentialId"))?;
|
||||
let credential_public_key = from_slice_stream(data)?;
|
||||
Ok(AttestedCredentialData {
|
||||
aaguid,
|
||||
credential_id,
|
||||
credential_public_key,
|
||||
})
|
||||
fn serde_to_nom<'a, Output>(input: &'a [u8]) -> IResult<&'a [u8], Output>
|
||||
where
|
||||
Output: Deserialize<'a>,
|
||||
{
|
||||
from_slice_stream(input)
|
||||
.map_err(|_e| nom::Err::Error(nom::error::make_error(input, nom::error::ErrorKind::NoneOf)))
|
||||
// can't use custom errorkind because of error type mismatch in parse_attested_cred_data
|
||||
//.map_err(|e| NomErr::Error(Context::Code(input, ErrorKind::Custom(e))))
|
||||
// .map_err(|_| NomErr::Error(Context::Code(input, ErrorKind::Custom(42))))
|
||||
}
|
||||
|
||||
fn parse_attested_cred_data(
|
||||
input: &[u8],
|
||||
) -> IResult<&[u8], AttestedCredentialData, NomError<&[u8]>> {
|
||||
let (rest, aaguid_res) = map(take(16u8), AAGuid::from)(input)?;
|
||||
// // We can unwrap here, since we _know_ the input will be 16 bytes error out before calling from()
|
||||
let aaguid = aaguid_res.unwrap();
|
||||
let (rest, cred_len) = be_u16(rest)?;
|
||||
let (rest, credential_id) = map(take(cred_len), Vec::from)(rest)?;
|
||||
let (rest, credential_public_key) = serde_to_nom(rest)?;
|
||||
Ok((
|
||||
rest,
|
||||
(AttestedCredentialData {
|
||||
aaguid,
|
||||
credential_id,
|
||||
credential_public_key,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -206,6 +226,36 @@ pub struct AuthenticatorData {
|
|||
pub extensions: Extension,
|
||||
}
|
||||
|
||||
fn parse_ad(input: &[u8]) -> IResult<&[u8], AuthenticatorData, NomError<&[u8]>> {
|
||||
let (rest, rp_id_hash_res) = map(take(32u8), RpIdHash::from)(input)?;
|
||||
// We can unwrap here, since we _know_ the input to from() will be 32 bytes or error out before calling from()
|
||||
let rp_id_hash = rp_id_hash_res.unwrap();
|
||||
// preserve the flags, even if some reserved values are set.
|
||||
let (rest, flags) = map(be_u8, AuthenticatorDataFlags::from_bits_truncate)(rest)?;
|
||||
let (rest, counter) = be_u32(rest)?;
|
||||
let (rest, credential_data) = cond(
|
||||
flags.contains(AuthenticatorDataFlags::ATTESTED),
|
||||
parse_attested_cred_data,
|
||||
)(rest)?;
|
||||
let (rest, extensions) = cond(
|
||||
flags.contains(AuthenticatorDataFlags::EXTENSION_DATA),
|
||||
parse_extensions,
|
||||
)(rest)?;
|
||||
// TODO(baloo): we should check for end of buffer and raise a parse
|
||||
// parse error if data is still in the buffer
|
||||
//eof!() >>
|
||||
Ok((
|
||||
rest,
|
||||
AuthenticatorData {
|
||||
rp_id_hash,
|
||||
flags,
|
||||
counter,
|
||||
credential_data,
|
||||
extensions: extensions.unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AuthenticatorData {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
@ -220,40 +270,23 @@ impl<'de> Deserialize<'de> for AuthenticatorData {
|
|||
formatter.write_str("a byte array")
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, input: &[u8]) -> Result<Self::Value, E>
|
||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: SerdeError,
|
||||
{
|
||||
let mut cursor = Cursor::new(input);
|
||||
let mut rp_id_hash_raw = [0u8; 32];
|
||||
cursor
|
||||
.read_exact(&mut rp_id_hash_raw)
|
||||
.map_err(|_| serde_parse_err("32 bytes"))?;
|
||||
let rp_id_hash = RpIdHash(rp_id_hash_raw);
|
||||
|
||||
// preserve the flags, even if some reserved values are set.
|
||||
let flags = AuthenticatorDataFlags::from_bits_truncate(read_byte(&mut cursor)?);
|
||||
let counter = read_be_u32(&mut cursor)?;
|
||||
let mut credential_data = None;
|
||||
if flags.contains(AuthenticatorDataFlags::ATTESTED) {
|
||||
credential_data = Some(parse_attested_cred_data(&mut cursor)?);
|
||||
}
|
||||
|
||||
let extensions = if flags.contains(AuthenticatorDataFlags::EXTENSION_DATA) {
|
||||
from_slice_stream(&mut cursor)?
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// TODO(baloo): we should check for end of buffer and raise a parse
|
||||
// parse error if data is still in the buffer
|
||||
Ok(AuthenticatorData {
|
||||
rp_id_hash,
|
||||
flags,
|
||||
counter,
|
||||
credential_data,
|
||||
extensions,
|
||||
})
|
||||
parse_ad(v)
|
||||
.map(|(_input, value)| value)
|
||||
.map_err(|e| match e {
|
||||
NomErr::Incomplete(nom::Needed::Size(len)) => {
|
||||
E::invalid_length(v.len(), &format!("{}", v.len() + len.get()).as_ref())
|
||||
}
|
||||
NomErr::Incomplete(nom::Needed::Unknown) => {
|
||||
E::invalid_length(v.len(), &"unknown") // We don't know the expected value
|
||||
}
|
||||
// TODO(baloo): is that enough? should we be more
|
||||
// specific on the error type?
|
||||
e => E::custom(e.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -772,21 +805,12 @@ mod test {
|
|||
let v: Vec<u8> = vec![
|
||||
0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72,
|
||||
];
|
||||
let mut data = Cursor::new(v);
|
||||
let value: String = from_slice_stream::<_, _, serde_cbor::Error>(&mut data).unwrap();
|
||||
let (rest, value): (&[u8], String) = from_slice_stream(&v).unwrap();
|
||||
assert_eq!(value, "foobar");
|
||||
let mut remaining = Vec::new();
|
||||
data.read_to_end(&mut remaining).unwrap();
|
||||
assert_eq!(
|
||||
remaining.as_slice(),
|
||||
&[0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]
|
||||
);
|
||||
let mut data = Cursor::new(remaining);
|
||||
let value: String = from_slice_stream::<_, _, serde_cbor::Error>(&mut data).unwrap();
|
||||
assert_eq!(rest, &[0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]);
|
||||
let (rest, value): (&[u8], String) = from_slice_stream(rest).unwrap();
|
||||
assert_eq!(value, "foobar");
|
||||
let mut remaining = Vec::new();
|
||||
data.read_to_end(&mut remaining).unwrap();
|
||||
assert!(remaining.is_empty());
|
||||
assert!(rest.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
// 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, PinUvAuthProtocol, SharedSecret};
|
||||
use crate::crypto::{COSEKey, CryptoError, PinUvAuthProtocol, PinUvAuthToken, SharedSecret};
|
||||
use crate::transport::errors::HIDError;
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde::{
|
||||
de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor},
|
||||
ser::SerializeMap,
|
||||
|
@ -55,14 +55,14 @@ impl Default for PinUvAuthTokenPermission {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientPIN {
|
||||
pub pin_protocol: Option<PinUvAuthProtocol>,
|
||||
pub subcommand: PINSubcommand,
|
||||
pub key_agreement: Option<COSEKey>,
|
||||
pub pin_auth: Option<ByteBuf>,
|
||||
pub new_pin_enc: Option<ByteBuf>,
|
||||
pub pin_hash_enc: Option<ByteBuf>,
|
||||
pub permissions: Option<u8>,
|
||||
pub rp_id: Option<String>,
|
||||
pin_protocol: Option<PinUvAuthProtocol>,
|
||||
subcommand: PINSubcommand,
|
||||
key_agreement: Option<COSEKey>,
|
||||
pin_auth: Option<ByteBuf>,
|
||||
new_pin_enc: Option<ByteBuf>,
|
||||
pin_hash_enc: Option<ByteBuf>,
|
||||
permissions: Option<u8>,
|
||||
rp_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ClientPIN {
|
||||
|
@ -145,14 +145,13 @@ pub trait ClientPINSubCommand {
|
|||
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClientPinResponse {
|
||||
pub key_agreement: Option<COSEKey>,
|
||||
pub pin_token: Option<Vec<u8>>,
|
||||
struct ClientPinResponse {
|
||||
key_agreement: Option<COSEKey>,
|
||||
pin_token: Option<EncryptedPinToken>,
|
||||
/// Number of PIN attempts remaining before lockout.
|
||||
pub pin_retries: Option<u8>,
|
||||
pub power_cycle_state: Option<bool>,
|
||||
pub uv_retries: Option<u8>,
|
||||
pin_retries: Option<u8>,
|
||||
power_cycle_state: Option<bool>,
|
||||
uv_retries: Option<u8>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientPinResponse {
|
||||
|
@ -242,7 +241,7 @@ impl GetKeyAgreement {
|
|||
}
|
||||
|
||||
impl ClientPINSubCommand for GetKeyAgreement {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = KeyAgreement;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
Ok(ClientPIN {
|
||||
|
@ -258,10 +257,14 @@ impl ClientPINSubCommand for GetKeyAgreement {
|
|||
|
||||
let get_pin_response: ClientPinResponse =
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
if get_pin_response.key_agreement.is_none() {
|
||||
return Err(CommandError::MissingRequiredField("key_agreement"));
|
||||
if let Some(key_agreement) = get_pin_response.key_agreement {
|
||||
Ok(KeyAgreement {
|
||||
pin_protocol: self.pin_protocol.clone(),
|
||||
peer_key: key_agreement,
|
||||
})
|
||||
} else {
|
||||
Err(CommandError::MissingRequiredField("key_agreement"))
|
||||
}
|
||||
Ok(get_pin_response)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +283,7 @@ impl<'sc, 'pin> GetPinToken<'sc, 'pin> {
|
|||
}
|
||||
|
||||
impl<'sc, 'pin> ClientPINSubCommand for GetPinToken<'sc, 'pin> {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = PinUvAuthToken;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
let input = self.pin.for_pin_token();
|
||||
|
@ -303,10 +306,19 @@ impl<'sc, 'pin> ClientPINSubCommand for GetPinToken<'sc, 'pin> {
|
|||
|
||||
let get_pin_response: ClientPinResponse =
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
if get_pin_response.pin_token.is_none() {
|
||||
return Err(CommandError::MissingRequiredField("pin_token"));
|
||||
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(default_permissions, encrypted_pin_token.as_ref())?;
|
||||
Ok(pin_token)
|
||||
}
|
||||
None => Err(CommandError::MissingRequiredField("key_agreement")),
|
||||
}
|
||||
Ok(get_pin_response)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,7 +347,7 @@ impl<'sc, 'pin> GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
|
|||
}
|
||||
|
||||
impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions<'sc, 'pin> {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = PinUvAuthToken;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
let input = self.pin.for_pin_token();
|
||||
|
@ -362,16 +374,21 @@ impl<'sc, 'pin> ClientPINSubCommand for GetPinUvAuthTokenUsingPinWithPermissions
|
|||
|
||||
let get_pin_response: ClientPinResponse =
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
if get_pin_response.pin_token.is_none() {
|
||||
return Err(CommandError::MissingRequiredField("pin_token"));
|
||||
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")),
|
||||
}
|
||||
Ok(get_pin_response)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! implementRetries {
|
||||
($name:ident, $getter:ident) => {
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct $name {}
|
||||
|
||||
impl $name {
|
||||
|
@ -381,7 +398,7 @@ macro_rules! implementRetries {
|
|||
}
|
||||
|
||||
impl ClientPINSubCommand for $name {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = u8;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
Ok(ClientPIN {
|
||||
|
@ -396,10 +413,10 @@ macro_rules! implementRetries {
|
|||
|
||||
let get_pin_response: ClientPinResponse =
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
if get_pin_response.$getter.is_none() {
|
||||
return Err(CommandError::MissingRequiredField(stringify!($getter)));
|
||||
match get_pin_response.$getter {
|
||||
Some($getter) => Ok($getter),
|
||||
None => Err(CommandError::MissingRequiredField(stringify!($getter))),
|
||||
}
|
||||
Ok(get_pin_response)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -430,7 +447,7 @@ impl<'sc> GetPinUvAuthTokenUsingUvWithPermissions<'sc> {
|
|||
}
|
||||
|
||||
impl<'sc> ClientPINSubCommand for GetPinUvAuthTokenUsingUvWithPermissions<'sc> {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = PinUvAuthToken;
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
Ok(ClientPIN {
|
||||
|
@ -450,10 +467,15 @@ impl<'sc> ClientPINSubCommand for GetPinUvAuthTokenUsingUvWithPermissions<'sc> {
|
|||
|
||||
let get_pin_response: ClientPinResponse =
|
||||
from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
if get_pin_response.pin_token.is_none() {
|
||||
return Err(CommandError::MissingRequiredField("pin_token"));
|
||||
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")),
|
||||
}
|
||||
Ok(get_pin_response)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,7 +495,7 @@ impl<'sc, 'pin> SetNewPin<'sc, 'pin> {
|
|||
}
|
||||
|
||||
impl<'sc, 'pin> ClientPINSubCommand for SetNewPin<'sc, 'pin> {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = ();
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
if self.new_pin.as_bytes().len() > 63 {
|
||||
|
@ -504,10 +526,12 @@ impl<'sc, 'pin> ClientPINSubCommand for SetNewPin<'sc, 'pin> {
|
|||
|
||||
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
|
||||
// Should be an empty response or a valid cbor-value (which we ignore)
|
||||
if !input.is_empty() {
|
||||
if input.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
let _: Value = from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
Ok(())
|
||||
}
|
||||
Ok(ClientPinResponse::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,7 +560,7 @@ impl<'sc, 'pin> ChangeExistingPin<'sc, 'pin> {
|
|||
}
|
||||
|
||||
impl<'sc, 'pin> ClientPINSubCommand for ChangeExistingPin<'sc, 'pin> {
|
||||
type Output = ClientPinResponse;
|
||||
type Output = ();
|
||||
|
||||
fn as_client_pin(&self) -> Result<ClientPIN, CommandError> {
|
||||
if self.new_pin.as_bytes().len() > 63 {
|
||||
|
@ -573,16 +597,18 @@ impl<'sc, 'pin> ClientPINSubCommand for ChangeExistingPin<'sc, 'pin> {
|
|||
|
||||
fn parse_response_payload(&self, input: &[u8]) -> Result<Self::Output, CommandError> {
|
||||
// Should be an empty response or a valid cbor-value (which we ignore)
|
||||
if !input.is_empty() {
|
||||
if input.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
let _: Value = from_slice(input).map_err(CommandError::Deserializing)?;
|
||||
Ok(())
|
||||
}
|
||||
Ok(ClientPinResponse::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RequestCtap2 for T
|
||||
where
|
||||
T: ClientPINSubCommand<Output = ClientPinResponse>,
|
||||
T: ClientPINSubCommand,
|
||||
T: fmt::Debug,
|
||||
{
|
||||
type Output = <T as ClientPINSubCommand>::Output;
|
||||
|
@ -599,11 +625,14 @@ where
|
|||
Ok(output)
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: U2FDevice,
|
||||
{
|
||||
trace!("Client pin subcomand response:{:04X?}", &input);
|
||||
if input.is_empty() {
|
||||
return Err(CommandError::InputTooSmall.into());
|
||||
|
@ -624,12 +653,26 @@ where
|
|||
Err(CommandError::StatusCode(status, add_data).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<ClientPinResponse, HIDError> {
|
||||
dev.client_pin(&self.as_client_pin()?)
|
||||
#[derive(Debug)]
|
||||
pub struct KeyAgreement {
|
||||
pin_protocol: PinUvAuthProtocol,
|
||||
peer_key: COSEKey,
|
||||
}
|
||||
|
||||
impl KeyAgreement {
|
||||
pub fn shared_secret(&self) -> Result<SharedSecret, CommandError> {
|
||||
Ok(self.pin_protocol.encapsulate(&self.peer_key)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EncryptedPinToken(ByteBuf);
|
||||
|
||||
impl AsRef<[u8]> for EncryptedPinToken {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,15 @@ use crate::ctap2::commands::make_credentials::UserVerification;
|
|||
use crate::ctap2::server::{
|
||||
PublicKeyCredentialDescriptor, RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement,
|
||||
};
|
||||
use crate::ctap2::utils::{read_be_u32, read_byte};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::transport::FidoDevice;
|
||||
use crate::u2ftypes::CTAP1RequestAPDU;
|
||||
use nom::{
|
||||
error::VerboseError,
|
||||
number::complete::{be_u32, be_u8},
|
||||
sequence::tuple,
|
||||
};
|
||||
use serde::{
|
||||
de::{Error as DesError, MapAccess, Visitor},
|
||||
ser::{Error as SerError, SerializeMap},
|
||||
|
@ -29,7 +33,7 @@ use serde::{
|
|||
use serde_bytes::ByteBuf;
|
||||
use serde_cbor::{de::from_slice, ser, Value};
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
use std::io;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[cfg_attr(test, derive(Deserialize))]
|
||||
|
@ -157,9 +161,9 @@ impl GetAssertionExtensions {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetAssertion {
|
||||
pub client_data_hash: ClientDataHash,
|
||||
pub rp: RelyingPartyWrapper,
|
||||
pub allow_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
pub(crate) client_data_hash: ClientDataHash,
|
||||
pub(crate) rp: RelyingPartyWrapper,
|
||||
pub(crate) allow_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
|
||||
// https://www.w3.org/TR/webauthn/#client-extension-input
|
||||
// The client extension input, which is a value that can be encoded in JSON,
|
||||
|
@ -167,13 +171,13 @@ pub struct GetAssertion {
|
|||
// create() call, while the CBOR authenticator extension input is passed
|
||||
// from the client to the authenticator for authenticator extensions during
|
||||
// the processing of these calls.
|
||||
pub extensions: GetAssertionExtensions,
|
||||
pub options: GetAssertionOptions,
|
||||
pub pin: Option<Pin>,
|
||||
pub pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
pub(crate) extensions: GetAssertionExtensions,
|
||||
pub(crate) options: GetAssertionOptions,
|
||||
pub(crate) pin: Option<Pin>,
|
||||
pub(crate) pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
|
||||
// This is used to implement the FIDO AppID extension.
|
||||
pub alternate_rp_id: Option<String>,
|
||||
pub(crate) alternate_rp_id: Option<String>,
|
||||
}
|
||||
|
||||
impl GetAssertion {
|
||||
|
@ -364,13 +368,6 @@ impl RequestCtap1 for GetAssertion {
|
|||
.map_err(HIDError::Command)
|
||||
.map_err(Retryable::Error)
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.get_assertion(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestCtap2 for GetAssertion {
|
||||
|
@ -384,11 +381,14 @@ impl RequestCtap2 for GetAssertion {
|
|||
Ok(ser::to_vec(&self).map_err(CommandError::Serializing)?)
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: FidoDevice + io::Read + io::Write + fmt::Debug,
|
||||
{
|
||||
if input.is_empty() {
|
||||
return Err(CommandError::InputTooSmall.into());
|
||||
}
|
||||
|
@ -425,13 +425,6 @@ impl RequestCtap2 for GetAssertion {
|
|||
Err(CommandError::StatusCode(status, None).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.get_assertion(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -463,11 +456,17 @@ impl GetAssertionResult {
|
|||
rp_id_hash: &RpIdHash,
|
||||
key_handle: &PublicKeyCredentialDescriptor,
|
||||
) -> Result<GetAssertionResult, CommandError> {
|
||||
let mut data = Cursor::new(input);
|
||||
let user_presence = read_byte(&mut data).map_err(CommandError::Deserializing)?;
|
||||
let counter = read_be_u32(&mut data).map_err(CommandError::Deserializing)?;
|
||||
// Remaining data is signature (Note: `data.remaining_slice()` is not yet stabilized)
|
||||
let signature = Vec::from(&data.get_ref()[data.position() as usize..]);
|
||||
let parse_authentication = |input| {
|
||||
// Parsing an u8, then a u32, and the rest is the signature
|
||||
let (rest, (user_presence, counter)) = tuple((be_u8, be_u32))(input)?;
|
||||
let signature = Vec::from(rest);
|
||||
Ok((user_presence, counter, signature))
|
||||
};
|
||||
let (user_presence, counter, signature) =
|
||||
parse_authentication(input).map_err(|e: nom::Err<VerboseError<_>>| {
|
||||
error!("error while parsing authentication: {:?}", e);
|
||||
CommandError::Deserializing(DesError::custom("unable to parse authentication"))
|
||||
})?;
|
||||
|
||||
// Step 5 of Section 10.3 of CTAP2.1: "Copy bits 0 (the UP bit) and bit 1 from the
|
||||
// CTAP2/U2F response user presence byte to bits 0 and 1 of the CTAP2 flags, respectively.
|
||||
|
@ -505,12 +504,12 @@ impl GetAssertionResult {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GetAssertionResponse {
|
||||
pub credentials: Option<PublicKeyCredentialDescriptor>,
|
||||
pub auth_data: AuthenticatorData,
|
||||
pub signature: Vec<u8>,
|
||||
pub user: Option<User>,
|
||||
pub number_of_credentials: Option<usize>,
|
||||
pub(crate) struct GetAssertionResponse {
|
||||
credentials: Option<PublicKeyCredentialDescriptor>,
|
||||
auth_data: AuthenticatorData,
|
||||
signature: Vec<u8>,
|
||||
user: Option<User>,
|
||||
number_of_credentials: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GetAssertionResponse {
|
||||
|
@ -617,8 +616,8 @@ pub mod test {
|
|||
};
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::{FidoDevice, FidoDeviceIO, FidoProtocol};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::FidoDevice;
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
#[test]
|
||||
|
@ -656,7 +655,6 @@ pub mod test {
|
|||
None,
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
device.set_cid(cid);
|
||||
|
@ -859,8 +857,6 @@ pub mod test {
|
|||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it)
|
||||
// channel id
|
||||
device.downgrade_to_ctap1();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP1);
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
|
||||
|
@ -952,8 +948,6 @@ pub mod test {
|
|||
|
||||
let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it)
|
||||
// channel id
|
||||
device.downgrade_to_ctap1();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP1);
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
|
||||
|
@ -1133,7 +1127,6 @@ pub mod test {
|
|||
None,
|
||||
);
|
||||
let mut device = Device::new("commands/get_assertion").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
device.set_cid(cid);
|
||||
|
@ -1159,7 +1152,7 @@ pub mod test {
|
|||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: Some(vec![1]),
|
||||
pin_protocols: vec![1],
|
||||
max_credential_count_in_list: None,
|
||||
max_credential_id_length: Some(80),
|
||||
transports: None,
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::{Command, CommandError, RequestCtap2, StatusCode};
|
|||
use crate::ctap2::attestation::AAGuid;
|
||||
use crate::ctap2::server::PublicKeyCredentialParameters;
|
||||
use crate::transport::errors::HIDError;
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde::{
|
||||
de::{Error as SError, IgnoredAny, MapAccess, Visitor},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
|
@ -25,11 +25,14 @@ impl RequestCtap2 for GetInfo {
|
|||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: U2FDevice,
|
||||
{
|
||||
if input.is_empty() {
|
||||
return Err(CommandError::InputTooSmall.into());
|
||||
}
|
||||
|
@ -50,13 +53,6 @@ impl RequestCtap2 for GetInfo {
|
|||
Err(CommandError::InputTooSmall.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.get_info()
|
||||
}
|
||||
}
|
||||
|
||||
fn true_val() -> bool {
|
||||
|
@ -320,7 +316,7 @@ pub struct AuthenticatorInfo {
|
|||
pub aaguid: AAGuid,
|
||||
pub options: AuthenticatorOptions,
|
||||
pub max_msg_size: Option<usize>,
|
||||
pub pin_protocols: Option<Vec<u64>>,
|
||||
pub pin_protocols: Vec<u64>,
|
||||
// CTAP 2.1
|
||||
pub max_credential_count_in_list: Option<usize>,
|
||||
pub max_credential_id_length: Option<usize>,
|
||||
|
@ -392,7 +388,7 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo {
|
|||
let mut aaguid = None;
|
||||
let mut options = AuthenticatorOptions::default();
|
||||
let mut max_msg_size = None;
|
||||
let mut pin_protocols: Option<Vec<_>> = None;
|
||||
let mut pin_protocols = Vec::new();
|
||||
let mut max_credential_count_in_list = None;
|
||||
let mut max_credential_id_length = None;
|
||||
let mut transports = None;
|
||||
|
@ -432,7 +428,10 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo {
|
|||
parse_next_optional_value!(max_msg_size, map);
|
||||
}
|
||||
0x06 => {
|
||||
parse_next_optional_value!(pin_protocols, map);
|
||||
if !pin_protocols.is_empty() {
|
||||
return Err(serde::de::Error::duplicate_field("pin_protocols"));
|
||||
}
|
||||
pin_protocols = map.next_value()?;
|
||||
}
|
||||
0x07 => {
|
||||
parse_next_optional_value!(max_credential_count_in_list, map);
|
||||
|
@ -493,15 +492,6 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo {
|
|||
));
|
||||
}
|
||||
|
||||
if let Some(protocols) = &pin_protocols {
|
||||
if protocols.is_empty() {
|
||||
return Err(M::Error::custom(
|
||||
"Token returned empty PIN protocol list, which is not allowed"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(aaguid) = aaguid {
|
||||
Ok(AuthenticatorInfo {
|
||||
versions,
|
||||
|
@ -543,7 +533,8 @@ pub mod tests {
|
|||
use crate::crypto::COSEAlgorithm;
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::platform::device::IN_HID_RPT_SIZE;
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, FidoProtocol};
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, Nonce};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde_cbor::de::from_slice;
|
||||
|
||||
|
@ -750,7 +741,7 @@ pub mod tests {
|
|||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: Some(vec![1]),
|
||||
pin_protocols: vec![1],
|
||||
max_credential_count_in_list: None,
|
||||
max_credential_id_length: None,
|
||||
transports: None,
|
||||
|
@ -830,7 +821,7 @@ pub mod tests {
|
|||
always_uv: Some(true),
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: Some(vec![2, 1]),
|
||||
pin_protocols: vec![2, 1],
|
||||
max_credential_count_in_list: Some(8),
|
||||
max_credential_id_length: Some(128),
|
||||
transports: Some(vec!["usb".to_string()]),
|
||||
|
@ -861,7 +852,6 @@ pub mod tests {
|
|||
#[test]
|
||||
fn test_get_info_ctap2_only() {
|
||||
let mut device = Device::new("commands/get_info").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
|
||||
|
||||
// channel id
|
||||
|
@ -904,7 +894,9 @@ pub mod tests {
|
|||
msg.extend(vec![0x00]); // SEQ
|
||||
msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[(IN_HID_RPT_SIZE - 8)..]);
|
||||
device.add_read(&msg, 0);
|
||||
device.init().expect("Failed to init device");
|
||||
device
|
||||
.init(Nonce::Use(nonce))
|
||||
.expect("Failed to init device");
|
||||
|
||||
assert_eq!(device.get_cid(), &cid);
|
||||
|
||||
|
@ -930,7 +922,7 @@ pub mod tests {
|
|||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: Some(vec![1]),
|
||||
pin_protocols: vec![1],
|
||||
max_credential_count_in_list: None,
|
||||
max_credential_id_length: None,
|
||||
transports: None,
|
||||
|
@ -988,57 +980,4 @@ pub mod tests {
|
|||
AuthenticatorVersion::FIDO_2_1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_authenticator_info_protocol_versions() {
|
||||
let mut expected = AuthenticatorInfo {
|
||||
versions: vec![AuthenticatorVersion::U2F_V2, AuthenticatorVersion::FIDO_2_0],
|
||||
extensions: vec!["uvm".to_string(), "hmac-secret".to_string()],
|
||||
aaguid: AAGuid(AAGUID_RAW),
|
||||
options: AuthenticatorOptions {
|
||||
platform_device: false,
|
||||
resident_key: true,
|
||||
client_pin: Some(false),
|
||||
user_presence: true,
|
||||
..Default::default()
|
||||
},
|
||||
max_msg_size: Some(1200),
|
||||
pin_protocols: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let raw_data = AUTHENTICATOR_INFO_PAYLOAD.to_vec();
|
||||
// pin protocol entry (last 3 bytes in payload):
|
||||
// 0x06, // unsigned(6)
|
||||
// 0x81, // array(1)
|
||||
// 0x01, // unsigned(1)
|
||||
|
||||
let mut raw_empty_list = raw_data.clone();
|
||||
raw_empty_list.pop();
|
||||
let raw_list_len = raw_empty_list.len();
|
||||
raw_empty_list[raw_list_len - 1] = 0x80; // array(0) instead of array(1)
|
||||
|
||||
// Empty protocols-array, that should produce an error
|
||||
from_slice::<AuthenticatorInfo>(&raw_empty_list).unwrap_err();
|
||||
|
||||
// No protocols specified
|
||||
let mut raw_no_list = raw_data.clone();
|
||||
raw_no_list.pop();
|
||||
raw_no_list.pop();
|
||||
raw_no_list.pop();
|
||||
raw_no_list[0] = 0xa5; // map(5) instead of map(6)
|
||||
|
||||
let authenticator_info: AuthenticatorInfo = from_slice(&raw_no_list).unwrap();
|
||||
assert_eq!(authenticator_info, expected);
|
||||
|
||||
// Both 1 and 2
|
||||
let mut raw_list = raw_data.clone();
|
||||
let raw_list_len = raw_list.len();
|
||||
raw_list[raw_list_len - 2] = 0x82; // array(2) instead of array(1)
|
||||
raw_list.push(0x02);
|
||||
|
||||
expected.pin_protocols = Some(vec![1, 2]);
|
||||
let authenticator_info: AuthenticatorInfo = from_slice(&raw_list).unwrap();
|
||||
assert_eq!(authenticator_info, expected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{Command, CommandError, RequestCtap2, StatusCode};
|
||||
use crate::ctap2::commands::get_assertion::GetAssertionResponse;
|
||||
use crate::transport::errors::HIDError;
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde_cbor::{de::from_slice, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -18,11 +18,14 @@ impl RequestCtap2 for GetNextAssertion {
|
|||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: U2FDevice,
|
||||
{
|
||||
if input.is_empty() {
|
||||
return Err(CommandError::InputTooSmall.into());
|
||||
}
|
||||
|
@ -44,11 +47,4 @@ impl RequestCtap2 for GetNextAssertion {
|
|||
Err(CommandError::StatusCode(status, None).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use super::{CommandError, RequestCtap1, Retryable};
|
||||
use crate::consts::U2F_VERSION;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::VirtualFidoDevice;
|
||||
use crate::u2ftypes::CTAP1RequestAPDU;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
|
@ -45,27 +44,19 @@ impl RequestCtap1 for GetVersion {
|
|||
let data = CTAP1RequestAPDU::serialize(cmd, flags, &[])?;
|
||||
Ok((data, ()))
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.get_version(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR};
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, FidoProtocol};
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, Nonce};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
#[test]
|
||||
fn test_get_version_ctap1_only() {
|
||||
let mut device = Device::new("commands/get_version").unwrap();
|
||||
device.downgrade_to_ctap1();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP1);
|
||||
let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
|
||||
|
||||
// channel id
|
||||
|
@ -104,7 +95,9 @@ pub mod tests {
|
|||
msg.extend(SW_NO_ERROR);
|
||||
device.add_read(&msg, 0);
|
||||
|
||||
device.init().expect("Failed to init device");
|
||||
device
|
||||
.init(Nonce::Use(nonce))
|
||||
.expect("Failed to init device");
|
||||
|
||||
assert_eq!(device.get_cid(), &cid);
|
||||
|
||||
|
|
|
@ -18,11 +18,14 @@ use crate::ctap2::server::{
|
|||
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
|
||||
RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement,
|
||||
};
|
||||
use crate::ctap2::utils::{read_byte, serde_parse_err};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::u2ftypes::CTAP1RequestAPDU;
|
||||
use crate::u2ftypes::{CTAP1RequestAPDU, U2FDevice};
|
||||
use nom::{
|
||||
bytes::complete::{tag, take},
|
||||
error::VerboseError,
|
||||
number::complete::be_u8,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use serde::Deserialize;
|
||||
use serde::{
|
||||
|
@ -31,7 +34,8 @@ use serde::{
|
|||
Serialize, Serializer,
|
||||
};
|
||||
use serde_cbor::{self, de::from_slice, ser, Value};
|
||||
use std::io::{Cursor, Read};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MakeCredentialsResult(pub AttestationObject);
|
||||
|
@ -41,35 +45,30 @@ impl MakeCredentialsResult {
|
|||
input: &[u8],
|
||||
rp_id_hash: &RpIdHash,
|
||||
) -> Result<MakeCredentialsResult, CommandError> {
|
||||
let mut data = Cursor::new(input);
|
||||
let magic_num = read_byte(&mut data).map_err(CommandError::Deserializing)?;
|
||||
if magic_num != 0x05 {
|
||||
error!("error while parsing registration: magic header not 0x05, but {magic_num}");
|
||||
return Err(CommandError::Deserializing(DesError::invalid_value(
|
||||
serde::de::Unexpected::Unsigned(magic_num as u64),
|
||||
&"0x05",
|
||||
)));
|
||||
}
|
||||
let mut public_key = [0u8; 65];
|
||||
data.read_exact(&mut public_key)
|
||||
.map_err(|_| CommandError::Deserializing(serde_parse_err("PublicKey")))?;
|
||||
let parse_register = |input| {
|
||||
let (rest, _) = tag(&[0x05])(input)?;
|
||||
let (rest, public_key) = take(65u8)(rest)?;
|
||||
let (rest, key_handle_len) = be_u8(rest)?;
|
||||
let (rest, key_handle) = take(key_handle_len)(rest)?;
|
||||
Ok((rest, public_key, key_handle))
|
||||
};
|
||||
|
||||
let credential_id_len = read_byte(&mut data).map_err(CommandError::Deserializing)?;
|
||||
let mut credential_id = vec![0u8; credential_id_len as usize];
|
||||
data.read_exact(&mut credential_id)
|
||||
.map_err(|_| CommandError::Deserializing(serde_parse_err("CredentialId")))?;
|
||||
let (rest, public_key, key_handle) =
|
||||
parse_register(input).map_err(|e: nom::Err<VerboseError<_>>| {
|
||||
error!("error while parsing registration: {:?}", e);
|
||||
CommandError::Deserializing(DesError::custom("unable to parse registration"))
|
||||
})?;
|
||||
|
||||
let cert_and_sig = parse_u2f_der_certificate(&data.get_ref()[data.position() as usize..])
|
||||
.map_err(|err| {
|
||||
CommandError::Deserializing(serde_parse_err(&format!(
|
||||
"Certificate and Signature: {err:?}",
|
||||
)))
|
||||
let cert_and_sig = parse_u2f_der_certificate(rest).map_err(|e| {
|
||||
error!("error while parsing registration: {:?}", e);
|
||||
CommandError::Deserializing(DesError::custom("unable to parse registration"))
|
||||
})?;
|
||||
|
||||
let credential_ec2_key = COSEEC2Key::from_sec1_uncompressed(Curve::SECP256R1, &public_key)
|
||||
.map_err(|err| {
|
||||
CommandError::Deserializing(serde_parse_err(&format!("EC2 Key: {err:?}",)))
|
||||
})?;
|
||||
let credential_ec2_key = COSEEC2Key::from_sec1_uncompressed(Curve::SECP256R1, public_key)
|
||||
.map_err(|e| {
|
||||
error!("error while parsing registration: {:?}", e);
|
||||
CommandError::Deserializing(DesError::custom("unable to parse registration"))
|
||||
})?;
|
||||
|
||||
let credential_public_key = COSEKey {
|
||||
alg: COSEAlgorithm::ES256,
|
||||
|
@ -86,7 +85,7 @@ impl MakeCredentialsResult {
|
|||
counter: 0,
|
||||
credential_data: Some(AttestedCredentialData {
|
||||
aaguid: AAGuid::default(),
|
||||
credential_id,
|
||||
credential_id: Vec::from(key_handle),
|
||||
credential_public_key,
|
||||
}),
|
||||
extensions: Default::default(),
|
||||
|
@ -153,12 +152,12 @@ impl MakeCredentialsExtensions {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MakeCredentials {
|
||||
pub client_data_hash: ClientDataHash,
|
||||
pub rp: RelyingPartyWrapper,
|
||||
pub(crate) client_data_hash: ClientDataHash,
|
||||
pub(crate) rp: RelyingPartyWrapper,
|
||||
// Note(baloo): If none -> ctap1
|
||||
pub user: Option<User>,
|
||||
pub pub_cred_params: Vec<PublicKeyCredentialParameters>,
|
||||
pub exclude_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
pub(crate) user: Option<User>,
|
||||
pub(crate) pub_cred_params: Vec<PublicKeyCredentialParameters>,
|
||||
pub(crate) exclude_list: Vec<PublicKeyCredentialDescriptor>,
|
||||
|
||||
// https://www.w3.org/TR/webauthn/#client-extension-input
|
||||
// The client extension input, which is a value that can be encoded in JSON,
|
||||
|
@ -166,11 +165,11 @@ pub struct MakeCredentials {
|
|||
// create() call, while the CBOR authenticator extension input is passed
|
||||
// from the client to the authenticator for authenticator extensions during
|
||||
// the processing of these calls.
|
||||
pub extensions: MakeCredentialsExtensions,
|
||||
pub options: MakeCredentialsOptions,
|
||||
pub pin: Option<Pin>,
|
||||
pub pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
pub enterprise_attestation: Option<u64>,
|
||||
pub(crate) extensions: MakeCredentialsExtensions,
|
||||
pub(crate) options: MakeCredentialsOptions,
|
||||
pub(crate) pin: Option<Pin>,
|
||||
pub(crate) pin_uv_auth_param: Option<PinUvAuthParam>,
|
||||
pub(crate) enterprise_attestation: Option<u64>,
|
||||
}
|
||||
|
||||
impl MakeCredentials {
|
||||
|
@ -362,13 +361,6 @@ impl RequestCtap1 for MakeCredentials {
|
|||
.map_err(HIDError::Command)
|
||||
.map_err(Retryable::Error)
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.make_credentials(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestCtap2 for MakeCredentials {
|
||||
|
@ -382,11 +374,14 @@ impl RequestCtap2 for MakeCredentials {
|
|||
Ok(ser::to_vec(&self).map_err(CommandError::Serializing)?)
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: U2FDevice + io::Read + io::Write + fmt::Debug,
|
||||
{
|
||||
if input.is_empty() {
|
||||
return Err(HIDError::Command(CommandError::InputTooSmall));
|
||||
}
|
||||
|
@ -410,13 +405,6 @@ impl RequestCtap2 for MakeCredentials {
|
|||
Err(HIDError::Command(CommandError::StatusCode(status, None)))
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.make_credentials(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dummy_make_credentials_cmd() -> MakeCredentials {
|
||||
|
@ -443,7 +431,7 @@ pub(crate) fn dummy_make_credentials_cmd() -> MakeCredentials {
|
|||
..Default::default()
|
||||
}),
|
||||
vec![PublicKeyCredentialParameters {
|
||||
alg: COSEAlgorithm::ES256,
|
||||
alg: crate::COSEAlgorithm::ES256,
|
||||
}],
|
||||
vec![],
|
||||
MakeCredentialsOptions::default(),
|
||||
|
@ -474,7 +462,6 @@ pub mod test {
|
|||
};
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::{FidoDevice, FidoProtocol};
|
||||
use serde_bytes::ByteBuf;
|
||||
|
||||
fn create_attestation_obj() -> AttestationObject {
|
||||
|
@ -610,7 +597,6 @@ pub mod test {
|
|||
);
|
||||
|
||||
let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it)
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
let req_serialized = req
|
||||
.wire_format()
|
||||
.expect("Failed to serialize MakeCredentials request");
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
use super::server::RelyingPartyWrapper;
|
||||
use crate::crypto::{CryptoError, PinUvAuthParam, PinUvAuthToken};
|
||||
use crate::ctap2::commands::client_pin::{
|
||||
ClientPinResponse, GetPinRetries, GetUvRetries, Pin, PinError,
|
||||
};
|
||||
use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, Pin, PinError};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::ctap2::server::UserVerificationRequirement;
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::transport::FidoDevice;
|
||||
use serde_cbor::{error::Error as CborError, Value};
|
||||
use serde_json as json;
|
||||
use std::error::Error as StdErrorT;
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub mod client_pin;
|
||||
pub mod get_assertion;
|
||||
pub mod get_info;
|
||||
pub mod get_next_assertion;
|
||||
pub mod get_version;
|
||||
pub mod make_credentials;
|
||||
pub mod reset;
|
||||
pub mod selection;
|
||||
pub(crate) mod client_pin;
|
||||
pub(crate) mod get_assertion;
|
||||
pub(crate) mod get_info;
|
||||
pub(crate) mod get_next_assertion;
|
||||
pub(crate) mod get_version;
|
||||
pub(crate) mod make_credentials;
|
||||
pub(crate) mod reset;
|
||||
pub(crate) mod selection;
|
||||
|
||||
pub trait Request<T>
|
||||
where
|
||||
|
@ -72,11 +71,6 @@ pub trait RequestCtap1: fmt::Debug {
|
|||
input: &[u8],
|
||||
add_info: &Self::AdditionalInfo,
|
||||
) -> Result<Self::Output, Retryable<HIDError>>;
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError>;
|
||||
}
|
||||
|
||||
pub trait RequestCtap2: fmt::Debug {
|
||||
|
@ -86,16 +80,13 @@ pub trait RequestCtap2: fmt::Debug {
|
|||
|
||||
fn wire_format(&self) -> Result<Vec<u8>, HIDError>;
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError>;
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError>;
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: FidoDevice + Read + Write + fmt::Debug;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -164,10 +155,8 @@ pub(crate) fn repackage_pin_errors<D: FidoDevice>(
|
|||
HIDError::Command(CommandError::StatusCode(StatusCode::PinInvalid, _)) => {
|
||||
// If the given PIN was wrong, determine no. of left retries
|
||||
let cmd = GetPinRetries::new();
|
||||
// Treat any error as if the device returned a valid response without a pinRetries
|
||||
// field.
|
||||
let resp = dev.send_cbor(&cmd).unwrap_or(ClientPinResponse::default());
|
||||
AuthenticatorError::PinError(PinError::InvalidPin(resp.pin_retries))
|
||||
let retries = dev.send_cbor(&cmd).ok(); // If we got retries, wrap it in Some, otherwise ignore err
|
||||
AuthenticatorError::PinError(PinError::InvalidPin(retries))
|
||||
}
|
||||
HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthBlocked, _)) => {
|
||||
AuthenticatorError::PinError(PinError::PinAuthBlocked)
|
||||
|
@ -187,10 +176,8 @@ pub(crate) fn repackage_pin_errors<D: FidoDevice>(
|
|||
HIDError::Command(CommandError::StatusCode(StatusCode::UvInvalid, _)) => {
|
||||
// If the internal UV failed, determine no. of left retries
|
||||
let cmd = GetUvRetries::new();
|
||||
// Treat any error as if the device returned a valid response without a uvRetries
|
||||
// field.
|
||||
let resp = dev.send_cbor(&cmd).unwrap_or(ClientPinResponse::default());
|
||||
AuthenticatorError::PinError(PinError::InvalidUv(resp.uv_retries))
|
||||
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)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::{Command, CommandError, RequestCtap2, StatusCode};
|
||||
use crate::transport::errors::HIDError;
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde_cbor::{de::from_slice, Value};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -17,11 +17,14 @@ impl RequestCtap2 for Reset {
|
|||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: U2FDevice,
|
||||
{
|
||||
if input.is_empty() {
|
||||
return Err(CommandError::InputTooSmall.into());
|
||||
}
|
||||
|
@ -40,13 +43,6 @@ impl RequestCtap2 for Reset {
|
|||
Err(CommandError::StatusCode(status, msg).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.reset(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -54,13 +50,13 @@ pub mod tests {
|
|||
use super::*;
|
||||
use crate::consts::HIDCmd;
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, FidoDeviceIO, FidoProtocol};
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde_cbor::{de::from_slice, Value};
|
||||
|
||||
fn issue_command_and_get_response(cmd: u8, add: &[u8]) -> Result<(), HIDError> {
|
||||
let mut device = Device::new("commands/Reset").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
// ctap2 request
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::{Command, CommandError, RequestCtap2, StatusCode};
|
||||
use crate::transport::errors::HIDError;
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use serde_cbor::{de::from_slice, Value};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -17,11 +17,14 @@ impl RequestCtap2 for Selection {
|
|||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn handle_response_ctap2<Dev: FidoDevice>(
|
||||
fn handle_response_ctap2<Dev>(
|
||||
&self,
|
||||
_dev: &mut Dev,
|
||||
input: &[u8],
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
) -> Result<Self::Output, HIDError>
|
||||
where
|
||||
Dev: U2FDevice,
|
||||
{
|
||||
if input.is_empty() {
|
||||
return Err(CommandError::InputTooSmall.into());
|
||||
}
|
||||
|
@ -40,13 +43,6 @@ impl RequestCtap2 for Selection {
|
|||
Err(CommandError::StatusCode(status, msg).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.selection(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -54,13 +50,13 @@ pub mod tests {
|
|||
use super::*;
|
||||
use crate::consts::HIDCmd;
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, FidoDeviceIO, FidoProtocol};
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice};
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde_cbor::{de::from_slice, Value};
|
||||
|
||||
fn issue_command_and_get_response(cmd: u8, add: &[u8]) -> Result<(), HIDError> {
|
||||
let mut device = Device::new("commands/selection").unwrap();
|
||||
assert_eq!(device.get_protocol(), FidoProtocol::CTAP2);
|
||||
// ctap2 request
|
||||
let mut cid = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut cid);
|
||||
|
|
|
@ -1,807 +1,10 @@
|
|||
pub mod attestation;
|
||||
pub mod client_data;
|
||||
#[allow(dead_code)] // TODO(MS): Remove me asap
|
||||
pub mod commands;
|
||||
pub mod preflight;
|
||||
pub use commands::get_assertion::GetAssertionResult;
|
||||
|
||||
pub mod attestation;
|
||||
|
||||
pub mod client_data;
|
||||
pub(crate) mod preflight;
|
||||
pub mod server;
|
||||
pub(crate) mod utils;
|
||||
|
||||
use crate::authenticatorservice::{RegisterArgs, SignArgs};
|
||||
|
||||
use crate::crypto::COSEAlgorithm;
|
||||
|
||||
use crate::ctap2::client_data::ClientDataHash;
|
||||
use crate::ctap2::commands::client_pin::{
|
||||
ChangeExistingPin, Pin, PinError, PinUvAuthTokenPermission, SetNewPin,
|
||||
};
|
||||
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionOptions};
|
||||
use crate::ctap2::commands::make_credentials::{
|
||||
dummy_make_credentials_cmd, MakeCredentials, MakeCredentialsOptions, MakeCredentialsResult,
|
||||
};
|
||||
use crate::ctap2::commands::reset::Reset;
|
||||
use crate::ctap2::commands::{
|
||||
repackage_pin_errors, CommandError, PinUvAuthCommand, PinUvAuthResult, Request, StatusCode,
|
||||
};
|
||||
use crate::ctap2::preflight::{
|
||||
do_credential_list_filtering_ctap1, do_credential_list_filtering_ctap2,
|
||||
};
|
||||
use crate::ctap2::server::{
|
||||
RelyingParty, RelyingPartyWrapper, ResidentKeyRequirement, UserVerificationRequirement,
|
||||
};
|
||||
use crate::errors::{AuthenticatorError, UnsupportedOption};
|
||||
use crate::statecallback::StateCallback;
|
||||
use crate::transport::device_selector::{Device, DeviceSelectorEvent};
|
||||
|
||||
use crate::status_update::send_status;
|
||||
use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, FidoDeviceIO, FidoProtocol};
|
||||
|
||||
use crate::{RegisterResult, SignResult, StatusPinUv, StatusUpdate};
|
||||
use std::sync::mpsc::{channel, RecvError, Sender};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
macro_rules! unwrap_result {
|
||||
($item: expr, $callback: expr) => {
|
||||
match $item {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
$callback.call(Err(e.into()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn ask_user_for_pin<U>(
|
||||
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();
|
||||
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(RecvError) => {
|
||||
// recv() can only fail, if the other side is dropping the Sender.
|
||||
info!("Callback dropped the channel. Aborting.");
|
||||
callback.call(Err(AuthenticatorError::CancelledByUser));
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Dev: FidoDevice, T: PinUvAuthCommand + Request<V>, V>(
|
||||
cmd: &mut T,
|
||||
dev: &mut Dev,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
skip_uv: bool,
|
||||
uv_req: UserVerificationRequirement,
|
||||
) -> Result<PinUvAuthResult, AuthenticatorError> {
|
||||
// CTAP 2.1 is very specific that the request should either include pinUvAuthParam
|
||||
// OR uv=true, but not both at the same time. We now have to decide which (if either)
|
||||
// to send. We may omit both values. Will never send an explicit uv=false, because
|
||||
// a) this is the default, and
|
||||
// b) some CTAP 2.0 authenticators return UnsupportedOption when uv=false.
|
||||
|
||||
// We ensure both pinUvAuthParam and uv are not set to start.
|
||||
cmd.set_pin_uv_auth_param(None)?;
|
||||
cmd.set_uv_option(None);
|
||||
|
||||
// Skip user verification if we're using CTAP1 or if the device does not support CTAP2.
|
||||
let info = match (dev.get_protocol(), dev.get_authenticator_info()) {
|
||||
(FidoProtocol::CTAP2, Some(info)) => info,
|
||||
_ => return Ok(PinUvAuthResult::DeviceIsCtap1),
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// Check if the combination of device-protection and request-options
|
||||
// are allowing for 'discouraged', meaning no auth required.
|
||||
if cmd.can_skip_user_verification(info, uv_req) {
|
||||
return Ok(PinUvAuthResult::NoAuthRequired);
|
||||
}
|
||||
|
||||
// Device does not support any (remaining) auth-method
|
||||
if (skip_uv || !supports_uv) && !supports_pin {
|
||||
if supports_uv && uv_req == UserVerificationRequirement::Required {
|
||||
// We should always set the uv option in the Required case, but the CTAP 2.1 spec
|
||||
// says 'Platforms MUST NOT include the "uv" option key if the authenticator does
|
||||
// not support built-in user verification.' This is to work around some CTAP 2.0
|
||||
// authenticators which incorrectly error out with CTAP2_ERR_UNSUPPORTED_OPTION
|
||||
// when the "uv" option is set. The RP that requested UV will (hopefully) reject our
|
||||
// response in the !supports_uv case.
|
||||
cmd.set_uv_option(Some(true));
|
||||
}
|
||||
return Ok(PinUvAuthResult::NoAuthTypeSupported);
|
||||
}
|
||||
|
||||
// Device supports PINs, but a PIN is not configured. Signal that we
|
||||
// can complete the operation if the user sets a PIN first.
|
||||
if (skip_uv || !supports_uv) && !pin_configured {
|
||||
return Err(AuthenticatorError::PinError(PinError::PinNotSet));
|
||||
}
|
||||
|
||||
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())
|
||||
.map_err(|e| repackage_pin_errors(dev, e))?;
|
||||
cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
|
||||
Ok(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(pin_auth_token))
|
||||
} else {
|
||||
// CTAP 2.1 - PIN
|
||||
// We did not take the `!skip_uv && supports_uv` branch, so we have
|
||||
// `(skip_uv || !supports_uv)`. Moreover we did not exit early in the
|
||||
// `(skip_uv || !supports_uv) && !pin_configured` case. So we have
|
||||
// `pin_configured`.
|
||||
let pin_auth_token = dev
|
||||
.get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
cmd.pin(),
|
||||
permission,
|
||||
cmd.get_rp().id(),
|
||||
)
|
||||
.map_err(|e| repackage_pin_errors(dev, e))?;
|
||||
cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
|
||||
Ok(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(pin_auth_token))
|
||||
}
|
||||
} 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
|
||||
if info.supports_hmac_secret() {
|
||||
let _shared_secret = dev.establish_shared_secret()?;
|
||||
}
|
||||
// CTAP 2.1, Section 6.1.1, Step 1.1.2.1.2.
|
||||
cmd.set_uv_option(Some(true));
|
||||
return Ok(PinUvAuthResult::UsingInternalUv);
|
||||
}
|
||||
|
||||
let pin_auth_token = dev
|
||||
.get_pin_token(cmd.pin())
|
||||
.map_err(|e| repackage_pin_errors(dev, e))?;
|
||||
cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
|
||||
Ok(PinUvAuthResult::SuccessGetPinToken(pin_auth_token))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Dev: FidoDevice, T: PinUvAuthCommand + Request<V>, U, V>(
|
||||
cmd: &mut T,
|
||||
dev: &mut Dev,
|
||||
mut skip_uv: bool,
|
||||
permission: PinUvAuthTokenPermission,
|
||||
uv_req: UserVerificationRequirement,
|
||||
status: &Sender<StatusUpdate>,
|
||||
callback: &StateCallback<crate::Result<U>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
) -> Result<PinUvAuthResult, ()> {
|
||||
while alive() {
|
||||
debug!("-----------------------------------------------------------------");
|
||||
debug!("Getting pinUvAuthParam");
|
||||
match get_pin_uv_auth_param(cmd, dev, permission, skip_uv, uv_req) {
|
||||
Ok(r) => {
|
||||
return Ok(r);
|
||||
}
|
||||
|
||||
Err(AuthenticatorError::PinError(PinError::PinRequired)) => {
|
||||
if let Ok(pin) = 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) = 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(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub fn register<Dev: FidoDevice>(
|
||||
dev: &mut Dev,
|
||||
args: RegisterArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::RegisterResult>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
) -> bool {
|
||||
let mut options = MakeCredentialsOptions::default();
|
||||
|
||||
if dev.get_protocol() == FidoProtocol::CTAP2 {
|
||||
let info = match dev.get_authenticator_info() {
|
||||
Some(info) => info,
|
||||
None => {
|
||||
callback.call(Err(HIDError::DeviceNotInitialized.into()));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// Check if extensions have been requested that are not supported by the device
|
||||
if let Some(true) = args.extensions.hmac_secret {
|
||||
if !info.supports_hmac_secret() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::HmacSecret,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set options based on the arguments and the device info.
|
||||
// The user verification option will be set in `determine_puap_if_needed`.
|
||||
options.resident_key = match args.resident_key_req {
|
||||
ResidentKeyRequirement::Required => Some(true),
|
||||
ResidentKeyRequirement::Preferred => {
|
||||
// Use a resident key if the authenticator supports it
|
||||
Some(info.options.resident_key)
|
||||
}
|
||||
ResidentKeyRequirement::Discouraged => Some(false),
|
||||
}
|
||||
} else {
|
||||
// Check that the request can be processed by a CTAP1 device.
|
||||
// See CTAP 2.1 Section 10.2. Some additional checks are performed in
|
||||
// MakeCredentials::RequestCtap1
|
||||
if args.resident_key_req == ResidentKeyRequirement::Required {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::ResidentKey,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
if args.user_verification_req == UserVerificationRequirement::Required {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::UserVerification,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
if !args
|
||||
.pub_cred_params
|
||||
.iter()
|
||||
.any(|x| x.alg == COSEAlgorithm::ES256)
|
||||
{
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::PubCredParams,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut makecred = MakeCredentials::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(args.relying_party),
|
||||
Some(args.user),
|
||||
args.pub_cred_params,
|
||||
args.exclude_list,
|
||||
options,
|
||||
args.extensions,
|
||||
args.pin,
|
||||
);
|
||||
|
||||
let mut skip_uv = false;
|
||||
while alive() {
|
||||
// Requesting both because pre-flighting (credential list filtering)
|
||||
// can potentially send GetAssertion-commands
|
||||
let permissions =
|
||||
PinUvAuthTokenPermission::MakeCredential | PinUvAuthTokenPermission::GetAssertion;
|
||||
|
||||
let pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut makecred,
|
||||
dev,
|
||||
skip_uv,
|
||||
permissions,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Do "pre-flight": Filter the exclude-list
|
||||
if dev.get_protocol() == FidoProtocol::CTAP2 {
|
||||
makecred.exclude_list = unwrap_result!(
|
||||
do_credential_list_filtering_ctap2(
|
||||
dev,
|
||||
&makecred.exclude_list,
|
||||
&makecred.rp,
|
||||
pin_uv_auth_result.get_pin_uv_auth_token(),
|
||||
),
|
||||
callback
|
||||
);
|
||||
} else {
|
||||
let key_handle = do_credential_list_filtering_ctap1(
|
||||
dev,
|
||||
&makecred.exclude_list,
|
||||
&makecred.rp,
|
||||
&makecred.client_data_hash,
|
||||
);
|
||||
// That handle was already registered with the token
|
||||
if key_handle.is_some() {
|
||||
// Now we need to send a dummy registration request, to make the token blink
|
||||
// Spec says "dummy appid and invalid challenge". We use the same, as we do for
|
||||
// making the token blink upon device selection.
|
||||
send_status(&status, crate::StatusUpdate::PresenceRequired);
|
||||
let msg = dummy_make_credentials_cmd();
|
||||
let _ = dev.send_msg_cancellable(&msg, alive); // Ignore answer, return "CredentialExcluded"
|
||||
callback.call(Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::CredentialExcluded,
|
||||
None,
|
||||
))
|
||||
.into()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{makecred:?} using {pin_uv_auth_result:?}");
|
||||
debug!("------------------------------------------------------------------");
|
||||
send_status(&status, crate::StatusUpdate::PresenceRequired);
|
||||
let resp = dev.send_msg_cancellable(&makecred, alive);
|
||||
match resp {
|
||||
Ok(MakeCredentialsResult(attestation)) => {
|
||||
callback.call(Ok(RegisterResult::CTAP2(attestation)));
|
||||
return true;
|
||||
}
|
||||
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 matches!(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 matches!(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 matches!(
|
||||
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(HIDError::Command(CommandError::StatusCode(StatusCode::CredentialExcluded, _))) => {
|
||||
callback.call(Err(AuthenticatorError::CredentialExcluded));
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error happened: {e}");
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn sign<Dev: FidoDevice>(
|
||||
dev: &mut Dev,
|
||||
args: SignArgs,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::SignResult>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
) -> bool {
|
||||
if dev.get_protocol() == FidoProtocol::CTAP2 {
|
||||
let info = match dev.get_authenticator_info() {
|
||||
Some(info) => info,
|
||||
None => {
|
||||
callback.call(Err(HIDError::DeviceNotInitialized.into()));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// Check if extensions have been requested that are not supported by the device
|
||||
if args.extensions.hmac_secret.is_some() && !info.supports_hmac_secret() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::HmacSecret,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Check that the request can be processed by a CTAP1 device.
|
||||
// See CTAP 2.1 Section 10.3. Some additional checks are performed in
|
||||
// GetAssertion::RequestCtap1
|
||||
if args.user_verification_req == UserVerificationRequirement::Required {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::UserVerification,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
if args.allow_list.is_empty() {
|
||||
callback.call(Err(AuthenticatorError::UnsupportedOption(
|
||||
UnsupportedOption::EmptyAllowList,
|
||||
)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut get_assertion = GetAssertion::new(
|
||||
ClientDataHash(args.client_data_hash),
|
||||
RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: args.relying_party_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
}),
|
||||
args.allow_list,
|
||||
GetAssertionOptions {
|
||||
user_presence: Some(args.user_presence_req),
|
||||
user_verification: None,
|
||||
},
|
||||
args.extensions,
|
||||
args.pin,
|
||||
args.alternate_rp_id,
|
||||
);
|
||||
|
||||
let mut skip_uv = false;
|
||||
while alive() {
|
||||
let pin_uv_auth_result = match determine_puap_if_needed(
|
||||
&mut get_assertion,
|
||||
dev,
|
||||
skip_uv,
|
||||
PinUvAuthTokenPermission::GetAssertion,
|
||||
args.user_verification_req,
|
||||
&status,
|
||||
&callback,
|
||||
alive,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(()) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Third, use the shared secret in the extensions, if requested
|
||||
if let Some(extension) = get_assertion.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 false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do "pre-flight": Filter the allow-list
|
||||
let original_allow_list_was_empty = get_assertion.allow_list.is_empty();
|
||||
if dev.get_protocol() == FidoProtocol::CTAP2 {
|
||||
get_assertion.allow_list = unwrap_result!(
|
||||
do_credential_list_filtering_ctap2(
|
||||
dev,
|
||||
&get_assertion.allow_list,
|
||||
&get_assertion.rp,
|
||||
pin_uv_auth_result.get_pin_uv_auth_token(),
|
||||
),
|
||||
callback
|
||||
);
|
||||
} else {
|
||||
let key_handle = do_credential_list_filtering_ctap1(
|
||||
dev,
|
||||
&get_assertion.allow_list,
|
||||
&get_assertion.rp,
|
||||
&get_assertion.client_data_hash,
|
||||
);
|
||||
match key_handle {
|
||||
Some(key_handle) => {
|
||||
get_assertion.allow_list = vec![key_handle];
|
||||
}
|
||||
None => {
|
||||
get_assertion.allow_list.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the incoming list was not empty, but the filtered list is, we have to error out
|
||||
if !original_allow_list_was_empty && get_assertion.allow_list.is_empty() {
|
||||
// We have to collect a user interaction
|
||||
send_status(&status, crate::StatusUpdate::PresenceRequired);
|
||||
let msg = dummy_make_credentials_cmd();
|
||||
let _ = dev.send_msg_cancellable(&msg, alive); // Ignore answer, return "NoCredentials"
|
||||
callback.call(Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::NoCredentials,
|
||||
None,
|
||||
))
|
||||
.into()));
|
||||
return false;
|
||||
}
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
|
||||
debug!("------------------------------------------------------------------");
|
||||
send_status(&status, crate::StatusUpdate::PresenceRequired);
|
||||
let mut resp = dev.send_msg_cancellable(&get_assertion, 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) = get_assertion.alternate_rp_id {
|
||||
get_assertion.rp = RelyingPartyWrapper::Data(RelyingParty {
|
||||
id: alternate_rp_id,
|
||||
..Default::default()
|
||||
});
|
||||
get_assertion.alternate_rp_id = None;
|
||||
resp = dev.send_msg_cancellable(&get_assertion, alive);
|
||||
}
|
||||
}
|
||||
match resp {
|
||||
Ok(assertions) => {
|
||||
callback.call(Ok(SignResult::CTAP2(assertions)));
|
||||
return true;
|
||||
}
|
||||
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 matches!(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 matches!(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 matches!(
|
||||
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)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn reset_helper(
|
||||
dev: &mut Device,
|
||||
selector: Sender<DeviceSelectorEvent>,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) {
|
||||
let reset = Reset {};
|
||||
info!("Device {:?} continues with the reset process", dev.id());
|
||||
|
||||
debug!("------------------------------------------------------------------");
|
||||
debug!("{:?}", reset);
|
||||
debug!("------------------------------------------------------------------");
|
||||
send_status(&status, crate::StatusUpdate::PresenceRequired);
|
||||
let resp = dev.send_cbor_cancellable(&reset, keep_alive);
|
||||
if resp.is_ok() {
|
||||
// The DeviceSelector could already be dead, but it might also wait
|
||||
// for us to respond, in order to cancel all other tokens in case
|
||||
// we skipped the "blinking"-action and went straight for the actual
|
||||
// request.
|
||||
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
|
||||
}
|
||||
|
||||
match resp {
|
||||
Ok(()) => callback.call(Ok(())),
|
||||
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
|
||||
Err(HIDError::Command(CommandError::StatusCode(StatusCode::ChannelBusy, _))) => {}
|
||||
Err(e) => {
|
||||
warn!("error happened: {}", e);
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_or_change_pin_helper(
|
||||
dev: &mut Device,
|
||||
mut current_pin: Option<Pin>,
|
||||
new_pin: Pin,
|
||||
status: Sender<crate::StatusUpdate>,
|
||||
callback: StateCallback<crate::Result<crate::ResetResult>>,
|
||||
alive: &dyn Fn() -> bool,
|
||||
) {
|
||||
let mut shared_secret = match dev.establish_shared_secret() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let authinfo = match dev.get_authenticator_info() {
|
||||
Some(i) => i.clone(),
|
||||
None => {
|
||||
callback.call(Err(HIDError::DeviceNotInitialized.into()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// If the device has a min PIN use that, otherwise default to 4 according to Spec
|
||||
if new_pin.as_bytes().len() < authinfo.min_pin_length.unwrap_or(4) as usize {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
|
||||
return;
|
||||
}
|
||||
|
||||
// As per Spec: "Maximum PIN Length: UTF-8 representation MUST NOT exceed 63 bytes"
|
||||
if new_pin.as_bytes().len() >= 64 {
|
||||
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong(
|
||||
new_pin.as_bytes().len(),
|
||||
))));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a client-pin is already set, or if a new one should be created
|
||||
let res = if Some(true) == authinfo.options.client_pin {
|
||||
let mut res;
|
||||
let mut was_invalid = false;
|
||||
let mut retries = None;
|
||||
loop {
|
||||
// current_pin will only be Some() in the interactive mode (running `manage()`)
|
||||
// In case that PIN is wrong, we want to avoid an endless-loop here with re-trying
|
||||
// that wrong PIN all the time. So we `take()` it, and only test it once.
|
||||
// If that PIN is wrong, we fall back to the "ask_user_for_pin"-method.
|
||||
let curr_pin = match current_pin.take() {
|
||||
None => match ask_user_for_pin(was_invalid, retries, &status, &callback) {
|
||||
Ok(pin) => pin,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
},
|
||||
Some(pin) => pin,
|
||||
};
|
||||
|
||||
res = ChangeExistingPin::new(&authinfo, &shared_secret, &curr_pin, &new_pin)
|
||||
.map_err(HIDError::Command)
|
||||
.and_then(|msg| dev.send_cbor_cancellable(&msg, alive))
|
||||
.map_err(|e| repackage_pin_errors(dev, 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) => {
|
||||
shared_secret = s;
|
||||
}
|
||||
Err(e) => {
|
||||
callback.call(Err(AuthenticatorError::HIDError(e)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
res
|
||||
} else {
|
||||
dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive)
|
||||
.map_err(AuthenticatorError::HIDError)
|
||||
};
|
||||
// the callback is expecting `Result<(), AuthenticatorError>`, but `ChangeExistingPin`
|
||||
// and `SetNewPin` return the default `ClientPinResponse` on success. Just discard it.
|
||||
callback.call(res.map(|_| ()));
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::crypto::PinUvAuthToken;
|
|||
use crate::ctap2::server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper};
|
||||
use crate::errors::AuthenticatorError;
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::{FidoDevice, VirtualFidoDevice};
|
||||
use crate::transport::FidoDevice;
|
||||
use crate::u2ftypes::CTAP1RequestAPDU;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
|
@ -17,7 +17,7 @@ use sha2::{Digest, Sha256};
|
|||
/// should send to the token. Or before a MakeCredential command, to determine
|
||||
/// if this token is already registered or not.
|
||||
#[derive(Debug)]
|
||||
pub struct CheckKeyHandle<'assertion> {
|
||||
pub(crate) struct CheckKeyHandle<'assertion> {
|
||||
pub(crate) key_handle: &'assertion [u8],
|
||||
pub(crate) client_data_hash: &'assertion [u8],
|
||||
pub(crate) rp: &'assertion RelyingPartyWrapper,
|
||||
|
@ -65,13 +65,6 @@ impl<'assertion> RequestCtap1 for CheckKeyHandle<'assertion> {
|
|||
Err(e) => Err(Retryable::Error(HIDError::ApduStatus(e))),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
|
||||
&self,
|
||||
dev: &mut Dev,
|
||||
) -> Result<Self::Output, HIDError> {
|
||||
dev.check_key_handle(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// "pre-flight": In order to determine whether authenticatorMakeCredential's excludeList or
|
||||
|
|
|
@ -1,39 +1,14 @@
|
|||
use serde::de;
|
||||
use serde_cbor::error::Result;
|
||||
use serde_cbor::Deserializer;
|
||||
use std::io::Read;
|
||||
|
||||
pub fn serde_parse_err<E: de::Error>(s: &str) -> E {
|
||||
E::custom(format!("Failed to parse {s}"))
|
||||
}
|
||||
|
||||
pub fn from_slice_stream<'a, T, R: Read, E: de::Error>(data: &mut R) -> Result<T, E>
|
||||
pub fn from_slice_stream<'a, T>(slice: &'a [u8]) -> Result<(&'a [u8], T)>
|
||||
where
|
||||
T: de::Deserialize<'a>,
|
||||
{
|
||||
let mut deserializer = Deserializer::from_reader(data);
|
||||
de::Deserialize::deserialize(&mut deserializer)
|
||||
.map_err(|x| serde_parse_err(&format!("{}: {}", stringify!(T), &x.to_string())))
|
||||
}
|
||||
let mut deserializer = Deserializer::from_slice(slice);
|
||||
let value = de::Deserialize::deserialize(&mut deserializer)?;
|
||||
let rest = &slice[deserializer.byte_offset()..];
|
||||
|
||||
// Parsing routines
|
||||
|
||||
pub fn read_be_u32<R: Read, E: de::Error>(data: &mut R) -> Result<u32, E> {
|
||||
let mut buf = [0; 4];
|
||||
data.read_exact(&mut buf)
|
||||
.map_err(|_| serde_parse_err("u32"))?;
|
||||
Ok(u32::from_be_bytes(buf))
|
||||
}
|
||||
|
||||
pub fn read_be_u16<R: Read, E: de::Error>(data: &mut R) -> Result<u16, E> {
|
||||
let mut buf = [0; 2];
|
||||
data.read_exact(&mut buf)
|
||||
.map_err(|_| serde_parse_err("u16"))?;
|
||||
Ok(u16::from_be_bytes(buf))
|
||||
}
|
||||
|
||||
pub fn read_byte<R: Read, E: de::Error>(data: &mut R) -> Result<u8, E> {
|
||||
match data.bytes().next() {
|
||||
Some(Ok(s)) => Ok(s),
|
||||
_ => Err(serde_parse_err("u8")),
|
||||
}
|
||||
Ok((rest, value))
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux"))]
|
||||
extern crate libudev;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
#[cfg(any(target_os = "freebsd"))]
|
||||
extern crate devd_rs;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(any(target_os = "macos"))]
|
||||
extern crate core_foundation;
|
||||
|
||||
extern crate libc;
|
||||
|
@ -27,25 +27,32 @@ extern crate runloop;
|
|||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
pub mod authenticatorservice;
|
||||
mod consts;
|
||||
mod manager;
|
||||
mod statemachine;
|
||||
mod status_update;
|
||||
mod transport;
|
||||
mod u2fprotocol;
|
||||
mod u2ftypes;
|
||||
|
||||
pub mod authenticatorservice;
|
||||
pub mod crypto;
|
||||
mod manager;
|
||||
|
||||
pub mod ctap2;
|
||||
pub use ctap2::attestation::AttestationObject;
|
||||
pub use ctap2::client_data::CollectedClientData;
|
||||
pub use ctap2::commands::client_pin::{Pin, PinError};
|
||||
pub use ctap2::commands::get_assertion::Assertion;
|
||||
pub use ctap2::commands::get_info::AuthenticatorInfo;
|
||||
pub use ctap2::GetAssertionResult;
|
||||
|
||||
pub mod errors;
|
||||
pub mod statecallback;
|
||||
pub use ctap2::attestation::AttestationObject;
|
||||
pub use ctap2::commands::client_pin::{Pin, PinError};
|
||||
pub use ctap2::commands::get_assertion::{Assertion, GetAssertionResult};
|
||||
pub use ctap2::commands::get_info::AuthenticatorInfo;
|
||||
pub use status_update::{InteractiveRequest, StatusPinUv, StatusUpdate};
|
||||
pub use transport::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice};
|
||||
mod transport;
|
||||
mod virtualdevices;
|
||||
|
||||
mod status_update;
|
||||
pub use status_update::*;
|
||||
|
||||
mod crypto;
|
||||
pub use crypto::COSEAlgorithm;
|
||||
|
||||
// Keep this in sync with the constants in u2fhid-capi.h.
|
||||
bitflags! {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,4 +1,4 @@
|
|||
use super::Pin;
|
||||
use super::{u2ftypes, Pin};
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use serde::{Deserialize, Serialize as DeriveSer, Serializer};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
@ -55,15 +55,30 @@ pub enum StatusPinUv {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum StatusUpdate {
|
||||
/// Device found
|
||||
DeviceAvailable { dev_info: u2ftypes::U2FDeviceInfo },
|
||||
/// Device got removed
|
||||
DeviceUnavailable { dev_info: u2ftypes::U2FDeviceInfo },
|
||||
/// We're waiting for the user to touch their token
|
||||
PresenceRequired,
|
||||
/// We successfully finished the register or sign request
|
||||
Success { dev_info: u2ftypes::U2FDeviceInfo },
|
||||
/// Sent if a PIN is needed (or was wrong), or some other kind of PIN-related
|
||||
/// error occurred. The Sender is for sending back a PIN (if needed).
|
||||
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)
|
||||
/// and the register or signing process continues with this device
|
||||
DeviceSelected(u2ftypes::U2FDeviceInfo),
|
||||
/// Sent when a token was selected for interactive management
|
||||
InteractiveManagement((Sender<InteractiveRequest>, Option<AuthenticatorInfo>)),
|
||||
InteractiveManagement(
|
||||
(
|
||||
Sender<InteractiveRequest>,
|
||||
u2ftypes::U2FDeviceInfo,
|
||||
Option<AuthenticatorInfo>,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use crate::transport::hid::HIDDevice;
|
||||
|
||||
pub use crate::transport::platform::device::Device;
|
||||
|
||||
use runloop::RunLoop;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::mpsc::{channel, RecvTimeoutError, Sender};
|
||||
use std::time::Duration;
|
||||
|
||||
// This import is used, but Rust 1.68 gives a warning
|
||||
#[allow(unused_imports)]
|
||||
use crate::u2ftypes::U2FDevice;
|
||||
|
||||
pub type DeviceID = <Device as HIDDevice>::Id;
|
||||
pub type DeviceBuildParameters = <Device as HIDDevice>::BuildParameters;
|
||||
|
||||
|
@ -184,7 +186,6 @@ pub mod tests {
|
|||
use crate::{
|
||||
consts::Capability,
|
||||
ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorOptions},
|
||||
transport::FidoDevice,
|
||||
u2ftypes::U2FDeviceInfo,
|
||||
};
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
extern crate libc;
|
||||
|
||||
use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
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::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::from_unix_result;
|
||||
use crate::util::io_err;
|
||||
use std::ffi::{CString, OsString};
|
||||
|
@ -26,7 +26,6 @@ pub struct Device {
|
|||
dev_info: Option<U2FDeviceInfo>,
|
||||
secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
|
@ -122,36 +121,7 @@ impl Write for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = OsString;
|
||||
type Id = OsString;
|
||||
|
||||
fn new(path: OsString) -> Result<Self, (HIDError, Self::Id)> {
|
||||
let cstr =
|
||||
CString::new(path.as_bytes()).map_err(|_| (HIDError::DeviceError, path.clone()))?;
|
||||
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
|
||||
let fd = from_unix_result(fd).map_err(|e| (e.into(), path.clone()))?;
|
||||
let mut res = Self {
|
||||
path,
|
||||
fd,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -183,15 +153,29 @@ impl HIDDevice for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = OsString;
|
||||
type Id = OsString;
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
fn new(path: OsString) -> Result<Self, (HIDError, Self::Id)> {
|
||||
let cstr =
|
||||
CString::new(path.as_bytes()).map_err(|_| (HIDError::DeviceError, path.clone()))?;
|
||||
let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
|
||||
let fd = from_unix_result(fd).map_err(|e| (e.into(), path.clone()))?;
|
||||
let mut res = Self {
|
||||
path,
|
||||
fd,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
|
@ -199,6 +183,10 @@ impl FidoDevice for Device {
|
|||
self.cid != CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
if !uhid::is_u2f_device(self.fd) {
|
||||
return false;
|
||||
|
@ -224,12 +212,6 @@ impl FidoDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -1,60 +1,60 @@
|
|||
use crate::consts::{HIDCmd, CID_BROADCAST};
|
||||
|
||||
use crate::ctap2::commands::{
|
||||
CommandError, Request, RequestCtap1, RequestCtap2, Retryable, StatusCode,
|
||||
};
|
||||
use crate::transport::errors::{ApduErrorStatus, HIDError};
|
||||
use crate::transport::{FidoDevice, FidoDeviceIO, FidoProtocol};
|
||||
use crate::u2ftypes::{U2FDeviceInfo, U2FHIDCont, U2FHIDInit, U2FHIDInitResp};
|
||||
use crate::util::io_err;
|
||||
use crate::crypto::SharedSecret;
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::{errors::HIDError, Nonce};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo, U2FHIDCont, U2FHIDInit, U2FHIDInitResp};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::cmp::Eq;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub trait HIDDevice: FidoDevice + Read + Write {
|
||||
pub trait HIDDevice
|
||||
where
|
||||
Self: io::Read,
|
||||
Self: io::Write,
|
||||
Self: U2FDevice,
|
||||
Self: Sized,
|
||||
Self: fmt::Debug,
|
||||
{
|
||||
type BuildParameters: Sized;
|
||||
type Id: fmt::Debug + PartialEq + Eq + Hash + Sized;
|
||||
|
||||
// Open device, verify that it is indeed a CTAP device and potentially read initial values
|
||||
fn new(parameters: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)>;
|
||||
fn id(&self) -> Self::Id;
|
||||
|
||||
fn get_device_info(&self) -> U2FDeviceInfo;
|
||||
fn set_device_info(&mut self, dev_info: U2FDeviceInfo);
|
||||
|
||||
// Channel ID management
|
||||
fn get_cid(&self) -> &[u8; 4];
|
||||
fn set_cid(&mut self, cid: [u8; 4]);
|
||||
|
||||
// HID report sizes
|
||||
fn in_rpt_size(&self) -> usize;
|
||||
fn out_rpt_size(&self) -> usize;
|
||||
|
||||
fn get_property(&self, prop_name: &str) -> io::Result<String>;
|
||||
fn initialized(&self) -> bool;
|
||||
// Check if the device is actually a token
|
||||
fn is_u2f(&mut self) -> bool;
|
||||
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>;
|
||||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo);
|
||||
fn set_shared_secret(&mut self, secret: SharedSecret);
|
||||
fn get_shared_secret(&self) -> Option<&SharedSecret>;
|
||||
|
||||
// Initialize on a protocol-level
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
fn initialize(&mut self, noncecmd: Nonce) -> Result<(), HIDError> {
|
||||
if self.initialized() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut nonce = [0u8; 8];
|
||||
thread_rng().fill_bytes(&mut nonce);
|
||||
let nonce = match noncecmd {
|
||||
Nonce::Use(x) => x,
|
||||
Nonce::CreateRandom => {
|
||||
let mut nonce = [0u8; 8];
|
||||
thread_rng().fill_bytes(&mut nonce);
|
||||
nonce
|
||||
}
|
||||
};
|
||||
|
||||
// Send Init to broadcast address to create a new channel
|
||||
self.set_cid(CID_BROADCAST);
|
||||
let (cmd, raw) = HIDDevice::sendrecv(self, HIDCmd::Init, &nonce, &|| true)?;
|
||||
let (cmd, raw) = self.sendrecv(HIDCmd::Init, &nonce, &|| true)?;
|
||||
if cmd != HIDCmd::Init {
|
||||
return Err(HIDError::DeviceError);
|
||||
}
|
||||
|
||||
let rsp = U2FHIDInitResp::read(&raw, &nonce)?;
|
||||
// Set the new Channel ID
|
||||
// Get the new Channel ID
|
||||
self.set_cid(rsp.cid);
|
||||
|
||||
let vendor = self
|
||||
|
@ -91,17 +91,11 @@ pub trait HIDDevice: FidoDevice + Read + Write {
|
|||
send: &[u8],
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> io::Result<(HIDCmd, Vec<u8>)> {
|
||||
self.u2f_write(cmd.into(), send)?;
|
||||
debug!("sent to Device {:?} cmd={:?}: {:?}", self.id(), cmd, send);
|
||||
let cmd: u8 = cmd.into();
|
||||
self.u2f_write(cmd, send)?;
|
||||
loop {
|
||||
let (cmd, data) = self.u2f_read()?;
|
||||
if cmd != HIDCmd::Keepalive {
|
||||
debug!(
|
||||
"got from Device {:?} status={:?}: {:?}",
|
||||
self.id(),
|
||||
cmd,
|
||||
data
|
||||
);
|
||||
return Ok((cmd, data));
|
||||
}
|
||||
// The authenticator might send us HIDCmd::Keepalive messages indefinitely, e.g. if
|
||||
|
@ -114,7 +108,7 @@ pub trait HIDDevice: FidoDevice + Read + Write {
|
|||
|
||||
// If this is a CTAP2 device we can tell the authenticator to cancel the transaction on its
|
||||
// side as well. There's nothing to do for U2F/CTAP1 devices.
|
||||
if self.get_protocol() == FidoProtocol::CTAP2 {
|
||||
if self.get_authenticator_info().is_some() {
|
||||
self.u2f_write(u8::from(HIDCmd::Cancel), &[])?;
|
||||
}
|
||||
// For CTAP2 devices we expect to read
|
||||
|
@ -157,83 +151,3 @@ pub trait HIDDevice: FidoDevice + Read + Write {
|
|||
Ok((cmd, data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HIDDevice> FidoDeviceIO for T {
|
||||
fn send_msg_cancellable<Out, Req: Request<Out>>(
|
||||
&mut self,
|
||||
msg: &Req,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Result<Out, HIDError> {
|
||||
if !self.initialized() {
|
||||
return Err(HIDError::DeviceNotInitialized);
|
||||
}
|
||||
|
||||
match self.get_protocol() {
|
||||
FidoProtocol::CTAP1 => self.send_ctap1_cancellable(msg, keep_alive),
|
||||
FidoProtocol::CTAP2 => self.send_cbor_cancellable(msg, keep_alive),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_cbor_cancellable<Req: RequestCtap2>(
|
||||
&mut self,
|
||||
msg: &Req,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Result<Req::Output, HIDError> {
|
||||
debug!("sending {:?} to {:?}", msg, self);
|
||||
|
||||
let mut data = msg.wire_format()?;
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(data.len() + 1);
|
||||
// CTAP2 command
|
||||
buf.push(Req::command() as u8);
|
||||
// payload
|
||||
buf.append(&mut data);
|
||||
let buf = buf;
|
||||
|
||||
let (cmd, resp) = self.sendrecv(HIDCmd::Cbor, &buf, keep_alive)?;
|
||||
if cmd == HIDCmd::Cbor {
|
||||
Ok(msg.handle_response_ctap2(self, &resp)?)
|
||||
} else {
|
||||
Err(HIDError::UnexpectedCmd(cmd.into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn send_ctap1_cancellable<Req: RequestCtap1>(
|
||||
&mut self,
|
||||
msg: &Req,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Result<Req::Output, HIDError> {
|
||||
debug!("sending {:?} to {:?}", msg, self);
|
||||
let (data, add_info) = msg.ctap1_format()?;
|
||||
|
||||
while keep_alive() {
|
||||
// sendrecv will not block with a CTAP1 device
|
||||
let (cmd, mut data) = self.sendrecv(HIDCmd::Msg, &data, &|| true)?;
|
||||
if cmd == HIDCmd::Msg {
|
||||
if data.len() < 2 {
|
||||
return Err(io_err("Unexpected Response: shorter than expected").into());
|
||||
}
|
||||
let split_at = data.len() - 2;
|
||||
let status = data.split_off(split_at);
|
||||
// This will bubble up error if status != no error
|
||||
let status = ApduErrorStatus::from([status[0], status[1]]);
|
||||
|
||||
match msg.handle_response_ctap1(status, &data, &add_info) {
|
||||
Ok(out) => return Ok(out),
|
||||
Err(Retryable::Retry) => {
|
||||
// sleep 100ms then loop again
|
||||
// TODO(baloo): meh, use tokio instead?
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
Err(Retryable::Error(e)) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
return Err(HIDError::UnexpectedCmd(cmd.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::KeepaliveCancel,
|
||||
None,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
allow(clippy::cast_lossless, clippy::needless_lifetimes)
|
||||
)]
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux"))]
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux"))]
|
||||
use crate::consts::{INIT_HEADER_SIZE, MAX_HID_RPT_SIZE};
|
||||
|
||||
// The 4 MSBs (the tag) are set when it's a long item.
|
||||
|
@ -181,7 +181,7 @@ pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux"))]
|
||||
pub fn read_hid_rpt_sizes(desc: ReportDescriptor) -> io::Result<(usize, usize)> {
|
||||
let mut in_rpt_count = None;
|
||||
let mut out_rpt_count = None;
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate libc;
|
||||
use crate::consts::{Capability, CID_BROADCAST};
|
||||
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::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::from_unix_result;
|
||||
use std::fs::OpenOptions;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -27,7 +27,6 @@ pub struct Device {
|
|||
dev_info: Option<U2FDeviceInfo>,
|
||||
secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl PartialEq for Device {
|
||||
|
@ -69,41 +68,7 @@ impl Write for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = PathBuf;
|
||||
type Id = PathBuf;
|
||||
|
||||
fn new(path: PathBuf) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("Opening device {:?}", path);
|
||||
let fd = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|e| (HIDError::IO(Some(path.clone()), e), path.clone()))?;
|
||||
let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd.as_raw_fd());
|
||||
let mut res = Self {
|
||||
path,
|
||||
fd,
|
||||
in_rpt_size,
|
||||
out_rpt_size,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path))
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -135,22 +100,45 @@ impl HIDDevice for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = PathBuf;
|
||||
type Id = PathBuf;
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
fn new(path: PathBuf) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("Opening device {:?}", path);
|
||||
let fd = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|e| (HIDError::IO(Some(path.clone()), e), path.clone()))?;
|
||||
let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd.as_raw_fd());
|
||||
let mut res = Self {
|
||||
path,
|
||||
fd,
|
||||
in_rpt_size,
|
||||
out_rpt_size,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path))
|
||||
}
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
// During successful init, the broadcast channel id gets replaced by an actual one
|
||||
// During successful init, the broadcast channel id gets repplaced by an actual one
|
||||
self.cid != CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
hidraw::is_u2f_device(self.fd.as_raw_fd())
|
||||
}
|
||||
|
@ -170,12 +158,6 @@ impl FidoDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -49,6 +49,3 @@ include!("ioctl_s390xbe.rs");
|
|||
|
||||
#[cfg(all(target_arch = "riscv64", target_endian = "little"))]
|
||||
include!("ioctl_riscv64.rs");
|
||||
|
||||
#[cfg(all(target_arch = "loongarch64", target_endian = "little"))]
|
||||
include!("ioctl_loongarch64.rs");
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
/* automatically generated by rust-bindgen */
|
||||
|
||||
pub type __u32 = ::std::os::raw::c_uint;
|
||||
pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
|
||||
pub const _HIDIOCGRDESC: __u32 = 2416199682;
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
extern crate log;
|
||||
|
||||
use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
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::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use core_foundation::base::*;
|
||||
use core_foundation::string::*;
|
||||
use std::convert::TryInto;
|
||||
|
@ -29,7 +29,6 @@ pub struct Device {
|
|||
dev_info: Option<U2FDeviceInfo>,
|
||||
secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
|
@ -131,27 +130,7 @@ impl Write for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = (IOHIDDeviceRef, Receiver<Vec<u8>>);
|
||||
type Id = IOHIDDeviceRef;
|
||||
|
||||
fn new(dev_ids: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
|
||||
let (device_ref, report_rx) = dev_ids;
|
||||
Ok(Self {
|
||||
device_ref,
|
||||
cid: CID_BROADCAST,
|
||||
report_rx: Some(report_rx),
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
})
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.device_ref
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -183,20 +162,30 @@ impl HIDDevice for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = (IOHIDDeviceRef, Receiver<Vec<u8>>);
|
||||
type Id = IOHIDDeviceRef;
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
fn new(dev_ids: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
|
||||
let (device_ref, report_rx) = dev_ids;
|
||||
Ok(Self {
|
||||
device_ref,
|
||||
cid: CID_BROADCAST,
|
||||
report_rx: Some(report_rx),
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
self.cid != CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.device_ref
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -215,12 +204,6 @@ impl FidoDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
use crate::consts::{Capability, HIDCmd, CID_BROADCAST};
|
||||
use crate::consts::CID_BROADCAST;
|
||||
use crate::crypto::SharedSecret;
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::device_selector::DeviceCommand;
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, FidoProtocol, HIDError};
|
||||
use crate::u2ftypes::{U2FDeviceInfo, U2FHIDInitResp};
|
||||
use crate::transport::{hid::HIDDevice, FidoDevice, HIDError};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
|
@ -24,7 +24,6 @@ pub struct Device {
|
|||
pub authenticator_info: Option<AuthenticatorInfo>,
|
||||
pub sender: Option<Sender<DeviceCommand>>,
|
||||
pub receiver: Option<Receiver<DeviceCommand>>,
|
||||
pub protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
|
@ -105,28 +104,7 @@ impl Hash for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type Id = String;
|
||||
type BuildParameters = &'static str; // None used
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn new(id: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
|
||||
Ok(Device {
|
||||
id: id.to_string(),
|
||||
cid: CID_BROADCAST,
|
||||
reads: vec![],
|
||||
writes: vec![],
|
||||
dev_info: None,
|
||||
authenticator_info: None,
|
||||
sender: None,
|
||||
receiver: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
})
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -146,7 +124,6 @@ impl HIDDevice for Device {
|
|||
fn get_property(&self, prop_name: &str) -> io::Result<String> {
|
||||
Ok(format!("{prop_name} not implemented"))
|
||||
}
|
||||
|
||||
fn get_device_info(&self) -> U2FDeviceInfo {
|
||||
self.dev_info.clone().unwrap()
|
||||
}
|
||||
|
@ -154,68 +131,11 @@ impl HIDDevice for Device {
|
|||
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
|
||||
self.dev_info = Some(dev_info);
|
||||
}
|
||||
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
if self.initialized() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
|
||||
|
||||
// Send Init to broadcast address to create a new channel
|
||||
self.set_cid(CID_BROADCAST);
|
||||
let (cmd, raw) = HIDDevice::sendrecv(self, HIDCmd::Init, &nonce, &|| true)?;
|
||||
if cmd != HIDCmd::Init {
|
||||
return Err(HIDError::DeviceError);
|
||||
}
|
||||
|
||||
let rsp = U2FHIDInitResp::read(&raw, &nonce)?;
|
||||
|
||||
// Set the new Channel ID
|
||||
self.set_cid(rsp.cid);
|
||||
|
||||
let info = U2FDeviceInfo {
|
||||
vendor_name: "Test vendor".as_bytes().to_vec(),
|
||||
device_name: "Test device".as_bytes().to_vec(),
|
||||
version_interface: rsp.version_interface,
|
||||
version_major: rsp.version_major,
|
||||
version_minor: rsp.version_minor,
|
||||
version_build: rsp.version_build,
|
||||
cap_flags: rsp.cap_flags,
|
||||
};
|
||||
debug!("{:?}: {:?}", self.id(), info);
|
||||
self.set_device_info(info);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
self.get_cid() != &CID_BROADCAST
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
self.sender.is_some()
|
||||
}
|
||||
|
||||
fn get_shared_secret(&self) -> std::option::Option<&SharedSecret> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_shared_secret(&mut self, _: SharedSecret) {
|
||||
// Nothing
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type Id = String;
|
||||
type BuildParameters = &'static str; // None used
|
||||
|
||||
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
|
||||
self.authenticator_info.as_ref()
|
||||
|
@ -225,11 +145,37 @@ impl FidoDevice for Device {
|
|||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
fn set_shared_secret(&mut self, _: SharedSecret) {
|
||||
// Nothing
|
||||
}
|
||||
fn get_shared_secret(&self) -> std::option::Option<&SharedSecret> {
|
||||
None
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
fn new(id: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
|
||||
Ok(Device {
|
||||
id: id.to_string(),
|
||||
cid: CID_BROADCAST,
|
||||
reads: vec![],
|
||||
writes: vec![],
|
||||
dev_info: None,
|
||||
authenticator_info: None,
|
||||
sender: None,
|
||||
receiver: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
self.get_cid() != &CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
self.sender.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
use crate::consts::{Capability, HIDCmd};
|
||||
use crate::crypto::{PinUvAuthProtocol, PinUvAuthToken, SharedSecret};
|
||||
use crate::ctap2::commands::client_pin::{
|
||||
ClientPIN, ClientPinResponse, GetKeyAgreement, GetPinToken,
|
||||
GetPinUvAuthTokenUsingPinWithPermissions, GetPinUvAuthTokenUsingUvWithPermissions,
|
||||
PinUvAuthTokenPermission,
|
||||
GetKeyAgreement, GetPinToken, GetPinUvAuthTokenUsingPinWithPermissions,
|
||||
GetPinUvAuthTokenUsingUvWithPermissions, PinUvAuthTokenPermission,
|
||||
};
|
||||
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult};
|
||||
use crate::ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorVersion, GetInfo};
|
||||
use crate::ctap2::commands::get_version::{GetVersion, U2FInfo};
|
||||
use crate::ctap2::commands::make_credentials::{
|
||||
dummy_make_credentials_cmd, MakeCredentials, MakeCredentialsResult,
|
||||
};
|
||||
use crate::ctap2::commands::reset::Reset;
|
||||
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;
|
||||
use crate::ctap2::commands::{CommandError, Request, RequestCtap1, RequestCtap2, StatusCode};
|
||||
use crate::ctap2::preflight::CheckKeyHandle;
|
||||
use crate::ctap2::commands::{
|
||||
CommandError, Request, RequestCtap1, RequestCtap2, Retryable, StatusCode,
|
||||
};
|
||||
use crate::transport::device_selector::BlinkResult;
|
||||
use crate::transport::errors::HIDError;
|
||||
|
||||
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::fmt;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod device_selector;
|
||||
pub mod errors;
|
||||
|
@ -71,13 +70,15 @@ pub mod platform;
|
|||
#[path = "mock/mod.rs"]
|
||||
pub mod platform;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FidoProtocol {
|
||||
CTAP1,
|
||||
CTAP2,
|
||||
#[derive(Debug)]
|
||||
pub enum Nonce {
|
||||
CreateRandom,
|
||||
Use([u8; 8]),
|
||||
}
|
||||
|
||||
pub trait FidoDeviceIO {
|
||||
// TODO(MS): This is the lazy way: FidoDevice currently only extends HIDDevice by more functions,
|
||||
// but the goal is to remove U2FDevice entirely and copy over the trait-definition here
|
||||
pub trait FidoDevice: HIDDevice {
|
||||
fn send_msg<Out, Req: Request<Out>>(&mut self, msg: &Req) -> Result<Out, HIDError> {
|
||||
self.send_msg_cancellable(msg, &|| true)
|
||||
}
|
||||
|
@ -94,64 +95,115 @@ pub trait FidoDeviceIO {
|
|||
&mut self,
|
||||
msg: &Req,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Result<Out, HIDError>;
|
||||
) -> Result<Out, HIDError> {
|
||||
if !self.initialized() {
|
||||
return Err(HIDError::DeviceNotInitialized);
|
||||
}
|
||||
|
||||
if self.get_authenticator_info().is_some() {
|
||||
self.send_cbor_cancellable(msg, keep_alive)
|
||||
} else {
|
||||
self.send_ctap1_cancellable(msg, keep_alive)
|
||||
}
|
||||
}
|
||||
|
||||
fn send_cbor_cancellable<Req: RequestCtap2>(
|
||||
&mut self,
|
||||
msg: &Req,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Result<Req::Output, HIDError>;
|
||||
) -> Result<Req::Output, HIDError> {
|
||||
debug!("sending {:?} to {:?}", msg, self);
|
||||
|
||||
let mut data = msg.wire_format()?;
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(data.len() + 1);
|
||||
// CTAP2 command
|
||||
buf.push(Req::command() as u8);
|
||||
// payload
|
||||
buf.append(&mut data);
|
||||
let buf = buf;
|
||||
|
||||
let (cmd, resp) = self.sendrecv(HIDCmd::Cbor, &buf, keep_alive)?;
|
||||
debug!(
|
||||
"got from Device {:?} status={:?}: {:?}",
|
||||
self.id(),
|
||||
cmd,
|
||||
resp
|
||||
);
|
||||
if cmd == HIDCmd::Cbor {
|
||||
Ok(msg.handle_response_ctap2(self, &resp)?)
|
||||
} else {
|
||||
Err(HIDError::UnexpectedCmd(cmd.into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn send_ctap1_cancellable<Req: RequestCtap1>(
|
||||
&mut self,
|
||||
msg: &Req,
|
||||
keep_alive: &dyn Fn() -> bool,
|
||||
) -> Result<Req::Output, HIDError>;
|
||||
}
|
||||
) -> Result<Req::Output, HIDError> {
|
||||
debug!("sending {:?} to {:?}", msg, self);
|
||||
let (data, add_info) = msg.ctap1_format()?;
|
||||
|
||||
pub trait FidoDevice: FidoDeviceIO
|
||||
where
|
||||
Self: Sized,
|
||||
Self: fmt::Debug,
|
||||
{
|
||||
fn pre_init(&mut self) -> Result<(), HIDError>;
|
||||
fn initialized(&self) -> bool;
|
||||
|
||||
// Check if the device is actually a token
|
||||
fn is_u2f(&mut self) -> bool;
|
||||
fn should_try_ctap2(&self) -> bool;
|
||||
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>;
|
||||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo);
|
||||
|
||||
// `get_protocol()` indicates whether we're using CTAP1 or CTAP2.
|
||||
// Prior to initializing the device, `get_protocol()` should return CTAP2 unless
|
||||
// there's a reason to believe that the device does not support CTAP2 (e.g. if
|
||||
// it's a HID device and it does not have the CBOR capability).
|
||||
fn get_protocol(&self) -> FidoProtocol;
|
||||
|
||||
// We do not provide a generic `set_protocol(..)` function as this would have complicated
|
||||
// interactions with the AuthenticatorInfo state.
|
||||
fn downgrade_to_ctap1(&mut self);
|
||||
|
||||
fn get_shared_secret(&self) -> Option<&SharedSecret>;
|
||||
fn set_shared_secret(&mut self, secret: SharedSecret);
|
||||
|
||||
fn init(&mut self) -> Result<(), HIDError> {
|
||||
self.pre_init()?;
|
||||
|
||||
if self.should_try_ctap2() {
|
||||
let command = GetInfo::default();
|
||||
if let Ok(info) = self.send_cbor(&command) {
|
||||
debug!("{:?}", info);
|
||||
if info.max_supported_version() == AuthenticatorVersion::U2F_V2 {
|
||||
self.downgrade_to_ctap1();
|
||||
while keep_alive() {
|
||||
// sendrecv will not block with a CTAP1 device
|
||||
let (cmd, mut data) = self.sendrecv(HIDCmd::Msg, &data, &|| true)?;
|
||||
debug!(
|
||||
"got from Device {:?} status={:?}: {:?}",
|
||||
self.id(),
|
||||
cmd,
|
||||
data
|
||||
);
|
||||
if cmd == HIDCmd::Msg {
|
||||
if data.len() < 2 {
|
||||
return Err(io_err("Unexpected Response: shorter than expected").into());
|
||||
}
|
||||
self.set_authenticator_info(info);
|
||||
return Ok(());
|
||||
let split_at = data.len() - 2;
|
||||
let status = data.split_off(split_at);
|
||||
// This will bubble up error if status != no error
|
||||
let status = ApduErrorStatus::from([status[0], status[1]]);
|
||||
|
||||
match msg.handle_response_ctap1(status, &data, &add_info) {
|
||||
Ok(out) => return Ok(out),
|
||||
Err(Retryable::Retry) => {
|
||||
// sleep 100ms then loop again
|
||||
// TODO(baloo): meh, use tokio instead?
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
Err(Retryable::Error(e)) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
return Err(HIDError::UnexpectedCmd(cmd.into()));
|
||||
}
|
||||
}
|
||||
|
||||
self.downgrade_to_ctap1();
|
||||
Err(HIDError::Command(CommandError::StatusCode(
|
||||
StatusCode::KeepaliveCancel,
|
||||
None,
|
||||
)))
|
||||
}
|
||||
|
||||
// This is ugly as we have 2 init-functions now, but the fastest way currently.
|
||||
fn init(&mut self, nonce: Nonce) -> Result<(), HIDError> {
|
||||
<Self as HIDDevice>::initialize(self, nonce)?;
|
||||
|
||||
// If the device has the CBOR capability flag, then we'll check
|
||||
// for CTAP2 support by sending an authenticatorGetInfo command.
|
||||
// We're not aware of any CTAP2 devices that fail to set the CBOR
|
||||
// capability flag, but we may need to rework this in the future.
|
||||
if self.get_device_info().cap_flags.contains(Capability::CBOR) {
|
||||
let command = GetInfo::default();
|
||||
if let Ok(info) = self.send_cbor(&command) {
|
||||
debug!("{:?}: {:?}", self.id(), info);
|
||||
if info.max_supported_version() != AuthenticatorVersion::U2F_V2 {
|
||||
// Device supports CTAP2
|
||||
self.set_authenticator_info(info);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// An error from GetInfo might indicate that we're talking
|
||||
// to a CTAP1 device that mistakenly claimed the CBOR capability,
|
||||
// so we fallthrough here.
|
||||
}
|
||||
// We want to return an error here if this device doesn't support CTAP1,
|
||||
// so we send a U2F_VERSION command.
|
||||
let command = GetVersion::default();
|
||||
|
@ -160,10 +212,9 @@ where
|
|||
}
|
||||
|
||||
fn block_and_blink(&mut self, keep_alive: &dyn Fn() -> bool) -> BlinkResult {
|
||||
let supports_select_cmd = self.get_protocol() == FidoProtocol::CTAP2
|
||||
&& self.get_authenticator_info().map_or(false, |i| {
|
||||
i.versions.contains(&AuthenticatorVersion::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)
|
||||
|
@ -205,28 +256,20 @@ where
|
|||
|
||||
fn establish_shared_secret(&mut self) -> Result<SharedSecret, HIDError> {
|
||||
// CTAP1 devices don't support establishing a shared secret
|
||||
let info = match (self.get_protocol(), self.get_authenticator_info()) {
|
||||
(FidoProtocol::CTAP2, Some(info)) => info,
|
||||
_ => return Err(HIDError::UnsupportedCommand),
|
||||
let info = match self.get_authenticator_info() {
|
||||
Some(info) => info,
|
||||
None => return Err(HIDError::UnsupportedCommand),
|
||||
};
|
||||
|
||||
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(pin_protocol.clone());
|
||||
let resp = self.send_cbor(&pin_command)?;
|
||||
if let Some(device_key_agreement_key) = resp.key_agreement {
|
||||
let shared_secret = pin_protocol
|
||||
.encapsulate(&device_key_agreement_key)
|
||||
.map_err(CommandError::from)?;
|
||||
self.set_shared_secret(shared_secret.clone());
|
||||
Ok(shared_secret)
|
||||
} else {
|
||||
Err(HIDError::Command(CommandError::MissingRequiredField(
|
||||
"key_agreement",
|
||||
)))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
/// CTAP 2.0-only version:
|
||||
|
@ -242,21 +285,9 @@ where
|
|||
let shared_secret = self.establish_shared_secret()?;
|
||||
|
||||
let pin_command = GetPinToken::new(&shared_secret, pin);
|
||||
let resp = self.send_cbor(&pin_command)?;
|
||||
if let Some(encrypted_pin_token) = resp.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 = shared_secret
|
||||
.decrypt_pin_token(default_permissions, encrypted_pin_token.as_ref())
|
||||
.map_err(CommandError::from)?;
|
||||
Ok(pin_token)
|
||||
} else {
|
||||
Err(HIDError::Command(CommandError::MissingRequiredField(
|
||||
"pin_token",
|
||||
)))
|
||||
}
|
||||
let pin_token = self.send_cbor(&pin_command)?;
|
||||
|
||||
Ok(pin_token)
|
||||
}
|
||||
|
||||
fn get_pin_uv_auth_token_using_uv_with_permissions(
|
||||
|
@ -271,19 +302,9 @@ where
|
|||
permission,
|
||||
rp_id.cloned(),
|
||||
);
|
||||
let pin_auth_token = self.send_cbor(&pin_command)?;
|
||||
|
||||
let resp = self.send_cbor(&pin_command)?;
|
||||
|
||||
if let Some(encrypted_pin_token) = resp.pin_token {
|
||||
let pin_token = shared_secret
|
||||
.decrypt_pin_token(permission, encrypted_pin_token.as_ref())
|
||||
.map_err(CommandError::from)?;
|
||||
Ok(pin_token)
|
||||
} else {
|
||||
Err(HIDError::Command(CommandError::MissingRequiredField(
|
||||
"pin_token",
|
||||
)))
|
||||
}
|
||||
Ok(pin_auth_token)
|
||||
}
|
||||
|
||||
fn get_pin_uv_auth_token_using_pin_with_permissions(
|
||||
|
@ -306,29 +327,8 @@ where
|
|||
permission,
|
||||
rp_id.cloned(),
|
||||
);
|
||||
let pin_auth_token = self.send_cbor(&pin_command)?;
|
||||
|
||||
let resp = self.send_cbor(&pin_command)?;
|
||||
|
||||
if let Some(encrypted_pin_token) = resp.pin_token {
|
||||
let pin_token = shared_secret
|
||||
.decrypt_pin_token(permission, encrypted_pin_token.as_ref())
|
||||
.map_err(CommandError::from)?;
|
||||
Ok(pin_token)
|
||||
} else {
|
||||
Err(HIDError::Command(CommandError::MissingRequiredField(
|
||||
"pin_token",
|
||||
)))
|
||||
}
|
||||
Ok(pin_auth_token)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VirtualFidoDevice: FidoDevice {
|
||||
fn check_key_handle(&self, req: &CheckKeyHandle) -> Result<(), HIDError>;
|
||||
fn client_pin(&self, req: &ClientPIN) -> Result<ClientPinResponse, HIDError>;
|
||||
fn get_assertion(&self, req: &GetAssertion) -> Result<GetAssertionResult, HIDError>;
|
||||
fn get_info(&self) -> Result<AuthenticatorInfo, HIDError>;
|
||||
fn get_version(&self, req: &GetVersion) -> Result<U2FInfo, HIDError>;
|
||||
fn make_credentials(&self, req: &MakeCredentials) -> Result<MakeCredentialsResult, HIDError>;
|
||||
fn reset(&self, req: &Reset) -> Result<(), HIDError>;
|
||||
fn selection(&self, req: &Selection) -> Result<(), HIDError>;
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate libc;
|
||||
use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
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::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::io_err;
|
||||
use std::ffi::OsString;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -25,7 +25,6 @@ pub struct Device {
|
|||
dev_info: Option<U2FDeviceInfo>,
|
||||
secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
|
@ -127,33 +126,7 @@ impl Write for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = WrappedOpenDevice;
|
||||
type Id = OsString;
|
||||
|
||||
fn new(fido: WrappedOpenDevice) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("device found: {:?}", fido);
|
||||
let mut res = Self {
|
||||
path: fido.os_path,
|
||||
fd: fido.fd,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -185,15 +158,26 @@ impl HIDDevice for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = WrappedOpenDevice;
|
||||
type Id = OsString;
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
fn new(fido: WrappedOpenDevice) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("device found: {:?}", fido);
|
||||
let mut res = Self {
|
||||
path: fido.os_path,
|
||||
fd: fido.fd,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
|
@ -201,6 +185,10 @@ impl FidoDevice for Device {
|
|||
self.cid != CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
if !uhid::is_u2f_device(&self.fd) {
|
||||
return false;
|
||||
|
@ -237,12 +225,6 @@ impl FidoDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate libc;
|
||||
use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
|
||||
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::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use crate::util::{from_unix_result, io_err};
|
||||
use std::ffi::{CString, OsString};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -26,7 +26,6 @@ pub struct Device {
|
|||
dev_info: Option<U2FDeviceInfo>,
|
||||
secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
|
@ -106,35 +105,7 @@ impl Write for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = WrappedOpenDevice;
|
||||
type Id = OsString;
|
||||
|
||||
fn new(fido: WrappedOpenDevice) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("device found: {:?}", fido);
|
||||
let mut res = Self {
|
||||
path: fido.os_path,
|
||||
fd: fido.fd,
|
||||
in_rpt_size: MAX_HID_RPT_SIZE,
|
||||
out_rpt_size: MAX_HID_RPT_SIZE,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -166,15 +137,28 @@ impl HIDDevice for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = WrappedOpenDevice;
|
||||
type Id = OsString;
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
fn new(fido: WrappedOpenDevice) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("device found: {:?}", fido);
|
||||
let mut res = Self {
|
||||
path: fido.os_path,
|
||||
fd: fido.fd,
|
||||
in_rpt_size: MAX_HID_RPT_SIZE,
|
||||
out_rpt_size: MAX_HID_RPT_SIZE,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
|
@ -182,6 +166,10 @@ impl FidoDevice for Device {
|
|||
self.cid != CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
debug!("device {:?} is U2F/FIDO", self.path);
|
||||
|
||||
|
@ -213,12 +201,6 @@ impl FidoDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
use crate::ctap2::commands::get_info::AuthenticatorInfo;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::transport::{FidoDevice, FidoProtocol};
|
||||
use crate::transport::FidoDevice;
|
||||
use crate::transport::{HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use std::hash::Hash;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
|
@ -17,17 +17,47 @@ pub struct Device {}
|
|||
|
||||
impl Read for Device {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
unimplemented!();
|
||||
panic!("not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Device {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
unimplemented!();
|
||||
panic!("not implemented");
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
unimplemented!();
|
||||
panic!("not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
panic!("not implemented");
|
||||
}
|
||||
|
||||
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||
panic!("not implemented");
|
||||
}
|
||||
|
||||
fn in_rpt_size(&self) -> usize {
|
||||
panic!("not implemented");
|
||||
}
|
||||
|
||||
fn out_rpt_size(&self) -> usize {
|
||||
panic!("not implemented");
|
||||
}
|
||||
|
||||
fn get_property(&self, prop_name: &str) -> io::Result<String> {
|
||||
panic!("not implemented")
|
||||
}
|
||||
|
||||
fn get_device_info(&self) -> U2FDeviceInfo {
|
||||
panic!("not implemented")
|
||||
}
|
||||
|
||||
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
|
||||
panic!("not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,52 +69,14 @@ impl HIDDevice for Device {
|
|||
unimplemented!();
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn set_cid(&mut self, cid: [u8; 4]) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn in_rpt_size(&self) -> usize {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn out_rpt_size(&self) -> usize {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn get_property(&self, prop_name: &str) -> io::Result<String> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn get_device_info(&self) -> U2FDeviceInfo {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
@ -104,12 +96,6 @@ impl FidoDevice for Device {
|
|||
fn get_shared_secret(&self) -> Option<&SharedSecret> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -3,13 +3,11 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use super::winapi::DeviceCapabilities;
|
||||
use crate::consts::{
|
||||
Capability, CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE,
|
||||
};
|
||||
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::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::U2FDeviceInfo;
|
||||
use crate::transport::{FidoDevice, HIDError, SharedSecret};
|
||||
use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::{self, Read, Write};
|
||||
|
@ -23,7 +21,6 @@ pub struct Device {
|
|||
dev_info: Option<U2FDeviceInfo>,
|
||||
secret: Option<SharedSecret>,
|
||||
authenticator_info: Option<AuthenticatorInfo>,
|
||||
protocol: FidoProtocol,
|
||||
}
|
||||
|
||||
impl PartialEq for Device {
|
||||
|
@ -62,37 +59,7 @@ impl Write for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = String;
|
||||
type Id = String;
|
||||
|
||||
fn new(path: String) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("Opening device {:?}", path);
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|e| (HIDError::IO(Some(path.clone().into()), e), path.clone()))?;
|
||||
let mut res = Self {
|
||||
path,
|
||||
file,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
protocol: FidoProtocol::CTAP2,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path))
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
impl U2FDevice for Device {
|
||||
fn get_cid(&self) -> &[u8; 4] {
|
||||
&self.cid
|
||||
}
|
||||
|
@ -124,15 +91,31 @@ impl HIDDevice for Device {
|
|||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {
|
||||
fn pre_init(&mut self) -> Result<(), HIDError> {
|
||||
HIDDevice::pre_init(self)
|
||||
}
|
||||
impl HIDDevice for Device {
|
||||
type BuildParameters = String;
|
||||
type Id = String;
|
||||
|
||||
fn should_try_ctap2(&self) -> bool {
|
||||
HIDDevice::get_device_info(self)
|
||||
.cap_flags
|
||||
.contains(Capability::CBOR)
|
||||
fn new(path: String) -> Result<Self, (HIDError, Self::Id)> {
|
||||
debug!("Opening device {:?}", path);
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|e| (HIDError::IO(Some(path.clone().into()), e), path.clone()))?;
|
||||
let mut res = Self {
|
||||
path,
|
||||
file,
|
||||
cid: CID_BROADCAST,
|
||||
dev_info: None,
|
||||
secret: None,
|
||||
authenticator_info: None,
|
||||
};
|
||||
if res.is_u2f() {
|
||||
info!("new device {:?}", res.path);
|
||||
Ok(res)
|
||||
} else {
|
||||
Err((HIDError::DeviceNotSupported, res.path))
|
||||
}
|
||||
}
|
||||
|
||||
fn initialized(&self) -> bool {
|
||||
|
@ -140,6 +123,10 @@ impl FidoDevice for Device {
|
|||
self.cid != CID_BROADCAST
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn is_u2f(&mut self) -> bool {
|
||||
match DeviceCapabilities::new(self.file.as_raw_handle()) {
|
||||
Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE,
|
||||
|
@ -162,12 +149,6 @@ impl FidoDevice for Device {
|
|||
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
|
||||
self.authenticator_info = Some(authenticator_info);
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> FidoProtocol {
|
||||
self.protocol
|
||||
}
|
||||
|
||||
fn downgrade_to_ctap1(&mut self) {
|
||||
self.protocol = FidoProtocol::CTAP1;
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoDevice for Device {}
|
||||
|
|
|
@ -9,9 +9,9 @@ extern crate std;
|
|||
use rand::{thread_rng, RngCore};
|
||||
use std::ffi::CString;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::consts::*;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::u2ftypes::*;
|
||||
use crate::util::io_err;
|
||||
|
||||
|
@ -19,7 +19,10 @@ use crate::util::io_err;
|
|||
// Device Commands
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub fn u2f_init_device<T: HIDDevice>(dev: &mut T) -> bool {
|
||||
pub fn u2f_init_device<T>(dev: &mut T) -> bool
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
let mut nonce = [0u8; 8];
|
||||
thread_rng().fill_bytes(&mut nonce);
|
||||
|
||||
|
@ -27,11 +30,10 @@ pub fn u2f_init_device<T: HIDDevice>(dev: &mut T) -> bool {
|
|||
init_device(dev, &nonce).is_ok() && is_v2_device(dev).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn u2f_register<T: HIDDevice>(
|
||||
dev: &mut T,
|
||||
challenge: &[u8],
|
||||
application: &[u8],
|
||||
) -> io::Result<Vec<u8>> {
|
||||
pub fn u2f_register<T>(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result<Vec<u8>>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
|
@ -48,12 +50,15 @@ pub fn u2f_register<T: HIDDevice>(
|
|||
status_word_to_result(status, resp)
|
||||
}
|
||||
|
||||
pub fn u2f_sign<T: HIDDevice>(
|
||||
pub fn u2f_sign<T>(
|
||||
dev: &mut T,
|
||||
challenge: &[u8],
|
||||
application: &[u8],
|
||||
key_handle: &[u8],
|
||||
) -> io::Result<Vec<u8>> {
|
||||
) -> io::Result<Vec<u8>>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
|
@ -79,12 +84,15 @@ pub fn u2f_sign<T: HIDDevice>(
|
|||
status_word_to_result(status, resp)
|
||||
}
|
||||
|
||||
pub fn u2f_is_keyhandle_valid<T: HIDDevice>(
|
||||
pub fn u2f_is_keyhandle_valid<T>(
|
||||
dev: &mut T,
|
||||
challenge: &[u8],
|
||||
application: &[u8],
|
||||
key_handle: &[u8],
|
||||
) -> io::Result<bool> {
|
||||
) -> io::Result<bool>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
|
@ -114,7 +122,10 @@ pub fn u2f_is_keyhandle_valid<T: HIDDevice>(
|
|||
// Internal Device Commands
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn init_device<T: HIDDevice>(dev: &mut T, nonce: &[u8]) -> io::Result<()> {
|
||||
fn init_device<T>(dev: &mut T, nonce: &[u8]) -> io::Result<()>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
|
||||
// Send Init to broadcast address to create a new channel
|
||||
let raw = sendrecv(dev, HIDCmd::Init, nonce)?;
|
||||
|
@ -142,7 +153,10 @@ fn init_device<T: HIDDevice>(dev: &mut T, nonce: &[u8]) -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn is_v2_device<T: HIDDevice>(dev: &mut T) -> io::Result<bool> {
|
||||
fn is_v2_device<T>(dev: &mut T) -> io::Result<bool>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
let (data, status) = send_ctap1(dev, U2F_VERSION, 0x00, &[])?;
|
||||
let actual = CString::new(data)?;
|
||||
let expected = CString::new("U2F_V2")?;
|
||||
|
@ -169,7 +183,10 @@ fn status_word_to_result<T>(status: [u8; 2], val: T) -> io::Result<T> {
|
|||
// Device Communication Functions
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub fn sendrecv<T: HIDDevice>(dev: &mut T, cmd: HIDCmd, send: &[u8]) -> io::Result<Vec<u8>> {
|
||||
pub fn sendrecv<T>(dev: &mut T, cmd: HIDCmd, send: &[u8]) -> io::Result<Vec<u8>>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
// Send initialization packet.
|
||||
let mut count = U2FHIDInit::write(dev, cmd.into(), send)?;
|
||||
|
||||
|
@ -195,12 +212,10 @@ pub fn sendrecv<T: HIDDevice>(dev: &mut T, cmd: HIDCmd, send: &[u8]) -> io::Resu
|
|||
Ok(data)
|
||||
}
|
||||
|
||||
fn send_ctap1<T: HIDDevice>(
|
||||
dev: &mut T,
|
||||
cmd: u8,
|
||||
p1: u8,
|
||||
send: &[u8],
|
||||
) -> io::Result<(Vec<u8>, [u8; 2])> {
|
||||
fn send_ctap1<T>(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])>
|
||||
where
|
||||
T: U2FDevice + Read + Write,
|
||||
{
|
||||
let apdu = CTAP1RequestAPDU::serialize(cmd, p1, send)?;
|
||||
let mut data = sendrecv(dev, HIDCmd::Msg, &apdu)?;
|
||||
|
||||
|
@ -219,7 +234,7 @@ fn send_ctap1<T: HIDDevice>(
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::{init_device, is_v2_device, send_ctap1, sendrecv};
|
||||
use super::{init_device, is_v2_device, send_ctap1, sendrecv, U2FDevice};
|
||||
use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR};
|
||||
use crate::transport::device_selector::Device;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
|
@ -243,11 +258,10 @@ pub(crate) mod tests {
|
|||
|
||||
// init_resp packet
|
||||
let mut msg = CID_BROADCAST.to_vec();
|
||||
msg.extend(vec![HIDCmd::Init.into(), 0x00, 0x12]); // cmd + bcnt
|
||||
msg.extend(vec![HIDCmd::Init.into(), 0x00, 0x11]); // cmd + bcnt
|
||||
msg.extend_from_slice(&nonce);
|
||||
msg.extend_from_slice(&cid); // new channel id
|
||||
msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags
|
||||
msg.extend(vec![0xff]); // grease for future extension
|
||||
device.add_read(&msg, 0);
|
||||
|
||||
init_device(&mut device, &nonce).unwrap();
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::consts::*;
|
||||
use crate::transport::hid::HIDDevice;
|
||||
use crate::util::io_err;
|
||||
use serde::Serialize;
|
||||
use std::{cmp, fmt, io, str};
|
||||
|
@ -19,6 +18,33 @@ pub fn trace_hex(data: &[u8]) {
|
|||
}
|
||||
}
|
||||
|
||||
// Trait for representing U2F HID Devices. Requires getters/setters for the
|
||||
// channel ID, created during device initialization.
|
||||
pub trait U2FDevice {
|
||||
fn get_cid(&self) -> &[u8; 4];
|
||||
fn set_cid(&mut self, cid: [u8; 4]);
|
||||
|
||||
fn in_rpt_size(&self) -> usize;
|
||||
fn in_init_data_size(&self) -> usize {
|
||||
self.in_rpt_size() - INIT_HEADER_SIZE
|
||||
}
|
||||
fn in_cont_data_size(&self) -> usize {
|
||||
self.in_rpt_size() - CONT_HEADER_SIZE
|
||||
}
|
||||
|
||||
fn out_rpt_size(&self) -> usize;
|
||||
fn out_init_data_size(&self) -> usize {
|
||||
self.out_rpt_size() - INIT_HEADER_SIZE
|
||||
}
|
||||
fn out_cont_data_size(&self) -> usize {
|
||||
self.out_rpt_size() - CONT_HEADER_SIZE
|
||||
}
|
||||
|
||||
fn get_property(&self, prop_name: &str) -> io::Result<String>;
|
||||
fn get_device_info(&self) -> U2FDeviceInfo;
|
||||
fn set_device_info(&mut self, dev_info: U2FDeviceInfo);
|
||||
}
|
||||
|
||||
// Init structure for U2F Communications. Tells the receiver what channel
|
||||
// communication is happening on, what command is running, and how much data to
|
||||
// expect to receive over all.
|
||||
|
@ -28,7 +54,10 @@ pub fn trace_hex(data: &[u8]) {
|
|||
pub struct U2FHIDInit {}
|
||||
|
||||
impl U2FHIDInit {
|
||||
pub fn read<T: HIDDevice>(dev: &mut T) -> io::Result<(HIDCmd, Vec<u8>)> {
|
||||
pub fn read<T>(dev: &mut T) -> io::Result<(HIDCmd, Vec<u8>)>
|
||||
where
|
||||
T: U2FDevice + io::Read,
|
||||
{
|
||||
let mut frame = vec![0u8; dev.in_rpt_size()];
|
||||
let mut count = dev.read(&mut frame)?;
|
||||
|
||||
|
@ -45,17 +74,16 @@ impl U2FHIDInit {
|
|||
let cap = (frame[5] as usize) << 8 | (frame[6] as usize);
|
||||
let mut data = Vec::with_capacity(cap);
|
||||
|
||||
let len = if dev.in_rpt_size() >= INIT_HEADER_SIZE {
|
||||
cmp::min(cap, dev.in_rpt_size() - INIT_HEADER_SIZE)
|
||||
} else {
|
||||
cap
|
||||
};
|
||||
let len = cmp::min(cap, dev.in_init_data_size());
|
||||
data.extend_from_slice(&frame[7..7 + len]);
|
||||
|
||||
Ok((cmd, data))
|
||||
}
|
||||
|
||||
pub fn write<T: HIDDevice>(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result<usize> {
|
||||
pub fn write<T>(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result<usize>
|
||||
where
|
||||
T: U2FDevice + io::Write,
|
||||
{
|
||||
if data.len() > 0xffff {
|
||||
return Err(io_err("payload length > 2^16"));
|
||||
}
|
||||
|
@ -66,11 +94,7 @@ impl U2FHIDInit {
|
|||
frame[6] = (data.len() >> 8) as u8;
|
||||
frame[7] = data.len() as u8;
|
||||
|
||||
let count = if dev.out_rpt_size() >= INIT_HEADER_SIZE {
|
||||
cmp::min(data.len(), dev.out_rpt_size() - INIT_HEADER_SIZE)
|
||||
} else {
|
||||
data.len()
|
||||
};
|
||||
let count = cmp::min(data.len(), dev.out_init_data_size());
|
||||
frame[8..8 + count].copy_from_slice(&data[..count]);
|
||||
trace_hex(&frame);
|
||||
|
||||
|
@ -92,7 +116,10 @@ impl U2FHIDInit {
|
|||
pub struct U2FHIDCont {}
|
||||
|
||||
impl U2FHIDCont {
|
||||
pub fn read<T: HIDDevice>(dev: &mut T, seq: u8, max: usize) -> io::Result<Vec<u8>> {
|
||||
pub fn read<T>(dev: &mut T, seq: u8, max: usize) -> io::Result<Vec<u8>>
|
||||
where
|
||||
T: U2FDevice + io::Read,
|
||||
{
|
||||
let mut frame = vec![0u8; dev.in_rpt_size()];
|
||||
let mut count = dev.read(&mut frame)?;
|
||||
|
||||
|
@ -108,24 +135,19 @@ impl U2FHIDCont {
|
|||
return Err(io_err("invalid sequence number"));
|
||||
}
|
||||
|
||||
let max = if dev.in_rpt_size() >= CONT_HEADER_SIZE {
|
||||
cmp::min(max, dev.in_rpt_size() - CONT_HEADER_SIZE)
|
||||
} else {
|
||||
max
|
||||
};
|
||||
let max = cmp::min(max, dev.in_cont_data_size());
|
||||
Ok(frame[5..5 + max].to_vec())
|
||||
}
|
||||
|
||||
pub fn write<T: HIDDevice>(dev: &mut T, seq: u8, data: &[u8]) -> io::Result<usize> {
|
||||
pub fn write<T>(dev: &mut T, seq: u8, data: &[u8]) -> io::Result<usize>
|
||||
where
|
||||
T: U2FDevice + io::Write,
|
||||
{
|
||||
let mut frame = vec![0u8; dev.out_rpt_size() + 1];
|
||||
frame[1..5].copy_from_slice(dev.get_cid());
|
||||
frame[5] = seq;
|
||||
|
||||
let count = if dev.out_rpt_size() >= CONT_HEADER_SIZE {
|
||||
cmp::min(data.len(), dev.out_rpt_size() - CONT_HEADER_SIZE)
|
||||
} else {
|
||||
data.len()
|
||||
};
|
||||
let count = cmp::min(data.len(), dev.out_cont_data_size());
|
||||
frame[6..6 + count].copy_from_slice(&data[..count]);
|
||||
trace_hex(&frame);
|
||||
|
||||
|
@ -156,7 +178,7 @@ impl U2FHIDInitResp {
|
|||
pub fn read(data: &[u8], nonce: &[u8]) -> io::Result<U2FHIDInitResp> {
|
||||
assert_eq!(nonce.len(), INIT_NONCE_SIZE);
|
||||
|
||||
if data.len() < INIT_NONCE_SIZE + 9 {
|
||||
if data.len() != INIT_NONCE_SIZE + 9 {
|
||||
return Err(io_err("invalid init response"));
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"11d614796edb6aaebd24bcc8ab5b832f0c5bb441a620d1e51bba72d9bc657a4d","README.md":"a76b467337dd5c5ac8f0e315b1e1f584f2d8c3b6fe57aba16306a3faf9f8fde7","bindings/bindings.toml":"f27f5370d0eaeba6eec54e0183955b16dc00904ba2db0d4ca646209d26b28eeb","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nspr_types.h":"6e1ca8e760c913e08507dcb8645748661c81b649ac148ee046d2e4e95f39cede","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_prelude.h":"3952a566aaa497b4c3094bc6c338bba987134a2d5f336c409d1bd6d31e14a8f8","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"3d8a2d22ca23653d6421a7bb2752c6c532e4cf823b2b3a6622730fd46586665d","src/err.rs":"6c7e785f9c52e7606bc4de3fa4e6805b73cf828430850fd6c4e87bff20f4530e","src/exp.rs":"01e999dc4be93a12e5890b2d8c4ce62d3f5afecf9f8373f150353633731445f9","src/lib.rs":"7b50ec7ddb61cdef461449e4380402569e4ffde7940341dbafc6b60922482132","src/p11.rs":"f79fbc3b639fe6ae7a2c68e2f89de0be9c40b44adcdaba1631fd980c24de77e6","src/prio.rs":"bdfef0e3898876ee223218aedc0b2d2f43575e4302ffdd0fa5a719d4f6e468e0","src/prtypes.rs":"270effec36a2d6836329e672e8043bf277f48fe136c8844f5eb503c0e32511f9","src/ssl.rs":"a36251e63484e382ff70d8c1008ec456746c7826e4c628fe42818880ecf1596b","src/time.rs":"1fce901a535d67baaef59c42f39c5d8eb5a4ac6cbb69026fbc7088dd822fa404","src/util.rs":"8e424724009256a19ef3f89551d0853f6303013b7129b861fb9e45d7648883ed"},"package":"4c17aec6d4e1822c023689899f09311592a36cbf6de8f85dfaf5f01976790d8d"}
|
||||
{"files":{"Cargo.toml":"484e9876017a5f297ed6921c5336e65235b1e4783753f4f3f31e0256e86ef266","README.md":"a76b467337dd5c5ac8f0e315b1e1f584f2d8c3b6fe57aba16306a3faf9f8fde7","bindings/bindings.toml":"4fbb1e31f56f068e040842408e0e9a5f0f1f35651298afa86836765049a2928b","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nspr_types.h":"6e1ca8e760c913e08507dcb8645748661c81b649ac148ee046d2e4e95f39cede","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_prelude.h":"3952a566aaa497b4c3094bc6c338bba987134a2d5f336c409d1bd6d31e14a8f8","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"3d8a2d22ca23653d6421a7bb2752c6c532e4cf823b2b3a6622730fd46586665d","src/err.rs":"6c7e785f9c52e7606bc4de3fa4e6805b73cf828430850fd6c4e87bff20f4530e","src/exp.rs":"01e999dc4be93a12e5890b2d8c4ce62d3f5afecf9f8373f150353633731445f9","src/lib.rs":"7b50ec7ddb61cdef461449e4380402569e4ffde7940341dbafc6b60922482132","src/p11.rs":"f79fbc3b639fe6ae7a2c68e2f89de0be9c40b44adcdaba1631fd980c24de77e6","src/prio.rs":"bdfef0e3898876ee223218aedc0b2d2f43575e4302ffdd0fa5a719d4f6e468e0","src/prtypes.rs":"270effec36a2d6836329e672e8043bf277f48fe136c8844f5eb503c0e32511f9","src/ssl.rs":"a36251e63484e382ff70d8c1008ec456746c7826e4c628fe42818880ecf1596b","src/time.rs":"1fce901a535d67baaef59c42f39c5d8eb5a4ac6cbb69026fbc7088dd822fa404","src/util.rs":"f3c163ff609b4211e1ebab08604f770e4bcf11a6946c9d0ecb4165594a4bb886"},"package":"1a689b62ba71fda80458a77b6ace9371d6e6a5473300901383ebd101659b3352"}
|
|
@ -13,7 +13,7 @@
|
|||
edition = "2018"
|
||||
rust-version = "1.57.0"
|
||||
name = "nss-gk-api"
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
authors = [
|
||||
"Martin Thomson <mt@lowentropy.net>",
|
||||
"Andy Leiserson <aleiserson@mozilla.com>",
|
||||
|
|
|
@ -165,7 +165,6 @@ functions = [
|
|||
"PK11_DigestOp",
|
||||
"PK11_DigestFinal",
|
||||
"PK11_Encrypt",
|
||||
"PK11_ExportDERPrivateKeyInfo",
|
||||
"PK11_ExtractKeyValue",
|
||||
"PK11_FindCertFromNickname",
|
||||
"PK11_FindKeyByAnyCert",
|
||||
|
@ -185,9 +184,6 @@ functions = [
|
|||
"PK11_PubDeriveWithKDF",
|
||||
"PK11_ReadRawAttribute",
|
||||
"PK11_ReferenceSymKey",
|
||||
"PK11_SignWithMechanism",
|
||||
"PK11_VerifyWithMechanism",
|
||||
"PK11_WrapPrivKey",
|
||||
"SECKEY_CopyPrivateKey",
|
||||
"SECKEY_CopyPublicKey",
|
||||
"SECKEY_DecodeDERSubjectPublicKeyInfo",
|
||||
|
@ -216,7 +212,6 @@ opaque = [
|
|||
]
|
||||
variables = [
|
||||
"AES_BLOCK_SIZE",
|
||||
"PK11_ATTR_EXTRACTABLE",
|
||||
"PK11_ATTR_INSENSITIVE",
|
||||
"PK11_ATTR_PRIVATE",
|
||||
"PK11_ATTR_PUBLIC",
|
||||
|
|
|
@ -98,26 +98,6 @@ impl SECItem {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe fn destroy_secitem(item: *mut SECItem) {
|
||||
SECITEM_FreeItem(item, PRBool::from(true));
|
||||
}
|
||||
scoped_ptr!(ScopedSECItem, SECItem, destroy_secitem);
|
||||
|
||||
impl ScopedSECItem {
|
||||
/// This dereferences the pointer held by the item and makes a copy of the
|
||||
/// content that is referenced there.
|
||||
///
|
||||
/// # Safety
|
||||
/// This dereferences two pointers. It doesn't get much less safe.
|
||||
pub unsafe fn into_vec(self) -> Vec<u8> {
|
||||
let b = self.ptr.as_ref().unwrap();
|
||||
// Sanity check the type, as some types don't count bytes in `Item::len`.
|
||||
assert_eq!(b.type_, SECItemType::siBuffer);
|
||||
let slc = std::slice::from_raw_parts(b.data, usize::try_from(b.len).unwrap());
|
||||
Vec::from(slc)
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned SECItem.
|
||||
///
|
||||
/// The SECItem structure is allocated by Rust. The buffer referenced by the
|
||||
|
|
Загрузка…
Ссылка в новой задаче