added feature assert_devices

This commit is contained in:
shimun 2020-03-31 19:06:53 +02:00
parent d4c9dd913f
commit 501b28e0d9
Signed by: shimun
GPG Key ID: E81D8382DC2F971B
5 changed files with 94 additions and 43 deletions

View File

@ -21,6 +21,10 @@ untrusted = "0.6"
rust-crypto = "0.2" rust-crypto = "0.2"
csv-core = "0.1.6" csv-core = "0.1.6"
derive_builder = "0.9.0" derive_builder = "0.9.0"
crossbeam = { version = "0.7.3", optional = true }
[dev-dependencies] [dev-dependencies]
crossbeam = "0.7.3" crossbeam = "0.7.3"
hex = "0.4.0" hex = "0.4.0"
[features]
assert_devices = ["crossbeam"]

View File

@ -1,8 +1,12 @@
extern crate ctap_hmac as ctap; extern crate ctap_hmac as ctap;
use crossbeam::thread;
use crypto::digest::Digest; use crypto::digest::Digest;
use crypto::sha2::Sha256; use crypto::sha2::Sha256;
use ctap::{FidoCredential, FidoCredentialRequestBuilder, FidoAssertionRequestBuilder, AuthenticatorOptions, FidoDevice, FidoError, FidoResult}; use ctap::{
FidoAssertionRequestBuilder, FidoCredential, FidoCredentialRequestBuilder, FidoDevice,
FidoError, FidoResult,
};
use failure::_core::time::Duration; use failure::_core::time::Duration;
use hex; use hex;
use std::env::args; use std::env::args;
@ -11,42 +15,41 @@ use std::io::stdin;
use std::io::stdout; use std::io::stdout;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use std::sync::Mutex; use std::sync::Mutex;
use crossbeam::thread;
const RP_ID: &str = "ctap_demo"; const RP_ID: &str = "ctap_demo";
fn run() -> ctap::FidoResult<()> { fn run() -> ctap::FidoResult<()> {
let mut credentials = args().skip(1).map(|id| FidoCredential { let mut credentials = args()
id: hex::decode(&id).expect("Invalid credential"), .skip(1)
public_key: None, .map(|id| FidoCredential {
}).collect::<Vec<_>>(); id: hex::decode(&id).expect("Invalid credential"),
public_key: None,
})
.collect::<Vec<_>>();
if credentials.len() == 0 { if credentials.len() == 0 {
credentials = ctap::get_devices()?.map(|h| FidoDevice::new(&h).and_then(|mut dev| FidoCredentialRequestBuilder::default() credentials = ctap::get_devices()?
.rp_id(RP_ID).build().unwrap().make_credential(&mut dev))).collect::<FidoResult<Vec<FidoCredential>>>()?; .map(|h| {
}
let credentials = credentials.iter().collect::<Vec<_>>();
let (s, r) = channel();
thread::scope(|scope| {
let handles = ctap::get_devices()?.map(|h| {
let req = FidoAssertionRequestBuilder::default().rp_id(RP_ID).credentials(&credentials[..]).build().unwrap();
let s = s.clone();
scope.spawn(move |_| {
FidoDevice::new(&h).and_then(|mut dev| { FidoDevice::new(&h).and_then(|mut dev| {
req.get_assertion(&mut dev).map(|res| { FidoCredentialRequestBuilder::default()
s.send(res.clone()); .rp_id(RP_ID)
res .build()
}) .unwrap()
.make_credential(&mut dev)
}) })
}) })
}).collect::<Vec<_>>(); .collect::<FidoResult<Vec<FidoCredential>>>()?;
for h in handles {
h.join();
}
Ok::<(), FidoError>(())
}).unwrap();
for res in r.iter().take(credentials.len()) {
dbg!(res);
} }
let credentials = credentials.iter().collect::<Vec<_>>();
let req = FidoAssertionRequestBuilder::default()
.rp_id(RP_ID)
.credentials(&credentials[..])
.build()
.unwrap();
let mut devices = ctap::get_devices()?
.map(|handle| FidoDevice::new(&handle))
.collect::<FidoResult<Vec<_>>>()?;
let (cred, _) = ctap::get_assertion_devices(&req, devices.iter_mut())?;
println!("Success, got assertion for: {}", hex::encode(&cred.id));
Ok(()) Ok(())
} }

View File

