CTAP HID Lock (#605)

* Implements the CTAP HID Lock command

This is a direct translation of our internal implementation.

* adds more HID Lock tests
This commit is contained in:
kaczmarczyck
2023-03-17 17:22:36 +01:00
committed by GitHub
parent 2560b6661c
commit 6d5ea16f2d
7 changed files with 522 additions and 51 deletions

View File

@@ -85,7 +85,7 @@ fn initialize(ctap: &mut Ctap<TestEnv>) -> ChannelID {
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) {
if let Ok(Some(result)) = assembler_reply.parse_packet(ctap.env(), &pkt_reply) {
if let Ok(Some(result)) = assembler_reply.parse_packet(ctap.env(), &pkt_reply, None) {
result_cid.copy_from_slice(&result.payload[8..12]);
}
}
@@ -124,7 +124,7 @@ fn process_message(data: &[u8], ctap: &mut Ctap<TestEnv>) {
for pkt_request in hid_packet_iterator {
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);
let _ = assembler_reply.parse_packet(ctap.env(), &pkt_reply, None);
}
}
}
@@ -264,10 +264,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), Ok(None));
assert_eq!(assembler.parse_packet(&mut env, packet, None), Ok(None));
}
assert_eq!(
assembler.parse_packet(&mut env, last_packet),
assembler.parse_packet(&mut env, last_packet, None),
Ok(Some(message))
);
}

View File

