Skip to content

Commit

Permalink
Allows initialization without Reset permission
Browse files Browse the repository at this point in the history
This PR is useful for all implementations that can trigger a reboot
without user intervention. In these cases, we don't want to allow the
Reset command. It should only be allowed after a user initiated power
cycle.

Adds tests to the new functionality and a few other coverage holes.
  • Loading branch information
kaczmarczyck committed Jan 9, 2024
1 parent 0185d1e commit 1e9579d
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 14 deletions.
4 changes: 2 additions & 2 deletions libraries/opensk/fuzz/fuzz_helper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ pub fn process_ctap_any_type(data: &[u8]) -> arbitrary::Result<()> {

let data = unstructured.take_rest();
// Initialize ctap state and hid and get the allocated cid.
let mut ctap = Ctap::new(env);
let mut ctap = Ctap::new(env, false);
let cid = initialize(&mut ctap);
// Wrap input as message with the allocated cid.
let mut command = cid.to_vec();
Expand Down Expand Up @@ -191,7 +191,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) -> arbitra
return Ok(());
}
// Initialize ctap state and hid and get the allocated cid.
let mut ctap = Ctap::new(env);
let mut ctap = Ctap::new(env, false);
let cid = initialize(&mut ctap);
// Wrap input as message with allocated cid and command type.
let mut command = cid.to_vec();
Expand Down
11 changes: 11 additions & 0 deletions libraries/opensk/src/api/attestation_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,14 @@ impl From<StoreError> for Error {
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_store_error() {
assert_eq!(Error::from(StoreError::StorageError), Error::Storage);
assert_eq!(Error::from(StoreError::InvalidStorage), Error::Internal);
}
}
16 changes: 15 additions & 1 deletion libraries/opensk/src/api/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

use core::convert::TryFrom;

#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UsbEndpoint {
MainHid = 1,
#[cfg(feature = "vendor_hid")]
Expand All @@ -40,10 +40,24 @@ pub enum SendOrRecvStatus {
Received(UsbEndpoint),
}

#[derive(Debug, PartialEq, Eq)]
pub struct SendOrRecvError;

pub type SendOrRecvResult = Result<SendOrRecvStatus, SendOrRecvError>;

