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.
This commit is contained in:
Howard Yang
2022-03-14 16:16:50 +08:00
parent 12c5a419b4
commit 17ecd46b04
9 changed files with 79 additions and 4 deletions

View File

@@ -22,6 +22,7 @@ subtle = { version = "2.2", default-features = false, features = ["nightly"] }
# This import explicitly locks the version. # This import explicitly locks the version.
serde_json = { version = "=1.0.69", default-features = false, features = ["alloc"] } serde_json = { version = "=1.0.69", default-features = false, features = ["alloc"] }
embedded-time = "0.12.1" embedded-time = "0.12.1"
arbitrary = { version = "0.4.7", features = ["derive"], optional = true }
[features] [features]
debug_allocations = ["lang_items/debug_allocations"] debug_allocations = ["lang_items/debug_allocations"]
@@ -32,6 +33,7 @@ verbose = ["debug_ctap", "libtock_drivers/verbose_usb"]
with_ctap1 = ["crypto/with_ctap1"] with_ctap1 = ["crypto/with_ctap1"]
with_nfc = ["libtock_drivers/with_nfc"] with_nfc = ["libtock_drivers/with_nfc"]
vendor_hid = [] vendor_hid = []
fuzz = ["arbitrary", "std"]
[dev-dependencies] [dev-dependencies]
enum-iterator = "0.6.0" enum-iterator = "0.6.0"

View File

@@ -48,6 +48,12 @@ path = "fuzz_targets/fuzz_target_process_ctap2_make_credential.rs"
test = false test = false
doc = 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]] [[bin]]
name = "fuzz_target_split_assemble" name = "fuzz_target_split_assemble"
path = "fuzz_targets/fuzz_target_split_assemble.rs" path = "fuzz_targets/fuzz_target_split_assemble.rs"

View File

@@ -11,5 +11,6 @@ embedded-time = "0.12.1"
libtock_drivers = { path = "../../third_party/libtock-drivers" } libtock_drivers = { path = "../../third_party/libtock-drivers" }
crypto = { path = "../../libraries/crypto", features = ['std'] } crypto = { path = "../../libraries/crypto", features = ['std'] }
sk-cbor = { path = "../../libraries/cbor" } sk-cbor = { path = "../../libraries/cbor" }
ctap2 = { path = "../..", features = ['std'] } ctap2 = { path = "../..", features = ["fuzz"] }
lang_items = { path = "../../third_party/lang-items", features = ['std'] } lang_items = { path = "../../third_party/lang-items", features = ['std'] }
arbitrary = { version = "0.4.7", features = ["derive"] }

View File

@@ -16,17 +16,18 @@
// `libtock_alloc_init` symbol. // `libtock_alloc_init` symbol.
extern crate lang_items; extern crate lang_items;
use arbitrary::{Arbitrary, Unstructured};
use arrayref::array_ref; use arrayref::array_ref;
use core::convert::TryFrom; use core::convert::TryFrom;
use ctap2::clock::CtapInstant; use ctap2::clock::CtapInstant;
use ctap2::ctap::cbor_read;
use ctap2::ctap::command::{ use ctap2::ctap::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, AuthenticatorMakeCredentialParameters, Command,
}; };
use ctap2::ctap::hid::{ use ctap2::ctap::hid::{
ChannelID, CtapHidCommand, HidPacket, HidPacketIterator, Message, MessageAssembler, ChannelID, CtapHidCommand, HidPacket, HidPacketIterator, Message, MessageAssembler,
}; };
use ctap2::ctap::{cbor_read, Channel, CtapState};
use ctap2::env::test::TestEnv; use ctap2::env::test::TestEnv;
use ctap2::{Ctap, Transport}; use ctap2::{Ctap, Transport};
@@ -166,6 +167,39 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
process_message(&command, &mut ctap); 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. // 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]) { pub fn split_assemble_hid_packets(data: &[u8]) {
let message = raw_to_message(data); let message = raw_to_message(data);

View File

@@ -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();
});

View File