@@ -22,6 +22,7 @@ pub use self::receive::MessageAssembler;
use self::receive::MessageAssembler;
pub use self::send::HidPacketIterator;
use super::status_code::Ctap2StatusCode;
use crate::api::clock::Clock;
#[cfg(test)]
use crate::env::test::TestEnv;
use crate::env::Env;
@@ -55,7 +56,6 @@ pub type ChannelID = [u8; 4];
pub enum CtapHidCommand {
Ping = 0x01,
Msg = 0x03,
// Lock is optional and may be used in the future.
Lock = 0x04,
Init = 0x06,
Wink = 0x08,
@@ -92,7 +92,7 @@ pub enum CtapHidError {
/// The command in the request is invalid.
InvalidCmd = 0x01,
/// A parameter in the request is invalid.
_InvalidPar = 0x02,
InvalidPar = 0x02,
/// The length of a message is too big.
InvalidLen = 0x03,
/// Expected a continuation packet with a specific sequence number, got another sequence number.
@@ -187,6 +187,8 @@ pub struct CtapHid<E: Env> {
// TODO(kaczmarczyck) We might want to limit or timeout open channels.
allocated_cids: usize,
capabilities: u8,
locked_cid: Option<ChannelID>,
lock_timer: <E::Clock as Clock>::Timer,
}
impl<E: Env> CtapHid<E> {
@@ -203,17 +205,31 @@ impl<E: Env> CtapHid<E> {
assembler: MessageAssembler::default(),
allocated_cids: 0,
capabilities,
locked_cid: None,
lock_timer: <E::Clock as Clock>::Timer::default(),
}
}
fn locked_channel(&mut self, env: &mut E) -> Option<ChannelID> {
if env.clock().is_elapsed(&self.lock_timer) {
self.locked_cid = None;
}
self.locked_cid
}
/// Returns whether this transport claims a lock.
pub fn has_channel_lock(&mut self, env: &mut E) -> bool {
self.locked_channel(env).is_some()
}
/// Parses a packet, and preprocesses some messages and errors.
///
/// The preprocessed commands are:
/// - INIT
/// - CANCEL
/// - LOCK
/// - ERROR
/// - Unknown and unexpected commands like KEEPALIVE
/// - LOCK is not implemented and currently treated like an unknown command
///
/// Commands that may still be processed:
/// - PING
@@ -225,11 +241,23 @@ impl<E: Env> CtapHid<E> {
/// Ignoring the others is incorrect behavior. You have to at least replace them with an error
/// message:
/// `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) {
pub fn parse_packet(
&mut self,
env: &mut E,
packet: &HidPacket,
is_transport_disabled: bool,
) -> Option<Message> {
let locked_cid = if is_transport_disabled {
// We use the reserved channel ID to block all valid channels. If we also think we hold
// a lock, we wait and rely on timeouts to resolve the deadlock.
Some(CHANNEL_RESERVED)
} else {
self.locked_channel(env)
};
match self.assembler.parse_packet(env, packet, locked_cid) {
Ok(Some(message)) => {
debug_ctap!(env, "Received message: {:02x?}", message);
self.preprocess_message(message)
self.preprocess_message(env, message)
}
Ok(None) => {
// Waiting for more packets to assemble the message, nothing to send for now.
@@ -252,10 +280,10 @@ impl<E: Env> CtapHid<E> {
/// The preprocessed commands are:
/// - INIT
/// - CANCEL
/// - LOCK
/// - ERROR
/// - Unknown and unexpected commands like KEEPALIVE
/// - LOCK is not implemented and currently treated like an unknown command
fn preprocess_message(&mut self, message: Message) -> Option<Message> {
fn preprocess_message(&mut self, env: &mut E, message: Message) -> Option<Message> {
let cid = message.cid;
if !self.has_valid_channel(&message) {
return Some(Self::error_message(cid, CtapHidError::InvalidChannel));
@@ -306,6 +334,26 @@ impl<E: Env> CtapHid<E> {
None
}
CtapHidCommand::Wink => Some(message),
CtapHidCommand::Lock => {
if message.payload.len() != 1 {
return Some(Self::error_message(cid, CtapHidError::InvalidLen));
}
if message.payload[0] > 10 {
return Some(Self::error_message(cid, CtapHidError::InvalidPar));
}
if message.payload[0] == 0 {
self.locked_cid = None;
} else {
self.locked_cid = Some(cid);
let lock_duration_ms = 1000 * message.payload[0] as usize;
self.lock_timer = env.clock().make_timer(lock_duration_ms);
}
Some(Message {
cid,
cmd: CtapHidCommand::Lock,
payload: Vec::new(),
})
}
_ => {
// Unknown or unsupported command.
Some(Self::error_message(cid, CtapHidError::InvalidCmd))
@@ -399,6 +447,8 @@ impl<E: Env> CtapHid<E> {
assembler: MessageAssembler::default(),
allocated_cids: 1,
capabilities: 0x0D,
locked_cid: None,
lock_timer: <E::Clock as Clock>::Timer::default(),
},
[0x00, 0x00, 0x00, 0x01],
)
@@ -422,7 +472,7 @@ mod test {
let mut messages = Vec::new();
let mut assembler = MessageAssembler::<TestEnv>::default();
for packet in HidPacketIterator::new(message.clone()).unwrap() {
match assembler.parse_packet(&mut env, &packet) {
match assembler.parse_packet(&mut env, &packet, None) {
Ok(Some(msg)) => messages.push(msg),
Ok(None) => (),
Err(_) => panic!("Couldn't assemble packet: {:02x?}", &packet as &[u8]),
@@ -440,18 +490,19 @@ mod test {
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), None);
assert_eq!(ctap_hid.parse_packet(&mut env, &packet, false), None);
}
#[test]
fn test_command_init() {
let mut env = TestEnv::default();
let mut ctap_hid = CtapHid::<TestEnv>::new(0x0D);
let init_message = Message {
cid: CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init,
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
};
let reply = ctap_hid.preprocess_message(init_message);
let reply = ctap_hid.preprocess_message(&mut env, init_message);
assert_eq!(
reply,
Some(Message {
@@ -483,9 +534,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, false), None);
assert_eq!(
ctap_hid.parse_packet(&mut env, &packet2),
ctap_hid.parse_packet(&mut env, &packet2, false),
Some(Message {
cid,
cmd: CtapHidCommand::Init,
@@ -509,7 +560,7 @@ mod test {
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),
ctap_hid.parse_packet(&mut env, &ping_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Ping,
@@ -527,7 +578,7 @@ mod test {
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);
let response = ctap_hid.parse_packet(&mut env, &cancel_packet, false);
assert_eq!(response, None);
}
@@ -619,4 +670,153 @@ mod test {
}
);
}
#[test]
fn test_locked_channel_id() {
let mut env = TestEnv::default();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
let mut init_packet = [0x00; 64];
init_packet[..4].copy_from_slice(&[0xFF; 4]);
init_packet[4..15].copy_from_slice(&[
0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
]);
let init_response = ctap_hid
.parse_packet(&mut env, &init_packet, false)
.unwrap();
assert_eq!(init_response.cmd, CtapHidCommand::Init);
let new_cid = *array_ref!(init_response.payload, 8, 4);
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&cid);
lock_packet[4..8].copy_from_slice(&[0x84, 0x00, 0x01, 0x01]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Lock,
payload: vec![]
})
);
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&new_cid);
lock_packet[4..8].copy_from_slice(&[0x84, 0x00, 0x01, 0x00]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid: new_cid,
cmd: CtapHidCommand::Error,
payload: vec![0x06]
})
);
}
#[test]
fn test_locked_transport() {
let mut env = TestEnv::default();
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, true),
Some(Message {
cid,
cmd: CtapHidCommand::Error,
payload: vec![0x06]
})
);
}
#[test]
fn test_command_lock_expires() {
let mut env = TestEnv::default();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
assert!(!ctap_hid.has_channel_lock(&mut env));
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&cid);
lock_packet[4..8].copy_from_slice(&[0x84, 0x00, 0x01, 0x01]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Lock,
payload: vec![]
})
);
assert!(ctap_hid.has_channel_lock(&mut env));
env.clock().advance(999);
assert!(ctap_hid.has_channel_lock(&mut env));
env.clock().advance(1);
assert!(!ctap_hid.has_channel_lock(&mut env));
}
#[test]
fn test_command_lock_releases() {
let mut env = TestEnv::default();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
assert!(!ctap_hid.has_channel_lock(&mut env));
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&cid);
lock_packet[4..8].copy_from_slice(&[0x84, 0x00, 0x01, 0x01]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Lock,
payload: vec![]
})
);
assert!(ctap_hid.has_channel_lock(&mut env));
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&cid);
lock_packet[4..8].copy_from_slice(&[0x84, 0x00, 0x01, 0x00]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Lock,
payload: vec![]
})
);
assert!(!ctap_hid.has_channel_lock(&mut env));
}
#[test]
fn test_command_lock_invalid() {
let mut env = TestEnv::default();
let (mut ctap_hid, cid) = CtapHid::<TestEnv>::new_initialized();
assert!(!ctap_hid.has_channel_lock(&mut env));
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&cid);
lock_packet[4..8].copy_from_slice(&[0x84, 0x00, 0x01, 0x0B]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Error,
payload: vec![0x02]
})
);
assert!(!ctap_hid.has_channel_lock(&mut env));
let mut lock_packet = [0x00; 64];
lock_packet[..4].copy_from_slice(&cid);
lock_packet[4..9].copy_from_slice(&[0x84, 0x00, 0x02, 0x01, 0x01]);
assert_eq!(
ctap_hid.parse_packet(&mut env, &lock_packet, false),
Some(Message {
cid,
cmd: CtapHidCommand::Error,
payload: vec![0x03]
})
);
assert!(!ctap_hid.has_channel_lock(&mut env));
}
}