@ -45,9 +45,9 @@ pub enum FidoErrorKind {
EncryptPin, EncryptPin,
#[fail(display = "Failed to decrypt PIN.")] #[fail(display = "Failed to decrypt PIN.")]
DecryptPin, DecryptPin,
#[fail(display = "Supplied key has incorrect type.")]
VerifySignature,
#[fail(display = "Failed to verify response signature.")] #[fail(display = "Failed to verify response signature.")]
VerifySignature,
#[fail(display = "Supplied key has incorrect type.")]
KeyType, KeyType,
#[fail(display = "Device returned error: {}", _0)] #[fail(display = "Device returned error: {}", _0)]
CborError(CborErrorCode), CborError(CborErrorCode),

View File

@ -61,6 +61,7 @@ pub mod extensions;
mod hid_common; mod hid_common;
mod hid_linux; mod hid_linux;
mod packet; mod packet;
mod util;
use std::cmp; use std::cmp;
use std::fs; use std::fs;
@ -68,11 +69,11 @@ use std::io::{Cursor, Write};
use std::u16; use std::u16;
use std::u8; use std::u8;
pub use self::cbor::AuthenticatorOptions; use self::cbor::{AuthenticatorOptions, PublicKeyCredentialDescriptor};
use self::cbor::PublicKeyCredentialDescriptor;
pub use self::error::*; pub use self::error::*;
use self::hid_linux as hid; use self::hid_linux as hid;
use self::packet::CtapCommand; use self::packet::CtapCommand;
pub use self::util::*;
use crate::cbor::{AuthenticatorData, GetAssertionRequest}; use crate::cbor::{AuthenticatorData, GetAssertionRequest};
use failure::{Fail, ResultExt}; use failure::{Fail, ResultExt};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@ -97,7 +98,6 @@ pub struct FidoCredential {
/// The public key provided by the authenticator, in uncompressed form. /// The public key provided by the authenticator, in uncompressed form.
pub public_key: Option<Vec<u8>>, pub public_key: Option<Vec<u8>>,
} }
/// An opened FIDO authenticator. /// An opened FIDO authenticator.
pub struct FidoDevice { pub struct FidoDevice {
device: fs::File, device: fs::File,
@ -517,23 +517,23 @@ impl FidoDevice {
credential credential
.and_then(|cred| { .and_then(|cred| {
cred.public_key if cred
.public_key
.as_ref() .as_ref()
.map(|public_key| { .map(|public_key| {
Some(crypto::verify_signature( crypto::verify_signature(
&public_key, &public_key,
&assertion.client_data_hash, &assertion.client_data_hash,
&response.auth_data_bytes, &response.auth_data_bytes,
&response.signature, &response.signature,
)) )
.unwrap_or(true)
}) })
.iter() .unwrap_or(true)
.filter_map(|valid| match valid { {
true => Some(cred), Some(cred)
false => None, } else {
}) None
.next() }
}) })
.ok_or(FidoError::from(FidoErrorKind::VerifySignature)) .ok_or(FidoError::from(FidoErrorKind::VerifySignature))
.map(|cred| (cred, response.auth_data)) .map(|cred| (cred, response.auth_data))

44
src/util.rs Normal file
View File

@ -0,0 +1,44 @@
use crate::cbor::AuthenticatorData;
use crate::{FidoAssertionRequest, FidoCredential, FidoDevice, FidoErrorKind, FidoResult};
use std::sync::mpsc::channel;
#[cfg(feature = "assert_devices")]
use crossbeam::thread;
/// Will send the `assertion` to all supplied `devices` and return either the first successful assertion or the last error
#[cfg(feature = "assert_devices")]
pub fn get_assertion_devices<'a>(
assertion: &'a FidoAssertionRequest,
devices: impl Iterator<Item = &'a mut FidoDevice>,
) -> FidoResult<(&'a FidoCredential, AuthenticatorData)> {
thread::scope(
|scope| -> FidoResult<(&'a FidoCredential, AuthenticatorData)> {
let (tx, rx) = channel();
let handles = devices
.map(|device| {
let cancel = device.cancel_handle()?;
let tx = tx.clone();
let thread_handle =
scope.spawn(move |_| tx.send(device.get_assertion(assertion)));
Ok((cancel, thread_handle))
})
.collect::<FidoResult<Vec<_>>>()?;
let mut err = None;
for res in rx.iter().take(handles.len()) {
match res {
Ok(_) => {
for (mut cancel, join) in handles {
// Canceling out of courtesy don't care if it fails
let _ = cancel.cancel();
let _ = join.join();
}
return res;
}
e => err = Some(e),
}
}
err.unwrap_or(Err(FidoErrorKind::DeviceUnsupported.into()))
},
)
.unwrap()
}