adds the command logic for credential management

This commit is contained in:
Fabian Kaczmarczyck
2021-01-13 11:22:41 +01:00
parent 46b9a0262c
commit c6726660ac
7 changed files with 1366 additions and 42 deletions

View File

@@ -14,6 +14,7 @@
pub mod apdu;
pub mod command;
mod credential_management;
#[cfg(feature = "with_ctap1")]
mod ctap1;
pub mod data_formats;
@@ -30,6 +31,7 @@ use self::command::{
AuthenticatorMakeCredentialParameters, AuthenticatorVendorConfigureParameters, Command,
MAX_CREDENTIAL_COUNT_IN_LIST,
};
use self::credential_management::process_credential_management;
use self::data_formats::{
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, GetAssertionHmacSecretInput,
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
@@ -98,7 +100,7 @@ pub const TOUCH_TIMEOUT_MS: isize = 30000;
#[cfg(feature = "with_ctap1")]
const U2F_UP_PROMPT_TIMEOUT: Duration<isize> = Duration::from_ms(10000);
const RESET_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(10000);
const STATEFUL_COMMAND_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(30000);
pub const STATEFUL_COMMAND_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(30000);
pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0";
#[cfg(feature = "with_ctap1")]
@@ -139,15 +141,29 @@ struct AssertionInput {
has_uv: bool,
}
struct AssertionState {
pub struct AssertionState {
assertion_input: AssertionInput,
// Sorted by ascending order of creation, so the last element is the most recent one.
next_credential_keys: Vec<usize>,
}
enum StatefulCommand {
pub enum StatefulCommand {
Reset,
GetAssertion(AssertionState),
EnumerateRps(Vec<String>),
EnumerateCredentials(Vec<usize>),
}
pub fn check_command_permission(
stateful_command_permission: &mut TimedPermission,
now: ClockValue,
) -> Result<(), Ctap2StatusCode> {
*stateful_command_permission = stateful_command_permission.check_expiration(now);
if stateful_command_permission.is_granted(now) {
Ok(())
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}
// This struct currently holds all state, not only the persistent memory. The persistent members are
@@ -200,15 +216,6 @@ where
self.stateful_command_permission = self.stateful_command_permission.check_expiration(now);
}
fn check_command_permission(&mut self, now: ClockValue) -> Result<(), Ctap2StatusCode> {
self.update_command_permission(now);
if self.stateful_command_permission.is_granted(now) {
Ok(())
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}
pub fn increment_global_signature_counter(&mut self) -> Result<(), Ctap2StatusCode> {
if USE_SIGNATURE_COUNTER {
let increment = self.rng.gen_uniform_u32x8()[0] % 8 + 1;
@@ -330,6 +337,14 @@ where
Command::AuthenticatorGetNextAssertion,
Some(StatefulCommand::GetAssertion(_)),
) => (),
(
Command::AuthenticatorCredentialManagement(_),
Some(StatefulCommand::EnumerateRps(_)),
) => (),
(
Command::AuthenticatorCredentialManagement(_),
Some(StatefulCommand::EnumerateCredentials(_)),
) => (),
(Command::AuthenticatorReset, Some(StatefulCommand::Reset)) => (),
// GetInfo does not reset stateful commands.
(Command::AuthenticatorGetInfo, _) => (),
@@ -350,6 +365,16 @@ where
Command::AuthenticatorGetInfo => self.process_get_info(),
Command::AuthenticatorClientPin(params) => self.process_client_pin(params),
Command::AuthenticatorReset => self.process_reset(cid, now),
Command::AuthenticatorCredentialManagement(params) => {
process_credential_management(
&mut self.persistent_store,
&mut self.stateful_command_permission,
&mut self.stateful_command_type,
&mut self.pin_protocol_v1,
params,
now,
)
}
Command::AuthenticatorSelection => self.process_selection(cid),
// TODO(kaczmarczyck) implement FIDO 2.1 commands
// Vendor specific commands
@@ -818,7 +843,7 @@ where
&mut self,
now: ClockValue,
) -> Result<ResponseData, Ctap2StatusCode> {
self.check_command_permission(now)?;
check_command_permission(&mut self.stateful_command_permission, now)?;
let (assertion_input, credential) =
if let Some(StatefulCommand::GetAssertion(assertion_state)) =
&mut self.stateful_command_type
@@ -844,6 +869,7 @@ where
String::from("clientPin"),
self.persistent_store.pin_hash()?.is_some(),
);
options_map.insert(String::from("credMgmt"), true);
Ok(ResponseData::AuthenticatorGetInfo(
AuthenticatorGetInfoResponse {
versions: vec![
@@ -895,7 +921,7 @@ where
) -> Result<ResponseData, Ctap2StatusCode> {
// Resets are only possible in the first 10 seconds after booting.
// TODO(kaczmarczyck) 2.1 allows Reset after Reset and 15 seconds?
self.check_command_permission(now)?;
check_command_permission(&mut self.stateful_command_permission, now)?;
match &self.stateful_command_type {
Some(StatefulCommand::Reset) => (),
_ => return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED),
@@ -1053,11 +1079,12 @@ mod test {
expected_response.extend(&ctap_state.persistent_store.aaguid().unwrap());
expected_response.extend(
[
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
0x08, 0x18, 0x70, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61,
0x6C, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69,
0x63, 0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04, 0x14, 0x18, 0x96,
0x04, 0xA4, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x68, 0x63, 0x72, 0x65,
0x64, 0x4D, 0x67, 0x6D, 0x74, 0xF5, 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50,
0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01, 0x08, 0x18, 0x70, 0x09,
0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C, 0x67, 0x26, 0x64,
0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65,
0x79, 0x0D, 0x04, 0x14, 0x18, 0x96,
]
.iter(),
);
@@ -2034,6 +2061,22 @@ mod test {
assert_eq!(reset_reponse, Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED));
}
#[test]
fn test_process_credential_management_unknown_subcommand() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// The subcommand 0xEE does not exist.
let reponse = ctap_state.process_command(
&[0x0A, 0xA1, 0x01, 0x18, 0xEE],
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
let expected_response = vec![Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND as u8];
assert_eq!(reponse, expected_response);
}
#[test]
fn test_process_unknown_command() {
let mut rng = ThreadRng256 {};
@@ -2041,10 +2084,9 @@ mod test {
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// This command does not exist.
let reset_reponse =
ctap_state.process_command(&[0xDF], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
let reponse = ctap_state.process_command(&[0xDF], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
let expected_response = vec![Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND as u8];
assert_eq!(reset_reponse, expected_response);
assert_eq!(reponse, expected_response);
}
#[test]