use std::fmt; use std::sync::mpsc::{channel, Sender}; use std::thread; use std::time::SystemTime; use authenticator::{RegisterResult, StatusUpdate}; use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use rand::Rng; use rust_util::XResult; use serde::{Deserialize, Serialize}; use crate::pkiutil::bytes_to_pem; use crate::util::base64_encode; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct U2FDeviceInfo { pub vendor_name: String, pub device_name: String, pub version_interface: u8, pub version_major: u8, pub version_minor: u8, pub version_build: u8, pub cap_flags: u8, } impl U2FDeviceInfo { fn from(register_result: &authenticator::RegisterResult) -> Self { let i = ®ister_result.1; Self { vendor_name: String::from_utf8_lossy(&i.vendor_name).to_string(), device_name: String::from_utf8_lossy(&i.device_name).to_string(), version_interface: i.version_interface, version_major: i.version_major, version_minor: i.version_minor, version_build: i.version_build, cap_flags: i.cap_flags, } } } impl fmt::Display for U2FDeviceInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Vendor: {}, Device: {}, Interface: {}, Firmware: v{}.{}.{}, Capabilities: {}", self.vendor_name, self.device_name, &self.version_interface, &self.version_major, &self.version_minor, &self.version_build, to_hex(&[self.cap_flags], ":"), ) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct U2fRegistrationData { pub app_id: String, pub device_info: U2FDeviceInfo, pub device_name: Option, pub client_data: String, pub registration_data: String, pub attestation_cert: Option>, pub attestation_cert_pem: Option, pub pub_key: Vec, pub key_handle: Vec, } impl U2fRegistrationData { pub fn from(app_id: &str, client_data: &str, register_result: &RegisterResult) -> XResult { let registration = opt_result!( u2f::register::parse_registration(app_id.to_string(), client_data.as_bytes().to_vec(), register_result.0.to_vec()), "Parse registration data failed: {}"); Ok(Self { app_id: app_id.to_string(), device_info: U2FDeviceInfo::from(register_result), device_name: registration.device_name, client_data: client_data.into(), registration_data: base64_encode(®ister_result.0), attestation_cert: registration.attestation_cert.clone(), attestation_cert_pem: registration.attestation_cert.map(|cert| { bytes_to_pem("CERTIFICATE", cert) }), pub_key: registration.pub_key, key_handle: registration.key_handle, }) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct U2fV2Challenge { challenge: String, version: String, #[serde(rename = "appId")] app_id: String, } impl U2fV2Challenge { pub fn new_challenge(challenge_hex: Option<&str>, app_id: &str, with_time_stamp_prefix: bool) -> XResult { Ok(match challenge_hex { None => U2fV2Challenge::new_random(app_id, with_time_stamp_prefix), Some(challenge_hex) => { let challenge_bytes = opt_result!(hex::decode(challenge_hex), "Decode challenge hex failed: {}"); let challenge = URL_SAFE_NO_PAD.encode(challenge_bytes); U2fV2Challenge::new(challenge, app_id) } }) } pub fn new_random(app_id: S, with_time_stamp_prefix: bool) -> Self where S: Into { let mut rng = rand::thread_rng(); let mut rand_bytes = [0_u8; 32]; for c in &mut rand_bytes { let b: u8 = rng.gen(); *c = b; } if with_time_stamp_prefix { let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis() as u64; let timestamp_be_bytes = timestamp.to_be_bytes(); rand_bytes[..8].clone_from_slice(×tamp_be_bytes[..8]); } let challenge = URL_SAFE_NO_PAD.encode(rand_bytes); Self::new(challenge, app_id) } pub fn new(challenge: S1, app_id: S2) -> Self where S1: Into, S2: Into { Self { challenge: challenge.into(), version: "U2F_V2".into(), app_id: app_id.into(), } } pub fn to_json(&self) -> String { serde_json::to_string(&self).unwrap() } } pub fn start_status_updater() -> Sender { let (status_tx, status_rx) = channel::(); thread::spawn(move || loop { match status_rx.recv() { Ok(StatusUpdate::DeviceAvailable { dev_info }) => { debugging!("STATUS: device available: {}", dev_info) } Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { debugging!("STATUS: device unavailable: {}", dev_info) } Ok(StatusUpdate::Success { dev_info }) => { debugging!("STATUS: success using device: {}", dev_info); } Err(_) => { debugging!("STATUS: end"); return; } } }); status_tx } pub fn to_hex(data: &[u8], joiner: &str) -> String { let parts: Vec = data.iter().map(|byte| format!("{:02x}", byte)).collect(); parts.join(joiner) }