command timeout for GetNextAssertion

This commit is contained in:
Fabian Kaczmarczyck
2020-11-26 14:40:02 +01:00
parent ffe19e152b
commit ed59ebac0d
6 changed files with 373 additions and 184 deletions

View File

@@ -427,7 +427,7 @@ mod test {
fn test_process_register() {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let application = [0x0A; 32];
let message = create_register_message(&application);
@@ -456,7 +456,7 @@ mod test {
fn test_process_register_bad_message() {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let application = [0x0A; 32];
let message = create_register_message(&application);
@@ -476,7 +476,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
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);
@@ -490,7 +490,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -508,7 +508,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -527,7 +527,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -553,7 +553,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -573,7 +573,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -593,7 +593,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -613,7 +613,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -640,7 +640,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -672,7 +672,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
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);
@@ -689,7 +689,7 @@ mod test {
let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
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);

View File

@@ -200,7 +200,8 @@ impl CtapHid {
// Each transaction is atomic, so we process the command directly here and
// don't handle any other packet in the meantime.
// TODO: Send keep-alive packets in the meantime.
let response = ctap_state.process_command(&message.payload, cid);
let response =
ctap_state.process_command(&message.payload, cid, clock_value);
if let Some(iterator) = CtapHid::split_message(Message {
cid,
cmd: CtapHid::COMMAND_CBOR,
@@ -520,7 +521,7 @@ mod test {
fn test_spurious_continuation_packet() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new();
let mut packet = [0x00; 64];
@@ -541,7 +542,7 @@ mod test {
fn test_command_init() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new();
let reply = process_messages(
@@ -586,7 +587,7 @@ mod test {
fn test_command_init_for_sync() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new();
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);
@@ -646,7 +647,7 @@ mod test {
fn test_command_ping() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new();
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);

View File

@@ -47,6 +47,7 @@ use self::response::{
};
use self::status_code::Ctap2StatusCode;
use self::storage::PersistentStore;
use self::timed_permission::TimedPermission;
#[cfg(feature = "with_ctap1")]
use self::timed_permission::U2fUserPresenceState;
use alloc::collections::BTreeMap;
@@ -65,7 +66,7 @@ use crypto::sha256::Sha256;
use crypto::Hash256;
#[cfg(feature = "debug_ctap")]
use libtock_drivers::console::Console;
use libtock_drivers::timer::{Duration, Timestamp};
use libtock_drivers::timer::{ClockValue, Duration};
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
// this setting. The basic attestation uses the signing key from key_material.rs
@@ -99,7 +100,8 @@ const ED_FLAG: u8 = 0x80;
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_MS: isize = 10000;
const RESET_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(10000);
const STATEFUL_COMMAND_TIMEOUT_DURATION: Duration<isize> = Duration::from_ms(30000);
pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0";
#[cfg(feature = "with_ctap1")]
@@ -140,6 +142,17 @@ struct AssertionInput {
hmac_secret_input: Option<GetAssertionHmacSecretInput>,
}
struct AssertionState {
assertion_input: AssertionInput,
// Sorted by ascending order of creation, so the last element is the most recent one.
next_credentials: Vec<PublicKeyCredentialSource>,
}
enum StatefulCommand {
Reset,
GetAssertion(AssertionState),
}
// This struct currently holds all state, not only the persistent memory. The persistent members are
// in the persistent store field.
pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>>
@@ -150,13 +163,11 @@ pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(
check_user_presence: CheckUserPresence,
persistent_store: PersistentStore,
pin_protocol_v1: PinProtocolV1,
// This variable will be irreversibly set to false RESET_TIMEOUT_MS milliseconds after boot.
accepts_reset: bool,
#[cfg(feature = "with_ctap1")]
pub u2f_up_state: U2fUserPresenceState,
// Sorted by ascending order of creation, so the last element is the most recent one.
next_credentials: Vec<PublicKeyCredentialSource>,
next_assertion_input: Option<AssertionInput>,
// The state initializes to Reset and its timeout, and never goes back to Reset.
stateful_command_permission: TimedPermission,
stateful_command_type: Option<StatefulCommand>,
}
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
@@ -169,6 +180,7 @@ where
pub fn new(
rng: &'a mut R,
check_user_presence: CheckUserPresence,
now: ClockValue,
) -> CtapState<'a, R, CheckUserPresence> {
let persistent_store = PersistentStore::new(rng);
let pin_protocol_v1 = PinProtocolV1::new(rng);
@@ -177,20 +189,26 @@ where
check_user_presence,
persistent_store,
pin_protocol_v1,
accepts_reset: true,
#[cfg(feature = "with_ctap1")]
u2f_up_state: U2fUserPresenceState::new(
U2F_UP_PROMPT_TIMEOUT,
Duration::from_ms(TOUCH_TIMEOUT_MS),
),
next_credentials: vec![],
next_assertion_input: None,
stateful_command_permission: TimedPermission::granted(now, RESET_TIMEOUT_DURATION),
stateful_command_type: Some(StatefulCommand::Reset),
}
}
pub fn check_disable_reset(&mut self, timestamp: Timestamp<isize>) {
if timestamp - Timestamp::<isize>::from_ms(0) > Duration::from_ms(RESET_TIMEOUT_MS) {
self.accepts_reset = false;
pub fn update_command_permission(&mut self, now: ClockValue) {
self.stateful_command_permission = self.stateful_command_permission.check_expiration(now);
}
fn check_command_permission(&mut self, now: ClockValue) -> Result<(), Ctap2StatusCode> {
self.stateful_command_permission = self.stateful_command_permission.check_expiration(now);
if self.stateful_command_permission.is_granted(now) {
Ok(())
} else {
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
}
}
@@ -306,7 +324,12 @@ where
}))
}
pub fn process_command(&mut self, command_cbor: &[u8], cid: ChannelID) -> Vec<u8> {
pub fn process_command(
&mut self,
command_cbor: &[u8],
cid: ChannelID,
now: ClockValue,
) -> Vec<u8> {
let cmd = Command::deserialize(command_cbor);
#[cfg(feature = "debug_ctap")]
writeln!(&mut Console::new(), "Received command: {:#?}", cmd).unwrap();
@@ -320,17 +343,32 @@ where
Duration::from_ms(TOUCH_TIMEOUT_MS),
);
}
match (&command, &self.stateful_command_type) {
(
Command::AuthenticatorGetNextAssertion,
Some(StatefulCommand::GetAssertion(_)),
) => (),
(Command::AuthenticatorReset, Some(StatefulCommand::Reset)) => (),
// GetInfo does not reset stateful commands.
(Command::AuthenticatorGetInfo, _) => (),
// AuthenticatorSelection does not reset stateful commands.
#[cfg(feature = "with_ctap2_1")]
(Command::AuthenticatorSelection, _) => (),
(_, _) => {
self.stateful_command_type = None;
}
}
let response = match command {
Command::AuthenticatorMakeCredential(params) => {
self.process_make_credential(params, cid)
}
Command::AuthenticatorGetAssertion(params) => {
self.process_get_assertion(params, cid)
self.process_get_assertion(params, cid, now)
}
Command::AuthenticatorGetNextAssertion => self.process_get_next_assertion(),
Command::AuthenticatorGetNextAssertion => self.process_get_next_assertion(now),
Command::AuthenticatorGetInfo => self.process_get_info(),
Command::AuthenticatorClientPin(params) => self.process_client_pin(params),
Command::AuthenticatorReset => self.process_reset(cid),
Command::AuthenticatorReset => self.process_reset(cid, now),
#[cfg(feature = "with_ctap2_1")]
Command::AuthenticatorSelection => self.process_selection(cid),
// TODO(kaczmarczyck) implement FIDO 2.1 commands
@@ -659,6 +697,7 @@ where
&mut self,
get_assertion_params: AuthenticatorGetAssertionParameters,
cid: ChannelID,
now: ClockValue,
) -> Result<ResponseData, Ctap2StatusCode> {
let AuthenticatorGetAssertionParameters {
rp_id,
@@ -745,7 +784,6 @@ where
let (credential, remaining_credentials) = credentials
.split_last()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)?;
self.next_credentials = remaining_credentials.to_vec();
self.increment_global_signature_counter()?;
@@ -754,29 +792,37 @@ where
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
hmac_secret_input,
};
let number_of_credentials = if self.next_credentials.is_empty() {
self.next_assertion_input = None;
let number_of_credentials = if remaining_credentials.is_empty() {
None
} else {
self.next_assertion_input = Some(assertion_input.clone());
Some(self.next_credentials.len() + 1)
self.stateful_command_permission =
TimedPermission::granted(now, STATEFUL_COMMAND_TIMEOUT_DURATION);
self.stateful_command_type = Some(StatefulCommand::GetAssertion(AssertionState {
assertion_input: assertion_input.clone(),
next_credentials: remaining_credentials.to_vec(),
}));
Some(remaining_credentials.len() + 1)
};
self.assertion_response(credential, assertion_input, number_of_credentials)
}
fn process_get_next_assertion(&mut self) -> Result<ResponseData, Ctap2StatusCode> {
// TODO(kaczmarczyck) introduce timer
let assertion_input = self
.next_assertion_input
.clone()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
let credential = self
.next_credentials
.pop()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
if self.next_credentials.is_empty() {
self.next_assertion_input = None;
};
fn process_get_next_assertion(
&mut self,
now: ClockValue,
) -> Result<ResponseData, Ctap2StatusCode> {
self.check_command_permission(now)?;
let (assertion_input, credential) =
if let Some(StatefulCommand::GetAssertion(assertion_state)) =
&mut self.stateful_command_type
{
let credential = assertion_state
.next_credentials
.pop()
.ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?;
(assertion_state.assertion_input.clone(), credential)
} else {
return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED);
};
self.assertion_response(&credential, assertion_input, None)
}
@@ -834,10 +880,17 @@ where
)
}
fn process_reset(&mut self, cid: ChannelID) -> Result<ResponseData, Ctap2StatusCode> {
fn process_reset(
&mut self,
cid: ChannelID,
now: ClockValue,
) -> Result<ResponseData, Ctap2StatusCode> {
// Resets are only possible in the first 10 seconds after booting.
if !self.accepts_reset {
return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED);
// TODO(kaczmarczyck) 2.1 allows Reset after Reset and 15 seconds?
self.check_command_permission(now)?;
match &self.stateful_command_type {
Some(StatefulCommand::Reset) => (),
_ => return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED),
}
(self.check_user_presence)(cid)?;
@@ -886,8 +939,11 @@ mod test {
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
};
use super::*;
use cbor::cbor_array;
use crypto::rng256::ThreadRng256;
const CLOCK_FREQUENCY_HZ: usize = 32768;
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
// The keep-alive logic in the processing of some commands needs a channel ID to send
// keep-alive packets to.
// In tests where we define a dummy user-presence check that immediately returns, the channel
@@ -898,8 +954,8 @@ mod test {
fn test_get_info() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
#[cfg(feature = "with_ctap2_1")]
let mut expected_response = vec![0x00, 0xAA, 0x01];
@@ -955,7 +1011,7 @@ mod test {
rp_icon: None,
};
let user = PublicKeyCredentialUserEntity {
user_id: vec![0xFA, 0xB1, 0xA2],
user_id: vec![0x1D],
user_name: None,
user_display_name: None,
user_icon: None,
@@ -1008,7 +1064,7 @@ mod test {
fn test_residential_process_make_credential() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let make_credential_params = create_minimal_make_credential_parameters();
let make_credential_response =
@@ -1044,7 +1100,7 @@ mod test {
fn test_non_residential_process_make_credential() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.options.rk = false;
@@ -1081,7 +1137,7 @@ mod test {
fn test_process_make_credential_unsupported_algorithm() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.pub_key_cred_params = vec![];
@@ -1099,7 +1155,7 @@ mod test {
let mut rng = ThreadRng256 {};
let excluded_private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let excluded_credential_id = vec![0x01, 0x23, 0x45, 0x67];
let make_credential_params =
@@ -1132,7 +1188,7 @@ mod test {
fn test_process_make_credential_credential_with_cred_protect() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let test_policy = CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList;
let make_credential_params =
@@ -1186,7 +1242,7 @@ mod test {
fn test_process_make_credential_hmac_secret() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let extensions = Some(MakeCredentialExtensions {
hmac_secret: true,
@@ -1236,7 +1292,7 @@ mod test {
fn test_process_make_credential_hmac_secret_resident_key() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let extensions = Some(MakeCredentialExtensions {
hmac_secret: true,
@@ -1285,7 +1341,8 @@ mod test {
fn test_process_make_credential_cancelled() {
let mut rng = ThreadRng256 {};
let user_presence_always_cancel = |_| Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL);
let mut ctap_state = CtapState::new(&mut rng, user_presence_always_cancel);
let mut ctap_state =
CtapState::new(&mut rng, user_presence_always_cancel, DUMMY_CLOCK_VALUE);
let make_credential_params = create_minimal_make_credential_parameters();
let make_credential_response =
@@ -1297,11 +1354,43 @@ mod test {
);
}
fn check_assertion_response(
response: Result<ResponseData, Ctap2StatusCode>,
expected_user_id: Vec<u8>,
expected_number_of_credentials: Option<u64>,
) {
match response.unwrap() {
ResponseData::AuthenticatorGetAssertion(get_assertion_response) => {
let AuthenticatorGetAssertionResponse {
auth_data,
user,
number_of_credentials,
..
} = get_assertion_response;
let expected_auth_data = vec![
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x00, 0x00, 0x00, 0x00, 0x01,
];
assert_eq!(auth_data, expected_auth_data);
let expected_user = PublicKeyCredentialUserEntity {
user_id: expected_user_id,
user_name: None,
user_display_name: None,
user_icon: None,
};
assert_eq!(user, Some(expected_user));
assert_eq!(number_of_credentials, expected_number_of_credentials);
}
_ => panic!("Invalid response type"),
}
}
#[test]
fn test_residential_process_get_assertion() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let make_credential_params = create_minimal_make_credential_parameters();
assert!(ctap_state
@@ -1320,34 +1409,12 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
match get_assertion_response.unwrap() {
ResponseData::AuthenticatorGetAssertion(get_assertion_response) => {
let AuthenticatorGetAssertionResponse {
auth_data,
user,
number_of_credentials,
..
} = get_assertion_response;
let expected_auth_data = vec![
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x00, 0x00, 0x00, 0x00, 0x01,
];
assert_eq!(auth_data, expected_auth_data);
let expected_user = PublicKeyCredentialUserEntity {
user_id: vec![0xFA, 0xB1, 0xA2],
user_name: None,
user_display_name: None,
user_icon: None,
};
assert_eq!(user, Some(expected_user));
assert!(number_of_credentials.is_none());
}
_ => panic!("Invalid response type"),
}
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
check_assertion_response(get_assertion_response, vec![0x1D], None);
}
#[test]
@@ -1355,7 +1422,7 @@ mod test {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let make_extensions = Some(MakeCredentialExtensions {
hmac_secret: true,
@@ -1405,8 +1472,11 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert_eq!(
get_assertion_response,
@@ -1419,7 +1489,7 @@ mod test {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let make_extensions = Some(MakeCredentialExtensions {
hmac_secret: true,
@@ -1453,8 +1523,11 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert_eq!(
get_assertion_response,
@@ -1468,7 +1541,7 @@ mod test {
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let credential_id = rng.gen_uniform_u8x32().to_vec();
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
@@ -1480,7 +1553,7 @@ mod test {
credential_id: credential_id.clone(),
private_key: private_key.clone(),
rp_id: String::from("example.com"),
user_handle: vec![0x00],
user_handle: vec![0x1D],
other_ui: None,
cred_random: None,
cred_protect_policy: Some(
@@ -1505,8 +1578,11 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS),
@@ -1524,16 +1600,19 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
assert!(get_assertion_response.is_ok());
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
check_assertion_response(get_assertion_response, vec![0x1D], None);
let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
credential_id,
private_key,
rp_id: String::from("example.com"),
user_handle: vec![0x00],
user_handle: vec![0x1D],
other_ui: None,
cred_random: None,
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
@@ -1556,8 +1635,11 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS),
@@ -1565,10 +1647,10 @@ mod test {
}
#[test]
fn test_process_get_next_assertion() {
fn test_process_get_next_assertion_two_credentials() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.user.user_id = vec![0x01];
@@ -1593,63 +1675,135 @@ mod test {
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response =
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
check_assertion_response(get_assertion_response, vec![0x02], Some(2));
match get_assertion_response.unwrap() {
ResponseData::AuthenticatorGetAssertion(get_assertion_response) => {
let AuthenticatorGetAssertionResponse {
auth_data,
user,
number_of_credentials,
..
} = get_assertion_response;
let expected_auth_data = vec![
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x00, 0x00, 0x00, 0x00, 0x01,
];
assert_eq!(auth_data, expected_auth_data);
let expected_user = PublicKeyCredentialUserEntity {
user_id: vec![0x02],
user_name: None,
user_display_name: None,
user_icon: None,
};
assert_eq!(user, Some(expected_user));
assert_eq!(number_of_credentials, Some(2));
}
_ => panic!("Invalid response type"),
}
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
check_assertion_response(get_assertion_response, vec![0x01], None);
let get_assertion_response = ctap_state.process_get_next_assertion();
match get_assertion_response.unwrap() {
ResponseData::AuthenticatorGetAssertion(get_assertion_response) => {
let AuthenticatorGetAssertionResponse {
auth_data,
user,
number_of_credentials,
..
} = get_assertion_response;
let expected_auth_data = vec![
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x00, 0x00, 0x00, 0x00, 0x01,
];
assert_eq!(auth_data, expected_auth_data);
let expected_user = PublicKeyCredentialUserEntity {
user_id: vec![0x01],
user_name: None,
user_display_name: None,
user_icon: None,
};
assert_eq!(user, Some(expected_user));
assert!(number_of_credentials.is_none());
}
_ => panic!("Invalid response type"),
}
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
);
}
let get_assertion_response = ctap_state.process_get_next_assertion();
#[test]
fn test_process_get_next_assertion_three_credentials() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.user.user_id = vec![0x01];
assert!(ctap_state
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.user.user_id = vec![0x02];
assert!(ctap_state
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.user.user_id = vec![0x03];
assert!(ctap_state
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: None,
extensions: None,
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
check_assertion_response(get_assertion_response, vec![0x03], Some(3));
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
check_assertion_response(get_assertion_response, vec![0x02], None);
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
check_assertion_response(get_assertion_response, vec![0x01], None);
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
);
}
#[test]
fn test_process_get_next_assertion_not_allowed() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
);
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.user.user_id = vec![0x01];
assert!(ctap_state
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.user.user_id = vec![0x02];
assert!(ctap_state
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: None,
extensions: None,
options: GetAssertionOptions {
up: false,
uv: false,
},
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
};
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert!(get_assertion_response.is_ok());
// This is a MakeCredential command.
let mut command_cbor = vec![0x01];
let cbor_value = cbor_map! {
1 => vec![0xCD; 16],
2 => cbor_map! {
"id" => "example.com",
},
3 => cbor_map! {
"id" => vec![0x1D, 0x1D, 0x1D, 0x1D],
},
4 => cbor_array![ES256_CRED_PARAM],
};
assert!(cbor::write(cbor_value, &mut command_cbor));
ctap_state.process_command(&command_cbor, DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
@@ -1661,7 +1815,7 @@ mod test {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let credential_id = vec![0x01, 0x23, 0x45, 0x67];
let credential_source = PublicKeyCredentialSource {
@@ -1681,7 +1835,8 @@ mod test {
.is_ok());
assert!(ctap_state.persistent_store.count_credentials().unwrap() > 0);
let reset_reponse = ctap_state.process_command(&[0x07], DUMMY_CHANNEL_ID);
let reset_reponse =
ctap_state.process_command(&[0x07], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
let expected_response = vec![0x00];
assert_eq!(reset_reponse, expected_response);
assert!(ctap_state.persistent_store.count_credentials().unwrap() == 0);
@@ -1691,9 +1846,10 @@ mod test {
fn test_process_reset_cancelled() {
let mut rng = ThreadRng256 {};
let user_presence_always_cancel = |_| Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL);
let mut ctap_state = CtapState::new(&mut rng, user_presence_always_cancel);
let mut ctap_state =
CtapState::new(&mut rng, user_presence_always_cancel, DUMMY_CLOCK_VALUE);
let reset_reponse = ctap_state.process_reset(DUMMY_CHANNEL_ID);
let reset_reponse = ctap_state.process_reset(DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
assert_eq!(
reset_reponse,
@@ -1701,14 +1857,28 @@ mod test {
);
}
#[test]
fn test_process_reset_not_first() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// This is a GetNextAssertion command.
ctap_state.process_command(&[0x08], DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
let reset_reponse = ctap_state.process_reset(DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
assert_eq!(reset_reponse, Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED));
}
#[test]
fn test_process_unknown_command() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
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);
let reset_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);
}
@@ -1718,7 +1888,7 @@ mod test {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// Usually, the relying party ID or its hash is provided by the client.
// We are not testing the correctness of our SHA256 here, only if it is checked.
@@ -1739,7 +1909,7 @@ mod test {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// Usually, the relying party ID or its hash is provided by the client.
// We are not testing the correctness of our SHA256 here, only if it is checked.
@@ -1762,7 +1932,7 @@ mod test {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// Same as above.
let rp_id_hash = [0x55; 32];
@@ -1784,7 +1954,7 @@ mod test {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
// Same as above.
let rp_id_hash = [0x55; 32];

View File

@@ -746,6 +746,21 @@ mod test {
assert!(persistent_store.count_credentials().unwrap() > 0);
}
#[test]
fn test_credential_order() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let credential_source = create_credential_source(&mut rng, "example.com", vec![]);
let current_latest_creation = credential_source.creation_order;
assert!(persistent_store.store_credential(credential_source).is_ok());
let mut credential_source = create_credential_source(&mut rng, "example.com", vec![]);
credential_source.creation_order = persistent_store.new_creation_order().unwrap();
assert!(credential_source.creation_order > current_latest_creation);
let current_latest_creation = credential_source.creation_order;
assert!(persistent_store.store_credential(credential_source).is_ok());
assert!(persistent_store.new_creation_order().unwrap() > current_latest_creation);
}
#[test]
#[allow(clippy::assertions_on_constants)]
fn test_fill_store() {