View File

@@ -78,10 +78,14 @@ impl<E: Env> MessageAssembler<E> {
&mut self,
env: &mut E,
packet: &HidPacket,
locked_cid: Option<ChannelID>,
) -> 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::<E>::process_single_packet(packet);
if let Some(locked_cid) = locked_cid {
if locked_cid != cid {
return Err((cid, CtapHidError::ChannelBusy));
}
}
if !self.idle && env.clock().is_elapsed(&self.timer) {
// The current channel timed out.
@@ -212,7 +216,11 @@ mod test {
let mut env = TestEnv::default();
let mut assembler = MessageAssembler::default();
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90]),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Cbor,
@@ -229,6 +237,7 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]),
None,
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -249,6 +258,7 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10], 0xFF),
None,
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -266,11 +276,16 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
None,
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -287,15 +302,24 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x80]),
None,
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
None
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -312,17 +336,26 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x1D, 0xB9]),
None,
),
Ok(None)
);
for seq in 0..0x7F {
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
None
),
Ok(None)
);
}
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -348,6 +381,7 @@ mod test {
&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd as u8, 0x00, 0x80],
byte
),
None,
),
Ok(None)
);
@@ -355,6 +389,7 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x00], byte),
None,
),
Ok(None)
);
@@ -362,6 +397,7 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x01], byte),
None,
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -387,17 +423,24 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd as u8, 0x00, 0x80], byte),
None,
),
Ok(None)
);
assert_eq!(
assembler
.parse_packet(&mut env, &byte_extend(&[0x12, 0x34, 0x56, cid, 0x00], byte)),
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x00], byte),
None
),
Ok(None)
);
assert_eq!(
assembler
.parse_packet(&mut env, &byte_extend(&[0x12, 0x34, 0x56, cid, 0x01], byte)),
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, cid, 0x01], byte),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, cid],
cmd,
@@ -415,6 +458,7 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
None,
),
Ok(None)
);
@@ -427,6 +471,7 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd as u8, 0x00], byte),
None,
),
Err(([0x12, 0x34, 0x56, 0x9A], CtapHidError::ChannelBusy))
);
@@ -434,7 +479,11 @@ mod test {
}
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -457,6 +506,7 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x10], byte),
None,
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -468,7 +518,11 @@ mod test {
// Spurious continuation packet.
let seq = i;
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
None
),
Err((
[0x12, 0x34, 0x56, 0x78],
CtapHidError::UnexpectedContinuation
@@ -485,11 +539,16 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
None,
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]),
None
),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::InvalidSeq))
);
}
@@ -502,11 +561,16 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
None,
),
Ok(None)
);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]),
None
),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::InvalidSeq))
);
}
@@ -519,12 +583,17 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
None,
),
Ok(None)
);
env.clock().advance(TIMEOUT_DURATION_MS);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
None
),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::MsgTimeout))
);
}
@@ -540,19 +609,28 @@ mod test {
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x1D, 0xB9]),
None,
),
Ok(None)
);
for seq in 0..0x7F {
env.clock().advance(delay);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]),
None
),
Ok(None)
);
}
env.clock().advance(delay);
assert_eq!(
assembler.parse_packet(&mut env, &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F])),
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]),
None
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Ping,
@@ -570,6 +648,7 @@ mod test {
assembler.parse_packet(
&mut env,
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x02, 0x00], 0x51),
None,
),
Ok(None)
);
@@ -581,6 +660,7 @@ mod test {
0x12, 0x34, 0x56, 0x78, 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC,
0xDE, 0xF0
]),
None,
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
@@ -590,5 +670,37 @@ mod test {
);
}
#[test]
fn test_locked_channel() {
let mut env = TestEnv::default();
let mut assembler = MessageAssembler::<TestEnv>::default();
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]),
Some([0x12, 0x34, 0x56, 0x78]),
),
Ok(Some(Message {
cid: [0x12, 0x34, 0x56, 0x78],
cmd: CtapHidCommand::Cbor,
payload: vec![0x00; 0x10]
}))
);
}
#[test]
fn test_other_locked_channel() {
let mut env = TestEnv::default();
let mut assembler = MessageAssembler::<TestEnv>::default();
assert_eq!(
assembler.parse_packet(
&mut env,
&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x90, 0x00, 0x10]),
Some([0xAA, 0xAA, 0xAA, 0xAA]),
),
Err(([0x12, 0x34, 0x56, 0x78], CtapHidError::ChannelBusy))
);
}
// TODO: more tests
}

