Skip to content

Commit

Permalink
Add arbitrary::Arbitrary implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
robin-nitrokey committed Jun 26, 2024
1 parent be9589d commit da1666b
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 3 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,28 @@ jobs:
- name: Check library
run: |
cargo check
cargo check --features arbitrary
cargo check --features get-info-full
cargo check --features large-blobs
cargo check --all-features
build-no-std:
name: Check library (no-std)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-unknown-linux-gnu
override: true
- name: Check library (no-std)
run: |
cargo check
cargo check --features get-info-full
cargo check --features large-blobs
test:
name: Run tests
runs-on: ubuntu-latest
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ description = "no_std friendly types for FIDO CTAP"
repository = "https://github.com/trussed-dev/ctap-types"

[dependencies]
arbitrary = { version = "1.3.2", features = ["derive"], optional = true }
bitflags = "1.3"
cbor-smol = "0.4"
cosey = "0.3"
Expand All @@ -21,6 +22,10 @@ serde_bytes = { version = "0.11.14", default-features = false }
serde_repr = "0.1"

[features]
std = []

# implements arbitrary::Arbitrary for requests
arbitrary = ["dep:arbitrary", "std"]
# enables all fields for ctap2::get_info
get-info-full = []
# enables support for implementing the large-blobs extension, see src/sizes.rs
Expand Down
332 changes: 332 additions & 0 deletions src/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
use core::{fmt::Debug, ops::ControlFlow};

use arbitrary::{Arbitrary, Error, Result, Unstructured};
use cosey::EcdhEsHkdf256PublicKey;
use heapless::{String, Vec};
use heapless_bytes::Bytes;
use serde_bytes::ByteArray;

use crate::{ctap1, ctap2, webauthn};

// cannot be derived because of missing impl for &[T; N]
impl<'a> Arbitrary<'a> for ctap1::authenticate::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let control_byte = Arbitrary::arbitrary(u)?;
let challenge = u.bytes(32)?.try_into().unwrap();
let app_id = u.bytes(32)?.try_into().unwrap();
let key_handle = Arbitrary::arbitrary(u)?;
Ok(Self {
control_byte,
challenge,
app_id,
key_handle,
})
}
}

// cannot be derived because of missing impl for &[T; N]
impl<'a> Arbitrary<'a> for ctap1::register::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let challenge = u.bytes(32)?.try_into().unwrap();
let app_id = u.bytes(32)?.try_into().unwrap();
Ok(Self { challenge, app_id })
}
}

// cannot be derived because of missing impl for serde_bytes::Bytes, EcdhEsHkdf256PublicKey
impl<'a> Arbitrary<'a> for ctap2::client_pin::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let pin_protocol = u.arbitrary()?;
let sub_command = u.arbitrary()?;
let key_agreement = arbitrary_option(u, arbitrary_key)?;
let pin_auth = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let new_pin_enc = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let pin_hash_enc = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let _placeholder07 = u.arbitrary()?;
let _placeholder08 = u.arbitrary()?;
let permissions = u.arbitrary()?;
let rp_id = u.arbitrary()?;
Ok(Self {
pin_protocol,
sub_command,
key_agreement,
pin_auth,
new_pin_enc,
pin_hash_enc,
_placeholder07,
_placeholder08,
permissions,
rp_id,
})
}
}

// cannot be derived because of missing impl for serde_bytes::Bytes
impl<'a> Arbitrary<'a> for ctap2::credential_management::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let sub_command = u.arbitrary()?;
let sub_command_params = u.arbitrary()?;
let pin_protocol = u.arbitrary()?;
let pin_auth = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
Ok(Self {
sub_command,
sub_command_params,
pin_protocol,
pin_auth,
})
}
}

// cannot be derived because of missing impl for serde_bytes::ByteArray
impl<'a> Arbitrary<'a> for ctap2::credential_management::SubcommandParameters<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let rp_id_hash = arbitrary_option(u, arbitrary_byte_array)?;
let credential_id = u.arbitrary()?;
let user = u.arbitrary()?;
Ok(Self {
rp_id_hash,
credential_id,
user,
})
}
}

// cannot be derived because of missing impl for EcdhEsHkdf256PublicKey, Bytes<_>
impl<'a> Arbitrary<'a> for ctap2::get_assertion::HmacSecretInput {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let key_agreement = arbitrary_key(u)?;
let salt_enc = arbitrary_bytes(u)?;
let salt_auth = arbitrary_bytes(u)?;
let pin_protocol = u.arbitrary()?;
Ok(Self {
key_agreement,
salt_enc,
salt_auth,
pin_protocol,
})
}
}

// cannot be derived because of missing impl for serde_bytes::Bytes, Vec<_>
impl<'a> Arbitrary<'a> for ctap2::get_assertion::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let rp_id = u.arbitrary()?;
let client_data_hash = serde_bytes::Bytes::new(u.arbitrary()?);
let allow_list = arbitrary_option(u, arbitrary_vec)?;
let extensions = u.arbitrary()?;
let options = u.arbitrary()?;
let pin_auth = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let pin_protocol = u.arbitrary()?;
Ok(Self {
rp_id,
client_data_hash,
allow_list,
extensions,
options,
pin_auth,
pin_protocol,
})
}
}

// cannot be derived because of missing impl for serde_bytes::Bytes
impl<'a> Arbitrary<'a> for ctap2::large_blobs::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let get = u.arbitrary()?;
let set = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let offset = u.arbitrary()?;
let length = u.arbitrary()?;
let pin_uv_auth_param = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let pin_uv_auth_protocol = u.arbitrary()?;
Ok(Self {
get,
set,
offset,
length,
pin_uv_auth_param,
pin_uv_auth_protocol,
})
}
}

