use crate::error::WebauthnCError;
use crate::U2FToken;
use crate::{U2FRegistrationData, U2FSignData};
use webauthn_rs::proto::AllowCredentials;
use std::convert::TryFrom;
use authenticator::{
authenticatorservice::AuthenticatorService, statecallback::StateCallback,
AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate,
};
use std::sync::mpsc::channel;
use std::thread;
pub struct U2FHid {}
fn asn1_seq_extractor(i: &[u8]) -> nom::IResult<&[u8], &[u8]> {
if i.len() < 2 {
return Err(nom::Err::Failure(nom::Context::Code(
i,
nom::ErrorKind::Custom(1),
)));
}
if i[0] != 0x30 {
return Err(nom::Err::Failure(nom::Context::Code(
i,
nom::ErrorKind::Custom(2),
)));
}
let length: usize = if i[1] & 0x40 == 0x40 {
return Err(nom::Err::Failure(nom::Context::Code(
i,
nom::ErrorKind::Custom(3),
)));
} else {
i[1] as usize
};
if i.len() < (2 + length) {
return Err(nom::Err::Failure(nom::Context::Code(
i,
nom::ErrorKind::Custom(4),
)));
}
let (cert, rem) = i.split_at(2 + length);
Ok((rem, cert))
}
named!( u2rd_parser<&[u8], U2FRegistrationData>,
preceded!(
verify!(take!(1), |val: &[u8]| val == &[0x05]),
do_parse!(
public_key_x: preceded!(
verify!(take!(1), |val: &[u8]| val == &[0x04]),
take!(32)
) >>
public_key_y: take!(32) >>
key_handle: length_data!(nom::be_u8) >>
att_cert: call!(asn1_seq_extractor) >>
signature: call!(nom::rest) >>
(U2FRegistrationData {
public_key_x: public_key_x.to_vec(),
public_key_y: public_key_y.to_vec(),
key_handle: key_handle.to_vec(),
att_cert: att_cert.to_vec(),
signature: signature.to_vec(),
})
)
)
);
impl TryFrom<&[u8]> for U2FRegistrationData {
type Error = WebauthnCError;
fn try_from(data: &[u8]) -> Result<U2FRegistrationData, WebauthnCError> {
u2rd_parser(data)
.map_err(|_| WebauthnCError::ParseNOMFailure)
.map(|(_, ad)| ad)
}
}
named!( u2sd_sign_data_parser<&[u8], (u8, u32, Vec<u8>)>,
do_parse!(
up: call!(nom::be_u8) >>
cnt: u32!(nom::Endianness::Big) >>
sig: call!(nom::rest) >>
(
(up, cnt, sig.to_vec())
)
)
);
impl U2FHid {
pub fn new() -> Self {
U2FHid {}
}
}
impl U2FToken for U2FHid {
fn perform_u2f_register(
&mut self,
app_bytes: Vec<u8>,
chal_bytes: Vec<u8>,
timeout_ms: u64,
platform_attached: bool,
resident_key: bool,
user_verification: bool,
) -> Result<U2FRegistrationData, WebauthnCError> {
if user_verification {
log::error!("User Verification not supported by attestation-rs");
return Err(WebauthnCError::NotSupported);
}
let mut manager = AuthenticatorService::new().map_err(|e| {
log::error!("Authentication Service -> {:?}", e);
WebauthnCError::PlatformAuthenticator
})?;
manager.add_u2f_usb_hid_platform_transports();
let mut flags = RegisterFlags::empty();
if platform_attached {
flags.insert(RegisterFlags::REQUIRE_PLATFORM_ATTACHMENT)
}
if resident_key {
flags.insert(RegisterFlags::REQUIRE_RESIDENT_KEY)
}
log::debug!("flags -> {:?}", flags);
let (status_tx, status_rx) = channel::<StatusUpdate>();
let (register_tx, register_rx) = channel();
thread::spawn(move || loop {
match status_rx.recv() {
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
log::info!("STATUS: device available: {}", dev_info)
}
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
log::error!("STATUS: device unavailable: {}", dev_info)
}
Ok(StatusUpdate::Success { dev_info }) => {
log::info!("STATUS: success using device: {}", dev_info);
}
Err(_RecvError) => {
log::debug!("STATUS: end");
return;
}
}
});
let callback = StateCallback::new(Box::new(move |rv| {
register_tx.send(rv).unwrap();
}));
manager.register(
flags,
timeout_ms,
chal_bytes,
app_bytes,
vec![],
status_tx.clone(),
callback,
);
let register_result = register_rx.recv().map_err(|e| {
log::error!("Registration Channel Error -> {:?}", e);
WebauthnCError::Internal
})?;
let (register_data, device_info) = register_result.map_err(|e| {
log::error!("Device Registration Error -> {:?}", e);
WebauthnCError::Internal
})?;
log::debug!("di -> {:?}", device_info);
let u2rd = U2FRegistrationData::try_from(register_data.as_slice()).map_err(|e| {
log::error!("U2F Registration Data Corrupt -> {:?}", e);
e
})?;
log::debug!("u2rd -> {:?}", u2rd);
Ok(u2rd)
}
fn perform_u2f_sign(
&mut self,
app_bytes: Vec<u8>,
chal_bytes: Vec<u8>,
timeout_ms: u64,
allowed_credentials: &[AllowCredentials],
user_verification: bool,
) -> Result<U2FSignData, WebauthnCError> {
if user_verification {
log::error!("User Verification not supported by attestation-rs");
return Err(WebauthnCError::NotSupported);
}
let allowed_credentials: Vec<KeyHandle> = allowed_credentials
.iter()
.map(|ac| {
KeyHandle {
credential: ac.id.0.clone(),
transports: AuthenticatorTransports::empty(),
}
})
.collect();
let mut manager = AuthenticatorService::new().map_err(|e| {
log::error!("Authentication Service -> {:?}", e);
WebauthnCError::PlatformAuthenticator
})?;
manager.add_u2f_usb_hid_platform_transports();
let flags = SignFlags::empty();
log::debug!("flags -> {:?}", flags);
let (status_tx, status_rx) = channel::<StatusUpdate>();
let (register_tx, register_rx) = channel();
thread::spawn(move || loop {
match status_rx.recv() {
Ok(StatusUpdate::DeviceAvailable { dev_info }) => {
log::info!("STATUS: device available: {}", dev_info)
}
Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
log::error!("STATUS: device unavailable: {}", dev_info)
}
Ok(StatusUpdate::Success { dev_info }) => {
log::info!("STATUS: success using device: {}", dev_info);
}
Err(RecvError) => {
log::debug!("STATUS: end");
return;
}
}
});
let callback = StateCallback::new(Box::new(move |rv| {
register_tx.send(rv).unwrap();
}));
manager.sign(
flags,
timeout_ms,
chal_bytes,
vec![app_bytes],
allowed_credentials,
status_tx.clone(),
callback,
);
let register_result = register_rx.recv().map_err(|e| {
log::error!("Registration Channel Error -> {:?}", e);
WebauthnCError::Internal
})?;
let (appid, key_handle, sign_data, device_info) = register_result.map_err(|e| {
log::error!("Device Registration Error -> {:?}", e);
WebauthnCError::Internal
})?;
log::debug!("di -> {:?}", device_info);
let (_, (user_present, counter, signature)) =
u2sd_sign_data_parser(sign_data.as_slice())
.map_err(|_| WebauthnCError::ParseNOMFailure)?;
Ok(U2FSignData {
appid,
key_handle,
counter,
signature,
user_present,
})
}
}