add authentication request UI; fix tests again.

trying to debug why PIN authentication doesn't work. looks like
Cosekey conversion is fine, the data is arriving OK...but the
dh key exchange is not generated the expected shared secret.
This commit is contained in:
bunnie 2022-06-07 17:36:28 +08:00
parent 0830513ef1
commit ae9c2d5b99
10 changed files with 56 additions and 30 deletions

View file

@ -21,6 +21,8 @@ cbc = "0.1.2"
aes = {path="../../../../services/aes"}
sha2 = {path="../../../../services/engine-sha512"}
log = "0.4.14" # xous debugging
[target.'cfg(any(windows,unix))'.dependencies]
rand = "0.8.5"
rand_chacha = "0.3.1"

View file

@ -11,6 +11,12 @@
"zh": "FIDO 提出凭证请求\n按任意键批准",
"en-tts": "FIDO Make Credential Request. Press any key to approve."
},
"vault.fido.get_assertion": {
"en": "FIDO Assertion Request.\nPress any key to consent to this transaction.",
"ja": "FIDOアサーションリクエスト。\nこのトランザクションに同意するには、任意のキーを押してください。",
"zh": "FIDO 断言请求。\n按任意键同意此事务。",
"en-tts": "FIDO Assertion Request.\nPress any key to consent to this transaction."
},
"vault.fido.exclude_list": {
"en": "FIDO server is excluding existing credentials.\nPress any key to acknowledge.",
"ja": "FIDOサーバーは既存のクレデンシャルを除外しています。\n任意のキーを押して確認します。",

View file

@ -19,7 +19,7 @@ use crate::ctap::array_ref;
const APDU_HEADER_LEN: usize = 4;
#[cfg_attr(test, derive(Clone, Debug))]
#[cfg_attr(test, derive(Clone))]
#[allow(non_camel_case_types, dead_code)]
#[derive(PartialEq, Debug)]
pub enum ApduStatusCode {

View file

@ -373,7 +373,7 @@ mod test {
const CLOCK_FREQUENCY_HZ: usize = 32768;
const START_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
const TIMEOUT_CLOCK_VALUE: ClockValue = ClockValue::new(
(30001 * CLOCK_FREQUENCY_HZ as isize) / 1000,
(30001 * CLOCK_FREQUENCY_HZ as i64) / 1000,
CLOCK_FREQUENCY_HZ,
);
@ -423,8 +423,8 @@ mod test {
let application = [0x0A; 32];
let message = create_register_message(&application);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
// Certificate and private key are missing
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
@ -434,8 +434,8 @@ mod test {
.persistent_store
.set_attestation_private_key(&fake_key)
.is_ok());
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
// Certificate is still missing
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
@ -445,8 +445,8 @@ mod test {
.persistent_store
.set_attestation_certificate(&fake_cert[..])
.is_ok());
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response =
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
@ -488,8 +488,8 @@ mod test {
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response =
Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE);
assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED));
@ -636,8 +636,8 @@ mod test {
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response =
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
assert_eq!(response[0], 0x01);
@ -689,8 +689,8 @@ mod test {
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA));
}
@ -706,8 +706,8 @@ mod test {
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
//ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response =
Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE);
assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED));

View file

