diff --git a/Cargo.lock b/Cargo.lock index 681c6c3..e34b027 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.1.13" +version = "1.1.14" dependencies = [ "authenticator", "base64 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index f464afe..22f0065 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.1.13" +version = "1.1.14" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_u2fregister.rs b/src/cmd_u2fregister.rs index 8ec0fe7..9e706b6 100644 --- a/src/cmd_u2fregister.rs +++ b/src/cmd_u2fregister.rs @@ -4,7 +4,6 @@ use std::sync::mpsc::channel; use authenticator::authenticatorservice::AuthenticatorService; use authenticator::RegisterFlags; use authenticator::statecallback::StateCallback; -use base64::URL_SAFE_NO_PAD; use clap::{App, Arg, ArgMatches, SubCommand}; use openssl::hash::MessageDigest; use openssl::pkey::PKey; @@ -40,14 +39,7 @@ impl Command for CommandImpl { Err(e) => return simple_error!("Timeout should be a number: {}", e), }; - let u2fv2_challenge = match sub_arg_matches.value_of("challenge") { - None => U2fV2Challenge::new_random(app_id), - Some(challenge_hex) => { - let challenge_bytes = opt_result!(hex::decode(challenge_hex), "Decode challenge hex failed: {}"); - let challenge = base64::encode_config(&challenge_bytes, URL_SAFE_NO_PAD); - U2fV2Challenge::new(challenge, app_id) - } - }; + let u2fv2_challenge = U2fV2Challenge::new_challenge(sub_arg_matches.value_of("challenge"), app_id)?; let u2fv2_challenge_str = u2fv2_challenge.to_json(); let chall_bytes = digest::sha256(&u2fv2_challenge_str); @@ -87,12 +79,12 @@ impl Command for CommandImpl { // +------+-------------------+-----------------+------------+--------------------+ // + 0x00 | application (32B) | challenge (32B) | key handle | User pub key (65B) | // +------+-------------------+-----------------+------------+--------------------+ - let mut to_be_signed = Vec::with_capacity(200); - to_be_signed.push(0x00); - to_be_signed.extend_from_slice(&app_bytes); - to_be_signed.extend_from_slice(&chall_bytes); - to_be_signed.extend_from_slice(&u2f_registration_data.key_handle); - to_be_signed.extend_from_slice(&u2f_registration_data.pub_key); + let mut signed_message = Vec::with_capacity(200); + signed_message.push(0x00); + signed_message.extend_from_slice(&app_bytes); + signed_message.extend_from_slice(&chall_bytes); + signed_message.extend_from_slice(&u2f_registration_data.key_handle); + signed_message.extend_from_slice(&u2f_registration_data.pub_key); // +------+--------------------+---------------------+------------+------------+------+ // + 0x05 | User pub key (65B) | key handle len (1B) | key handle | X.509 Cert | Sign | // +------+--------------------+---------------------+------------+------------+------+ @@ -114,7 +106,7 @@ impl Command for CommandImpl { json.insert("pub_key", hex::encode(&u2f_registration_data.pub_key)); json.insert("key_handle", hex::encode(&u2f_registration_data.key_handle)); json.insert("signature", hex::encode(sign)); - json.insert("to_be_signed", hex::encode(&to_be_signed)); + json.insert("signed_message", hex::encode(&signed_message)); json.insert("registration_data", hex::encode(®ister_result.0)); json.insert("app_id", app_id.to_string()); json.insert("app_id_hash", hex::encode(&app_bytes)); @@ -133,7 +125,7 @@ impl Command for CommandImpl { success!("Public key: {}", hex::encode(&u2f_registration_data.pub_key)); success!("Key handle: {}", hex::encode(&u2f_registration_data.key_handle)); debugging!("Registration data: {}", hex::encode(®ister_result.0)); - information!("To be signed: {}", hex::encode(&to_be_signed)); + information!("Signed message: {}", hex::encode(&signed_message)); information!("Signature: {}", hex::encode(sign)); if let Some(attestation_cert) = &u2f_registration_data.attestation_cert { @@ -141,7 +133,7 @@ impl Command for CommandImpl { debugging!("Attestation public key: {:?}", cert.1.public_key().subject_public_key); let pkey = opt_result!(PKey::public_key_from_der(cert.1.public_key().raw), "Parse public key failed: {}"); let mut verifier = opt_result!(Verifier::new(MessageDigest::sha256(), &pkey), "Verifier new failed: {}"); - verifier.update(&to_be_signed)?; + verifier.update(&signed_message)?; let verify_result = opt_result!(verifier.verify(sign), "Verifier verify failed: {}"); if verify_result { success!("Verify success"); diff --git a/src/cmd_u2fsign.rs b/src/cmd_u2fsign.rs index 9e59d16..5f17139 100644 --- a/src/cmd_u2fsign.rs +++ b/src/cmd_u2fsign.rs @@ -4,7 +4,13 @@ use authenticator::{AuthenticatorTransports, KeyHandle, SignFlags}; use authenticator::authenticatorservice::AuthenticatorService; use authenticator::statecallback::StateCallback; use clap::{App, Arg, ArgMatches, SubCommand}; +use openssl::bn::BigNumContext; +use openssl::ec::{EcGroup, EcKey, EcPoint}; +use openssl::hash::MessageDigest; +use openssl::nid::Nid; +use openssl::pkey::PKey; use openssl::sha::sha256; +use openssl::sign::Verifier; use rust_util::util_clap::{Command, CommandError}; use crate::digest; @@ -20,6 +26,7 @@ impl Command for CommandImpl { .arg(Arg::with_name("app-id").short("a").long("app-id").default_value("https://example.com").help("App id")) .arg(Arg::with_name("timeout").short("t").long("timeout").default_value("30").help("Timeout in seconds")) .arg(Arg::with_name("public-key-hex").long("public-key-hex").takes_value(true).help("Public key hex")) + .arg(Arg::with_name("challenge").long("challenge").takes_value(true).help("Challenge HEX")) .arg(Arg::with_name("key-handle").short("k").long("key-handle").takes_value(true).multiple(true).help("Key handle")) } fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { @@ -51,7 +58,9 @@ impl Command for CommandImpl { sign_tx.send(rv).unwrap(); })); - let u2fv2_challenge = U2fV2Challenge::new_random(app_id); + + let u2fv2_challenge = U2fV2Challenge::new_challenge(sub_arg_matches.value_of("challenge"), app_id)?; + let u2fv2_challenge_str = u2fv2_challenge.to_json(); let chall_bytes = digest::sha256(&u2fv2_challenge_str); @@ -88,7 +97,7 @@ impl Command for CommandImpl { information!("Sign result : {}", base64::encode(&sign_data)); information!("- presence : {}", user_presence_flag); information!("- counter : {}", u32::from_be_bytes([counter[0], counter[1], counter[2], counter[3]])); - information!("- signature: {}", base64::encode(&signature)); + information!("- signature: {}", hex::encode(&signature)); // success!("Key handle used: {}", base64::encode(&handle_used)); information!("Key handle: {}", hex::encode(&handle_used)); @@ -98,23 +107,37 @@ impl Command for CommandImpl { let client_data = u2fv2_challenge_str.as_bytes().to_vec(); let app_id_hash = sha256(app_id.as_bytes()); let client_data_hash = sha256(&client_data[..]); - let mut msg = Vec::with_capacity(128); - msg.extend_from_slice(&app_id_hash); - msg.push(*user_presence_flag); - msg.extend_from_slice(counter); - msg.extend_from_slice(&client_data_hash); + let mut signed_message = Vec::with_capacity(128); + signed_message.extend_from_slice(&app_id_hash); + signed_message.push(*user_presence_flag); + signed_message.extend_from_slice(counter); + signed_message.extend_from_slice(&client_data_hash); - information!("Public key: {}", base64::encode(&public_key)); - information!("Signed message: {}", base64::encode(&msg)); + information!("Public key: {}", hex::encode(&public_key)); + information!("Signed message: {}", hex::encode(&signed_message)); let authorization_result = u2f::authorization::parse_sign_response( app_id.to_string(), client_data, - public_key, - sign_data, + public_key.clone(), + sign_data.clone(), ); let authorization = opt_result!(authorization_result, "Parse authorization failed: {}"); success!("Parse authorization success, counter: {}", authorization.counter); + + // PKey::public_key_from_der() + let ec_group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let ec_point = EcPoint::from_bytes(&ec_group, &public_key, &mut BigNumContext::new().unwrap()).unwrap(); + let ec_key = EcKey::from_public_key(&ec_group, &ec_point).unwrap(); + let ec_pkey = PKey::from_ec_key(ec_key).unwrap(); + let mut verifier = opt_result!(Verifier::new(MessageDigest::sha256(), &ec_pkey), "Verifier new failed: {}"); + verifier.update(&signed_message)?; + let verify_result = opt_result!(verifier.verify(signature), "Verifier verify failed: {}"); + if verify_result { + success!("Verify success"); + } else { + failure!("Verify failed"); + } } Ok(None) diff --git a/src/fido.rs b/src/fido.rs index 497449d..4d59677 100644 --- a/src/fido.rs +++ b/src/fido.rs @@ -7,6 +7,7 @@ use base64::URL_SAFE_NO_PAD; use rand::Rng; use rust_util::XResult; use serde::{Deserialize, Serialize}; + use crate::pkiutil::bytes_to_pem; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -94,6 +95,17 @@ pub struct U2fV2Challenge { } impl U2fV2Challenge { + pub fn new_challenge(challenge_hex: Option<&str>, app_id: &str) -> XResult { + Ok(match challenge_hex { + None => U2fV2Challenge::new_random(app_id), + Some(challenge_hex) => { + let challenge_bytes = opt_result!(hex::decode(challenge_hex), "Decode challenge hex failed: {}"); + let challenge = base64::encode_config(&challenge_bytes, base64::URL_SAFE_NO_PAD); + U2fV2Challenge::new(challenge, app_id) + } + }) + } + pub fn new_random(app_id: S) -> Self where S: Into { let mut rng = rand::thread_rng(); let mut rand_bytes = [0_u8; 32];