#[cfg(feature = "std")]
use std::fmt;
#[cfg(not(feature = "std"))]
use core::fmt;
use alloc::string::String;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
Success,
DoesAlreadyExist,
DoesNotExist,
KeyStoreFull,
OutOfMemory,
Timeout,
Other,
InitializationFailed,
InvalidCallbackResult,
CborCommandFailed(i32),
InvalidClientDataHash,
NoCredentials,
PinAuthRequired,
UnauthorizedPermission,
InvalidRpIdHash,
PinTokenExpired,
InvalidSubcommand,
CtapError(u8),
IoError(String),
InvalidPinLength,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Success => write!(f, "Success"),
Error::DoesAlreadyExist => write!(f, "Value already exists"),
Error::DoesNotExist => write!(f, "Value does not exist"),
Error::KeyStoreFull => write!(f, "Key store is full"),
Error::OutOfMemory => write!(f, "Out of memory"),
Error::Timeout => write!(f, "Operation timed out"),
Error::Other => write!(f, "Unspecified error"),
Error::InitializationFailed => write!(f, "Initialization failed"),
Error::InvalidCallbackResult => write!(f, "Invalid callback result"),
Error::CborCommandFailed(code) => {
write!(f, "CBOR command failed with code {}", code)
}
Error::InvalidClientDataHash => {
write!(f, "Invalid client data hash (must be 32 bytes)")
}
Error::NoCredentials => write!(f, "No credentials found"),
Error::PinAuthRequired => write!(f, "PIN/UV authentication required"),
Error::UnauthorizedPermission => write!(f, "Insufficient permissions"),
Error::InvalidRpIdHash => write!(f, "Invalid RP ID hash (must be 32 bytes)"),
Error::PinTokenExpired => write!(f, "PIN/UV auth token expired"),
Error::InvalidSubcommand => write!(f, "Invalid subcommand"),
Error::CtapError(code) => write!(f, "CTAP error: 0x{:02X}", code),
Error::IoError(msg) => write!(f, "IO error: {}", msg),
Error::InvalidPinLength => write!(f, "Invalid PIN length (must be 4-63 characters)"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl From<i32> for Error {
fn from(value: i32) -> Self {
match value {
0 => Error::Success,
-1 => Error::DoesAlreadyExist,
-2 => Error::DoesNotExist,
-3 => Error::KeyStoreFull,
-4 => Error::OutOfMemory,
-5 => Error::Timeout,
-6 => Error::Other,
_ => Error::CborCommandFailed(value),
}
}
}
impl From<soft_fido2_ctap::StatusCode> for Error {
fn from(status: soft_fido2_ctap::StatusCode) -> Self {
use soft_fido2_ctap::StatusCode;
match status {
StatusCode::Success => Error::Success,
StatusCode::Timeout | StatusCode::UserActionTimeout | StatusCode::ActionTimeout => {
Error::Timeout
}
StatusCode::KeyStoreFull => Error::KeyStoreFull,
StatusCode::NoCredentials => Error::NoCredentials,
StatusCode::Other => Error::Other,
_ => Error::CtapError(status.to_u8()),
}
}
}
impl From<Error> for soft_fido2_ctap::StatusCode {
fn from(error: Error) -> Self {
use soft_fido2_ctap::StatusCode;
match error {
Error::Success => StatusCode::Success,
Error::DoesNotExist | Error::NoCredentials => StatusCode::NoCredentials,
Error::KeyStoreFull => StatusCode::KeyStoreFull,
Error::Timeout => StatusCode::Timeout,
Error::Other => StatusCode::Other,
Error::CtapError(code) => StatusCode::from_u8(code),
Error::InvalidPinLength => StatusCode::PinPolicyViolation,
Error::PinAuthRequired => StatusCode::PuatRequired,
Error::UnauthorizedPermission => StatusCode::UnauthorizedPermission,
Error::InvalidRpIdHash => StatusCode::InvalidParameter,
Error::PinTokenExpired => StatusCode::PinAuthInvalid,
Error::InvalidSubcommand => StatusCode::InvalidSubcommand,
_ => StatusCode::Other,
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::IoError(error.to_string())
}
}
impl Error {
pub fn parse_ctap_response(data: &[u8]) -> Result<&[u8]> {
if data.is_empty() {
return Err(Error::Other);
}
let status_byte = data[0];
if status_byte == 0x00 {
Ok(&data[1..])
} else {
Err(soft_fido2_ctap::StatusCode::from(status_byte).into())
}
}
}
#[cfg(feature = "std")]
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(not(feature = "std"))]
pub type Result<T> = core::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
use soft_fido2_ctap::StatusCode;
#[test]
fn status_to_error_uses_the_status_registry() {
assert_eq!(
Error::from(StatusCode::PuatRequired),
Error::CtapError(0x36)
);
assert_eq!(Error::from(StatusCode::UpRequired), Error::CtapError(0x3b));
assert_eq!(
Error::from(StatusCode::UnauthorizedPermission),
Error::CtapError(0x40)
);
}
#[test]
fn ctap_error_round_trips_through_the_status_registry() {
assert_eq!(
StatusCode::from(Error::CtapError(0x36)),
StatusCode::PuatRequired
);
assert_eq!(
StatusCode::from(Error::CtapError(0x3b)),
StatusCode::UpRequired
);
assert_eq!(StatusCode::from(Error::CtapError(0x38)), StatusCode::Other);
assert_eq!(StatusCode::from(Error::CtapError(0x41)), StatusCode::Other);
}
}