mod client_pin_command;
mod client_pin_response;
mod cose;
mod ctaphid;
mod get_assertion_command;
pub mod get_assertion_params;
mod get_assertion_response;
mod get_info_command;
mod get_info_response;
mod get_next_assertion_command;
mod make_credential_command;
pub mod make_credential_params;
mod make_credential_response;
mod p256;
mod pintoken;
mod ss;
pub mod util;
pub mod verifier;
pub struct HidParam {
pub vid: u16,
pub pid: u16,
}
impl HidParam {
pub fn get_default_params() -> Vec<HidParam> {
vec![
HidParam {
vid: 0x1050,
pid: 0x0402,
},
HidParam {
vid: 0x1050,
pid: 0x0120,
},
HidParam {
vid: 0x096E,
pid: 0x085D,
},
HidParam {
vid: 0x096E,
pid: 0x0866,
},
HidParam {
vid: 0x0483,
pid: 0xa2ca,
},
]
}
}
pub fn get_hid_devices() -> Vec<(String, HidParam)> {
ctaphid::get_hid_devices(None)
}
pub fn get_fidokey_devices() -> Vec<(String, HidParam)> {
ctaphid::get_hid_devices(Some(ctaphid::USAGE_PAGE_FIDO))
}
pub fn wink(hid_params: &[HidParam]) -> Result<(), &'static str> {
let device = ctaphid::connect_device(hid_params, ctaphid::USAGE_PAGE_FIDO)?;
let cid = ctaphid::ctaphid_init(&device);
ctaphid::ctaphid_wink(&device, &cid);
Ok(())
}
pub fn get_info(hid_params: &[HidParam]) -> Result<Vec<(String, String)>, &'static str> {
let device = ctaphid::connect_device(hid_params, ctaphid::USAGE_PAGE_FIDO)?;
let cid = ctaphid::ctaphid_init(&device);
let send_payload = get_info_command::create_payload();
let response_cbor = ctaphid::ctaphid_cbor(&device, &cid, &send_payload).unwrap();
let info = get_info_response::parse_cbor(&response_cbor).unwrap();
let mut result: Vec<(String, String)> = vec![];
for i in info.versions {
result.push(("versions".to_string(), i));
}
for i in info.extensions {
result.push(("extensions".to_string(), i));
}
result.push(("aaguid".to_string(), util::to_hex_str(&info.aaguid)));
for i in info.options {
result.push((format!("options-{}", i.0), i.1.to_string()));
}
result.push(("max_msg_size".to_string(), info.max_msg_size.to_string()));
for i in info.pin_protocols {
result.push(("pin_protocols".to_string(), i.to_string()));
}
Ok(result)
}
pub fn get_pin_retries(hid_params: &[HidParam]) -> Result<i32, &'static str> {
let device = ctaphid::connect_device(hid_params, ctaphid::USAGE_PAGE_FIDO)?;
let cid = ctaphid::ctaphid_init(&device);
let send_payload =
client_pin_command::create_payload(client_pin_command::SubCommand::GetRetries).unwrap();
let response_cbor = ctaphid::ctaphid_cbor(&device, &cid, &send_payload).unwrap();
let pin = client_pin_response::parse_cbor_client_pin_get_retries(&response_cbor).unwrap();
Ok(pin.retries)
}
pub fn make_credential(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
pin: &str,
) -> Result<make_credential_params::Attestation, String> {
let result = make_credential_inter(hid_params, rpid, challenge, pin, false, true, None)?;
Ok(result)
}
pub fn make_credential_rk(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
pin: &str,
rkparam: &make_credential_params::RkParam,
) -> Result<make_credential_params::Attestation, String> {
let result =
make_credential_inter(hid_params, rpid, challenge, pin, true, true, Some(rkparam))?;
Ok(result)
}
pub fn make_credential_without_pin(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
) -> Result<make_credential_params::Attestation, String> {
let result = make_credential_inter(hid_params, rpid, challenge, "", false, false, None)?;
Ok(result)
}
fn make_credential_inter(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
pin: &str,
rk: bool,
uv: bool,
rkparam: Option<&make_credential_params::RkParam>,
) -> Result<make_credential_params::Attestation, String> {
let device = ctaphid::connect_device(hid_params, ctaphid::USAGE_PAGE_FIDO)?;
let cid = ctaphid::ctaphid_init(&device);
let user_id = {
if let Some(rkp) = rkparam {
rkp.user_id.to_vec()
} else {
[].to_vec()
}
};
let send_payload = {
let mut params = make_credential_command::Params::new(rpid, challenge.to_vec(), user_id);
params.option_rk = rk;
params.option_uv = uv;
if let Some(rkp) = rkparam {
params.user_name = rkp.user_name.to_string();
params.user_display_name = rkp.user_display_name.to_string();
}
if pin.len() > 0 {
let pin_auth =
get_pin_token(&device, &cid, pin.to_string())?.auth(¶ms.client_data_hash);
params.pin_auth = pin_auth.to_vec();
}
make_credential_command::create_payload(params)
};
if util::is_debug() == true {
println!(
"- make_credential({:02}) = {:?}",
send_payload.len(),
util::to_hex_str(&send_payload)
);
}
let response_cbor = match ctaphid::ctaphid_cbor(&device, &cid, &send_payload) {
Ok(n) => n,
Err(err) => {
let msg = format!(
"make_credential_command err = {}",
util::get_ctap_status_message(err)
);
return Err(msg);
}
};
let att = make_credential_response::parse_cbor(&response_cbor).unwrap();
Ok(att)
}
pub fn get_assertion(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
credential_id: &[u8],
pin: &str,
) -> Result<get_assertion_params::Assertion, String> {
let asss = get_assertion_inter(hid_params, rpid, challenge, credential_id, pin, true, true)?;
Ok(asss[0].clone())
}
pub fn get_assertions_rk(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
pin: &str,
) -> Result<Vec<get_assertion_params::Assertion>, String> {
let dmy: [u8; 0] = [];
let asss = get_assertion_inter(hid_params, rpid, challenge, &dmy, pin, true, true)?;
Ok(asss)
}
fn get_assertion_inter(
hid_params: &[HidParam],
rpid: &str,
challenge: &[u8],
credential_id: &[u8],
pin: &str,
up: bool,
uv: bool,
) -> Result<Vec<get_assertion_params::Assertion>, String> {
let device = ctaphid::connect_device(hid_params, ctaphid::USAGE_PAGE_FIDO)?;
let cid = ctaphid::ctaphid_init(&device);
let pin_token = get_pin_token(&device, &cid, pin.to_string())?;
let send_payload = {
let mut params =
get_assertion_command::Params::new(rpid, challenge.to_vec(), credential_id.to_vec());
params.option_up = up;
params.option_uv = uv;
let pin_auth = pin_token.auth(¶ms.client_data_hash);
params.pin_auth = pin_auth.to_vec();
get_assertion_command::create_payload(params)
};
let response_cbor = match ctaphid::ctaphid_cbor(&device, &cid, &send_payload) {
Ok(n) => n,
Err(err) => {
let msg = format!(
"get_assertion_command err = {}",
util::get_ctap_status_message(err)
);
return Err(msg);
}
};
if util::is_debug() == true {
println!(
"- response_cbor({:02}) = {:?}",
response_cbor.len(),
util::to_hex_str(&response_cbor)
);
}
let ass = get_assertion_response::parse_cbor(&response_cbor).unwrap();
let mut asss = vec![ass];
for _ in 0..(asss[0].number_of_credentials - 1) {
let ass = get_next_assertion(&device, &cid).unwrap();
asss.push(ass);
}
Ok(asss)
}
fn get_next_assertion(
device: &hidapi::HidDevice,
cid: &[u8],
) -> Result<get_assertion_params::Assertion, String> {
let send_payload = get_next_assertion_command::create_payload();
let response_cbor = match ctaphid::ctaphid_cbor(&device, &cid, &send_payload) {
Ok(n) => n,
Err(err) => {
let msg = format!(
"get_next_assertion_command err = {}",
util::get_ctap_status_message(err)
);
return Err(msg);
}
};
let ass = get_assertion_response::parse_cbor(&response_cbor).unwrap();
Ok(ass)
}
fn get_pin_token(
device: &hidapi::HidDevice,
cid: &[u8],
pin: String,
) -> Result<pintoken::PinToken, String> {
if pin.len() > 0 {
let send_payload =
client_pin_command::create_payload(client_pin_command::SubCommand::GetKeyAgreement)
.unwrap();
let response_cbor = match ctaphid::ctaphid_cbor(device, cid, &send_payload){
Ok(result) => result,
Err(err) => {
let msg = format!("ctaphid_cbor err = 0x{:02X}",err);
return Err(msg);
}
};
let key_agreement =
client_pin_response::parse_cbor_client_pin_get_keyagreement(&response_cbor).unwrap();
let shared_secret = ss::SharedSecret::new(&key_agreement).unwrap();
let pin_hash_enc = shared_secret.encrypt_pin(&pin).unwrap();
let send_payload = client_pin_command::create_payload_get_pin_token(
&shared_secret.public_key,
pin_hash_enc.to_vec(),
);
let response_cbor = match ctaphid::ctaphid_cbor(&device, &cid, &send_payload) {
Ok(result) => result,
Err(err) => {
let msg = format!(
"get_pin_token_command err = {}",
util::get_ctap_status_message(err)
);
return Err(msg);
}
};
let mut pin_token_enc =
client_pin_response::parse_cbor_client_pin_get_pin_token(&response_cbor).unwrap();
let pin_token_dec = shared_secret.decrypt_token(&mut pin_token_enc).unwrap();
Ok(pin_token_dec)
} else {
Err("pin not set".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ring::{digest, hmac};
#[test]
fn test_get_hid_devices() {
get_hid_devices();
assert!(true);
}
#[test]
fn test_wink() {
let hid_params = HidParam::get_default_params();
wink(&hid_params).unwrap();
assert!(true);
}
#[test]
fn test_get_info() {
let hid_params = HidParam::get_default_params();
get_info(&hid_params).unwrap();
assert!(true);
}
#[test]
fn test_client_pin_get_retries() {
let hid_params = HidParam::get_default_params();
let retry = get_pin_retries(&hid_params);
println!("- retries = {:?}", retry);
assert!(true);
}
#[test]
fn test_client_pin_get_keyagreement() {
let params = HidParam::get_default_params();
let device = ctaphid::connect_device(¶ms, ctaphid::USAGE_PAGE_FIDO).unwrap();
let cid = ctaphid::ctaphid_init(&device);
let send_payload =
client_pin_command::create_payload(client_pin_command::SubCommand::GetKeyAgreement)
.unwrap();
let response_cbor = ctaphid::ctaphid_cbor(&device, &cid, &send_payload).unwrap();
let key_agreement =
client_pin_response::parse_cbor_client_pin_get_keyagreement(&response_cbor).unwrap();
key_agreement.print("authenticatorClientPIN (0x06) - getKeyAgreement");
assert!(true);
}
#[test]
fn test_make_credential_with_pin_non_rk() {
let rpid = "test.com";
let challenge = b"this is challenge".to_vec();
let pin = "1234";
let params = HidParam::get_default_params();
let att = make_credential(¶ms, rpid, &challenge, pin).unwrap();
att.print("Attestation");
let ass = get_assertion(¶ms, rpid, &challenge, &att.credential_id, pin).unwrap();
ass.print("Assertion");
assert!(true);
}
#[test]
fn test_make_credential_with_pin_non_rk_command() {
let rpid = "test.com";
let challenge = b"this is challenge".to_vec();
let pin_auth = hex::decode("6F79FB322D74972ACAA844C10C183BF7").unwrap();
let check = "01A7015820E61E2BD6C4612662960B159CD54CF8EFF1A998C89B3742519D11F85E0F5E787602A262696468746573742E636F6D646E616D656003A36269644100646E616D6561206B646973706C61794E616D6561200481A263616C672664747970656A7075626C69632D6B657907A262726BF4627576F508506F79FB322D74972ACAA844C10C183BF70901".to_string();
let send_payload = {
let mut params =
make_credential_command::Params::new(rpid, challenge.to_vec(), [].to_vec());
params.option_rk = false;
params.option_uv = true;
println!(
"- client_data_hash({:02}) = {:?}",
params.client_data_hash.len(),
util::to_hex_str(¶ms.client_data_hash)
);
params.pin_auth = pin_auth.to_vec();
make_credential_command::create_payload(params)
};
let command = hex::encode(send_payload).to_uppercase();
assert_eq!(command, check);
}
#[test]
fn test_create_pin_auth() {
let out_bytes = hex::decode("1A81CD600A1F6CF4BE5260FE3257B241").unwrap();
let client_data_hash =
hex::decode("E61E2BD6C4612662960B159CD54CF8EFF1A998C89B3742519D11F85E0F5E7876")
.unwrap();
let check = "F0AC99D6AAD2E199AF9CF25F6568A6F5".to_string();
let pin_token_dec = pintoken::PinToken(hmac::SigningKey::new(&digest::SHA256, &out_bytes));
let pin_auth = pin_token_dec.auth(&client_data_hash);
assert_eq!(check, hex::encode(pin_auth).to_uppercase());
}
}