From a528985ffd974e47b8e41d61872ecd45ea0ffe87 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 3 Jul 2021 11:29:23 +0800 Subject: [PATCH] feat: register outputs json --- src/fido.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++-- src/register.rs | 39 ++++++++++++++---------- src/sign.rs | 5 ++- 3 files changed, 106 insertions(+), 19 deletions(-) diff --git a/src/fido.rs b/src/fido.rs index 67aca19..ed09c06 100644 --- a/src/fido.rs +++ b/src/fido.rs @@ -1,9 +1,81 @@ +use std::fmt; use std::thread; use std::sync::mpsc::{channel, Sender}; use serde::{Deserialize, Serialize}; -use authenticator::StatusUpdate; +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 u2f_device_info: U2FDeviceInfo, + pub device_name: Option, + pub attestation_cert_pem: Option, + pub pub_key: String, + pub key_handle: String, +} + +impl U2fRegistrationData { + pub fn from(app_id: &str, client_data: &str, register_result: RegisterResult) -> XResult { + 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 { + // register_result, + u2f_device_info: U2FDeviceInfo::from(®ister_result), + device_name: rr.device_name, + 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 { @@ -82,4 +154,9 @@ pub fn to_pem(bs: &[u8], sub: &str, w: usize) -> String { } s.push_str(&format!("\n-----END {}-----", sub)); s -} \ No newline at end of file +} + +pub fn to_hex(data: &[u8], joiner: &str) -> String { + let parts: Vec = data.iter().map(|byte| format!("{:02x}", byte)).collect(); + parts.join(joiner) +} diff --git a/src/register.rs b/src/register.rs index a61c0b4..ff8fe3f 100644 --- a/src/register.rs +++ b/src/register.rs @@ -6,7 +6,7 @@ use authenticator::RegisterFlags; use std::sync::mpsc::channel; use crate::fido; use crate::digest; -use crate::fido::U2fV2Challenge; +use crate::fido::{U2fV2Challenge, U2fRegistrationData}; pub struct CommandImpl; @@ -17,6 +17,7 @@ impl Command for CommandImpl { SubCommand::with_name(self.name()).about("Register subcommand") .arg(Arg::with_name("app-id").long("app-id").default_value("https://example.com").help("App id")) .arg(Arg::with_name("timeout").long("timeout").default_value("10").help("Timeout in seconds")) + .arg(Arg::with_name("json").long("json").help("JSON output")) } fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { @@ -25,6 +26,7 @@ impl Command for CommandImpl { Ok(t) => (t * 1000) as u64, Err(e) => return simple_error!("Timeout should be a number: {}", e), }; + let json_output = sub_arg_matches.is_present("json"); let u2fv2_challenge = U2fV2Challenge::new_random(app_id); let u2fv2_challenge_str = u2fv2_challenge.to_json(); @@ -57,24 +59,29 @@ impl Command for CommandImpl { )?; let register_result = register_rx.recv()?; - let (register_data, device_info) = register_result?; - success!("Device info: {}", &device_info); - success!("Register result: {}", base64::encode(®ister_data)); + let u2f_registration_data = U2fRegistrationData::from(app_id, &u2fv2_challenge_str, register_result?); - let client_data = &u2fv2_challenge_str; - let rr = match u2f::register::parse_registration(app_id.to_string(), client_data.as_bytes().to_vec(), register_data) { - Ok(rr) => rr, - Err(e) => return simple_error!("Parse registration data failed: {}", e), - }; - if let Some(cert) = rr.attestation_cert { - success!("Certificate: {}", fido::to_pem(&cert, "CERTIFICATE", 64)); + match u2f_registration_data { + Ok(data) => { + if json_output { + success!("{}", serde_json::to_string_pretty(&data).unwrap()); + } else { + success!("Device info: {}", data.u2f_device_info); + if let Some(cert) = data.attestation_cert_pem { + success!("Certificate: {}", cert); + } + if let Some(device_name) = data.device_name { + success!("Device name: {}", device_name); + } + success!("Public key: {}", hex::encode(data.pub_key)); + success!("Key handle: {}", hex::encode(data.key_handle)); + } + } + Err(e) => { + return simple_error!("Parse registration data failed: {}", e); + } } - if let Some(device_name) = rr.device_name { - success!("Device name: {}", device_name); - } - success!("Public key: {}", hex::encode(rr.pub_key)); - success!("Key handle: {}", hex::encode(rr.key_handle)); Ok(()) } } diff --git a/src/sign.rs b/src/sign.rs index 0778042..20ddbbe 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -48,7 +48,8 @@ impl Command for CommandImpl { })); let u2fv2_challenge = U2fV2Challenge::new_random(app_id); - let chall_bytes = digest::sha256(&u2fv2_challenge.to_json()); + let u2fv2_challenge_str = u2fv2_challenge.to_json(); + let chall_bytes = digest::sha256(&u2fv2_challenge_str); let app_bytes = digest::sha256(app_id); @@ -80,6 +81,8 @@ impl Command for CommandImpl { success!("Key handle used: {}", base64::encode(&handle_used)); success!("Key handle used: {}", hex::encode(&handle_used)); + // u2f::authorization::parse_sign_response(app_id.to_string(), u2fv2_challenge_str.as_bytes().to_vec(), ) + Ok(()) } } \ No newline at end of file