Skip to content

Commit f533ae0

Browse files
committed
Refactors HID connections to support busy response
The main intended difference is that OpenSK now responds with ERR_CHANNEL_BUSY when it receives a packet while waiting for touch in in a CTAP2 command. To support this more elegantly, we changed `HidConnection` to have a `send` and `recv` instead of `send_and_maybe_recv` for a cleaner API. This also resolves an older TODO to respond to incoming channels on the other USB endpoint. It doesn't process the incoming traffic correctly, but unblock the host at least, and tells it to come back later. We still only allow one active channel at the same time. We now don't need the `TRANSMIT_AND_RECEIVE` syscall anymore. I didn't remove it from the Tock pack for simplicity, but cleaned up libtock-drivers. The Env API changes from having one connection per endpoint, to having one send function that takes an endpoint, and a receive function that receives on all endpoints at the same time. The `HidConnection` already received on all endpoints before, which was inconsistent with the existance of, e.g., `VendorHidConnection` being able to receive on the main endpoint. Since we now have a cleaner send and receive API, we use this in `src/main.rs` and simplify it a bit, while making it more consistent with other calls to the HID API. I found an additional inaccuracy in our implementation while refactoring: We want to send keepalives every 100 ms. To do so, we first wait for a button callback for 100 ms. Then we send the keepalive and check if a cancel packet appeared. What should have happened instead is that we listen for HID packets and button presses at the same time during these 100 ms. If nothing happens that stops the loop, we send the keepalive. The old implementation would, in some implementations, wait 200 ms for each keepalive: First 100 for touch, then 100 for `send_and_maybe_receive`. The new implementation can loop faster in case there is a lot of unrelated HID traffic. To make the touch timeout last 30 seconds, we introduce an extra timer and loop until time is up. This might still make us blink the LEDs too fast, but that is already the case in the main loop, and generally would need a bigger refactoring. The only simple workaround to make the LEDs slightly more precise is to wait for touch and packets until it is time to flip the LEDs, and if we return too early within some thresholds, wait again. If we care enough, we can fix that in a later PR. Not sure how to make the test work that I removed in ctap/mod.rs. I'd need to advance the time while the loop is running. Setting a user presence function that advances time seems hard with the Rust borrowing rules.
1 parent 7da88b0 commit f533ae0

File tree

12 files changed

+248
-395
lines changed

12 files changed

+248
-395
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libraries/opensk/src/api/connection.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ pub struct SendOrRecvError;
4646
pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;
4747

4848
pub trait HidConnection {
49-
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult;
49+
fn send(&mut self, buf: &[u8; 64], endpoint: UsbEndpoint) -> SendOrRecvResult;
50+
fn recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult;
5051
}
5152

5253
#[cfg(test)]