@ -140,7 +140,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialType {
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
#[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
// #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
#[derive(Debug)]
pub struct PublicKeyCredentialParameter {
pub cred_type: PublicKeyCredentialType,

View file

@ -472,7 +472,7 @@ where
// (self.check_user_presence)(cid)?;
let mut desc = String::from(t!("vault.fido.exclude_list", xous::LANG));
desc.push_str(
&format!("\nRelying Party: {}\nUser name: {}",
&format!("\n\nRelying Party: {}\nUser name: {}",
if let Some(name) = rp.rp_name {name.to_string()} else {rp_id.to_string()},
if let Some(name) = user.user_display_name {name} else {user.user_name.unwrap_or("*Unspecified*".to_string())},
)
@ -523,7 +523,7 @@ where
let mut make_cred_desc = String::from(t!("vault.fido.make_credential", xous::LANG));
let alt_name = user.user_name.as_deref().unwrap_or("*Unspecified*");
make_cred_desc.push_str(
&format!("\nRelying Party: {}\nUser name: {}",
&format!("\n\nRelying Party: {}\nUser name: {}",
// prefer the "friendly name" over the technical name
if let Some(name) = &rp.rp_name {name.to_string()} else {rp_id.to_string()},
if let Some(name) = &user.user_display_name {name} else {alt_name}
@ -815,7 +815,14 @@ where
// For CTAP 2.1, it was moved to a later protocol step.
if options.up {
//(self.check_user_presence)(cid)?;
if crate::ux::request_permission_blocking("process_get_assertion requests user presence".to_string(), cid).is_none() {
let mut desc = String::from(t!("vault.fido.get_assertion", xous::LANG));
desc.push_str(
&format!("\n\nRelying Party: {}\nClient data hash: {}",
rp_id,
hex::encode(&client_data_hash),
)
);
if crate::ux::request_permission_blocking(desc, cid).is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}

View file

@ -41,8 +41,10 @@ const PIN_TOKEN_LENGTH: usize = 32;
/// Returns LEFT(HMAC(hmac_key, hmac_contents), 16) == pin_auth).
fn verify_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
if pin_auth.len() != PIN_AUTH_LENGTH {
log::info!("pin_auth length is wrong: {}", pin_auth.len());
return false;
}
log::info!("key {:?}, contents: {:?}, pin: {:?}", hmac_key, hmac_contents, pin_auth);
verify_hmac_256_first_128bits::<Sha256>(
hmac_key,
hmac_contents,
@ -247,10 +249,14 @@ impl PinProtocolV1 {
pin_auth: &[u8],
authenticated_message: &[u8],
) -> Result<[u8; 32], Ctap2StatusCode> {
log::info!("cosekey: {:?}", key_agreement);
let pk: ctap_crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
log::info!("pk: {:?}", pk);
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
log::info!("shared_secret: {:?}", shared_secret);
if !verify_pin_auth(&shared_secret, authenticated_message, pin_auth) {
log::info!("pin_auth invalid");
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
@ -287,10 +293,12 @@ impl PinProtocolV1 {
new_pin_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
if persistent_store.pin_hash()?.is_some() {
log::info!("pin_hash() is_some()");
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
let pin_decryption_key =
self.exchange_decryption_key(key_agreement, &pin_auth, &new_pin_enc)?;
log::info!("exchanged decryption key");
check_and_store_new_pin(persistent_store, &pin_decryption_key, new_pin_enc)?;
persistent_store.reset_pin_retries()?;
Ok(())

View file

@ -623,7 +623,10 @@ openssl asn1parse -in opensk_cert.pem -inform pem
}
}
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => Ok(None),
std::io::ErrorKind::NotFound => {
log::info!("pin_hash not found");
Ok(None)
},
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), // PDDB internal error
}
}

View file

@ -106,12 +106,13 @@ mod test {
const CLOCK_FREQUENCY_HZ: usize = 1000;
const ZERO: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
const BIG_POSITIVE: ClockValue = ClockValue::new(isize::MAX / 1000 - 1, CLOCK_FREQUENCY_HZ);
const BIG_POSITIVE: ClockValue = ClockValue::new(i64::MAX / 1000 - 1, CLOCK_FREQUENCY_HZ);
const NEGATIVE: ClockValue = ClockValue::new(-1, CLOCK_FREQUENCY_HZ);
const SMALL_NEGATIVE: ClockValue = ClockValue::new(isize::MIN / 1000 + 1, CLOCK_FREQUENCY_HZ);
const SMALL_NEGATIVE: ClockValue = ClockValue::new(i64::MIN / 1000 + 1, CLOCK_FREQUENCY_HZ);
const REQUEST_DURATION: Duration<i64> = Duration::from_ms(1000);
const PRESENCE_DURATION: Duration<i64> = Duration::from_ms(1000);
/* // ux tests not valid in xous
fn grant_up_when_needed(start_time: ClockValue) {
let mut u2f_state = U2fUserPresenceState::new(REQUEST_DURATION, PRESENCE_DURATION);
assert!(!u2f_state.consume_up(start_time));
@ -136,8 +137,8 @@ mod test {
u2f_state.grant_up(start_time);
// The timeout excludes equality, so it should be over at this instant.
assert!(!u2f_state.consume_up(start_time.wrapping_add(PRESENCE_DURATION)));
}
} */
/*
#[test]
fn test_grant_up_timeout() {
grant_up_timeout(ZERO);
@ -168,5 +169,5 @@ mod test {
u2f_state.grant_up(ZERO);
assert!(!u2f_state.is_up_needed(ZERO));
assert!(!u2f_state.consume_up(ZERO));
}
}*/
}

View file

@ -315,7 +315,7 @@ pub(crate) fn start_ux_thread() {
if deferred {
last_timer = 1 + (prompt_expiration_ms - tt.elapsed_ms() as i64 - 1) / 1000;
request_str.push_str(
&format!("\n\n⚠ {}{} ⚠",
&format!("\n\n⚠ {}{} ⚠\n",
last_timer,
t!("vault.fido.countdown", xous::LANG)
));
@ -388,13 +388,12 @@ pub(crate) fn start_ux_thread() {
log::info!("new_timer: {}", new_timer);
let mut request_str = String::from(&request_str_base);
request_str.push_str(
&format!("{}\n\n⚠ {}{} ⚠",
&request_str_base,
&format!("\n\n⚠ {}{} ⚠\n",
new_timer,
t!("vault.fido.countdown", xous::LANG)
));
modals.dynamic_notification_update(
None,
Some(t!("vault.u2freq", xous::LANG)),
Some(&request_str),
).unwrap();
last_timer = new_timer;