Clock trait (#596)

* adds generic Env parameters

* adds Clock type to Env

* use new Clock

* TockTimer improvements

* new Clock interface

* addressed comments

* renames constants to milliseconds, other style fixes

* removes all cargo fmt artifacts
This commit is contained in:
kaczmarczyck
2023-02-28 17:35:42 +01:00
committed by GitHub
parent 963549f9bb
commit 73c60d8740
31 changed files with 1163 additions and 1463 deletions

73
Cargo.lock generated
View File

@@ -105,7 +105,6 @@ dependencies = [
"byteorder",
"crypto",
"ed25519-compact",
"embedded-time",
"enum-iterator",
"lang_items",
"libtock_core",
@@ -136,15 +135,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee9df587982575886a8682edcee11877894349a805f25629c27f63abe3e9ae8"
[[package]]
name = "embedded-time"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58"
dependencies = [
"num",
]
[[package]]
name = "enum-iterator"
version = "0.6.0"
@@ -277,69 +267,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "num"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg 1.1.0",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg 1.1.0",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "once_cell"
version = "1.14.0"

View File

@@ -20,7 +20,6 @@ persistent_store = { path = "libraries/persistent_store" }
byteorder = { version = "1", default-features = false }
arrayref = "0.3.6"
subtle = { version = "2.2", default-features = false, features = ["nightly"] }
embedded-time = "0.12.1"
arbitrary = { version = "0.4.7", features = ["derive"], optional = true }
rand = { version = "0.8.4", optional = true }
ed25519-compact = { version = "1", default-features = false, optional = true }

1
fuzz/Cargo.lock generated
View File

@@ -104,7 +104,6 @@ dependencies = [
"arrayref",
"byteorder",
"crypto",
"embedded-time",
"lang_items",
"libtock_core",
"libtock_drivers",

View File

@@ -20,7 +20,6 @@ use arbitrary::{Arbitrary, Unstructured};
use arrayref::array_ref;
use core::convert::TryFrom;
use ctap2::api::customization::is_valid;
use ctap2::clock::CtapInstant;
use ctap2::ctap::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, Command,
@@ -89,12 +88,8 @@ fn initialize(ctap: &mut Ctap<TestEnv>) -> ChannelID {
let mut assembler_reply = MessageAssembler::new();
let mut result_cid: ChannelID = Default::default();
for pkt_request in HidPacketIterator::new(message).unwrap() {
for pkt_reply in
ctap.process_hid_packet(&pkt_request, Transport::MainHid, CtapInstant::new(0))
{
if let Ok(Some(result)) =
assembler_reply.parse_packet(ctap.env(), &pkt_reply, CtapInstant::new(0))
{
for pkt_reply in ctap.process_hid_packet(&pkt_request, Transport::MainHid) {
if let Ok(Some(result)) = assembler_reply.parse_packet(ctap.env(), &pkt_reply) {
result_cid.copy_from_slice(&result.payload[8..12]);
}
}
@@ -131,11 +126,9 @@ fn process_message(data: &[u8], ctap: &mut Ctap<TestEnv>) {
if let Some(hid_packet_iterator) = HidPacketIterator::new(message) {
let mut assembler_reply = MessageAssembler::new();
for pkt_request in hid_packet_iterator {
for pkt_reply in
ctap.process_hid_packet(&pkt_request, Transport::MainHid, CtapInstant::new(0))
{
for pkt_reply in ctap.process_hid_packet(&pkt_request, Transport::MainHid) {
// Only checks for assembling crashes, not for semantics.
let _ = assembler_reply.parse_packet(ctap.env(), &pkt_reply, CtapInstant::new(0));
let _ = assembler_reply.parse_packet(ctap.env(), &pkt_reply);
}
}
}
@@ -152,7 +145,7 @@ pub fn process_ctap_any_type(data: &[u8]) -> arbitrary::Result<()> {
let data = unstructured.take_rest();
// Initialize ctap state and hid and get the allocated cid.
let mut ctap = Ctap::new(env, CtapInstant::new(0));
let mut ctap = Ctap::new(env);
let cid = initialize(&mut ctap);
// Wrap input as message with the allocated cid.
let mut command = cid.to_vec();
@@ -179,7 +172,7 @@ fn setup_customization(
fn setup_state(
unstructured: &mut Unstructured,
state: &mut CtapState,
state: &mut CtapState<TestEnv>,
env: &mut TestEnv,
) -> FuzzResult<()> {
if bool::arbitrary(unstructured)? {
@@ -202,7 +195,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) -> arbitra
return Ok(());
}
// Initialize ctap state and hid and get the allocated cid.
let mut ctap = Ctap::new(env, CtapInstant::new(0));
let mut ctap = Ctap::new(env);
let cid = initialize(&mut ctap);
// Wrap input as message with allocated cid and command type.
let mut command = cid.to_vec();
@@ -232,7 +225,7 @@ pub fn process_ctap_structured(data: &[u8], input_type: InputType) -> FuzzResult
env.rng().seed_from_u64(u64::arbitrary(unstructured)?);
setup_customization(unstructured, env.customization_mut())?;
let mut state = CtapState::new(&mut env, CtapInstant::new(0));
let mut state = CtapState::new(&mut env);
setup_state(unstructured, &mut state, &mut env)?;
let command = match input_type {
@@ -255,7 +248,6 @@ pub fn process_ctap_structured(data: &[u8], input_type: InputType) -> FuzzResult
&mut env,
command,
Channel::MainHid(ChannelID::arbitrary(unstructured)?),
CtapInstant::new(0),
)
.ok();
@@ -276,13 +268,10 @@ pub fn split_assemble_hid_packets(data: &[u8]) -> arbitrary::Result<()> {
let packets: Vec<HidPacket> = hid_packet_iterator.collect();
if let Some((last_packet, first_packets)) = packets.split_last() {
for packet in first_packets {
assert_eq!(
assembler.parse_packet(&mut env, packet, CtapInstant::new(0)),
Ok(None)
);
assert_eq!(assembler.parse_packet(&mut env, packet), Ok(None));
}
assert_eq!(
assembler.parse_packet(&mut env, last_packet, CtapInstant::new(0)),
assembler.parse_packet(&mut env, last_packet),
Ok(Some(message))
);
}

39
src/api/clock.rs Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub trait Clock {
/// Stores data for the clock to recognize if this timer is elapsed or not.
///
/// The Clock does not keep track of the timers it creates. Therefore, they should not wrap
/// unexpectedly. A timer that is elapsed may never return to a non-elapsed state.
///
/// A default Timer should return `true` when checked with `is_elapsed`.
type Timer: Default;
/// Creates a new timer that expires after the given time in ms.
fn make_timer(&mut self, milliseconds: usize) -> Self::Timer;
/// Checks whether a given timer is expired.
///
/// Until a timer expires, this function consistently returns false. Once it expires, this
/// function consistently returns true. In particular, it is valid to continue calling this
/// function after the first time it returns true.
fn is_elapsed(&mut self, timer: &Self::Timer) -> bool;
/// Timestamp in microseconds.
///
/// Normal operation only needs relative time, absolute timestamps are useful for debugging.
#[cfg(feature = "debug_ctap")]
fn timestamp_us(&mut self) -> usize;
}

View File

@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::clock::ClockInt;
use embedded_time::duration::Milliseconds;
use libtock_drivers::usb_ctap_hid::UsbEndpoint;
pub enum SendOrRecvStatus {
@@ -27,9 +25,5 @@ pub struct SendOrRecvError;
pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;
pub trait HidConnection {
fn send_and_maybe_recv(
&mut self,
buf: &mut [u8; 64],
timeout: Milliseconds<ClockInt>,
) -> SendOrRecvResult;
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult;
}

View File

@@ -18,6 +18,7 @@
//! by a trait. This module gathers the API of those components.
pub mod attestation_store;
pub mod clock;
pub mod connection;
pub mod customization;
pub mod firmware_protection;

View File

@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::clock::ClockInt;
use embedded_time::duration::Milliseconds;
#[derive(Debug)]
pub enum UserPresenceError {
/// User explicitly declined user presence check.
@@ -36,7 +33,7 @@ pub trait UserPresence {
/// Waits until user presence is confirmed, rejected, or the given timeout expires.
///
/// Must be called between calls to [`Self::check_init`] and [`Self::check_complete`].
fn wait_with_timeout(&mut self, timeout: Milliseconds<ClockInt>) -> UserPresenceResult;
fn wait_with_timeout(&mut self, timeout_ms: usize) -> UserPresenceResult;
/// Finalizes a user presence check.
///

View File

@@ -1,170 +0,0 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(not(feature = "std"))]
use alloc::fmt;
use embedded_time::duration::Milliseconds;
pub use embedded_time::Clock;
#[cfg(not(feature = "std"))]
use libtock_drivers::result::FlexUnwrap;
#[cfg(not(feature = "std"))]
pub struct LibtockClock<const CLOCK_FREQUENCY: u32>(libtock_drivers::timer::Timer<'static>);
#[cfg(not(feature = "std"))]
impl<const CLOCK_FREQUENCY: u32> LibtockClock<CLOCK_FREQUENCY> {
pub fn new() -> Self {
let boxed_cb = alloc::boxed::Box::new(libtock_drivers::timer::with_callback(|_, _| {}));
let timer = alloc::boxed::Box::leak(boxed_cb).init().flex_unwrap();
Self(timer)
}
}
#[cfg(not(feature = "std"))]
impl<const CLOCK_FREQUENCY: u32> fmt::Debug for LibtockClock<CLOCK_FREQUENCY> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LibtockClock")
.field("CLOCK_FREQUENCY", &CLOCK_FREQUENCY)
.finish()
}
}
pub const KEEPALIVE_DELAY_MS: ClockInt = 100;
pub const KEEPALIVE_DELAY: Milliseconds<ClockInt> = Milliseconds(KEEPALIVE_DELAY_MS);
#[cfg(target_pointer_width = "32")]
pub type ClockInt = u32;
#[cfg(target_pointer_width = "64")]
pub type ClockInt = u64;
#[cfg(not(feature = "std"))]
impl<const CLOCK_FREQUENCY: u32> embedded_time::Clock for LibtockClock<CLOCK_FREQUENCY> {
// TODO: Implement and use a 24-bits TimeInt for Nordic
type T = ClockInt;
const SCALING_FACTOR: embedded_time::fraction::Fraction =
<embedded_time::fraction::Fraction>::new(1, CLOCK_FREQUENCY);
fn try_now(&self) -> Result<embedded_time::Instant<Self>, embedded_time::clock::Error> {
let timer = &self.0;
let now = timer.get_current_clock().flex_unwrap();
Ok(embedded_time::Instant::new(now.num_ticks() as Self::T))
}
}
#[cfg(not(feature = "std"))]
pub type CtapClock = LibtockClock<32768>;
#[cfg(feature = "std")]
pub type CtapClock = TestClock;
pub fn new_clock() -> CtapClock {
CtapClock::new()
}
pub type CtapInstant = embedded_time::Instant<CtapClock>;
#[cfg(feature = "std")]
pub const TEST_CLOCK_FREQUENCY_HZ: u32 = 32768;
#[cfg(feature = "std")]
#[derive(Default, Clone, Copy, Debug)]
pub struct TestClock;
#[cfg(feature = "std")]
impl TestClock {
pub fn new() -> Self {
TestClock
}
}
#[cfg(feature = "std")]
impl embedded_time::Clock for TestClock {
type T = u64;
const SCALING_FACTOR: embedded_time::fraction::Fraction =
<embedded_time::fraction::Fraction>::new(1, TEST_CLOCK_FREQUENCY_HZ);
fn try_now(&self) -> Result<embedded_time::Instant<Self>, embedded_time::clock::Error> {
Ok(embedded_time::Instant::new(0))
}
}
#[cfg(test)]
mod test {
use super::*;
use embedded_time::duration::{Milliseconds, Seconds};
#[test]
fn test_checked_add() {
let now = CtapInstant::new(0);
assert_eq!(
now.checked_add(Seconds::new(1 as ClockInt)),
Some(CtapInstant::new(TEST_CLOCK_FREQUENCY_HZ as ClockInt))
);
assert_eq!(
now.checked_add(Seconds::new(1 as ClockInt)),
now.checked_add(Milliseconds::new(1000 as ClockInt))
);
}
#[test]
fn test_checked_add_overflow() {
assert_eq!(
CtapInstant::new(u64::MAX).checked_add(Seconds::new(1 as ClockInt)),
Some(CtapInstant::new(TEST_CLOCK_FREQUENCY_HZ as u64 - 1))
);
}
#[test]
fn test_checked_add_error() {
assert!(CtapInstant::new(0)
.checked_add(Seconds::new(u64::MAX / TEST_CLOCK_FREQUENCY_HZ as ClockInt))
.is_none());
assert!(CtapInstant::new(0)
.checked_add(Seconds::new(
u64::MAX / TEST_CLOCK_FREQUENCY_HZ as ClockInt / 2
))
.is_some());
assert!(CtapInstant::new(0)
.checked_add(Seconds::new(
u64::MAX / TEST_CLOCK_FREQUENCY_HZ as ClockInt / 2 + 1
))
.is_none());
}
#[test]
fn test_duration_since() {
let early = CtapInstant::new(0);
let later = CtapInstant::new(1000);
assert_eq!(
later.checked_duration_since(&early).unwrap().integer(),
1000
);
assert_eq!(early.checked_duration_since(&later), None);
}
#[test]
fn test_duration_since_overflow() {
let early = CtapInstant::new(u64::MAX);
let later = CtapInstant::new(1000);
assert_eq!(
later.checked_duration_since(&early).unwrap().integer(),
1001
);
assert_eq!(early.checked_duration_since(&later), None);
}
#[test]
#[should_panic]
fn add_panic() {
let _ =
CtapInstant::new(0) + Seconds(u64::MAX / TEST_CLOCK_FREQUENCY_HZ as ClockInt / 2 + 1);
}
}

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::super::clock::CtapInstant;
use super::command::AuthenticatorClientPinParameters;
use super::data_formats::{
ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PinUvAuthProtocol,
@@ -33,7 +32,6 @@ use crypto::sha256::Sha256;
use crypto::Hash256;
#[cfg(test)]
use enum_iterator::IntoEnumIterator;
use rng256::Rng256;
use subtle::ConstantTimeEq;
/// The prefix length of the PIN hash that is stored and compared.
@@ -106,18 +104,18 @@ pub enum PinPermission {
AuthenticatorConfiguration = 0x20,
}
pub struct ClientPin {
pub struct ClientPin<E: Env> {
pin_protocol_v1: PinProtocol,
pin_protocol_v2: PinProtocol,
consecutive_pin_mismatches: u8,
pin_uv_auth_token_state: PinUvAuthTokenState,
pin_uv_auth_token_state: PinUvAuthTokenState<E>,
}
impl ClientPin {
pub fn new(rng: &mut impl Rng256) -> ClientPin {
impl<E: Env> ClientPin<E> {
pub fn new(env: &mut E) -> Self {
ClientPin {
pin_protocol_v1: PinProtocol::new(rng),
pin_protocol_v2: PinProtocol::new(rng),
pin_protocol_v1: PinProtocol::new(env.rng()),
pin_protocol_v2: PinProtocol::new(env.rng()),
consecutive_pin_mismatches: 0,
pin_uv_auth_token_state: PinUvAuthTokenState::new(),
}
@@ -159,7 +157,7 @@ impl ClientPin {
/// Also, in case of failure, the key agreement key is randomly reset.
fn verify_pin_hash_enc(
&mut self,
env: &mut impl Env,
env: &mut E,
pin_uv_auth_protocol: PinUvAuthProtocol,
shared_secret: &dyn SharedSecret,
pin_hash_enc: Vec<u8>,
@@ -197,7 +195,7 @@ impl ClientPin {
fn process_get_pin_retries(
&self,
env: &mut impl Env,
env: &mut E,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
Ok(AuthenticatorClientPinResponse {
key_agreement: None,
@@ -225,7 +223,7 @@ impl ClientPin {
fn process_set_pin(
&mut self,
env: &mut impl Env,
env: &mut E,
client_pin_params: AuthenticatorClientPinParameters,
) -> Result<(), Ctap2StatusCode> {
let AuthenticatorClientPinParameters {
@@ -252,7 +250,7 @@ impl ClientPin {
fn process_change_pin(
&mut self,
env: &mut impl Env,
env: &mut E,
client_pin_params: AuthenticatorClientPinParameters,
) -> Result<(), Ctap2StatusCode> {
let AuthenticatorClientPinParameters {
@@ -290,9 +288,8 @@ impl ClientPin {
fn process_get_pin_token(
&mut self,
env: &mut impl Env,
env: &mut E,
client_pin_params: AuthenticatorClientPinParameters,
now: CtapInstant,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
let AuthenticatorClientPinParameters {
pin_uv_auth_protocol,
@@ -325,7 +322,7 @@ impl ClientPin {
self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng());
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
self.pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(now);
.begin_using_pin_uv_auth_token(env);
self.pin_uv_auth_token_state.set_default_permissions();
let pin_uv_auth_token = shared_secret.encrypt(
env.rng(),
@@ -358,9 +355,8 @@ impl ClientPin {
fn process_get_pin_uv_auth_token_using_pin_with_permissions(
&mut self,
env: &mut impl Env,
env: &mut E,
mut client_pin_params: AuthenticatorClientPinParameters,
now: CtapInstant,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
// Mutating client_pin_params is just an optimization to move it into
// process_get_pin_token, without cloning permissions_rp_id here.
@@ -376,7 +372,7 @@ impl ClientPin {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
let response = self.process_get_pin_token(env, client_pin_params, now)?;
let response = self.process_get_pin_token(env, client_pin_params)?;
self.pin_uv_auth_token_state.set_permissions(permissions);
self.pin_uv_auth_token_state
.set_permissions_rp_id(permissions_rp_id);
@@ -387,9 +383,8 @@ impl ClientPin {
/// Processes the authenticatorClientPin command.
pub fn process_command(
&mut self,
env: &mut impl Env,
env: &mut E,
client_pin_params: AuthenticatorClientPinParameters,
now: CtapInstant,
) -> Result<ResponseData, Ctap2StatusCode> {
if !env.customization().allows_pin_protocol_v1()
&& client_pin_params.pin_uv_auth_protocol == PinUvAuthProtocol::V1
@@ -410,7 +405,7 @@ impl ClientPin {
None
}
ClientPinSubCommand::GetPinToken => {
Some(self.process_get_pin_token(env, client_pin_params, now)?)
Some(self.process_get_pin_token(env, client_pin_params)?)
}
ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some(
self.process_get_pin_uv_auth_token_using_uv_with_permissions(client_pin_params)?,
@@ -420,7 +415,6 @@ impl ClientPin {
self.process_get_pin_uv_auth_token_using_pin_with_permissions(
env,
client_pin_params,
now,
)?,
),
};
@@ -447,11 +441,11 @@ impl ClientPin {
}
/// Resets all held state.
pub fn reset(&mut self, rng: &mut impl Rng256) {
self.pin_protocol_v1.regenerate(rng);
self.pin_protocol_v1.reset_pin_uv_auth_token(rng);
self.pin_protocol_v2.regenerate(rng);
self.pin_protocol_v2.reset_pin_uv_auth_token(rng);
pub fn reset(&mut self, env: &mut E) {
self.pin_protocol_v1.regenerate(env.rng());
self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng());
self.pin_protocol_v2.regenerate(env.rng());
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
self.consecutive_pin_mismatches = 0;
self.pin_uv_auth_token_state.stop_using_pin_uv_auth_token();
}
@@ -466,7 +460,7 @@ impl ClientPin {
/// 32 byte.
pub fn process_hmac_secret(
&self,
rng: &mut impl Rng256,
env: &mut E,
hmac_secret_input: GetAssertionHmacSecretInput,
cred_random: &[u8; 32],
) -> Result<Vec<u8>, Ctap2StatusCode> {
@@ -490,7 +484,7 @@ impl ClientPin {
let mut output2 = hmac_256::<Sha256>(cred_random, &decrypted_salts[32..]).to_vec();
output.append(&mut output2);
}
shared_secret.encrypt(rng, &output)
shared_secret.encrypt(env.rng(), &output)
}
/// Consumes flags and permissions related to the pinUvAuthToken.
@@ -501,9 +495,9 @@ impl ClientPin {
}
/// Updates the running timers, triggers timeout events.
pub fn update_timeouts(&mut self, now: CtapInstant) {
pub fn update_timeouts(&mut self, env: &mut E) {
self.pin_uv_auth_token_state
.pin_uv_auth_token_usage_timer_observer(now);
.pin_uv_auth_token_usage_timer_observer(env);
}
/// Checks if user verification is cached for use of the pinUvAuthToken.
@@ -563,19 +557,19 @@ impl ClientPin {
#[cfg(test)]
pub fn new_test(
env: &mut E,
key_agreement_key: crypto::ecdh::SecKey,
pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH],
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> ClientPin {
let mut env = crate::env::test::TestEnv::new();
) -> Self {
let (key_agreement_key_v1, key_agreement_key_v2) = match pin_uv_auth_protocol {
PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(env.rng())),
PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(env.rng()), key_agreement_key),
};
let mut pin_uv_auth_token_state = PinUvAuthTokenState::new();
pin_uv_auth_token_state.set_permissions(0xFF);
pin_uv_auth_token_state.begin_using_pin_uv_auth_token(CtapInstant::new(0));
ClientPin {
pin_uv_auth_token_state.begin_using_pin_uv_auth_token(env);
Self {
pin_protocol_v1: PinProtocol::new_test(key_agreement_key_v1, pin_uv_auth_token),
pin_protocol_v2: PinProtocol::new_test(key_agreement_key_v2, pin_uv_auth_token),
consecutive_pin_mismatches: 0,
@@ -590,7 +584,6 @@ mod test {
use super::*;
use crate::env::test::TestEnv;
use alloc::vec;
use embedded_time::duration::Milliseconds;
/// Stores a PIN hash corresponding to the dummy PIN "1234".
fn set_standard_pin(env: &mut TestEnv) {
@@ -618,14 +611,18 @@ mod test {
/// should fail.
fn create_client_pin_and_shared_secret(
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> (ClientPin, Box<dyn SharedSecret>) {
) -> (ClientPin<TestEnv>, Box<dyn SharedSecret>) {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pk = key_agreement_key.genpk();
let key_agreement = CoseKey::from(pk);
let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let shared_secret = client_pin
.get_pin_protocol(pin_uv_auth_protocol)
.decapsulate(key_agreement, pin_uv_auth_protocol)
@@ -639,7 +636,7 @@ mod test {
fn create_client_pin_and_parameters(
pin_uv_auth_protocol: PinUvAuthProtocol,
sub_command: ClientPinSubCommand,
) -> (ClientPin, AuthenticatorClientPinParameters) {
) -> (ClientPin<TestEnv>, AuthenticatorClientPinParameters) {
let mut env = TestEnv::new();
let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol);
@@ -683,7 +680,7 @@ mod test {
#[test]
fn test_mix_pin_protocols() {
let mut env = TestEnv::new();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::<TestEnv>::new(&mut env);
let pin_protocol_v1 = client_pin.get_pin_protocol(PinUvAuthProtocol::V1);
let pin_protocol_v2 = client_pin.get_pin_protocol(PinUvAuthProtocol::V2);
let message = vec![0xAA; 16];
@@ -724,7 +721,7 @@ mod test {
fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol);
let shared_secret = pin_protocol
.decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol)
@@ -823,7 +820,7 @@ mod test {
power_cycle_state: Some(false),
});
assert_eq!(
client_pin.process_command(&mut env, params.clone(), CtapInstant::new(0)),
client_pin.process_command(&mut env, params.clone()),
Ok(ResponseData::AuthenticatorClientPin(expected_response))
);
@@ -835,7 +832,7 @@ mod test {
power_cycle_state: Some(true),
});
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Ok(ResponseData::AuthenticatorClientPin(expected_response))
);
}
@@ -863,7 +860,7 @@ mod test {
power_cycle_state: None,
});
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Ok(ResponseData::AuthenticatorClientPin(expected_response))
);
}
@@ -887,7 +884,7 @@ mod test {
let mut env = TestEnv::new();
env.customization_mut().set_allows_pin_protocol_v1(false);
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
}
@@ -897,7 +894,7 @@ mod test {
create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin);
let mut env = TestEnv::new();
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Ok(ResponseData::AuthenticatorClientPin(None))
);
}
@@ -930,14 +927,14 @@ mod test {
let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data);
params.pin_uv_auth_param = Some(pin_uv_auth_param);
assert_eq!(
client_pin.process_command(&mut env, params.clone(), CtapInstant::new(0)),
client_pin.process_command(&mut env, params.clone()),
Ok(ResponseData::AuthenticatorClientPin(None))
);
let mut bad_params = params.clone();
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
assert_eq!(
client_pin.process_command(&mut env, bad_params, CtapInstant::new(0)),
client_pin.process_command(&mut env, bad_params),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
@@ -945,7 +942,7 @@ mod test {
storage::decr_pin_retries(&mut env).unwrap();
}
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED)
);
}
@@ -976,7 +973,7 @@ mod test {
set_standard_pin(&mut env);
let response = client_pin
.process_command(&mut env, params.clone(), CtapInstant::new(0))
.process_command(&mut env, params.clone())
.unwrap();
let encrypted_token = match response {
ResponseData::AuthenticatorClientPin(Some(response)) => {
@@ -1012,7 +1009,7 @@ mod test {
let mut bad_params = params;
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
assert_eq!(
client_pin.process_command(&mut env, bad_params, CtapInstant::new(0)),
client_pin.process_command(&mut env, bad_params),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
);
}
@@ -1037,7 +1034,7 @@ mod test {
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID),
);
}
@@ -1070,7 +1067,7 @@ mod test {
set_standard_pin(&mut env);
let response = client_pin
.process_command(&mut env, params.clone(), CtapInstant::new(0))
.process_command(&mut env, params.clone())
.unwrap();
let encrypted_token = match response {
ResponseData::AuthenticatorClientPin(Some(response)) => {
@@ -1106,21 +1103,21 @@ mod test {
let mut bad_params = params.clone();
bad_params.permissions = Some(0x00);
assert_eq!(
client_pin.process_command(&mut env, bad_params, CtapInstant::new(0)),
client_pin.process_command(&mut env, bad_params),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
let mut bad_params = params.clone();
bad_params.permissions_rp_id = None;
assert_eq!(
client_pin.process_command(&mut env, bad_params, CtapInstant::new(0)),
client_pin.process_command(&mut env, bad_params),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
let mut bad_params = params;
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
assert_eq!(
client_pin.process_command(&mut env, bad_params, CtapInstant::new(0)),
client_pin.process_command(&mut env, bad_params),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
);
}
@@ -1147,7 +1144,7 @@ mod test {
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
client_pin.process_command(&mut env, params),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
);
}
@@ -1281,7 +1278,7 @@ mod test {
salt_auth,
pin_uv_auth_protocol,
};
let output = client_pin.process_hmac_secret(env.rng(), hmac_secret_input, cred_random);
let output = client_pin.process_hmac_secret(&mut env, hmac_secret_input, cred_random);
output.map(|v| shared_secret.as_ref().decrypt(&v).unwrap())
}
@@ -1301,7 +1298,7 @@ mod test {
salt_auth,
pin_uv_auth_protocol,
};
let output = client_pin.process_hmac_secret(env.rng(), hmac_secret_input, &cred_random);
let output = client_pin.process_hmac_secret(&mut env, hmac_secret_input, &cred_random);
assert_eq!(output, Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID));
}
@@ -1403,7 +1400,7 @@ mod test {
#[test]
fn test_has_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
client_pin.pin_uv_auth_token_state.set_permissions(0x7F);
for permission in PinPermission::into_enum_iter() {
assert_eq!(
@@ -1427,7 +1424,7 @@ mod test {
#[test]
fn test_has_no_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
client_pin
.pin_uv_auth_token_state
@@ -1441,7 +1438,7 @@ mod test {
#[test]
fn test_has_no_or_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
client_pin
.pin_uv_auth_token_state
@@ -1456,7 +1453,7 @@ mod test {
#[test]
fn test_has_no_or_rp_id_hash_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
let rp_id_hash = Sha256::hash(b"example.com");
assert_eq!(
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
@@ -1478,7 +1475,7 @@ mod test {
#[test]
fn test_ensure_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
assert_eq!(
client_pin
@@ -1496,11 +1493,11 @@ mod test {
#[test]
fn test_verify_pin_uv_auth_token() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
let message = [0xAA];
client_pin
.pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(CtapInstant::new(0));
.begin_using_pin_uv_auth_token(&mut env);
let pin_uv_auth_token_v1 = client_pin
.get_pin_protocol(PinUvAuthProtocol::V1)
@@ -1570,7 +1567,7 @@ mod test {
#[test]
fn test_verify_pin_uv_auth_token_not_in_use() {
let mut env = TestEnv::new();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::<TestEnv>::new(&mut env);
let message = [0xAA];
let pin_uv_auth_token_v1 = client_pin
@@ -1592,7 +1589,7 @@ mod test {
#[test]
fn test_reset() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::<TestEnv>::new(&mut env);
let public_key_v1 = client_pin.pin_protocol_v1.get_public_key();
let public_key_v2 = client_pin.pin_protocol_v2.get_public_key();
let token_v1 = *client_pin.pin_protocol_v1.get_pin_uv_auth_token();
@@ -1601,7 +1598,7 @@ mod test {
client_pin
.pin_uv_auth_token_state
.set_permissions_rp_id(Some(String::from("example.com")));
client_pin.reset(env.rng());
client_pin.reset(&mut env);
assert_ne!(public_key_v1, client_pin.pin_protocol_v1.get_public_key());
assert_ne!(public_key_v2, client_pin.pin_protocol_v2.get_public_key());
assert_ne!(
@@ -1631,9 +1628,7 @@ mod test {
set_standard_pin(&mut env);
params.permissions = Some(0xFF);
assert!(client_pin
.process_command(&mut env, params, CtapInstant::new(0))
.is_ok());
assert!(client_pin.process_command(&mut env, params).is_ok());
for permission in PinPermission::into_enum_iter() {
assert_eq!(
client_pin
@@ -1649,8 +1644,8 @@ mod test {
Ok(())
);
let timeout = CtapInstant::new(0) + Milliseconds::new(30001_u32);
client_pin.update_timeouts(timeout);
env.clock().advance(30001);
client_pin.update_timeouts(&mut env);
for permission in PinPermission::into_enum_iter() {
assert_eq!(
client_pin
@@ -1677,9 +1672,7 @@ mod test {
set_standard_pin(&mut env);
params.permissions = Some(0xFF);
assert!(client_pin
.process_command(&mut env, params, CtapInstant::new(0))
.is_ok());
assert!(client_pin.process_command(&mut env, params).is_ok());
for permission in PinPermission::into_enum_iter() {
assert_eq!(
client_pin

View File

@@ -73,9 +73,9 @@ fn process_set_min_pin_length(
}
/// Processes the AuthenticatorConfig command.
pub fn process_config(
env: &mut impl Env,
client_pin: &mut ClientPin,
pub fn process_config<E: Env>(
env: &mut E,
client_pin: &mut ClientPin<E>,
params: AuthenticatorConfigParameters,
) -> Result<ResponseData, Ctap2StatusCode> {
let AuthenticatorConfigParameters {
@@ -133,8 +133,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::EnableEnterpriseAttestation,
@@ -160,8 +164,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::ToggleAlwaysUv,
@@ -195,8 +203,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
let mut config_data = vec![0xFF; 32];
@@ -265,8 +277,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// First, increase minimum PIN length from 4 to 6 without PIN auth.
let min_pin_length = 6;
@@ -309,8 +325,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// First, set RP IDs without PIN auth.
let min_pin_length = 6;
@@ -385,8 +405,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
// Increase min PIN, force PIN change.
@@ -408,8 +432,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
@@ -439,8 +467,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::VendorPrototype,

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::super::clock::CtapInstant;
use super::client_pin::{ClientPin, PinPermission};
use super::command::AuthenticatorCredentialManagementParameters;
use super::data_formats::{
@@ -108,9 +107,9 @@ fn enumerate_credentials_response(
/// Check if the token permissions have the correct associated RP ID.
///
/// Either no RP ID is associated, or the RP ID matches the stored credential.
fn check_rp_id_permissions(
env: &mut impl Env,
client_pin: &mut ClientPin,
fn check_rp_id_permissions<E: Env>(
env: &mut E,
client_pin: &mut ClientPin<E>,
credential_id: &[u8],
) -> Result<(), Ctap2StatusCode> {
// Pre-check a sufficient condition before calling the store.
@@ -135,17 +134,16 @@ fn process_get_creds_metadata(
}
/// Processes the subcommand enumerateRPsBegin for CredentialManagement.
fn process_enumerate_rps_begin(
env: &mut impl Env,
stateful_command_permission: &mut StatefulPermission,
fn process_enumerate_rps_begin<E: Env>(
env: &mut E,
stateful_command_permission: &mut StatefulPermission<E>,
channel: Channel,
now: CtapInstant,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
let rp_set = get_stored_rp_ids(env)?;
let total_rps = rp_set.len();
if total_rps > 1 {
stateful_command_permission.set_command(now, StatefulCommand::EnumerateRps(1), channel);
stateful_command_permission.set_command(env, StatefulCommand::EnumerateRps(1), channel);
}
// TODO https://github.com/rust-lang/rust/issues/62924 replace with pop_first()
let rp_id = rp_set
@@ -156,9 +154,9 @@ fn process_enumerate_rps_begin(
}
/// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement.
fn process_enumerate_rps_get_next_rp(
env: &mut impl Env,
stateful_command_permission: &mut StatefulPermission,
fn process_enumerate_rps_get_next_rp<E: Env>(
env: &mut E,
stateful_command_permission: &mut StatefulPermission<E>,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
let rp_id_index = stateful_command_permission.next_enumerate_rp()?;
let rp_set = get_stored_rp_ids(env)?;
@@ -171,13 +169,12 @@ fn process_enumerate_rps_get_next_rp(
}
/// Processes the subcommand enumerateCredentialsBegin for CredentialManagement.
fn process_enumerate_credentials_begin(
env: &mut impl Env,
stateful_command_permission: &mut StatefulPermission,
client_pin: &mut ClientPin,
fn process_enumerate_credentials_begin<E: Env>(
env: &mut E,
stateful_command_permission: &mut StatefulPermission<E>,
client_pin: &mut ClientPin<E>,
sub_command_params: CredentialManagementSubCommandParameters,
channel: Channel,
now: CtapInstant,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
let rp_id_hash = sub_command_params
.rp_id_hash
@@ -203,7 +200,7 @@ fn process_enumerate_credentials_begin(
let credential = storage::get_credential(env, current_key)?;
if total_credentials > 1 {
stateful_command_permission.set_command(
now,
env,
StatefulCommand::EnumerateCredentials(rp_credentials),
channel,
);
@@ -212,9 +209,9 @@ fn process_enumerate_credentials_begin(
}
/// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement.
fn process_enumerate_credentials_get_next_credential(
env: &mut impl Env,
stateful_command_permission: &mut StatefulPermission,
fn process_enumerate_credentials_get_next_credential<E: Env>(
env: &mut E,
stateful_command_permission: &mut StatefulPermission<E>,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
let credential_key = stateful_command_permission.next_enumerate_credential()?;
let credential = storage::get_credential(env, credential_key)?;
@@ -222,9 +219,9 @@ fn process_enumerate_credentials_get_next_credential(
}
/// Processes the subcommand deleteCredential for CredentialManagement.
fn process_delete_credential(
env: &mut impl Env,
client_pin: &mut ClientPin,
fn process_delete_credential<E: Env>(
env: &mut E,
client_pin: &mut ClientPin<E>,
sub_command_params: CredentialManagementSubCommandParameters,
) -> Result<(), Ctap2StatusCode> {
let credential_id = sub_command_params
@@ -236,9 +233,9 @@ fn process_delete_credential(
}
/// Processes the subcommand updateUserInformation for CredentialManagement.
fn process_update_user_information(
env: &mut impl Env,
client_pin: &mut ClientPin,
fn process_update_user_information<E: Env>(
env: &mut E,
client_pin: &mut ClientPin<E>,
sub_command_params: CredentialManagementSubCommandParameters,
) -> Result<(), Ctap2StatusCode> {
let credential_id = sub_command_params
@@ -253,13 +250,12 @@ fn process_update_user_information(
}
/// Processes the CredentialManagement command and all its subcommands.
pub fn process_credential_management(
env: &mut impl Env,
stateful_command_permission: &mut StatefulPermission,
client_pin: &mut ClientPin,
pub fn process_credential_management<E: Env>(
env: &mut E,
stateful_command_permission: &mut StatefulPermission<E>,
client_pin: &mut ClientPin<E>,
cred_management_params: AuthenticatorCredentialManagementParameters,
channel: Channel,
now: CtapInstant,
) -> Result<ResponseData, Ctap2StatusCode> {
let AuthenticatorCredentialManagementParameters {
sub_command,
@@ -319,7 +315,6 @@ pub fn process_credential_management(
env,
stateful_command_permission,
channel,
now,
)?)
}
CredentialManagementSubCommand::EnumerateRpsGetNextRp => Some(
@@ -332,7 +327,6 @@ pub fn process_credential_management(
client_pin,
sub_command_params.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
channel,
now,
)?)
}
CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential => Some(
@@ -392,11 +386,15 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let credential_source = create_credential_source(&mut env);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.client_pin = client_pin;
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
@@ -419,7 +417,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let initial_capacity = match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -445,7 +442,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -474,13 +470,17 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.rp_id = "another.example.com".to_string();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.client_pin = client_pin;
storage::store_credential(&mut env, credential_source1).unwrap();
@@ -504,7 +504,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let first_rp_id = match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -529,7 +528,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let second_rp_id = match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -555,7 +553,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
@@ -568,11 +565,15 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source = create_credential_source(&mut env);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.client_pin = client_pin;
const NUM_CREDENTIALS: usize = 20;
@@ -605,7 +606,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -636,7 +636,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
@@ -649,8 +648,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.user_handle = vec![0x02];
@@ -658,7 +661,7 @@ mod test {
credential_source2.user_display_name = Some("User Two".to_string());
credential_source2.user_icon = Some("icon2".to_string());
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.client_pin = client_pin;
storage::store_credential(&mut env, credential_source1).unwrap();
@@ -689,7 +692,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let first_credential_id = match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -713,7 +715,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let second_credential_id = match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
@@ -738,7 +739,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
@@ -751,12 +751,16 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut credential_source = create_credential_source(&mut env);
credential_source.credential_id = vec![0x1D; 32];
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.client_pin = client_pin;
storage::store_credential(&mut env, credential_source).unwrap();
@@ -789,7 +793,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
@@ -808,7 +811,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
@@ -821,12 +823,16 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut credential_source = create_credential_source(&mut env);
credential_source.credential_id = vec![0x1D; 32];
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.client_pin = client_pin;
storage::store_credential(&mut env, credential_source).unwrap();
@@ -865,7 +871,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
@@ -887,7 +892,7 @@ mod test {
#[test]
fn test_process_credential_management_invalid_pin_uv_auth_param() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
@@ -903,7 +908,6 @@ mod test {
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::super::clock::CtapInstant;
use super::apdu::{Apdu, ApduStatusCode};
use super::credential_id::{decrypt_credential_id, encrypt_to_credential_id};
use super::crypto_wrapper::PrivateKey;
@@ -174,11 +173,10 @@ impl Ctap1Command {
const VENDOR_SPECIFIC_FIRST: u8 = 0x40;
const VENDOR_SPECIFIC_LAST: u8 = 0xBF;
pub fn process_command(
env: &mut impl Env,
pub fn process_command<E: Env>(
env: &mut E,
message: &[u8],
ctap_state: &mut CtapState,
clock_value: CtapInstant,
ctap_state: &mut CtapState<E>,
) -> Result<Vec<u8>, Ctap1StatusCode> {
if !ctap_state
.allows_ctap1(env)
@@ -192,7 +190,7 @@ impl Ctap1Command {
challenge,
application,
} => {
if !ctap_state.u2f_up_state.consume_up(clock_value) {
if !ctap_state.u2f_up_state.consume_up(env) {
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
}
Ctap1Command::process_register(env, challenge, application)
@@ -205,8 +203,7 @@ impl Ctap1Command {
flags,
} => {
// The order is important due to side effects of checking user presence.
if flags == Ctap1Flags::EnforceUpAndSign
&& !ctap_state.u2f_up_state.consume_up(clock_value)
if flags == Ctap1Flags::EnforceUpAndSign && !ctap_state.u2f_up_state.consume_up(env)
{
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
}
@@ -301,13 +298,13 @@ impl Ctap1Command {
// +-------------------+---------+--------------+-----------------+
// + application (32B) | UP (1B) | Counter (4B) | challenge (32B) |
// +-------------------+---------+--------------+-----------------+
fn process_authenticate(
env: &mut impl Env,
fn process_authenticate<E: Env>(
env: &mut E,
challenge: [u8; 32],
application: [u8; 32],
key_handle: Vec<u8>,
flags: Ctap1Flags,
ctap_state: &mut CtapState,
ctap_state: &mut CtapState<E>,
) -> Result<Vec<u8>, Ctap1StatusCode> {
let credential_source = decrypt_credential_id(env, key_handle, &application)
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
@@ -345,9 +342,9 @@ impl Ctap1Command {
mod test {
use super::super::credential_id::CBOR_CREDENTIAL_ID_SIZE;
use super::super::data_formats::SignatureAlgorithm;
use super::super::TOUCH_TIMEOUT_MS;
use super::*;
use crate::api::customization::Customization;
use crate::clock::TEST_CLOCK_FREQUENCY_HZ;
use crate::ctap::storage;
use crate::env::test::TestEnv;
use crypto::Hash256;
@@ -394,15 +391,14 @@ mod test {
let mut env = TestEnv::new();
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
storage::toggle_always_uv(&mut env).unwrap();
let application = [0x0A; 32];
let message = create_register_message(&application);
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_NOT_ALLOWED));
}
@@ -411,14 +407,13 @@ mod test {
let mut env = TestEnv::new();
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let application = [0x0A; 32];
let message = create_register_message(&application);
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
// Certificate and private key are missing
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
@@ -429,11 +424,9 @@ mod test {
env.attestation_store()
.set(&attestation_store::Id::Batch, Some(&attestation))
.unwrap();
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0))
.unwrap();
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap();
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
assert_eq!(response[66], CBOR_CREDENTIAL_ID_SIZE as u8);
assert!(decrypt_credential_id(
@@ -455,16 +448,12 @@ mod test {
let mut env = TestEnv::new();
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let application = [0x0A; 32];
let message = create_register_message(&application);
let response = Ctap1Command::process_command(
&mut env,
&message[..message.len() - 1],
&mut ctap_state,
CtapInstant::new(0),
);
let response =
Ctap1Command::process_command(&mut env, &message[..message.len() - 1], &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH));
}
@@ -477,14 +466,12 @@ mod test {
let mut env = TestEnv::new();
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let timeout_clock_value: CtapInstant =
CtapInstant::new((30001 * TEST_CLOCK_FREQUENCY_HZ as u64) / 1000);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, timeout_clock_value);
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
env.clock().advance(TOUCH_TIMEOUT_MS);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED));
}
@@ -494,15 +481,14 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED));
}
@@ -512,7 +498,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -520,8 +506,7 @@ mod test {
let application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA));
}
@@ -531,7 +516,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -543,23 +528,19 @@ mod test {
);
message.push(0x00);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert!(response.is_ok());
message.push(0x00);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert!(response.is_ok());
message.push(0x00);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert!(response.is_ok());
message.push(0x00);
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH));
}
@@ -569,7 +550,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -578,8 +559,7 @@ mod test {
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE;
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_CLA_INVALID));
}
@@ -589,7 +569,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -598,8 +578,7 @@ mod test {
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE;
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_INS_INVALID));
}
@@ -609,7 +588,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -618,8 +597,7 @@ mod test {
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE;
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA));
}
@@ -637,7 +615,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -645,11 +623,9 @@ mod test {
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0))
.unwrap();
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap();
assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_signature_counter(
@@ -665,7 +641,7 @@ mod test {
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
@@ -676,13 +652,8 @@ mod test {
&key_handle,
);
let response = Ctap1Command::process_command(
&mut env,
&message,
&mut ctap_state,
CtapInstant::new((30001 * TEST_CLOCK_FREQUENCY_HZ as u64) / 1000),
)
.unwrap();
env.clock().advance(TOUCH_TIMEOUT_MS);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap();
assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
check_signature_counter(
@@ -702,12 +673,11 @@ mod test {
let mut env = TestEnv::new();
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let response =
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0));
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA));
}
@@ -721,16 +691,12 @@ mod test {
let mut env = TestEnv::new();
env.user_presence()
.set(|| panic!("Unexpected user presence check in CTAP1"));
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::new(&mut env);
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
let response = Ctap1Command::process_command(
&mut env,
&message,
&mut ctap_state,
CtapInstant::new((30001 * TEST_CLOCK_FREQUENCY_HZ as u64) / 1000),
);
ctap_state.u2f_up_state.consume_up(&mut env);
ctap_state.u2f_up_state.grant_up(&mut env);
env.clock().advance(TOUCH_TIMEOUT_MS);
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED));
}
}

View File

@@ -21,16 +21,29 @@ pub use self::receive::MessageAssembler;
#[cfg(not(feature = "std"))]
use self::receive::MessageAssembler;
pub use self::send::HidPacketIterator;
use super::super::clock::{ClockInt, CtapInstant};
use super::status_code::Ctap2StatusCode;
#[cfg(test)]
use crate::env::test::TestEnv;
use crate::env::Env;
use alloc::vec;
use alloc::vec::Vec;
use arrayref::{array_ref, array_refs};
use embedded_time::duration::Milliseconds;
#[cfg(test)]
use enum_iterator::IntoEnumIterator;
// We implement CTAP 2.1 from 2021-06-15. Please see section
// 11.2. USB Human Interface Device (USB HID)
const CHANNEL_RESERVED: ChannelID = [0, 0, 0, 0];
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
const PACKET_TYPE_MASK: u8 = 0x80;
// See section 11.2.9.1.3. CTAPHID_INIT (0x06).
const PROTOCOL_VERSION: u8 = 2;
// The device version number is vendor-defined.
const DEVICE_VERSION_MAJOR: u8 = 1;
const DEVICE_VERSION_MINOR: u8 = 0;
const DEVICE_VERSION_BUILD: u8 = 0;
pub type HidPacket = [u8; 64];
pub type ChannelID = [u8; 4];
@@ -164,8 +177,8 @@ pub enum KeepaliveStatus {
/// 2. If you didn't receive any message or preprocessing discarded it, stop.
/// 3. Handles all CTAP protocol interactions.
/// 4. `split_message` creates packets out of the response message.
pub struct CtapHid {
assembler: MessageAssembler,
pub struct CtapHid<E: Env> {
assembler: MessageAssembler<E>,
// The specification only requires unique CIDs, the allocation algorithm is vendor specific.
// We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are
// allocated.
@@ -176,34 +189,17 @@ pub struct CtapHid {
capabilities: u8,
}
impl CtapHid {
// We implement CTAP 2.1 from 2021-06-15. Please see section
// 11.2. USB Human Interface Device (USB HID)
const CHANNEL_RESERVED: ChannelID = [0, 0, 0, 0];
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
const TYPE_INIT_BIT: u8 = 0x80;
const PACKET_TYPE_MASK: u8 = 0x80;
// See section 11.2.9.1.3. CTAPHID_INIT (0x06).
const PROTOCOL_VERSION: u8 = 2;
// The device version number is vendor-defined.
const DEVICE_VERSION_MAJOR: u8 = 1;
const DEVICE_VERSION_MINOR: u8 = 0;
const DEVICE_VERSION_BUILD: u8 = 0;
impl<E: Env> CtapHid<E> {
pub const CAPABILITY_WINK: u8 = 0x01;
pub const CAPABILITY_CBOR: u8 = 0x04;
#[cfg(any(not(feature = "with_ctap1"), feature = "vendor_hid"))]
pub const CAPABILITY_NMSG: u8 = 0x08;
// TODO: Is this timeout duration specified?
const TIMEOUT_DURATION: Milliseconds<ClockInt> = Milliseconds(100 as ClockInt);
/// Creates a new CTAP HID packet parser.
///
/// The capabilities passed in are reported to the client in Init.
pub fn new(capabilities: u8) -> CtapHid {
CtapHid {
pub fn new(capabilities: u8) -> CtapHid<E> {
Self {
assembler: MessageAssembler::new(),
allocated_cids: 0,
capabilities,
@@ -228,14 +224,9 @@ impl CtapHid {
/// You may ignore PING, it's behaving correctly by default (input == output).
/// Ignoring the others is incorrect behavior. You have to at least replace them with an error
/// message:
/// `CtapHid::error_message(message.cid, CtapHid::ERR_INVALID_CMD)`
pub fn parse_packet(
&mut self,
env: &mut impl Env,
packet: &HidPacket,
clock_value: CtapInstant,
) -> Option<Message> {
match self.assembler.parse_packet(env, packet, clock_value) {
/// `Self::error_message(message.cid, CtapHidError::InvalidCmd)`
pub fn parse_packet(&mut self, env: &mut E, packet: &HidPacket) -> Option<Message> {
match self.assembler.parse_packet(env, packet) {
Ok(Some(message)) => {
debug_ctap!(env, "Received message: {:02x?}", message);
self.preprocess_message(message)
@@ -248,9 +239,9 @@ impl CtapHid {
if matches!(error, CtapHidError::UnexpectedContinuation) {
None
} else if !self.is_allocated_channel(cid) {
Some(CtapHid::error_message(cid, CtapHidError::InvalidChannel))
Some(Self::error_message(cid, CtapHidError::InvalidChannel))
} else {
Some(CtapHid::error_message(cid, error))
Some(Self::error_message(cid, error))
}
}
}
@@ -267,7 +258,7 @@ impl CtapHid {
fn preprocess_message(&mut self, message: Message) -> Option<Message> {
let cid = message.cid;
if !self.has_valid_channel(&message) {
return Some(CtapHid::error_message(cid, CtapHidError::InvalidChannel));
return Some(Self::error_message(cid, CtapHidError::InvalidChannel));
}
match message.cmd {
@@ -276,10 +267,10 @@ impl CtapHid {
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.3.
CtapHidCommand::Init => {
if message.payload.len() != 8 {
return Some(CtapHid::error_message(cid, CtapHidError::InvalidLen));
return Some(Self::error_message(cid, CtapHidError::InvalidLen));
}
let new_cid = if cid == CtapHid::CHANNEL_BROADCAST {
let new_cid = if cid == CHANNEL_BROADCAST {
// TODO: Prevent allocating 2^32 channels.
self.allocated_cids += 1;
(self.allocated_cids as u32).to_be_bytes()
@@ -291,10 +282,10 @@ impl CtapHid {
let mut payload = vec![0; 17];
payload[..8].copy_from_slice(&message.payload);
payload[8..12].copy_from_slice(&new_cid);
payload[12] = CtapHid::PROTOCOL_VERSION;
payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
payload[14] = CtapHid::DEVICE_VERSION_MINOR;
payload[15] = CtapHid::DEVICE_VERSION_BUILD;
payload[12] = PROTOCOL_VERSION;
payload[13] = DEVICE_VERSION_MAJOR;
payload[14] = DEVICE_VERSION_MINOR;
payload[15] = DEVICE_VERSION_BUILD;
payload[16] = self.capabilities;
Some(Message {
@@ -317,7 +308,7 @@ impl CtapHid {
CtapHidCommand::Wink => Some(message),
_ => {
// Unknown or unsupported command.
Some(CtapHid::error_message(cid, CtapHidError::InvalidCmd))
Some(Self::error_message(cid, CtapHidError::InvalidCmd))
}
}
}
@@ -325,14 +316,14 @@ impl CtapHid {
fn has_valid_channel(&self, message: &Message) -> bool {
match message.cid {
// Only INIT commands use the broadcast channel.
CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHidCommand::Init,
CHANNEL_BROADCAST => message.cmd == CtapHidCommand::Init,
// Check that the channel is allocated.
_ => self.is_allocated_channel(message.cid),
}
}
fn is_allocated_channel(&self, cid: ChannelID) -> bool {
cid != CtapHid::CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids
cid != CHANNEL_RESERVED && u32::from_be_bytes(cid) as usize <= self.allocated_cids
}
pub fn error_message(cid: ChannelID, error_code: CtapHidError) -> Message {
@@ -346,8 +337,8 @@ impl CtapHid {
/// Helper function to parse a raw packet.
pub fn process_single_packet(packet: &HidPacket) -> (ChannelID, ProcessedPacket) {
let (&cid, rest) = array_refs![packet, 4, 60];
if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 {
let cmd = rest[0] & !CtapHid::PACKET_TYPE_MASK;
if rest[0] & PACKET_TYPE_MASK != 0 {
let cmd = rest[0] & !PACKET_TYPE_MASK;
let len = (rest[1] as usize) << 8 | (rest[2] as usize);
(
cid,
@@ -394,7 +385,7 @@ impl CtapHid {
/// Generates the HID response packets for a keepalive status.
pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator {
CtapHid::split_message(Message {
Self::split_message(Message {
cid,
cmd: CtapHidCommand::Keepalive,
payload: vec![status as u8],
@@ -402,9 +393,9 @@ impl CtapHid {
}
#[cfg(test)]
pub fn new_initialized() -> (CtapHid, ChannelID) {
pub fn new_initialized() -> (Self, ChannelID) {
(
CtapHid {
Self {
assembler: MessageAssembler::new(),
allocated_cids: 1,
capabilities: 0x0D,
@@ -417,7 +408,6 @@ impl CtapHid {
#[cfg(test)]
mod test {
use super::*;
use crate::env::test::TestEnv;
#[test]
fn test_split_assemble() {
@@ -430,9 +420,9 @@ mod test {
};
let mut messages = Vec::new();
let mut assembler = MessageAssembler::new();
let mut assembler = MessageAssembler::<TestEnv>::new();
for packet in HidPacketIterator::new(message.clone()).unwrap() {
match assembler.parse_packet(&mut env, &packet, CtapInstant::new(0)) {
match assembler.parse_packet(&mut env, &packet) {
Ok(Some(msg)) => messages.push(msg),
Ok(None) => (),
Err(_) => panic!("Couldn't assemble packet: {:02x?}", &packet as &[u8]),
@@ -446,21 +436,18 @@ mod test {
#[test]
fn test_spurious_continuation_packet() {
let mut env = TestEnv::new();
let mut ctap_hid = CtapHid::new(0x0D);
let mut ctap_hid = CtapHid::<TestEnv>::new(0x0D);
let mut packet = [0x00; 64];
packet[0..7].copy_from_slice(&[0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x51, 0x51]);
// Continuation packets are silently ignored.
assert_eq!(
ctap_hid.parse_packet(&mut env, &packet, CtapInstant::new(0)),
None
);
assert_eq!(ctap_hid.parse_packet(&mut env, &packet), None);
}
#[test]
fn test_command_init() {
let mut ctap_hid = CtapHid::new(0x0D);
let mut ctap_hid = CtapHid::<TestEnv>::new(0x0D);
let init_message = Message {
cid: CtapHid::CHANNEL_BROADCAST,
cid: CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init,
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
};
@@ -468,7 +455,7 @@ mod test {
assert_eq!(
reply,
Some(Message {
cid: CtapHid::CHANNEL_BROADCAST,
cid: CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init,
payload: vec![
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, // Nonce
@@ -484,7 +471,7 @@ mod test {
#[test]
fn test_command_init_for_sync() {
let mut env = TestEnv::new();
let (mut ctap_hid, cid) = CtapHid::new_initialized();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
// Ping packet with a length longer than one packet.
let mut packet1 = [0x51; 64];
@@ -496,12 +483,9 @@ mod test {
packet2[4..15].copy_from_slice(&[
0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
]);
assert_eq!(ctap_hid.parse_packet(&mut env, &packet1), None);
assert_eq!(
ctap_hid.parse_packet(&mut env, &packet1, CtapInstant::new(0)),
None
);
assert_eq!(
ctap_hid.parse_packet(&mut env, &packet2, CtapInstant::new(0)),
ctap_hid.parse_packet(&mut env, &packet2),
Some(Message {
cid,
cmd: CtapHidCommand::Init,
@@ -519,13 +503,13 @@ mod test {
#[test]
fn test_command_ping() {
let mut env = TestEnv::new();
let (mut ctap_hid, cid) = CtapHid::new_initialized();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
let mut ping_packet = [0x00; 64];
ping_packet[..4].copy_from_slice(&cid);
ping_packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &ping_packet, CtapInstant::new(0)),
ctap_hid.parse_packet(&mut env, &ping_packet),
Some(Message {
cid,
cmd: CtapHidCommand::Ping,
@@ -537,13 +521,13 @@ mod test {
#[test]
fn test_command_cancel() {
let mut env = TestEnv::new();
let (mut ctap_hid, cid) = CtapHid::new_initialized();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
let mut cancel_packet = [0x00; 64];
cancel_packet[..4].copy_from_slice(&cid);
cancel_packet[4..7].copy_from_slice(&[0x91, 0x00, 0x00]);
let response = ctap_hid.parse_packet(&mut env, &cancel_packet, CtapInstant::new(0));
let response = ctap_hid.parse_packet(&mut env, &cancel_packet);
assert_eq!(response, None);
}
@@ -554,7 +538,7 @@ mod test {
cmd: CtapHidCommand::Ping,
payload: vec![0x99, 0x99],
};
let mut response = CtapHid::split_message(message);
let mut response = CtapHid::<TestEnv>::split_message(message);
let mut expected_packet = [0x00; 64];
expected_packet[..9]
.copy_from_slice(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x02, 0x99, 0x99]);
@@ -570,7 +554,7 @@ mod test {
cmd: CtapHidCommand::Cbor,
payload,
};
let mut response = CtapHid::split_message(message);
let mut response = CtapHid::<TestEnv>::split_message(message);
let mut expected_packet = [0x00; 64];
expected_packet[..8].copy_from_slice(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x01, 0xF2]);
assert_eq!(response.next(), Some(expected_packet));
@@ -581,7 +565,7 @@ mod test {
fn test_keepalive() {
for &status in [KeepaliveStatus::Processing, KeepaliveStatus::UpNeeded].iter() {
let cid = [0x12, 0x34, 0x56, 0x78];
let mut response = CtapHid::keepalive(cid, status);
let mut response = CtapHid::<TestEnv>::keepalive(cid, status);
let mut expected_packet = [0x00; 64];
expected_packet[..8].copy_from_slice(&[
0x12,
@@ -604,7 +588,7 @@ mod test {
let mut packet = [0x00; 64];
packet[..4].copy_from_slice(&cid);
packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
let (processed_cid, processed_packet) = CtapHid::process_single_packet(&packet);
let (processed_cid, processed_packet) = CtapHid::<TestEnv>::process_single_packet(&packet);
assert_eq!(processed_cid, cid);
let expected_packet = ProcessedPacket::InitPacket {
cmd: CtapHidCommand::Ping as u8,
@@ -627,7 +611,7 @@ mod test {
fn test_error_message() {
let cid = [0x12, 0x34, 0x56, 0x78];
assert_eq!(
CtapHid::error_message(cid, CtapHidError::InvalidCmd),
CtapHid::<TestEnv>::error_message(cid, CtapHidError::InvalidCmd),
Message {
cid,
cmd: CtapHidCommand::Error,

View File

@@ -15,20 +15,23 @@
use super::{
ChannelID, CtapHid, CtapHidCommand, CtapHidError, HidPacket, Message, ProcessedPacket,
};
use crate::api::clock::Clock;
use crate::api::customization::Customization;
use crate::clock::CtapInstant;
use crate::env::Env;
use alloc::vec::Vec;
use core::mem::swap;
// TODO: Is this timeout duration specified?
const TIMEOUT_DURATION_MS: usize = 100;
/// A structure to assemble CTAPHID commands from a series of incoming USB HID packets.
pub struct MessageAssembler {
pub struct MessageAssembler<E: Env> {
// Whether this is waiting to receive an initialization packet.
idle: bool,
// Current channel ID.
cid: ChannelID,
// Timestamp of the last packet received on the current channel.
last_timestamp: CtapInstant,
timer: <E::Clock as Clock>::Timer,
// Current command.
cmd: u8,
// Sequence number expected for the next packet.
@@ -39,12 +42,12 @@ pub struct MessageAssembler {
payload: Vec<u8>,
}
impl MessageAssembler {
pub fn new() -> MessageAssembler {
impl<E: Env> MessageAssembler<E> {
pub fn new() -> MessageAssembler<E> {
MessageAssembler {
idle: true,
cid: [0, 0, 0, 0],
last_timestamp: CtapInstant::new(0),
timer: <E::Clock as Clock>::Timer::default(),
cmd: 0,
seq: 0,
remaining_payload_len: 0,
@@ -57,7 +60,7 @@ impl MessageAssembler {
fn reset(&mut self) {
self.idle = true;
self.cid = [0, 0, 0, 0];
self.last_timestamp = CtapInstant::new(0);
self.timer = <E::Clock as Clock>::Timer::default();
self.cmd = 0;
self.seq = 0;
self.remaining_payload_len = 0;
@@ -69,19 +72,16 @@ impl MessageAssembler {
// full message was assembled after this packet, or None if more packets are needed to fill the
// message.
// - An Err() result if there was a parsing error.
// TODO: Implement timeouts. For example, have the caller pass us a timestamp of when this
// packet was received.
pub fn parse_packet(
&mut self,
env: &mut impl Env,
env: &mut E,
packet: &HidPacket,
timestamp: CtapInstant,
) -> Result<Option<Message>, (ChannelID, CtapHidError)> {
// TODO: Support non-full-speed devices (i.e. packet len != 64)? This isn't recommended by
// section 8.8.1
let (cid, processed_packet) = CtapHid::process_single_packet(packet);
let (cid, processed_packet) = CtapHid::<E>::process_single_packet(packet);
if !self.idle && timestamp >= self.last_timestamp + CtapHid::TIMEOUT_DURATION {
if !self.idle && env.clock().is_elapsed(&self.timer) {
// The current channel timed out.
// Save the channel ID and reset the state.
let current_cid = self.cid;
@@ -98,7 +98,7 @@ impl MessageAssembler {
// Expecting an initialization packet.
match processed_packet {
ProcessedPacket::InitPacket { cmd, len, data } => {
self.parse_init_packet(env, cid, cmd, len, data, timestamp)
self.parse_init_packet(env, cid, cmd, len, data)
}
ProcessedPacket::ContinuationPacket { .. } => {
// CTAP specification (version 20190130) section 8.1.5.4
@@ -120,7 +120,7 @@ impl MessageAssembler {
ProcessedPacket::InitPacket { cmd, len, data } => {
self.reset();
if cmd == CtapHidCommand::Init as u8 {
self.parse_init_packet(env, cid, cmd, len, data, timestamp)
self.parse_init_packet(env, cid, cmd, len, data)
} else {
Err((cid, CtapHidError::InvalidSeq))
}
@@ -132,7 +132,7 @@ impl MessageAssembler {
Err((cid, CtapHidError::InvalidSeq))
} else {
// Update the last timestamp.
self.last_timestamp = timestamp;
self.timer = env.clock().make_timer(TIMEOUT_DURATION_MS);
// Increment the sequence number for the next packet.
self.seq += 1;
Ok(self.append_payload(data))
@@ -144,12 +144,11 @@ impl MessageAssembler {
fn parse_init_packet(
&mut self,
env: &mut impl Env,
env: &mut E,
cid: ChannelID,
cmd: u8,
len: usize,
data: &[u8],
timestamp: CtapInstant,
) -> Result<Option<Message>, (ChannelID, CtapHidError)> {
// Reject invalid lengths early to reduce the risk of running out of memory.
// TODO: also reject invalid commands early?
@@ -157,7 +156,7 @@ impl MessageAssembler {
return Err((cid, CtapHidError::InvalidLen));
}
self.cid = cid;
self.last_timestamp = timestamp;
self.timer = env.clock().make_timer(TIMEOUT_DURATION_MS);
self.cmd = cmd;
self.seq = 0;
self.remaining_payload_len = len;
@@ -187,9 +186,7 @@ impl MessageAssembler {
#[cfg(test)]
mod test {
use crate::ctap::hid::CtapHid;
use crate::env::test::TestEnv;
use embedded_time::duration::Milliseconds;
use super::*;
@@ -212,14 +209,8 @@ mod test {
fn test_empty_payload() {
let mut env = TestEnv::new();
let mut assembler = MessageAssembler::new();
// Except for tests that exercise timeouts, all packets are synchronized at the same dummy
// timestamp.
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90])),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Cbor,
@@ -236,7 +227,6 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]),
CtapInstant::new(0)
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -257,7 +247,6 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10], 0xFF),
CtapInstant::new(0)
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -275,16 +264,11 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
CtapInstant::new(0)
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -301,24 +285,15 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x80]),
CtapInstant::new(0)
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01])),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -335,26 +310,17 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x1D, 0xB9]),
CtapInstant::new(0)
),
Ok(None)
);
for seq in 0..0x7F {
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq])),
Ok(None)
);
}
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F])),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -380,7 +346,6 @@ mod test {
&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd as u8, 0x00, 0x80],
byte
),
CtapInstant::new(0)
),
Ok(None)
);
@@ -388,7 +353,6 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x00], byte),
CtapInstant::new(0)
),
Ok(None)
);
@@ -396,7 +360,6 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x01], byte),
CtapInstant::new(0)
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -422,24 +385,17 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd as u8, 0x00, 0x80], byte),
CtapInstant::new(0)
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x00], byte),
CtapInstant::new(0)
),
assembler
.parse_packet(&mut env, &byte_extend(&[0x12, 0x34, 0x56, cid, 0x00], byte)),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x01], byte),
CtapInstant::new(0)
),
assembler
.parse_packet(&mut env, &byte_extend(&[0x12, 0x34, 0x56, cid, 0x01], byte)),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, cid],
cmd,
@@ -457,7 +413,6 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
CtapInstant::new(0)
),
Ok(None)
);
@@ -470,7 +425,6 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte),
CtapInstant::new(0)
),
Err(([0x12, 0x34, 0x56, 0x9A], CtapHidError::ChannelBusy))
);
@@ -478,11 +432,7 @@ mod test {
}
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -505,7 +455,6 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x10], byte),
CtapInstant::new(0)
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -517,11 +466,7 @@ mod test {
// Spurious continuation packet.
let seq = i;
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq])),
Err((
[0x12, 0x34, 0x56, 0x78],
CtapHidError::UnexpectedContinuation
@@ -538,16 +483,11 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
CtapInstant::new(0)
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80])),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::InvalidSeq))
);
}
@@ -560,16 +500,11 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
CtapInstant::new(0)
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]),
CtapInstant::new(0)
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01])),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::InvalidSeq))
);
}
@@ -582,16 +517,12 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
CtapInstant::new(0)
),
Ok(None)
);
env.clock().advance(TIMEOUT_DURATION_MS);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
CtapInstant::new(0) + CtapHid::TIMEOUT_DURATION
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::MsgTimeout))
);
}
@@ -599,37 +530,27 @@ mod test {
#[test]
fn test_just_in_time_packets() {
let mut env = TestEnv::new();
let mut timestamp: CtapInstant = CtapInstant::new(0);
// Delay between each packet is just below the threshold.
let delay = CtapHid::TIMEOUT_DURATION - Milliseconds(1_u32);
let delay = TIMEOUT_DURATION_MS - 1;
let mut assembler = MessageAssembler::new();
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x1D, 0xB9]),
timestamp
),
Ok(None)
);
for seq in 0..0x7F {
timestamp = timestamp + delay;
env.clock().advance(delay);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
timestamp
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq])),
Ok(None)
);
}
timestamp = timestamp + delay;
env.clock().advance(delay);
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]),
timestamp
),
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F])),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -647,7 +568,6 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x02, 0x00], 0x51),
CtapInstant::new(0)
),
Ok(None)
);
@@ -659,7 +579,6 @@ mod test {
0x12, 0x34, 0x56, 0x78, 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC,
0xDE, 0xF0
]),
CtapInstant::new(0)
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],

View File

@@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{CtapHid, HidPacket, Message};
use super::{HidPacket, Message};
const TYPE_INIT_BIT: u8 = 0x80;
/// Iterator for HID packets.
///
@@ -121,7 +123,7 @@ impl Iterator for MessageSplitter {
match self.seq {
None => {
// First, send an initialization packet.
self.packet[4] = self.message.cmd as u8 | CtapHid::TYPE_INIT_BIT;
self.packet[4] = self.message.cmd as u8 | TYPE_INIT_BIT;
self.packet[5] = (payload_len >> 8) as u8;
self.packet[6] = payload_len as u8;

View File

@@ -45,10 +45,10 @@ impl LargeBlobs {
}
/// Process the large blob command.
pub fn process_command(
pub fn process_command<E: Env>(
&mut self,
env: &mut impl Env,
client_pin: &mut ClientPin,
env: &mut E,
client_pin: &mut ClientPin<E>,
large_blobs_params: AuthenticatorLargeBlobsParameters,
) -> Result<ResponseData, Ctap2StatusCode> {
let AuthenticatorLargeBlobsParameters {
@@ -147,8 +147,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
let large_blob = vec![
@@ -178,8 +182,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -240,8 +248,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -286,8 +298,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -332,8 +348,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
let large_blobs_params = AuthenticatorLargeBlobsParameters {
@@ -355,8 +375,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;
@@ -383,8 +407,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let mut client_pin = ClientPin::<TestEnv>::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::clock::{ClockInt, CtapInstant};
use crate::api::clock::Clock;
#[cfg(feature = "with_ctap1")]
use crate::ctap::ctap1;
#[cfg(feature = "with_ctap1")]
@@ -20,29 +20,29 @@ use crate::ctap::hid::ChannelID;
use crate::ctap::hid::{
CtapHid, CtapHidCommand, CtapHidError, HidPacket, HidPacketIterator, Message,
};
use crate::ctap::{Channel, CtapState, TimedPermission};
use crate::ctap::{Channel, CtapState};
use crate::env::Env;
use embedded_time::duration::Milliseconds;
const WINK_TIMEOUT_DURATION_MS: usize = 5000;
/// Implements the standard CTAP command processing for HID.
pub struct MainHid {
hid: CtapHid,
wink_permission: TimedPermission,
pub struct MainHid<E: Env> {
hid: CtapHid<E>,
wink_permission: <E::Clock as Clock>::Timer,
}
impl MainHid {
const WINK_TIMEOUT_DURATION: Milliseconds<ClockInt> = Milliseconds(5000 as ClockInt);
impl<E: Env> MainHid<E> {
/// Instantiates a HID handler for CTAP1, CTAP2 and Wink.
pub fn new() -> Self {
#[cfg(feature = "with_ctap1")]
let capabilities = CtapHid::CAPABILITY_WINK | CtapHid::CAPABILITY_CBOR;
let capabilities = CtapHid::<E>::CAPABILITY_WINK | CtapHid::<E>::CAPABILITY_CBOR;
#[cfg(not(feature = "with_ctap1"))]
let capabilities =
CtapHid::CAPABILITY_WINK | CtapHid::CAPABILITY_CBOR | CtapHid::CAPABILITY_NMSG;
let capabilities = CtapHid::<E>::CAPABILITY_WINK
| CtapHid::<E>::CAPABILITY_CBOR
| CtapHid::<E>::CAPABILITY_NMSG;
let hid = CtapHid::new(capabilities);
let wink_permission = TimedPermission::waiting();
let wink_permission = <E::Clock as Clock>::Timer::default();
MainHid {
hid,
wink_permission,
@@ -52,15 +52,14 @@ impl MainHid {
/// Processes an incoming USB HID packet, and returns an iterator for all outgoing packets.
pub fn process_hid_packet(
&mut self,
env: &mut impl Env,
env: &mut E,
packet: &HidPacket,
now: CtapInstant,
ctap_state: &mut CtapState,
ctap_state: &mut CtapState<E>,
) -> HidPacketIterator {
if let Some(message) = self.hid.parse_packet(env, packet, now) {
let processed_message = self.process_message(env, message, now, ctap_state);
if let Some(message) = self.hid.parse_packet(env, packet) {
let processed_message = self.process_message(env, message, ctap_state);
debug_ctap!(env, "Sending message: {:02x?}", processed_message);
CtapHid::split_message(processed_message)
CtapHid::<E>::split_message(processed_message)
} else {
HidPacketIterator::none()
}
@@ -69,13 +68,12 @@ impl MainHid {
/// Processes a message's commands that affect the protocol outside HID.
pub fn process_message(
&mut self,
env: &mut impl Env,
env: &mut E,
message: Message,
now: CtapInstant,
ctap_state: &mut CtapState,
ctap_state: &mut CtapState<E>,
) -> Message {
// If another command arrives, stop winking to prevent accidential button touches.
self.wink_permission = TimedPermission::waiting();
self.wink_permission = <E::Clock as Clock>::Timer::default();
let cid = message.cid;
match message.cmd {
@@ -83,12 +81,12 @@ impl MainHid {
CtapHidCommand::Msg => {
// If we don't have CTAP1 backward compatibilty, this command is invalid.
#[cfg(not(feature = "with_ctap1"))]
return CtapHid::error_message(cid, CtapHidError::InvalidCmd);
return CtapHid::<E>::error_message(cid, CtapHidError::InvalidCmd);
#[cfg(feature = "with_ctap1")]
match ctap1::Ctap1Command::process_command(env, &message.payload, ctap_state, now) {
Ok(payload) => MainHid::ctap1_success_message(cid, &payload),
Err(ctap1_status_code) => MainHid::ctap1_error_message(cid, ctap1_status_code),
match ctap1::Ctap1Command::process_command(env, &message.payload, ctap_state) {
Ok(payload) => Self::ctap1_success_message(cid, &payload),
Err(ctap1_status_code) => Self::ctap1_error_message(cid, ctap1_status_code),
}
}
// CTAP 2.1 from 2021-06-15, section 11.2.9.1.2.
@@ -97,7 +95,7 @@ impl MainHid {
// don't handle any other packet in the meantime.
// TODO: Send "Processing" type keep-alive packets in the meantime.
let response =
ctap_state.process_command(env, &message.payload, Channel::MainHid(cid), now);
ctap_state.process_command(env, &message.payload, Channel::MainHid(cid));
Message {
cid,
cmd: CtapHidCommand::Cbor,
@@ -107,12 +105,11 @@ impl MainHid {
// CTAP 2.1 from 2021-06-15, section 11.2.9.2.1.
CtapHidCommand::Wink => {
if message.payload.is_empty() {
self.wink_permission =
TimedPermission::granted(now, Self::WINK_TIMEOUT_DURATION);
self.wink_permission = env.clock().make_timer(WINK_TIMEOUT_DURATION_MS);
// The response is empty like the request.
message
} else {
CtapHid::error_message(cid, CtapHidError::InvalidLen)
CtapHid::<E>::error_message(cid, CtapHidError::InvalidLen)
}
}
// All other commands have already been processed, keep them as is.
@@ -121,13 +118,8 @@ impl MainHid {
}
/// Returns whether a wink permission is currently granted.
pub fn should_wink(&self, now: CtapInstant) -> bool {
self.wink_permission.is_granted(now)
}
/// Updates the timeout for the wink permission.
pub fn update_wink_timeout(&mut self, now: CtapInstant) {
self.wink_permission = self.wink_permission.check_expiration(now);
pub fn should_wink(&self, env: &mut E) -> bool {
!env.clock().is_elapsed(&self.wink_permission)
}
#[cfg(feature = "with_ctap1")]
@@ -159,11 +151,11 @@ mod test {
use crate::ctap::hid::ChannelID;
use crate::env::test::TestEnv;
fn new_initialized() -> (MainHid, ChannelID) {
fn new_initialized() -> (MainHid<TestEnv>, ChannelID) {
let (hid, cid) = CtapHid::new_initialized();
let wink_permission = TimedPermission::waiting();
let wink_permission = <<TestEnv as Env>::Clock as Clock>::Timer::default();
(
MainHid {
MainHid::<TestEnv> {
hid,
wink_permission,
},
@@ -174,19 +166,14 @@ mod test {
#[test]
fn test_process_hid_packet() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let (mut main_hid, cid) = new_initialized();
let mut ping_packet = [0x00; 64];
ping_packet[..4].copy_from_slice(&cid);
ping_packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
let mut response = main_hid.process_hid_packet(
&mut env,
&ping_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = main_hid.process_hid_packet(&mut env, &ping_packet, &mut ctap_state);
assert_eq!(response.next(), Some(ping_packet));
assert_eq!(response.next(), None);
}
@@ -194,42 +181,33 @@ mod test {
#[test]
fn test_process_hid_packet_empty() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let (mut main_hid, cid) = new_initialized();
let mut cancel_packet = [0x00; 64];
cancel_packet[..4].copy_from_slice(&cid);
cancel_packet[4..7].copy_from_slice(&[0x91, 0x00, 0x00]);
let mut response = main_hid.process_hid_packet(
&mut env,
&cancel_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = main_hid.process_hid_packet(&mut env, &cancel_packet, &mut ctap_state);
assert_eq!(response.next(), None);
}
#[test]
fn test_wink() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let (mut main_hid, cid) = new_initialized();
assert!(!main_hid.should_wink(CtapInstant::new(0)));
assert!(!main_hid.should_wink(&mut env));
let mut wink_packet = [0x00; 64];
wink_packet[..4].copy_from_slice(&cid);
wink_packet[4..7].copy_from_slice(&[0x88, 0x00, 0x00]);
let mut response = main_hid.process_hid_packet(
&mut env,
&wink_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = main_hid.process_hid_packet(&mut env, &wink_packet, &mut ctap_state);
assert_eq!(response.next(), Some(wink_packet));
assert_eq!(response.next(), None);
assert!(main_hid.should_wink(CtapInstant::new(0)));
assert!(!main_hid.should_wink(CtapInstant::new(1) + MainHid::WINK_TIMEOUT_DURATION));
assert!(main_hid.should_wink(&mut env));
env.clock().advance(WINK_TIMEOUT_DURATION_MS);
assert!(!main_hid.should_wink(&mut env));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,199 +0,0 @@
// Copyright 2019-2021 Google LLC
//
// Licensed under the Apache License, Version 2 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::super::clock::{ClockInt, CtapInstant};
use embedded_time::duration::Milliseconds;
#[derive(Debug, Copy, Clone)]
pub enum TimedPermission {
Waiting,
Granted(CtapInstant),
}
impl TimedPermission {
pub fn waiting() -> TimedPermission {
TimedPermission::Waiting
}
pub fn granted(now: CtapInstant, grant_duration: Milliseconds<ClockInt>) -> TimedPermission {
// TODO: Should panic or saturate if the grant duration is too big.
TimedPermission::Granted(now.checked_add(grant_duration).unwrap())
}
// Checks if the timeout is not reached, false for differing ClockValue frequencies.
pub fn is_granted(&self, now: CtapInstant) -> bool {
if let TimedPermission::Granted(timeout) = self {
return timeout.checked_duration_since(&now).is_some();
}
false
}
// Consumes the state and returns the current new permission state at time "now".
// Returns a new state for differing ClockValue frequencies.
pub fn check_expiration(self, now: CtapInstant) -> TimedPermission {
if let TimedPermission::Granted(timeout) = self {
if timeout.checked_duration_since(&now).is_some() {
return TimedPermission::Granted(timeout);
}
}
TimedPermission::Waiting
}
}
#[cfg(feature = "with_ctap1")]
#[derive(Debug)]
pub struct U2fUserPresenceState {
// If user presence was recently requested, its timeout is saved here.
needs_up: TimedPermission,
// Button touch timeouts, while user presence is requested, are saved here.
has_up: TimedPermission,
// This is the timeout duration of user presence requests.
request_duration: Milliseconds<ClockInt>,
// This is the timeout duration of button touches.
presence_duration: Milliseconds<ClockInt>,
}
#[cfg(feature = "with_ctap1")]
impl U2fUserPresenceState {
pub fn new(
request_duration: Milliseconds<ClockInt>,
presence_duration: Milliseconds<ClockInt>,
) -> U2fUserPresenceState {
U2fUserPresenceState {
needs_up: TimedPermission::Waiting,
has_up: TimedPermission::Waiting,
request_duration,
presence_duration,
}
}
// Granting user presence is ignored if it needs activation, but waits. Also cleans up.
pub fn grant_up(&mut self, now: CtapInstant) {
self.check_expiration(now);
if self.needs_up.is_granted(now) {
self.needs_up = TimedPermission::Waiting;
self.has_up = TimedPermission::granted(now, self.presence_duration);
}
}
// This marks user presence as needed or uses it up if already granted. Also cleans up.
pub fn consume_up(&mut self, now: CtapInstant) -> bool {
self.check_expiration(now);
if self.has_up.is_granted(now) {
self.has_up = TimedPermission::Waiting;
true
} else {
self.needs_up = TimedPermission::granted(now, self.request_duration);
false
}
}
// Returns if user presence was requested. Also cleans up.
pub fn is_up_needed(&mut self, now: CtapInstant) -> bool {
self.check_expiration(now);
self.needs_up.is_granted(now)
}
// If you don't regularly call any other function, not cleaning up leads to overflow problems.
pub fn check_expiration(&mut self, now: CtapInstant) {
self.needs_up = self.needs_up.check_expiration(now);
self.has_up = self.has_up.check_expiration(now);
}
}
#[cfg(feature = "with_ctap1")]
#[cfg(test)]
mod test {
use super::*;
fn zero() -> CtapInstant {
CtapInstant::new(0)
}
fn big_positive() -> CtapInstant {
CtapInstant::new(u64::MAX / 1000 - 1)
}
fn request_duration() -> Milliseconds<u64> {
Milliseconds::new(1000)
}
fn presence_duration() -> Milliseconds<u64> {
Milliseconds::new(1000)
}
fn epsilon() -> Milliseconds<u64> {
Milliseconds::new(1)
}
fn grant_up_when_needed(start_time: CtapInstant) {
let mut u2f_state = U2fUserPresenceState::new(request_duration(), presence_duration());
assert!(!u2f_state.consume_up(start_time));
assert!(u2f_state.is_up_needed(start_time));
u2f_state.grant_up(start_time);
assert!(u2f_state.consume_up(start_time));
assert!(!u2f_state.consume_up(start_time));
}
fn need_up_timeout(start_time: CtapInstant) {
let mut u2f_state = U2fUserPresenceState::new(request_duration(), presence_duration());
assert!(!u2f_state.consume_up(start_time));
assert!(u2f_state.is_up_needed(start_time));
// The timeout excludes equality, so it should be over at this instant.
assert!(!u2f_state.is_up_needed(
start_time
.checked_add(presence_duration() + epsilon())
.unwrap()
));
}
fn grant_up_timeout(start_time: CtapInstant) {
let mut u2f_state = U2fUserPresenceState::new(request_duration(), presence_duration());
assert!(!u2f_state.consume_up(start_time));
assert!(u2f_state.is_up_needed(start_time));
u2f_state.grant_up(start_time);
// The timeout excludes equality, so it should be over at this instant.
assert!(!u2f_state.consume_up(
start_time
.checked_add(presence_duration() + epsilon())
.unwrap()
));
}
#[test]
fn test_grant_up_timeout() {
grant_up_timeout(zero());
grant_up_timeout(big_positive());
}
#[test]
fn test_need_up_timeout() {
need_up_timeout(zero());
need_up_timeout(big_positive());
}
#[test]
fn test_grant_up_when_needed() {
grant_up_when_needed(zero());
grant_up_when_needed(big_positive());
}
#[test]
fn test_grant_up_without_need() {
let mut u2f_state = U2fUserPresenceState::new(request_duration(), presence_duration());
u2f_state.grant_up(zero());
assert!(!u2f_state.is_up_needed(zero()));
assert!(!u2f_state.consume_up(zero()));
}
}

View File

@@ -12,22 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::api::clock::Clock;
use crate::ctap::client_pin::PinPermission;
use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::timed_permission::TimedPermission;
use crate::env::Env;
use alloc::string::String;
use crypto::sha256::Sha256;
use crypto::Hash256;
use embedded_time::duration::Milliseconds;
use crate::clock::{ClockInt, CtapInstant};
/// Timeout for auth tokens.
///
/// This usage time limit is correct for USB, BLE, and internal.
/// NFC only allows 19.8 seconds.
/// TODO(#15) multiplex over transports, add NFC
const INITIAL_USAGE_TIME_LIMIT: Milliseconds<ClockInt> = Milliseconds(30000 as ClockInt);
const INITIAL_USAGE_TIME_LIMIT_MS: usize = 30000;
/// Implements pinUvAuthToken state from section 6.5.2.1.
///
@@ -35,22 +33,22 @@ const INITIAL_USAGE_TIME_LIMIT: Milliseconds<ClockInt> = Milliseconds(30000 as C
/// built-in user verification. Therefore, we never cache user presence.
///
/// This implementation does not use a rolling timer.
pub struct PinUvAuthTokenState {
pub struct PinUvAuthTokenState<E: Env> {
// Relies on the fact that all permissions are represented by powers of two.
permissions_set: u8,
permissions_rp_id: Option<String>,
usage_timer: TimedPermission,
usage_timer: <E::Clock as Clock>::Timer,
user_verified: bool,
in_use: bool,
}
impl PinUvAuthTokenState {
impl<E: Env> PinUvAuthTokenState<E> {
/// Creates a pinUvAuthToken state without permissions.
pub fn new() -> PinUvAuthTokenState {
pub fn new() -> Self {
PinUvAuthTokenState {
permissions_set: 0,
permissions_rp_id: None,
usage_timer: TimedPermission::waiting(),
usage_timer: <E::Clock as Clock>::Timer::default(),
user_verified: false,
in_use: false,
}
@@ -113,19 +111,18 @@ impl PinUvAuthTokenState {
}
/// Starts the timer for pinUvAuthToken usage.
pub fn begin_using_pin_uv_auth_token(&mut self, now: CtapInstant) {
pub fn begin_using_pin_uv_auth_token(&mut self, env: &mut E) {
self.user_verified = true;
self.usage_timer = TimedPermission::granted(now, INITIAL_USAGE_TIME_LIMIT);
self.usage_timer = env.clock().make_timer(INITIAL_USAGE_TIME_LIMIT_MS);
self.in_use = true;
}
/// Updates the usage timer, and disables the pinUvAuthToken on timeout.
pub fn pin_uv_auth_token_usage_timer_observer(&mut self, now: CtapInstant) {
pub fn pin_uv_auth_token_usage_timer_observer(&mut self, env: &mut E) {
if !self.in_use {
return;
}
self.usage_timer = self.usage_timer.check_expiration(now);
if !self.usage_timer.is_granted(now) {
if env.clock().is_elapsed(&self.usage_timer) {
self.stop_using_pin_uv_auth_token();
}
}
@@ -149,7 +146,7 @@ impl PinUvAuthTokenState {
pub fn stop_using_pin_uv_auth_token(&mut self) {
self.permissions_rp_id = None;
self.permissions_set = 0;
self.usage_timer = TimedPermission::waiting();
self.usage_timer = <E::Clock as Clock>::Timer::default();
self.user_verified = false;
self.in_use = false;
}
@@ -158,27 +155,28 @@ impl PinUvAuthTokenState {
#[cfg(test)]
mod test {
use super::*;
use crate::env::test::TestEnv;
use enum_iterator::IntoEnumIterator;
#[test]
fn test_observer() {
let mut token_state = PinUvAuthTokenState::new();
let mut now: CtapInstant = CtapInstant::new(0);
token_state.begin_using_pin_uv_auth_token(now);
let mut env = TestEnv::new();
let mut token_state = PinUvAuthTokenState::<TestEnv>::new();
token_state.begin_using_pin_uv_auth_token(&mut env);
assert!(token_state.is_in_use());
now = now + Milliseconds(100_u32);
token_state.pin_uv_auth_token_usage_timer_observer(now);
env.clock().advance(100);
token_state.pin_uv_auth_token_usage_timer_observer(&mut env);
assert!(token_state.is_in_use());
now = now + INITIAL_USAGE_TIME_LIMIT;
token_state.pin_uv_auth_token_usage_timer_observer(now);
env.clock().advance(INITIAL_USAGE_TIME_LIMIT_MS);
token_state.pin_uv_auth_token_usage_timer_observer(&mut env);
assert!(!token_state.is_in_use());
}
#[test]
fn test_stop() {
let mut token_state = PinUvAuthTokenState::new();
let now: CtapInstant = CtapInstant::new(0);
token_state.begin_using_pin_uv_auth_token(now);
let mut env = TestEnv::new();
let mut token_state = PinUvAuthTokenState::<TestEnv>::new();
token_state.begin_using_pin_uv_auth_token(&mut env);
assert!(token_state.is_in_use());
token_state.stop_using_pin_uv_auth_token();
assert!(!token_state.is_in_use());
@@ -186,7 +184,7 @@ mod test {
#[test]
fn test_permissions() {
let mut token_state = PinUvAuthTokenState::new();
let mut token_state = PinUvAuthTokenState::<TestEnv>::new();
token_state.set_permissions(0xFF);
for permission in PinPermission::into_enum_iter() {
assert_eq!(token_state.has_permission(permission), Ok(()));
@@ -211,7 +209,7 @@ mod test {
#[test]
fn test_permissions_rp_id_none() {
let mut token_state = PinUvAuthTokenState::new();
let mut token_state = PinUvAuthTokenState::<TestEnv>::new();
let example_hash = Sha256::hash(b"example.com");
token_state.set_permissions_rp_id(None);
assert_eq!(token_state.has_no_permissions_rp_id(), Ok(()));
@@ -227,7 +225,7 @@ mod test {
#[test]
fn test_permissions_rp_id_some() {
let mut token_state = PinUvAuthTokenState::new();
let mut token_state = PinUvAuthTokenState::<TestEnv>::new();
let example_hash = Sha256::hash(b"example.com");
token_state.set_permissions_rp_id(Some(String::from("example.com")));
@@ -262,14 +260,14 @@ mod test {
#[test]
fn test_user_verified_flag() {
let mut token_state = PinUvAuthTokenState::new();
let mut env = TestEnv::new();
let mut token_state = PinUvAuthTokenState::<TestEnv>::new();
assert!(!token_state.get_user_verified_flag_value());
let now: CtapInstant = CtapInstant::new(0);
token_state.begin_using_pin_uv_auth_token(now);
token_state.begin_using_pin_uv_auth_token(&mut env);
assert!(token_state.get_user_verified_flag_value());
token_state.clear_user_verified_flag();
assert!(!token_state.get_user_verified_flag_value());
token_state.begin_using_pin_uv_auth_token(now);
token_state.begin_using_pin_uv_auth_token(&mut env);
assert!(token_state.get_user_verified_flag_value());
token_state.stop_using_pin_uv_auth_token();
assert!(!token_state.get_user_verified_flag_value());

137
src/ctap/u2f_up.rs Normal file
View File

@@ -0,0 +1,137 @@
// Copyright 2019-2021 Google LLC
//
// Licensed under the Apache License, Version 2 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::TOUCH_TIMEOUT_MS;
use crate::api::clock::Clock;
use crate::env::Env;
const U2F_UP_PROMPT_TIMEOUT_MS: usize = 10000;
pub struct U2fUserPresenceState<E: Env> {
/// If user presence was recently requested, its timeout is saved here.
needs_up: <E::Clock as Clock>::Timer,
/// Button touch timeouts, while user presence is requested, are saved here.
has_up: <E::Clock as Clock>::Timer,
}
impl<E: Env> U2fUserPresenceState<E> {
pub fn new() -> U2fUserPresenceState<E> {
U2fUserPresenceState {
needs_up: <E::Clock as Clock>::Timer::default(),
has_up: <E::Clock as Clock>::Timer::default(),
}
}
/// Allows consuming user presence until timeout, if it was needed.
///
/// If user presence was not requested, granting user presence does nothing.
pub fn grant_up(&mut self, env: &mut E) {
if !env.clock().is_elapsed(&self.needs_up) {
self.needs_up = <E::Clock as Clock>::Timer::default();
self.has_up = env.clock().make_timer(TOUCH_TIMEOUT_MS);
}
}
/// Returns whether user presence was granted within the timeout and not yet consumed.
pub fn consume_up(&mut self, env: &mut E) -> bool {
if !env.clock().is_elapsed(&self.has_up) {
self.has_up = <E::Clock as Clock>::Timer::default();
true
} else {
self.needs_up = env.clock().make_timer(U2F_UP_PROMPT_TIMEOUT_MS);
false
}
}
/// Returns whether user presence was requested.
///
/// This function doesn't represent interaction with the environment, and does not change the
/// state, i.e. neither grants nor consumes user presence.
pub fn is_up_needed(&mut self, env: &mut E) -> bool {
!env.clock().is_elapsed(&self.needs_up)
}
}
#[cfg(feature = "with_ctap1")]
#[cfg(test)]
mod test {
use super::*;
use crate::env::test::TestEnv;
fn big_positive() -> usize {
1000000
}
fn grant_up_when_needed(env: &mut TestEnv) {
let mut u2f_state = U2fUserPresenceState::new();
assert!(!u2f_state.consume_up(env));
assert!(u2f_state.is_up_needed(env));
u2f_state.grant_up(env);
assert!(u2f_state.consume_up(env));
assert!(!u2f_state.consume_up(env));
}
fn need_up_timeout(env: &mut TestEnv) {
let mut u2f_state = U2fUserPresenceState::new();
assert!(!u2f_state.consume_up(env));
assert!(u2f_state.is_up_needed(env));
env.clock().advance(U2F_UP_PROMPT_TIMEOUT_MS);
// The timeout excludes equality, so it should be over at this instant.
assert!(!u2f_state.is_up_needed(env));
}
fn grant_up_timeout(env: &mut TestEnv) {
let mut u2f_state = U2fUserPresenceState::new();
assert!(!u2f_state.consume_up(env));
assert!(u2f_state.is_up_needed(env));
u2f_state.grant_up(env);
env.clock().advance(TOUCH_TIMEOUT_MS);
// The timeout excludes equality, so it should be over at this instant.
assert!(!u2f_state.consume_up(env));
}
#[test]
fn test_grant_up_timeout() {
let mut env = TestEnv::new();
grant_up_timeout(&mut env);
env.clock().advance(big_positive());
grant_up_timeout(&mut env);
}
#[test]
fn test_need_up_timeout() {
let mut env = TestEnv::new();
need_up_timeout(&mut env);
env.clock().advance(big_positive());
need_up_timeout(&mut env);
}
#[test]
fn test_grant_up_when_needed() {
let mut env = TestEnv::new();
grant_up_when_needed(&mut env);
env.clock().advance(big_positive());
grant_up_when_needed(&mut env);
}
#[test]
fn test_grant_up_without_need() {
let mut env = TestEnv::new();
let mut u2f_state = U2fUserPresenceState::new();
u2f_state.grant_up(&mut env);
assert!(!u2f_state.is_up_needed(&mut env));
assert!(!u2f_state.consume_up(&mut env));
}
}

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::clock::CtapInstant;
use crate::ctap::hid::{
CtapHid, CtapHidCommand, CtapHidError, HidPacket, HidPacketIterator, Message,
};
@@ -22,33 +21,32 @@ use crate::env::Env;
/// Implements the non-standard command processing for HID.
///
/// Outside of the pure HID commands like INIT, only PING and CBOR commands are allowed.
pub struct VendorHid {
hid: CtapHid,
pub struct VendorHid<E: Env> {
hid: CtapHid<E>,
}
impl VendorHid {
impl<E: Env> VendorHid<E> {
/// Instantiates a HID handler for CTAP1, CTAP2 and Wink.
pub fn new() -> Self {
let hid = CtapHid::new(CtapHid::CAPABILITY_CBOR | CtapHid::CAPABILITY_NMSG);
let hid = CtapHid::<E>::new(CtapHid::<E>::CAPABILITY_CBOR | CtapHid::<E>::CAPABILITY_NMSG);
VendorHid { hid }
}
/// Processes an incoming USB HID packet, and returns an iterator for all outgoing packets.
pub fn process_hid_packet(
&mut self,
env: &mut impl Env,
env: &mut E,
packet: &HidPacket,
now: CtapInstant,
ctap_state: &mut CtapState,
ctap_state: &mut CtapState<E>,
) -> HidPacketIterator {
if let Some(message) = self.hid.parse_packet(env, packet, now) {
let processed_message = self.process_message(env, message, now, ctap_state);
if let Some(message) = self.hid.parse_packet(env, packet) {
let processed_message = self.process_message(env, message, ctap_state);
debug_ctap!(
env,
"Sending message through the second usage page: {:02x?}",
processed_message
);
CtapHid::split_message(processed_message)
CtapHid::<E>::split_message(processed_message)
} else {
HidPacketIterator::none()
}
@@ -57,19 +55,18 @@ impl VendorHid {
/// Processes a message's commands that affect the protocol outside HID.
pub fn process_message(
&mut self,
env: &mut impl Env,
env: &mut E,
message: Message,
now: CtapInstant,
ctap_state: &mut CtapState,
ctap_state: &mut CtapState<E>,
) -> Message {
let cid = message.cid;
match message.cmd {
// There are no custom CTAP1 commands.
CtapHidCommand::Msg => CtapHid::error_message(cid, CtapHidError::InvalidCmd),
CtapHidCommand::Msg => CtapHid::<E>::error_message(cid, CtapHidError::InvalidCmd),
// The CTAP2 processing function multiplexes internally.
CtapHidCommand::Cbor => {
let response =
ctap_state.process_command(env, &message.payload, Channel::VendorHid(cid), now);
ctap_state.process_command(env, &message.payload, Channel::VendorHid(cid));
Message {
cid,
cmd: CtapHidCommand::Cbor,
@@ -77,7 +74,7 @@ impl VendorHid {
}
}
// Call Wink over the main HID.
CtapHidCommand::Wink => CtapHid::error_message(cid, CtapHidError::InvalidCmd),
CtapHidCommand::Wink => CtapHid::<E>::error_message(cid, CtapHidError::InvalidCmd),
// All other commands have already been processed, keep them as is.
_ => message,
}
@@ -90,27 +87,22 @@ mod test {
use crate::ctap::hid::ChannelID;
use crate::env::test::TestEnv;
fn new_initialized() -> (VendorHid, ChannelID) {
fn new_initialized() -> (VendorHid<TestEnv>, ChannelID) {
let (hid, cid) = CtapHid::new_initialized();
(VendorHid { hid }, cid)
(VendorHid::<TestEnv> { hid }, cid)
}
#[test]
fn test_process_hid_packet() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let (mut vendor_hid, cid) = new_initialized();
let mut ping_packet = [0x00; 64];
ping_packet[..4].copy_from_slice(&cid);
ping_packet[4..9].copy_from_slice(&[0x81, 0x00, 0x02, 0x99, 0x99]);
let mut response = vendor_hid.process_hid_packet(
&mut env,
&ping_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = vendor_hid.process_hid_packet(&mut env, &ping_packet, &mut ctap_state);
assert_eq!(response.next(), Some(ping_packet));
assert_eq!(response.next(), None);
}
@@ -118,26 +110,21 @@ mod test {
#[test]
fn test_process_hid_packet_empty() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let (mut vendor_hid, cid) = new_initialized();
let mut cancel_packet = [0x00; 64];
cancel_packet[..4].copy_from_slice(&cid);
cancel_packet[4..7].copy_from_slice(&[0x91, 0x00, 0x00]);
let mut response = vendor_hid.process_hid_packet(
&mut env,
&cancel_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = vendor_hid.process_hid_packet(&mut env, &cancel_packet, &mut ctap_state);
assert_eq!(response.next(), None);
}
#[test]
fn test_blocked_commands() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let (mut vendor_hid, cid) = new_initialized();
// Usually longer, but we don't parse them anyway.
@@ -153,21 +140,11 @@ mod test {
error_packet[..4].copy_from_slice(&cid);
error_packet[4..8].copy_from_slice(&[0xBF, 0x00, 0x01, 0x01]);
let mut response = vendor_hid.process_hid_packet(
&mut env,
&msg_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = vendor_hid.process_hid_packet(&mut env, &msg_packet, &mut ctap_state);
assert_eq!(response.next(), Some(error_packet));
assert_eq!(response.next(), None);
let mut response = vendor_hid.process_hid_packet(
&mut env,
&wink_packet,
CtapInstant::new(0),
&mut ctap_state,
);
let mut response = vendor_hid.process_hid_packet(&mut env, &wink_packet, &mut ctap_state);
assert_eq!(response.next(), Some(error_packet));
assert_eq!(response.next(), None);
}

5
src/env/mod.rs vendored
View File

@@ -1,4 +1,4 @@
// Copyright 2022 Google LLC
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::api::attestation_store::AttestationStore;
use crate::api::clock::Clock;
use crate::api::connection::HidConnection;
use crate::api::customization::Customization;
use crate::api::firmware_protection::FirmwareProtection;
@@ -38,12 +39,14 @@ pub trait Env {
type Customization: Customization;
type HidConnection: HidConnection;
type AttestationStore: AttestationStore;
type Clock: Clock;
fn rng(&mut self) -> &mut Self::Rng;
fn user_presence(&mut self) -> &mut Self::UserPresence;
fn store(&mut self) -> &mut Store<Self::Storage>;
fn key_store(&mut self) -> &mut Self::KeyStore;
fn attestation_store(&mut self) -> &mut Self::AttestationStore;
fn clock(&mut self) -> &mut Self::Clock;
/// Returns the upgrade storage instance.
///

73
src/env/test/mod.rs vendored
View File

@@ -14,15 +14,14 @@
use self::upgrade_storage::BufferUpgradeStorage;
use crate::api::attestation_store::AttestationStore;
use crate::api::clock::Clock;
use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus};
use crate::api::customization::DEFAULT_CUSTOMIZATION;
use crate::api::firmware_protection::FirmwareProtection;
use crate::api::user_presence::{UserPresence, UserPresenceResult};
use crate::api::{attestation_store, key_store};
use crate::clock::ClockInt;
use crate::env::Env;
use customization::TestCustomization;
use embedded_time::duration::Milliseconds;
use persistent_store::{BufferOptions, BufferStorage, Store};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
@@ -37,6 +36,7 @@ pub struct TestEnv {
store: Store<BufferStorage>,
upgrade_storage: Option<BufferUpgradeStorage>,
customization: TestCustomization,
clock: TestClock,
}
pub struct TestRng256 {
@@ -57,6 +57,43 @@ impl Rng256 for TestRng256 {
}
}
#[derive(Debug, Default, PartialEq)]
pub struct TestTimer {
end_ms: usize,
}
#[derive(Debug, Default)]
pub struct TestClock {
/// The current time, as advanced, in milliseconds.
now_ms: usize,
}
impl TestClock {
pub fn advance(&mut self, milliseconds: usize) {
self.now_ms += milliseconds;
}
}
impl Clock for TestClock {
type Timer = TestTimer;
fn make_timer(&mut self, milliseconds: usize) -> Self::Timer {
TestTimer {
end_ms: self.now_ms + milliseconds,
}
}
fn is_elapsed(&mut self, timer: &Self::Timer) -> bool {
self.now_ms >= timer.end_ms
}
#[cfg(feature = "debug_ctap")]
fn timestamp_us(&mut self) -> usize {
// Unused, but let's implement something because it's easy.
self.now_ms * 1000
}
}
pub struct TestUserPresence {
check: Box<dyn Fn() -> UserPresenceResult>,
}
@@ -85,11 +122,7 @@ fn new_storage() -> BufferStorage {
}
impl HidConnection for TestEnv {
fn send_and_maybe_recv(
&mut self,
_buf: &mut [u8; 64],
_timeout: Milliseconds<ClockInt>,
) -> SendOrRecvResult {
fn send_and_maybe_recv(&mut self, _buf: &mut [u8; 64], _timeout_ms: usize) -> SendOrRecvResult {
// TODO: Implement I/O from canned requests/responses for integration testing.
Ok(SendOrRecvStatus::Sent)
}
@@ -107,12 +140,14 @@ impl TestEnv {
let store = Store::new(storage).ok().unwrap();
let upgrade_storage = Some(BufferUpgradeStorage::new().unwrap());
let customization = DEFAULT_CUSTOMIZATION.into();
let clock = TestClock::default();
TestEnv {
rng,
user_presence,
store,
upgrade_storage,
customization,
clock,
}
}
@@ -137,7 +172,7 @@ impl TestUserPresence {
impl UserPresence for TestUserPresence {
fn check_init(&mut self) {}
fn wait_with_timeout(&mut self, _timeout: Milliseconds<ClockInt>) -> UserPresenceResult {
fn wait_with_timeout(&mut self, _timeout_ms: usize) -> UserPresenceResult {
(self.check)()
}
fn check_complete(&mut self) {}
@@ -174,6 +209,7 @@ impl Env for TestEnv {
type Storage = BufferStorage;
type KeyStore = Self;
type AttestationStore = Self;
type Clock = TestClock;
type UpgradeStorage = BufferUpgradeStorage;
type FirmwareProtection = Self;
type Write = TestWrite;
@@ -200,6 +236,10 @@ impl Env for TestEnv {
self
}
fn clock(&mut self) -> &mut Self::Clock {
&mut self.clock
}
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
self.upgrade_storage.as_mut()
}
@@ -225,3 +265,20 @@ impl Env for TestEnv {
self
}
}
#[cfg(test)]
#[allow(clippy::module_inception)]
mod test {
use super::*;
#[test]
fn test_clock() {
let mut clock = TestClock::default();
let timer = clock.make_timer(3);
assert!(!clock.is_elapsed(&timer));
clock.advance(2);
assert!(!clock.is_elapsed(&timer));
clock.advance(1);
assert!(clock.is_elapsed(&timer));
}
}

118
src/env/tock/clock.rs vendored Normal file
View File

@@ -0,0 +1,118 @@
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::api::clock::Clock;
use libtock_drivers::timer::{get_clock_frequency, get_ticks};
/// 56-bits timestamp (valid for 70k+ years)
#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
struct Timestamp {
epoch: usize, // 32-bits
tick: usize, // 24-bits (32kHz)
}
impl Timestamp {
/// Adds (potentially more than 24 bit of) ticks to this timestamp.
pub fn add_ticks(&mut self, ticks: usize) {
// Saturating should never happen, but it fails gracefully.
let sum = self.tick.saturating_add(ticks);
self.epoch += sum >> 24;
self.tick = sum & 0xff_ffff;
}
}
#[derive(Default)]
pub struct TockTimer {
deadline: Timestamp,
}
/// Clock that produces timers through Tock syscalls.
///
/// To guarantee correctness, you have to call any of its functions at least once per full tick
/// counter wrap. In our case, 24 bit ticks with a 32 kHz frequency wrap after 512 seconds. If you
/// can't guarantee to regularly create or check timers, call tickle at least every 8 minutes.
#[derive(Default)]
pub struct TockClock {
now: Timestamp,
}
impl TockClock {
/// Elapses timers before the clock wraps.
///
/// Call this regularly to timeout reliably despite wrapping clock ticks.
pub fn tickle(&mut self) {
let cur_tick = get_ticks().ok().unwrap();
if cur_tick < self.now.tick {
self.now.epoch += 1;
}
self.now.tick = cur_tick;
}
}
impl Clock for TockClock {
type Timer = TockTimer;
fn make_timer(&mut self, milliseconds: usize) -> Self::Timer {
self.tickle();
let clock_frequency = get_clock_frequency().ok().unwrap();
let delta_tick = match milliseconds.checked_mul(clock_frequency) {
Some(x) => x / 1000,
// All CTAP timeouts are multiples of 100 so far. Worst case we timeout too early.
None => (milliseconds / 100).saturating_mul(clock_frequency / 10),
};
let mut deadline = self.now;
deadline.add_ticks(delta_tick);
Self::Timer { deadline }
}
fn is_elapsed(&mut self, timer: &Self::Timer) -> bool {
self.tickle();
self.now >= timer.deadline
}
#[cfg(feature = "debug_ctap")]
fn timestamp_us(&mut self) -> usize {
let clock_frequency = get_clock_frequency().ok().unwrap();
let total_ticks = 0x100_0000u64 * self.now.epoch as u64 + self.now.tick as u64;
(total_ticks.wrapping_mul(1_000_000u64) / clock_frequency as u64) as usize
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_timestamp_add_ticks() {
let mut timestamp = Timestamp::default();
timestamp.add_ticks(1);
let expected = Timestamp { epoch: 0, tick: 1 };
assert_eq!(timestamp, expected);
timestamp.add_ticks(0xff_ffff);
let expected = Timestamp { epoch: 1, tick: 0 };
assert_eq!(timestamp, expected);
timestamp.add_ticks(0x100_0000);
let expected = Timestamp { epoch: 2, tick: 0 };
assert_eq!(timestamp, expected);
timestamp.add_ticks(0x1ff_ffff);
let expected = Timestamp {
epoch: 3,
tick: 0xff_ffff,
};
assert_eq!(timestamp, expected);
timestamp.add_ticks(1);
let expected = Timestamp { epoch: 4, tick: 0 };
assert_eq!(timestamp, expected);
}
}

29
src/env/tock/mod.rs vendored
View File

@@ -19,12 +19,10 @@ use crate::api::customization::{CustomizationImpl, DEFAULT_CUSTOMIZATION};
use crate::api::firmware_protection::FirmwareProtection;
use crate::api::user_presence::{UserPresence, UserPresenceError, UserPresenceResult};
use crate::api::{attestation_store, key_store};
use crate::clock::{ClockInt, KEEPALIVE_DELAY_MS};
use crate::env::Env;
use clock::TockClock;
use core::cell::Cell;
use core::sync::atomic::{AtomicBool, Ordering};
use embedded_time::duration::Milliseconds;
use embedded_time::fixed_point::FixedPoint;
use libtock_core::result::{CommandError, EALREADY};
use libtock_drivers::buttons::{self, ButtonState};
use libtock_drivers::console::Console;
@@ -35,6 +33,7 @@ use libtock_drivers::{crp, led, timer};
use persistent_store::{StorageResult, Store};
use rng256::TockRng256;
mod clock;
mod storage;
pub struct TockHidConnection {
@@ -42,14 +41,10 @@ pub struct TockHidConnection {
}
impl HidConnection for TockHidConnection {
fn send_and_maybe_recv(
&mut self,
buf: &mut [u8; 64],
timeout: Milliseconds<ClockInt>,
) -> SendOrRecvResult {
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult {
match usb_ctap_hid::send_or_recv_with_timeout(
buf,
timer::Duration::from_ms(timeout.integer() as isize),
Duration::from_ms(timeout_ms as isize),
self.endpoint,
) {
Ok(usb_ctap_hid::SendOrRecvStatus::Timeout) => Ok(SendOrRecvStatus::Timeout),
@@ -70,6 +65,7 @@ pub struct TockEnv {
#[cfg(feature = "vendor_hid")]
vendor_connection: TockHidConnection,
blink_pattern: usize,
clock: TockClock,
}
impl TockEnv {
@@ -95,6 +91,7 @@ impl TockEnv {
endpoint: UsbEndpoint::VendorHid,
},
blink_pattern: 0,
clock: TockClock::default(),
}
}
}
@@ -115,8 +112,9 @@ impl UserPresence for TockEnv {
fn check_init(&mut self) {
self.blink_pattern = 0;
}
fn wait_with_timeout(&mut self, timeout: Milliseconds<ClockInt>) -> UserPresenceResult {
if timeout.integer() == 0 {
fn wait_with_timeout(&mut self, timeout_ms: usize) -> UserPresenceResult {
if timeout_ms == 0 {
return Err(UserPresenceError::Timeout);
}
blink_leds(self.blink_pattern);
@@ -141,7 +139,7 @@ impl UserPresence for TockEnv {
});
let mut keepalive = keepalive_callback.init().flex_unwrap();
let keepalive_alarm = keepalive
.set_alarm(timer::Duration::from_ms(timeout.integer() as isize))
.set_alarm(Duration::from_ms(timeout_ms as isize))
.flex_unwrap();
// Wait for a button touch or an alarm.
@@ -224,6 +222,7 @@ impl Env for TockEnv {
type Storage = TockStorage;
type KeyStore = Self;
type AttestationStore = Self;
type Clock = TockClock;
type UpgradeStorage = TockUpgradeStorage;
type FirmwareProtection = Self;
type Write = Console;
@@ -250,6 +249,10 @@ impl Env for TockEnv {
self
}
fn clock(&mut self) -> &mut Self::Clock {
&mut self.clock
}
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
self.upgrade_storage.as_mut()
}
@@ -324,5 +327,3 @@ pub fn switch_off_leds() {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
pub const KEEPALIVE_DELAY_TOCK: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS as isize);

View File

@@ -25,7 +25,6 @@ use crate::ctap::vendor_hid::VendorHid;
use crate::ctap::CtapState;
pub use crate::ctap::Transport;
use crate::env::Env;
use clock::CtapInstant;
// Those macros should eventually be split into trace, debug, info, warn, and error macros when
// adding either the defmt or log feature and crate dependency.
@@ -45,7 +44,6 @@ macro_rules! debug_ctap {
}
pub mod api;
pub mod clock;
// TODO(kaczmarczyck): Refactor this so that ctap module isn't public.
pub mod ctap;
pub mod env;
@@ -55,18 +53,18 @@ pub mod test_helpers;
/// CTAP implementation parameterized by its environment.
pub struct Ctap<E: Env> {
env: E,
state: CtapState,
hid: MainHid,
state: CtapState<E>,
hid: MainHid<E>,
#[cfg(feature = "vendor_hid")]
vendor_hid: VendorHid,
vendor_hid: VendorHid<E>,
}
impl<E: Env> Ctap<E> {
/// Instantiates a CTAP implementation given its environment.
// This should only take the environment, but it temporarily takes the boot time until the
// clock is part of the environment.
pub fn new(mut env: E, now: CtapInstant) -> Self {
let state = CtapState::new(&mut env, now);
pub fn new(mut env: E) -> Self {
let state = CtapState::<E>::new(&mut env);
let hid = MainHid::new();
#[cfg(feature = "vendor_hid")]
let vendor_hid = VendorHid::new();
@@ -79,11 +77,11 @@ impl<E: Env> Ctap<E> {
}
}
pub fn state(&mut self) -> &mut CtapState {
pub fn state(&mut self) -> &mut CtapState<E> {
&mut self.state
}
pub fn hid(&mut self) -> &mut MainHid {
pub fn hid(&mut self) -> &mut MainHid<E> {
&mut self.hid
}
@@ -95,23 +93,35 @@ impl<E: Env> Ctap<E> {
&mut self,
packet: &HidPacket,
transport: Transport,
now: CtapInstant,
) -> HidPacketIterator {
match transport {
Transport::MainHid => {
self.hid
.process_hid_packet(&mut self.env, packet, now, &mut self.state)
.process_hid_packet(&mut self.env, packet, &mut self.state)
}
#[cfg(feature = "vendor_hid")]
Transport::VendorHid => {
self.vendor_hid
.process_hid_packet(&mut self.env, packet, now, &mut self.state)
.process_hid_packet(&mut self.env, packet, &mut self.state)
}
}
}
pub fn update_timeouts(&mut self, now: CtapInstant) {
self.state.update_timeouts(now);
self.hid.update_wink_timeout(now);
pub fn update_timeouts(&mut self) {
self.state.update_timeouts(&mut self.env);
}
pub fn should_wink(&mut self) -> bool {
self.hid.should_wink(&mut self.env)
}
#[cfg(feature = "with_ctap1")]
pub fn u2f_grant_user_presence(&mut self) {
self.state.u2f_grant_user_presence(&mut self.env)
}
#[cfg(feature = "with_ctap1")]
pub fn u2f_needs_user_presence(&mut self) -> bool {
self.state.u2f_needs_user_presence(&mut self.env)
}
}

View File

@@ -24,22 +24,16 @@ extern crate lang_items;
#[cfg(feature = "with_ctap1")]
use core::cell::Cell;
#[cfg(feature = "debug_ctap")]
use core::convert::TryFrom;
use core::convert::TryInto;
#[cfg(feature = "debug_ctap")]
use core::fmt::Write;
use ctap2::api::clock::Clock;
use ctap2::api::connection::{HidConnection, SendOrRecvStatus};
#[cfg(feature = "debug_ctap")]
use ctap2::clock::CtapClock;
use ctap2::clock::{new_clock, Clock, ClockInt, KEEPALIVE_DELAY, KEEPALIVE_DELAY_MS};
use ctap2::ctap::hid::HidPacketIterator;
use ctap2::ctap::KEEPALIVE_DELAY_MS;
#[cfg(feature = "with_ctap1")]
use ctap2::env::tock::blink_leds;
use ctap2::env::tock::{switch_off_leds, wink_leds, TockEnv};
use ctap2::env::Env;
use ctap2::Transport;
#[cfg(feature = "debug_ctap")]
use embedded_time::duration::Microseconds;
use embedded_time::duration::Milliseconds;
#[cfg(feature = "with_ctap1")]
use libtock_drivers::buttons::{self, ButtonState};
#[cfg(feature = "debug_ctap")]
@@ -51,8 +45,8 @@ use usb_ctap_hid::UsbEndpoint;
libtock_core::stack_size! {0x4000}
const SEND_TIMEOUT: Milliseconds<ClockInt> = Milliseconds(1000);
const KEEPALIVE_DELAY_TOCK: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS as isize);
const SEND_TIMEOUT_MS: usize = 1000;
const KEEPALIVE_DELAY_MS_TOCK: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS as isize);
#[cfg(not(feature = "vendor_hid"))]
const NUM_ENDPOINTS: usize = 1;
@@ -113,20 +107,18 @@ impl EndpointReplies {
None
}
}
fn main() {
let clock = new_clock();
fn main() {
// Setup USB driver.
if !usb_ctap_hid::setup() {
panic!("Cannot setup USB driver");
}
let boot_time = clock.try_now().unwrap();
let env = TockEnv::new();
let mut ctap = ctap2::Ctap::new(env, boot_time);
let mut ctap = ctap2::Ctap::new(env);
let mut led_counter = 0;
let mut last_led_increment = boot_time;
let mut led_blink_timer = <<TockEnv as Env>::Clock as Clock>::Timer::default();
let mut replies = EndpointReplies::new();
@@ -159,21 +151,27 @@ fn main() {
if let Some(mut packet) = replies.next_packet() {
// send and receive.
let hid_connection = packet.transport.hid_connection(ctap.env());
match hid_connection.send_and_maybe_recv(&mut packet.packet, SEND_TIMEOUT) {
match hid_connection.send_and_maybe_recv(&mut packet.packet, SEND_TIMEOUT_MS) {
Ok(SendOrRecvStatus::Timeout) => {
#[cfg(feature = "debug_ctap")]
print_packet_notice("Sending packet timed out", &clock);
print_packet_notice(
"Sending packet timed out",
ctap.env().clock().timestamp_us(),
);
// TODO: reset the ctap_hid state.
// Since sending the packet timed out, we cancel this reply.
break;
}
Ok(SendOrRecvStatus::Sent) => {
#[cfg(feature = "debug_ctap")]
print_packet_notice("Sent packet", &clock);
print_packet_notice("Sent packet", ctap.env().clock().timestamp_us());
}
Ok(SendOrRecvStatus::Received(ep)) => {
#[cfg(feature = "debug_ctap")]
print_packet_notice("Received another packet", &clock);
print_packet_notice(
"Received another packet",
ctap.env().clock().timestamp_us(),
);
usb_endpoint = Some(ep);
// Copy to incoming packet to local buffer to be consistent
@@ -185,12 +183,12 @@ fn main() {
} else {
// receive
usb_endpoint =
match usb_ctap_hid::recv_with_timeout(&mut pkt_request, KEEPALIVE_DELAY_TOCK)
match usb_ctap_hid::recv_with_timeout(&mut pkt_request, KEEPALIVE_DELAY_MS_TOCK)
.flex_unwrap()
{
usb_ctap_hid::SendOrRecvStatus::Received(endpoint) => {
#[cfg(feature = "debug_ctap")]
print_packet_notice("Received packet", &clock);
print_packet_notice("Received packet", ctap.env().clock().timestamp_us());
Some(endpoint)
}
usb_ctap_hid::SendOrRecvStatus::Sent => {
@@ -200,11 +198,10 @@ fn main() {
};
}
let now = clock.try_now().unwrap();
#[cfg(feature = "with_ctap1")]
{
if button_touched.get() {
ctap.state().u2f_grant_user_presence(now);
ctap.u2f_grant_user_presence();
}
// Cleanup button callbacks. We miss button presses while processing though.
// Heavy computation mostly follows a registered touch luckily. Unregistering
@@ -218,7 +215,8 @@ fn main() {
// These calls are making sure that even for long inactivity, wrapping clock values
// don't cause problems with timers.
ctap.update_timeouts(now);
ctap.update_timeouts();
ctap.env().clock().tickle();
if let Some(endpoint) = usb_endpoint {
let transport = match endpoint {
@@ -226,7 +224,7 @@ fn main() {
#[cfg(feature = "vendor_hid")]
UsbEndpoint::VendorHid => Transport::VendorHid,
};
let reply = ctap.process_hid_packet(&pkt_request, transport, now);
let reply = ctap.process_hid_packet(&pkt_request, transport);
if reply.has_data() {
// Update endpoint with the reply.
for ep in replies.replies.iter_mut() {
@@ -247,28 +245,20 @@ fn main() {
}
}
let now = clock.try_now().unwrap();
if let Some(wait_duration) = now.checked_duration_since(&last_led_increment) {
let wait_duration: Milliseconds<ClockInt> = wait_duration.try_into().unwrap();
if wait_duration > KEEPALIVE_DELAY {
// Loops quickly when waiting for U2F user presence, so the next LED blink
// state is only set if enough time has elapsed.
led_counter += 1;
last_led_increment = now;
}
} else {
// This branch means the clock frequency changed. This should never happen.
if ctap.env().clock().is_elapsed(&led_blink_timer) {
// Loops quickly when waiting for U2F user presence, so the next LED blink
// state is only set if enough time has elapsed.
led_counter += 1;
last_led_increment = now;
led_blink_timer = ctap.env().clock().make_timer(KEEPALIVE_DELAY_MS)
}
if ctap.hid().should_wink(now) {
if ctap.should_wink() {
wink_leds(led_counter);
} else {
#[cfg(not(feature = "with_ctap1"))]
switch_off_leds();
#[cfg(feature = "with_ctap1")]
if ctap.state().u2f_needs_user_presence(now) {
if ctap.u2f_needs_user_presence() {
// Flash the LEDs with an almost regular pattern. The inaccuracy comes from
// delay caused by processing and sending of packets.
blink_leds(led_counter);
@@ -280,11 +270,7 @@ fn main() {
}
#[cfg(feature = "debug_ctap")]
fn print_packet_notice(notice_text: &str, clock: &CtapClock) {
let now = clock.try_now().unwrap();
let now_us = Microseconds::<u64>::try_from(now.duration_since_epoch())
.unwrap()
.0;
fn print_packet_notice(notice_text: &str, now_us: usize) {
writeln!(
Console::new(),
"{} at {}.{:06} s",

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::clock::CtapInstant;
use crate::ctap::command::{
AuthenticatorAttestationMaterial, AuthenticatorConfigParameters,
AuthenticatorVendorConfigureParameters, Command,
@@ -28,9 +27,9 @@ const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]);
#[cfg(feature = "vendor_hid")]
const VENDOR_CHANNEL: Channel = Channel::VendorHid([0x12, 0x34, 0x56, 0x78]);
pub fn enable_enterprise_attestation(
state: &mut CtapState,
env: &mut impl Env,
pub fn enable_enterprise_attestation<E: Env>(
state: &mut CtapState<E>,
env: &mut E,
) -> Result<AuthenticatorAttestationMaterial, Ctap2StatusCode> {
let dummy_key = [0x41; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
let dummy_cert = vec![0xdd; 20];
@@ -47,7 +46,7 @@ pub fn enable_enterprise_attestation(
#[cfg(not(feature = "vendor_hid"))]
let vendor_channel = DUMMY_CHANNEL;
let vendor_command = Command::AuthenticatorVendorConfigure(configure_params);
state.process_parsed_command(env, vendor_command, vendor_channel, CtapInstant::new(0))?;
state.process_parsed_command(env, vendor_command, vendor_channel)?;
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::EnableEnterpriseAttestation,
@@ -56,7 +55,7 @@ pub fn enable_enterprise_attestation(
pin_uv_auth_protocol: None,
};
let config_command = Command::AuthenticatorConfig(config_params);
state.process_parsed_command(env, config_command, DUMMY_CHANNEL, CtapInstant::new(0))?;
state.process_parsed_command(env, config_command, DUMMY_CHANNEL)?;
Ok(attestation_material)
}

View File

@@ -41,6 +41,24 @@ pub fn sleep(duration: Duration<isize>) -> TockResult<()> {
}
}
pub fn get_ticks() -> TockResult<usize> {
Ok(syscalls::command(
DRIVER_NUMBER,
command_nr::GET_CLOCK_VALUE,
0,
0,
)?)
}
pub fn get_clock_frequency() -> TockResult<usize> {
Ok(syscalls::command(
DRIVER_NUMBER,
command_nr::GET_CLOCK_FREQUENCY,
0,
0,
)?)
}
pub fn with_callback<CB>(callback: CB) -> WithCallback<'static, CB> {
WithCallback {
callback,