diff --git a/libraries/opensk/fuzz/fuzz_helper/src/lib.rs b/libraries/opensk/fuzz/fuzz_helper/src/lib.rs index a79462d..ba0f1b2 100644 --- a/libraries/opensk/fuzz/fuzz_helper/src/lib.rs +++ b/libraries/opensk/fuzz/fuzz_helper/src/lib.rs @@ -85,7 +85,7 @@ fn initialize(ctap: &mut Ctap) -> 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) { 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 = 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)) ); } diff --git a/libraries/opensk/src/ctap/hid/mod.rs b/libraries/opensk/src/ctap/hid/mod.rs index 71fdcdb..955b949 100644 --- a/libraries/opensk/src/ctap/hid/mod.rs +++ b/libraries/opensk/src/ctap/hid/mod.rs @@ -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 { // TODO(kaczmarczyck) We might want to limit or timeout open channels. allocated_cids: usize, capabilities: u8, + locked_cid: Option, + lock_timer: ::Timer, } impl CtapHid { @@ -203,17 +205,31 @@ impl CtapHid { assembler: MessageAssembler::default(), allocated_cids: 0, capabilities, + locked_cid: None, + lock_timer: ::Timer::default(), } } + fn locked_channel(&mut self, env: &mut E) -> Option { + 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 CtapHid { /// 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 { - match self.assembler.parse_packet(env, packet) { + pub fn parse_packet( + &mut self, + env: &mut E, + packet: &HidPacket, + is_transport_disabled: bool, + ) -> Option { + 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 CtapHid { /// 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 { + fn preprocess_message(&mut self, env: &mut E, message: Message) -> Option { let cid = message.cid; if !self.has_valid_channel(&message) { return Some(Self::error_message(cid, CtapHidError::InvalidChannel)); @@ -306,6 +334,26 @@ impl CtapHid { 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 CtapHid { assembler: MessageAssembler::default(), allocated_cids: 1, capabilities: 0x0D, + locked_cid: None, + lock_timer: ::Timer::default(), }, [0x00, 0x00, 0x00, 0x01], ) @@ -422,7 +472,7 @@ mod test { let mut messages = Vec::new(); let mut assembler = MessageAssembler::::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::::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::::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::::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::::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::::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::::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)); + } } diff --git a/libraries/opensk/src/ctap/hid/receive.rs b/libraries/opensk/src/ctap/hid/receive.rs index a3fcda7..c3689c3 100644 --- a/libraries/opensk/src/ctap/hid/receive.rs +++ b/libraries/opensk/src/ctap/hid/receive.rs @@ -78,10 +78,14 @@ impl MessageAssembler { &mut self, env: &mut E, packet: &HidPacket, + locked_cid: Option, ) -> Result, (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); + 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::::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::::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 } diff --git a/libraries/opensk/src/ctap/large_blobs.rs b/libraries/opensk/src/ctap/large_blobs.rs index ebf45ad..54bc630 100644 --- a/libraries/opensk/src/ctap/large_blobs.rs +++ b/libraries/opensk/src/ctap/large_blobs.rs @@ -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 { diff --git a/libraries/opensk/src/ctap/main_hid.rs b/libraries/opensk/src/ctap/main_hid.rs index 2c0d415..15c4a40 100644 --- a/libraries/opensk/src/ctap/main_hid.rs +++ b/libraries/opensk/src/ctap/main_hid.rs @@ -56,9 +56,10 @@ impl MainHid { &mut self, env: &mut E, packet: &HidPacket, + is_transport_disabled: bool, ctap_state: &mut CtapState, ) -> 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::::split_message(processed_message) @@ -119,6 +120,11 @@ impl MainHid { } } + /// 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::::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); + } } diff --git a/libraries/opensk/src/ctap/vendor_hid.rs b/libraries/opensk/src/ctap/vendor_hid.rs index 7482dcc..c3d4e63 100644 --- a/libraries/opensk/src/ctap/vendor_hid.rs +++ b/libraries/opensk/src/ctap/vendor_hid.rs @@ -39,9 +39,10 @@ impl VendorHid { &mut self, env: &mut E, packet: &HidPacket, + is_transport_disabled: bool, ctap_state: &mut CtapState, ) -> 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 VendorHid { _ => 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::::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); } diff --git a/libraries/opensk/src/lib.rs b/libraries/opensk/src/lib.rs index d172fc3..1412a95 100644 --- a/libraries/opensk/src/lib.rs +++ b/libraries/opensk/src/lib.rs @@ -96,13 +96,22 @@ impl Ctap { ) -> 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 Ctap { 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::::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::::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::::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); + } +}