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