168 lines
5.1 KiB
Rust
168 lines
5.1 KiB
Rust
use std::fmt;
|
|
use std::thread;
|
|
use std::sync::mpsc::{channel, Sender};
|
|
use serde::{Deserialize, Serialize};
|
|
use authenticator::{StatusUpdate, RegisterResult};
|
|
use base64::URL_SAFE_NO_PAD;
|
|
use rand::Rng;
|
|
use rust_util::XResult;
|
|
|
|
#[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<String>,
|
|
pub client_data: String,
|
|
pub registration_data: String,
|
|
pub attestation_cert_pem: Option<String>,
|
|
pub pub_key: String,
|
|
pub key_handle: String,
|
|
}
|
|
|
|
impl U2fRegistrationData {
|
|
pub fn from(app_id: &str, client_data: &str, register_result: RegisterResult) -> XResult<Self> {
|
|
let rr = match u2f::register::parse_registration(app_id.to_string(), client_data.as_bytes().to_vec(), register_result.0.to_vec()) {
|
|
Ok(rr) => rr,
|
|
Err(e) => return simple_error!("Parse registration data failed: {}", e),
|
|
};
|
|
Ok(Self {
|
|
app_id: app_id.to_string(),
|
|
device_info: U2FDeviceInfo::from(®ister_result),
|
|
device_name: rr.device_name,
|
|
client_data: client_data.into(),
|
|
registration_data: base64::encode(®ister_result.0),
|
|
attestation_cert_pem: rr.attestation_cert.map(|c| {
|
|
to_pem(&c, "CERTIFICATE", 64)
|
|
}),
|
|
pub_key: hex::encode(rr.pub_key),
|
|
key_handle: hex::encode(rr.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_random<S>(app_id: S) -> Self where S: Into<String> {
|
|
let mut rng = rand::thread_rng();
|
|
let mut rand_bytes = [0_u8; 32];
|
|
for i in 0..32 {
|
|
let b: u8 = rng.gen();
|
|
rand_bytes[i] = b;
|
|
}
|
|
|
|
let challenge = base64::encode_config(&rand_bytes, URL_SAFE_NO_PAD);
|
|
Self::new(challenge, app_id)
|
|
}
|
|
|
|
pub fn new<S1, S2>(challenge: S1, app_id: S2) -> Self where S1: Into<String>, S2: Into<String> {
|
|
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<StatusUpdate> {
|
|
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
|
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_pem(bs: &[u8], sub: &str, w: usize) -> String {
|
|
let mut s = String::with_capacity(bs.len() * 2);
|
|
s.push_str(&format!("-----BEGIN {}-----", sub));
|
|
let b64 = base64::encode(bs).chars().collect::<Vec<char>>();
|
|
let mut b64 = b64.as_slice();
|
|
while b64.len() > 0 {
|
|
s.push('\n');
|
|
if b64.len() >= w {
|
|
for i in 0..w {
|
|
s.push(b64[i]);
|
|
}
|
|
b64 = &b64[w..];
|
|
} else {
|
|
for c in b64 {
|
|
s.push(*c);
|
|
}
|
|
b64 = &[];
|
|
}
|
|
}
|
|
s.push_str(&format!("\n-----END {}-----", sub));
|
|
s
|
|
}
|
|
|
|
pub fn to_hex(data: &[u8], joiner: &str) -> String {
|
|
let parts: Vec<String> = data.iter().map(|byte| format!("{:02x}", byte)).collect();
|
|
parts.join(joiner)
|
|
}
|