Backed out changeset e637dc2466d4 (bug 1837473) for breaking CTAP2 PIN entry. a=backout

This commit is contained in:
Butkovits Atila 2023-06-17 02:35:10 +03:00
Родитель f36cc743ce
Коммит bf80c8e256
56 изменённых файлов: 1964 добавлений и 2541 удалений

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

@ -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, &notification_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"

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

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

@ -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",

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

@ -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]

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

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

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

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

2
third_party/rust/nss-gk-api/Cargo.toml поставляемый
Просмотреть файл

@ -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",

20
third_party/rust/nss-gk-api/src/util.rs поставляемый
Просмотреть файл

@ -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