Compare commits

...

4 Commits

779
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -3,3 +3,6 @@
members = [
"crates/*",
]
[patch.crates-io]
serde = { path = "../serde/serde" }

@ -1,6 +1,6 @@
#![feature(associated_const_equality, async_fn_in_trait)]
#![allow(clippy::missing_errors_doc, incomplete_features)]
pub mod credential;
pub mod discovery;
pub mod mediation;
// #![feature(associated_const_equality, async_fn_in_trait)]
// #![allow(clippy::missing_errors_doc, incomplete_features)]
//
// pub mod credential;
// pub mod discovery;
// pub mod mediation;

@ -8,9 +8,10 @@ edition = "2021"
[dependencies]
fido-common = { path = "../fido-common" }
bounded-integer = { version = "0.5.3", features = ["types", "std"] }
bounded-vec = "0.7.1"
coset = "0.3.3"
serde = { version = "1.0", features = ["derive"], optional = true }
typed-builder = { version = "0.14.0", default-features = false }
serde_with = { path = "../../../serde_with/serde_with", optional = true }
[features]
serde = ["dep:serde", "bounded-vec/serde", "fido-common/serde"]
serde = ["dep:serde", "dep:serde_with", "fido-common/serde", "bounded-integer/serde1"]

@ -0,0 +1,260 @@
#![feature(let_chains)]
extern crate ctap2_proto;
use std::collections::BTreeMap;
use ctap2_proto::authenticator::Transport;
// Recommended
use ctap2_proto::prelude::*;
use ctap2_proto::{
authenticator,
prelude::{
client_pin::AuthProtocolVersion,
device::{Aaguid, Version},
},
};
use fido_common::credential::public_key::{RelyingPartyEntity, UserEntity};
use fido_common::{
attestation,
credential::{self, public_key::Parameters},
registry::algorithms::Signature,
};
const AAGUID: Aaguid = Aaguid::from(*b"\xed\xeeZow\xdc\xf5\xadZ\xe3\xd7\xb8\xf5\xf6\xf7\xd7");
const VERSION: usize = 1;
const SUPPORTED_AUTH_PROTOCOL_VERSIONS: [AuthProtocolVersion; 1] = [AuthProtocolVersion::Two];
const SUPPORTED_PUBLIC_KEY_ALGORITHMS: [Parameters; 1] = [Parameters {
credential_type: credential::Type::PublicKey,
algorithm: Signature::Ed25519EddsaSha512Raw,
}];
struct VirtualAuthenticator<R, W>
where
R: std::io::Read + Send,
W: std::io::Write + Send,
{
pin: Option<Vec<u8>>,
input: R,
output: W,
}
enum Presence {
Present,
NotPresent,
}
impl<R, W> VirtualAuthenticator<R, W>
where
R: std::io::Read + Send,
W: std::io::Write + Send,
{
fn verify_presence(reader: &mut R, writer: &mut W) -> Result<Presence, std::io::Error> {
write!(writer, "Allow this operation? [Y/n]:")?;
let mut input = <[u8; 1]>::default();
reader.read_exact(&mut input)?;
writeln!(writer)?;
if input[0] == b'Y' {
Ok(Presence::Present)
} else {
Ok(Presence::NotPresent)
}
}
}
impl<R, W> Ctap2_2Authenticator for VirtualAuthenticator<R, W>
where
R: std::io::Read + Send,
W: std::io::Write + Send,
{
fn make_credential(&mut self, request: make::Request) -> Result<make::Response, make::Error> {
// If authenticator supports either pinUvAuthToken or clientPin features and the
// platform sends a zero length pinUvAuthParam:
if let Some(options) = self.get_info().options &&
(options.contains_key(&device::OptionId::PinUvAuthToken) || options.contains_key(&device::OptionId::ClientPin)) && request.pin_uv_auth_param.len() == 0
{
// Request evidence of user interaction in an authenticator-specific way (e.g., flash
// the LED light).
// If the user declines permission, or the operation times out, then end the operation
// by returning CTAP2_ERR_OPERATION_DENIED.
let Ok(Presence::Present) = Self::verify_presence(&mut self.input, &mut self.output) else {
return Err(make::Error::OperationDenied);
};
// If evidence of user interaction is provided in this step then return either
// CTAP2_ERR_PIN_NOT_SET if PIN is not set or CTAP2_ERR_PIN_INVALID if PIN has been
// set.
if self.pin.is_some() {
return Err(make::Error::PinInvalid);
} else {
return Err(make::Error::PinNotSet);
}
}
// If the pinUvAuthParam parameter is present:
debug_assert!(request.pin_uv_auth_param.len() > 0);
// If the pinUvAuthProtocol parameters value is not supported, return
// CTAP1_ERR_INVALID_PARAMETER error.
// If the pinUvAuthProtocol parameter is absent, return
// CTAP2_ERR_MISSING_PARAMETER error.
let Some(auth_protocol_version) = request.pin_uv_auth_protocol_version else { return Err(make::Error::MissingParameter) };
if !SUPPORTED_AUTH_PROTOCOL_VERSIONS.contains(&auth_protocol_version) {
return Err(make::Error::InvalidParameter);
}
// Filter out any invalid or unsupported params, then take the first valid one.
// TODO: The following NOTE from the specs refers to the following loop, however
// this loop only iterates over the algorithms until a match is found.
//
// The only risk I can imagine is enumerating the supported algorithms of the
// authenticator, which doesn't seem that important to protect against.
//
// NOTE: This loop chooses the first occurrence of an algorithm identifier
// supported by this authenticator but always iterates over every
// element of pubKeyCredParams to validate them.
// Validate pubKeyCredParams with the following steps:
// 1. For each element of pubKeyCredParams:
let Some(public_key_algorithm) = request.public_key_credential_params.into_iter().filter(|params| {
// TODO: Ensure that invalid params are unrepresentable
// If the element is missing required members, including members that are mandatory
// only for the specific type, then return an error, for example
// CTAP2_ERR_INVALID_CBOR.
// TODO: What would it mean for a credential to have the wrong type?
// If the values of any known members have the wrong type then return an error, for
// example CTAP2_ERR_CBOR_UNEXPECTED_TYPE.
// If the element specifies an algorithm that is supported by the authenticator, and no
// algorithm has yet been chosen by this loop, then let the algorithm specified by the
// current element be the chosen algorithm.
return SUPPORTED_PUBLIC_KEY_ALGORITHMS.contains(params);
}).next() else {
// If the loop completes and no algorithm was chosen then return
// CTAP2_ERR_UNSUPPORTED_ALGORITHM.
return Err(make::Error::UnsupportedAlgorithm);
};
// No attestation
let format = attestation::FormatIdentifier::None;
// No enterprise attestation
let enterprise_attestation = None;
// If the "uv" option is absent, let the "uv" option be treated as being present
// with the value false. (This is the default)
let mut user_verification = false;
let mut discoverable_credential = false;
let mut user_presence = true;
let authenticator_data = authenticator::Data {
relying_party_id_hash: todo!(),
user_is_present: todo!(),
user_is_verified: todo!(),
signature_counter: todo!(),
attested_credential_data: todo!(),
extensions: None,
};
if let Some(req_options) = request.options {
// If the pinUvAuthParam is present, let the "uv" option be treated as being
// present with the value false.
if let Some(true) = req_options.get(&make::OptionKey::UserVerification) {}
}
Ok(make::Response {
format,
authenticator_data,
enterprise_attestation,
large_blob_key: None,
unsigned_extension_outputs: None,
})
}
fn get_assertion(request: get::Request) -> Result<get::Response, get::Error> {
todo!()
}
fn get_info(&self) -> device::Info {
device::Info {
versions: [Version::Fido2_1].into_iter().collect(),
extensions: None,
aaguid: AAGUID,
options: None,
max_message_size: None,
pin_uv_auth_protocols: Some(SUPPORTED_AUTH_PROTOCOL_VERSIONS.into()),
max_credential_count_in_list: None,
max_credential_id_length: None,
transports: Some([Transport::Internal].into_iter().collect()),
algorithms: Some(SUPPORTED_PUBLIC_KEY_ALGORITHMS.into()),
max_serialized_large_blob_array_size: None,
force_pin_change: Some(self.pin.is_none()),
min_pin_length: None,
firmware_version: Some(VERSION),
max_cred_blob_length: None,
max_rpids_for_set_min_pin_length: None,
preferred_platform_uv_attempts: None,
uv_modality: None,
certifications: None,
remaining_discoverable_credentials: None,
vendor_prototype_config_commands: None,
}
}
fn client_pin(request: client_pin::Request) -> Result<client_pin::Response, client_pin::Error> {
todo!()
}
fn reset() -> Result<(), reset::Error> {
todo!()
}
fn selection() -> Result<(), selection::Error> {
todo!()
}
}
fn main() {
let pin = None;
let authenticator = VirtualAuthenticator {
pin,
input: std::io::stdin(),
output: std::io::stdout(),
};
let client_data_hash = [0; 32];
let relying_party = RelyingPartyEntity {
id: "example.com".into(),
name: Some("Example Inc.".into()),
};
let user_entity = UserEntity {
id: [1u8; 64].into(),
name: Some("user@example.com".into()),
display_name: Some("Example User".into()),
};
// Get authenticator info
let info = authenticator.get_info();
// Make a new discoverable credential
let options = BTreeMap::from([
(make::OptionKey::UserVerification, true),
(make::OptionKey::Discoverable, true),
(make::OptionKey::UserPresence, false),
]);
let request = make::Request {
client_data_hash: &client_data_hash,
relying_party: &relying_party,
user: &user_entity,
public_key_credential_params: &SUPPORTED_PUBLIC_KEY_ALGORITHMS,
exclude_list: None,
extensions: None,
options: Some(&options),
pin_uv_auth_param: todo!(),
pin_uv_auth_protocol_version: todo!(),
enterprise_attestation: None,
};
authenticator.make_credential(request);
}