View File

@@ -25,7 +25,7 @@ use byteorder::{ByteOrder, LittleEndian};
use crypto::sha256::Sha256;
use crypto::Hash256;
/// The length of the truncated hash that as appended to the large blob data.
/// The length of the truncated hash that is appended to the large blob data.
const TRUNCATED_HASH_LEN: usize = 16;
pub struct LargeBlobs {

View File

@@ -56,9 +56,10 @@ impl<E: Env> MainHid<E> {
&mut self,
env: &mut E,
packet: &HidPacket,
is_transport_disabled: bool,
ctap_state: &mut CtapState<E>,
) -> HidPacketIterator {
if let Some(message) = self.hid.parse_packet(env, packet) {
if let Some(message) = self.hid.parse_packet(env, packet, is_transport_disabled) {
let processed_message = self.process_message(env, message, ctap_state);
debug_ctap!(env, "Sending message: {:02x?}", processed_message);
CtapHid::<E>::split_message(processed_message)
@@ -119,6 +120,11 @@ impl<E: Env> MainHid<E> {
}
}
/// Returns whether this transport claims a lock.
pub fn has_channel_lock(&mut self, env: &mut E) -> bool {
self.hid.has_channel_lock(env)
}
/// Returns whether a wink permission is currently granted.
pub fn should_wink(&self, env: &mut E) -> bool {
!env.clock().is_elapsed(&self.wink_permission)
@@ -175,7 +181,8 @@ mod test {
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, &mut ctap_state);
let mut response =
main_hid.process_hid_packet(&mut env, &ping_packet, false, &mut ctap_state);
assert_eq!(response.next(), Some(ping_packet));
assert_eq!(response.next(), None);
}
@@ -190,7 +197,8 @@ mod test {
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, &mut ctap_state);
let mut response =
main_hid.process_hid_packet(&mut env, &cancel_packet, false, &mut ctap_state);
assert_eq!(response.next(), None);
}
@@ -205,11 +213,31 @@ mod test {
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, &mut ctap_state);
let mut response =
main_hid.process_hid_packet(&mut env, &wink_packet, false, &mut ctap_state);
assert_eq!(response.next(), Some(wink_packet));
assert_eq!(response.next(), None);
assert!(main_hid.should_wink(&mut env));
env.clock().advance(WINK_TIMEOUT_DURATION_MS);
assert!(!main_hid.should_wink(&mut env));
}
#[test]
fn test_locked_channels() {
let mut env = TestEnv::default();
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, true, &mut ctap_state);
let mut error_packet = [0x00; 64];
error_packet[..4].copy_from_slice(&cid);
error_packet[4..8].copy_from_slice(&[0xBF, 0x00, 0x01, 0x06]);
assert_eq!(response.next(), Some(error_packet));
assert_eq!(response.next(), None);
}
}