// cannot be derived because of missing impl for serde_bytes::Bytes
impl<'a> Arbitrary<'a> for ctap2::make_credential::Request<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let client_data_hash = serde_bytes::Bytes::new(u.arbitrary()?);
let rp = u.arbitrary()?;
let user = u.arbitrary()?;
let pub_key_cred_params = u.arbitrary()?;
let exclude_list = arbitrary_option(u, arbitrary_vec)?;
let extensions = u.arbitrary()?;
let options = u.arbitrary()?;
let pin_auth = if bool::arbitrary(u)? {
Some(serde_bytes::Bytes::new(u.arbitrary()?))
} else {
None
};
let pin_protocol = u.arbitrary()?;
let enterprise_attestation = u.arbitrary()?;
Ok(Self {
client_data_hash,
rp,
user,
pub_key_cred_params,
exclude_list,
extensions,
options,
pin_auth,
pin_protocol,
enterprise_attestation,
})
}
}

// cannot be derived because of missing impl for Vec<_>
impl<'a> Arbitrary<'a> for webauthn::FilteredPublicKeyCredentialParameters {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let parameters = arbitrary_vec(u)?;
Ok(Self(parameters))
}
}

// cannot be derived because we want to make sure that we have valid values
impl<'a> Arbitrary<'a> for webauthn::KnownPublicKeyCredentialParameters {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let alg = *u.choose(&webauthn::KNOWN_ALGS)?;
Ok(Self { alg })
}
}

// cannot be derived because of missing impl for serde_bytes::Bytes
impl<'a> Arbitrary<'a> for webauthn::PublicKeyCredentialDescriptorRef<'a> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let id = serde_bytes::Bytes::new(u.arbitrary()?);
let key_type = u.arbitrary()?;
Ok(Self { id, key_type })
}
}

// cannot be derived because of missing impl for String<_>
impl<'a> Arbitrary<'a> for webauthn::PublicKeyCredentialRpEntity {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let id = arbitrary_str(u)?;
let name = if bool::arbitrary(u)? {
Some(arbitrary_str(u)?)
} else {
None
};
let icon = Arbitrary::arbitrary(u)?;
Ok(Self { id, name, icon })
}
}

// cannot be derived because of missing impl for Bytes<_> and String<_>
impl<'a> Arbitrary<'a> for webauthn::PublicKeyCredentialUserEntity {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let id = arbitrary_bytes(u)?;
let icon = if bool::arbitrary(u)? {
Some(arbitrary_str(u)?)
} else {
None
};
let name = if bool::arbitrary(u)? {
Some(arbitrary_str(u)?)
} else {
None
};
let display_name = if bool::arbitrary(u)? {
Some(arbitrary_str(u)?)
} else {
None
};
Ok(Self {
id,
icon,
name,
display_name,
})
}
}

fn arbitrary_byte_array<'a, const N: usize>(u: &mut Unstructured<'_>) -> Result<&'a ByteArray<N>> {
let bytes: &[u8; N] = u.bytes(N)?.try_into().unwrap();
// TODO: conversion should be provided by serde_bytes
Ok(unsafe { &*(bytes as *const [u8; N] as *const ByteArray<N>) })
}

fn arbitrary_bytes<const N: usize>(u: &mut Unstructured<'_>) -> Result<Bytes<N>> {
let n = usize::arbitrary(u)?.min(N);
Ok(Bytes::from_slice(u.bytes(n)?).unwrap())
}

fn arbitrary_vec<'a, T: Arbitrary<'a> + Debug, const N: usize>(
u: &mut Unstructured<'a>,
) -> Result<Vec<T, N>> {
let mut vec = Vec::new();
u.arbitrary_loop(Some(0), Some(N.try_into().unwrap()), |u| {
vec.push(u.arbitrary()?).unwrap();
Ok(ControlFlow::Continue(()))
})?;
Ok(vec)
}

fn arbitrary_str<const N: usize>(u: &mut Unstructured<'_>) -> Result<String<N>> {
let n = usize::arbitrary(u)?.min(N);
match core::str::from_utf8(u.peek_bytes(n).ok_or(Error::NotEnoughData)?) {
Ok(s) => {
u.bytes(n)?;
Ok(s.try_into().unwrap())
}
Err(e) => {
let i = e.valid_up_to();
let valid = u.bytes(i)?;
let s = unsafe { core::str::from_utf8_unchecked(valid) };
Ok(s.try_into().unwrap())
}
}
}

fn arbitrary_option<'a, T, F>(u: &mut Unstructured<'a>, f: F) -> Result<Option<T>>
where
F: FnOnce(&mut Unstructured<'a>) -> Result<T>,
{
if bool::arbitrary(u)? {
f(u).map(Some)
} else {
Ok(None)
}
}

fn arbitrary_key(u: &mut Unstructured<'_>) -> Result<EcdhEsHkdf256PublicKey> {
let x = arbitrary_bytes(u)?;
let y = arbitrary_bytes(u)?;
Ok(EcdhEsHkdf256PublicKey { x, y })
}
1 change: 1 addition & 0 deletions src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub use ctap1::Authenticator as Ctap1Authenticator;
pub use ctap2::Authenticator as Ctap2Authenticator;

#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
// clippy says (2022-02-26): large size difference
// - first is 88 bytes
// - second is 10456 bytes
Expand Down
Loading

0 comments on commit da1666b

Please sign in to comment.