From d2ef3853e5191eb6497872004c4e7b315fb15af5 Mon Sep 17 00:00:00 2001 From: Mirna <29131616+MirnaMuhammad98@users.noreply.github.com> Date: Fri, 16 Oct 2020 09:29:23 +0200 Subject: [PATCH 01/32] Delete nfc.rs This branch is for the example application. --- third_party/libtock-drivers/src/nfc.rs | 117 ------------------------- 1 file changed, 117 deletions(-) delete mode 100644 third_party/libtock-drivers/src/nfc.rs diff --git a/third_party/libtock-drivers/src/nfc.rs b/third_party/libtock-drivers/src/nfc.rs deleted file mode 100644 index 977b49b..0000000 --- a/third_party/libtock-drivers/src/nfc.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::util; -use core::cell::Cell; -use libtock_core::{callback, syscalls}; - -const DRIVER_NUMBER: usize = 0x30003; - -mod command_nr { - pub const TRANSMIT: usize = 1; - pub const RECEIVE: usize = 2; - pub const EMULATE: usize = 3; - pub const CONFIGURE: usize = 4; -} - -mod subscribe_nr { - pub const TRANSMIT: usize = 1; - pub const RECEIVE: usize = 2; - pub const SELECT: usize = 3; -} - -mod allow_nr { - pub const TRANSMIT: usize = 1; - pub const RECEIVE: usize = 2; -} - -pub fn enable_emulation() { - emulate(true); -} - -pub fn disable_emulation() { - emulate(false); -} - -pub fn emulate(enabled: bool) -> bool { - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::EMULATE, enabled as usize, 0); - if result_code.is_err() { - return false; - } - - true -} - -pub fn selected() -> bool { - let is_selected = Cell::new(false); - let mut is_selected_alarm = || is_selected.set(true); - let subscription = syscalls::subscribe::( - DRIVER_NUMBER, - subscribe_nr::SELECT, - &mut is_selected_alarm, - ); - if subscription.is_err() { - return false; - } - - util::yieldk_for(|| is_selected.get()); - true -} - -pub fn configure(tag_type: u8) -> bool { - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::CONFIGURE, tag_type as usize, 0); - if result_code.is_err() { - return false; - } - - true -} - -pub fn receive(buf: &mut [u8]) -> bool { - let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf); - if result.is_err() { - return false; - } - - let done = Cell::new(false); - let mut alarm = || done.set(true); - let subscription = syscalls::subscribe::( - DRIVER_NUMBER, - subscribe_nr::RECEIVE, - &mut alarm, - ); - if subscription.is_err() { - return false; - } - - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::RECEIVE, 0, 0); - if result_code.is_err() { - return false; - } - - util::yieldk_for(|| done.get()); - true -} - -pub fn transmit(buf: &mut [u8]) -> bool { - let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT, buf); - if result.is_err() { - return false; - } - - let done = Cell::new(false); - let mut alarm = || done.set(true); - let subscription = syscalls::subscribe::( - DRIVER_NUMBER, - subscribe_nr::TRANSMIT, - &mut alarm, - ); - if subscription.is_err() { - return false; - } - - let result_code = syscalls::command(DRIVER_NUMBER, command_nr::TRANSMIT, 0, 0); - if result_code.is_err() { - return false; - } - - util::yieldk_for(|| done.get()); - true -} From c520095ab7c2ebaf6885a7e68ed63575b5317eaa Mon Sep 17 00:00:00 2001 From: Mirna <29131616+MirnaMuhammad98@users.noreply.github.com> Date: Fri, 16 Oct 2020 09:29:44 +0200 Subject: [PATCH 02/32] Update lib.rs This branch is for the example application. --- third_party/libtock-drivers/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/third_party/libtock-drivers/src/lib.rs b/third_party/libtock-drivers/src/lib.rs index ec8a2d7..bf62d05 100644 --- a/third_party/libtock-drivers/src/lib.rs +++ b/third_party/libtock-drivers/src/lib.rs @@ -3,7 +3,6 @@ pub mod buttons; pub mod console; pub mod led; -pub mod nfc; pub mod result; pub mod rng; pub mod timer; From 16870d52c55c3e55344c2d4e2300157209c9a74f Mon Sep 17 00:00:00 2001 From: Mirna Date: Thu, 22 Oct 2020 12:15:24 +0200 Subject: [PATCH 03/32] Update the application logic --- examples/nfct_test.rs | 131 ++++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 36 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index 42176cd..f7ae3a4 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -6,6 +6,7 @@ extern crate lang_items; use core::fmt::Write; use libtock_drivers::console::Console; use libtock_drivers::nfc::NfcTag; +use libtock_drivers::nfc::RecvOp; #[allow(dead_code)] /// Helper function to write a slice into a fixed @@ -16,52 +17,110 @@ fn write_tx_buffer(buf: &mut [u8], slice: &[u8]) { } } +#[derive(PartialEq, Eq)] +enum State { + Enabled, + Disabled, +} + fn main() { let mut console = Console::new(); writeln!(console, "****************************************").unwrap(); writeln!(console, "nfct_test application is installed").unwrap(); - // 1. Configure Type 4 tag - if NfcTag::configure(4) { - writeln!(console, " -- TAG CONFIGURED").unwrap(); - } - // 2. Subscribe to a SELECTED CALLBACK - if NfcTag::selected() { - writeln!(console, " -- TAG SELECTED").unwrap(); - // 0xfffff results in 1048575 / 13.56e6 = 77ms - NfcTag::set_framedelaymax(0xfffff); - } - /* - [_.] TODO: Enable Tag emulation (currently the tag is always activated) - needs field detection support in the driver level. - */ - let mut rx_buf = [0; 64]; - let mut unknown_cmd_cntr = 0; + let mut state = State::Disabled; + let mut state_change_cntr = 0; loop { - NfcTag::receive(&mut rx_buf); - match rx_buf[0] { - 0xe0 /* RATS */=> { - let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; - let amount = answer_to_select.len(); - NfcTag::transmit(&mut answer_to_select, amount); + match state { + State::Enabled => { + let mut rx_buf = [0; 256]; + loop { + match NfcTag::receive(&mut rx_buf) { + Ok(RecvOp { + recv_amount: amount, + .. + }) => match amount { + 1 => writeln!(console, " -- RX Packet: {:02x?}", rx_buf[0],).unwrap(), + 2 => writeln!( + console, + " -- RX Packet: {:02x?} {:02x?}", + rx_buf[0], rx_buf[1], + ) + .unwrap(), + 3 => writeln!( + console, + " -- RX Packet: {:02x?} {:02x?} {:02x?}", + rx_buf[0], rx_buf[1], rx_buf[2], + ) + .unwrap(), + _ => writeln!( + console, + " -- RX Packet: {:02x?} {:02x?} {:02x?} {:02x?}", + rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3], + ) + .unwrap(), + }, + Err(_) => writeln!(console, " -- rx error!").unwrap(), + } + + match rx_buf[0] { + 0xe0 /* RATS */=> { + let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; + let amount = answer_to_select.len(); + match NfcTag::transmit(&mut answer_to_select, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } + } + 0xc2 /* DESELECT */ => { + // Ignore the request + let mut command_error = [0x6A, 0x81]; + let amount = command_error.len(); + match NfcTag::transmit(&mut command_error, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } + } + 0x02 | 0x03 /* APDU Prefix */ => { + let mut reply = [rx_buf[0], 0x90, 0x00]; + let amount = reply.len(); + match NfcTag::transmit(&mut reply, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } + } + 0x52 | 0x50 /* WUPA | Halt */ => { + if NfcTag::disable_emulation() { + writeln!(console, " -- TAG DISABLED").unwrap(); + } + state = State::Disabled; + break; + } + _ => { + } + } + } } - 0xc2 /* DESELECT */ => { - // Ignore the request - let mut command_error = [0x6A, 0x81]; - let amount = command_error.len(); - NfcTag::transmit(&mut command_error, amount); - } - 0x02 | 0x03 /* APDU Prefix */ => { - let mut reply = [rx_buf[0], 0x90, 0x00]; - let amount = reply.len(); - NfcTag::transmit(&mut reply, amount); - } - _ => { - unknown_cmd_cntr += 1; + State::Disabled => { + if NfcTag::enable_emulation() { + writeln!(console, " -- TAG ENABLED").unwrap(); + } + // 1. Configure Type 4 tag + if NfcTag::configure(4) { + writeln!(console, " -- TAG CONFIGURED").unwrap(); + } + // 2. Subscribe to a SELECTED CALLBACK + if NfcTag::selected() { + writeln!(console, " -- TAG SELECTED").unwrap(); + // 0xfffff results in 1048575 / 13.56e6 = 77ms + NfcTag::set_framedelaymax(0xfffff); + } + state = State::Enabled; } } - if unknown_cmd_cntr > 50 { + state_change_cntr += 1; + if state_change_cntr > 10 && state == State::Disabled { break; } } From 69194f3960c6b44ef881fa301b11511725f9da0e Mon Sep 17 00:00:00 2001 From: Mirna Date: Thu, 29 Oct 2020 23:07:23 +0200 Subject: [PATCH 04/32] Updates in the application logic --- examples/nfct_test.rs | 102 ++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index f7ae3a4..82a1625 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -1,23 +1,44 @@ #![no_std] +#![allow(unused_imports)] extern crate alloc; extern crate lang_items; +extern crate libtock_drivers; use core::fmt::Write; +use libtock_core::result::CommandError; use libtock_drivers::console::Console; +#[cfg(feature = "with_nfc")] use libtock_drivers::nfc::NfcTag; +#[cfg(feature = "with_nfc")] use libtock_drivers::nfc::RecvOp; +use libtock_drivers::result::TockError; #[allow(dead_code)] -/// Helper function to write a slice into a fixed -/// length transmission buffer. +/// Helper function to write a slice into a transmission buffer. fn write_tx_buffer(buf: &mut [u8], slice: &[u8]) { for (i, &byte) in slice.iter().enumerate() { buf[i] = byte; } } +#[allow(dead_code)] +/// Helper function to write on console the received packet. +fn print_rx_buffer(buf: &mut [u8], amount: usize) { + if amount < 1 || amount > buf.len() { + return; + } + let mut console = Console::new(); + write!(console, " -- RX Packet:").unwrap(); + for byte in buf.iter().take(amount - 1) { + write!(console, " {:02x?}", byte).unwrap(); + } + writeln!(console, " {:02x?}", buf[amount - 1]).unwrap(); +} + +#[cfg(feature = "with_nfc")] #[derive(PartialEq, Eq)] +/// enum for reserving the NFC tag state. enum State { Enabled, Disabled, @@ -29,8 +50,11 @@ fn main() { writeln!(console, "****************************************").unwrap(); writeln!(console, "nfct_test application is installed").unwrap(); + #[cfg(feature = "with_nfc")] let mut state = State::Disabled; + #[cfg(feature = "with_nfc")] let mut state_change_cntr = 0; + #[cfg(feature = "with_nfc")] loop { match state { State::Enabled => { @@ -40,28 +64,15 @@ fn main() { Ok(RecvOp { recv_amount: amount, .. - }) => match amount { - 1 => writeln!(console, " -- RX Packet: {:02x?}", rx_buf[0],).unwrap(), - 2 => writeln!( - console, - " -- RX Packet: {:02x?} {:02x?}", - rx_buf[0], rx_buf[1], - ) - .unwrap(), - 3 => writeln!( - console, - " -- RX Packet: {:02x?} {:02x?} {:02x?}", - rx_buf[0], rx_buf[1], rx_buf[2], - ) - .unwrap(), - _ => writeln!( - console, - " -- RX Packet: {:02x?} {:02x?} {:02x?} {:02x?}", - rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3], - ) - .unwrap(), - }, - Err(_) => writeln!(console, " -- rx error!").unwrap(), + }) => print_rx_buffer(&mut rx_buf, amount), + Err(TockError::Command(CommandError { + return_code: -4, /* EOFF: Not Ready */ + .. + })) => (), + Err(TockError::Command(CommandError { + return_code: value, .. + })) => writeln!(console, " -- Err({})!", value).unwrap(), + Err(_) => writeln!(console, " -- RX ERROR").unwrap(), } match rx_buf[0] { @@ -83,11 +94,24 @@ fn main() { } } 0x02 | 0x03 /* APDU Prefix */ => { - let mut reply = [rx_buf[0], 0x90, 0x00]; - let amount = reply.len(); - match NfcTag::transmit(&mut reply, amount) { - Ok(_) => (), - Err(_) => writeln!(console, " -- tx error!").unwrap(), + // If the received packet is applet selection command (FIDO 2) + if rx_buf[1] == 0x00 && rx_buf[2] == 0xa4 && rx_buf[3] == 0x04 { + // Vesion: "U2F_V2" + // let mut reply = [rx_buf[0], 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00,]; + // Vesion: "FIDO_2_0" + let mut reply = [rx_buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; + let amount = reply.len(); + match NfcTag::transmit(&mut reply, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } + } else { + let mut reply = [rx_buf[0], 0x90, 0x00]; + let amount = reply.len(); + match NfcTag::transmit(&mut reply, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } } } 0x52 | 0x50 /* WUPA | Halt */ => { @@ -97,30 +121,20 @@ fn main() { state = State::Disabled; break; } - _ => { - } + _ => (), } } } State::Disabled => { - if NfcTag::enable_emulation() { - writeln!(console, " -- TAG ENABLED").unwrap(); - } - // 1. Configure Type 4 tag + NfcTag::enable_emulation(); + // Configure Type 4 tag if NfcTag::configure(4) { - writeln!(console, " -- TAG CONFIGURED").unwrap(); + state = State::Enabled; } - // 2. Subscribe to a SELECTED CALLBACK - if NfcTag::selected() { - writeln!(console, " -- TAG SELECTED").unwrap(); - // 0xfffff results in 1048575 / 13.56e6 = 77ms - NfcTag::set_framedelaymax(0xfffff); - } - state = State::Enabled; } } state_change_cntr += 1; - if state_change_cntr > 10 && state == State::Disabled { + if state_change_cntr > 100 && state == State::Disabled { break; } } From dd814b8ded7cbe8fa2bf00d8e8d970974feb5cd4 Mon Sep 17 00:00:00 2001 From: Mirna Date: Mon, 2 Nov 2020 10:49:00 +0200 Subject: [PATCH 05/32] Remove duplicate code --- examples/nfct_test.rs | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index 82a1625..90d2dfa 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -36,6 +36,16 @@ fn print_rx_buffer(buf: &mut [u8], amount: usize) { writeln!(console, " {:02x?}", buf[amount - 1]).unwrap(); } +#[allow(dead_code)] +/// Helper function to write on console the received packet. +fn transmit_slice(buf: &mut [u8] { + let amount = buf.len(); + match NfcTag::transmit(&mut buf, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } +} + #[cfg(feature = "with_nfc")] #[derive(PartialEq, Eq)] /// enum for reserving the NFC tag state. @@ -72,46 +82,30 @@ fn main() { Err(TockError::Command(CommandError { return_code: value, .. })) => writeln!(console, " -- Err({})!", value).unwrap(), - Err(_) => writeln!(console, " -- RX ERROR").unwrap(), + Err(_) => writeln!(console, " -- RX Err").unwrap(), } match rx_buf[0] { 0xe0 /* RATS */=> { let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; - let amount = answer_to_select.len(); - match NfcTag::transmit(&mut answer_to_select, amount) { - Ok(_) => (), - Err(_) => writeln!(console, " -- tx error!").unwrap(), - } + transmit_slice(&mut answer_to_select); } 0xc2 /* DESELECT */ => { // Ignore the request let mut command_error = [0x6A, 0x81]; - let amount = command_error.len(); - match NfcTag::transmit(&mut command_error, amount) { - Ok(_) => (), - Err(_) => writeln!(console, " -- tx error!").unwrap(), - } + transmit_slice(&mut command_error); } 0x02 | 0x03 /* APDU Prefix */ => { // If the received packet is applet selection command (FIDO 2) if rx_buf[1] == 0x00 && rx_buf[2] == 0xa4 && rx_buf[3] == 0x04 { - // Vesion: "U2F_V2" + /// Vesion: "U2F_V2" // let mut reply = [rx_buf[0], 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00,]; - // Vesion: "FIDO_2_0" + /// Vesion: "FIDO_2_0" let mut reply = [rx_buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; - let amount = reply.len(); - match NfcTag::transmit(&mut reply, amount) { - Ok(_) => (), - Err(_) => writeln!(console, " -- tx error!").unwrap(), - } + transmit_slice(&mut reply); } else { let mut reply = [rx_buf[0], 0x90, 0x00]; - let amount = reply.len(); - match NfcTag::transmit(&mut reply, amount) { - Ok(_) => (), - Err(_) => writeln!(console, " -- tx error!").unwrap(), - } + transmit_slice(&mut reply); } } 0x52 | 0x50 /* WUPA | Halt */ => { From f9705d7c26ac4dfe507a0f0703cfb083a8d7dd3a Mon Sep 17 00:00:00 2001 From: Mirna Date: Mon, 2 Nov 2020 11:13:58 +0200 Subject: [PATCH 06/32] example app --- deploy.py | 7 +++ examples/nfct_test.rs | 136 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 examples/nfct_test.rs diff --git a/deploy.py b/deploy.py index 358355d..e1ec38f 100755 --- a/deploy.py +++ b/deploy.py @@ -928,6 +928,13 @@ if __name__ == "__main__": const="console_test", help=("Compiles and installs the console_test example that tests the " "console driver with messages of various lengths.")) + apps_group.add_argument( + "--nfct_test", + dest="application", + action="store_const", + const="nfct_test", + help=("Compiles and installs the nfct_test example that tests the " + "NFC driver.")) main_parser.set_defaults(features=["with_ctap1"]) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs new file mode 100644 index 0000000..90d2dfa --- /dev/null +++ b/examples/nfct_test.rs @@ -0,0 +1,136 @@ +#![no_std] +#![allow(unused_imports)] + +extern crate alloc; +extern crate lang_items; +extern crate libtock_drivers; + +use core::fmt::Write; +use libtock_core::result::CommandError; +use libtock_drivers::console::Console; +#[cfg(feature = "with_nfc")] +use libtock_drivers::nfc::NfcTag; +#[cfg(feature = "with_nfc")] +use libtock_drivers::nfc::RecvOp; +use libtock_drivers::result::TockError; + +#[allow(dead_code)] +/// Helper function to write a slice into a transmission buffer. +fn write_tx_buffer(buf: &mut [u8], slice: &[u8]) { + for (i, &byte) in slice.iter().enumerate() { + buf[i] = byte; + } +} + +#[allow(dead_code)] +/// Helper function to write on console the received packet. +fn print_rx_buffer(buf: &mut [u8], amount: usize) { + if amount < 1 || amount > buf.len() { + return; + } + let mut console = Console::new(); + write!(console, " -- RX Packet:").unwrap(); + for byte in buf.iter().take(amount - 1) { + write!(console, " {:02x?}", byte).unwrap(); + } + writeln!(console, " {:02x?}", buf[amount - 1]).unwrap(); +} + +#[allow(dead_code)] +/// Helper function to write on console the received packet. +fn transmit_slice(buf: &mut [u8] { + let amount = buf.len(); + match NfcTag::transmit(&mut buf, amount) { + Ok(_) => (), + Err(_) => writeln!(console, " -- tx error!").unwrap(), + } +} + +#[cfg(feature = "with_nfc")] +#[derive(PartialEq, Eq)] +/// enum for reserving the NFC tag state. +enum State { + Enabled, + Disabled, +} + +fn main() { + let mut console = Console::new(); + + writeln!(console, "****************************************").unwrap(); + writeln!(console, "nfct_test application is installed").unwrap(); + + #[cfg(feature = "with_nfc")] + let mut state = State::Disabled; + #[cfg(feature = "with_nfc")] + let mut state_change_cntr = 0; + #[cfg(feature = "with_nfc")] + loop { + match state { + State::Enabled => { + let mut rx_buf = [0; 256]; + loop { + match NfcTag::receive(&mut rx_buf) { + Ok(RecvOp { + recv_amount: amount, + .. + }) => print_rx_buffer(&mut rx_buf, amount), + Err(TockError::Command(CommandError { + return_code: -4, /* EOFF: Not Ready */ + .. + })) => (), + Err(TockError::Command(CommandError { + return_code: value, .. + })) => writeln!(console, " -- Err({})!", value).unwrap(), + Err(_) => writeln!(console, " -- RX Err").unwrap(), + } + + match rx_buf[0] { + 0xe0 /* RATS */=> { + let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; + transmit_slice(&mut answer_to_select); + } + 0xc2 /* DESELECT */ => { + // Ignore the request + let mut command_error = [0x6A, 0x81]; + transmit_slice(&mut command_error); + } + 0x02 | 0x03 /* APDU Prefix */ => { + // If the received packet is applet selection command (FIDO 2) + if rx_buf[1] == 0x00 && rx_buf[2] == 0xa4 && rx_buf[3] == 0x04 { + /// Vesion: "U2F_V2" + // let mut reply = [rx_buf[0], 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00,]; + /// Vesion: "FIDO_2_0" + let mut reply = [rx_buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; + transmit_slice(&mut reply); + } else { + let mut reply = [rx_buf[0], 0x90, 0x00]; + transmit_slice(&mut reply); + } + } + 0x52 | 0x50 /* WUPA | Halt */ => { + if NfcTag::disable_emulation() { + writeln!(console, " -- TAG DISABLED").unwrap(); + } + state = State::Disabled; + break; + } + _ => (), + } + } + } + State::Disabled => { + NfcTag::enable_emulation(); + // Configure Type 4 tag + if NfcTag::configure(4) { + state = State::Enabled; + } + } + } + state_change_cntr += 1; + if state_change_cntr > 100 && state == State::Disabled { + break; + } + } + writeln!(console, "****************************************").unwrap(); +} From eb3187680701dba632759160407dbce3c55bc232 Mon Sep 17 00:00:00 2001 From: Mirna Date: Wed, 4 Nov 2020 17:48:50 +0200 Subject: [PATCH 07/32] Update to calculate elapsed time for transmission --- examples/nfct_test.rs | 94 +++++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index 90d2dfa..9da0312 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -1,5 +1,4 @@ #![no_std] -#![allow(unused_imports)] extern crate alloc; extern crate lang_items; @@ -8,19 +7,13 @@ extern crate libtock_drivers; use core::fmt::Write; use libtock_core::result::CommandError; use libtock_drivers::console::Console; -#[cfg(feature = "with_nfc")] use libtock_drivers::nfc::NfcTag; -#[cfg(feature = "with_nfc")] use libtock_drivers::nfc::RecvOp; +use libtock_drivers::result::FlexUnwrap; use libtock_drivers::result::TockError; - -#[allow(dead_code)] -/// Helper function to write a slice into a transmission buffer. -fn write_tx_buffer(buf: &mut [u8], slice: &[u8]) { - for (i, &byte) in slice.iter().enumerate() { - buf[i] = byte; - } -} +use libtock_drivers::timer; +use libtock_drivers::timer::Timer; +use libtock_drivers::timer::Timestamp; #[allow(dead_code)] /// Helper function to write on console the received packet. @@ -29,24 +22,37 @@ fn print_rx_buffer(buf: &mut [u8], amount: usize) { return; } let mut console = Console::new(); - write!(console, " -- RX Packet:").unwrap(); + write!(console, "RX:").unwrap(); for byte in buf.iter().take(amount - 1) { write!(console, " {:02x?}", byte).unwrap(); } writeln!(console, " {:02x?}", buf[amount - 1]).unwrap(); + console.flush(); } #[allow(dead_code)] -/// Helper function to write on console the received packet. -fn transmit_slice(buf: &mut [u8] { +/// Function to identify the time elapsed for a transmission request. +fn bench_transmit(console: &mut Console, timer: &Timer, title: &str, mut buf: &mut [u8]) { let amount = buf.len(); + let start = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); match NfcTag::transmit(&mut buf, amount) { Ok(_) => (), - Err(_) => writeln!(console, " -- tx error!").unwrap(), + Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(), } + let end = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); + let elapsed = (end - start).ms(); + writeln!( + console, + "{}\n{:.2} ms elapsed for {} bytes ({:.2} kbit/s)", + title, + elapsed, + amount, + (amount as f64) / elapsed * 8. + ) + .unwrap(); + console.flush(); } -#[cfg(feature = "with_nfc")] #[derive(PartialEq, Eq)] /// enum for reserving the NFC tag state. enum State { @@ -56,15 +62,23 @@ enum State { fn main() { let mut console = Console::new(); + // Setup the timer with a dummy callback (we only care about reading the current time, but the + // API forces us to set an alarm callback too). + let mut with_callback = timer::with_callback(|_, _| {}); + let timer = with_callback.init().flex_unwrap(); writeln!(console, "****************************************").unwrap(); writeln!(console, "nfct_test application is installed").unwrap(); + writeln!( + console, + "Clock frequency: {} Hz", + timer.clock_frequency().hz() + ) + .unwrap(); - #[cfg(feature = "with_nfc")] let mut state = State::Disabled; - #[cfg(feature = "with_nfc")] + // Variable to count the change in the tag's state let mut state_change_cntr = 0; - #[cfg(feature = "with_nfc")] loop { match state { State::Enabled => { @@ -88,24 +102,44 @@ fn main() { match rx_buf[0] { 0xe0 /* RATS */=> { let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; - transmit_slice(&mut answer_to_select); + bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select); } 0xc2 /* DESELECT */ => { // Ignore the request let mut command_error = [0x6A, 0x81]; - transmit_slice(&mut command_error); + bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error); } - 0x02 | 0x03 /* APDU Prefix */ => { + 0x02 | 0x03 /* APDU Prefix */ => match rx_buf[2] { // If the received packet is applet selection command (FIDO 2) - if rx_buf[1] == 0x00 && rx_buf[2] == 0xa4 && rx_buf[3] == 0x04 { - /// Vesion: "U2F_V2" - // let mut reply = [rx_buf[0], 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00,]; - /// Vesion: "FIDO_2_0" - let mut reply = [rx_buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; - transmit_slice(&mut reply); - } else { + 0xa4 /* SELECT */ => if rx_buf[3] == 0x04 && rx_buf[5] == 0x08 && rx_buf[6] == 0xa0 { + // Vesion: "FIDO_2_0" + let mut reply = [rx_buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply); + } else { + let mut reply = [rx_buf[0], 0x90, 0x00]; + bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + } + 0xb0 /* READ */ => match rx_buf[5] { + 0x02 => { + let mut reply = [rx_buf[0], 0x12, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: File Size", &mut reply); + } + 0x12 => { + let mut reply = [rx_buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply); + } + 0x0f => { + let mut reply = [rx_buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04, 0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: CC", &mut reply); + } + _ => { + let mut reply = [rx_buf[0], 0x90, 0x00]; + bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + } + } + _ => { let mut reply = [rx_buf[0], 0x90, 0x00]; - transmit_slice(&mut reply); + bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); } } 0x52 | 0x50 /* WUPA | Halt */ => { From aa47d1c278419cf329af99cffe5f65183b398bc7 Mon Sep 17 00:00:00 2001 From: Mirna Date: Wed, 4 Nov 2020 17:49:28 +0200 Subject: [PATCH 08/32] Supply NFC feature flag to desktop checks --- run_desktop_tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh index b22e1d8..696455f 100755 --- a/run_desktop_tests.sh +++ b/run_desktop_tests.sh @@ -31,7 +31,7 @@ cargo fmt --all -- --check cd ../.. echo "Running Clippy lints..." -cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings +cargo clippy --all-targets --features std --features with_nfc -- -A clippy::new_without_default -D warnings echo "Building sha256sum tool..." cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml @@ -53,7 +53,7 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ct cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose echo "Checking that examples build properly..." -cargo check --release --target=thumbv7em-none-eabi --examples +cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc echo "Checking that fuzz targets build properly..." cargo fuzz build From c232f2344c6232554d2a346660be1338aad2aacc Mon Sep 17 00:00:00 2001 From: Mirna Date: Wed, 4 Nov 2020 20:05:32 +0200 Subject: [PATCH 09/32] Resolve comments --- examples/nfct_test.rs | 326 +++++++++++++++++++++++------------------- run_desktop_tests.sh | 4 +- 2 files changed, 179 insertions(+), 151 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index 9da0312..2e0df8a 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -5,166 +5,194 @@ extern crate lang_items; extern crate libtock_drivers; use core::fmt::Write; -use libtock_core::result::CommandError; use libtock_drivers::console::Console; -use libtock_drivers::nfc::NfcTag; -use libtock_drivers::nfc::RecvOp; -use libtock_drivers::result::FlexUnwrap; -use libtock_drivers::result::TockError; -use libtock_drivers::timer; -use libtock_drivers::timer::Timer; -use libtock_drivers::timer::Timestamp; -#[allow(dead_code)] -/// Helper function to write on console the received packet. -fn print_rx_buffer(buf: &mut [u8], amount: usize) { - if amount < 1 || amount > buf.len() { - return; +#[cfg(not(feature = "with_nfc"))] +mod example { + use super::Console; + use super::Write; + + pub fn nfc(console: &mut Console) { + writeln!(console, "NFC feature flag is missing!").unwrap(); } - let mut console = Console::new(); - write!(console, "RX:").unwrap(); - for byte in buf.iter().take(amount - 1) { - write!(console, " {:02x?}", byte).unwrap(); - } - writeln!(console, " {:02x?}", buf[amount - 1]).unwrap(); - console.flush(); } -#[allow(dead_code)] -/// Function to identify the time elapsed for a transmission request. -fn bench_transmit(console: &mut Console, timer: &Timer, title: &str, mut buf: &mut [u8]) { - let amount = buf.len(); - let start = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); - match NfcTag::transmit(&mut buf, amount) { - Ok(_) => (), - Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(), - } - let end = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); - let elapsed = (end - start).ms(); - writeln!( - console, - "{}\n{:.2} ms elapsed for {} bytes ({:.2} kbit/s)", - title, - elapsed, - amount, - (amount as f64) / elapsed * 8. - ) - .unwrap(); - console.flush(); -} +#[cfg(feature = "with_nfc")] +mod example { -#[derive(PartialEq, Eq)] -/// enum for reserving the NFC tag state. -enum State { - Enabled, - Disabled, + use super::Console; + use super::Write; + use libtock_core::result::CommandError; + use libtock_drivers::nfc::NfcTag; + use libtock_drivers::nfc::RecvOp; + use libtock_drivers::result::FlexUnwrap; + use libtock_drivers::result::TockError; + use libtock_drivers::timer; + use libtock_drivers::timer::Timer; + use libtock_drivers::timer::Timestamp; + + /// Helper function to write on console the received packet. + fn print_rx_buffer(buf: &mut [u8]) { + let mut console = Console::new(); + write!(console, "RX:").unwrap(); + if let Some((last, bytes)) = buf.split_last() { + for byte in bytes { + write!(console, " {:02x?}", byte).unwrap(); + } + writeln!(console, " {:02x?}", last).unwrap(); + } + console.flush(); + } + + /// Function to identify the time elapsed for a transmission request. + fn bench_transmit(console: &mut Console, timer: &Timer, title: &str, mut buf: &mut [u8]) { + let amount = buf.len(); + let start = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); + match NfcTag::transmit(&mut buf, amount) { + Ok(_) => (), + Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(), + } + let end = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); + let elapsed = (end - start).ms(); + writeln!( + console, + "{}\n{:.2} ms elapsed for {} bytes ({:.2} kbit/s)", + title, + elapsed, + amount, + (amount as f64) / elapsed * 8. + ) + .unwrap(); + console.flush(); + } + + fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) { + match NfcTag::receive(&mut buf) { + Ok(RecvOp { + recv_amount: amount, + .. + }) => { + if amount > 0 && amount <= buf.len() { + print_rx_buffer(&mut buf[..amount]); + } + } + Err(TockError::Command(CommandError { + return_code: -4, /* EOFF: Not Ready */ + .. + })) => (), + Err(TockError::Command(CommandError { + return_code: value, .. + })) => writeln!(console, " -- Err({})!", value).unwrap(), + Err(_) => writeln!(console, " -- RX Err").unwrap(), + } + } + + fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> bool { + match buf[0] { + 0xe0 /* RATS */=> { + let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; + bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select); + } + 0xc2 /* DESELECT */ => { + // Ignore the request + let mut command_error = [0x6A, 0x81]; + bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error); + } + 0x02 | 0x03 /* APDU Prefix */ => match buf[2] { + // If the received packet is applet selection command (FIDO 2) + 0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 { + // Vesion: "FIDO_2_0" + let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply); + } else { + let mut reply = [buf[0], 0x90, 0x00]; + bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + } + 0xb0 /* READ */ => match buf[5] { + 0x02 => { + let mut reply = [buf[0], 0x12, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: File Size", &mut reply); + } + 0x12 => { + let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply); + } + 0x0f => { + let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04, + 0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,]; + bench_transmit(&mut console, &timer, "TX: CC", &mut reply); + } + _ => { + let mut reply = [buf[0], 0x90, 0x00]; + bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + } + } + _ => { + let mut reply = [buf[0], 0x90, 0x00]; + bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + } + } + 0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => { + if NfcTag::disable_emulation() { + writeln!(console, " -- TAG DISABLED").unwrap(); + } + return false; + } + _ => (), + } + true + } + + pub fn nfc(mut console: &mut Console) { + // Setup the timer with a dummy callback (we only care about reading the current time, but the + // API forces us to set an alarm callback too). + let mut with_callback = timer::with_callback(|_, _| {}); + let timer = with_callback.init().flex_unwrap(); + + writeln!( + console, + "Clock frequency: {} Hz", + timer.clock_frequency().hz() + ) + .unwrap(); + + let mut is_enabled = false; + let mut state_change_counter = 0; + loop { + match is_enabled { + true => { + let mut rx_buf = [0; 256]; + loop { + receive_packet(&mut console, &mut rx_buf); + // If the reader restarts the communication and we can't + // reply to the received packet, then disable the tag. + if !transmit_reply(&mut console, &timer, &rx_buf) { + is_enabled = false; + break; + } + } + } + false => { + NfcTag::enable_emulation(); + // Configure Type 4 tag + if NfcTag::configure(4) { + is_enabled = true; + } + } + } + state_change_counter += 1; + if state_change_counter > 100 && is_enabled == false { + break; + } + } + } } fn main() { let mut console = Console::new(); - // Setup the timer with a dummy callback (we only care about reading the current time, but the - // API forces us to set an alarm callback too). - let mut with_callback = timer::with_callback(|_, _| {}); - let timer = with_callback.init().flex_unwrap(); - writeln!(console, "****************************************").unwrap(); writeln!(console, "nfct_test application is installed").unwrap(); - writeln!( - console, - "Clock frequency: {} Hz", - timer.clock_frequency().hz() - ) - .unwrap(); - - let mut state = State::Disabled; - // Variable to count the change in the tag's state - let mut state_change_cntr = 0; - loop { - match state { - State::Enabled => { - let mut rx_buf = [0; 256]; - loop { - match NfcTag::receive(&mut rx_buf) { - Ok(RecvOp { - recv_amount: amount, - .. - }) => print_rx_buffer(&mut rx_buf, amount), - Err(TockError::Command(CommandError { - return_code: -4, /* EOFF: Not Ready */ - .. - })) => (), - Err(TockError::Command(CommandError { - return_code: value, .. - })) => writeln!(console, " -- Err({})!", value).unwrap(), - Err(_) => writeln!(console, " -- RX Err").unwrap(), - } - - match rx_buf[0] { - 0xe0 /* RATS */=> { - let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; - bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select); - } - 0xc2 /* DESELECT */ => { - // Ignore the request - let mut command_error = [0x6A, 0x81]; - bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error); - } - 0x02 | 0x03 /* APDU Prefix */ => match rx_buf[2] { - // If the received packet is applet selection command (FIDO 2) - 0xa4 /* SELECT */ => if rx_buf[3] == 0x04 && rx_buf[5] == 0x08 && rx_buf[6] == 0xa0 { - // Vesion: "FIDO_2_0" - let mut reply = [rx_buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply); - } else { - let mut reply = [rx_buf[0], 0x90, 0x00]; - bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); - } - 0xb0 /* READ */ => match rx_buf[5] { - 0x02 => { - let mut reply = [rx_buf[0], 0x12, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: File Size", &mut reply); - } - 0x12 => { - let mut reply = [rx_buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply); - } - 0x0f => { - let mut reply = [rx_buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04, 0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: CC", &mut reply); - } - _ => { - let mut reply = [rx_buf[0], 0x90, 0x00]; - bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); - } - } - _ => { - let mut reply = [rx_buf[0], 0x90, 0x00]; - bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); - } - } - 0x52 | 0x50 /* WUPA | Halt */ => { - if NfcTag::disable_emulation() { - writeln!(console, " -- TAG DISABLED").unwrap(); - } - state = State::Disabled; - break; - } - _ => (), - } - } - } - State::Disabled => { - NfcTag::enable_emulation(); - // Configure Type 4 tag - if NfcTag::configure(4) { - state = State::Enabled; - } - } - } - state_change_cntr += 1; - if state_change_cntr > 100 && state == State::Disabled { - break; - } - } + example::nfc(&mut console); writeln!(console, "****************************************").unwrap(); } diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh index 696455f..b22e1d8 100755 --- a/run_desktop_tests.sh +++ b/run_desktop_tests.sh @@ -31,7 +31,7 @@ cargo fmt --all -- --check cd ../.. echo "Running Clippy lints..." -cargo clippy --all-targets --features std --features with_nfc -- -A clippy::new_without_default -D warnings +cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings echo "Building sha256sum tool..." cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml @@ -53,7 +53,7 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ct cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose echo "Checking that examples build properly..." -cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc +cargo check --release --target=thumbv7em-none-eabi --examples echo "Checking that fuzz targets build properly..." cargo fuzz build From 0eaa2b529103d743fe62ba5e25c7d05a7008f044 Mon Sep 17 00:00:00 2001 From: Mirna Date: Wed, 4 Nov 2020 20:25:30 +0200 Subject: [PATCH 10/32] Refactor a `match` expression --- examples/nfct_test.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index 2e0df8a..0a81751 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -19,7 +19,6 @@ mod example { #[cfg(feature = "with_nfc")] mod example { - use super::Console; use super::Write; use libtock_core::result::CommandError; @@ -160,29 +159,26 @@ mod example { let mut is_enabled = false; let mut state_change_counter = 0; loop { - match is_enabled { - true => { - let mut rx_buf = [0; 256]; - loop { - receive_packet(&mut console, &mut rx_buf); - // If the reader restarts the communication and we can't - // reply to the received packet, then disable the tag. - if !transmit_reply(&mut console, &timer, &rx_buf) { - is_enabled = false; - break; - } + if is_enabled { + let mut rx_buf = [0; 256]; + loop { + receive_packet(&mut console, &mut rx_buf); + // If the reader restarts the communication and we can't + // reply to the received packet, then disable the tag. + if !transmit_reply(&mut console, &timer, &rx_buf) { + is_enabled = false; + break; } } - false => { - NfcTag::enable_emulation(); - // Configure Type 4 tag - if NfcTag::configure(4) { - is_enabled = true; - } + } else { + NfcTag::enable_emulation(); + // Configure Type 4 tag + if NfcTag::configure(4) { + is_enabled = true; } } state_change_counter += 1; - if state_change_counter > 100 && is_enabled == false { + if !is_enabled && state_change_counter > 100 { break; } } From 203367b081b14ca6622c6dee2ca29e1194397cf7 Mon Sep 17 00:00:00 2001 From: Mirna Date: Thu, 5 Nov 2020 10:38:59 +0200 Subject: [PATCH 11/32] Updated control flow + cleaned some code --- examples/nfct_test.rs | 45 ++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index 0a81751..a43a059 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -32,15 +32,15 @@ mod example { /// Helper function to write on console the received packet. fn print_rx_buffer(buf: &mut [u8]) { - let mut console = Console::new(); - write!(console, "RX:").unwrap(); if let Some((last, bytes)) = buf.split_last() { + let mut console = Console::new(); + write!(console, "RX:").unwrap(); for byte in bytes { write!(console, " {:02x?}", byte).unwrap(); } writeln!(console, " {:02x?}", last).unwrap(); + console.flush(); } - console.flush(); } /// Function to identify the time elapsed for a transmission request. @@ -65,25 +65,27 @@ mod example { console.flush(); } - fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) { + fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) -> bool { match NfcTag::receive(&mut buf) { Ok(RecvOp { recv_amount: amount, .. }) => { - if amount > 0 && amount <= buf.len() { + if amount <= buf.len() { print_rx_buffer(&mut buf[..amount]); } } Err(TockError::Command(CommandError { return_code: -4, /* EOFF: Not Ready */ .. - })) => (), + })) => return false, + // For the example app, just print any other received error without handling. Err(TockError::Command(CommandError { return_code: value, .. })) => writeln!(console, " -- Err({})!", value).unwrap(), Err(_) => writeln!(console, " -- RX Err").unwrap(), } + true } fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> bool { @@ -156,29 +158,24 @@ mod example { ) .unwrap(); - let mut is_enabled = false; let mut state_change_counter = 0; loop { - if is_enabled { + while !NfcTag::enable_emulation() {} + // Configure Type 4 tag + while !NfcTag::configure(4) {} + state_change_counter += 1; + loop { let mut rx_buf = [0; 256]; - loop { - receive_packet(&mut console, &mut rx_buf); - // If the reader restarts the communication and we can't - // reply to the received packet, then disable the tag. - if !transmit_reply(&mut console, &timer, &rx_buf) { - is_enabled = false; - break; - } - } - } else { - NfcTag::enable_emulation(); - // Configure Type 4 tag - if NfcTag::configure(4) { - is_enabled = true; + // Await a successful receive + while !receive_packet(&mut console, &mut rx_buf) {} + // If the reader restarts the communication and we can't + // reply to the received packet, then disable the tag. + if !transmit_reply(&mut console, &timer, &rx_buf) { + state_change_counter += 1; + break; } } - state_change_counter += 1; - if !is_enabled && state_change_counter > 100 { + if state_change_counter > 100 { break; } } From f48046d1e4cb3adde676c62c0c6b0db2f6f62e03 Mon Sep 17 00:00:00 2001 From: Mirna Date: Thu, 5 Nov 2020 10:39:51 +0200 Subject: [PATCH 12/32] Added checks using NFC feature flag --- run_desktop_tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh index b22e1d8..49c2990 100755 --- a/run_desktop_tests.sh +++ b/run_desktop_tests.sh @@ -32,6 +32,7 @@ cd ../.. echo "Running Clippy lints..." cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings +cargo clippy --all-targets --features std,with_nfc -- -A clippy::new_without_default -D warnings echo "Building sha256sum tool..." cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml @@ -54,6 +55,7 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ct echo "Checking that examples build properly..." cargo check --release --target=thumbv7em-none-eabi --examples +cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc echo "Checking that fuzz targets build properly..." cargo fuzz build From 84dec3f636fa7c261135ff8b0930621990f98b71 Mon Sep 17 00:00:00 2001 From: Mirna Date: Tue, 10 Nov 2020 16:57:31 +0200 Subject: [PATCH 13/32] Update in the application's workflow --- examples/nfct_test.rs | 150 +++++++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 46 deletions(-) diff --git a/examples/nfct_test.rs b/examples/nfct_test.rs index a43a059..924237d 100644 --- a/examples/nfct_test.rs +++ b/examples/nfct_test.rs @@ -30,6 +30,41 @@ mod example { use libtock_drivers::timer::Timer; use libtock_drivers::timer::Timestamp; + #[derive(Copy, Clone, Debug, PartialEq)] + enum ReturnCode { + /// Operation completed successfully + SUCCESS, + /// Generic failure condition + FAIL, + /// Underlying system is busy; retry + EBUSY, + /// The component is powered down + EOFF, + /// An invalid parameter was passed + EINVAL, + /// Operation canceled by a call + ECANCEL, + /// Memory required not available + ENOMEM, + /// Operation or command is unsupported + ENOSUPPORT, + } + + impl From for ReturnCode { + fn from(original: isize) -> ReturnCode { + match original { + 0 => ReturnCode::SUCCESS, + -1 => ReturnCode::FAIL, + -2 => ReturnCode::EBUSY, + -4 => ReturnCode::EOFF, + -6 => ReturnCode::EINVAL, + -8 => ReturnCode::ECANCEL, + -9 => ReturnCode::ENOMEM, + _ => ReturnCode::ENOSUPPORT, + } + } + } + /// Helper function to write on console the received packet. fn print_rx_buffer(buf: &mut [u8]) { if let Some((last, bytes)) = buf.split_last() { @@ -44,11 +79,20 @@ mod example { } /// Function to identify the time elapsed for a transmission request. - fn bench_transmit(console: &mut Console, timer: &Timer, title: &str, mut buf: &mut [u8]) { + fn bench_transmit( + console: &mut Console, + timer: &Timer, + title: &str, + mut buf: &mut [u8], + ) -> ReturnCode { let amount = buf.len(); let start = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); match NfcTag::transmit(&mut buf, amount) { Ok(_) => (), + Err(TockError::Command(CommandError { + return_code: -8, /* ECANCEL: No Field*/ + .. + })) => return ReturnCode::ECANCEL, Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(), } let end = Timestamp::::from_clock_value(timer.get_current_clock().flex_unwrap()); @@ -63,9 +107,10 @@ mod example { ) .unwrap(); console.flush(); + ReturnCode::SUCCESS } - fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) -> bool { + fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) -> ReturnCode { match NfcTag::receive(&mut buf) { Ok(RecvOp { recv_amount: amount, @@ -75,74 +120,71 @@ mod example { print_rx_buffer(&mut buf[..amount]); } } - Err(TockError::Command(CommandError { - return_code: -4, /* EOFF: Not Ready */ - .. - })) => return false, - // For the example app, just print any other received error without handling. - Err(TockError::Command(CommandError { - return_code: value, .. - })) => writeln!(console, " -- Err({})!", value).unwrap(), - Err(_) => writeln!(console, " -- RX Err").unwrap(), + Err(TockError::Command(CommandError { return_code, .. })) => return return_code.into(), + Err(_) => { + writeln!(console, " -- RX Err").unwrap(); + return ReturnCode::ECANCEL; + } } - true + ReturnCode::SUCCESS } - fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> bool { + fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> ReturnCode { + let mut return_code = ReturnCode::SUCCESS; match buf[0] { 0xe0 /* RATS */=> { let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; - bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select); + return_code = bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select); } 0xc2 /* DESELECT */ => { // Ignore the request let mut command_error = [0x6A, 0x81]; - bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error); + return_code = bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error); } 0x02 | 0x03 /* APDU Prefix */ => match buf[2] { // If the received packet is applet selection command (FIDO 2) 0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 { - // Vesion: "FIDO_2_0" - let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply); - } else { - let mut reply = [buf[0], 0x90, 0x00]; - bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); - } + // Vesion: "FIDO_2_0" + let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; + return_code = bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply); + } else if (buf[6] == 0xd2 && buf[7] == 0x76) || (buf[6] == 0xe1 && (buf[7] == 0x03 || buf[7] == 0x04)){ + let mut reply = [buf[0], 0x90, 0x00]; + return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + } else /* Unknown file */ { + let mut reply = [buf[0], 0x6a, 0x82]; + return_code = bench_transmit(&mut console, &timer, "TX: 0x6A82", &mut reply); + } 0xb0 /* READ */ => match buf[5] { - 0x02 => { + 0x02 => { let mut reply = [buf[0], 0x12, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: File Size", &mut reply); + return_code = bench_transmit(&mut console, &timer, "TX: File Size", &mut reply); } 0x12 => { let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply); + return_code = bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply); } 0x0f => { let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04, 0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,]; - bench_transmit(&mut console, &timer, "TX: CC", &mut reply); + return_code = bench_transmit(&mut console, &timer, "TX: CC", &mut reply); } _ => { let mut reply = [buf[0], 0x90, 0x00]; - bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); } } _ => { let mut reply = [buf[0], 0x90, 0x00]; - bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); + return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply); } } 0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => { - if NfcTag::disable_emulation() { - writeln!(console, " -- TAG DISABLED").unwrap(); - } - return false; + return ReturnCode::EOFF; } _ => (), } - true + return_code } pub fn nfc(mut console: &mut Console) { @@ -160,19 +202,35 @@ mod example { let mut state_change_counter = 0; loop { - while !NfcTag::enable_emulation() {} - // Configure Type 4 tag - while !NfcTag::configure(4) {} - state_change_counter += 1; - loop { - let mut rx_buf = [0; 256]; - // Await a successful receive - while !receive_packet(&mut console, &mut rx_buf) {} - // If the reader restarts the communication and we can't - // reply to the received packet, then disable the tag. - if !transmit_reply(&mut console, &timer, &rx_buf) { - state_change_counter += 1; - break; + let mut rx_buf = [0; 256]; + match receive_packet(&mut console, &mut rx_buf) { + ReturnCode::EOFF => { + // Not configured + while !NfcTag::enable_emulation() {} + // Configure Type 4 tag + while !NfcTag::configure(4) {} + } + ReturnCode::ECANCEL /* field lost */ => { + NfcTag::disable_emulation(); + } + ReturnCode::EBUSY /* awaiting select*/ => (), + ReturnCode::ENOMEM => { + writeln!(console, " -- Amount more than buffer limit").unwrap() + } + ReturnCode::FAIL => writeln!(console, " -- Invalid CRC").unwrap(), + ReturnCode::EINVAL /* covered in driver interface */ => (), + ReturnCode::ENOSUPPORT => (), + ReturnCode::SUCCESS => { + // If the reader restarts the communication then disable the tag. + match transmit_reply(&mut console, &timer, &rx_buf) { + ReturnCode::ECANCEL | ReturnCode::EOFF => { + if NfcTag::disable_emulation() { + writeln!(console, " -- TAG DISABLED").unwrap(); + } + state_change_counter += 1; + } + _ => (), + } } } if state_change_counter > 100 { From 163e92fa6bfb05cfc3ba1cfb88ee5d38c9d948f9 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Wed, 11 Nov 2020 12:30:24 +0100 Subject: [PATCH 14/32] Create fuzzing and add entropy helpers --- .github/workflows/cargo_fuzz.yml | 2 + libraries/persistent_store/fuzz/.gitignore | 4 + libraries/persistent_store/fuzz/Cargo.toml | 21 +++ .../fuzz/fuzz_targets/store.rs | 21 +++ libraries/persistent_store/fuzz/src/lib.rs | 152 ++++++++++++++++++ 5 files changed, 200 insertions(+) create mode 100644 libraries/persistent_store/fuzz/.gitignore create mode 100644 libraries/persistent_store/fuzz/Cargo.toml create mode 100644 libraries/persistent_store/fuzz/fuzz_targets/store.rs create mode 100644 libraries/persistent_store/fuzz/src/lib.rs diff --git a/.github/workflows/cargo_fuzz.yml b/.github/workflows/cargo_fuzz.yml index 4143b5a..b71b10a 100644 --- a/.github/workflows/cargo_fuzz.yml +++ b/.github/workflows/cargo_fuzz.yml @@ -29,3 +29,5 @@ jobs: run: cargo fuzz build - name: Cargo fuzz build (libraries/cbor) run: cd libraries/cbor && cargo fuzz build && cd ../.. + - name: Cargo fuzz build (libraries/persistent_store) + run: cd libraries/persistent_store && cargo fuzz build && cd ../.. diff --git a/libraries/persistent_store/fuzz/.gitignore b/libraries/persistent_store/fuzz/.gitignore new file mode 100644 index 0000000..110126b --- /dev/null +++ b/libraries/persistent_store/fuzz/.gitignore @@ -0,0 +1,4 @@ +/Cargo.lock +/artifacts/ +/corpus/ +/target/ diff --git a/libraries/persistent_store/fuzz/Cargo.toml b/libraries/persistent_store/fuzz/Cargo.toml new file mode 100644 index 0000000..18b986d --- /dev/null +++ b/libraries/persistent_store/fuzz/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "fuzz-store" +version = "0.0.0" +authors = ["Julien Cretin "] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.3" +persistent_store = { path = "..", features = ["std"] } + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "store" +path = "fuzz_targets/store.rs" diff --git a/libraries/persistent_store/fuzz/fuzz_targets/store.rs b/libraries/persistent_store/fuzz/fuzz_targets/store.rs new file mode 100644 index 0000000..1cff2a4 --- /dev/null +++ b/libraries/persistent_store/fuzz/fuzz_targets/store.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // TODO(ia0): Call fuzzing when implemented. +}); diff --git a/libraries/persistent_store/fuzz/src/lib.rs b/libraries/persistent_store/fuzz/src/lib.rs new file mode 100644 index 0000000..87ca7aa --- /dev/null +++ b/libraries/persistent_store/fuzz/src/lib.rs @@ -0,0 +1,152 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO(ia0): Remove when used. +#![allow(dead_code)] + +/// Bit-level entropy source based on a byte slice shared reference. +struct Entropy<'a> { + /// The byte slice shared reference providing the entropy. + data: &'a [u8], + + /// The bit position in the byte slice of the next entropy bit. + bit: usize, +} + +impl Entropy<'_> { + /// Creates a bit-level entropy given a byte slice. + fn new(data: &[u8]) -> Entropy { + let bit = 0; + Entropy { data, bit } + } + + /// Consumes the remaining entropy. + fn consume_all(&mut self) { + self.bit = 8 * self.data.len(); + } + + /// Returns whether there is entropy remaining. + fn is_empty(&self) -> bool { + assert!(self.bit <= 8 * self.data.len()); + self.bit == 8 * self.data.len() + } + + /// Reads a bit. + fn read_bit(&mut self) -> bool { + if self.is_empty() { + return false; + } + let b = self.bit; + self.bit += 1; + self.data[b / 8] & 1 << (b % 8) != 0 + } + + /// Reads a number with a given bit-width. + /// + /// # Preconditions + /// + /// - The number should fit in the return type: `n <= 8 * size_of::()`. + fn read_bits(&mut self, n: usize) -> usize { + assert!(n <= 8 * std::mem::size_of::()); + let mut r = 0; + for i in 0..n { + r |= (self.read_bit() as usize) << i; + } + r + } + + /// Reads a byte. + fn read_byte(&mut self) -> u8 { + self.read_bits(8) as u8 + } + + /// Reads a slice. + fn read_slice(&mut self, length: usize) -> Vec { + let mut result = Vec::with_capacity(length); + for _ in 0..length { + result.push(self.read_byte()); + } + result + } + + /// Reads a bounded number. + /// + /// # Preconditions + /// + /// - The bounds should be correctly ordered: `min <= max`. + /// - The upper-bound should not be too large: `max < usize::max_value()`. + fn read_range(&mut self, min: usize, max: usize) -> usize { + assert!(min <= max && max < usize::max_value()); + let count = max - min + 1; + let delta = self.read_bits(num_bits(count - 1)) % count; + min + delta + } +} + +/// Returns the number of bits necessary to represent a number. +fn num_bits(x: usize) -> usize { + 8 * std::mem::size_of::() - x.leading_zeros() as usize +} + +#[test] +fn num_bits_ok() { + assert_eq!(num_bits(0), 0); + assert_eq!(num_bits(1), 1); + assert_eq!(num_bits(2), 2); + assert_eq!(num_bits(3), 2); + assert_eq!(num_bits(4), 3); + assert_eq!(num_bits(7), 3); + assert_eq!(num_bits(8), 4); + assert_eq!(num_bits(15), 4); + assert_eq!(num_bits(16), 5); + assert_eq!( + num_bits(usize::max_value()), + 8 * std::mem::size_of::() + ); +} + +#[test] +fn read_bit_ok() { + let mut entropy = Entropy::new(&[0b10110010]); + assert!(!entropy.read_bit()); + assert!(entropy.read_bit()); + assert!(!entropy.read_bit()); + assert!(!entropy.read_bit()); + assert!(entropy.read_bit()); + assert!(entropy.read_bit()); + assert!(!entropy.read_bit()); + assert!(entropy.read_bit()); +} + +#[test] +fn read_bits_ok() { + let mut entropy = Entropy::new(&[0x83, 0x92]); + assert_eq!(entropy.read_bits(4), 0x3); + assert_eq!(entropy.read_bits(8), 0x28); + assert_eq!(entropy.read_bits(2), 0b01); + assert_eq!(entropy.read_bits(2), 0b10); +} + +#[test] +fn read_range_ok() { + let mut entropy = Entropy::new(&[0b00101011]); + assert_eq!(entropy.read_range(0, 7), 0b011); + assert_eq!(entropy.read_range(1, 8), 1 + 0b101); + assert_eq!(entropy.read_range(4, 6), 4 + 0b00); + let mut entropy = Entropy::new(&[0b00101011]); + assert_eq!(entropy.read_range(0, 8), 0b1011 % 9); + assert_eq!(entropy.read_range(3, 15), 3 + 0b0010); + let mut entropy = Entropy::new(&[0x12, 0x34, 0x56, 0x78]); + assert_eq!(entropy.read_range(0, usize::max_value() - 1), 0x78563412); +} From d3ee698d6972e7c55fa4e8ee8be7b8fed82435ae Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Wed, 11 Nov 2020 12:56:36 +0100 Subject: [PATCH 15/32] Build store fuzzing in run_desktop_tests too --- run_desktop_tests.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh index b22e1d8..c3a853c 100755 --- a/run_desktop_tests.sh +++ b/run_desktop_tests.sh @@ -60,6 +60,9 @@ cargo fuzz build cd libraries/cbor cargo fuzz build cd ../.. +cd libraries/persistent_store +cargo fuzz build +cd ../.. echo "Checking that CTAP2 builds and links properly (1 set of features)..." cargo build --release --target=thumbv7em-none-eabi --features with_ctap1 From c6f9270be1d7d5ac75a585ca267602d525dace32 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Wed, 11 Nov 2020 17:52:33 +0100 Subject: [PATCH 16/32] Update documentation --- libraries/persistent_store/fuzz/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/persistent_store/fuzz/src/lib.rs b/libraries/persistent_store/fuzz/src/lib.rs index 87ca7aa..90e9dc4 100644 --- a/libraries/persistent_store/fuzz/src/lib.rs +++ b/libraries/persistent_store/fuzz/src/lib.rs @@ -80,7 +80,11 @@ impl Entropy<'_> { result } - /// Reads a bounded number. + /// Reads a number between `min` and `max` (inclusive bounds). + /// + /// The distribution is uniform if the range width is a power of two. Otherwise, the minimum + /// amount of entropy is used (the next power of two) and the distribution is the closest to + /// uniform for that entropy. /// /// # Preconditions /// From db5b21a4ff96c6a0a19a37bd5e0ca5b0b25a3062 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 12 Nov 2020 10:52:47 +0100 Subject: [PATCH 17/32] Add more documentation --- libraries/persistent_store/fuzz/src/lib.rs | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/libraries/persistent_store/fuzz/src/lib.rs b/libraries/persistent_store/fuzz/src/lib.rs index 90e9dc4..3f45620 100644 --- a/libraries/persistent_store/fuzz/src/lib.rs +++ b/libraries/persistent_store/fuzz/src/lib.rs @@ -12,10 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Fuzzing library for the persistent store. +//! +//! The overall design principles are (in order of precedence): +//! - Determinism: fuzzing is a function from seeds (byte slices) to sequences of store +//! manipulations (things like creating a store, applying operations, interrupting operations, +//! interrupting reboots, checking invariant, etc). We can replay this function on the same input +//! to get the same sequence of manipulations (for the same fuzzing and store code). +//! - Coverage: fuzzing tries to coverage as much different behaviors as possible for small seeds. +//! Ideally, each seed bit would control a branch decision in the tree of execution paths. +//! - Surjectivity: all sequences of manipulations are reachable by fuzzing for some seed. The only +//! situation where coverage takes precedence over surjectivity is for the value of insert updates +//! where a pseudo-random generator is used to avoid wasting entropy. + // TODO(ia0): Remove when used. #![allow(dead_code)] /// Bit-level entropy source based on a byte slice shared reference. +/// +/// The entropy has the following properties (in order of precedence): +/// - It always returns a result. +/// - It is deterministic: for a given slice and a given sequence of operations, the same results +/// are returned. This permits to replay and debug fuzzing artifacts. +/// - It uses the slice as a bit stream. In particular, it doesn't do big number arithmetic. This +/// permits to have a simple implementation. +/// - It doesn't waste information: for a given operation, the minimum integer number of bits is +/// used to produce the result. As a consequence fractional bits can be wasted at each operation. +/// - It uses the information uniformly: each bit is used exactly once, except when only a fraction +/// of it is used. In particular, a bit is not used more than once. A consequence of each bit +/// being used essentially once, is that the results are mostly uniformly distributed. +/// +/// # Invariant +/// +/// - The bit is a valid position in the slice, or one past: `bit <= 8 * data.len()`. struct Entropy<'a> { /// The byte slice shared reference providing the entropy. data: &'a [u8], From 1c2e450660884c4c599e617d8203fb66eef7e117 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 12 Nov 2020 16:24:35 +0100 Subject: [PATCH 18/32] Improve documentation --- libraries/persistent_store/fuzz/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/persistent_store/fuzz/src/lib.rs b/libraries/persistent_store/fuzz/src/lib.rs index 3f45620..1feeabc 100644 --- a/libraries/persistent_store/fuzz/src/lib.rs +++ b/libraries/persistent_store/fuzz/src/lib.rs @@ -30,6 +30,12 @@ /// Bit-level entropy source based on a byte slice shared reference. /// +/// This is used to convert the byte slice provided by the fuzzer into the entropy used by the +/// fuzzing code to generate a sequence of store manipulations, among other things. Entropy +/// operations use the shortest necessary sequence of bits from the byte slice, such that fuzzer +/// mutations of the byte slice have local impact or cascading effects towards future operations +/// only. +/// /// The entropy has the following properties (in order of precedence): /// - It always returns a result. /// - It is deterministic: for a given slice and a given sequence of operations, the same results From de77d4fc0cd13b7b22916babad4dd4d9a0f0f702 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 13 Nov 2020 10:34:23 +0100 Subject: [PATCH 19/32] Add histogram for fuzzing --- .../persistent_store/fuzz/src/histogram.rs | 100 ++++++++++++++++++ libraries/persistent_store/fuzz/src/lib.rs | 2 + 2 files changed, 102 insertions(+) create mode 100644 libraries/persistent_store/fuzz/src/histogram.rs diff --git a/libraries/persistent_store/fuzz/src/histogram.rs b/libraries/persistent_store/fuzz/src/histogram.rs new file mode 100644 index 0000000..0d76fd7 --- /dev/null +++ b/libraries/persistent_store/fuzz/src/histogram.rs @@ -0,0 +1,100 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::num_bits; +use std::collections::HashMap; + +/// Histogram with logarithmic buckets. +/// +/// This is used to compute coverage statistics of the fuzzing runs of a corpus. This is not used +/// during actual fuzzing, only when replaying a corpus to compute statistics. +#[derive(Default)] +pub struct Histogram { + /// Maps each bucket to its count. + /// + /// Buckets are numbers sharing the same highest bit. The first buckets are: only 0, only 1, 2 + /// to 3, 4 to 7, 8 to 15. Buckets are identified by their lower-bound. + buckets: HashMap, +} + +impl Histogram { + /// Increases the count of the bucket of an item. + /// + /// The bucket of `item` is the highest power of two, lower or equal to `item`. If `item` is + /// zero, then its bucket is also zero. + /// + /// # Panics + /// + /// Panics if the item is too big, i.e. it uses its most significant bit. + pub fn add(&mut self, item: usize) { + assert!(item <= usize::max_value() / 2); + *self.buckets.entry(get_bucket(item)).or_insert(0) += 1; + } + + /// Merges another histogram into this one. + pub fn merge(&mut self, other: &Histogram) { + for (&bucket, &count) in &other.buckets { + *self.buckets.entry(bucket).or_insert(0) += count; + } + } + + /// Returns one past the highest non-empty bucket. + /// + /// In other words, all non-empty buckets of the histogram are smaller than the returned bucket. + pub fn bucket_lim(&self) -> usize { + match self.buckets.keys().max() { + None => 0, + Some(0) => 1, + Some(x) => 2 * x, + } + } + + /// Returns the count of a bucket. + pub fn get(&self, bucket: usize) -> Option { + self.buckets.get(&bucket).cloned() + } + + /// Returns the total count. + pub fn count(&self) -> usize { + self.buckets.values().sum() + } +} + +/// Returns the bucket of an item. +fn get_bucket(item: usize) -> usize { + let bucket = bucket_from_width(num_bits(item)); + assert!(bucket <= item && (item == 0 || item / 2 < bucket)); + bucket +} + +/// Returns the bucket of an item given its bit-width. +pub fn bucket_from_width(width: usize) -> usize { + if width == 0 { + 0 + } else { + 1 << (width - 1) + } +} + +#[test] +fn get_bucket_ok() { + assert_eq!(get_bucket(0), 0); + assert_eq!(get_bucket(1), 1); + assert_eq!(get_bucket(2), 2); + assert_eq!(get_bucket(3), 2); + assert_eq!(get_bucket(4), 4); + assert_eq!(get_bucket(7), 4); + assert_eq!(get_bucket(8), 8); + assert_eq!(get_bucket(15), 8); +} diff --git a/libraries/persistent_store/fuzz/src/lib.rs b/libraries/persistent_store/fuzz/src/lib.rs index 1feeabc..3dd5932 100644 --- a/libraries/persistent_store/fuzz/src/lib.rs +++ b/libraries/persistent_store/fuzz/src/lib.rs @@ -28,6 +28,8 @@ // TODO(ia0): Remove when used. #![allow(dead_code)] +mod histogram; + /// Bit-level entropy source based on a byte slice shared reference. /// /// This is used to convert the byte slice provided by the fuzzer into the entropy used by the From fcc94845100e08f7c67f3811124ab67a14690489 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Mon, 16 Nov 2020 22:34:42 +0100 Subject: [PATCH 20/32] Add stats for fuzzing --- libraries/persistent_store/fuzz/Cargo.toml | 1 + libraries/persistent_store/fuzz/src/lib.rs | 3 + libraries/persistent_store/fuzz/src/stats.rs | 187 +++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 libraries/persistent_store/fuzz/src/stats.rs diff --git a/libraries/persistent_store/fuzz/Cargo.toml b/libraries/persistent_store/fuzz/Cargo.toml index 18b986d..fdc4f89 100644 --- a/libraries/persistent_store/fuzz/Cargo.toml +++ b/libraries/persistent_store/fuzz/Cargo.toml @@ -11,6 +11,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.3" persistent_store = { path = "..", features = ["std"] } +strum = { version = "0.19", features = ["derive"] } # Prevent this from interfering with workspaces [workspace] diff --git a/libraries/persistent_store/fuzz/src/lib.rs b/libraries/persistent_store/fuzz/src/lib.rs index 3dd5932..11645f3 100644 --- a/libraries/persistent_store/fuzz/src/lib.rs +++ b/libraries/persistent_store/fuzz/src/lib.rs @@ -29,6 +29,9 @@ #![allow(dead_code)] mod histogram; +mod stats; + +pub use stats::{StatKey, Stats}; /// Bit-level entropy source based on a byte slice shared reference. /// diff --git a/libraries/persistent_store/fuzz/src/stats.rs b/libraries/persistent_store/fuzz/src/stats.rs new file mode 100644 index 0000000..fdb5983 --- /dev/null +++ b/libraries/persistent_store/fuzz/src/stats.rs @@ -0,0 +1,187 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Helpers to compute and display fuzzing coverage statistics. +//! +//! This is not used during actual fuzzing, only when replaying a corpus to compute statistics. + +use crate::histogram::{bucket_from_width, Histogram}; +use crate::num_bits; +use std::collections::HashMap; +use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; + +/// Statistics for each fuzzing run. +#[derive(Copy, Clone, PartialEq, Eq, Hash, EnumIter, EnumString, Display)] +pub enum StatKey { + /// The available entropy in bytes. + Entropy, + + /// The size of a page in bytes. + PageSize, + + /// The number of pages. + NumPages, + + /// The maximum number times a page can be erased. + MaxPageErases, + + /// The dirty length of the initial storage in bytes. + /// + /// This is the length of the prefix of the storage that is written using entropy before the + /// store is initialized. This permits to check the store against an invalid storage: it should + /// not crash but may misbehave. + DirtyLength, + + /// The number of used erase cycles of the initial storage. + /// + /// This permits to check the store as if it already consumed lifetime. In particular it permits + /// to check the store when lifetime is almost out. + InitCycles, + + /// The number of words written during fuzzing. + /// + /// This permits to get an idea of how much lifetime was exercised during fuzzing. + UsedLifetime, + + /// Whether the store reached the end of the lifetime during fuzzing. + FinishedLifetime, + + /// The number of times the store was fully compacted. + /// + /// The store is considered fully compacted when all pages have been compacted once. So each + /// page has been compacted at least that number of times. + NumCompactions, + + /// The number of times the store was powered on. + PowerOnCount, + + /// The number of times a transaction was applied. + TransactionCount, + + /// The number of times a clear operation was applied. + ClearCount, + + /// The number of times a prepare operation was applied. + PrepareCount, + + /// The number of times an insert update was applied. + InsertCount, + + /// The number of times a remove update was applied. + RemoveCount, + + /// The number of times a store operation was interrupted. + InterruptionCount, +} + +/// Statistics about multiple fuzzing runs. +#[derive(Default)] +pub struct Stats { + /// Maps each statistics to its histogram. + stats: HashMap, +} + +impl Stats { + /// Adds a measure for a statistics. + pub fn add(&mut self, key: StatKey, value: usize) { + self.stats.entry(key).or_default().add(value); + } + + /// Merges another statistics into this one. + pub fn merge(&mut self, other: &Stats) { + for (&key, other) in &other.stats { + self.stats.entry(key).or_default().merge(other); + } + } + + /// Returns the count of a bucket for a given key. + pub fn get_count(&self, key: StatKey, bucket: usize) -> Option { + self.stats.get(&key).and_then(|h| h.get(bucket)) + } + + /// Returns one past the highest non-empty bucket. + /// + /// In other words, all non-empty buckets of the histogram are smaller than the returned bucket. + fn bucket_lim(&self) -> usize { + self.stats + .values() + .map(|h| h.bucket_lim()) + .max() + .unwrap_or(0) + } +} + +impl std::fmt::Display for Stats { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + let mut matrix: Vec> = Vec::new(); + + let mut header = Vec::new(); + header.push(String::new()); + let bits = num_bits(self.bucket_lim()); + for width in 0..bits { + header.push(format!(" {}", bucket_from_width(width))); + } + header.push(" count".into()); + matrix.push(header); + + for key in StatKey::iter() { + let mut row = Vec::new(); + row.push(format!("{}:", key)); + for width in 0..bits { + row.push(match self.get_count(key, bucket_from_width(width)) { + None => String::new(), + Some(x) => format!(" {}", x), + }); + } + let count = self.stats.get(&key).map_or(0, |h| h.count()); + row.push(format!(" {}", count)); + matrix.push(row); + } + + write_matrix(f, matrix) + } +} + +/// Prints a string aligned to the right for a given width. +fn align(f: &mut std::fmt::Formatter, x: &str, n: usize) -> Result<(), std::fmt::Error> { + for _ in 0..n.saturating_sub(x.len()) { + write!(f, " ")?; + } + write!(f, "{}", x) +} + +/// Prints a matrix with columns of minimal width to fit all elements. +fn write_matrix( + f: &mut std::fmt::Formatter, + mut m: Vec>, +) -> Result<(), std::fmt::Error> { + if m.is_empty() { + return Ok(()); + } + let num_cols = m.iter().map(|r| r.len()).max().unwrap(); + let mut col_len = vec![0; num_cols]; + for row in &mut m { + row.resize(num_cols, String::new()); + for col in 0..num_cols { + col_len[col] = std::cmp::max(col_len[col], row[col].len()); + } + } + for row in m { + for col in 0..num_cols { + align(f, &row[col], col_len[col])?; + } + writeln!(f)?; + } + Ok(()) +} From bbb73c77a8d7f61470e93f4092ba28fd68ea6845 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 17 Nov 2020 10:16:39 +0100 Subject: [PATCH 21/32] Use width_lim instead of bucket_lim --- .../persistent_store/fuzz/src/histogram.rs | 18 +++++------------- libraries/persistent_store/fuzz/src/stats.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/libraries/persistent_store/fuzz/src/histogram.rs b/libraries/persistent_store/fuzz/src/histogram.rs index 0d76fd7..8237055 100644 --- a/libraries/persistent_store/fuzz/src/histogram.rs +++ b/libraries/persistent_store/fuzz/src/histogram.rs @@ -33,12 +33,7 @@ impl Histogram { /// /// The bucket of `item` is the highest power of two, lower or equal to `item`. If `item` is /// zero, then its bucket is also zero. - /// - /// # Panics - /// - /// Panics if the item is too big, i.e. it uses its most significant bit. pub fn add(&mut self, item: usize) { - assert!(item <= usize::max_value() / 2); *self.buckets.entry(get_bucket(item)).or_insert(0) += 1; } @@ -49,15 +44,12 @@ impl Histogram { } } - /// Returns one past the highest non-empty bucket. + /// Returns the bit-width of one past the highest non-empty bucket. /// - /// In other words, all non-empty buckets of the histogram are smaller than the returned bucket. - pub fn bucket_lim(&self) -> usize { - match self.buckets.keys().max() { - None => 0, - Some(0) => 1, - Some(x) => 2 * x, - } + /// In other words, all non-empty buckets of the histogram have a bit-width smaller than the + /// returned width. + pub fn width_lim(&self) -> usize { + self.buckets.keys().max().map_or(0, |&x| num_bits(x) + 1) } /// Returns the count of a bucket. diff --git a/libraries/persistent_store/fuzz/src/stats.rs b/libraries/persistent_store/fuzz/src/stats.rs index fdb5983..7bef07e 100644 --- a/libraries/persistent_store/fuzz/src/stats.rs +++ b/libraries/persistent_store/fuzz/src/stats.rs @@ -17,7 +17,6 @@ //! This is not used during actual fuzzing, only when replaying a corpus to compute statistics. use crate::histogram::{bucket_from_width, Histogram}; -use crate::num_bits; use std::collections::HashMap; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; @@ -110,13 +109,14 @@ impl Stats { self.stats.get(&key).and_then(|h| h.get(bucket)) } - /// Returns one past the highest non-empty bucket. + /// Returns the bit-width of one past the highest non-empty bucket. /// - /// In other words, all non-empty buckets of the histogram are smaller than the returned bucket. - fn bucket_lim(&self) -> usize { + /// In other words, all non-empty buckets of the histogram have a bit-width smaller than the + /// returned width. + fn width_lim(&self) -> usize { self.stats .values() - .map(|h| h.bucket_lim()) + .map(|h| h.width_lim()) .max() .unwrap_or(0) } @@ -125,10 +125,10 @@ impl Stats { impl std::fmt::Display for Stats { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { let mut matrix: Vec> = Vec::new(); + let bits = self.width_lim(); let mut header = Vec::new(); header.push(String::new()); - let bits = num_bits(self.bucket_lim()); for width in 0..bits { header.push(format!(" {}", bucket_from_width(width))); } From 315016f55265621cc7448c31817b41e66b694e49 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Fri, 20 Nov 2020 02:50:47 +0100 Subject: [PATCH 22/32] unwraps credentials in the exclude list --- src/ctap/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 1a98ce5..66ef234 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -392,12 +392,16 @@ where let has_extension_output = use_hmac_extension || cred_protect_policy.is_some(); let rp_id = rp.rp_id; + let rp_id_hash = Sha256::hash(rp_id.as_bytes()); if let Some(exclude_list) = exclude_list { for cred_desc in exclude_list { if self .persistent_store .find_credential(&rp_id, &cred_desc.key_id, pin_uv_auth_param.is_none())? .is_some() + || self + .decrypt_credential_source(cred_desc.key_id, &rp_id_hash)? + .is_some() { // Perform this check, so bad actors can't brute force exclude_list // without user interaction. @@ -446,7 +450,6 @@ where let sk = crypto::ecdsa::SecKey::gensk(self.rng); let pk = sk.genpk(); - let rp_id_hash = Sha256::hash(rp_id.as_bytes()); let credential_id = if options.rk { let random_id = self.rng.gen_uniform_u8x32().to_vec(); let credential_source = PublicKeyCredentialSource { From e1b419c104903cded5310f9313e5191675aa3092 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Fri, 20 Nov 2020 05:49:11 +0100 Subject: [PATCH 23/32] changes sync response and tests it --- src/ctap/hid/mod.rs | 117 ++++++++++++++++++++++++++++------------ src/ctap/hid/receive.rs | 28 ++++++++++ 2 files changed, 112 insertions(+), 33 deletions(-) diff --git a/src/ctap/hid/mod.rs b/src/ctap/hid/mod.rs index 7489f6e..3a96699 100644 --- a/src/ctap/hid/mod.rs +++ b/src/ctap/hid/mod.rs @@ -227,44 +227,35 @@ impl CtapHid { } // CTAP specification (version 20190130) section 8.1.9.1.3 CtapHid::COMMAND_INIT => { - if cid == CtapHid::CHANNEL_BROADCAST { - if message.payload.len() != 8 { - return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); - } + if message.payload.len() != 8 { + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); + } + let new_cid = if cid == CtapHid::CHANNEL_BROADCAST { // TODO: Prevent allocating 2^32 channels. self.allocated_cids += 1; - let allocated_cid = (self.allocated_cids as u32).to_ne_bytes(); - - let mut payload = vec![0; 17]; - payload[..8].copy_from_slice(&message.payload); - payload[8..12].copy_from_slice(&allocated_cid); - payload[12] = CtapHid::PROTOCOL_VERSION; - payload[13] = CtapHid::DEVICE_VERSION_MAJOR; - payload[14] = CtapHid::DEVICE_VERSION_MINOR; - payload[15] = CtapHid::DEVICE_VERSION_BUILD; - payload[16] = CtapHid::CAPABILITIES; - - // This unwrap is safe because the payload length is 17 <= 7609 bytes. - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_INIT, - payload, - }) - .unwrap() + (self.allocated_cids as u32).to_ne_bytes() } else { // Sync the channel and discard the current transaction. - // TODO: The specification (version 20190130) wording isn't clear about - // the payload format in this case. - // - // This unwrap is safe because the payload length is 0 <= 7609 bytes. - CtapHid::split_message(Message { - cid, - cmd: CtapHid::COMMAND_INIT, - payload: vec![], - }) - .unwrap() - } + cid + }; + + let mut payload = vec![0; 17]; + payload[..8].copy_from_slice(&message.payload); + payload[8..12].copy_from_slice(&new_cid); + payload[12] = CtapHid::PROTOCOL_VERSION; + payload[13] = CtapHid::DEVICE_VERSION_MAJOR; + payload[14] = CtapHid::DEVICE_VERSION_MINOR; + payload[15] = CtapHid::DEVICE_VERSION_BUILD; + payload[16] = CtapHid::CAPABILITIES; + + // This unwrap is safe because the payload length is 17 <= 7609 bytes. + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_INIT, + payload, + }) + .unwrap() } // CTAP specification (version 20190130) section 8.1.9.1.4 CtapHid::COMMAND_PING => { @@ -568,6 +559,66 @@ mod test { ); } + #[test] + fn test_command_init_for_sync() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let mut ctap_hid = CtapHid::new(); + let cid = cid_from_init(&mut ctap_hid, &mut ctap_state); + + // Ping packet with a length longer than one packet. + let mut packet1 = [0x51; 64]; + packet1[..4].copy_from_slice(&cid); + packet1[4..7].copy_from_slice(&[0x81, 0x02, 0x00]); + // Init packet on the same channel. + let mut packet2 = [0x00; 64]; + packet2[..4].copy_from_slice(&cid); + packet2[4..15].copy_from_slice(&[ + 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, + ]); + let mut result = Vec::new(); + let mut assembler_reply = MessageAssembler::new(); + for pkt_request in &[packet1, packet2] { + for pkt_reply in + ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, &mut ctap_state) + { + if let Some(message) = assembler_reply + .parse_packet(&pkt_reply, DUMMY_TIMESTAMP) + .unwrap() + { + result.push(message); + } + } + } + assert_eq!( + result, + vec![Message { + cid, + cmd: CtapHid::COMMAND_INIT, + payload: vec![ + 0x12, // Nonce + 0x34, + 0x56, + 0x78, + 0x9A, + 0xBC, + 0xDE, + 0xF0, + cid[0], // Allocated CID + cid[1], + cid[2], + cid[3], + 0x02, // Protocol version + 0x00, // Device version + 0x00, + 0x00, + CtapHid::CAPABILITIES + ] + }] + ); + } + #[test] fn test_command_ping() { let mut rng = ThreadRng256 {}; diff --git a/src/ctap/hid/receive.rs b/src/ctap/hid/receive.rs index fef51a4..b522837 100644 --- a/src/ctap/hid/receive.rs +++ b/src/ctap/hid/receive.rs @@ -586,5 +586,33 @@ mod test { ); } + #[test] + fn test_init_sync() { + let mut assembler = MessageAssembler::new(); + // Ping packet with a length longer than one packet. + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x02, 0x00], 0x51), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + // Init packet on the same channel. + assert_eq!( + assembler.parse_packet( + &zero_extend(&[ + 0x12, 0x34, 0x56, 0x78, 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, + 0xDE, 0xF0 + ]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x06, + payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + })) + ); + } + // TODO: more tests } From 9a29795ca65870508267b45d5744878357a97c62 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Fri, 20 Nov 2020 06:03:52 +0100 Subject: [PATCH 24/32] changes priority of error codes --- src/ctap/hid/mod.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ctap/hid/mod.rs b/src/ctap/hid/mod.rs index 7489f6e..e975f33 100644 --- a/src/ctap/hid/mod.rs +++ b/src/ctap/hid/mod.rs @@ -307,7 +307,9 @@ impl CtapHid { HidPacketIterator::none() } Err((cid, error)) => { - if !self.is_allocated_channel(cid) { + if !self.is_allocated_channel(cid) + && error != receive::Error::UnexpectedContinuation + { CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL) } else { match error { @@ -523,6 +525,27 @@ mod test { } } + #[test] + fn test_spurious_continuation_packet() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let mut ctap_hid = CtapHid::new(); + + let mut packet = [0x00; 64]; + packet[0..7].copy_from_slice(&[0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x51, 0x51]); + let mut assembler_reply = MessageAssembler::new(); + for pkt_reply in ctap_hid.process_hid_packet(&packet, DUMMY_CLOCK_VALUE, &mut ctap_state) { + // Continuation packets are silently ignored. + assert_eq!( + assembler_reply + .parse_packet(&pkt_reply, DUMMY_TIMESTAMP) + .unwrap(), + None + ); + } + } + #[test] fn test_command_init() { let mut rng = ThreadRng256 {}; From dab0077b87451fa448f72f1dadd1d2c42c142ca6 Mon Sep 17 00:00:00 2001 From: Jean-Michel Picod Date: Fri, 20 Nov 2020 11:58:39 +0100 Subject: [PATCH 25/32] Fix broken crypto_test workflow. The use of `::set-env` command in workflows is not being depreacted. Moving to the new way of setting environment variables. --- .github/workflows/crypto_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/crypto_test.yml b/.github/workflows/crypto_test.yml index 1740280..50fdf88 100644 --- a/.github/workflows/crypto_test.yml +++ b/.github/workflows/crypto_test.yml @@ -27,7 +27,7 @@ jobs: - name: Set up OpenSK run: ./setup.sh - - run: echo "::set-env name=RUSTFLAGS::-C target-feature=+aes" + - run: echo "RUSTFLAGS=-C target-feature=+aes" >> $GITHUB_ENV - name: Unit testing of crypto library (release mode) uses: actions-rs/cargo@v1 From 5bf73cb8fdcd4bcb33381992c8608fd77df7dce6 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Fri, 20 Nov 2020 03:26:26 +0100 Subject: [PATCH 26/32] fail on UP=true in make --- src/ctap/data_formats.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index dacafe5..45ebf9f 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -361,10 +361,8 @@ impl TryFrom for MakeCredentialOptions { Some(options_entry) => extract_bool(options_entry)?, None => false, }; - if let Some(options_entry) = up { - if !extract_bool(options_entry)? { - return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); - } + if up.is_some() { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); } let uv = match uv { Some(options_entry) => extract_bool(options_entry)?, From 9bb1aad45d526b29bb5f6477c56b5136a880cff2 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Mon, 23 Nov 2020 12:02:51 +0100 Subject: [PATCH 27/32] wraps HMAC secret into credentials --- src/ctap/ctap1.rs | 50 ++++++---- src/ctap/mod.rs | 229 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 236 insertions(+), 43 deletions(-) diff --git a/src/ctap/ctap1.rs b/src/ctap/ctap1.rs index 84c6fb0..a5a7921 100644 --- a/src/ctap/ctap1.rs +++ b/src/ctap/ctap1.rs @@ -288,7 +288,7 @@ impl Ctap1Command { let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng); let pk = sk.genpk(); let key_handle = ctap_state - .encrypt_key_handle(sk, &application) + .encrypt_key_handle(sk, &application, None) .map_err(|_| Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG)?; if key_handle.len() > 0xFF { // This is just being defensive with unreachable code. @@ -373,7 +373,7 @@ impl Ctap1Command { #[cfg(test)] mod test { - use super::super::{ENCRYPTED_CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER}; + use super::super::{CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER}; use super::*; use crypto::rng256::ThreadRng256; use crypto::Hash256; @@ -413,12 +413,12 @@ mod test { 0x00, 0x00, 0x00, - 65 + ENCRYPTED_CREDENTIAL_ID_SIZE as u8, + 65 + CREDENTIAL_ID_BASE_SIZE as u8, ]; let challenge = [0x0C; 32]; message.extend(&challenge); message.extend(application); - message.push(ENCRYPTED_CREDENTIAL_ID_SIZE as u8); + message.push(CREDENTIAL_ID_BASE_SIZE as u8); message.extend(key_handle); message } @@ -437,15 +437,15 @@ mod test { Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap(); assert_eq!(response[0], Ctap1Command::LEGACY_BYTE); - assert_eq!(response[66], ENCRYPTED_CREDENTIAL_ID_SIZE as u8); + assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8); assert!(ctap_state .decrypt_credential_source( - response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(), + response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(), &application ) .unwrap() .is_some()); - const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE; + const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE; assert_eq!( &response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()], &ATTESTATION_CERTIFICATE[..] @@ -494,7 +494,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); @@ -510,7 +512,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let application = [0x55; 32]; let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -527,7 +531,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -551,7 +557,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); message[0] = 0xEE; @@ -569,7 +577,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); message[1] = 0xEE; @@ -587,7 +597,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); message[2] = 0xEE; @@ -605,7 +617,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let message = create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); @@ -630,7 +644,9 @@ 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 key_handle = ctap_state + .encrypt_key_handle(sk, &application, None) + .unwrap(); let message = create_authenticate_message( &application, Ctap1Flags::DontEnforceUpAndSign, @@ -650,7 +666,7 @@ mod test { #[test] fn test_process_authenticate_bad_key_handle() { let application = [0x0A; 32]; - let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE]; + let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE]; let message = create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); @@ -667,7 +683,7 @@ mod test { #[test] fn test_process_authenticate_without_up() { let application = [0x0A; 32]; - let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE]; + let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE]; let message = create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 66ef234..2e095ed 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -83,8 +83,9 @@ const USE_SIGNATURE_COUNTER: bool = true; // - 16 byte initialization vector for AES-256, // - 32 byte ECDSA private key for the credential, // - 32 byte relying party ID hashed with SHA256, +// - (optional) 32 byte for HMAC-secret, // - 32 byte HMAC-SHA256 over everything else. -pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112; +pub const CREDENTIAL_ID_BASE_SIZE: usize = 112; // Set this bit when checking user presence. const UP_FLAG: u8 = 0x01; // Set this bit when checking user verification. @@ -195,6 +196,7 @@ where &mut self, private_key: crypto::ecdsa::SecKey, application: &[u8; 32], + cred_random: Option<&[u8; 32]>, ) -> Result, Ctap2StatusCode> { let master_keys = self.persistent_store.master_keys()?; let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); @@ -203,14 +205,19 @@ where let mut iv = [0; 16]; iv.copy_from_slice(&self.rng.gen_uniform_u8x32()[..16]); - let mut blocks = [[0u8; 16]; 4]; + let block_len = if cred_random.is_some() { 6 } else { 4 }; + let mut blocks = vec![[0u8; 16]; block_len]; blocks[0].copy_from_slice(&sk_bytes[..16]); blocks[1].copy_from_slice(&sk_bytes[16..]); blocks[2].copy_from_slice(&application[..16]); blocks[3].copy_from_slice(&application[16..]); + if let Some(cred_random) = cred_random { + blocks[4].copy_from_slice(&cred_random[..16]); + blocks[5].copy_from_slice(&cred_random[16..]); + } cbc_encrypt(&aes_enc_key, iv, &mut blocks); - let mut encrypted_id = Vec::with_capacity(ENCRYPTED_CREDENTIAL_ID_SIZE); + let mut encrypted_id = Vec::with_capacity(16 * (block_len + 3)); encrypted_id.extend(&iv); for b in &blocks { encrypted_id.extend(b); @@ -228,11 +235,15 @@ where credential_id: Vec, rp_id_hash: &[u8], ) -> Result, Ctap2StatusCode> { - if credential_id.len() != ENCRYPTED_CREDENTIAL_ID_SIZE { + let has_cred_random = if credential_id.len() == CREDENTIAL_ID_BASE_SIZE { + false + } else if credential_id.len() == CREDENTIAL_ID_BASE_SIZE + 32 { + true + } else { return Ok(None); - } + }; let master_keys = self.persistent_store.master_keys()?; - let payload_size = ENCRYPTED_CREDENTIAL_ID_SIZE - 32; + let payload_size = credential_id.len() - 32; if !verify_hmac_256::( &master_keys.hmac, &credential_id[..payload_size], @@ -244,8 +255,9 @@ where let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); let mut iv = [0; 16]; iv.copy_from_slice(&credential_id[..16]); - let mut blocks = [[0u8; 16]; 4]; - for i in 0..4 { + let block_len = if has_cred_random { 6 } else { 4 }; + let mut blocks = vec![[0u8; 16]; block_len]; + for i in 0..block_len { blocks[i].copy_from_slice(&credential_id[16 * (i + 1)..16 * (i + 2)]); } @@ -256,6 +268,14 @@ where decrypted_sk[16..].clone_from_slice(&blocks[1]); decrypted_rp_id_hash[..16].clone_from_slice(&blocks[2]); decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]); + let cred_random = if has_cred_random { + let mut decrypted_cred_random = [0; 32]; + decrypted_cred_random[..16].clone_from_slice(&blocks[4]); + decrypted_cred_random[16..].clone_from_slice(&blocks[5]); + Some(decrypted_cred_random.to_vec()) + } else { + None + }; if rp_id_hash != decrypted_rp_id_hash { return Ok(None); @@ -269,7 +289,7 @@ where rp_id: String::from(""), user_handle: vec![], other_ui: None, - cred_random: None, + cred_random, cred_protect_policy: None, })) } @@ -380,11 +400,7 @@ where }; let cred_random = if use_hmac_extension { - if !options.rk { - // The extension is actually supported, but we need resident keys. - return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION); - } - Some(self.rng.gen_uniform_u8x32().to_vec()) + Some(self.rng.gen_uniform_u8x32()) } else { None }; @@ -463,13 +479,13 @@ where other_ui: user .user_display_name .map(|s| truncate_to_char_boundary(&s, 64).to_string()), - cred_random, + cred_random: cred_random.map(|c| c.to_vec()), cred_protect_policy, }; self.persistent_store.store_credential(credential_source)?; random_id } else { - self.encrypt_key_handle(sk.clone(), &rp_id_hash)? + self.encrypt_key_handle(sk.clone(), &rp_id_hash, cred_random.as_ref())? }; let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?; @@ -728,10 +744,9 @@ where ]), #[cfg(feature = "with_ctap2_1")] max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64), - // You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your - // browser passes that value, it might be used to fingerprint. + // #TODO(106) update with version 2.1 of HMAC-secret #[cfg(feature = "with_ctap2_1")] - max_credential_id_length: None, + max_credential_id_length: Some(CREDENTIAL_ID_BASE_SIZE as u64 + 32), #[cfg(feature = "with_ctap2_1")] transports: Some(vec![AuthenticatorTransport::Usb]), #[cfg(feature = "with_ctap2_1")] @@ -829,7 +844,7 @@ mod test { let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID); #[cfg(feature = "with_ctap2_1")] - let mut expected_response = vec![0x00, 0xA9, 0x01]; + let mut expected_response = vec![0x00, 0xAA, 0x01]; #[cfg(not(feature = "with_ctap2_1"))] let mut expected_response = vec![0x00, 0xA6, 0x01]; // The difference here is a longer array of supported versions. @@ -864,9 +879,9 @@ mod test { #[cfg(feature = "with_ctap2_1")] expected_response.extend( [ - 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C, 0x67, 0x26, - 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, - 0x65, 0x79, 0x0D, 0x04, + 0x08, 0x18, 0x90, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, + 0x6C, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04, ] .iter(), ); @@ -993,7 +1008,7 @@ mod test { 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00, ]; expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap()); - expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]); + expected_auth_data.extend(&[0x00, CREDENTIAL_ID_BASE_SIZE as u8]); assert_eq!( auth_data[0..expected_auth_data.len()], expected_auth_data[..] @@ -1114,6 +1129,57 @@ mod test { let user_immediately_present = |_| Ok(()); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let extensions = Some(MakeCredentialExtensions { + hmac_secret: true, + cred_protect: None, + }); + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.options.rk = false; + make_credential_params.extensions = extensions; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + match make_credential_response.unwrap() { + ResponseData::AuthenticatorMakeCredential(make_credential_response) => { + let AuthenticatorMakeCredentialResponse { + fmt, + auth_data, + att_stmt, + } = make_credential_response; + // The expected response is split to only assert the non-random parts. + assert_eq!(fmt, "packed"); + let mut expected_auth_data = vec![ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, + 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, + 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00, + ]; + expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap()); + let credential_size = CREDENTIAL_ID_BASE_SIZE + 32; + expected_auth_data.extend(&[0x00, credential_size as u8]); + assert_eq!( + auth_data[0..expected_auth_data.len()], + expected_auth_data[..] + ); + let expected_extension_cbor = vec![ + 0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0xF5, + ]; + assert_eq!( + auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()], + expected_extension_cbor[..] + ); + assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64); + } + _ => panic!("Invalid response type"), + } + } + + #[test] + fn test_process_make_credential_hmac_secret_resident_key() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let extensions = Some(MakeCredentialExtensions { hmac_secret: true, cred_protect: None, @@ -1220,6 +1286,71 @@ mod test { } } + #[test] + fn test_process_get_assertion_hmac_secret() { + let mut rng = ThreadRng256 {}; + let sk = crypto::ecdh::SecKey::gensk(&mut rng); + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let make_extensions = Some(MakeCredentialExtensions { + hmac_secret: true, + cred_protect: None, + }); + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.options.rk = false; + make_credential_params.extensions = make_extensions; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + assert!(make_credential_response.is_ok()); + let credential_id = match make_credential_response.unwrap() { + ResponseData::AuthenticatorMakeCredential(make_credential_response) => { + let auth_data = make_credential_response.auth_data; + let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len(); + let credential_size = CREDENTIAL_ID_BASE_SIZE + 32; + assert_eq!(auth_data[offset], 0x00); + assert_eq!(auth_data[offset + 1] as usize, credential_size); + auth_data[offset + 2..offset + 2 + credential_size].to_vec() + } + _ => panic!("Invalid response type"), + }; + + let pk = sk.genpk(); + let hmac_secret_input = GetAssertionHmacSecretInput { + key_agreement: CoseKey::from(pk), + salt_enc: vec![0x02; 32], + salt_auth: vec![0x03; 16], + }; + let get_extensions = Some(GetAssertionExtensions { + hmac_secret: Some(hmac_secret_input), + }); + + let cred_desc = PublicKeyCredentialDescriptor { + key_type: PublicKeyCredentialType::PublicKey, + key_id: credential_id, + transports: None, + }; + let get_assertion_params = AuthenticatorGetAssertionParameters { + rp_id: String::from("example.com"), + client_data_hash: vec![0xCD], + allow_list: Some(vec![cred_desc]), + extensions: get_extensions, + options: GetAssertionOptions { + up: false, + uv: false, + }, + pin_uv_auth_param: None, + pin_uv_auth_protocol: None, + }; + let get_assertion_response = + ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID); + + assert_eq!( + get_assertion_response, + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) + ); + } + #[test] fn test_residential_process_get_assertion_hmac_secret() { let mut rng = ThreadRng256 {}; @@ -1435,7 +1566,7 @@ mod test { // We are not testing the correctness of our SHA256 here, only if it is checked. let rp_id_hash = [0x55; 32]; let encrypted_id = ctap_state - .encrypt_key_handle(private_key.clone(), &rp_id_hash) + .encrypt_key_handle(private_key.clone(), &rp_id_hash, None) .unwrap(); let decrypted_source = ctap_state .decrypt_credential_source(encrypted_id, &rp_id_hash) @@ -1445,6 +1576,29 @@ mod test { assert_eq!(private_key, decrypted_source.private_key); } + #[test] + fn test_encrypt_decrypt_credential_with_cred_random() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + // Usually, the relying party ID or its hash is provided by the client. + // We are not testing the correctness of our SHA256 here, only if it is checked. + let rp_id_hash = [0x55; 32]; + let cred_random = [0xC9; 32]; + let encrypted_id = ctap_state + .encrypt_key_handle(private_key.clone(), &rp_id_hash, Some(&cred_random)) + .unwrap(); + let decrypted_source = ctap_state + .decrypt_credential_source(encrypted_id, &rp_id_hash) + .unwrap() + .unwrap(); + + assert_eq!(private_key, decrypted_source.private_key); + assert_eq!(Some(cred_random.to_vec()), decrypted_source.cred_random); + } + #[test] fn test_encrypt_decrypt_bad_hmac() { let mut rng = ThreadRng256 {}; @@ -1455,7 +1609,30 @@ mod test { // Same as above. let rp_id_hash = [0x55; 32]; let encrypted_id = ctap_state - .encrypt_key_handle(private_key, &rp_id_hash) + .encrypt_key_handle(private_key, &rp_id_hash, None) + .unwrap(); + for i in 0..encrypted_id.len() { + let mut modified_id = encrypted_id.clone(); + modified_id[i] ^= 0x01; + assert!(ctap_state + .decrypt_credential_source(modified_id, &rp_id_hash) + .unwrap() + .is_none()); + } + } + + #[test] + fn test_encrypt_decrypt_bad_hmac_with_cred_random() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + // Same as above. + let rp_id_hash = [0x55; 32]; + let cred_random = [0xC9; 32]; + let encrypted_id = ctap_state + .encrypt_key_handle(private_key, &rp_id_hash, Some(&cred_random)) .unwrap(); for i in 0..encrypted_id.len() { let mut modified_id = encrypted_id.clone(); From a099ddbabde0c208b821c5417d5ed16e86560bd9 Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Mon, 23 Nov 2020 14:34:38 +0100 Subject: [PATCH 28/32] introduce max credential size for readability --- src/ctap/mod.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 2e095ed..dd9cabe 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -86,6 +86,7 @@ const USE_SIGNATURE_COUNTER: bool = true; // - (optional) 32 byte for HMAC-secret, // - 32 byte HMAC-SHA256 over everything else. pub const CREDENTIAL_ID_BASE_SIZE: usize = 112; +pub const CREDENTIAL_ID_MAX_SIZE: usize = CREDENTIAL_ID_BASE_SIZE + 32; // Set this bit when checking user presence. const UP_FLAG: u8 = 0x01; // Set this bit when checking user verification. @@ -235,12 +236,10 @@ where credential_id: Vec, rp_id_hash: &[u8], ) -> Result, Ctap2StatusCode> { - let has_cred_random = if credential_id.len() == CREDENTIAL_ID_BASE_SIZE { - false - } else if credential_id.len() == CREDENTIAL_ID_BASE_SIZE + 32 { - true - } else { - return Ok(None); + let has_cred_random = match credential_id.len() { + CREDENTIAL_ID_BASE_SIZE => false, + CREDENTIAL_ID_MAX_SIZE => true, + _ => return Ok(None), }; let master_keys = self.persistent_store.master_keys()?; let payload_size = credential_id.len() - 32; @@ -1154,8 +1153,7 @@ mod test { 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00, ]; expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap()); - let credential_size = CREDENTIAL_ID_BASE_SIZE + 32; - expected_auth_data.extend(&[0x00, credential_size as u8]); + expected_auth_data.extend(&[0x00, CREDENTIAL_ID_MAX_SIZE as u8]); assert_eq!( auth_data[0..expected_auth_data.len()], expected_auth_data[..] @@ -1307,10 +1305,9 @@ mod test { ResponseData::AuthenticatorMakeCredential(make_credential_response) => { let auth_data = make_credential_response.auth_data; let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len(); - let credential_size = CREDENTIAL_ID_BASE_SIZE + 32; assert_eq!(auth_data[offset], 0x00); - assert_eq!(auth_data[offset + 1] as usize, credential_size); - auth_data[offset + 2..offset + 2 + credential_size].to_vec() + assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_MAX_SIZE); + auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_MAX_SIZE].to_vec() } _ => panic!("Invalid response type"), }; From 174c292f2f700cbfc85dec8da6c0412180e50fff Mon Sep 17 00:00:00 2001 From: Jean-Michel Picod Date: Mon, 23 Nov 2020 19:16:48 +0100 Subject: [PATCH 29/32] Adding metadata file used for certification. --- metadata/metadata.json | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 metadata/metadata.json diff --git a/metadata/metadata.json b/metadata/metadata.json new file mode 100644 index 0000000..bb81d0c --- /dev/null +++ b/metadata/metadata.json @@ -0,0 +1,47 @@ +{ + "assertionScheme" : "FIDOV2", + "keyProtection" : 1, + "attestationRootCertificates" : [ + ], + "aaguid" : "664d9f67-84a2-412a-9ff7-b4f7d8ee6d05", + "publicKeyAlgAndEncoding" : 260, + "protocolFamily" : "fido2", + "upv" : [ + { + "major" : 1, + "minor" : 0 + } + ], + "icon" : "data:image/png;base64,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", + "matcherProtection" : 1, + "supportedExtensions" : [ + { + "id" : "hmac-secret", + "fail_if_unknown" : false + }, + { + "id" : "credProtect", + "fail_if_unknown" : false + } + ], + "cryptoStrength" : 128, + "description" : "OpenSK authenticator", + "authenticatorVersion" : 1, + "isSecondFactorOnly" : false, + "userVerificationDetails" : [ + [ + { + "userVerification" : 1 + }, + { + "userVerification" : 4 + } + ] + ], + "attachmentHint" : 6, + "attestationTypes" : [ + 15880 + ], + "authenticationAlgorithm" : 1, + "tcDisplay" : 0 +} From 90f2d4a24955544fe426eb79a201f18b20253aa3 Mon Sep 17 00:00:00 2001 From: Jean-Michel Picod Date: Mon, 23 Nov 2020 20:33:01 +0100 Subject: [PATCH 30/32] Fix indentation --- metadata/metadata.json | 55 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/metadata/metadata.json b/metadata/metadata.json index bb81d0c..eedeed9 100644 --- a/metadata/metadata.json +++ b/metadata/metadata.json @@ -1,47 +1,46 @@ { - "assertionScheme" : "FIDOV2", - "keyProtection" : 1, - "attestationRootCertificates" : [ - ], - "aaguid" : "664d9f67-84a2-412a-9ff7-b4f7d8ee6d05", - "publicKeyAlgAndEncoding" : 260, - "protocolFamily" : "fido2", - "upv" : [ + "assertionScheme": "FIDOV2", + "keyProtection": 1, + "attestationRootCertificates": [], + "aaguid": "664d9f67-84a2-412a-9ff7-b4f7d8ee6d05", + "publicKeyAlgAndEncoding": 260, + "protocolFamily": "fido2", + "upv": [ { - "major" : 1, - "minor" : 0 + "major": 1, + "minor": 0 } ], - "icon" : "data:image/png;base64,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", - "matcherProtection" : 1, - "supportedExtensions" : [ + "icon": "data:image/png;base64,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", + "matcherProtection": 1, + "supportedExtensions": [ { - "id" : "hmac-secret", - "fail_if_unknown" : false + "id": "hmac-secret", + "fail_if_unknown": false }, { - "id" : "credProtect", - "fail_if_unknown" : false + "id": "credProtect", + "fail_if_unknown": false } ], - "cryptoStrength" : 128, - "description" : "OpenSK authenticator", - "authenticatorVersion" : 1, - "isSecondFactorOnly" : false, - "userVerificationDetails" : [ + "cryptoStrength": 128, + "description": "OpenSK authenticator", + "authenticatorVersion": 1, + "isSecondFactorOnly": false, + "userVerificationDetails": [ [ { - "userVerification" : 1 + "userVerification": 1 }, { - "userVerification" : 4 + "userVerification": 4 } ] ], - "attachmentHint" : 6, - "attestationTypes" : [ + "attachmentHint": 6, + "attestationTypes": [ 15880 ], - "authenticationAlgorithm" : 1, - "tcDisplay" : 0 + "authenticationAlgorithm": 1, + "tcDisplay": 0 } From 29ee45de6cf809e1a78250476d914a5e34308163 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 24 Nov 2020 11:29:14 +0100 Subject: [PATCH 31/32] Do not crash in the driver for store errors We prefer to return those errors to the fuzzer which can then decide whether they are expected or not (e.g. when starting from a dirty storage, the store is expected to have errors). --- libraries/persistent_store/src/driver.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/libraries/persistent_store/src/driver.rs b/libraries/persistent_store/src/driver.rs index 15001cb..e529274 100644 --- a/libraries/persistent_store/src/driver.rs +++ b/libraries/persistent_store/src/driver.rs @@ -181,6 +181,12 @@ pub enum StoreInvariant { }, } +impl From for StoreInvariant { + fn from(error: StoreError) -> StoreInvariant { + StoreInvariant::StoreError(error) + } +} + impl StoreDriver { /// Provides read-only access to the storage. pub fn storage(&self) -> &BufferStorage { @@ -249,6 +255,10 @@ impl StoreDriverOff { } /// Powers on the store without interruption. + /// + /// # Panics + /// + /// Panics if the store cannot be powered on. pub fn power_on(self) -> Result { Ok(self .partial_power_on(StoreInterruption::none()) @@ -506,8 +516,8 @@ impl StoreDriverOn { /// Checks that the store and model are in sync. fn check_model(&self) -> Result<(), StoreInvariant> { let mut model_content = self.model.content().clone(); - for handle in self.store.iter().unwrap() { - let handle = handle.unwrap(); + for handle in self.store.iter()? { + let handle = handle?; let model_value = match model_content.remove(&handle.get_key()) { None => { return Err(StoreInvariant::OnlyInStore { @@ -516,7 +526,7 @@ impl StoreDriverOn { } Some(x) => x, }; - let store_value = handle.get_value(&self.store).unwrap().into_boxed_slice(); + let store_value = handle.get_value(&self.store)?.into_boxed_slice(); if store_value != model_value { return Err(StoreInvariant::DifferentValue { key: handle.get_key(), @@ -528,7 +538,7 @@ impl StoreDriverOn { if let Some(&key) = model_content.keys().next() { return Err(StoreInvariant::OnlyInModel { key }); } - let store_capacity = self.store.capacity().unwrap().remaining(); + let store_capacity = self.store.capacity()?.remaining(); let model_capacity = self.model.capacity().remaining(); if store_capacity != model_capacity { return Err(StoreInvariant::DifferentCapacity { @@ -544,8 +554,8 @@ impl StoreDriverOn { let format = self.model.format(); let storage = self.store.storage(); let num_words = format.page_size() / format.word_size(); - let head = self.store.head().unwrap(); - let tail = self.store.tail().unwrap(); + let head = self.store.head()?; + let tail = self.store.tail()?; for page in 0..format.num_pages() { // Check the erase cycle of the page. let store_erase = head.cycle(format) + (page < head.page(format)) as Nat; From 0b2ea7d98be2dabe7925ed8c98504631d261b09e Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Tue, 24 Nov 2020 15:14:03 +0100 Subject: [PATCH 32/32] makes HMAC secret output reproducible --- src/ctap/pin_protocol_v1.rs | 118 ++++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 33 deletions(-) diff --git a/src/ctap/pin_protocol_v1.rs b/src/ctap/pin_protocol_v1.rs index c03b81b..564ca6d 100644 --- a/src/ctap/pin_protocol_v1.rs +++ b/src/ctap/pin_protocol_v1.rs @@ -19,7 +19,6 @@ use super::status_code::Ctap2StatusCode; use super::storage::PersistentStore; #[cfg(feature = "with_ctap2_1")] use alloc::string::String; -#[cfg(feature = "with_ctap2_1")] use alloc::vec; use alloc::vec::Vec; use arrayref::array_ref; @@ -74,10 +73,9 @@ fn encrypt_hmac_secret_output( let mut cred_random_secret = [0u8; 32]; cred_random_secret.copy_from_slice(cred_random); - // Initialization of 4 blocks in any case makes this function more readable. - let mut blocks = [[0u8; 16]; 4]; // With the if clause restriction above, block_len can only be 2 or 4. let block_len = salt_enc.len() / 16; + let mut blocks = vec![[0u8; 16]; block_len]; for i in 0..block_len { blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]); } @@ -85,8 +83,8 @@ fn encrypt_hmac_secret_output( let mut decrypted_salt1 = [0u8; 32]; decrypted_salt1[..16].copy_from_slice(&blocks[0]); - let output1 = hmac_256::(&cred_random_secret, &decrypted_salt1[..]); decrypted_salt1[16..].copy_from_slice(&blocks[1]); + let output1 = hmac_256::(&cred_random_secret, &decrypted_salt1[..]); for i in 0..2 { blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]); } @@ -638,36 +636,52 @@ impl PinProtocolV1 { #[cfg(test)] mod test { use super::*; - use arrayref::array_refs; use crypto::rng256::ThreadRng256; // Stores a PIN hash corresponding to the dummy PIN "1234". fn set_standard_pin(persistent_store: &mut PersistentStore) { let mut pin = [0u8; 64]; - pin[0] = 0x31; - pin[1] = 0x32; - pin[2] = 0x33; - pin[3] = 0x34; + pin[..4].copy_from_slice(b"1234"); let mut pin_hash = [0u8; 16]; pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]); persistent_store.set_pin_hash(&pin_hash).unwrap(); } + // Encrypts the message with a zero IV and key derived from shared_secret. + fn encrypt_message(shared_secret: &[u8; 32], message: &[u8]) -> Vec { + assert!(message.len() % 16 == 0); + let block_len = message.len() / 16; + let mut blocks = vec![[0u8; 16]; block_len]; + for i in 0..block_len { + blocks[i][..].copy_from_slice(&message[i * 16..(i + 1) * 16]); + } + let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret); + let iv = [0u8; 16]; + cbc_encrypt(&aes_enc_key, iv, &mut blocks); + blocks.iter().flatten().cloned().collect::>() + } + + // Decrypts the message with a zero IV and key derived from shared_secret. + fn decrypt_message(shared_secret: &[u8; 32], message: &[u8]) -> Vec { + assert!(message.len() % 16 == 0); + let block_len = message.len() / 16; + let mut blocks = vec![[0u8; 16]; block_len]; + for i in 0..block_len { + blocks[i][..].copy_from_slice(&message[i * 16..(i + 1) * 16]); + } + let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret); + let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); + let iv = [0u8; 16]; + cbc_decrypt(&aes_dec_key, iv, &mut blocks); + blocks.iter().flatten().cloned().collect::>() + } + // Fails on PINs bigger than 64 bytes. fn encrypt_pin(shared_secret: &[u8; 32], pin: Vec) -> Vec { assert!(pin.len() <= 64); let mut padded_pin = [0u8; 64]; padded_pin[..pin.len()].copy_from_slice(&pin[..]); - let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret); - let mut blocks = [[0u8; 16]; 4]; - let (b0, b1, b2, b3) = array_refs!(&padded_pin, 16, 16, 16, 16); - blocks[0][..].copy_from_slice(b0); - blocks[1][..].copy_from_slice(b1); - blocks[2][..].copy_from_slice(b2); - blocks[3][..].copy_from_slice(b3); - let iv = [0u8; 16]; - cbc_encrypt(&aes_enc_key, iv, &mut blocks); - blocks.iter().flatten().cloned().collect::>() + encrypt_message(shared_secret, &padded_pin) } // Encrypts the dummy PIN "1234". @@ -677,22 +691,10 @@ mod test { // Encrypts the PIN hash corresponding to the dummy PIN "1234". fn encrypt_standard_pin_hash(shared_secret: &[u8; 32]) -> Vec { - let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret); let mut pin = [0u8; 64]; - pin[0] = 0x31; - pin[1] = 0x32; - pin[2] = 0x33; - pin[3] = 0x34; + pin[..4].copy_from_slice(b"1234"); let pin_hash = Sha256::hash(&pin); - - let mut blocks = [[0u8; 16]; 1]; - blocks[0].copy_from_slice(&pin_hash[..16]); - let iv = [0u8; 16]; - cbc_encrypt(&aes_enc_key, iv, &mut blocks); - - let mut encrypted_pin_hash = Vec::with_capacity(16); - encrypted_pin_hash.extend(&blocks[0]); - encrypted_pin_hash + encrypt_message(shared_secret, &pin_hash[..16]) } #[test] @@ -1184,6 +1186,56 @@ mod test { output, Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) ); + + let mut salt_enc = [0x00; 32]; + let cred_random = [0xC9; 32]; + + // Test values to check for reproducibility. + let salt1 = [0x01; 32]; + let salt2 = [0x02; 32]; + let expected_output1 = hmac_256::(&cred_random, &salt1); + let expected_output2 = hmac_256::(&cred_random, &salt2); + + let salt_enc1 = encrypt_message(&shared_secret, &salt1); + salt_enc.copy_from_slice(salt_enc1.as_slice()); + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap(); + let output_dec = decrypt_message(&shared_secret, &output); + assert_eq!(&output_dec, &expected_output1); + + let salt_enc2 = &encrypt_message(&shared_secret, &salt2); + salt_enc.copy_from_slice(salt_enc2.as_slice()); + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap(); + let output_dec = decrypt_message(&shared_secret, &output); + assert_eq!(&output_dec, &expected_output2); + + let mut salt_enc = [0x00; 64]; + let mut salt12 = [0x00; 64]; + salt12[..32].copy_from_slice(&salt1); + salt12[32..].copy_from_slice(&salt2); + let salt_enc12 = encrypt_message(&shared_secret, &salt12); + salt_enc.copy_from_slice(salt_enc12.as_slice()); + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap(); + let output_dec = decrypt_message(&shared_secret, &output); + assert_eq!(&output_dec[..32], &expected_output1); + assert_eq!(&output_dec[32..], &expected_output2); + + let mut salt_enc = [0x00; 64]; + let mut salt02 = [0x00; 64]; + salt02[32..].copy_from_slice(&salt2); + let salt_enc02 = encrypt_message(&shared_secret, &salt02); + salt_enc.copy_from_slice(salt_enc02.as_slice()); + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap(); + let output_dec = decrypt_message(&shared_secret, &output); + assert_eq!(&output_dec[32..], &expected_output2); + + let mut salt_enc = [0x00; 64]; + let mut salt10 = [0x00; 64]; + salt10[..32].copy_from_slice(&salt1); + let salt_enc10 = encrypt_message(&shared_secret, &salt10); + salt_enc.copy_from_slice(salt_enc10.as_slice()); + let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap(); + let output_dec = decrypt_message(&shared_secret, &output); + assert_eq!(&output_dec[..32], &expected_output1); } #[cfg(feature = "with_ctap2_1")]