Skip to main content

soft_fido2/
error.rs

1//! Error types for CTAP operations
2
3#[cfg(feature = "std")]
4use std::fmt;
5
6#[cfg(not(feature = "std"))]
7use core::fmt;
8
9use alloc::string::String;
10
11/// Error type for CTAP operations
12#[non_exhaustive]
13#[derive(Debug, Clone, PartialEq)]
14pub enum Error {
15    /// The given operation was successful
16    Success,
17    /// The given value already exists
18    DoesAlreadyExist,
19    /// The requested value doesn't exist
20    DoesNotExist,
21    /// Credentials can't be inserted into the key-store
22    KeyStoreFull,
23    /// The client ran out of memory
24    OutOfMemory,
25    /// The operation timed out
26    Timeout,
27    /// Unspecified operation
28    Other,
29    /// Initialization failed
30    InitializationFailed,
31    /// Invalid callback result
32    InvalidCallbackResult,
33    /// CBOR command failed
34    CborCommandFailed(i32),
35    /// Invalid client data hash (must be 32 bytes)
36    InvalidClientDataHash,
37    /// No credentials exist for the requested operation
38    ///
39    /// Returned when:
40    /// - Attempting to enumerate credentials for an RP with no credentials
41    /// - Attempting to delete a non-existent credential
42    NoCredentials,
43    /// PIN/UV authentication required but not provided
44    PinAuthRequired,
45    /// PIN/UV auth token has insufficient permissions
46    ///
47    /// The token may not have the required permission bit set,
48    /// or may have the wrong permissions RP ID.
49    UnauthorizedPermission,
50    /// Invalid RP ID hash
51    ///
52    /// RP ID hash must be exactly 32 bytes (SHA-256 output).
53    InvalidRpIdHash,
54    /// PIN/UV auth token has expired
55    PinTokenExpired,
56    /// Invalid subcommand for credential management
57    InvalidSubcommand,
58    /// CTAP error with status code
59    CtapError(u8),
60    /// IO error (from transport operations)
61    IoError(String),
62    /// Invalid PIN length (must be 4-63 characters)
63    InvalidPinLength,
64}
65
66impl fmt::Display for Error {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self {
69            Error::Success => write!(f, "Success"),
70            Error::DoesAlreadyExist => write!(f, "Value already exists"),
71            Error::DoesNotExist => write!(f, "Value does not exist"),
72            Error::KeyStoreFull => write!(f, "Key store is full"),
73            Error::OutOfMemory => write!(f, "Out of memory"),
74            Error::Timeout => write!(f, "Operation timed out"),
75            Error::Other => write!(f, "Unspecified error"),
76            Error::InitializationFailed => write!(f, "Initialization failed"),
77            Error::InvalidCallbackResult => write!(f, "Invalid callback result"),
78            Error::CborCommandFailed(code) => {
79                write!(f, "CBOR command failed with code {}", code)
80            }
81            Error::InvalidClientDataHash => {
82                write!(f, "Invalid client data hash (must be 32 bytes)")
83            }
84            Error::NoCredentials => write!(f, "No credentials found"),
85            Error::PinAuthRequired => write!(f, "PIN/UV authentication required"),
86            Error::UnauthorizedPermission => write!(f, "Insufficient permissions"),
87            Error::InvalidRpIdHash => write!(f, "Invalid RP ID hash (must be 32 bytes)"),
88            Error::PinTokenExpired => write!(f, "PIN/UV auth token expired"),
89            Error::InvalidSubcommand => write!(f, "Invalid subcommand"),
90            Error::CtapError(code) => write!(f, "CTAP error: 0x{:02X}", code),
91            Error::IoError(msg) => write!(f, "IO error: {}", msg),
92            Error::InvalidPinLength => write!(f, "Invalid PIN length (must be 4-63 characters)"),
93        }
94    }
95}
96
97#[cfg(feature = "std")]
98impl std::error::Error for Error {}
99
100impl From<i32> for Error {
101    fn from(value: i32) -> Self {
102        match value {
103            0 => Error::Success,
104            -1 => Error::DoesAlreadyExist,
105            -2 => Error::DoesNotExist,
106            -3 => Error::KeyStoreFull,
107            -4 => Error::OutOfMemory,
108            -5 => Error::Timeout,
109            -6 => Error::Other,
110            _ => Error::CborCommandFailed(value),
111        }
112    }
113}
114
115impl From<soft_fido2_ctap::StatusCode> for Error {
116    fn from(status: soft_fido2_ctap::StatusCode) -> Self {
117        use soft_fido2_ctap::StatusCode;
118
119        match status {
120            StatusCode::Success => Error::Success,
121            StatusCode::InvalidCommand => Error::CtapError(0x01),
122            StatusCode::InvalidParameter => Error::CtapError(0x02),
123            StatusCode::InvalidLength => Error::CtapError(0x03),
124            StatusCode::InvalidSeq => Error::CtapError(0x04),
125            StatusCode::Timeout => Error::Timeout,
126            StatusCode::ChannelBusy => Error::CtapError(0x06),
127            StatusCode::LockRequired => Error::CtapError(0x0A),
128            StatusCode::InvalidChannel => Error::CtapError(0x0B),
129            StatusCode::CborUnexpectedType => Error::CtapError(0x11),
130            StatusCode::InvalidCbor => Error::CtapError(0x12),
131            StatusCode::MissingParameter => Error::CtapError(0x14),
132            StatusCode::LimitExceeded => Error::CtapError(0x15),
133            StatusCode::UnsupportedExtension => Error::CtapError(0x16),
134            StatusCode::CredentialExcluded => Error::CtapError(0x19),
135            StatusCode::Processing => Error::CtapError(0x21),
136            StatusCode::InvalidCredential => Error::CtapError(0x22),
137            StatusCode::UserActionPending => Error::CtapError(0x23),
138            StatusCode::OperationPending => Error::CtapError(0x24),
139            StatusCode::NoOperations => Error::CtapError(0x25),
140            StatusCode::UnsupportedAlgorithm => Error::CtapError(0x26),
141            StatusCode::OperationDenied => Error::CtapError(0x27),
142            StatusCode::KeyStoreFull => Error::KeyStoreFull,
143            StatusCode::NotBusy => Error::CtapError(0x29),
144            StatusCode::NoOperationPending => Error::CtapError(0x2A),
145            StatusCode::UnsupportedOption => Error::CtapError(0x2B),
146            StatusCode::InvalidOption => Error::CtapError(0x2C),
147            StatusCode::KeepaliveCancel => Error::CtapError(0x2D),
148            StatusCode::NoCredentials => Error::NoCredentials,
149            StatusCode::UserActionTimeout => Error::Timeout,
150            StatusCode::NotAllowed => Error::CtapError(0x30),
151            StatusCode::PinInvalid => Error::CtapError(0x31),
152            StatusCode::PinBlocked => Error::CtapError(0x32),
153            StatusCode::PinAuthInvalid => Error::CtapError(0x33),
154            StatusCode::PinAuthBlocked => Error::CtapError(0x34),
155            StatusCode::PinNotSet => Error::CtapError(0x35),
156            StatusCode::PinRequired => Error::CtapError(0x36),
157            StatusCode::PinPolicyViolation => Error::CtapError(0x37),
158            StatusCode::PinTokenExpired => Error::CtapError(0x38),
159            StatusCode::RequestTooLarge => Error::CtapError(0x39),
160            StatusCode::ActionTimeout => Error::Timeout,
161            StatusCode::UpRequired => Error::CtapError(0x3A),
162            StatusCode::UvBlocked => Error::CtapError(0x3C),
163            StatusCode::IntegrityFailure => Error::CtapError(0x3D),
164            StatusCode::InvalidSubcommand => Error::CtapError(0x3E),
165            StatusCode::UvInvalid => Error::CtapError(0x3F),
166            StatusCode::UnauthorizedPermission => Error::CtapError(0x40),
167            StatusCode::PuatRequired => Error::CtapError(0x41),
168            StatusCode::Other => Error::Other,
169            _ => Error::Other,
170        }
171    }
172}
173
174impl From<Error> for soft_fido2_ctap::StatusCode {
175    fn from(error: Error) -> Self {
176        use soft_fido2_ctap::StatusCode;
177
178        match error {
179            Error::Success => StatusCode::Success,
180            Error::DoesNotExist => StatusCode::NoCredentials,
181            Error::KeyStoreFull => StatusCode::KeyStoreFull,
182            Error::Timeout => StatusCode::Timeout,
183            Error::Other => StatusCode::Other,
184            Error::CtapError(code) => {
185                // Map back to StatusCode
186                match code {
187                    0x01 => StatusCode::InvalidCommand,
188                    0x02 => StatusCode::InvalidParameter,
189                    0x03 => StatusCode::InvalidLength,
190                    0x04 => StatusCode::InvalidSeq,
191                    0x06 => StatusCode::ChannelBusy,
192                    0x0A => StatusCode::LockRequired,
193                    0x0B => StatusCode::InvalidChannel,
194                    0x11 => StatusCode::CborUnexpectedType,
195                    0x12 => StatusCode::InvalidCbor,
196                    0x14 => StatusCode::MissingParameter,
197                    0x15 => StatusCode::LimitExceeded,
198                    0x31 => StatusCode::PinInvalid,
199                    0x33 => StatusCode::PinAuthInvalid,
200                    0x35 => StatusCode::PinNotSet,
201                    0x36 => StatusCode::PinRequired,
202                    _ => StatusCode::Other,
203                }
204            }
205            Error::InvalidPinLength => StatusCode::PinPolicyViolation,
206            _ => StatusCode::Other,
207        }
208    }
209}
210
211// Conversion from IO errors
212#[cfg(feature = "std")]
213impl From<std::io::Error> for Error {
214    fn from(error: std::io::Error) -> Self {
215        Error::IoError(error.to_string())
216    }
217}
218
219impl Error {
220    /// Parse CTAP response and extract CBOR data
221    ///
222    /// CTAP responses follow the format: `[status_byte, ...cbor_data]`
223    /// - `0x00` = success, returns the CBOR data
224    /// - `!0x00` = error, converts status byte to Error
225    ///
226    /// This is the single source of truth for CTAP status code handling.
227    pub fn parse_ctap_response(data: &[u8]) -> Result<&[u8]> {
228        if data.is_empty() {
229            return Err(Error::Other);
230        }
231
232        let status_byte = data[0];
233        if status_byte == 0x00 {
234            // Success - return CBOR data (skip status byte)
235            Ok(&data[1..])
236        } else {
237            // Error - convert status byte to StatusCode, then to Error
238            Err(soft_fido2_ctap::StatusCode::from(status_byte).into())
239        }
240    }
241}
242
243/// Result type alias for common operations
244#[cfg(feature = "std")]
245pub type Result<T> = std::result::Result<T, Error>;
246
247#[cfg(not(feature = "std"))]
248pub type Result<T> = core::result::Result<T, Error>;