added feature assert_devices
This commit is contained in:
parent
d4c9dd913f
commit
501b28e0d9
@ -21,6 +21,10 @@ untrusted = "0.6"
|
||||
rust-crypto = "0.2"
|
||||
csv-core = "0.1.6"
|
||||
derive_builder = "0.9.0"
|
||||
crossbeam = { version = "0.7.3", optional = true }
|
||||
[dev-dependencies]
|
||||
crossbeam = "0.7.3"
|
||||
hex = "0.4.0"
|
||||
|
||||
[features]
|
||||
assert_devices = ["crossbeam"]
|
||||
|
@ -1,8 +1,12 @@
|
||||
extern crate ctap_hmac as ctap;
|
||||
|
||||
use crossbeam::thread;
|
||||
use crypto::digest::Digest;
|
||||
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 hex;
|
||||
use std::env::args;
|
||||
@ -11,42 +15,41 @@ use std::io::stdin;
|
||||
use std::io::stdout;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::Mutex;
|
||||
use crossbeam::thread;
|
||||
|
||||
const RP_ID: &str = "ctap_demo";
|
||||
|
||||
fn run() -> ctap::FidoResult<()> {
|
||||
let mut credentials = args().skip(1).map(|id| FidoCredential {
|
||||
let mut credentials = args()
|
||||
.skip(1)
|
||||
.map(|id| FidoCredential {
|
||||
id: hex::decode(&id).expect("Invalid credential"),
|
||||
public_key: None,
|
||||
}).collect::<Vec<_>>();
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if credentials.len() == 0 {
|
||||
credentials = ctap::get_devices()?.map(|h| FidoDevice::new(&h).and_then(|mut dev| FidoCredentialRequestBuilder::default()
|
||||
.rp_id(RP_ID).build().unwrap().make_credential(&mut dev))).collect::<FidoResult<Vec<FidoCredential>>>()?;
|
||||
credentials = ctap::get_devices()?
|
||||
.map(|h| {
|
||||
FidoDevice::new(&h).and_then(|mut dev| {
|
||||
FidoCredentialRequestBuilder::default()
|
||||
.rp_id(RP_ID)
|
||||
.build()
|
||||
.unwrap()
|
||||
.make_credential(&mut dev)
|
||||
})
|
||||
})
|
||||
.collect::<FidoResult<Vec<FidoCredential>>>()?;
|
||||
}
|
||||
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| {
|
||||
req.get_assertion(&mut dev).map(|res| {
|
||||
s.send(res.clone());
|
||||
res
|
||||
})
|
||||
})
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
for h in handles {
|
||||
h.join();
|
||||
}
|
||||
Ok::<(), FidoError>(())
|
||||
}).unwrap();
|
||||
for res in r.iter().take(credentials.len()) {
|
||||
dbg!(res);
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
|
@ -45,9 +45,9 @@ pub enum FidoErrorKind {
|
||||
EncryptPin,
|
||||
#[fail(display = "Failed to decrypt PIN.")]
|
||||
DecryptPin,
|
||||
#[fail(display = "Supplied key has incorrect type.")]
|
||||
VerifySignature,
|
||||
#[fail(display = "Failed to verify response signature.")]
|
||||
VerifySignature,
|
||||
#[fail(display = "Supplied key has incorrect type.")]
|
||||
KeyType,
|
||||
#[fail(display = "Device returned error: {}", _0)]
|
||||
CborError(CborErrorCode),
|
||||
|
26
src/lib.rs
26
src/lib.rs
@ -61,6 +61,7 @@ pub mod extensions;
|
||||
mod hid_common;
|
||||
mod hid_linux;
|
||||
mod packet;
|
||||
mod util;
|
||||
|
||||
use std::cmp;
|
||||
use std::fs;
|
||||
@ -68,11 +69,11 @@ use std::io::{Cursor, Write};
|
||||
use std::u16;
|
||||
use std::u8;
|
||||
|
||||
pub use self::cbor::AuthenticatorOptions;
|
||||
use self::cbor::PublicKeyCredentialDescriptor;
|
||||
use self::cbor::{AuthenticatorOptions, PublicKeyCredentialDescriptor};
|
||||
pub use self::error::*;
|
||||
use self::hid_linux as hid;
|
||||
use self::packet::CtapCommand;
|
||||
pub use self::util::*;
|
||||
use crate::cbor::{AuthenticatorData, GetAssertionRequest};
|
||||
use failure::{Fail, ResultExt};
|
||||
use num_traits::FromPrimitive;
|
||||
@ -97,7 +98,6 @@ pub struct FidoCredential {
|
||||
/// The public key provided by the authenticator, in uncompressed form.
|
||||
pub public_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// An opened FIDO authenticator.
|
||||
pub struct FidoDevice {
|
||||
device: fs::File,
|
||||
@ -517,23 +517,23 @@ impl FidoDevice {
|
||||
|
||||
credential
|
||||
.and_then(|cred| {
|
||||
cred.public_key
|
||||
if cred
|
||||
.public_key
|
||||
.as_ref()
|
||||
.map(|public_key| {
|
||||
Some(crypto::verify_signature(
|
||||
crypto::verify_signature(
|
||||
&public_key,
|
||||
&assertion.client_data_hash,
|
||||
&response.auth_data_bytes,
|
||||
&response.signature,
|
||||
))
|
||||
)
|
||||
})
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.iter()
|
||||
.filter_map(|valid| match valid {
|
||||
true => Some(cred),
|
||||
false => None,
|
||||
})
|
||||
.next()
|
||||
{
|
||||
Some(cred)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or(FidoError::from(FidoErrorKind::VerifySignature))
|
||||
.map(|cred| (cred, response.auth_data))
|
||||
|
44
src/util.rs
Normal file
44
src/util.rs
Normal 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()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user