stricter API for StatefulCommandPermission

This commit is contained in:
Fabian Kaczmarczyck
2021-01-20 12:08:07 +01:00
parent 9953b3f1a0
commit 9296f51e19
2 changed files with 79 additions and 40 deletions

View File

@@ -140,18 +140,14 @@ fn process_enumerate_rps_get_next_rp(
persistent_store: &PersistentStore, persistent_store: &PersistentStore,
stateful_command_permission: &mut StatefulPermission, stateful_command_permission: &mut StatefulPermission,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> { ) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
if let StatefulCommand::EnumerateRps(rp_id_index) = stateful_command_permission.get_command()? { let rp_id_index = stateful_command_permission.next_enumerate_rp()?;
let rp_set = get_stored_rp_ids(persistent_store)?; let rp_set = get_stored_rp_ids(persistent_store)?;
// A BTreeSet is already sorted. // A BTreeSet is already sorted.
let rp_id = rp_set let rp_id = rp_set
.into_iter() .into_iter()
.nth(*rp_id_index) .nth(rp_id_index)
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?; .ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
*rp_id_index += 1; enumerate_rps_response(Some(rp_id), None)
enumerate_rps_response(Some(rp_id), None)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
} }
/// Processes the subcommand enumerateCredentialsBegin for CredentialManagement. /// Processes the subcommand enumerateCredentialsBegin for CredentialManagement.
@@ -194,17 +190,9 @@ fn process_enumerate_credentials_get_next_credential(
persistent_store: &PersistentStore, persistent_store: &PersistentStore,
stateful_command_permission: &mut StatefulPermission, stateful_command_permission: &mut StatefulPermission,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> { ) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
if let StatefulCommand::EnumerateCredentials(rp_credentials) = let credential_key = stateful_command_permission.next_enumerate_credential()?;
stateful_command_permission.get_command()? let credential = persistent_store.get_credential(credential_key)?;
{ enumerate_credentials_response(credential, None)
let current_key = rp_credentials
.pop()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
let credential = persistent_store.get_credential(current_key)?;
enumerate_credentials_response(credential, None)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
} }
/// Processes the subcommand deleteCredential for CredentialManagement. /// Processes the subcommand deleteCredential for CredentialManagement.

View File