libraries/opensk/src/api/user_presence.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use crate::api::connection::UsbEndpoint;
16+
1517
#[derive(Debug)]
1618
pub enum UserPresenceError {
1719
/// User explicitly declined user presence check.
@@ -24,7 +26,10 @@ pub enum UserPresenceError {
2426
Fail,
2527
}
2628

27-
pub type UserPresenceResult = Result<(), UserPresenceError>;
29+
pub type UserPresenceResult = (
30+
Result<(), UserPresenceError>,
31+
Option<([u8; 64], UsbEndpoint)>,
32+
);
2833

2934
pub trait UserPresence {
3035
/// Initializes for a user presence check.
@@ -35,6 +40,7 @@ pub trait UserPresence {
3540
/// Waits until user presence is confirmed, rejected, or the given timeout expires.
3641
///
3742
/// Must be called between calls to [`Self::check_init`] and [`Self::check_complete`].
43+
/// Additionally returns a packet if one was received during the wait.
3844
fn wait_with_timeout(&mut self, timeout_ms: usize) -> UserPresenceResult;
3945

4046
/// Finalizes a user presence check.

libraries/opensk/src/ctap/hid/mod.rs

+24
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,11 @@ impl<E: Env> CtapHid<E> {
440440
})
441441
}
442442

443+
/// Generates a the HID response packets for a busy error.
444+
pub fn busy_error(cid: ChannelID) -> HidPacketIterator {
445+
Self::split_message(Self::error_message(cid, CtapHidError::ChannelBusy))
446+
}
447+
443448
#[cfg(test)]
444449
pub fn new_initialized() -> (Self, ChannelID) {
445450
(
@@ -633,6 +638,25 @@ mod test {
633638
}
634639
}
635640

641+
#[test]
642+
fn test_busy_error() {
643+
let cid = [0x12, 0x34, 0x56, 0x78];
644+
let mut response = CtapHid::<TestEnv>::busy_error(cid);
645+
let mut expected_packet = [0x00; 64];
646+
expected_packet[..8].copy_from_slice(&[
647+
0x12,
648+
0x34,
649+
0x56,
650+
0x78,
651+
0xBF,
652+
0x00,
653+
0x01,
654+
CtapHidError::ChannelBusy as u8,
655+
]);
656+
assert_eq!(response.next(), Some(expected_packet));
657+
assert_eq!(response.next(), None);
658+
}
659+
636660
#[test]
637661
fn test_process_single_packet() {
638662
let cid = [0x12, 0x34, 0x56, 0x78];

libraries/opensk/src/ctap/mod.rs

+64-116
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ use self::data_formats::{
5050
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
5151
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
5252
};
53-
use self::hid::{ChannelID, CtapHid, CtapHidCommand, KeepaliveStatus, ProcessedPacket};
53+
use self::hid::{
54+
ChannelID, CtapHid, CtapHidCommand, HidPacketIterator, KeepaliveStatus, ProcessedPacket,
55+
};
5456
use self::large_blobs::LargeBlobState;
5557
use self::response::{
5658
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
@@ -149,11 +151,11 @@ pub enum Transport {
149151
}
150152

151153
impl Transport {
152-
pub fn hid_connection<E: Env>(self, env: &mut E) -> &mut E::HidConnection {
154+
pub fn usb_endpoint(self) -> UsbEndpoint {
153155
match self {
154-
Transport::MainHid => env.main_hid_connection(),
156+
Transport::MainHid => UsbEndpoint::MainHid,
155157
#[cfg(feature = "vendor_hid")]
156-
Transport::VendorHid => env.vendor_hid_connection(),
158+
Transport::VendorHid => UsbEndpoint::VendorHid,
157159
}
158160
}
159161
}
@@ -255,120 +257,80 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
255257
}
256258
}
257259

258-
// Sends keepalive packet during user presence checking. If user agent replies with CANCEL response,
259-
// returns Err(UserPresenceError::Canceled).
260-
fn send_keepalive_up_needed<E: Env>(
261-
env: &mut E,
262-
channel: Channel,
263-
timeout_ms: usize,
264-
) -> Result<(), UserPresenceError> {
265-
let (cid, transport) = match channel {
266-
Channel::MainHid(cid) => (cid, Transport::MainHid),
267-
#[cfg(feature = "vendor_hid")]
268-
Channel::VendorHid(cid) => (cid, Transport::VendorHid),
269-
};
270-
let keepalive_msg = CtapHid::<E>::keepalive(cid, KeepaliveStatus::UpNeeded);
271-
for mut pkt in keepalive_msg {
272-
let ctap_hid_connection = transport.hid_connection(env);
273-
match ctap_hid_connection.send_and_maybe_recv(&mut pkt, timeout_ms) {
260+
/// Send non-critical packets using fire-and-forget.
261+
fn send_packets<E: Env>(env: &mut E, endpoint: UsbEndpoint, packets: HidPacketIterator) {
262+
for pkt in packets {
263+
match env.hid_connection().send(&pkt, endpoint) {
274264
Ok(SendOrRecvStatus::Timeout) => {
275-
debug_ctap!(env, "Sending a KEEPALIVE packet timed out");
276-
// The client is likely unresponsive, but let's retry.
277-
}
278-
Err(_) => panic!("Error sending KEEPALIVE packet"),
279-
Ok(SendOrRecvStatus::Sent) => {
280-
debug_ctap!(env, "Sent KEEPALIVE packet");
281-
}
282-
Ok(SendOrRecvStatus::Received(endpoint)) => {
283-
let rx_transport = match endpoint {
284-
UsbEndpoint::MainHid => Transport::MainHid,
285-
#[cfg(feature = "vendor_hid")]
286-
UsbEndpoint::VendorHid => Transport::VendorHid,
287-
};
288-
if rx_transport != transport {
289-
debug_ctap!(
290-
env,
291-
"Received a packet on transport {:?} while sending a KEEPALIVE packet on transport {:?}",
292-
rx_transport, transport
293-
);
294-
// Ignore this packet.
295-
// TODO(liamjm): Support receiving packets on both interfaces.
296-
continue;
297-
}
298-
299-
// We only parse one packet, because we only care about CANCEL.
300-
let (received_cid, processed_packet) = CtapHid::<E>::process_single_packet(&pkt);
301-
if received_cid != cid {
302-
debug_ctap!(
303-
env,
304-
"Received a packet on channel ID {:?} while sending a KEEPALIVE packet",
305-
received_cid,
306-
);
307-
return Ok(());
308-
}
309-
match processed_packet {
310-
ProcessedPacket::InitPacket { cmd, .. } => {
311-
if cmd == CtapHidCommand::Cancel as u8 {
312-
// We ignore the payload, we can't answer with an error code anyway.
313-
debug_ctap!(env, "User presence check cancelled");
314-
return Err(UserPresenceError::Canceled);
315-
} else {
316-
debug_ctap!(
317-
env,
318-
"Discarded packet with command {} received while sending a KEEPALIVE packet",
319-
cmd,
320-
);
321-
}
322-
}
323-
ProcessedPacket::ContinuationPacket { .. } => {
324-
debug_ctap!(
325-
env,
326-
"Discarded continuation packet received while sending a KEEPALIVE packet",
327-
);
328-
}
329-
}
265+
debug_ctap!(env, "Timeout sending packet");
330266
}
267+
Ok(SendOrRecvStatus::Sent) => (),
268+
_ => panic!("Error sending packet"),
331269
}
332270
}
333-
Ok(())
334271
}
335272

336273
/// Blocks for user presence.
337274
///
338275
/// Returns an error in case of timeout, user declining presence request, or keepalive error.
339276
pub fn check_user_presence<E: Env>(env: &mut E, channel: Channel) -> CtapResult<()> {
340277
env.user_presence().check_init();
278+
let loop_timer = env.clock().make_timer(TOUCH_TIMEOUT_MS);
341279

342-
// The timeout is N times the keepalive delay.
343-
const TIMEOUT_ITERATIONS: usize = TOUCH_TIMEOUT_MS / KEEPALIVE_DELAY_MS;
280+
let (cid, transport) = match channel {
281+
Channel::MainHid(cid) => (cid, Transport::MainHid),
282+
#[cfg(feature = "vendor_hid")]
283+
Channel::VendorHid(cid) => (cid, Transport::VendorHid),
284+
};
285+
let endpoint = transport.usb_endpoint();
344286

345287
// All fallible functions are called without '?' operator to always reach
346288
// check_complete(...) cleanup function.
347289

348290
let mut result = Err(UserPresenceError::Timeout);
349-
for i in 0..=TIMEOUT_ITERATIONS {
350-
// First presence check is made without timeout. That way Env implementation may return
351-
// user presence check result immediately to client, without sending any keepalive packets.
352-
result = env
353-
.user_presence()
354-
.wait_with_timeout(if i == 0 { 0 } else { KEEPALIVE_DELAY_MS });
355-
if !matches!(result, Err(UserPresenceError::Timeout)) {
356-
break;
291+
while !env.clock().is_elapsed(&loop_timer) {
292+
let (status, data) = env.user_presence().wait_with_timeout(KEEPALIVE_DELAY_MS);
293+
if let Some((packet, rx_endpoint)) = data {
294+
let (received_cid, processed_packet) = CtapHid::<E>::process_single_packet(&packet);
295+
if rx_endpoint != endpoint || received_cid != cid {
296+
debug_ctap!(
297+
env,
298+
"Received an unrelated packet on endpoint {:?} while sending a KEEPALIVE packet",
299+
rx_endpoint,
300+
);
301+
let busy_error = CtapHid::<E>::busy_error(received_cid);
302+
send_packets(env, rx_endpoint, busy_error);
303+
}
304+
match processed_packet {
305+
ProcessedPacket::InitPacket { cmd, .. } => {
306+
if cmd == CtapHidCommand::Cancel as u8 {
307+
// Ignored, CANCEL specification says: "the authenticator MUST NOT reply"
308+
debug_ctap!(env, "User presence check cancelled");
309+
return Err(UserPresenceError::Canceled.into());
310+
} else {
311+
// Ignored. A client shouldn't try to talk to us on this channel yet.
312+
debug_ctap!(
313+
env,
314+
"Discarded packet with command {} received while sending a KEEPALIVE packet",
315+
cmd,
316+
);
317+
}
318+
}
319+
// Ignored. We likely ignore the init packet before as well.
320+
ProcessedPacket::ContinuationPacket { .. } => {
321+
debug_ctap!(
322+
env,
323+
"Discarded continuation packet received while sending a KEEPALIVE packet",
324+
);
325+
}
326+
}
357327
}
358-
// TODO: this may take arbitrary time. Next wait's delay should be adjusted
359-
// accordingly, so that all wait_with_timeout invocations are separated by
360-
// equal time intervals. That way token indicators, such as LEDs, will blink
361-
// with a consistent pattern.
362-
let keepalive_result = send_keepalive_up_needed(env, channel, KEEPALIVE_DELAY_MS);
363-
if keepalive_result.is_err() {
364-
debug_ctap!(
365-
env,
366-
"Sending keepalive failed with error {:?}",
367-
keepalive_result.as_ref().unwrap_err()
368-
);
369-
result = keepalive_result;
328+
if !matches!(status, Err(UserPresenceError::Timeout)) {
329+
result = status;
370330
break;
371331
}
332+
let keepalive_msg = CtapHid::<E>::keepalive(cid, KeepaliveStatus::UpNeeded);
333+
send_packets(env, endpoint, keepalive_msg);
372334
}
373335

374336
env.user_presence().check_complete();
@@ -2290,7 +2252,8 @@ mod test {
22902252
#[test]
22912253
fn test_process_make_credential_cancelled() {
22922254
let mut env = TestEnv::default();
2293-
env.user_presence().set(|| Err(UserPresenceError::Canceled));
2255+
env.user_presence()
2256+
.set(|| (Err(UserPresenceError::Canceled), None));
22942257
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
22952258

22962259
let make_credential_params = create_minimal_make_credential_parameters();
@@ -3170,7 +3133,8 @@ mod test {
31703133
#[test]
31713134
fn test_process_reset_cancelled() {
31723135
let mut env = TestEnv::default();
3173-
env.user_presence().set(|| Err(UserPresenceError::Canceled));
3136+
env.user_presence()
3137+
.set(|| (Err(UserPresenceError::Canceled), None));
31743138
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
31753139

31763140
let reset_reponse = ctap_state.process_reset(&mut env, DUMMY_CHANNEL);
@@ -3396,22 +3360,6 @@ mod test {
33963360
assert!(matches!(response, Ok(_)));
33973361
}
33983362

3399-
#[test]
3400-
fn test_check_user_presence_timeout() {
3401-
// This will always return timeout.
3402-
fn user_presence_timeout() -> UserPresenceResult {
3403-
Err(UserPresenceError::Timeout)
3404-
}
3405-
3406-
let mut env = TestEnv::default();
3407-
env.user_presence().set(user_presence_timeout);
3408-
let response = check_user_presence(&mut env, DUMMY_CHANNEL);
3409-
assert!(matches!(
3410-
response,
3411-
Err(Ctap2StatusCode::CTAP2_ERR_USER_ACTION_TIMEOUT)
3412-
));
3413-
}
3414-
34153363
#[test]
34163364
fn test_channel_interleaving() {
34173365
let mut env = TestEnv::default();

libraries/opensk/src/env/mod.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,8 @@ pub trait Env {
6666

6767
fn customization(&self) -> &Self::Customization;
6868

69-
/// I/O connection for sending packets implementing CTAP HID protocol.
70-
fn main_hid_connection(&mut self) -> &mut Self::HidConnection;
71-
72-
/// I/O connection for sending packets implementing vendor extensions to CTAP HID protocol.
73-
#[cfg(feature = "vendor_hid")]
74-
fn vendor_hid_connection(&mut self) -> &mut Self::HidConnection;
69+
/// I/O connection for sending HID packets.
70+
fn hid_connection(&mut self) -> &mut Self::HidConnection;
7571

7672
/// Indicates that the last power cycle was not caused by user action.
7773
fn boots_after_soft_reset(&self) -> bool;

libraries/opensk/src/env/test/mod.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
use crate::api::clock::Clock;
16-
use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus};
16+
use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus, UsbEndpoint};
1717
use crate::api::crypto::software_crypto::SoftwareCrypto;
1818
use crate::api::customization::DEFAULT_CUSTOMIZATION;
1919
use crate::api::key_store;
@@ -128,17 +128,22 @@ impl Persist for TestEnv {
128128
}
129129

130130
impl HidConnection for TestEnv {
131-
fn send_and_maybe_recv(&mut self, _buf: &mut [u8; 64], _timeout_ms: usize) -> SendOrRecvResult {
132-
// TODO: Implement I/O from canned requests/responses for integration testing.
131+
// TODO: Implement I/O from canned requests/responses for integration testing.
132+
133+
fn send(&mut self, _buf: &[u8; 64], _endpoint: UsbEndpoint) -> SendOrRecvResult {
133134
Ok(SendOrRecvStatus::Sent)
134135
}
136+
137+
fn recv(&mut self, _buf: &mut [u8; 64], _timeout_ms: usize) -> SendOrRecvResult {
138+
Ok(SendOrRecvStatus::Received(UsbEndpoint::MainHid))
139+
}
135140
}
136141

137142
impl Default for TestEnv {
138143
fn default() -> Self {
139144
let rng = StdRng::seed_from_u64(0);
140145
let user_presence = TestUserPresence {
141-
check: Box::new(|| Ok(())),
146+
check: Box::new(|| (Ok(()), None)),
142147
};
143148
let storage = new_storage();
144149
let store = Store::new(storage).ok().unwrap();
@@ -224,12 +229,7 @@ impl Env for TestEnv {
224229
&self.customization
225230
}
226231

227-
fn main_hid_connection(&mut self) -> &mut Self::HidConnection {
228-
self
229-
}
230-
231-
#[cfg(feature = "vendor_hid")]
232-
fn vendor_hid_connection(&mut self) -> &mut Self::HidConnection {
232+
fn hid_connection(&mut self) -> &mut Self {
233233
self
234234
}
235235

0 commit comments

Comments
 (0)