pub trait HidConnection {
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult;
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_endpoint_num() {
assert_eq!(UsbEndpoint::try_from(1), Ok(UsbEndpoint::MainHid));
#[cfg(feature = "vendor_hid")]
assert_eq!(UsbEndpoint::try_from(2), Ok(UsbEndpoint::VendorHid));
assert_eq!(UsbEndpoint::try_from(3), Err(SendOrRecvError));
}
}
43 changes: 43 additions & 0 deletions libraries/opensk/src/api/customization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,4 +457,47 @@ mod test {
fn test_invariants() {
assert!(is_valid(&DEFAULT_CUSTOMIZATION));
}

#[test]
fn test_accessors() {
let customization = CustomizationImpl {
aaguid: &[0; AAGUID_LENGTH],
allows_pin_protocol_v1: true,
default_cred_protect: None,
default_min_pin_length: 4,
default_min_pin_length_rp_ids: &["example.com"],
enforce_always_uv: false,
enterprise_attestation_mode: None,
enterprise_rp_id_list: &[],
max_msg_size: 7609,
max_pin_retries: 8,
use_batch_attestation: true,
use_signature_counter: true,
max_cred_blob_length: 32,
max_credential_count_in_list: Some(3),
max_large_blob_array_size: 2048,
max_rp_ids_length: 8,
max_supported_resident_keys: 150,
};
assert_eq!(customization.aaguid(), &[0; AAGUID_LENGTH]);
assert!(customization.allows_pin_protocol_v1());
assert!(customization.default_cred_protect().is_none());
assert_eq!(customization.default_min_pin_length(), 4);
assert_eq!(
customization.default_min_pin_length_rp_ids(),
vec![String::from("example.com")]
);
assert!(!customization.enforce_always_uv());
assert!(customization.enterprise_attestation_mode().is_none());
assert!(customization.enterprise_rp_id_list().is_empty());
assert_eq!(customization.max_msg_size(), 7609);
assert_eq!(customization.max_pin_retries(), 8);
assert!(customization.use_batch_attestation());
assert!(customization.use_signature_counter());
assert_eq!(customization.max_cred_blob_length(), 32);
assert_eq!(customization.max_credential_count_in_list(), Some(3));
assert_eq!(customization.max_large_blob_array_size(), 2048);
assert_eq!(customization.max_rp_ids_length(), 8);
assert_eq!(customization.max_supported_resident_keys(), 150);
}
}
11 changes: 10 additions & 1 deletion libraries/opensk/src/ctap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,9 @@ pub struct StatefulPermission<E: Env> {
impl<E: Env> StatefulPermission<E> {
/// Creates the command state at device startup.
///
/// Resets are only possible after a power cycle. Therefore, initialization
/// Resets are only possible after a power cycle. Therefore, there is no way to grant the Reset
/// permission outside of this function. If you initialize the app without a power cycle
/// (potentially after waking up from sleep), `clear` the permissions.
/// means allowing Reset, and Reset cannot be granted later.
pub fn new_reset(env: &mut E) -> StatefulPermission<E> {
StatefulPermission {
Expand Down Expand Up @@ -552,6 +554,13 @@ impl<E: Env> CtapState<E> {
}
}

/// Creates new CTAP state that doesn't assume a user intended power cycle.
pub fn new_soft_reset(env: &mut E) -> Self {
let mut ctap_state = CtapState::new(env);
ctap_state.stateful_command_permission.clear();
ctap_state
}

pub fn increment_global_signature_counter(
&mut self,
env: &mut E,
Expand Down
78 changes: 69 additions & 9 deletions libraries/opensk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ impl<E: Env> Ctap<E> {
/// Instantiates a CTAP implementation given its environment.
// This should only take the environment, but it temporarily takes the boot time until the
// clock is part of the environment.
pub fn new(mut env: E) -> Self {
let state = CtapState::<E>::new(&mut env);
pub fn new(mut env: E, is_soft_reset: bool) -> Self {
let state = if is_soft_reset {
CtapState::<E>::new_soft_reset(&mut env)
} else {
CtapState::<E>::new(&mut env)
};
let hid = MainHid::default();
#[cfg(feature = "vendor_hid")]
let vendor_hid = VendorHid::default();
Expand All @@ -81,10 +85,6 @@ impl<E: Env> Ctap<E> {
&mut self.state
}

pub fn hid(&mut self) -> &mut MainHid<E> {
&mut self.hid
}

pub fn env(&mut self) -> &mut E {
&mut self.env
}
Expand Down Expand Up @@ -134,6 +134,7 @@ impl<E: Env> Ctap<E> {
#[cfg(test)]
mod test {
use super::*;
use crate::ctap::status_code::Ctap2StatusCode;
use crate::env::test::TestEnv;

/// Assembles a packet for a payload that fits into one packet.
Expand Down Expand Up @@ -162,7 +163,7 @@ mod test {
#[test]
fn test_wink() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
let mut ctap = Ctap::<TestEnv>::new(env, false);

// Send Init, receive Init response and check wink if disabled.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
Expand All @@ -181,7 +182,7 @@ mod test {
#[test]
fn test_locked_channel_id() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
let mut ctap = Ctap::<TestEnv>::new(env, false);

// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
Expand All @@ -200,11 +201,60 @@ mod test {
assert_eq!(response_packet[4], 0xBF);
}

#[test]
fn test_hard_reset() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env, false);

// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);

// Send Reset, get Ok.
let reset_packet = assemble_packet(&cid, 0x10, &[0x07]);
let mut reset_response = ctap.process_hid_packet(&reset_packet, Transport::MainHid);
let response_packet = reset_response.next().unwrap();
let status_byte = Ctap2StatusCode::CTAP2_OK as u8;
let expected_data = [0x90, 0x00, 0x01, status_byte];
assert_eq!(response_packet[..4], cid);
assert_eq!(response_packet[4..8], expected_data);
}

#[test]
fn test_soft_reset() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env, true);

// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0x86);
let cid = *array_ref!(response_packet, 15, 4);

// Send Reset, get error.
let reset_packet = assemble_packet(&cid, 0x10, &[0x07]);
let mut reset_response = ctap.process_hid_packet(&reset_packet, Transport::MainHid);
let response_packet = reset_response.next().unwrap();
let status_byte = Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED as u8;
let expected_data = [0x90, 0x00, 0x01, status_byte];
assert_eq!(response_packet[..4], cid);
assert_eq!(response_packet[4..8], expected_data);
}

#[test]
fn test_env_api() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env, true);
assert_eq!(ctap.env().firmware_version(), Some(0));
}

#[test]
#[cfg(feature = "vendor_hid")]
fn test_locked_transport() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env);
let mut ctap = Ctap::<TestEnv>::new(env, false);

// Send Init, receive Init response.
let mut init_response = ctap.process_hid_packet(&init_packet(), Transport::MainHid);
Expand All @@ -222,4 +272,14 @@ mod test {
let response_packet = init_response.next().unwrap();
assert_eq!(response_packet[4], 0xBF);
}

#[test]
#[cfg(feature = "with_ctap1")]
fn test_ctap1_initial_state() {
let env = TestEnv::default();
let mut ctap = Ctap::<TestEnv>::new(env, false);
// Granting doesn't work until a CTAP1 request was processed.
ctap.u2f_grant_user_presence();
assert!(!ctap.u2f_needs_user_presence());
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ fn main() {
}

let env = TockEnv::<SyscallImplementation>::default();
let mut ctap = opensk::Ctap::new(env);
let mut ctap = opensk::Ctap::new(env, false);

let mut led_counter = 0;
let mut led_blink_timer =
Expand Down

0 comments on commit 1e9579d

Please sign in to comment.