You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

261 lines
9.4 KiB
Rust

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#![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);
}