#![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 where R: std::io::Read + Send, W: std::io::Write + Send, { pin: Option>, input: R, output: W, } enum Presence { Present, NotPresent, } impl VirtualAuthenticator where R: std::io::Read + Send, W: std::io::Write + Send, { fn verify_presence(reader: &mut R, writer: &mut W) -> Result { 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 Ctap2_2Authenticator for VirtualAuthenticator where R: std::io::Read + Send, W: std::io::Write + Send, { fn make_credential(&mut self, request: make::Request) -> Result { // 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 parameter’s 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 { 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 { 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); }