command timeout for GetNextAssertion
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
490
src/ctap/mod.rs
490
src/ctap/mod.rs
@@ -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];
|
||||
|
||||
@@ -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() {
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -37,9 +37,11 @@ use libtock_drivers::console::Console;
|
||||
use libtock_drivers::led;
|
||||
use libtock_drivers::result::{FlexUnwrap, TockError};
|
||||
use libtock_drivers::timer;
|
||||
use libtock_drivers::timer::Duration;
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use libtock_drivers::timer::Timer;
|
||||
use libtock_drivers::timer::{Duration, Timestamp};
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use libtock_drivers::timer::Timestamp;
|
||||
use libtock_drivers::usb_ctap_hid;
|
||||
|
||||
const KEEPALIVE_DELAY_MS: isize = 100;
|
||||
@@ -57,12 +59,13 @@ fn main() {
|
||||
panic!("Cannot setup USB driver");
|
||||
}
|
||||
|
||||
let boot_time = timer.get_current_clock().flex_unwrap();
|
||||
let mut rng = TockRng256 {};
|
||||
let mut ctap_state = CtapState::new(&mut rng, check_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, check_user_presence, boot_time);
|
||||
let mut ctap_hid = CtapHid::new();
|
||||
|
||||
let mut led_counter = 0;
|
||||
let mut last_led_increment = timer.get_current_clock().flex_unwrap();
|
||||
let mut last_led_increment = boot_time;
|
||||
|
||||
// Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting.
|
||||
// The way TockOS and apps currently interact, callbacks need a yield syscall to execute,
|
||||
@@ -115,7 +118,7 @@ fn main() {
|
||||
|
||||
// These calls are making sure that even for long inactivity, wrapping clock values
|
||||
// never randomly wink or grant user presence for U2F.
|
||||
ctap_state.check_disable_reset(Timestamp::<isize>::from_clock_value(now));
|
||||
ctap_state.update_command_permission(now);
|
||||
ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now);
|
||||
|
||||
if has_packet {
|
||||
|
||||
Reference in New Issue
Block a user