use crate::crypto::{CryptoError, PinUvAuthParam, PinUvAuthToken};
use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, PinError};
use crate::ctap2::commands::get_info::AuthenticatorInfo;
use crate::ctap2::server::UserVerificationRequirement;
use crate::errors::AuthenticatorError;
use crate::transport::errors::{ApduErrorStatus, HIDError};
use crate::transport::{FidoDevice, VirtualFidoDevice};
use serde_cbor::{error::Error as CborError, Value};
use serde_json as json;
use std::error::Error as StdErrorT;
use std::fmt;
pub mod authenticator_config;
pub mod bio_enrollment;
pub mod client_pin;
pub mod credential_management;
pub mod get_assertion;
pub mod get_info;
pub mod get_next_assertion;
pub mod get_version;
pub mod make_credentials;
pub mod reset;
pub mod selection;
#[derive(Debug)]
pub enum Retryable<T> {
Retry,
Error(T),
}
impl<T> Retryable<T> {
pub fn is_retry(&self) -> bool {
matches!(*self, Retryable::Retry)
}
pub fn is_error(&self) -> bool {
!self.is_retry()
}
}
impl<T> From<T> for Retryable<T> {
fn from(e: T) -> Self {
Retryable::Error(e)
}
}
pub trait RequestCtap1: fmt::Debug {
type Output: CtapResponse;
type AdditionalInfo;
fn ctap1_format(&self) -> Result<(Vec<u8>, Self::AdditionalInfo), HIDError>;
fn handle_response_ctap1<Dev: FidoDevice>(
&self,
dev: &mut Dev,
status: Result<(), ApduErrorStatus>,
input: &[u8],
add_info: &Self::AdditionalInfo,
) -> Result<Self::Output, Retryable<HIDError>>;
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
&self,
dev: &mut Dev,
) -> Result<Self::Output, HIDError>;
}
pub trait RequestCtap2: fmt::Debug {
type Output: CtapResponse;
fn command(&self) -> Command;
fn wire_format(&self) -> Result<Vec<u8>, HIDError>;
fn handle_response_ctap2<Dev: FidoDevice>(
&self,
dev: &mut Dev,
input: &[u8],
) -> Result<Self::Output, HIDError>;
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
&self,
dev: &mut Dev,
) -> Result<Self::Output, HIDError>;
}
pub trait CtapResponse: std::fmt::Debug + 'static {}
#[derive(Debug, Clone)]
pub enum PinUvAuthResult {
RequestIsCtap1,
DeviceIsCtap1,
NoAuthTypeSupported,
NoAuthRequired,
UsingInternalUv,
SuccessGetPinToken(PinUvAuthToken),
SuccessGetPinUvAuthTokenUsingUvWithPermissions(PinUvAuthToken),
SuccessGetPinUvAuthTokenUsingPinWithPermissions(PinUvAuthToken),
}
impl PinUvAuthResult {
pub(crate) fn get_pin_uv_auth_token(&self) -> Option<PinUvAuthToken> {
match self {
PinUvAuthResult::RequestIsCtap1
| PinUvAuthResult::DeviceIsCtap1
| PinUvAuthResult::NoAuthTypeSupported
| PinUvAuthResult::NoAuthRequired
| PinUvAuthResult::UsingInternalUv => None,
PinUvAuthResult::SuccessGetPinToken(token) => Some(token.clone()),
PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(token) => {
Some(token.clone())
}
PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(token) => {
Some(token.clone())
}
}
}
}
pub(crate) trait PinUvAuthCommand: RequestCtap2 {
fn set_pin_uv_auth_param(
&mut self,
pin_uv_auth_token: Option<PinUvAuthToken>,
) -> Result<(), AuthenticatorError>;
fn get_pin_uv_auth_param(&self) -> Option<&PinUvAuthParam>;
fn set_uv_option(&mut self, uv: Option<bool>);
fn get_rp_id(&self) -> Option<&String>;
fn hmac_requested(&self) -> bool;
fn can_skip_user_verification(
&mut self,
info: &AuthenticatorInfo,
uv_req: UserVerificationRequirement,
) -> bool;
}
pub(crate) fn repackage_pin_errors<D: FidoDevice>(
dev: &mut D,
error: HIDError,
) -> AuthenticatorError {
match error {
HIDError::Command(CommandError::StatusCode(StatusCode::PinInvalid, _)) => {
let cmd = GetPinRetries::new();
let resp = dev.send_cbor(&cmd).unwrap_or_default();
AuthenticatorError::PinError(PinError::InvalidPin(resp.pin_retries))
}
HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthBlocked, _)) => {
AuthenticatorError::PinError(PinError::PinAuthBlocked)
}
HIDError::Command(CommandError::StatusCode(StatusCode::PinBlocked, _)) => {
AuthenticatorError::PinError(PinError::PinBlocked)
}
HIDError::Command(CommandError::StatusCode(StatusCode::PinRequired, _)) => {
AuthenticatorError::PinError(PinError::PinRequired)
}
HIDError::Command(CommandError::StatusCode(StatusCode::PinNotSet, _)) => {
AuthenticatorError::PinError(PinError::PinNotSet)
}
HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _)) => {
AuthenticatorError::PinError(PinError::PinAuthInvalid)
}
HIDError::Command(CommandError::StatusCode(StatusCode::UvInvalid, _)) => {
let cmd = GetUvRetries::new();
let resp = dev.send_cbor(&cmd).unwrap_or_default();
AuthenticatorError::PinError(PinError::InvalidUv(resp.uv_retries))
}
HIDError::Command(CommandError::StatusCode(StatusCode::UvBlocked, _)) => {
AuthenticatorError::PinError(PinError::UvBlocked)
}
err => AuthenticatorError::HIDError(err),
}
}
#[repr(u8)]
#[derive(Debug, PartialEq, Clone)]
pub enum Command {
MakeCredentials = 0x01,
GetAssertion = 0x02,
GetInfo = 0x04,
ClientPin = 0x06,
Reset = 0x07,
GetNextAssertion = 0x08,
BioEnrollment = 0x09,
CredentialManagement = 0x0A,
Selection = 0x0B,
AuthenticatorConfig = 0x0D,
BioEnrollmentPreview = 0x40,
CredentialManagementPreview = 0x41,
}
#[derive(Debug)]
pub enum StatusCode {
OK,
InvalidCommand,
InvalidParameter,
InvalidLength,
InvalidSeq,
Timeout,
ChannelBusy,
LockRequired,
InvalidChannel,
CBORUnexpectedType,
InvalidCBOR,
MissingParameter,
LimitExceeded,
UnsupportedExtension,
CredentialExcluded,
Processing,
InvalidCredential,
UserActionPending,
OperationPending,
NoOperations,
UnsupportedAlgorithm,
OperationDenied,
KeyStoreFull,
NoOperationPending,
UnsupportedOption,
InvalidOption,
KeepaliveCancel,
NoCredentials,
UserActionTimeout,
NotAllowed,
PinInvalid,
PinBlocked,
PinAuthInvalid,
PinAuthBlocked,
PinNotSet,
PinRequired,
PinPolicyViolation,
PinTokenExpired,
RequestTooLarge,
ActionTimeout,
UpRequired,
UvBlocked,
IntegrityFailure,
InvalidSubcommand,
UvInvalid,
UnauthorizedPermission,
Other,
Unknown(u8),
}
impl StatusCode {
fn is_ok(&self) -> bool {
matches!(*self, StatusCode::OK)
}
fn device_busy(&self) -> bool {
matches!(*self, StatusCode::ChannelBusy)
}
}
impl From<u8> for StatusCode {
fn from(value: u8) -> StatusCode {
match value {
0x00 => StatusCode::OK,
0x01 => StatusCode::InvalidCommand,
0x02 => StatusCode::InvalidParameter,
0x03 => StatusCode::InvalidLength,
0x04 => StatusCode::InvalidSeq,
0x05 => StatusCode::Timeout,
0x06 => StatusCode::ChannelBusy,
0x0A => StatusCode::LockRequired,
0x0B => StatusCode::InvalidChannel,
0x11 => StatusCode::CBORUnexpectedType,
0x12 => StatusCode::InvalidCBOR,
0x14 => StatusCode::MissingParameter,
0x15 => StatusCode::LimitExceeded,
0x16 => StatusCode::UnsupportedExtension,
0x19 => StatusCode::CredentialExcluded,
0x21 => StatusCode::Processing,
0x22 => StatusCode::InvalidCredential,
0x23 => StatusCode::UserActionPending,
0x24 => StatusCode::OperationPending,
0x25 => StatusCode::NoOperations,
0x26 => StatusCode::UnsupportedAlgorithm,
0x27 => StatusCode::OperationDenied,
0x28 => StatusCode::KeyStoreFull,
0x2A => StatusCode::NoOperationPending,
0x2B => StatusCode::UnsupportedOption,
0x2C => StatusCode::InvalidOption,
0x2D => StatusCode::KeepaliveCancel,
0x2E => StatusCode::NoCredentials,
0x2f => StatusCode::UserActionTimeout,
0x30 => StatusCode::NotAllowed,
0x31 => StatusCode::PinInvalid,
0x32 => StatusCode::PinBlocked,
0x33 => StatusCode::PinAuthInvalid,
0x34 => StatusCode::PinAuthBlocked,
0x35 => StatusCode::PinNotSet,
0x36 => StatusCode::PinRequired,
0x37 => StatusCode::PinPolicyViolation,
0x38 => StatusCode::PinTokenExpired,
0x39 => StatusCode::RequestTooLarge,
0x3A => StatusCode::ActionTimeout,
0x3B => StatusCode::UpRequired,
0x3C => StatusCode::UvBlocked,
0x3D => StatusCode::IntegrityFailure,
0x3E => StatusCode::InvalidSubcommand,
0x3F => StatusCode::UvInvalid,
0x40 => StatusCode::UnauthorizedPermission,
0x7F => StatusCode::Other,
othr => StatusCode::Unknown(othr),
}
}
}
#[cfg(test)]
impl From<StatusCode> for u8 {
fn from(v: StatusCode) -> u8 {
match v {
StatusCode::OK => 0x00,
StatusCode::InvalidCommand => 0x01,
StatusCode::InvalidParameter => 0x02,
StatusCode::InvalidLength => 0x03,
StatusCode::InvalidSeq => 0x04,
StatusCode::Timeout => 0x05,
StatusCode::ChannelBusy => 0x06,
StatusCode::LockRequired => 0x0A,
StatusCode::InvalidChannel => 0x0B,
StatusCode::CBORUnexpectedType => 0x11,
StatusCode::InvalidCBOR => 0x12,
StatusCode::MissingParameter => 0x14,
StatusCode::LimitExceeded => 0x15,
StatusCode::UnsupportedExtension => 0x16,
StatusCode::CredentialExcluded => 0x19,
StatusCode::Processing => 0x21,
StatusCode::InvalidCredential => 0x22,
StatusCode::UserActionPending => 0x23,
StatusCode::OperationPending => 0x24,
StatusCode::NoOperations => 0x25,
StatusCode::UnsupportedAlgorithm => 0x26,
StatusCode::OperationDenied => 0x27,
StatusCode::KeyStoreFull => 0x28,
StatusCode::NoOperationPending => 0x2A,
StatusCode::UnsupportedOption => 0x2B,
StatusCode::InvalidOption => 0x2C,
StatusCode::KeepaliveCancel => 0x2D,
StatusCode::NoCredentials => 0x2E,
StatusCode::UserActionTimeout => 0x2f,
StatusCode::NotAllowed => 0x30,
StatusCode::PinInvalid => 0x31,
StatusCode::PinBlocked => 0x32,
StatusCode::PinAuthInvalid => 0x33,
StatusCode::PinAuthBlocked => 0x34,
StatusCode::PinNotSet => 0x35,
StatusCode::PinRequired => 0x36,
StatusCode::PinPolicyViolation => 0x37,
StatusCode::PinTokenExpired => 0x38,
StatusCode::RequestTooLarge => 0x39,
StatusCode::ActionTimeout => 0x3A,
StatusCode::UpRequired => 0x3B,
StatusCode::UvBlocked => 0x3C,
StatusCode::IntegrityFailure => 0x3D,
StatusCode::InvalidSubcommand => 0x3E,
StatusCode::UvInvalid => 0x3F,
StatusCode::UnauthorizedPermission => 0x40,
StatusCode::Other => 0x7F,
StatusCode::Unknown(othr) => othr,
}
}
}
#[derive(Debug)]
pub enum CommandError {
InputTooSmall,
MissingRequiredField(&'static str),
Deserializing(CborError),
Serializing(CborError),
StatusCode(StatusCode, Option<Value>),
Json(json::Error),
Crypto(CryptoError),
UnsupportedPinProtocol,
}
impl fmt::Display for CommandError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CommandError::InputTooSmall => write!(f, "CommandError: Input is too small"),
CommandError::MissingRequiredField(field) => {
write!(f, "CommandError: Missing required field {field}")
}
CommandError::Deserializing(ref e) => {
write!(f, "CommandError: Error while parsing: {e}")
}
CommandError::Serializing(ref e) => {
write!(f, "CommandError: Error while serializing: {e}")
}
CommandError::StatusCode(ref code, ref value) => {
write!(f, "CommandError: Unexpected code: {code:?} ({value:?})")
}
CommandError::Json(ref e) => write!(f, "CommandError: Json serializing error: {e}"),
CommandError::Crypto(ref e) => write!(f, "CommandError: Crypto error: {e:?}"),
CommandError::UnsupportedPinProtocol => {
write!(f, "CommandError: Pin protocol is not supported")
}
}
}
}
impl StdErrorT for CommandError {}