From e3353cb232e2892de0df43e6a04a8d4a2d376b6c Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Tue, 19 Jan 2021 12:42:41 +0100 Subject: [PATCH] only stores the RP ID index as state --- src/ctap/credential_management.rs | 128 ++++++++++++++++++++++++++---- src/ctap/mod.rs | 2 +- 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/src/ctap/credential_management.rs b/src/ctap/credential_management.rs index 71c1e39..dba7d36 100644 --- a/src/ctap/credential_management.rs +++ b/src/ctap/credential_management.rs @@ -31,11 +31,23 @@ use alloc::collections::BTreeSet; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use core::iter::FromIterator; use crypto::sha256::Sha256; use crypto::Hash256; use libtock_drivers::timer::ClockValue; +/// Generates a set with all existing RP IDs. +fn get_stored_rp_ids( + persistent_store: &PersistentStore, +) -> Result, Ctap2StatusCode> { + let mut rp_set = BTreeSet::new(); + let mut iter_result = Ok(()); + for (_, credential) in persistent_store.iter_credentials(&mut iter_result)? { + rp_set.insert(credential.rp_id); + } + iter_result?; + Ok(rp_set) +} + /// Generates the response for subcommands enumerating RPs. fn enumerate_rps_response( rp_id: Option, @@ -117,34 +129,35 @@ fn process_enumerate_rps_begin( stateful_command_type: &mut Option, now: ClockValue, ) -> Result { - let mut rp_set = BTreeSet::new(); - let mut iter_result = Ok(()); - for (_, credential) in persistent_store.iter_credentials(&mut iter_result)? { - rp_set.insert(credential.rp_id); - } - iter_result?; - let mut rp_ids = Vec::from_iter(rp_set); - let total_rps = rp_ids.len(); + let rp_set = get_stored_rp_ids(persistent_store)?; + let total_rps = rp_set.len(); - // TODO(kaczmarczyck) behaviour with empty list? - let rp_id = rp_ids.pop(); + // TODO(kaczmarczyck) should we return CTAP2_ERR_NO_CREDENTIALS if empty? if total_rps > 1 { *stateful_command_permission = TimedPermission::granted(now, STATEFUL_COMMAND_TIMEOUT_DURATION); - *stateful_command_type = Some(StatefulCommand::EnumerateRps(rp_ids)); + *stateful_command_type = Some(StatefulCommand::EnumerateRps(1)); } - enumerate_rps_response(rp_id, Some(total_rps as u64)) + // TODO https://github.com/rust-lang/rust/issues/62924 replace with pop_first() + enumerate_rps_response(rp_set.into_iter().next(), Some(total_rps as u64)) } /// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement. fn process_enumerate_rps_get_next_rp( + persistent_store: &PersistentStore, stateful_command_permission: &mut TimedPermission, stateful_command_type: &mut Option, now: ClockValue, ) -> Result { check_command_permission(stateful_command_permission, now)?; - if let Some(StatefulCommand::EnumerateRps(rp_ids)) = stateful_command_type { - let rp_id = rp_ids.pop().ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?; + if let Some(StatefulCommand::EnumerateRps(rp_id_index)) = stateful_command_type { + let rp_set = get_stored_rp_ids(persistent_store)?; + // A BTreeSet is already sorted. + let rp_id = rp_set + .into_iter() + .nth(*rp_id_index) + .ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?; + *stateful_command_type = Some(StatefulCommand::EnumerateRps(*rp_id_index + 1)); enumerate_rps_response(Some(rp_id), None) } else { Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED) @@ -305,6 +318,7 @@ pub fn process_credential_management( )?), CredentialManagementSubCommand::EnumerateRpsGetNextRp => { Some(process_enumerate_rps_get_next_rp( + persistent_store, stateful_command_permission, stateful_command_type, now, @@ -544,6 +558,90 @@ mod test { ); } + #[test] + fn test_process_enumerate_rps_completeness() { + let mut rng = ThreadRng256 {}; + let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng); + let pin_uv_auth_token = [0x55; 32]; + let pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token); + let credential_source = create_credential_source(&mut rng); + + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE); + ctap_state.pin_protocol_v1 = pin_protocol_v1; + + const NUM_CREDENTIALS: usize = 20; + for i in 0..NUM_CREDENTIALS { + let mut credential = credential_source.clone(); + credential.rp_id = i.to_string(); + ctap_state + .persistent_store + .store_credential(credential) + .unwrap(); + } + + ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap(); + let pin_auth = Some(vec![ + 0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9, + 0xD0, 0xD1, + ]); + + let mut rp_set = BTreeSet::new(); + // This mut is just to make the test code shorter. + // The command is different on the first loop iteration. + let mut cred_management_params = AuthenticatorCredentialManagementParameters { + sub_command: CredentialManagementSubCommand::EnumerateRpsBegin, + sub_command_params: None, + pin_protocol: Some(1), + pin_auth, + }; + + for _ in 0..NUM_CREDENTIALS { + let cred_management_response = process_credential_management( + &mut ctap_state.persistent_store, + &mut ctap_state.stateful_command_permission, + &mut ctap_state.stateful_command_type, + &mut ctap_state.pin_protocol_v1, + cred_management_params, + DUMMY_CLOCK_VALUE, + ); + match cred_management_response.unwrap() { + ResponseData::AuthenticatorCredentialManagement(Some(response)) => { + if rp_set.is_empty() { + assert_eq!(response.total_rps, Some(NUM_CREDENTIALS as u64)); + } else { + assert_eq!(response.total_rps, None); + } + let rp_id = response.rp.unwrap().rp_id; + let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + assert_eq!(rp_id_hash, response.rp_id_hash.unwrap().as_slice()); + assert!(!rp_set.contains(&rp_id)); + rp_set.insert(rp_id); + } + _ => panic!("Invalid response type"), + }; + cred_management_params = AuthenticatorCredentialManagementParameters { + sub_command: CredentialManagementSubCommand::EnumerateRpsGetNextRp, + sub_command_params: None, + pin_protocol: None, + pin_auth: None, + }; + } + + let cred_management_response = process_credential_management( + &mut ctap_state.persistent_store, + &mut ctap_state.stateful_command_permission, + &mut ctap_state.stateful_command_type, + &mut ctap_state.pin_protocol_v1, + cred_management_params, + DUMMY_CLOCK_VALUE, + ); + assert_eq!( + cred_management_response, + Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED) + ); + } + #[test] fn test_process_enumerate_credentials_with_uv() { let mut rng = ThreadRng256 {}; diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 96dbd41..49d14f4 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -165,7 +165,7 @@ pub struct AssertionState { pub enum StatefulCommand { Reset, GetAssertion(AssertionState), - EnumerateRps(Vec), + EnumerateRps(usize), EnumerateCredentials(Vec), }