diff --git a/src/ctap/apdu.rs b/src/ctap/apdu.rs index bb94a91..c99bf84 100644 --- a/src/ctap/apdu.rs +++ b/src/ctap/apdu.rs @@ -16,43 +16,31 @@ use alloc::vec::Vec; use byteorder::{BigEndian, ByteOrder}; use core::convert::TryFrom; -type ByteArray = &'static [u8]; - const APDU_HEADER_LEN: usize = 4; #[cfg_attr(test, derive(Clone, Debug))] -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, dead_code)] #[derive(PartialEq)] pub enum ApduStatusCode { - SW_SUCCESS, + SW_SUCCESS = 0x90_00, /// Command successfully executed; 'XX' bytes of data are /// available and can be requested using GET RESPONSE. - SW_GET_RESPONSE, - SW_WRONG_DATA, - SW_WRONG_LENGTH, - SW_COND_USE_NOT_SATISFIED, - SW_FILE_NOT_FOUND, - SW_INCORRECT_P1P2, + SW_GET_RESPONSE = 0x61_00, + SW_MEMERR = 0x65_01, + SW_WRONG_DATA = 0x6a_80, + SW_WRONG_LENGTH = 0x67_00, + SW_COND_USE_NOT_SATISFIED = 0x69_85, + SW_FILE_NOT_FOUND = 0x6a_82, + SW_INCORRECT_P1P2 = 0x6a_86, /// Instruction code not supported or invalid - SW_INS_INVALID, - SW_CLA_INVALID, - SW_INTERNAL_EXCEPTION, + SW_INS_INVALID = 0x6d_00, + SW_CLA_INVALID = 0x6e_00, + SW_INTERNAL_EXCEPTION = 0x6f_00, } -impl From for ByteArray { - fn from(status_code: ApduStatusCode) -> ByteArray { - match status_code { - ApduStatusCode::SW_SUCCESS => b"\x90\x00", - ApduStatusCode::SW_GET_RESPONSE => b"\x61\x00", - ApduStatusCode::SW_WRONG_DATA => b"\x6A\x80", - ApduStatusCode::SW_WRONG_LENGTH => b"\x67\x00", - ApduStatusCode::SW_COND_USE_NOT_SATISFIED => b"\x69\x85", - ApduStatusCode::SW_FILE_NOT_FOUND => b"\x6a\x82", - ApduStatusCode::SW_INCORRECT_P1P2 => b"\x6a\x86", - ApduStatusCode::SW_INS_INVALID => b"\x6d\x00", - ApduStatusCode::SW_CLA_INVALID => b"\x6e\x00", - ApduStatusCode::SW_INTERNAL_EXCEPTION => b"\x6f\x00", - } +impl From for u16 { + fn from(code: ApduStatusCode) -> Self { + code as u16 } } @@ -67,10 +55,10 @@ pub enum ApduInstructions { #[allow(dead_code)] #[derive(Default, PartialEq)] pub struct ApduHeader { - cla: u8, - ins: u8, - p1: u8, - p2: u8, + pub cla: u8, + pub ins: u8, + pub p1: u8, + pub p2: u8, } impl From<&[u8; APDU_HEADER_LEN]> for ApduHeader { @@ -110,11 +98,11 @@ pub enum ApduType { #[allow(dead_code)] #[derive(PartialEq)] pub struct APDU { - header: ApduHeader, - lc: u16, - data: Vec, - le: u32, - case_type: ApduType, + pub header: ApduHeader, + pub lc: u16, + pub data: Vec, + pub le: u32, + pub case_type: ApduType, } impl TryFrom<&[u8]> for APDU { @@ -182,12 +170,19 @@ impl TryFrom<&[u8]> for APDU { } if payload.len() > 2 { // Lc is possibly three-bytes long - let extended_apdu_lc: usize = BigEndian::read_u16(&payload[1..]) as usize; - let extended_apdu_le_len: usize = if payload.len() > extended_apdu_lc { - payload.len() - extended_apdu_lc - 3 - } else { - 0 - }; + let extended_apdu_lc = BigEndian::read_u16(&payload[1..3]) as usize; + if payload.len() < extended_apdu_lc + 3 { + return Err(ApduStatusCode::SW_WRONG_LENGTH); + } + + let extended_apdu_le_len: usize = payload + .len() + .checked_sub(extended_apdu_lc + 3) + .ok_or(ApduStatusCode::SW_WRONG_LENGTH)?; + if extended_apdu_le_len > 3 { + return Err(ApduStatusCode::SW_WRONG_LENGTH); + } + if byte_0 == 0 && extended_apdu_le_len <= 3 { // If first byte is zero AND the next two bytes can be parsed as a big-endian // length that covers the rest of the block (plus few additional bytes for Le), we diff --git a/src/ctap/ctap1.rs b/src/ctap/ctap1.rs index 22b4b90..0932e2c 100644 --- a/src/ctap/ctap1.rs +++ b/src/ctap/ctap1.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::apdu::{ApduStatusCode, APDU}; use super::hid::ChannelID; use super::status_code::Ctap2StatusCode; use super::CtapState; @@ -22,49 +23,12 @@ use core::convert::TryFrom; use crypto::rng256::Rng256; use libtock_drivers::timer::ClockValue; +// For now, they're the same thing with apdu.rs containing the authoritative definition +pub type Ctap1StatusCode = ApduStatusCode; + // The specification referenced in this file is at: // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.pdf -// status codes specification (version 20170411) section 3.3 -#[allow(non_camel_case_types)] -#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] -pub enum Ctap1StatusCode { - SW_NO_ERROR = 0x9000, - SW_CONDITIONS_NOT_SATISFIED = 0x6985, - SW_WRONG_DATA = 0x6A80, - SW_WRONG_LENGTH = 0x6700, - SW_CLA_NOT_SUPPORTED = 0x6E00, - SW_INS_NOT_SUPPORTED = 0x6D00, - SW_MEMERR = 0x6501, - SW_COMMAND_ABORTED = 0x6F00, - SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000, -} - -impl TryFrom for Ctap1StatusCode { - type Error = (); - - fn try_from(value: u16) -> Result { - match value { - 0x9000 => Ok(Ctap1StatusCode::SW_NO_ERROR), - 0x6985 => Ok(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED), - 0x6A80 => Ok(Ctap1StatusCode::SW_WRONG_DATA), - 0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH), - 0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED), - 0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED), - 0x6501 => Ok(Ctap1StatusCode::SW_MEMERR), - 0x6F00 => Ok(Ctap1StatusCode::SW_COMMAND_ABORTED), - 0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG), - _ => Err(()), - } - } -} - -impl Into for Ctap1StatusCode { - fn into(self) -> u16 { - self as u16 - } -} - #[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug))] #[derive(PartialEq)] pub enum Ctap1Flags { @@ -118,11 +82,14 @@ impl TryFrom<&[u8]> for U2fCommand { type Error = Ctap1StatusCode; fn try_from(message: &[u8]) -> Result { - if message.len() < Ctap1Command::APDU_HEADER_LEN as usize { - return Err(Ctap1StatusCode::SW_WRONG_DATA); - } + let apdu: APDU = match APDU::try_from(message) { + Ok(apdu) => apdu, + Err(apdu_status_code) => { + return Err(Ctap1StatusCode::try_from(apdu_status_code).unwrap()) + } + }; - let (apdu, payload) = message.split_at(Ctap1Command::APDU_HEADER_LEN as usize); + let lc = apdu.lc as usize; // ISO7816 APDU Header format. Each cell is 1 byte. Note that the CTAP flavor always // encodes the length on 3 bytes and doesn't use the field "Le" (Length Expected). @@ -131,19 +98,17 @@ impl TryFrom<&[u8]> for U2fCommand { // +-----+-----+----+----+-----+-----+-----+ // | CLA | INS | P1 | P2 | Lc1 | Lc2 | Lc3 | // +-----+-----+----+----+-----+-----+-----+ - if apdu[0] != Ctap1Command::CTAP1_CLA { - return Err(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED); + if apdu.header.cla != Ctap1Command::CTAP1_CLA { + return Err(Ctap1StatusCode::SW_CLA_INVALID); } - let lc = (((apdu[4] as u32) << 16) | ((apdu[5] as u32) << 8) | (apdu[6] as u32)) as usize; - // Since there is always request data, the expected length is either omitted or // encoded in 2 bytes. - if lc != payload.len() && lc + 2 != payload.len() { + if lc != apdu.data.len() && lc + 2 != apdu.data.len() { return Err(Ctap1StatusCode::SW_WRONG_LENGTH); } - match apdu[1] { + match apdu.header.ins { // U2F raw message format specification, Section 4.1 // +-----------------+-------------------+ // + Challenge (32B) | Application (32B) | @@ -153,8 +118,8 @@ impl TryFrom<&[u8]> for U2fCommand { return Err(Ctap1StatusCode::SW_WRONG_LENGTH); } Ok(Self::Register { - challenge: *array_ref!(payload, 0, 32), - application: *array_ref!(payload, 32, 32), + challenge: *array_ref!(apdu.data, 0, 32), + application: *array_ref!(apdu.data, 32, 32), }) } @@ -166,15 +131,15 @@ impl TryFrom<&[u8]> for U2fCommand { if lc < 65 { return Err(Ctap1StatusCode::SW_WRONG_LENGTH); } - let handle_length = payload[64] as usize; + let handle_length = apdu.data[64] as usize; if lc != 65 + handle_length { return Err(Ctap1StatusCode::SW_WRONG_LENGTH); } - let flag = Ctap1Flags::try_from(apdu[2])?; + let flag = Ctap1Flags::try_from(apdu.header.p1)?; Ok(Self::Authenticate { - challenge: *array_ref!(payload, 0, 32), - application: *array_ref!(payload, 32, 32), - key_handle: payload[65..lc].to_vec(), + challenge: *array_ref!(apdu.data, 0, 32), + application: *array_ref!(apdu.data, 32, 32), + key_handle: apdu.data[65..].to_vec(), flags: flag, }) } @@ -190,11 +155,11 @@ impl TryFrom<&[u8]> for U2fCommand { // For Vendor specific command. Ctap1Command::VENDOR_SPECIFIC_FIRST..=Ctap1Command::VENDOR_SPECIFIC_LAST => { Ok(Self::VendorSpecific { - payload: payload.to_vec(), + payload: apdu.data.to_vec(), }) } - _ => Err(Ctap1StatusCode::SW_INS_NOT_SUPPORTED), + _ => Err(Ctap1StatusCode::SW_INS_INVALID), } } } @@ -202,8 +167,6 @@ impl TryFrom<&[u8]> for U2fCommand { pub struct Ctap1Command {} impl Ctap1Command { - const APDU_HEADER_LEN: u32 = 7; // CLA + INS + P1 + P2 + LC1-3 - const CTAP1_CLA: u8 = 0; // This byte is used in Register, but only serves backwards compatibility. const LEGACY_BYTE: u8 = 0x05; @@ -234,7 +197,7 @@ impl Ctap1Command { application, } => { if !ctap_state.u2f_up_state.consume_up(clock_value) { - return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED); + return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED); } Ctap1Command::process_register(challenge, application, ctap_state) } @@ -249,7 +212,7 @@ impl Ctap1Command { if flags == Ctap1Flags::EnforceUpAndSign && !ctap_state.u2f_up_state.consume_up(clock_value) { - return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED); + return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED); } Ctap1Command::process_authenticate( challenge, @@ -264,7 +227,7 @@ impl Ctap1Command { U2fCommand::Version => Ok(Vec::::from(super::U2F_VERSION_STRING)), // TODO: should we return an error instead such as SW_INS_NOT_SUPPORTED? - U2fCommand::VendorSpecific { .. } => Err(Ctap1StatusCode::SW_NO_ERROR), + U2fCommand::VendorSpecific { .. } => Err(Ctap1StatusCode::SW_SUCCESS), } } @@ -292,22 +255,22 @@ impl Ctap1Command { let pk = sk.genpk(); let key_handle = ctap_state .encrypt_key_handle(sk, &application) - .map_err(|_| Ctap1StatusCode::SW_COMMAND_ABORTED)?; + .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; if key_handle.len() > 0xFF { // This is just being defensive with unreachable code. - return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG); + return Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION); } let certificate = ctap_state .persistent_store .attestation_certificate() .map_err(|_| Ctap1StatusCode::SW_MEMERR)? - .ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?; + .ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; let private_key = ctap_state .persistent_store .attestation_private_key() - .map_err(|_| Ctap1StatusCode::SW_MEMERR)? - .ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?; + .map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)? + .ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?; let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len()); response.push(Ctap1Command::LEGACY_BYTE); @@ -362,7 +325,7 @@ impl Ctap1Command { .map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?; if let Some(credential_source) = credential_source { if flags == Ctap1Flags::CheckOnly { - return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED); + return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED); } ctap_state .increment_global_signature_counter() @@ -448,7 +411,7 @@ mod test { ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); // Certificate and private key are missing - assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)); let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]; assert!(ctap_state @@ -459,7 +422,7 @@ mod test { ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); // Certificate is still missing - assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)); let fake_cert = [0x99u8; 100]; // Arbitrary length assert!(ctap_state @@ -513,7 +476,7 @@ mod test { ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); let response = Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE); - assert_eq!(response, Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED)); } #[test] @@ -529,7 +492,7 @@ mod test { let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); - assert_eq!(response, Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED)); } #[test] @@ -559,15 +522,24 @@ mod test { let rp_id = "example.com"; let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap(); - let mut message = - create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + let mut message = create_authenticate_message( + &application, + Ctap1Flags::DontEnforceUpAndSign, + &key_handle, + ); message.push(0x00); let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); - assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH)); + assert!(response.is_ok()); - // Two extra zeros are okay, they could encode the expected response length. message.push(0x00); + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert!(response.is_ok()); + + message.push(0x00); + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert!(response.is_ok()); + message.push(0x00); let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH)); @@ -588,7 +560,7 @@ mod test { message[0] = 0xEE; let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); - assert_eq!(response, Err(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_CLA_INVALID)); } #[test] @@ -606,7 +578,7 @@ mod test { message[1] = 0xEE; let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); - assert_eq!(response, Err(Ctap1StatusCode::SW_INS_NOT_SUPPORTED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_INS_INVALID)); } #[test] @@ -722,6 +694,6 @@ mod test { ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); let response = Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE); - assert_eq!(response, Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED)); + assert_eq!(response, Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED)); } } diff --git a/src/ctap/hid/mod.rs b/src/ctap/hid/mod.rs index 3874792..a36063b 100644 --- a/src/ctap/hid/mod.rs +++ b/src/ctap/hid/mod.rs @@ -417,7 +417,7 @@ impl CtapHid { #[cfg(feature = "with_ctap1")] fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> HidPacketIterator { let mut response = payload.to_vec(); - let code: u16 = ctap1::Ctap1StatusCode::SW_NO_ERROR.into(); + let code: u16 = ctap1::Ctap1StatusCode::SW_SUCCESS.into(); response.extend_from_slice(&code.to_be_bytes()); CtapHid::split_message(Message { cid,