From b33ffb7979044327f6717b23b08e810d76ec823d Mon Sep 17 00:00:00 2001 From: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com> Date: Tue, 5 Apr 2022 05:52:33 +0200 Subject: [PATCH] Vendor HID for libtock-drivers (#452) * multi HID interface for libtock-drivers * proper u8 print --- Cargo.toml | 2 +- src/env/tock/mod.rs | 27 +-- src/main.rs | 25 ++- third_party/libtock-drivers/Cargo.toml | 1 + .../libtock-drivers/src/usb_ctap_hid.rs | 166 ++++++------------ 5 files changed, 86 insertions(+), 135 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f2509c..23d309a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ std = ["crypto/std", "lang_items/std", "persistent_store/std"] verbose = ["debug_ctap", "libtock_drivers/verbose_usb"] with_ctap1 = ["crypto/with_ctap1"] with_nfc = ["libtock_drivers/with_nfc"] -vendor_hid = [] +vendor_hid = ["libtock_drivers/vendor_hid"] fuzz = ["arbitrary", "std"] [dev-dependencies] diff --git a/src/env/tock/mod.rs b/src/env/tock/mod.rs index 4801a95..8c46b0d 100644 --- a/src/env/tock/mod.rs +++ b/src/env/tock/mod.rs @@ -1,6 +1,6 @@ pub use self::storage::{TockStorage, TockUpgradeStorage}; use crate::api::firmware_protection::FirmwareProtection; -use crate::ctap::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket}; +use crate::ctap::hid::{CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket}; use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::Channel; use crate::env::{Env, UserPresence}; @@ -56,11 +56,7 @@ pub fn take_storage() -> StorageResult { impl UserPresence for TockEnv { fn check(&mut self, channel: Channel) -> Result<(), Ctap2StatusCode> { - match channel { - Channel::MainHid(cid) => check_user_presence(self, cid), - #[cfg(feature = "vendor_hid")] - Channel::VendorHid(cid) => check_user_presence(self, cid), - } + check_user_presence(self, channel) } } @@ -113,12 +109,17 @@ impl Env for TockEnv { // Returns whether the keepalive was sent, or false if cancelled. fn send_keepalive_up_needed( env: &mut TockEnv, - cid: ChannelID, + channel: Channel, timeout: Duration, ) -> Result<(), Ctap2StatusCode> { + let (interface, cid) = match channel { + Channel::MainHid(cid) => (usb_ctap_hid::UsbInterface::MainHid, cid), + #[cfg(feature = "vendor_hid")] + Channel::VendorHid(cid) => (usb_ctap_hid::UsbInterface::VendorHid, cid), + }; 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); + let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt, timeout, interface); match status { None => { debug_ctap!(env, "Sending a KEEPALIVE packet timed out"); @@ -128,10 +129,10 @@ fn send_keepalive_up_needed( Some(usb_ctap_hid::SendOrRecvStatus::Sent) => { debug_ctap!(env, "Sent KEEPALIVE packet"); } - Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + Some(usb_ctap_hid::SendOrRecvStatus::Received(received_interface)) => { // 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 { + if received_interface != interface || received_cid != &cid { debug_ctap!( env, "Received a packet on channel ID {:?} while sending a KEEPALIVE packet", @@ -218,13 +219,13 @@ pub fn switch_off_leds() { const KEEPALIVE_DELAY_MS: isize = 100; pub const KEEPALIVE_DELAY_TOCK: Duration = Duration::from_ms(KEEPALIVE_DELAY_MS); -fn check_user_presence(env: &mut TockEnv, cid: ChannelID) -> Result<(), Ctap2StatusCode> { +fn check_user_presence(env: &mut TockEnv, channel: Channel) -> 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(env, cid, KEEPALIVE_DELAY_TOCK)?; + send_keepalive_up_needed(env, channel, KEEPALIVE_DELAY_TOCK)?; // Listen to the button presses. let button_touched = Cell::new(false); @@ -274,7 +275,7 @@ fn check_user_presence(env: &mut TockEnv, cid: ChannelID) -> Result<(), Ctap2Sta // 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(env, cid, KEEPALIVE_DELAY_TOCK); + keepalive_response = send_keepalive_up_needed(env, channel, KEEPALIVE_DELAY_TOCK); } if button_touched.get() || keepalive_response.is_err() { diff --git a/src/main.rs b/src/main.rs index 26788b6..53a4e90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,15 +89,15 @@ fn main() { } let mut pkt_request = [0; 64]; - let has_packet = + let usb_interface = match usb_ctap_hid::recv_with_timeout(&mut pkt_request, KEEPALIVE_DELAY_TOCK) { - Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + Some(usb_ctap_hid::SendOrRecvStatus::Received(interface)) => { #[cfg(feature = "debug_ctap")] print_packet_notice("Received packet", &clock); - true + Some(interface) } Some(_) => panic!("Error receiving packet"), - None => false, + None => None, }; let now = clock.try_now().unwrap(); @@ -120,11 +120,20 @@ fn main() { // don't cause problems with timers. ctap.update_timeouts(now); - if has_packet { - let reply = ctap.process_hid_packet(&pkt_request, Transport::MainHid, now); + if let Some(interface) = usb_interface { + let transport = match interface { + usb_ctap_hid::UsbInterface::MainHid => Transport::MainHid, + #[cfg(feature = "vendor_hid")] + usb_ctap_hid::UsbInterface::VendorHid => Transport::VendorHid, + }; + let reply = ctap.process_hid_packet(&pkt_request, transport, now); // This block handles sending packets. for mut pkt_reply in reply { - let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt_reply, SEND_TIMEOUT); + let status = usb_ctap_hid::send_or_recv_with_timeout( + &mut pkt_reply, + SEND_TIMEOUT, + interface, + ); match status { None => { #[cfg(feature = "debug_ctap")] @@ -138,7 +147,7 @@ fn main() { #[cfg(feature = "debug_ctap")] print_packet_notice("Sent packet", &clock); } - Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + Some(usb_ctap_hid::SendOrRecvStatus::Received(_)) => { #[cfg(feature = "debug_ctap")] print_packet_notice("Received an UNEXPECTED packet", &clock); // TODO: handle this unexpected packet. diff --git a/third_party/libtock-drivers/Cargo.toml b/third_party/libtock-drivers/Cargo.toml index 41e5c32..d70cd4f 100644 --- a/third_party/libtock-drivers/Cargo.toml +++ b/third_party/libtock-drivers/Cargo.toml @@ -13,5 +13,6 @@ libtock_core = { path = "../../third_party/libtock-rs/core" } [features] debug_ctap = [] +vendor_hid = [] verbose_usb = ["debug_ctap"] with_nfc=[] diff --git a/third_party/libtock-drivers/src/usb_ctap_hid.rs b/third_party/libtock-drivers/src/usb_ctap_hid.rs index 440b039..15b0a47 100644 --- a/third_party/libtock-drivers/src/usb_ctap_hid.rs +++ b/third_party/libtock-drivers/src/usb_ctap_hid.rs @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC +// Copyright 2019-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. @@ -28,14 +28,14 @@ const DRIVER_NUMBER: usize = 0x20009; mod command_nr { pub const CHECK: usize = 0; pub const CONNECT: usize = 1; - pub const TRANSMIT: usize = 2; + pub const _TRANSMIT: usize = 2; pub const RECEIVE: usize = 3; pub const TRANSMIT_OR_RECEIVE: usize = 4; pub const CANCEL: usize = 5; } mod subscribe_nr { - pub const TRANSMIT: usize = 1; + pub const _TRANSMIT: usize = 1; pub const RECEIVE: usize = 2; pub const TRANSMIT_OR_RECEIVE: usize = 3; pub mod callback_status { @@ -45,7 +45,7 @@ mod subscribe_nr { } mod allow_nr { - pub const TRANSMIT: usize = 1; + pub const _TRANSMIT: usize = 1; pub const RECEIVE: usize = 2; pub const TRANSMIT_OR_RECEIVE: usize = 3; } @@ -64,114 +64,23 @@ pub fn setup() -> bool { true } -#[allow(dead_code)] -pub fn recv(buf: &mut [u8; 64]) -> bool { - let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf); - if result.is_err() { - return false; - } - - let done = Cell::new(false); - let mut alarm = || done.set(true); - let subscription = syscalls::subscribe::( - DRIVER_NUMBER, - subscribe_nr::RECEIVE, - &mut alarm, - ); - if subscription.is_err() { - return false; - } - - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::RECEIVE, 0, 0); - if result_code.is_err() { - return false; - } - - util::yieldk_for(|| done.get()); - true -} - -#[allow(dead_code)] -pub fn send(buf: &mut [u8; 64]) -> bool { - let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT, buf); - if result.is_err() { - return false; - } - - let done = Cell::new(false); - let mut alarm = || done.set(true); - let subscription = syscalls::subscribe::( - DRIVER_NUMBER, - subscribe_nr::TRANSMIT, - &mut alarm, - ); - if subscription.is_err() { - return false; - } - - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::TRANSMIT, 0, 0); - if result_code.is_err() { - return false; - } - - util::yieldk_for(|| done.get()); - true +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum UsbInterface { + MainHid = 0, + #[cfg(feature = "vendor_hid")] + VendorHid = 1, } #[derive(Clone, Copy, PartialEq, Eq)] pub enum SendOrRecvStatus { Error, Sent, - Received, + Received(UsbInterface), } -// Either sends or receive a packet. -// Because USB transactions are initiated by the host, we don't decide whether an IN transaction -// (send for us), an OUT transaction (receive for us), or no transaction at all will happen next. -// -// - If an IN transaction happens first, the initial content of buf is sent to the host and the -// Sent status is returned. -// - If an OUT transaction happens first, the content of buf is replaced by the packet received -// from the host and Received status is returned. In that case, the original content of buf is not -// sent to the host, and it's up to the caller to retry sending or to handle the packet received -// from the host. -#[allow(dead_code)] -pub fn send_or_recv(buf: &mut [u8; 64]) -> SendOrRecvStatus { - let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT_OR_RECEIVE, buf); - if result.is_err() { - return SendOrRecvStatus::Error; - } - - let status = Cell::new(None); - let mut alarm = |direction| { - status.set(Some(match direction { - subscribe_nr::callback_status::TRANSMITTED => SendOrRecvStatus::Sent, - subscribe_nr::callback_status::RECEIVED => SendOrRecvStatus::Received, - // Unknown direction sent by the kernel. - _ => SendOrRecvStatus::Error, - })); - }; - - let subscription = syscalls::subscribe::( - DRIVER_NUMBER, - subscribe_nr::TRANSMIT_OR_RECEIVE, - &mut alarm, - ); - if subscription.is_err() { - return SendOrRecvStatus::Error; - } - - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::TRANSMIT_OR_RECEIVE, 0, 0); - if result_code.is_err() { - return SendOrRecvStatus::Error; - } - - util::yieldk_for(|| status.get().is_some()); - status.get().unwrap() -} - -// Same as recv, but with a timeout. -// If the timeout elapses, return None. +/// Waits to receive a packet. +/// +/// Returns None if the transaction timed out, else its status. #[allow(clippy::let_and_return)] pub fn recv_with_timeout( buf: &mut [u8; 64], @@ -188,19 +97,36 @@ pub fn recv_with_timeout( let result = recv_with_timeout_detail(buf, timeout_delay); #[cfg(feature = "verbose_usb")] - if let Some(SendOrRecvStatus::Received) = result { - writeln!(Console::new(), "Received packet = {:02x?}", buf as &[u8]).unwrap(); + if let Some(SendOrRecvStatus::Received(interface)) = result { + writeln!( + Console::new(), + "Received packet = {:02x?} on interface {}", + buf as &[u8], + interface as u8, + ) + .unwrap(); } result } -// Same as send_or_recv, but with a timeout. -// If the timeout elapses, return None. +/// Either sends or receives a packet within a given time. +/// +/// Because USB transactions are initiated by the host, we don't decide whether an IN transaction +/// (send for us), an OUT transaction (receive for us), or no transaction at all will happen next. +/// +/// - If an IN transaction happens first, the initial content of buf is sent to the host and the +/// Sent status is returned. +/// - If an OUT transaction happens first, the content of buf is replaced by the packet received +/// from the host and Received status is returned. In that case, the original content of buf is not +/// sent to the host, and it's up to the caller to retry sending or to handle the packet received +/// from the host. +/// If the timeout elapses, return None. #[allow(clippy::let_and_return)] pub fn send_or_recv_with_timeout( buf: &mut [u8; 64], timeout_delay: Duration, + interface: UsbInterface, ) -> Option { #[cfg(feature = "verbose_usb")] writeln!( @@ -211,11 +137,17 @@ pub fn send_or_recv_with_timeout( ) .unwrap(); - let result = send_or_recv_with_timeout_detail(buf, timeout_delay); + let result = send_or_recv_with_timeout_detail(buf, timeout_delay, interface); #[cfg(feature = "verbose_usb")] - if let Some(SendOrRecvStatus::Received) = result { - writeln!(Console::new(), "Received packet = {:02x?}", buf as &[u8]).unwrap(); + if let Some(SendOrRecvStatus::Received(received_interface)) = result { + writeln!( + Console::new(), + "Received packet = {:02x?} on interface {}", + buf as &[u8], + received_interface as u8, + ) + .unwrap(); } result @@ -233,7 +165,10 @@ fn recv_with_timeout_detail( let status = Cell::new(None); let mut alarm = |direction| { status.set(Some(match direction { - subscribe_nr::callback_status::RECEIVED => SendOrRecvStatus::Received, + subscribe_nr::callback_status::RECEIVED => { + // TODO: set the correct interface + SendOrRecvStatus::Received(UsbInterface::MainHid) + } // Unknown direction or "transmitted" sent by the kernel. _ => SendOrRecvStatus::Error, })); @@ -324,6 +259,8 @@ fn recv_with_timeout_detail( fn send_or_recv_with_timeout_detail( buf: &mut [u8; 64], timeout_delay: Duration, + // TODO: To be used as part of the syscall. + _interface: UsbInterface, ) -> Option { let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT_OR_RECEIVE, buf); if result.is_err() { @@ -334,7 +271,10 @@ fn send_or_recv_with_timeout_detail( let mut alarm = |direction| { status.set(Some(match direction { subscribe_nr::callback_status::TRANSMITTED => SendOrRecvStatus::Sent, - subscribe_nr::callback_status::RECEIVED => SendOrRecvStatus::Received, + subscribe_nr::callback_status::RECEIVED => { + // TODO: set the correct interface + SendOrRecvStatus::Received(UsbInterface::MainHid) + } // Unknown direction sent by the kernel. _ => SendOrRecvStatus::Error, }));