From 17ecd46b048780c01c06ccbdf61d179991fefbcc Mon Sep 17 00:00:00 2001 From: Howard Yang Date: Mon, 14 Mar 2022 16:16:50 +0800 Subject: [PATCH] Generate valid structure for MakeCredential params * Add crate arbitrary as ctap's optional dependency, when feature "fuzz" is activated. * Derive Arbitrary for all the necessary types in order to generate the concrete types from random bytes. * Add a fuzz target that transforms the input to valid format for MakeCredential. --- Cargo.toml | 2 + fuzz/Cargo.toml | 6 +++ fuzz/fuzz_helper/Cargo.toml | 3 +- fuzz/fuzz_helper/src/lib.rs | 38 ++++++++++++++++++- ...rocess_ctap2_make_credential_structured.rs | 10 +++++ libraries/cbor/src/values.rs | 6 +++ src/ctap/command.rs | 3 ++ src/ctap/data_formats.rs | 13 +++++++ src/ctap/mod.rs | 2 +- 9 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 fuzz/fuzz_targets/fuzz_target_process_ctap2_make_credential_structured.rs diff --git a/Cargo.toml b/Cargo.toml index c55b09a..7f2509c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ subtle = { version = "2.2", default-features = false, features = ["nightly"] } # This import explicitly locks the version. serde_json = { version = "=1.0.69", default-features = false, features = ["alloc"] } embedded-time = "0.12.1" +arbitrary = { version = "0.4.7", features = ["derive"], optional = true } [features] debug_allocations = ["lang_items/debug_allocations"] @@ -32,6 +33,7 @@ verbose = ["debug_ctap", "libtock_drivers/verbose_usb"] with_ctap1 = ["crypto/with_ctap1"] with_nfc = ["libtock_drivers/with_nfc"] vendor_hid = [] +fuzz = ["arbitrary", "std"] [dev-dependencies] enum-iterator = "0.6.0" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 56bc591..e6c8308 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -48,6 +48,12 @@ path = "fuzz_targets/fuzz_target_process_ctap2_make_credential.rs" test = false doc = false +[[bin]] +name = "fuzz_target_process_ctap2_make_credential_structured" +path = "fuzz_targets/fuzz_target_process_ctap2_make_credential_structured.rs" +test = false +doc = false + [[bin]] name = "fuzz_target_split_assemble" path = "fuzz_targets/fuzz_target_split_assemble.rs" diff --git a/fuzz/fuzz_helper/Cargo.toml b/fuzz/fuzz_helper/Cargo.toml index 05a62c1..1c23476 100644 --- a/fuzz/fuzz_helper/Cargo.toml +++ b/fuzz/fuzz_helper/Cargo.toml @@ -11,5 +11,6 @@ embedded-time = "0.12.1" libtock_drivers = { path = "../../third_party/libtock-drivers" } crypto = { path = "../../libraries/crypto", features = ['std'] } sk-cbor = { path = "../../libraries/cbor" } -ctap2 = { path = "../..", features = ['std'] } +ctap2 = { path = "../..", features = ["fuzz"] } lang_items = { path = "../../third_party/lang-items", features = ['std'] } +arbitrary = { version = "0.4.7", features = ["derive"] } diff --git a/fuzz/fuzz_helper/src/lib.rs b/fuzz/fuzz_helper/src/lib.rs index 7c4ca1c..a4b5dc0 100644 --- a/fuzz/fuzz_helper/src/lib.rs +++ b/fuzz/fuzz_helper/src/lib.rs @@ -16,17 +16,18 @@ // `libtock_alloc_init` symbol. extern crate lang_items; +use arbitrary::{Arbitrary, Unstructured}; use arrayref::array_ref; use core::convert::TryFrom; use ctap2::clock::CtapInstant; -use ctap2::ctap::cbor_read; use ctap2::ctap::command::{ AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, - AuthenticatorMakeCredentialParameters, + AuthenticatorMakeCredentialParameters, Command, }; use ctap2::ctap::hid::{ ChannelID, CtapHidCommand, HidPacket, HidPacketIterator, Message, MessageAssembler, }; +use ctap2::ctap::{cbor_read, Channel, CtapState}; use ctap2::env::test::TestEnv; use ctap2::{Ctap, Transport}; @@ -166,6 +167,39 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) { process_message(&command, &mut ctap); } +pub fn process_ctap_structured(data: &[u8], input_type: InputType) -> arbitrary::Result<()> { + let unstructured = &mut Unstructured::new(data); + + let mut env = TestEnv::new(); + let mut state = CtapState::new(&mut env, CtapInstant::new(0)); + + let command = match input_type { + InputType::CborMakeCredentialParameter => Command::AuthenticatorMakeCredential( + AuthenticatorMakeCredentialParameters::arbitrary(unstructured)?, + ), + InputType::CborGetAssertionParameter => { + unimplemented!() + } + InputType::CborClientPinParameter => { + unimplemented!() + } + InputType::Ctap1 => { + unimplemented!() + } + }; + + state + .process_parsed_command( + &mut env, + command, + Channel::MainHid(ChannelID::arbitrary(unstructured)?), + CtapInstant::new(0), + ) + .ok(); + + Ok(()) +} + // Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed. pub fn split_assemble_hid_packets(data: &[u8]) { let message = raw_to_message(data); diff --git a/fuzz/fuzz_targets/fuzz_target_process_ctap2_make_credential_structured.rs b/fuzz/fuzz_targets/fuzz_target_process_ctap2_make_credential_structured.rs new file mode 100644 index 0000000..ff74744 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_target_process_ctap2_make_credential_structured.rs @@ -0,0 +1,10 @@ +#![no_main] + +use fuzz_helper::{process_ctap_structured, InputType}; +use libfuzzer_sys::fuzz_target; + +// Fuzz inputs as CTAP2 make credential command parameters. +// The inputs will used to construct arbitrary make credential parameters. +fuzz_target!(|data: &[u8]| { + process_ctap_structured(data, InputType::CborMakeCredentialParameter).ok(); +}); diff --git a/libraries/cbor/src/values.rs b/libraries/cbor/src/values.rs index 253d67f..a293bff 100644 --- a/libraries/cbor/src/values.rs +++ b/libraries/cbor/src/values.rs @@ -225,6 +225,12 @@ impl From<&str> for Value { } } +impl From> for Value { + fn from(array: Vec) -> Self { + Value::Array(array) + } +} + impl From> for Value { fn from(map: Vec<(Value, Value)>) -> Self { Value::Map(map) diff --git a/src/ctap/command.rs b/src/ctap/command.rs index 5fb5e3e..69b8fe6 100644 --- a/src/ctap/command.rs +++ b/src/ctap/command.rs @@ -26,6 +26,8 @@ use super::status_code::Ctap2StatusCode; use super::{cbor_read, key_material}; use alloc::string::String; use alloc::vec::Vec; +#[cfg(feature = "fuzz")] +use arbitrary::Arbitrary; use arrayref::array_ref; use core::convert::TryFrom; use sk_cbor as cbor; @@ -155,6 +157,7 @@ impl Command { } #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct AuthenticatorMakeCredentialParameters { pub client_data_hash: Vec, pub rp: PublicKeyCredentialRpEntity, diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index fc0df1a..ba7ef0c 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -15,6 +15,8 @@ use super::status_code::Ctap2StatusCode; use alloc::string::String; use alloc::vec::Vec; +#[cfg(feature = "fuzz")] +use arbitrary::Arbitrary; use arrayref::array_ref; use core::convert::TryFrom; use crypto::{ecdh, ecdsa}; @@ -28,6 +30,7 @@ const ES256_ALGORITHM: i64 = -7; // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct PublicKeyCredentialRpEntity { pub rp_id: String, pub rp_name: Option, @@ -70,6 +73,7 @@ impl From for cbor::Value { // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct PublicKeyCredentialUserEntity { pub user_id: Vec, pub user_name: Option, @@ -117,6 +121,7 @@ impl From for cbor::Value { // https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum PublicKeyCredentialType { PublicKey, // This is the default for all strings not covered above. @@ -149,6 +154,7 @@ impl TryFrom for PublicKeyCredentialType { // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct PublicKeyCredentialParameter { pub cred_type: PublicKeyCredentialType, pub alg: SignatureAlgorithm, @@ -183,6 +189,7 @@ impl From for cbor::Value { // https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport #[derive(Clone, Debug, PartialEq)] #[cfg_attr(test, derive(IntoEnumIterator))] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum AuthenticatorTransport { Usb, Nfc, @@ -219,6 +226,7 @@ impl TryFrom for AuthenticatorTransport { // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct PublicKeyCredentialDescriptor { pub key_type: PublicKeyCredentialType, pub key_id: Vec, @@ -270,6 +278,7 @@ impl From for cbor::Value { } #[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct MakeCredentialExtensions { pub hmac_secret: bool, pub cred_protect: Option, @@ -388,6 +397,7 @@ impl TryFrom for GetAssertionHmacSecretInput { // Even though options are optional, we can use the default if not present. #[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub struct MakeCredentialOptions { pub rk: bool, pub uv: bool, @@ -488,6 +498,7 @@ impl From for cbor::Value { } #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum SignatureAlgorithm { ES256 = ES256_ALGORITHM as isize, // This is the default for all numbers not covered above. @@ -516,6 +527,7 @@ impl TryFrom for SignatureAlgorithm { #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] #[cfg_attr(test, derive(IntoEnumIterator))] #[allow(clippy::enum_variant_names)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum CredentialProtectionPolicy { /// The credential is always discoverable, as if it had no protection level. UserVerificationOptional = 0x01, @@ -884,6 +896,7 @@ impl TryFrom for ecdsa::Signature { } #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum PinUvAuthProtocol { V1 = 1, V2 = 2, diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index b93b39d..903449f 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -532,7 +532,7 @@ impl CtapState { /// /// This function contains the logic of `parse_command`, minus all CBOR encoding and decoding. /// It should make command parsing easier to test. - fn process_parsed_command( + pub fn process_parsed_command( &mut self, env: &mut impl Env, command: Command,