@ -1,9 +1,14 @@
use crate::{authenticator::client_pin::AuthProtocolVersion, extensions};
use crate::Sha256Hash;
use bounded_vec::BoundedVec;
use crate::{authenticator::client_pin::AuthProtocolVersion, extensions};
use fido_common::credential::public_key;
use std::{collections::BTreeMap, usize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_with::{serde_as, skip_serializing_none, Bytes};
#[derive(Debug)]
pub enum Error {
OperationDenied,
PinNotSet,
@ -21,63 +26,92 @@ pub enum Error {
/// > The following option keys are defined for use in
/// > [`assertion::get::Request`]'s `options` parameter.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionKey {
/// > user presence: Instructs the authenticator to require user consent
/// > to complete the operation.
UserPrecense,
#[serde(rename = "up")]
UserPresence,
/// > user verification: If true, instructs the authenticator to require
/// > a user-verifying gesture in order to complete the request.
/// > Examples of such gestures are fingerprint scan or a PIN.
#[serde(rename = "uv")]
UserVerification,
}
/// Request parameters for [`Ctap2Device::get_assertion`] operation.
#[derive(Clone, Copy)]
#[cfg_eval]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", serde_as, skip_serializing_none, derive(Serialize))]
pub struct Request<'a> {
/// > relying party identifier
#[serde(rename = 0x01)]
pub relying_party_id: &'a str,
/// > Hash of the serialized client data collected by the host.
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = 0x02))]
pub client_data_hash: &'a Sha256Hash,
/// > An array of [`public_key::Descriptor`] structures, each denoting a
/// > credential, as specified in `WebAuthn`... If this parameter is present
/// > the authenticator MUST only generate a assertion using one of the
/// > denoted credentials.
pub allow_list: Option<&'a BoundedVec<&'a public_key::Descriptor, 1, { usize::MAX }>>,
// Cannot be empty if present
#[serde(rename = 0x03)]
pub allow_list: Option<&'a Vec<&'a public_key::Descriptor>>,
/// > Parameters to influence authenticator operation. These parameters
/// > might be authenticator specific.
#[serde(rename = 0x04)]
pub extensions: Option<&'a BTreeMap<extensions::Identifier, &'a [u8]>>,
/// > Parameters to influence authenticator operation.
#[serde(rename = 0x05)]
pub options: Option<&'a BTreeMap<OptionKey, bool>>,
#[serde(rename = 0x06)]
pub pin_uv_auth_param: Option<&'a [u8]>,
/// > PIN/UV protocol version selected by platform.
#[serde(rename = 0x07)]
pub pin_uv_auth_protocol_version: Option<AuthProtocolVersion>,
}
/// Response structure for [`Ctap2Device::get_assertion`] operation.
#[cfg_eval]
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "serde",
serde_as,
skip_serializing_none,
derive(Deserialize)
)]
pub struct Response {
/// > PublicKeyCredentialDescriptor structure containing the credential
/// > identifier whose private key was used to generate the assertion.
#[serde(rename = 0x01)]
pub credential: public_key::Descriptor,
/// > The signed-over contextual bindings made by the authenticator, as
/// > specified in [WebAuthn].
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = 0x02))]
pub auth_data: Vec<u8>,
/// > The assertion signature produced by the authenticator, as
/// > specified in [WebAuthn].
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = 0x03))]
pub signature: Vec<u8>,
/// > [`public_key::UserEntity`] structure containing the user account
/// > information
#[serde(rename = 0x04)]
pub user: Option<public_key::UserEntity>,
/// > Total number of account credentials for the RP. Optional; defaults
/// > to one. This member is required when more than one credential is
/// > found for an RP, and the authenticator does not have a display or
/// > the UV & UP flags are false.
#[serde(rename = 0x05)]
pub number_of_credentials: Option<usize>,
/// > Indicates that a credential was selected by the user via
/// > interaction directly with the authenticator, and thus the platform
/// > does not need to confirm the credential.
#[serde(rename = 0x06)]
pub user_selected: Option<bool>,
/// > The contents of the associated `largeBlobKey` if present for the
/// > asserted credential, and if `largeBlobKey` was true in the
/// > extensions input.
#[cfg_attr(feature = "serde", serde_as(as = "Option<Bytes>"), serde(rename = 0x07))]
pub large_blob_key: Option<Vec<u8>>,
}

