Merge pull request #430 from ia0/check_user_presence

Move check_user_presence to env::tock
This commit is contained in:
Julien Cretin
2022-03-04 11:33:05 +01:00
committed by GitHub
3 changed files with 229 additions and 223 deletions

234
src/env/tock.rs vendored
View File

@@ -1,35 +1,36 @@
use crypto::rng256::TockRng256;
use crate::ctap::hid::ChannelID;
use crate::ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket};
use crate::ctap::status_code::Ctap2StatusCode;
use crate::env::{Env, UserPresence};
use core::cell::Cell;
#[cfg(feature = "debug_ctap")]
use core::fmt::Write;
use crypto::rng256::TockRng256;
use libtock_core::result::{CommandError, EALREADY};
use libtock_drivers::buttons::{self, ButtonState};
#[cfg(feature = "debug_ctap")]
use libtock_drivers::console::Console;
use libtock_drivers::result::{FlexUnwrap, TockError};
use libtock_drivers::timer::Duration;
use libtock_drivers::{led, timer, usb_ctap_hid};
pub struct TockEnv<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> {
pub struct TockEnv {
rng: TockRng256,
check_user_presence: CheckUserPresence,
}
impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> TockEnv<CheckUserPresence> {
pub fn new(check_user_presence: CheckUserPresence) -> Self {
impl TockEnv {
pub fn new() -> Self {
let rng = TockRng256 {};
TockEnv {
rng,
check_user_presence,
}
TockEnv { rng }
}
}
impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> UserPresence
for TockEnv<CheckUserPresence>
{
impl UserPresence for TockEnv {
fn check(&self, cid: ChannelID) -> Result<(), Ctap2StatusCode> {
(self.check_user_presence)(cid)
check_user_presence(cid)
}
}
impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> Env
for TockEnv<CheckUserPresence>
{
impl Env for TockEnv {
type Rng = TockRng256;
type UserPresence = Self;
@@ -41,3 +42,200 @@ impl<CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> Env
self
}
}
// Returns whether the keepalive was sent, or false if cancelled.
fn send_keepalive_up_needed(
cid: ChannelID,
timeout: Duration<isize>,
) -> Result<(), Ctap2StatusCode> {
let keepalive_msg = CtapHid::keepalive(cid, KeepaliveStatus::UpNeeded);
for mut pkt in keepalive_msg {
let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt, timeout);
match status {
None => {
#[cfg(feature = "debug_ctap")]
writeln!(Console::new(), "Sending a KEEPALIVE packet timed out").unwrap();
// TODO: abort user presence test?
}
Some(usb_ctap_hid::SendOrRecvStatus::Error) => panic!("Error sending KEEPALIVE packet"),
Some(usb_ctap_hid::SendOrRecvStatus::Sent) => {
#[cfg(feature = "debug_ctap")]
writeln!(Console::new(), "Sent KEEPALIVE packet").unwrap();
}
Some(usb_ctap_hid::SendOrRecvStatus::Received) => {
// We only parse one packet, because we only care about CANCEL.
let (received_cid, processed_packet) = CtapHid::process_single_packet(&pkt);
if received_cid != &cid {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
"Received a packet on channel ID {:?} while sending a KEEPALIVE packet",
received_cid,
)
.unwrap();
return Ok(());
}
match processed_packet {
ProcessedPacket::InitPacket { cmd, .. } => {
if cmd == CtapHid::COMMAND_CANCEL {
// We ignore the payload, we can't answer with an error code anyway.
#[cfg(feature = "debug_ctap")]
writeln!(Console::new(), "User presence check cancelled").unwrap();
return Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL);
} else {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
"Discarded packet with command {} received while sending a KEEPALIVE packet",
cmd,
)
.unwrap();
}
}
ProcessedPacket::ContinuationPacket { .. } => {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
"Discarded continuation packet received while sending a KEEPALIVE packet",
)
.unwrap();
}
}
}
}
}
Ok(())
}
pub fn blink_leds(pattern_seed: usize) {
for l in 0..led::count().flex_unwrap() {
if (pattern_seed ^ l).count_ones() & 1 != 0 {
led::get(l).flex_unwrap().on().flex_unwrap();
} else {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
}
pub fn wink_leds(pattern_seed: usize) {
// This generates a "snake" pattern circling through the LEDs.
// Fox example with 4 LEDs the sequence of lit LEDs will be the following.
// 0 1 2 3
// * *
// * * *
// * *
// * * *
// * *
// * * *
// * *
// * * *
// * *
let count = led::count().flex_unwrap();
let a = (pattern_seed / 2) % count;
let b = ((pattern_seed + 1) / 2) % count;
let c = ((pattern_seed + 3) / 2) % count;
for l in 0..count {
// On nRF52840-DK, logically swap LEDs 3 and 4 so that the order of LEDs form a circle.
let k = match l {
2 => 3,
3 => 2,
_ => l,
};
if k == a || k == b || k == c {
led::get(l).flex_unwrap().on().flex_unwrap();
} else {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
}
pub fn switch_off_leds() {
for l in 0..led::count().flex_unwrap() {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
const KEEPALIVE_DELAY_MS: isize = 100;
pub const KEEPALIVE_DELAY: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS);
fn check_user_presence(cid: ChannelID) -> Result<(), Ctap2StatusCode> {
// The timeout is N times the keepalive delay.
const TIMEOUT_ITERATIONS: usize =
crate::ctap::TOUCH_TIMEOUT_MS as usize / KEEPALIVE_DELAY_MS as usize;
// First, send a keep-alive packet to notify that the keep-alive status has changed.
send_keepalive_up_needed(cid, KEEPALIVE_DELAY)?;
// Listen to the button presses.
let button_touched = Cell::new(false);
let mut buttons_callback = buttons::with_callback(|_button_num, state| {
match state {
ButtonState::Pressed => button_touched.set(true),
ButtonState::Released => (),
};
});
let mut buttons = buttons_callback.init().flex_unwrap();
// At the moment, all buttons are accepted. You can customize your setup here.
for mut button in &mut buttons {
button.enable().flex_unwrap();
}
let mut keepalive_response = Ok(());
for i in 0..TIMEOUT_ITERATIONS {
blink_leds(i);
// Setup a keep-alive callback.
let keepalive_expired = Cell::new(false);
let mut keepalive_callback = timer::with_callback(|_, _| {
keepalive_expired.set(true);
});
let mut keepalive = keepalive_callback.init().flex_unwrap();
let keepalive_alarm = keepalive.set_alarm(KEEPALIVE_DELAY).flex_unwrap();
// Wait for a button touch or an alarm.
libtock_drivers::util::yieldk_for(|| button_touched.get() || keepalive_expired.get());
// Cleanup alarm callback.
match keepalive.stop_alarm(keepalive_alarm) {
Ok(()) => (),
Err(TockError::Command(CommandError {
return_code: EALREADY,
..
})) => assert!(keepalive_expired.get()),
Err(_e) => {
#[cfg(feature = "debug_ctap")]
panic!("Unexpected error when stopping alarm: {:?}", _e);
#[cfg(not(feature = "debug_ctap"))]
panic!("Unexpected error when stopping alarm: <error is only visible with the debug_ctap feature>");
}
}
// TODO: this may take arbitrary time. The keepalive_delay should be adjusted accordingly,
// so that LEDs blink with a consistent pattern.
if keepalive_expired.get() {
// Do not return immediately, because we must clean up still.
keepalive_response = send_keepalive_up_needed(cid, KEEPALIVE_DELAY);
}
if button_touched.get() || keepalive_response.is_err() {
break;
}
}
switch_off_leds();
// Cleanup button callbacks.
for mut button in &mut buttons {
button.disable().flex_unwrap();
}
// Returns whether the user was present.
if keepalive_response.is_err() {
keepalive_response
} else if button_touched.get() {
Ok(())
} else {
Err(Ctap2StatusCode::CTAP2_ERR_USER_ACTION_TIMEOUT)
}
}

View File

@@ -24,7 +24,12 @@ use crate::ctap::CtapState;
use crate::env::Env;
use libtock_drivers::timer::ClockValue;
// Implementation details must be public for testing (in particular fuzzing).
#[cfg(feature = "std")]
pub mod ctap;
#[cfg(not(feature = "std"))]
mod ctap;
// Store example binaries use the flash directly. Eventually, they should access it from env::tock.
pub mod embedded_flash;
pub mod env;

View File

@@ -21,19 +21,18 @@ extern crate byteorder;
extern crate core;
extern crate lang_items;
#[cfg(feature = "with_ctap1")]
use core::cell::Cell;
#[cfg(feature = "debug_ctap")]
use core::fmt::Write;
use ctap2::ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket};
use ctap2::ctap::status_code::Ctap2StatusCode;
use ctap2::env::tock::TockEnv;
use libtock_core::result::{CommandError, EALREADY};
use libtock_drivers::buttons;
use libtock_drivers::buttons::ButtonState;
#[cfg(feature = "with_ctap1")]
use ctap2::env::tock::blink_leds;
use ctap2::env::tock::{switch_off_leds, wink_leds, TockEnv, KEEPALIVE_DELAY};
#[cfg(feature = "with_ctap1")]
use libtock_drivers::buttons::{self, ButtonState};
#[cfg(feature = "debug_ctap")]
use libtock_drivers::console::Console;
use libtock_drivers::led;
use libtock_drivers::result::{FlexUnwrap, TockError};
use libtock_drivers::result::FlexUnwrap;
use libtock_drivers::timer;
use libtock_drivers::timer::Duration;
#[cfg(feature = "debug_ctap")]
@@ -44,8 +43,6 @@ use libtock_drivers::usb_ctap_hid;
libtock_core::stack_size! {0x4000}
const KEEPALIVE_DELAY_MS: isize = 100;
const KEEPALIVE_DELAY: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS);
const SEND_TIMEOUT: Duration<isize> = Duration::from_ms(1000);
fn main() {
@@ -60,7 +57,7 @@ fn main() {
}
let boot_time = timer.get_current_clock().flex_unwrap();
let env = TockEnv::new(check_user_presence);
let env = TockEnv::new();
let mut ctap = ctap2::Ctap::new(env, boot_time);
let mut led_counter = 0;
@@ -192,197 +189,3 @@ fn print_packet_notice(notice_text: &str, timer: &Timer) {
)
.unwrap();
}
// Returns whether the keepalive was sent, or false if cancelled.
fn send_keepalive_up_needed(
cid: ChannelID,
timeout: Duration<isize>,
) -> Result<(), Ctap2StatusCode> {
let keepalive_msg = CtapHid::keepalive(cid, KeepaliveStatus::UpNeeded);
for mut pkt in keepalive_msg {
let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt, timeout);
match status {
None => {
#[cfg(feature = "debug_ctap")]
writeln!(Console::new(), "Sending a KEEPALIVE packet timed out").unwrap();
// TODO: abort user presence test?
}
Some(usb_ctap_hid::SendOrRecvStatus::Error) => panic!("Error sending KEEPALIVE packet"),
Some(usb_ctap_hid::SendOrRecvStatus::Sent) => {
#[cfg(feature = "debug_ctap")]
writeln!(Console::new(), "Sent KEEPALIVE packet").unwrap();
}
Some(usb_ctap_hid::SendOrRecvStatus::Received) => {
// We only parse one packet, because we only care about CANCEL.
let (received_cid, processed_packet) = CtapHid::process_single_packet(&pkt);
if received_cid != &cid {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
"Received a packet on channel ID {:?} while sending a KEEPALIVE packet",
received_cid,
)
.unwrap();
return Ok(());
}
match processed_packet {
ProcessedPacket::InitPacket { cmd, .. } => {
if cmd == CtapHid::COMMAND_CANCEL {
// We ignore the payload, we can't answer with an error code anyway.
#[cfg(feature = "debug_ctap")]
writeln!(Console::new(), "User presence check cancelled").unwrap();
return Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL);
} else {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
"Discarded packet with command {} received while sending a KEEPALIVE packet",
cmd,
)
.unwrap();
}
}
ProcessedPacket::ContinuationPacket { .. } => {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
"Discarded continuation packet received while sending a KEEPALIVE packet",
)
.unwrap();
}
}
}
}
}
Ok(())
}
fn blink_leds(pattern_seed: usize) {
for l in 0..led::count().flex_unwrap() {
if (pattern_seed ^ l).count_ones() & 1 != 0 {
led::get(l).flex_unwrap().on().flex_unwrap();
} else {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
}
fn wink_leds(pattern_seed: usize) {
// This generates a "snake" pattern circling through the LEDs.
// Fox example with 4 LEDs the sequence of lit LEDs will be the following.
// 0 1 2 3
// * *
// * * *
// * *
// * * *
// * *
// * * *
// * *
// * * *
// * *
let count = led::count().flex_unwrap();
let a = (pattern_seed / 2) % count;
let b = ((pattern_seed + 1) / 2) % count;
let c = ((pattern_seed + 3) / 2) % count;
for l in 0..count {
// On nRF52840-DK, logically swap LEDs 3 and 4 so that the order of LEDs form a circle.
let k = match l {
2 => 3,
3 => 2,
_ => l,
};
if k == a || k == b || k == c {
led::get(l).flex_unwrap().on().flex_unwrap();
} else {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
}
fn switch_off_leds() {
for l in 0..led::count().flex_unwrap() {
led::get(l).flex_unwrap().off().flex_unwrap();
}
}
fn check_user_presence(cid: ChannelID) -> Result<(), Ctap2StatusCode> {
// The timeout is N times the keepalive delay.
const TIMEOUT_ITERATIONS: usize =
ctap2::ctap::TOUCH_TIMEOUT_MS as usize / KEEPALIVE_DELAY_MS as usize;
// First, send a keep-alive packet to notify that the keep-alive status has changed.
send_keepalive_up_needed(cid, KEEPALIVE_DELAY)?;
// Listen to the button presses.
let button_touched = Cell::new(false);
let mut buttons_callback = buttons::with_callback(|_button_num, state| {
match state {
ButtonState::Pressed => button_touched.set(true),
ButtonState::Released => (),
};
});
let mut buttons = buttons_callback.init().flex_unwrap();
// At the moment, all buttons are accepted. You can customize your setup here.
for mut button in &mut buttons {
button.enable().flex_unwrap();
}
let mut keepalive_response = Ok(());
for i in 0..TIMEOUT_ITERATIONS {
blink_leds(i);
// Setup a keep-alive callback.
let keepalive_expired = Cell::new(false);
let mut keepalive_callback = timer::with_callback(|_, _| {
keepalive_expired.set(true);
});
let mut keepalive = keepalive_callback.init().flex_unwrap();
let keepalive_alarm = keepalive.set_alarm(KEEPALIVE_DELAY).flex_unwrap();
// Wait for a button touch or an alarm.
libtock_drivers::util::yieldk_for(|| button_touched.get() || keepalive_expired.get());
// Cleanup alarm callback.
match keepalive.stop_alarm(keepalive_alarm) {
Ok(()) => (),
Err(TockError::Command(CommandError {
return_code: EALREADY,
..
})) => assert!(keepalive_expired.get()),
Err(_e) => {
#[cfg(feature = "debug_ctap")]
panic!("Unexpected error when stopping alarm: {:?}", _e);
#[cfg(not(feature = "debug_ctap"))]
panic!("Unexpected error when stopping alarm: <error is only visible with the debug_ctap feature>");
}
}
// TODO: this may take arbitrary time. The keepalive_delay should be adjusted accordingly,
// so that LEDs blink with a consistent pattern.
if keepalive_expired.get() {
// Do not return immediately, because we must clean up still.
keepalive_response = send_keepalive_up_needed(cid, KEEPALIVE_DELAY);
}
if button_touched.get() || keepalive_response.is_err() {
break;
}
}
switch_off_leds();
// Cleanup button callbacks.
for mut button in &mut buttons {
button.disable().flex_unwrap();
}
// Returns whether the user was present.
if keepalive_response.is_err() {
keepalive_response
} else if button_touched.get() {
Ok(())
} else {
Err(Ctap2StatusCode::CTAP2_ERR_USER_ACTION_TIMEOUT)
}
}