From 3a39c4dff1abc9cc1408ffaaa83d42be6a67d143 Mon Sep 17 00:00:00 2001 From: hcyang <100930165+hcyang-google@users.noreply.github.com> Date: Tue, 10 May 2022 18:50:18 +0800 Subject: [PATCH] Add test_helpers (#474) * Add set_enterprise_attestation in TestEnv * Add test_helpers for Test Unification * Used it in structured fuzzer and enterprise attestation unittests * Restructure test_helpers * Rename setup_enterprise_attestation to enable_ --- fuzz/fuzz_helper/src/lib.rs | 60 +++++++++++++++++++++++++++++------ src/ctap/command.rs | 2 +- src/ctap/data_formats.rs | 1 + src/ctap/mod.rs | 49 ++++++++++++---------------- src/env/test/customization.rs | 43 ++++++++++++++++--------- src/env/test/mod.rs | 4 +-- src/lib.rs | 2 ++ src/test_helpers/mod.rs | 48 ++++++++++++++++++++++++++++ 8 files changed, 153 insertions(+), 56 deletions(-) create mode 100644 src/test_helpers/mod.rs diff --git a/fuzz/fuzz_helper/src/lib.rs b/fuzz/fuzz_helper/src/lib.rs index f3498bf..9acf7ec 100644 --- a/fuzz/fuzz_helper/src/lib.rs +++ b/fuzz/fuzz_helper/src/lib.rs @@ -19,17 +19,20 @@ extern crate lang_items; use arbitrary::{Arbitrary, Unstructured}; use arrayref::array_ref; use core::convert::TryFrom; +use ctap2::api::customization::is_valid; use ctap2::clock::CtapInstant; use ctap2::ctap::command::{ AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, AuthenticatorMakeCredentialParameters, Command, }; +use ctap2::ctap::data_formats::EnterpriseAttestationMode; use ctap2::ctap::hid::{ ChannelID, CtapHidCommand, HidPacket, HidPacketIterator, Message, MessageAssembler, }; use ctap2::ctap::{cbor_read, Channel, CtapState}; +use ctap2::env::test::customization::TestCustomization; use ctap2::env::test::TestEnv; -use ctap2::{Ctap, Transport}; +use ctap2::{test_helpers, Ctap, Transport}; const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; @@ -41,6 +44,19 @@ pub enum InputType { Ctap1, } +pub enum FuzzError { + ArbitraryError(arbitrary::Error), + InvalidCustomization, +} + +pub type FuzzResult = Result; + +impl From for FuzzError { + fn from(err: arbitrary::Error) -> Self { + Self::ArbitraryError(err) + } +} + // Converts a byte slice into Message fn raw_to_message(data: &[u8]) -> Message { if data.len() <= 4 { @@ -132,8 +148,7 @@ pub fn process_ctap_any_type(data: &[u8]) -> arbitrary::Result<()> { let mut unstructured = Unstructured::new(data); let mut env = TestEnv::new(); - env.rng() - .seed_rng_from_u64(u64::arbitrary(&mut unstructured)?); + env.rng().seed_from_u64(u64::arbitrary(&mut unstructured)?); let data = unstructured.take_rest(); // Initialize ctap state and hid and get the allocated cid. @@ -146,6 +161,33 @@ pub fn process_ctap_any_type(data: &[u8]) -> arbitrary::Result<()> { Ok(()) } +fn setup_customization( + unstructured: &mut Unstructured, + customization: &mut TestCustomization, +) -> FuzzResult<()> { + customization.setup_enterprise_attestation( + Option::::arbitrary(unstructured)?, + // TODO: Generate arbitrary rp_id_list (but with some dummies because content doesn't + // matter), and use the rp ids in commands. + None, + ); + if !is_valid(customization) { + return Err(FuzzError::InvalidCustomization); + } + Ok(()) +} + +fn setup_state( + unstructured: &mut Unstructured, + state: &mut CtapState, + env: &mut TestEnv, +) -> FuzzResult<()> { + if bool::arbitrary(unstructured)? { + test_helpers::enable_enterprise_attestation(state, env).ok(); + } + Ok(()) +} + // Interprets the raw data as of the given input type and // invokes message splitting, packet processing at CTAP HID level and response assembling // using an initialized and allocated channel. @@ -153,8 +195,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) -> arbitra let mut unstructured = Unstructured::new(data); let mut env = TestEnv::new(); - env.rng() - .seed_rng_from_u64(u64::arbitrary(&mut unstructured)?); + env.rng().seed_from_u64(u64::arbitrary(&mut unstructured)?); let data = unstructured.take_rest(); if !is_type(data, input_type) { @@ -184,13 +225,15 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) -> arbitra Ok(()) } -pub fn process_ctap_structured(data: &[u8], input_type: InputType) -> arbitrary::Result<()> { +pub fn process_ctap_structured(data: &[u8], input_type: InputType) -> FuzzResult<()> { let unstructured = &mut Unstructured::new(data); let mut env = TestEnv::new(); - env.rng().seed_rng_from_u64(u64::arbitrary(unstructured)?); + env.rng().seed_from_u64(u64::arbitrary(unstructured)?); + setup_customization(unstructured, env.customization_mut())?; let mut state = CtapState::new(&mut env, CtapInstant::new(0)); + setup_state(unstructured, &mut state, &mut env)?; let command = match input_type { InputType::CborMakeCredentialParameter => Command::AuthenticatorMakeCredential( @@ -224,8 +267,7 @@ pub fn split_assemble_hid_packets(data: &[u8]) -> arbitrary::Result<()> { let mut unstructured = Unstructured::new(data); let mut env = TestEnv::new(); - env.rng() - .seed_rng_from_u64(u64::arbitrary(&mut unstructured)?); + env.rng().seed_from_u64(u64::arbitrary(&mut unstructured)?); let data = unstructured.take_rest(); let message = raw_to_message(data); diff --git a/src/ctap/command.rs b/src/ctap/command.rs index bfcfc7a..0255dd0 100644 --- a/src/ctap/command.rs +++ b/src/ctap/command.rs @@ -489,7 +489,7 @@ impl TryFrom for AuthenticatorConfigParameters { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct AuthenticatorAttestationMaterial { pub certificate: Vec, pub private_key: [u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH], diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index 9516dec..094bdb2 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -1053,6 +1053,7 @@ impl From for cbor::Value { /// The level of enterprise attestation allowed in MakeCredential. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum EnterpriseAttestationMode { /// Enterprise attestation is restricted to a list of RP IDs. Add your /// enterprises domain, e.g. "example.com", to the list below. diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 44b14b7..5f1fe0c 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -22,7 +22,7 @@ mod crypto_wrapper; mod ctap1; pub mod data_formats; pub mod hid; -mod key_material; +pub mod key_material; mod large_blobs; pub mod main_hid; mod pin_protocol; @@ -1436,7 +1436,9 @@ mod test { }; use super::pin_protocol::{authenticate_pin_uv_auth_token, PinProtocol}; use super::*; + use crate::api::customization; use crate::env::test::TestEnv; + use crate::test_helpers; use cbor::{cbor_array, cbor_array_vec, cbor_map}; // The keep-alive logic in the processing of some commands needs a channel ID to send @@ -2062,17 +2064,13 @@ mod test { #[test] fn test_process_make_credential_with_enterprise_attestation_vendor_facilitated() { let mut env = TestEnv::new(); - env.customization_mut().enterprise_attestation_mode = - Some(EnterpriseAttestationMode::VendorFacilitated); - env.customization_mut().enterprise_rp_id_list = vec!["example.com".to_string()]; - let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); + env.customization_mut().setup_enterprise_attestation( + Some(EnterpriseAttestationMode::VendorFacilitated), + Some(vec!["example.com".to_string()]), + ); - let mut key_bytes = [0; 32]; - let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); - private_key.to_bytes(array_mut_ref!(key_bytes, 0, 32)); - storage::set_attestation_certificate(&mut env, &[0xCC]).unwrap(); - storage::set_attestation_private_key(&mut env, &key_bytes).unwrap(); - storage::enable_enterprise_attestation(&mut env).unwrap(); + let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); + test_helpers::enable_enterprise_attestation(&mut ctap_state, &mut env).unwrap(); let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.enterprise_attestation = Some(1); @@ -2112,17 +2110,14 @@ mod test { #[test] fn test_process_make_credential_with_enterprise_attestation_platform_managed() { let mut env = TestEnv::new(); - env.customization_mut().enterprise_attestation_mode = - Some(EnterpriseAttestationMode::PlatformManaged); - env.customization_mut().enterprise_rp_id_list = vec!["example.com".to_string()]; - let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); + env.customization_mut().setup_enterprise_attestation( + Some(EnterpriseAttestationMode::PlatformManaged), + Some(vec!["example.com".to_string()]), + ); + assert!(customization::is_valid(env.customization())); - let mut key_bytes = [0; 32]; - let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); - private_key.to_bytes(array_mut_ref!(key_bytes, 0, 32)); - storage::set_attestation_certificate(&mut env, &[0xCC]).unwrap(); - storage::set_attestation_private_key(&mut env, &key_bytes).unwrap(); - storage::enable_enterprise_attestation(&mut env).unwrap(); + let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); + test_helpers::enable_enterprise_attestation(&mut ctap_state, &mut env).unwrap(); let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.enterprise_attestation = Some(1); @@ -2151,8 +2146,9 @@ mod test { #[test] fn test_process_make_credential_with_enterprise_attestation_invalid() { let mut env = TestEnv::new(); - env.customization_mut().enterprise_attestation_mode = - Some(EnterpriseAttestationMode::PlatformManaged); + env.customization_mut() + .setup_enterprise_attestation(Some(EnterpriseAttestationMode::PlatformManaged), None); + let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); let mut make_credential_params = create_minimal_make_credential_parameters(); @@ -2164,12 +2160,7 @@ mod test { Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) ); - let mut key_bytes = [0; 32]; - let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); - private_key.to_bytes(array_mut_ref!(key_bytes, 0, 32)); - storage::set_attestation_certificate(&mut env, &[0xCC]).unwrap(); - storage::set_attestation_private_key(&mut env, &key_bytes).unwrap(); - storage::enable_enterprise_attestation(&mut env).unwrap(); + test_helpers::enable_enterprise_attestation(&mut ctap_state, &mut env).unwrap(); let mut make_credential_params = create_minimal_make_credential_parameters(); make_credential_params.enterprise_attestation = Some(3); diff --git a/src/env/test/customization.rs b/src/env/test/customization.rs index 0d99fc8..42d4425 100644 --- a/src/env/test/customization.rs +++ b/src/env/test/customization.rs @@ -4,21 +4,34 @@ use alloc::string::String; use alloc::vec::Vec; pub struct TestCustomization { - pub default_cred_protect: Option, - pub default_min_pin_length: u8, - pub default_min_pin_length_rp_ids: Vec, - pub enforce_always_uv: bool, - pub enterprise_attestation_mode: Option, - pub enterprise_rp_id_list: Vec, - pub max_msg_size: usize, - pub max_pin_retries: u8, - pub use_batch_attestation: bool, - pub use_signature_counter: bool, - pub max_cred_blob_length: usize, - pub max_credential_count_in_list: Option, - pub max_large_blob_array_size: usize, - pub max_rp_ids_length: usize, - pub max_supported_resident_keys: usize, + default_cred_protect: Option, + default_min_pin_length: u8, + default_min_pin_length_rp_ids: Vec, + enforce_always_uv: bool, + enterprise_attestation_mode: Option, + enterprise_rp_id_list: Vec, + max_msg_size: usize, + max_pin_retries: u8, + use_batch_attestation: bool, + use_signature_counter: bool, + max_cred_blob_length: usize, + max_credential_count_in_list: Option, + max_large_blob_array_size: usize, + max_rp_ids_length: usize, + max_supported_resident_keys: usize, +} + +impl TestCustomization { + pub fn setup_enterprise_attestation( + &mut self, + mode: Option, + rp_id_list: Option>, + ) { + self.enterprise_attestation_mode = mode; + if let Some(rp_id_list) = rp_id_list { + self.enterprise_rp_id_list = rp_id_list; + } + } } impl Customization for TestCustomization { diff --git a/src/env/test/mod.rs b/src/env/test/mod.rs index a777a37..73d7541 100644 --- a/src/env/test/mod.rs +++ b/src/env/test/mod.rs @@ -10,7 +10,7 @@ use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use rng256::Rng256; -mod customization; +pub mod customization; mod upgrade_storage; pub struct TestEnv { @@ -26,7 +26,7 @@ pub struct TestRng256 { } impl TestRng256 { - pub fn seed_rng_from_u64(&mut self, state: u64) { + pub fn seed_from_u64(&mut self, state: u64) { self.rng = StdRng::seed_from_u64(state); } } diff --git a/src/lib.rs b/src/lib.rs index 840bbfe..89b5cf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,8 @@ pub mod ctap; #[cfg(not(feature = "std"))] mod ctap; pub mod env; +#[cfg(feature = "std")] +pub mod test_helpers; /// CTAP implementation parameterized by its environment. pub struct Ctap { diff --git a/src/test_helpers/mod.rs b/src/test_helpers/mod.rs new file mode 100644 index 0000000..b7b7de6 --- /dev/null +++ b/src/test_helpers/mod.rs @@ -0,0 +1,48 @@ +use crate::clock::CtapInstant; +use crate::ctap::command::{ + AuthenticatorAttestationMaterial, AuthenticatorConfigParameters, + AuthenticatorVendorConfigureParameters, Command, +}; +use crate::ctap::data_formats::ConfigSubCommand; +use crate::ctap::status_code::Ctap2StatusCode; +use crate::ctap::{key_material, Channel, CtapState}; +use crate::env::Env; + +// In tests where we define a dummy user-presence check that immediately returns, the channel +// ID is irrelevant, so we pass this (dummy but valid) value. +const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]); +#[cfg(feature = "vendor_hid")] +const VENDOR_CHANNEL: Channel = Channel::VendorHid([0x12, 0x34, 0x56, 0x78]); + +pub fn enable_enterprise_attestation( + state: &mut CtapState, + env: &mut impl Env, +) -> Result { + let dummy_key = [0x41; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]; + let dummy_cert = vec![0xdd; 20]; + let attestation_material = AuthenticatorAttestationMaterial { + certificate: dummy_cert, + private_key: dummy_key, + }; + let configure_params = AuthenticatorVendorConfigureParameters { + lockdown: false, + attestation_material: Some(attestation_material.clone()), + }; + #[cfg(feature = "vendor_hid")] + let vendor_channel = VENDOR_CHANNEL; + #[cfg(not(feature = "vendor_hid"))] + let vendor_channel = DUMMY_CHANNEL; + let vendor_command = Command::AuthenticatorVendorConfigure(configure_params); + state.process_parsed_command(env, vendor_command, vendor_channel, CtapInstant::new(0))?; + + let config_params = AuthenticatorConfigParameters { + sub_command: ConfigSubCommand::EnableEnterpriseAttestation, + sub_command_params: None, + pin_uv_auth_param: None, + pin_uv_auth_protocol: None, + }; + let config_command = Command::AuthenticatorConfig(config_params); + state.process_parsed_command(env, config_command, DUMMY_CHANNEL, CtapInstant::new(0))?; + + Ok(attestation_material) +}