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

2 years ago
#![feature(let_chains)]
extern crate ctap2_proto;
use std::collections::BTreeMap;
2 years ago
use ctap2_proto::authenticator::Transport;
2 years ago
// 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(),
};
2 years ago
let client_data_hash = [0; 32];
2 years ago
let relying_party = RelyingPartyEntity {
id: "example.com".into(),
name: Some("Example Inc.".into()),
};
let user_entity = UserEntity {
2 years ago
id: [1u8; 64].into(),
2 years ago
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
2 years ago
let options = BTreeMap::from([
2 years ago
(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),
2 years ago
pin_uv_auth_param: todo!(),
pin_uv_auth_protocol_version: todo!(),
2 years ago
enterprise_attestation: None,
};
authenticator.make_credential(request);
}