View File

@@ -39,9 +39,10 @@ impl<E: Env> VendorHid<E> {
&mut self,
env: &mut E,
packet: &HidPacket,
is_transport_disabled: bool,
ctap_state: &mut CtapState<E>,
) -> HidPacketIterator {
if let Some(message) = self.hid.parse_packet(env, packet) {
if let Some(message) = self.hid.parse_packet(env, packet, is_transport_disabled) {
let processed_message = self.process_message(env, message, ctap_state);
debug_ctap!(
env,
@@ -81,6 +82,11 @@ impl<E: Env> VendorHid<E> {
_ => message,
}
}
/// Returns whether this transport claims a lock.
pub fn has_channel_lock(&mut self, env: &mut E) -> bool {
self.hid.has_channel_lock(env)
}
}
#[cfg(test)]
@@ -104,7 +110,8 @@ mod test {
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, &mut ctap_state);
let mut response =
vendor_hid.process_hid_packet(&mut env, &ping_packet, false, &mut ctap_state);
assert_eq!(response.next(), Some(ping_packet));
assert_eq!(response.next(), None);
}
@@ -119,7 +126,8 @@ mod test {
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, &mut ctap_state);
let mut response =
vendor_hid.process_hid_packet(&mut env, &cancel_packet, false, &mut ctap_state);
assert_eq!(response.next(), None);
}
@@ -142,11 +150,32 @@ 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, &mut ctap_state);
let mut response =
vendor_hid.process_hid_packet(&mut env, &msg_packet, false, &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, &mut ctap_state);
let mut response =
vendor_hid.process_hid_packet(&mut env, &wink_packet, false, &mut ctap_state);
assert_eq!(response.next(), Some(error_packet));
assert_eq!(response.next(), None);
}
#[test]
fn test_locked_channels() {
let mut env = TestEnv::default();
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, true, &mut ctap_state);
let mut error_packet = [0x00; 64];
error_packet[..4].copy_from_slice(&cid);
error_packet[4..8].copy_from_slice(&[0xBF, 0x00, 0x01, 0x06]);
assert_eq!(response.next(), Some(error_packet));
assert_eq!(response.next(), None);
}