@@ -149,20 +149,23 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
} }
} }
/// Holds data necessary to sign an assertion for a credential.
#[derive(Clone)] #[derive(Clone)]
struct AssertionInput { pub struct AssertionInput {
client_data_hash: Vec<u8>, client_data_hash: Vec<u8>,
auth_data: Vec<u8>, auth_data: Vec<u8>,
hmac_secret_input: Option<GetAssertionHmacSecretInput>, hmac_secret_input: Option<GetAssertionHmacSecretInput>,
has_uv: bool, has_uv: bool,
} }
/// Contains the state we need to store for GetNextAssertion.
pub struct AssertionState { pub struct AssertionState {
assertion_input: AssertionInput, assertion_input: AssertionInput,
// Sorted by ascending order of creation, so the last element is the most recent one. // Sorted by ascending order of creation, so the last element is the most recent one.
next_credential_keys: Vec<usize>, next_credential_keys: Vec<usize>,
} }
/// Stores which command currently holds state for subsequent calls.
pub enum StatefulCommand { pub enum StatefulCommand {
Reset, Reset,
GetAssertion(AssertionState), GetAssertion(AssertionState),
@@ -170,14 +173,25 @@ pub enum StatefulCommand {
EnumerateCredentials(Vec<usize>), EnumerateCredentials(Vec<usize>),
} }
/// Stores the current CTAP command state and when it times out.
///
/// Some commands are executed in a series of calls to the authenticator.
/// Interleaving calls to other commands interrupt the current command and
/// remove all state and permissions. Power cycling allows the Reset command,
/// and to prevent misuse or accidents, we disallow Reset after receiving
/// different commands. Therefore, Reset behaves just like all other stateful
/// commands and is included here. Please not that the allowed time for Reset
/// differs from all other stateful commands.
pub struct StatefulPermission { pub struct StatefulPermission {
permission: TimedPermission, permission: TimedPermission,
command_type: Option<StatefulCommand>, command_type: Option<StatefulCommand>,
} }
impl StatefulPermission { impl StatefulPermission {
// Resets are only possible in the first 10 seconds after booting. /// Creates the command state at device startup.
// Therefore, initialization includes allowing Reset. ///
/// Resets are only possible after a power cycle. Therefore, initialization
/// means allowing Reset, and Reset cannot be granted later.
pub fn new_reset(now: ClockValue) -> StatefulPermission { pub fn new_reset(now: ClockValue) -> StatefulPermission {
StatefulPermission { StatefulPermission {
permission: TimedPermission::granted(now, RESET_TIMEOUT_DURATION), permission: TimedPermission::granted(now, RESET_TIMEOUT_DURATION),
@@ -185,11 +199,13 @@ impl StatefulPermission {
} }
} }
/// Clears all permissions and state.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.permission = TimedPermission::waiting(); self.permission = TimedPermission::waiting();
self.command_type = None; self.command_type = None;
} }
/// Checks the permission timeout.
pub fn check_command_permission(&mut self, now: ClockValue) -> Result<(), Ctap2StatusCode> { pub fn check_command_permission(&mut self, now: ClockValue) -> Result<(), Ctap2StatusCode> {
if self.permission.is_granted(now) { if self.permission.is_granted(now) {
Ok(()) Ok(())
@@ -199,12 +215,14 @@ impl StatefulPermission {
} }
} }
pub fn get_command(&mut self) -> Result<&mut StatefulCommand, Ctap2StatusCode> { /// Gets a reference to the current command state, if any exists.
pub fn get_command(&self) -> Result<&StatefulCommand, Ctap2StatusCode> {
self.command_type self.command_type
.as_mut() .as_ref()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED) .ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
} }
/// Sets a new command state, and starts a new clock for timeouts.
pub fn set_command(&mut self, now: ClockValue, new_command_type: StatefulCommand) { pub fn set_command(&mut self, now: ClockValue, new_command_type: StatefulCommand) {
match &new_command_type { match &new_command_type {
// Reset is only allowed after a power cycle. // Reset is only allowed after a power cycle.
@@ -215,6 +233,47 @@ impl StatefulPermission {
} }
} }
} }
/// Returns the state for the next assertion and advances it.
///
/// The state includes all information from GetAssertion and the storage key
/// to the next credential that needs to be processed.
pub fn next_assertion_credential(
&mut self,
) -> Result<(AssertionInput, usize), Ctap2StatusCode> {
if let Some(StatefulCommand::GetAssertion(assertion_state)) = &mut self.command_type {
let credential_key = assertion_state
.next_credential_keys
.pop()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
Ok((assertion_state.assertion_input.clone(), credential_key))
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}
/// Returns the index to the next RP ID for enumeration and advances it.
pub fn next_enumerate_rp(&mut self) -> Result<usize, Ctap2StatusCode> {
if let Some(StatefulCommand::EnumerateRps(rp_id_index)) = &mut self.command_type {
let current_index = *rp_id_index;
*rp_id_index += 1;
Ok(current_index)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}
/// Returns the next storage credential key for enumeration and advances it.
pub fn next_enumerate_credential(&mut self) -> Result<usize, Ctap2StatusCode> {
if let Some(StatefulCommand::EnumerateCredentials(rp_credentials)) = &mut self.command_type
{
rp_credentials
.pop()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}
} }
// This struct currently holds all state, not only the persistent memory. The persistent members are // This struct currently holds all state, not only the persistent memory. The persistent members are
@@ -904,18 +963,10 @@ where
) -> Result<ResponseData, Ctap2StatusCode> { ) -> Result<ResponseData, Ctap2StatusCode> {
self.stateful_command_permission self.stateful_command_permission
.check_command_permission(now)?; .check_command_permission(now)?;
let (assertion_input, credential) = if let StatefulCommand::GetAssertion(assertion_state) = let (assertion_input, credential_key) = self
self.stateful_command_permission.get_command()? .stateful_command_permission
{ .next_assertion_credential()?;
let credential_key = assertion_state let credential = self.persistent_store.get_credential(credential_key)?;
.next_credential_keys
.pop()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
let credential = self.persistent_store.get_credential(credential_key)?;
(assertion_state.assertion_input.clone(), credential)
} else {
return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED);
};
self.assertion_response(credential, assertion_input, None) self.assertion_response(credential, assertion_input, None)
} }