From c596f785fff92d7b96a472b1e09ae230afd6518c Mon Sep 17 00:00:00 2001 From: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com> Date: Tue, 23 Mar 2021 12:07:15 +0100 Subject: [PATCH] Output parameters for CTAP2.1 (#297) * finalizes output parameters for CTAP2.1 * explanation for internal UV --- src/ctap/client_pin.rs | 24 +++++++++++++++++++++- src/ctap/mod.rs | 1 + src/ctap/response.rs | 45 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/ctap/client_pin.rs b/src/ctap/client_pin.rs index 82e7904..96c3f02 100644 --- a/src/ctap/client_pin.rs +++ b/src/ctap/client_pin.rs @@ -200,6 +200,7 @@ impl ClientPin { key_agreement: None, pin_token: None, retries: Some(persistent_store.pin_retries()? as u64), + power_cycle_state: Some(self.consecutive_pin_mismatches >= 3), }) } @@ -215,6 +216,7 @@ impl ClientPin { key_agreement, pin_token: None, retries: None, + power_cycle_state: None, }) } @@ -331,6 +333,7 @@ impl ClientPin { key_agreement: None, pin_token: Some(pin_token), retries: None, + power_cycle_state: None, }) } @@ -812,6 +815,24 @@ mod test { key_agreement: None, pin_token: None, retries: Some(persistent_store.pin_retries().unwrap() as u64), + power_cycle_state: Some(false), + }); + assert_eq!( + client_pin.process_command( + &mut rng, + &mut persistent_store, + params.clone(), + DUMMY_CLOCK_VALUE + ), + Ok(ResponseData::AuthenticatorClientPin(expected_response)) + ); + + client_pin.consecutive_pin_mismatches = 3; + let expected_response = Some(AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: None, + retries: Some(persistent_store.pin_retries().unwrap() as u64), + power_cycle_state: Some(true), }); assert_eq!( client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), @@ -840,6 +861,7 @@ mod test { key_agreement: params.key_agreement.clone(), pin_token: None, retries: None, + power_cycle_state: None, }); assert_eq!( client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE), @@ -1266,7 +1288,7 @@ mod test { let salt_enc = vec![0x01; 32]; let mut salt_auth = shared_secret.authenticate(&salt_enc); - salt_auth[0] = 0x00; + salt_auth[0] ^= 0x01; let hmac_secret_input = GetAssertionHmacSecretInput { key_agreement: client_pin .get_pin_protocol(pin_uv_auth_protocol) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 46d805b..d47a248 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -1101,6 +1101,7 @@ where firmware_version: None, max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64), max_rp_ids_for_set_min_pin_length: Some(MAX_RP_IDS_LENGTH as u64), + certifications: None, remaining_discoverable_credentials: Some( self.persistent_store.remaining_credentials()? as u64, ), diff --git a/src/ctap/response.rs b/src/ctap/response.rs index b6b3d25..ddc186b 100644 --- a/src/ctap/response.rs +++ b/src/ctap/response.rs @@ -20,7 +20,7 @@ use super::data_formats::{ use alloc::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; -use cbor::{cbor_array_vec, cbor_bool, cbor_map_btree, cbor_map_options, cbor_text}; +use cbor::{cbor_array_vec, cbor_bool, cbor_int, cbor_map_btree, cbor_map_options, cbor_text}; #[derive(Debug, PartialEq)] pub enum ResponseData { @@ -92,6 +92,7 @@ pub struct AuthenticatorGetAssertionResponse { pub signature: Vec, pub user: Option, pub number_of_credentials: Option, + // 0x06: userSelected missing as we don't support displays. pub large_blob_key: Option>, } @@ -135,7 +136,14 @@ pub struct AuthenticatorGetInfoResponse { pub firmware_version: Option, pub max_cred_blob_length: Option, pub max_rp_ids_for_set_min_pin_length: Option, + // Missing response fields as they are only relevant for internal UV: + // - 0x11: preferredPlatformUvAttempts + // - 0x12: uvModality + // Add them when your hardware supports any kind of user verification within + // the boundary of the device, e.g. fingerprint or built-in keyboard. + pub certifications: Option>, pub remaining_discoverable_credentials: Option, + // - 0x15: vendorPrototypeConfigCommands missing as we don't support it. } impl From for cbor::Value { @@ -157,15 +165,24 @@ impl From for cbor::Value { firmware_version, max_cred_blob_length, max_rp_ids_for_set_min_pin_length, + certifications, remaining_discoverable_credentials, } = get_info_response; let options_cbor: Option = options.map(|options| { - let option_map: BTreeMap<_, _> = options + let options_map: BTreeMap<_, _> = options .into_iter() .map(|(key, value)| (cbor_text!(key), cbor_bool!(value))) .collect(); - cbor_map_btree!(option_map) + cbor_map_btree!(options_map) + }); + + let certifications_cbor: Option = certifications.map(|certifications| { + let certifications_map: BTreeMap<_, _> = certifications + .into_iter() + .map(|(key, value)| (cbor_text!(key), cbor_int!(value))) + .collect(); + cbor_map_btree!(certifications_map) }); cbor_map_options! { @@ -185,6 +202,7 @@ impl From for cbor::Value { 0x0E => firmware_version, 0x0F => max_cred_blob_length, 0x10 => max_rp_ids_for_set_min_pin_length, + 0x13 => certifications_cbor, 0x14 => remaining_discoverable_credentials, } } @@ -195,6 +213,8 @@ pub struct AuthenticatorClientPinResponse { pub key_agreement: Option, pub pin_token: Option>, pub retries: Option, + pub power_cycle_state: Option, + // - 0x05: uvRetries missing as we don't support internal UV. } impl From for cbor::Value { @@ -203,12 +223,14 @@ impl From for cbor::Value { key_agreement, pin_token, retries, + power_cycle_state, } = client_pin_response; cbor_map_options! { 0x01 => key_agreement.map(cbor::Value::from), 0x02 => pin_token, 0x03 => retries, + 0x04 => power_cycle_state, } } } @@ -401,6 +423,7 @@ mod test { firmware_version: None, max_cred_blob_length: None, max_rp_ids_for_set_min_pin_length: None, + certifications: None, remaining_discoverable_credentials: None, }; let response_cbor: Option = @@ -417,6 +440,8 @@ mod test { fn test_get_info_optionals_into_cbor() { let mut options_map = BTreeMap::new(); options_map.insert(String::from("rk"), true); + let mut certifications_map = BTreeMap::new(); + certifications_map.insert(String::from("example-cert"), 1); let get_info_response = AuthenticatorGetInfoResponse { versions: vec!["FIDO_2_0".to_string()], extensions: Some(vec!["extension".to_string()]), @@ -434,6 +459,7 @@ mod test { firmware_version: Some(0), max_cred_blob_length: Some(1024), max_rp_ids_for_set_min_pin_length: Some(8), + certifications: Some(certifications_map), remaining_discoverable_credentials: Some(150), }; let response_cbor: Option = @@ -455,6 +481,7 @@ mod test { 0x0E => 0, 0x0F => 1024, 0x10 => 8, + 0x13 => cbor_map! {"example-cert" => 1}, 0x14 => 150, }; assert_eq!(response_cbor, Some(expected_cbor)); @@ -462,15 +489,23 @@ mod test { #[test] fn test_used_client_pin_into_cbor() { + let mut rng = ThreadRng256 {}; + let sk = crypto::ecdh::SecKey::gensk(&mut rng); + let pk = sk.genpk(); + let cose_key = CoseKey::from(pk); let client_pin_response = AuthenticatorClientPinResponse { - key_agreement: None, + key_agreement: Some(cose_key.clone()), pin_token: Some(vec![70]), - retries: None, + retries: Some(8), + power_cycle_state: Some(false), }; let response_cbor: Option = ResponseData::AuthenticatorClientPin(Some(client_pin_response)).into(); let expected_cbor = cbor_map_options! { + 0x01 => cbor::Value::from(cose_key), 0x02 => vec![70], + 0x03 => 8, + 0x04 => false, }; assert_eq!(response_cbor, Some(expected_cbor)); }