View File

@@ -96,13 +96,22 @@ impl<E: Env> Ctap<E> {
) -> HidPacketIterator {
match transport {
Transport::MainHid => {
#[cfg(not(feature = "vendor_hid"))]
let is_disabled = false;
#[cfg(feature = "vendor_hid")]
let is_disabled = self.vendor_hid.has_channel_lock(&mut self.env);
self.hid
.process_hid_packet(&mut self.env, packet, &mut self.state)
.process_hid_packet(&mut self.env, packet, is_disabled, &mut self.state)
}
#[cfg(feature = "vendor_hid")]
Transport::VendorHid => {
self.vendor_hid
.process_hid_packet(&mut self.env, packet, &mut self.state)
let is_disabled = self.hid.has_channel_lock(&mut self.env);
self.vendor_hid.process_hid_packet(
&mut self.env,
packet,
is_disabled,
&mut self.state,
)
}
}
}
@@ -121,3 +130,96 @@ impl<E: Env> Ctap<E> {
self.state.u2f_needs_user_presence(&mut self.env)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::env::test::TestEnv;
/// Assembles a packet for a payload that fits into one packet.
fn assemble_packet(cid: &[u8; 4], cmd: u8, payload: &[u8]) -> HidPacket {
assert!(payload.len() <= 57);
let mut packet = [0x00; 64];
packet[..4].copy_from_slice(cid);
packet[4] = cmd | 0x80;
packet[6] = payload.len() as u8;
packet[7..][..payload.len()].copy_from_slice(payload);
packet
}
fn init_packet() -> HidPacket {
assemble_packet(&[0xFF; 4], 0x06, &[0x55; 8])
}
fn lock_packet(cid: &[u8; 4]) -> HidPacket {
assemble_packet(cid, 0x04, &[0x01; 1])
}
fn wink_packet(cid: &[u8; 4]) -> HidPacket {
assemble_packet(cid, 0x08, &[])
}
#[test]
fn test_wink() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
// Send Init, receive Init response and check wink if disabled.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);
assert!(!ctap.should_wink());
// Send Wink, receive Wink response and check wink is enabled.
let mut lock_response = ctap.process_hid_packet(&wink_packet(&cid), Transport::MainHid);
let response_packet = lock_response.next().unwrap();
assert_eq!(response_packet[4], 0x88);
assert!(ctap.should_wink());
}
#[test]
fn test_locked_channel_id() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);
// Send Lock, receive Lock response.
let mut lock_response = ctap.process_hid_packet(&lock_packet(&cid), Transport::MainHid);
let response_packet = lock_response.next().unwrap();
assert_eq!(response_packet[4], 0x84);
// Send another Init, receive Error.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0xBF);
}
#[test]
#[cfg(feature = "vendor_hid")]
fn test_locked_transport() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);
// Send Lock, receive Lock response.
let mut lock_response = ctap.process_hid_packet(&lock_packet(&cid), Transport::MainHid);
let response_packet = lock_response.next().unwrap();
assert_eq!(response_packet[4], 0x84);
// Send Init on other transport, receive Error.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::VendorHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0xBF);
}
}