@ -1,6 +1,8 @@
use bounded_integer::BoundedUsize;
use std::collections::BTreeSet;
use bounded_integer::BoundedUsize;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum AuthProtocolVersion {
@ -8,6 +10,42 @@ pub enum AuthProtocolVersion {
Two,
}
// workaround until <https://github.com/serde-rs/serde/pull/2056> is merged
// PR: ( Integer/boolean tags for internally/adjacently tagged enums #2056 )
#[cfg(feature = "serde")]
impl Serialize for AuthProtocolVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(match self {
AuthProtocolVersion::One => 1,
AuthProtocolVersion::Two => 2,
})
}
}
// workaround until <https://github.com/serde-rs/serde/pull/2056> is merged
// PR: ( Integer/boolean tags for internally/adjacently tagged enums #2056 )
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for AuthProtocolVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de;
match u8::deserialize(deserializer)? {
1 => Ok(Self::One),
2 => Ok(Self::Two),
i => Err(de::Error::invalid_value(
de::Unexpected::Unsigned(i.into()),
&"1 or 2",
)),
}
}
}
pub enum Subcommand {
GetPinRetries,
GetKeyAgreement,

@ -1,4 +1,4 @@
use std::collections::{BTreeMap};
use std::collections::BTreeMap;
use super::client_pin::AuthProtocolVersion;

@ -3,8 +3,15 @@ use crate::{
extensions, Sha256Hash,
};
use fido_common::{attestation, credential::public_key};
use std::collections::{BTreeMap, HashMap};
use std::collections::BTreeMap;
use typed_builder::TypedBuilder;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_with::{serde_as, skip_serializing_none, Bytes};
#[derive(Debug)]
pub enum Error {
OperationDenied,
PinNotSet,
@ -24,36 +31,46 @@ pub enum Error {
/// > The following option keys are defined for use in
/// > `authenticatorMakeCredential`'s `options` parameter.
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionKey {
/// > Specifies whether this credential is to be discoverable or
/// > not.
#[cfg_attr(feature = "serde", serde(rename = "rk"))]
Discoverable,
/// > user presence: Instructs the authenticator to require user
/// > consent
/// > to complete the operation.
#[cfg_attr(feature = "serde", serde(rename = "up"))]
UserPresence,
/// > user verification: If true, instructs the authenticator to require a
/// > user-verifying gesture in order to complete the request. Examples of
/// > such gestures are fingerprint scan or a PIN.
#[cfg_attr(feature = "serde", serde(rename = "uv"))]
UserVerification,
}
#[cfg_eval]
/// Input parameters for [`Ctap2Device::make_credential`] operation.
#[derive(Clone, Copy)]
#[cfg_attr(feature = "serde", serde_as, skip_serializing_none, derive(Serialize))]
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct Request<'a> {
/// > Hash of the ClientData contextual binding specified by host.
#[cfg_attr(feature = "serde", serde(rename = 0x01), serde_as(as = "Bytes"))]
pub client_data_hash: &'a Sha256Hash,
/// > This PublicKeyCredentialRpEntity data structure describes a
/// > Relying Party with which the new public key credential will be
/// > associated.
#[cfg_attr(feature = "serde", serde(rename = 0x02))]
pub relying_party: &'a public_key::RelyingPartyEntity,
/// > ... describes the user account to which the new public key
/// > credential will be associated at the RP.
#[cfg_attr(feature = "serde", serde(rename = 0x03))]
pub user: &'a public_key::UserEntity,
/// > List of supported algorithms for credential generation, as
/// > specified in [WebAuthn]. The array is ordered from most preferred
/// > to least preferred and MUST NOT include duplicate entries.
#[cfg_attr(feature = "serde", serde(rename = 0x04))]
pub public_key_credential_params: &'a [public_key::Parameters], // TODO: BTreeSet? BTreeMap
// with preference as key?
/// > An array of PublicKeyCredentialDescriptor structures, as specified
@ -61,13 +78,27 @@ pub struct Request<'a> {
/// > authenticator already contains one of the credentials enumerated
/// > in this array. This allows RPs to limit the creation of multiple
/// > credentials for the same account on a single authenticator.
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x05))]
pub exclude_list: Option<&'a [&'a public_key::Descriptor]>,
/// > Parameters to influence authenticator operation, as specified in
/// > [WebAuthn]. These parameters might be authenticator specific.
pub extensions: Option<&'a HashMap<extensions::Identifier, Vec<u8>>>,
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x06))]
pub extensions: Option<&'a BTreeMap<extensions::Identifier, Vec<u8>>>,
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x07))]
pub options: Option<&'a BTreeMap<OptionKey, bool>>,
pub pin_uv_auth_param: &'a [u8],
#[builder(default, setter(strip_option))]
#[cfg_attr(
feature = "serde",
serde(rename = 0x08),
serde_as(as = "Option<Bytes>")
)]
pub pin_uv_auth_param: Option<&'a [u8]>,
/// > PIN/UV protocol version selected by platform.
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x09))]
pub pin_uv_auth_protocol_version: Option<client_pin::AuthProtocolVersion>,
/// > An authenticator supporting this enterprise attestation feature is
/// > enterprise attestation capable and signals its support via the `ep`
@ -81,21 +112,36 @@ pub struct Request<'a> {
/// > attestation batching may not apply to the results of this operation
/// > and the platform is requesting an enterprise attestation that includes
/// > uniquely identifying information.
#[builder(default, setter(strip_option))]
#[cfg_attr(feature = "serde", serde(rename = 0x0A))]
pub enterprise_attestation: Option<attestation::enterprise::Kind>,
}
#[cfg_eval]
#[cfg_attr(feature = "serde", serde_as, derive(Deserialize))]
#[derive(Debug)]
pub struct Response {
#[cfg_attr(feature = "serde", serde(rename = 0x01))]
pub format: fido_common::attestation::FormatIdentifier,
#[cfg_attr(feature = "serde", serde(rename = 0x02))]
pub authenticator_data: authenticator::Data,
#[cfg_attr(feature = "serde", serde(rename = 0x03))]
pub attestation_statement: Option<attestation::Statement>,
/// > Indicates whether an enterprise attestation was returned for this
/// > credential. If `epAtt` is absent or present and set to false, then an
/// > enterprise attestation was not returned. If `epAtt` is present and set
/// > to true, then an enterprise attestation was returned.
#[cfg_attr(feature = "serde", serde(rename = 0x04))]
pub enterprise_attestation: Option<bool>,
/// > Contains the `largeBlobKey` for the credential, if requested with the
/// > `largeBlobKey` extension.
#[cfg_attr(feature = "serde", serde(rename = 0x05))]
pub large_blob_key: Option<Vec<u8>>,
/// > A map, keyed by extension identifiers, to unsigned outputs of
/// > extensions, if any.
#[cfg_attr(feature = "serde", serde(rename = 0x06))]
pub unsigned_extension_outputs: Option<BTreeMap<extensions::Identifier, Vec<u8>>>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
struct AttestationStatement {}

@ -1,15 +1,18 @@
use crate::authenticator::client_pin::AuthProtocolVersion;
use crate::authenticator::Transport;
use crate::extensions;
use bounded_vec::BoundedVec;
use fido_common::credential::public_key;
use fido_common::registry;
use fido_common::{attestation, registry};
use std::collections::{BTreeMap, BTreeSet};
use std::num::NonZeroUsize;
use std::usize;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// A usize with a minimum value of N
#[derive(PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UsizeN<const N: usize>(bounded_integer::BoundedUsize<N, { usize::MAX }>);
/// > data type byte string and identifying the authenticator model, i.e.
@ -18,10 +21,15 @@ pub struct UsizeN<const N: usize>(bounded_integer::BoundedUsize<N, { usize::MAX
pub type Aaguid = [u8; 16];
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Version {
#[cfg_attr(feature = "serde", serde(rename = "FIDO_2_1"))]
Fido2_1,
#[cfg_attr(feature = "serde", serde(rename = "FIDO_2_0"))]
Fido2_0,
#[cfg_attr(feature = "serde", serde(rename = "FIDO_2_1_PRE"))]
Fido2_1Preview,
#[cfg_attr(feature = "serde", serde(rename = "U2F_V2"))]
U2fV2,
}
@ -32,6 +40,8 @@ pub enum Version {
/// > `AAGUID` via appropriate methods. Platforms may alter their behaviour
/// > based on these hints such as selecting a PIN protocol or `credProtect`
/// > level.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Certification {
/// > The [FIPS140-2] Cryptographic-Module-Validation-Program overall
/// > certification level.
@ -44,6 +54,8 @@ pub enum Certification {
}
#[repr(usize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FipsCryptoValidation2Level {
Level1 = 1,
Level2 = 2,
@ -52,6 +64,8 @@ pub enum FipsCryptoValidation2Level {
}
#[repr(usize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FipsCryptoValidation3Level {
Level1 = 1,
Level2 = 2,
@ -60,6 +74,8 @@ pub enum FipsCryptoValidation3Level {
}
#[repr(usize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FipsPhysicalCryptoValidation2Level {
Level1 = 1,
Level2 = 2,
@ -68,6 +84,8 @@ pub enum FipsPhysicalCryptoValidation2Level {
}
#[repr(usize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FipsPhysicalCryptoValidation3Level {
Level1 = 1,
Level2 = 2,
@ -78,6 +96,8 @@ pub enum FipsPhysicalCryptoValidation3Level {
/// > Common Criteria Evaluation Assurance Level [CC1V3-1R5]. This is a integer
/// > from 1 to 7. The intermediate-plus levels are not represented.
#[repr(usize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum CommonCriterialLevel {
EAL1 = 1,
EAL2 = 2,
@ -92,6 +112,8 @@ pub enum CommonCriterialLevel {
/// > numbered levels are mapped to the odd numbers, with the plus levels mapped
/// > to the even numbers e.g., level 3+ is mapped to 6.
#[repr(usize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FidoLevel {
L1 = 1,
L1Plus = 2,
@ -103,13 +125,16 @@ pub enum FidoLevel {
/// These options describe properties of a CTAP device.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionId {
/// > Indicates that the device is attached to the client and therefore
/// > cant be removed and used on another client.
#[cfg_attr(feature = "serde", serde(rename = "plat"))]
PlatformDevice,
/// > Specifies whether this authenticator can create discoverable
/// > credentials, and therefore can satisfy `authenticatorGetAssertion`
/// > requests with the `allowList` parameter omitted.
#[cfg_attr(feature = "serde", serde(rename = "rk"))]
DiscoverableCredentials,
/// > ClientPIN feature support:
/// > If present and set to true, it indicates that the device is capable of
@ -120,8 +145,10 @@ pub enum OptionId {
/// >
/// > If absent, it indicates that the device is not capable of accepting a
/// > PIN from the client.
#[cfg_attr(feature = "serde", serde(rename = "clientPin"))]
ClientPin,
/// > Indicates that the device is capable of testing user presence.
#[cfg_attr(feature = "serde", serde(rename = "up"))]
UserPresence,
/// > Indicates that the authenticator supports a built-in user verification
/// > method. For example, devices with UI, biometrics fall into this
@ -146,7 +173,9 @@ pub enum OptionId {
/// > If a device is capable of both built-in user verification and Client
/// > PIN, the authenticator will return both the "uv" and the "clientPin"
/// > option ids.
#[cfg_attr(feature = "serde", serde(rename = "uv"))]
UserVerification,
#[cfg_attr(feature = "serde", serde(rename = "pinUvAuthToken"))]
PinUvAuthToken,
/// > If this noMcGaPermissionsWithClientPin is:
/// > - present and set to true: A `pinUvAuthToken` obtained via
@ -164,17 +193,31 @@ pub enum OptionId {
/// >
/// > Note: `noMcGaPermissionsWithClientPin` MUST only be present if the
/// > `clientPin` option ID is present.
#[cfg_attr(feature = "serde", serde(rename = "noMcGaPermissionsWithClientPin"))]
NoMcGaPermissionsWithClientPin,
#[cfg_attr(feature = "serde", serde(rename = "largeBlobs"))]
LargeBlobs,
#[cfg_attr(feature = "serde", serde(rename = "ep"))]
EnterpriseAttestation,
#[cfg_attr(feature = "serde", serde(rename = "bioEnroll"))]
BiometricEnroll,
#[cfg_attr(feature = "serde", serde(rename = "userVerificationMgmtPreview"))]
UvManagementPreview,
#[cfg_attr(feature = "serde", serde(rename = "uvBioEnroll"))]
UvBiometricEnroll,
#[cfg_attr(feature = "serde", serde(rename = "authnrCfg"))]
AuthenticatorConfig,
#[cfg_attr(feature = "serde", serde(rename = "uvAcfg"))]
UvAuthenticatorConfig,
#[cfg_attr(feature = "serde", serde(rename = "credMgmt"))]
CredentialManagement,
#[cfg_attr(feature = "serde", serde(rename = "credentialMgmtPreview"))]
CredentialManagementPreview,
#[cfg_attr(feature = "serde", serde(rename = "setMinPINLength"))]
SetMinPinLength,
#[cfg_attr(feature = "serde", serde(rename = "makeCredUvNotRqd"))]
MakeCredentialUvNotRequired,
#[cfg_attr(feature = "serde", serde(rename = "alwaysUv"))]
AlwaysRequireUv,
}
@ -182,53 +225,72 @@ pub enum OptionId {
/// > list of its supported protocol versions and extensions, its AAGUID, and
/// > other aspects of its overall capabilities. Platforms should use this
/// > information to tailor their command parameters choices.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Info {
/// > List of supported CTAP versions.
#[cfg_attr(feature = "serde", serde(rename = 0x01))]
pub versions: BTreeSet<Version>,
/// > List of supported extensions.
#[cfg_attr(feature = "serde", serde(rename = 0x02))]
pub extensions: Option<BTreeSet<extensions::Identifier>>,
/// > The claimed AAGUID.
#[cfg_attr(feature = "serde", serde(rename = 0x03))]
pub aaguid: Aaguid,
/// > List of supported options.
#[cfg_attr(feature = "serde", serde(rename = 0x04))]
pub options: Option<BTreeMap<OptionId, bool>>,
/// > Maximum message size supported by the authenticator.
#[cfg_attr(feature = "serde", serde(rename = 0x05))]
pub max_message_size: Option<usize>,
/// > List of supported PIN/UV auth protocols in order of decreasing
/// > authenticator preference. MUST NOT contain duplicate values...
pub pin_uv_auth_protocols: Option<BoundedVec<AuthProtocolVersion, 1, { usize::MAX }>>,
// Cannot be empty if present
#[cfg_attr(feature = "serde", serde(rename = 0x06))]
pub pin_uv_auth_protocols: Option<Vec<AuthProtocolVersion>>,
/// > Maximum number of credentials supported in credentialID list at a time
/// > by the authenticator.
#[cfg_attr(feature = "serde", serde(rename = 0x07))]
pub max_credential_count_in_list: Option<NonZeroUsize>,
/// > Maximum Credential ID Length supported by the authenticator.
#[cfg_attr(feature = "serde", serde(rename = 0x08))]
pub max_credential_id_length: Option<NonZeroUsize>,
/// > List of supported transports.
#[cfg_attr(feature = "serde", serde(rename = 0x09))]
pub transports: Option<BTreeSet<Transport>>,
/// > List of supported algorithms for credential generation... The array is
/// > ordered from most preferred to least preferred and MUST NOT include
/// > duplicate entries...
pub algorithms: Option<BoundedVec<public_key::Parameters, 1, { usize::MAX }>>,
#[cfg_attr(feature = "serde", serde(rename = 0x0A))]
// Cannot be empty if present
pub algorithms: Option<Vec<public_key::Parameters>>,
/// > The maximum size, in bytes, of the serialized large-blob array that
/// > this authenticator can store. If the `authenticatorLargeBlobs` command
/// > is supported, this MUST be specified. Otherwise it MUST NOT be.
#[cfg_attr(feature = "serde", serde(rename = 0x0B))]
pub max_serialized_large_blob_array_size: Option<UsizeN<1024>>,
/// > If this member is:
/// > - present and set to true: `getPinToken` and
/// > `getPinUvAuthTokenUsingPinWithPermissions` will return errors until
/// > after a successful PIN Change.
/// > - present and set to false, or absent: no PIN Change is required.
#[cfg_attr(feature = "serde", serde(rename = 0x0C))]
pub force_pin_change: Option<bool>,
/// > This specifies the current minimum PIN length, in Unicode code points,
/// > the authenticator enforces for ClientPIN. This is applicable for
/// > ClientPIN only: the minPINLength member MUST be absent if the
/// > clientPin option ID is absent; it MUST be present if the authenticator
/// > supports authenticatorClientPIN.
#[cfg_attr(feature = "serde", serde(rename = 0x0D))]
pub min_pin_length: Option<usize>,
/// > Indicates the firmware version of the authenticator model identified
/// > by AAGUID.
#[cfg_attr(feature = "serde", serde(rename = 0x0E))]
pub firmware_version: Option<usize>,
/// > Maximum credBlob length in bytes supported by the authenticator. Must
/// > be present if, and only if, credBlob is included in the supported
/// > extensions list.
#[cfg_attr(feature = "serde", serde(rename = 0x0F))]
pub max_cred_blob_length: Option<UsizeN<32>>,
/// > This specifies the max number of RP IDs that authenticator can set via
/// > `setMinPINLength` subcommand. This is in addition to pre-configured
@ -236,12 +298,14 @@ pub struct Info {
/// > adding additional RP IDs, its value is 0. This MUST ONLY be present
/// > if, and only if, the authenticator supports the `setMinPINLength`
/// > subcommand.
#[cfg_attr(feature = "serde", serde(rename = 0x10))]
pub max_rpids_for_set_min_pin_length: Option<usize>,
/// > This specifies the preferred number of invocations of the
/// > `getPinUvAuthTokenUsingUvWithPermissions` subCommand the platform may
/// > attempt before falling back to the
/// > `getPinUvAuthTokenUsingPinWithPermissions` subCommand or displaying an
/// > error.
#[cfg_attr(feature = "serde", serde(rename = 0x11))]
pub preferred_platform_uv_attempts: Option<NonZeroUsize>,
/// > This specifies the user verification modality supported by the
/// > authenticator via `authenticatorClientPIN`'s
@ -249,8 +313,10 @@ pub struct Info {
/// > to help the platform construct user dialogs. If `clientPin`
/// > is supported it MUST NOT be included in the bit-flags, as `clientPIN`
/// > is not a built-in user verification method.
#[cfg_attr(feature = "serde", serde(rename = 0x12))]
pub uv_modality: Option<BTreeSet<registry::UserVerify>>,
/// > This specifies a list of authenticator certifications.
#[cfg_attr(feature = "serde", serde(rename = 0x13))]
pub certifications: Option<BTreeSet<Certification>>,
/// > If this member is present it indicates the estimated number of
/// > additional discoverable credentials that can be stored. If this value
@ -268,10 +334,22 @@ pub struct Info {
/// > user.id that match an existing discoverable credential and thus
/// > overwrite it, but this value should be set assuming that will not
/// > happen.
#[cfg_attr(feature = "serde", serde(rename = 0x14))]
pub remaining_discoverable_credentials: Option<usize>,
/// > If present the authenticator supports the `authenticatorConfig`
/// > `vendorPrototype` subcommand, and its value is a list of
/// > `authenticatorConfig` `vendorCommandId` values supported, which MAY be
/// > empty.
#[cfg_attr(feature = "serde", serde(rename = 0x15))]
pub vendor_prototype_config_commands: Option<BTreeSet<usize>>,
/// > List of supported attestation formats.
#[cfg_attr(feature = "serde", serde(rename = 0x16))]
pub attestation_formats: Option<BTreeSet<attestation::FormatIdentifier>>,
/// > If present the number of internal User Verification operations since
/// > the last pin entry including all failed attempts.
#[cfg_attr(feature = "serde", serde(rename = 0x17))]
pub uv_count_since_last_pin_entry: Option<usize>,
/// > If present the authenticator requires a 10 second touch for reset.
#[cfg_attr(feature = "serde", serde(rename = 0x18))]
pub long_touch_for_reset: Option<bool>,
}

@ -8,4 +8,3 @@ pub mod credential;
pub mod device;
pub mod reset;
pub mod selection;

@ -1,4 +1,5 @@
/// Possible errors for the [`Ctap2Device::reset`] command.
#[derive(Debug)]
pub enum Error {
/// Returned if the `reset` operation is disabled for the transport used or
/// if user precense is explicitly denied.

@ -1,3 +1,5 @@
#![feature(cfg_eval)]
pub mod prelude {
pub use crate::{
authenticator::{
@ -6,16 +8,15 @@ pub mod prelude {
credential::{make, management},
device, reset, selection,
},
Ctap2_2Authenticator,
Command, Ctap2_2Authenticator,
};
pub use fido_common::Sha256Hash;
pub use fido_common::*;
}
use prelude::*;
pub mod authenticator;
pub mod extensions;
use prelude::*;
/// Defines the raw CTAP operations
pub trait Ctap2_2Authenticator {
#[allow(clippy::missing_errors_doc)]
@ -28,7 +29,7 @@ pub trait Ctap2_2Authenticator {
/// > authentication as well as user consent to a given transaction, using a
/// > previously generated credential that is bound to the authenticator and
/// > relying party identifier.
fn get_assertion(request: get::Request) -> Result<get::Response, get::Error>;
fn get_assertion(&mut self, request: get::Request) -> Result<get::Response, get::Error>;
/// > Using this method, platforms can request that the authenticator report
/// > a list of its supported protocol versions and extensions, its AAGUID,
@ -47,7 +48,7 @@ pub trait Ctap2_2Authenticator {
#[allow(clippy::missing_errors_doc)]
/// > This method is used by the client to reset an authenticator back to a
/// > factory default state.
fn reset() -> Result<(), reset::Error>;
fn reset(&mut self) -> Result<(), reset::Error>;
// fn bio_enrollment(
// request: bio_enrollment::Request,
@ -73,3 +74,20 @@ pub trait Ctap2_2Authenticator {
// fn authenticator_config(request: config::Request) -> Result<(),
// config::Error>;
}
#[repr(u8)]
pub enum Command {
AuthenticatorMakeCredential = 0x01,
AuthenticatorGetAssertion = 0x02,
AuthenticatorGetNextAssertion = 0x08,
AuthenticatorGetInfo = 0x04,
AuthenticatorClientPin = 0x06,
AuthenticatorReset = 0x07,
AuthenticatorBioEnrollment = 0x09,
AuthenticatorCredentialManagement = 0x0A,
AuthenticatorSelection = 0x0B,
AuthenticatorLargeBlobs = 0x0C,
AuthenticatorConfig = 0x0D,
PrototypeAuthenticatorBioEnrollment = 0x40,
PrototypeAuthenticatorCredentialmanagement = 0x41,
}

@ -6,9 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bounded-vec = { version = "0.7.1", features = ["serde"] }
ciborium = { version = "0.2.1", default-features = false, optional = true }
coset = { version = "0.3.4", default-features = false }
serde = { version = "1", features = ["derive"], optional = true }
serde_with = { path = "../../../serde_with/serde_with", optional = true }
bitflags = { version = "2.2.1", default-features = false, optional = true }
[features]
serde = ["dep:serde", "bounded-vec/serde"]
serde = ["dep:serde", "dep:serde_with", "dep:bitflags", "dep:ciborium"]

@ -1,5 +1,11 @@
#[cfg(feature = "serde")]
use crate::credential::public_key::algorithm;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_with::{serde_as, Bytes};
pub mod enterprise;
/// > Attestation statement formats are identified by a string, called an
/// > attestation statement format identifier, chosen by the author of the
@ -35,7 +41,7 @@ use serde::{Deserialize, Serialize};
/// > of registered `WebAuthn` Extensions is maintained in the IANA "WebAuthn
/// > Attestation Statement Format Identifiers" registry
/// > [IANA-WebAuthn-Registries] established by [RFC8809].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FormatIdentifier {
/// > The "packed" attestation statement format is a WebAuthn-optimized
@ -70,41 +76,93 @@ pub enum FormatIdentifier {
None,
}
pub mod enterprise {
#[repr(usize)]
#[derive(Clone, Copy)]
pub enum Kind {
/// > In this case, an enterprise attestation capable authenticator, on
/// > which enterprise attestation is enabled, upon receiving the
/// > enterpriseAttestation parameter with a value of 1 (or 2, see Note
/// > below) on a authenticatorMakeCredential command, will provide
/// > enterprise attestation to a non-updateable pre-configured RP ID
/// > list, as identified by the enterprise and provided to the
/// > authenticator vendor, which is "burned into" the authenticator by
/// > the vendor.
/// > If enterprise attestation is requested for any RP ID other than
/// > the pre-configured RP ID(s), the attestation returned along with
/// > the new credential is a regular privacy-preserving attestation,
/// > i.e., NOT an enterprise attestation.
VendorFacilitated = 1,
/// > In this case, an enterprise attestation capable authenticator on
/// > which enterprise attestation is enabled, upon receiving the
/// > enterpriseAttestation parameter with a value of 2 on a
/// > authenticatorMakeCredential command, will return an enterprise
/// > attestation. The platform is enterprise-managed and has already
/// > performed the necessary vetting of the RP ID.
PlatformManaged = 2,
}
#[cfg_eval]
#[derive(Debug)]
#[cfg_attr(
feature = "serde",
serde_as,
derive(Serialize, Deserialize),
// TODO: Workaround until serde can use integer keys as tag, since "fmt" is CBOR key 0x01.
serde(untagged)
)]
pub enum Statement {
#[cfg_attr(feature = "serde", serde(rename = "packed"))]
Packed {
#[cfg_attr(feature = "serde", serde(rename = "alg", with = "algorithm"))]
algorithm: coset::iana::Algorithm,
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"), serde(rename = "sig"))]
signature: Vec<u8>,
#[cfg_attr(feature = "serde", serde_as(as = "Vec<Bytes>"), serde(rename = "x5c"))]
attestation_certificate_chain: Vec<Vec<u8>>, // TODO: Parse X.509 certs
},
Unregistered {
identifier: String,
data: Vec<u8>,
},
}
/// > Attested credential data is a variable-length byte array added to the
/// > authenticator data when generating an attestation object for a given
/// > credential.
#[derive(Debug)]
pub struct CredentialData {
/// > The AAGUID of the authenticator.
pub aaguid: [u8; 16],
/// The ID of the credential.
pub id: Vec<u8>,
pub id: Vec<u8>, // TODO: Make no_std compatible with the credential id length?
/// The public key of the credential.
pub public_key: coset::CoseKey,
}
impl TryFrom<&[u8]> for CredentialData {
// TODO: Custom error type?
type Error = coset::CoseError;
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
// aaguid: 16 Bytes
// SAFETY: Validate that data.len >= 16 for aaguid bytes
if data.len() < 16 {
return Err(coset::CoseError::DecodeFailed(ciborium::de::Error::Io(
coset::EndOfFile,
)));
}
let (&aaguid, data) = data.split_array_ref::<16>();
// credentialIdLengh: 2 Bytes
// > Byte length L of credentialId, 16-bit unsigned big-endian integer. Value
// > MUST be ≤ 1023.
// SAFETY: Validate that there are 2 bytes for u16
if data.len() < 2 {
return Err(coset::CoseError::DecodeFailed(ciborium::de::Error::Io(
coset::EndOfFile,
)));
}
let (&credential_id_length, mut data) = data.split_array_ref::<2>();
let credential_id_length = u16::from_be_bytes(credential_id_length);
if credential_id_length > 1023 {
return Err(coset::CoseError::UnexpectedItem(
"a credentialIdLength (L) of greater than 1023",
"a 16-bit unsigned big-endian integer less than or equal to 1023",
));
}
// credentialId: L (credential_id_length) Bytes
let credential_id: &[u8] = data.take(..credential_id_length as usize)
.ok_or(coset::CoseError::DecodeFailed(ciborium::de::Error::Io(
coset::EndOfFile,
)))?;
Ok(Self { aaguid, id: credential_id.to_vec(), public_key: Default::default() })
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for CredentialData {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
let data = Vec::<u8>::deserialize(deserializer)?;
// TODO: Improve error handling
CredentialData::try_from(data.as_slice()).map_err(serde::de::Error::custom)
}
}

@ -0,0 +1,28 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[repr(usize)]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Kind {
/// > In this case, an enterprise attestation capable authenticator, on
/// > which enterprise attestation is enabled, upon receiving the
/// > enterpriseAttestation parameter with a value of 1 (or 2, see Note
/// > below) on a authenticatorMakeCredential command, will provide
/// > enterprise attestation to a non-updateable pre-configured RP ID
/// > list, as identified by the enterprise and provided to the
/// > authenticator vendor, which is "burned into" the authenticator by
/// > the vendor.
/// > If enterprise attestation is requested for any RP ID other than
/// > the pre-configured RP ID(s), the attestation returned along with
/// > the new credential is a regular privacy-preserving attestation,
/// > i.e., NOT an enterprise attestation.
VendorFacilitated = 1,
/// > In this case, an enterprise attestation capable authenticator on
/// > which enterprise attestation is enabled, upon receiving the
/// > enterpriseAttestation parameter with a value of 2 on a
/// > authenticatorMakeCredential command, will return an enterprise
/// > attestation. The platform is enterprise-managed and has already
/// > performed the necessary vetting of the RP ID.
PlatformManaged = 2,
}

@ -1,7 +1,27 @@
use crate::{attestation, extensions, Sha256Hash};
use crate::{
attestation,
credential::{BackupEligibility, BackupState},
extensions, Sha256Hash,
};
use std::collections::BTreeMap;
pub enum Flags {}
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use bitflags::bitflags;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserPresence {
Present,
NotPresent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserVerification {
Verified,
NotVerified,
}
/// > The authenticator data structure encodes contextual bindings made by the
/// > authenticator. These bindings are controlled by the authenticator itself,
@ -13,37 +33,132 @@ pub enum Flags {}
/// > software, connected to the client over a secure channel. In both cases,
/// > the Relying Party receives the authenticator data in the same format, and
/// > uses its knowledge of the authenticator to make trust decisions.
#[derive(Debug)]
pub struct Data {
/// > SHA-256 hash of the RP ID the credential is scoped to.
pub relying_party_id_hash: Sha256Hash,
pub user_is_present: bool,
pub user_is_verified: bool,
pub user_presence: UserPresence,
pub user_verification: UserVerification,
pub backup_eligibility: BackupEligibility,
pub backup_state: BackupState,
pub signature_counter: u32,
pub attested_credential_data: Option<attestation::CredentialData>,
pub extensions: Option<BTreeMap<extensions::Identifier, Vec<u8>>>,
}
impl Data {
fn try_from(value: &[u8]) -> Option<Self> {
// 32 bytes: RP id hash
let rp_id = value.get(0..32)?.as_ref();
//
let flags = value.get(32)?;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
// > Flags (bit 0 is the least significant bit):
struct DataFlags: u8 {
// > Bit 0: User Present (UP) result.
// > 1 means the user is present.
const USER_PRESENCE = 0b1 << 0;
// > Bit 2: User Verified (UV) result.
// > 1 means the user is verified.
const USER_VERIFIED = 0b1 << 2;
// > Bit 3: Backup Eligibility (BE).
// > 1 means the public key credential source is backup eligible.
const BACKUP_ELIGIBLE = 0b1 << 3;
// > Bit 4: Backup State (BS).
// > 1 means the public key credential source is currently backed up.
const BACKUP_STATE = 0b1 << 4;
// > Bit 6: Attested credential data included (AT).
// > Indicates whether the authenticator added attested credential data.
const ATTESTED_CREDENTIAL_DATA = 0b1 << 6;
// > Bit 7: Extension data included (ED).
// > Indicates if the authenticator data has extensions.
const EXTENSION_DATA_INCLUDED = 0b1 << 7;
}
}
None
impl DataFlags {
fn user_presence(&self) -> UserPresence {
if self.contains(DataFlags::USER_PRESENCE) {
UserPresence::Present
} else {
UserPresence::NotPresent
}
}
fn user_verification(&self) -> UserVerification {
if self.contains(DataFlags::USER_VERIFIED) {
UserVerification::Verified
} else {
UserVerification::NotVerified
}
}
fn backup_eligibility(&self) -> BackupEligibility {
if self.contains(DataFlags::BACKUP_ELIGIBLE) {
BackupEligibility::Eligible
} else {
BackupEligibility::Ineligible
}
}
impl TryFrom<&[u8]> for Data {
type Error = ();
fn backup_state(&self) -> BackupState {
if self.contains(DataFlags::BACKUP_STATE) {
BackupState::BackedUp
} else {
BackupState::NotBackedUp
}
}
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::try_from(value).ok_or(())
fn has_attested_credential_data(&self) -> bool {
self.contains(DataFlags::ATTESTED_CREDENTIAL_DATA)
}
}
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
impl<'de> Deserialize<'de> for Data {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de;
let data = Vec::<u8>::deserialize(deserializer)?;
// The authenticator data structure is a byte array of 37 bytes or more
if data.len() < 37 {
return Err(de::Error::invalid_length(data.len(), &"at least 37 bytes"));
}
// SAFETY: split_array_ref panics if const param is out of bounds for slice.
// data.len() guard protects against out of bounds indicies.
// rpIdHash: 32 Bytes
// > SHA-256 hash of the RP ID the credential is scoped to.
let (&relying_party_id_hash, data): (&Sha256Hash, _) = data.split_array_ref::<32>();
// flags: 1 Byte
let (&[flags], data): (&[u8; 1], _) = data.split_array_ref::<1>();
let flags = DataFlags::from_bits_truncate(flags);
// signCount: 4 Bytes
// > Signature counter, 32-bit unsigned big-endian integer.
let (&counter_be_bytes, data) = data.split_array_ref::<4>();
let signature_counter = u32::from_be_bytes(counter_be_bytes);
let attested_credential_data: Option<attestation::CredentialData> =
if flags.has_attested_credential_data() {
Some(attestation::CredentialData::try_from(data).map_err(de::Error::custom)?)
} else {
None
};
Ok(Self {
relying_party_id_hash,
user_presence: flags.user_presence(),
user_verification: flags.user_verification(),
backup_eligibility: flags.backup_eligibility(),
backup_state: flags.backup_state(),
signature_counter,
attested_credential_data,
extensions: None,
})
}
}
/// > Authenticators may implement various transports for communicating with
/// > clients. This enumeration defines hints as to how clients might

@ -16,3 +16,15 @@ pub enum Type {
#[cfg_attr(feature = "serde", serde(rename = "public-key"))]
PublicKey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackupState {
BackedUp = 0b0,
NotBackedUp = 0b1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackupEligibility {
Eligible,
Ineligible,
}

@ -1,13 +1,39 @@
use crate::registry::algorithms;
use crate::{authenticator::Transport, credential};
use bounded_vec::BoundedVec;
use std::collections::BTreeSet;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_with::{serde_as, skip_serializing_none, Bytes};
#[cfg(feature = "serde")]
pub(crate) mod algorithm {
use coset::iana::{Algorithm, EnumI64};
use serde::{Deserialize, Serialize};
pub(crate) fn serialize<S>(algorithm: &Algorithm, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let i = algorithm.to_i64();
i64::serialize(&i, serializer)
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Algorithm, D::Error>
where
D: serde::Deserializer<'de>,
{
let i = i64::deserialize(deserializer)?;
coset::iana::Algorithm::from_i64(i).ok_or(serde::de::Error::invalid_value(
serde::de::Unexpected::Signed(i),
&"an IANA-registered COSE algorithm value",
))
}
}
/// > This dictionary is used to supply additional parameters when
/// > creating a new credential.
#[cfg_eval]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Parameters {
@ -20,13 +46,41 @@ pub struct Parameters {
/// > algorithm with which the newly generated credential will
/// > be used, and thus also the type of asymmetric key pair to
/// > be generated, e.g., RSA or Elliptic Curve.
#[cfg_attr(feature = "serde", serde(rename = "alg"))]
pub algorithm: algorithms::Signature,
#[cfg_attr(feature = "serde", serde(rename = "alg", with = "algorithm"))]
pub algorithm: coset::iana::Algorithm,
}
// #[cfg(feature = "serde")]
// pub(crate) fn deserialize_algorithm<'de, D>(deserializer: D) ->
// Result<coset::iana::Algorithm, D::Error> where
// D: serde::Deserializer<'de>,
// {
// use serde::de;
//
// let i = i64::deserialize(deserializer)?;
// coset::iana::Algorithm::from_i64(i).ok_or(de::Error::invalid_value(
// de::Unexpected::Signed(i),
// &"an IANA-registered COSE algorithm value",
// ))
// }
//
// #[cfg(feature = "serde")]
// pub(crate) fn serialize_algorithm<S: serde::Serializer>(
// algorithm: &coset::iana::Algorithm,
// s: S,
// ) -> Result<S::Ok, S::Error> {
// s.serialize_i64(algorithm.to_i64())
// }
/// > This dictionary identifies a specific public key credential.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
#[cfg_eval]
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "serde",
serde_as,
skip_serializing_none,
derive(Serialize, Deserialize)
)]
pub struct Descriptor {
/// > This member contains the type of the public key credential
/// > the caller is referring to.
@ -35,7 +89,9 @@ pub struct Descriptor {
/// > A probabilistically-unique byte sequence identifying a
/// > public key credential source and its authentication
/// > assertions.
pub id: BoundedVec<u8, 16, 1023>,
// Bounds: [16, 1023] bytes
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"))]
pub id: Vec<u8>,
/// > This... member contains a hint as to how the client might
/// > communicate with the managing authenticator of the public
/// > key credential the caller is referring to.
@ -46,9 +102,12 @@ pub struct Descriptor {
/// > account to which the new public key credential will be associated at
/// > the RP.
/// Due to deprecation, the `icon` URL is omitted. See <https://github.com/w3c/webauthn/pull/1337/>.
#[derive(Debug)]
#[cfg_eval]
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "serde",
serde_as,
skip_serializing_none,
derive(Serialize, Deserialize),
serde(rename_all = "camelCase")
)]
@ -64,7 +123,9 @@ pub struct UserEntity {
//
// WebAuthn says that "The user handle MUST NOT be empty." To maximimize compatibility, the
// definition from the CTAP specs is used.
pub id: BoundedVec<u8, 0, 64>,
// Bounds: [0, 64] bytes
#[cfg_attr(feature = "serde", serde_as(as = "Bytes"))]
pub id: Vec<u8>,
/// > a human-palatable identifier for a user account. It is intended
/// > only for display, i.e., aiding the user in determining the
/// > difference between user accounts with similar displayNames. For

@ -1,3 +1,6 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// > Extensions are identified by a string, called an extension identifier,
/// > chosen by the extension author.
/// >
@ -18,22 +21,39 @@
/// > Extensions that may exist in multiple versions should take care to include
/// > a version in their identifier. In effect, different versions are thus
/// > treated as different extensions, e.g., `myCompany_extension_01`
#[derive(PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Identifier {
#[cfg_attr(feature = "serde", serde(rename = "appid"))]
AppId,
#[cfg_attr(feature = "serde", serde(rename = "txAuthSimple"))]
TransactionAuthSimple,
#[cfg_attr(feature = "serde", serde(rename = "txAuthGeneric"))]
TransactionAuthGeneric,
#[cfg_attr(feature = "serde", serde(rename = "authnSel"))]
AuthenticationSelection,
#[cfg_attr(feature = "serde", serde(rename = "exts"))]
Extensions,
#[cfg_attr(feature = "serde", serde(rename = "uvi"))]
UserVerificationIndex,
#[cfg_attr(feature = "serde", serde(rename = "loc"))]
Location,
#[cfg_attr(feature = "serde", serde(rename = "uvm"))]
UserVerificationMethod,
#[cfg_attr(feature = "serde", serde(rename = "credProtect"))]
CredentialProtection,
#[cfg_attr(feature = "serde", serde(rename = "credBlob"))]
CredentialBlob,
#[cfg_attr(feature = "serde", serde(rename = "largeBlobKey"))]
LargeBlobKey,
#[cfg_attr(feature = "serde", serde(rename = "minPinLength"))]
MinPinLength,
#[cfg_attr(feature = "serde", serde(rename = "hmac-secret"))]
HmacSecret,
#[cfg_attr(feature = "serde", serde(rename = "appidExclude"))]
AppIdExclude,
#[cfg_attr(feature = "serde", serde(rename = "credProps"))]
CredentialProperties,
#[cfg_attr(feature = "serde", serde(rename = "largeBlob"))]
LargeBlob,
}

@ -1,5 +1,7 @@
pub mod authenticator;
#![feature(cfg_eval, split_array, slice_take)]
pub mod attestation;
pub mod authenticator;
pub mod credential;
pub mod extensions;
pub mod registry;

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
/// > representation (in quotes), which is used in the authoritative metadata
/// > for FIDO authenticators.
#[repr(u32)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UserVerify {
/// > This flag MUST be set if the authenticator is able to confirm user

@ -0,0 +1,18 @@
[package]
name = "hid-ctap2-proto"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ciborium = "0.2.1"
ciborium-io = "0.2.1"
ctap2-proto = { path = "../ctap2-proto", features = ["serde"] }
hidapi = { version = "2.3.2" }
ctaphid = { version = "0.3.1", default_features = false }
serde = "1.0.163"
[dev-dependencies]
rand = "0.8.5"
env_logger = "0.10.0"

@ -0,0 +1,109 @@
use ctap2_proto::Command;
use device::{DeviceInfo, HidDevice};
pub struct HidAuthenticator<D: ctaphid::HidDevice = HidDevice>(ctaphid::Device<D>);
impl TryFrom<hidapi::HidDevice> for HidAuthenticator<HidDevice> {
type Error = ctaphid::error::Error;
fn try_from(device: hidapi::HidDevice) -> Result<Self, Self::Error> {
let info = device
.get_device_info()
.map_err(|e| ctaphid::error::RequestError::PacketSendingFailed(Box::new(e)))?;
let device = ctaphid::Device::new(HidDevice(device), DeviceInfo(info))?;
Ok(Self(device))
}
}
mod device {
//! Provides wrapper types for `hidapi` device types that implement required
//! `ctaphid` interfaces.
use std::borrow::Cow;
pub struct HidDevice(pub hidapi::HidDevice);
#[derive(Debug)]
pub struct DeviceInfo(pub hidapi::DeviceInfo);
impl ctaphid::HidDevice for HidDevice {
type Info = DeviceInfo;
fn send(&self, data: &[u8]) -> Result<(), ctaphid::error::RequestError> {
self.0
.write(data)
.map_err(|e| ctaphid::error::RequestError::PacketSendingFailed(e.into()))?;
Ok(())
}
fn receive<'a>(
&self,
buffer: &'a mut [u8],
timeout: Option<std::time::Duration>,
) -> Result<&'a [u8], ctaphid::error::ResponseError> {
let timeout_millis: i32 = match timeout {
Some(timeout) => timeout.as_millis() as i32,
None => -1,
};
let count = self
.0
.read_timeout(buffer, timeout_millis)
.map_err(|e| match e {
hidapi::HidError::HidApiError { message } => {
ctaphid::error::ResponseError::PacketReceivingFailed(message.into())
}
_ => todo!(),
})?;
Ok(&buffer[..count])
}
}
impl ctaphid::HidDeviceInfo for DeviceInfo {
fn vendor_id(&self) -> u16 {
self.0.vendor_id()
}
fn product_id(&self) -> u16 {
self.0.product_id()
}
fn path(&self) -> std::borrow::Cow<'_, str> {
let path = self
.0
.path()
.to_str()
.expect("Device path must be valid UTF-8.");
Cow::from(path)
}
}
}
impl<D: ctaphid::HidDevice> HidAuthenticator<D> {
fn send_raw(&self, command: Command, bytes: &[u8]) -> Result<Vec<u8>, ctaphid::error::Error> {
self.0.ctap2(command as u8, bytes)
}
pub fn send<Req, Res>(&self, command: Command, req: Req) -> Result<Res, ctaphid::error::Error>
where
Req: serde::Serialize,
Res: for<'de> serde::Deserialize<'de>,
{
let mut data: Vec<u8> = Vec::new();
ciborium::ser::into_writer(&req, &mut data).map_err(|e| match e {
ciborium::ser::Error::Io(_) => ctaphid::error::RequestError::IncompleteWrite,
ciborium::ser::Error::Value(desc) => {
ctaphid::error::RequestError::PacketSendingFailed(desc.into())
}
})?;
let response = self.send_raw(command, &data)?;
match ciborium::de::from_reader(response.as_slice()) {
Ok(response) => Ok(response),
// TODO: Improve error handling
Err(e) => {
println!("ERROR: {e}");
todo!()
}
}
}
}

@ -0,0 +1,152 @@
#![cfg_attr(test, feature(split_array, lazy_cell))]
use ctap2_proto::{prelude::*, Ctap2_2Authenticator};
use hid::HidAuthenticator;
pub mod hid;
impl<D: ctaphid::HidDevice> Ctap2_2Authenticator for HidAuthenticator<D> {
fn make_credential(&mut self, request: make::Request) -> Result<make::Response, make::Error> {
Ok(self
.send(Command::AuthenticatorMakeCredential, request)
.unwrap()) // TODO: Properly parse/convert errors
}
fn get_assertion(&mut self, request: get::Request) -> Result<get::Response, get::Error> {
Ok(self
.send(Command::AuthenticatorGetAssertion, request)
.unwrap())
}
fn get_info(&self) -> device::Info {
self.send(Command::AuthenticatorGetInfo, ()).unwrap()
}
fn client_pin(
_request: client_pin::Request,
) -> Result<client_pin::Response, client_pin::Error> {
todo!()
}
fn reset(&mut self) -> Result<(), reset::Error> {
let _: () = self.send(Command::AuthenticatorReset, ()).unwrap();
Ok(())
}
fn selection() -> Result<(), ctap2_proto::authenticator::selection::Error> {
todo!()
}
}
#[cfg(test)]
mod tests {
extern crate hidapi;
use std::sync::{LazyLock, Mutex};
use crate::hid::HidAuthenticator;
use ctap2_proto::prelude::{credential::public_key, *};
use rand::{distributions, Rng};
static AUTHENTICATOR: LazyLock<Mutex<Option<HidAuthenticator>>> =
LazyLock::new(|| Mutex::new(get_authenticator()));
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
fn get_authenticator() -> Option<HidAuthenticator> {
let hidapi = hidapi::HidApi::new().unwrap();
for info in hidapi.device_list() {
let Ok(device) = info.open_device(&hidapi) else {
continue;
};
let Ok(authenticator) = device.try_into() else { continue };
return Some(authenticator);
}
None
}
// #[test]
fn reset() {
init();
let mut guard = AUTHENTICATOR.lock().unwrap();
let authenticator = guard.as_mut().unwrap();
authenticator.reset().unwrap();
}
#[test]
fn test_get_info() {
init();
let guard = AUTHENTICATOR.lock().unwrap();
let authenticator = guard.as_ref().unwrap();
let info = authenticator.get_info();
println!("deserialized: {:#?}", info);
}
#[test]
fn make_credential() {
init();
let mut guard = AUTHENTICATOR.lock().unwrap();
let authenticator = guard.as_mut().unwrap();
let info = authenticator.get_info();
let client_data_hash: Vec<u8> = rand::thread_rng()
.sample_iter(&distributions::Standard)
.take(32)
.collect();
let user_id = rand::thread_rng()
.sample_iter(&distributions::Standard)
.take(32)
.collect();
let rp = public_key::RelyingPartyEntity {
id: "com.example".to_string(),
name: Some("Example Inc.".into()),
};
let user = public_key::UserEntity {
id: user_id,
name: Some("example_user".to_string()),
display_name: Some("Example User".to_string()),
};
let pub_key_params = info.algorithms.unwrap();
let options = [(make::OptionKey::Discoverable, true)].into();
let req = make::Request::builder()
.client_data_hash(&client_data_hash.split_array_ref::<32>().0)
.relying_party(&rp)
.user(&user)
.public_key_credential_params(pub_key_params.as_slice())
.options(&options)
.build();
println!("request: {req:#?}");
let response = authenticator.make_credential(req);
println!("response: {response:#?}");
let req = get::Request {
relying_party_id: &rp.id,
client_data_hash: client_data_hash.as_slice().try_into().unwrap(),
allow_list: None,
extensions: None,
options: None,
pin_uv_auth_param: None,
pin_uv_auth_protocol_version: None,
};
println!("request: {req:#?}");
let response = authenticator.get_assertion(req);
println!("response: {response:#?}");
}
}

@ -32,18 +32,3 @@ pub struct Data<const TYPE: DataType> {
/// > absence indicates that the client doesnt support token binding.
pub token_binding: Option<token::Binding>,
}
#[cfg(feature = "serde")]
impl<const TYPE: DataType> Serialize for Data<TYPE> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// Keys are: "type", "challenge", "origin", "topOrigin", "crossOrigin"
const LEN: usize = 5;
let mut map = serializer.serialize_map(Some(LEN))?;
// map.serialize_entry("type", value)
todo!()
}
}

@ -1,34 +1,34 @@
#![feature(async_fn_in_trait, adt_const_params, associated_const_equality)]
#![allow(incomplete_features, clippy::unused_async, clippy::doc_markdown)]
pub mod attestation;
pub mod authenticator;
pub mod client;
pub mod public_key;
pub mod token;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
/// > A WebAuthn Relying Party may require user verification for some of its
/// > operations but not for others, and may use this type to express its needs.
pub enum UserVerificationRequirement {
/// > The Relying Party requires user verification for the operation and
/// > will fail the overall ceremony if the response does not have the UV
/// > flag set. The client MUST return an error if user verification cannot
/// > be performed.
#[cfg_attr(feature = "serde", serde(rename = "required"))]
Required,
/// > The Relying Party prefers user verification for the operation if
/// > possible, but will not fail the operation if the response does not
/// > have the UV flag set.
#[cfg_attr(feature = "serde", serde(rename = "preferred"))]
Preferred,
/// > The Relying Party does not want user verification employed during the
/// > operation (e.g., in the interest of minimizing disruption to the user
/// > interaction flow).
#[cfg_attr(feature = "serde", serde(rename = "discouraged"))]
Discouraged,
}
// #![feature(async_fn_in_trait, adt_const_params, associated_const_equality)]
// #![allow(incomplete_features, clippy::unused_async, clippy::doc_markdown)]
//
// pub mod attestation;
// pub mod authenticator;
// pub mod client;
// pub mod public_key;
// pub mod token;
//
// #[cfg(feature = "serde")]
// use serde::{Deserialize, Serialize};
//
// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// #[derive(Debug, Clone, Copy)]
// > A WebAuthn Relying Party may require user verification for some of its
// > operations but not for others, and may use this type to express its needs.
// pub enum UserVerificationRequirement {
// > The Relying Party requires user verification for the operation and
// > will fail the overall ceremony if the response does not have the UV
// > flag set. The client MUST return an error if user verification cannot
// > be performed.
// #[cfg_attr(feature = "serde", serde(rename = "required"))]
// Required,
// > The Relying Party prefers user verification for the operation if
// > possible, but will not fail the operation if the response does not
// > have the UV flag set.
// #[cfg_attr(feature = "serde", serde(rename = "preferred"))]
// Preferred,
// > The Relying Party does not want user verification employed during the
// > operation (e.g., in the interest of minimizing disruption to the user
// > interaction flow).
// #[cfg_attr(feature = "serde", serde(rename = "discouraged"))]
// Discouraged,
// }

Loading…
Cancel
Save