@@ -225,6 +225,12 @@ impl From<&str> for Value {
} }
} }
impl From<Vec<Value>> for Value {
fn from(array: Vec<Value>) -> Self {
Value::Array(array)
}
}
impl From<Vec<(Value, Value)>> for Value { impl From<Vec<(Value, Value)>> for Value {
fn from(map: Vec<(Value, Value)>) -> Self { fn from(map: Vec<(Value, Value)>) -> Self {
Value::Map(map) Value::Map(map)

View File

@@ -26,6 +26,8 @@ use super::status_code::Ctap2StatusCode;
use super::{cbor_read, key_material}; use super::{cbor_read, key_material};
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
#[cfg(feature = "fuzz")]
use arbitrary::Arbitrary;
use arrayref::array_ref; use arrayref::array_ref;
use core::convert::TryFrom; use core::convert::TryFrom;
use sk_cbor as cbor; use sk_cbor as cbor;
@@ -155,6 +157,7 @@ impl Command {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct AuthenticatorMakeCredentialParameters { pub struct AuthenticatorMakeCredentialParameters {
pub client_data_hash: Vec<u8>, pub client_data_hash: Vec<u8>,
pub rp: PublicKeyCredentialRpEntity, pub rp: PublicKeyCredentialRpEntity,

View File

@@ -15,6 +15,8 @@
use super::status_code::Ctap2StatusCode; use super::status_code::Ctap2StatusCode;
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
#[cfg(feature = "fuzz")]
use arbitrary::Arbitrary;
use arrayref::array_ref; use arrayref::array_ref;
use core::convert::TryFrom; use core::convert::TryFrom;
use crypto::{ecdh, ecdsa}; use crypto::{ecdh, ecdsa};
@@ -28,6 +30,7 @@ const ES256_ALGORITHM: i64 = -7;
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct PublicKeyCredentialRpEntity { pub struct PublicKeyCredentialRpEntity {
pub rp_id: String, pub rp_id: String,
pub rp_name: Option<String>, pub rp_name: Option<String>,
@@ -70,6 +73,7 @@ impl From<PublicKeyCredentialRpEntity> for cbor::Value {
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct PublicKeyCredentialUserEntity { pub struct PublicKeyCredentialUserEntity {
pub user_id: Vec<u8>, pub user_id: Vec<u8>,
pub user_name: Option<String>, pub user_name: Option<String>,
@@ -117,6 +121,7 @@ impl From<PublicKeyCredentialUserEntity> for cbor::Value {
// https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype // https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum PublicKeyCredentialType { pub enum PublicKeyCredentialType {
PublicKey, PublicKey,
// This is the default for all strings not covered above. // This is the default for all strings not covered above.
@@ -149,6 +154,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialType {
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct PublicKeyCredentialParameter { pub struct PublicKeyCredentialParameter {
pub cred_type: PublicKeyCredentialType, pub cred_type: PublicKeyCredentialType,
pub alg: SignatureAlgorithm, pub alg: SignatureAlgorithm,
@@ -183,6 +189,7 @@ impl From<PublicKeyCredentialParameter> for cbor::Value {
// https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport // https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(test, derive(IntoEnumIterator))] #[cfg_attr(test, derive(IntoEnumIterator))]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum AuthenticatorTransport { pub enum AuthenticatorTransport {
Usb, Usb,
Nfc, Nfc,
@@ -219,6 +226,7 @@ impl TryFrom<cbor::Value> for AuthenticatorTransport {
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct PublicKeyCredentialDescriptor { pub struct PublicKeyCredentialDescriptor {
pub key_type: PublicKeyCredentialType, pub key_type: PublicKeyCredentialType,
pub key_id: Vec<u8>, pub key_id: Vec<u8>,
@@ -270,6 +278,7 @@ impl From<PublicKeyCredentialDescriptor> for cbor::Value {
} }
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct MakeCredentialExtensions { pub struct MakeCredentialExtensions {
pub hmac_secret: bool, pub hmac_secret: bool,
pub cred_protect: Option<CredentialProtectionPolicy>, pub cred_protect: Option<CredentialProtectionPolicy>,
@@ -388,6 +397,7 @@ impl TryFrom<cbor::Value> for GetAssertionHmacSecretInput {
// Even though options are optional, we can use the default if not present. // Even though options are optional, we can use the default if not present.
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct MakeCredentialOptions { pub struct MakeCredentialOptions {
pub rk: bool, pub rk: bool,
pub uv: bool, pub uv: bool,
@@ -488,6 +498,7 @@ impl From<PackedAttestationStatement> for cbor::Value {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum SignatureAlgorithm { pub enum SignatureAlgorithm {
ES256 = ES256_ALGORITHM as isize, ES256 = ES256_ALGORITHM as isize,
// This is the default for all numbers not covered above. // This is the default for all numbers not covered above.
@@ -516,6 +527,7 @@ impl TryFrom<cbor::Value> for SignatureAlgorithm {
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
#[cfg_attr(test, derive(IntoEnumIterator))] #[cfg_attr(test, derive(IntoEnumIterator))]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum CredentialProtectionPolicy { pub enum CredentialProtectionPolicy {
/// The credential is always discoverable, as if it had no protection level. /// The credential is always discoverable, as if it had no protection level.
UserVerificationOptional = 0x01, UserVerificationOptional = 0x01,
@@ -884,6 +896,7 @@ impl TryFrom<CoseSignature> for ecdsa::Signature {
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum PinUvAuthProtocol { pub enum PinUvAuthProtocol {
V1 = 1, V1 = 1,
V2 = 2, V2 = 2,

View File

@@ -532,7 +532,7 @@ impl CtapState {
/// ///
/// This function contains the logic of `parse_command`, minus all CBOR encoding and decoding. /// This function contains the logic of `parse_command`, minus all CBOR encoding and decoding.
/// It should make command parsing easier to test. /// It should make command parsing easier to test.
fn process_parsed_command( pub fn process_parsed_command(
&mut self, &mut self,
env: &mut impl Env, env: &mut impl Env,
command: Command, command: Command,