soft_fido2_ctap/
status.rs

1//! CTAP2 status codes
2//!
3//! Status codes defined in FIDO2 specification:
4//! <https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#error-responses>
5
6use core::fmt;
7
8/// CTAP2 status codes
9///
10/// These status codes are returned in CTAP responses to indicate success or various error conditions.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum StatusCode {
14    /// Successful completion of command
15    Success = 0x00,
16
17    /// Invalid command
18    InvalidCommand = 0x01,
19
20    /// Invalid parameter in request
21    InvalidParameter = 0x02,
22
23    /// Invalid message or item length
24    InvalidLength = 0x03,
25
26    /// Invalid message sequencing
27    InvalidSeq = 0x04,
28
29    /// Message timed out
30    Timeout = 0x05,
31
32    /// Channel busy
33    ChannelBusy = 0x06,
34
35    /// Command requires channel lock
36    LockRequired = 0x0A,
37
38    /// Invalid channel
39    InvalidChannel = 0x0B,
40
41    /// CBOR unexpected type
42    CborUnexpectedType = 0x11,
43
44    /// Invalid CBOR encoding
45    InvalidCbor = 0x12,
46
47    /// Missing required parameter
48    MissingParameter = 0x14,
49
50    /// Limit exceeded
51    LimitExceeded = 0x15,
52
53    /// Unsupported extension
54    UnsupportedExtension = 0x16,
55
56    /// Credential excluded (already exists)
57    CredentialExcluded = 0x19,
58
59    /// Processing (e.g. waiting for user presence)
60    Processing = 0x21,
61
62    /// Invalid credential
63    InvalidCredential = 0x22,
64
65    /// User action pending
66    UserActionPending = 0x23,
67
68    /// Operation pending
69    OperationPending = 0x24,
70
71    /// No operations pending
72    NoOperations = 0x25,
73
74    /// Unsupported algorithm
75    UnsupportedAlgorithm = 0x26,
76
77    /// Operation denied by user
78    OperationDenied = 0x27,
79
80    /// Key store full
81    KeyStoreFull = 0x28,
82
83    /// Not busy
84    NotBusy = 0x29,
85
86    /// No operation pending
87    NoOperationPending = 0x2A,
88
89    /// Unsupported option
90    UnsupportedOption = 0x2B,
91
92    /// Invalid option
93    InvalidOption = 0x2C,
94
95    /// Keepalive cancel
96    KeepaliveCancel = 0x2D,
97
98    /// No credentials found
99    NoCredentials = 0x2E,
100
101    /// User action timeout
102    UserActionTimeout = 0x2F,
103
104    /// Not allowed
105    NotAllowed = 0x30,
106
107    /// PIN invalid
108    PinInvalid = 0x31,
109
110    /// PIN blocked
111    PinBlocked = 0x32,
112
113    /// PIN/UV auth parameter invalid
114    PinAuthInvalid = 0x33,
115
116    /// PIN/UV auth blocked
117    PinAuthBlocked = 0x34,
118
119    /// PIN not set
120    PinNotSet = 0x35,
121
122    /// PIN required for this operation
123    PinRequired = 0x36,
124
125    /// PIN policy violation
126    PinPolicyViolation = 0x37,
127
128    /// PIN token expired
129    PinTokenExpired = 0x38,
130
131    /// Request too large
132    RequestTooLarge = 0x39,
133
134    /// Action timeout
135    ActionTimeout = 0x3A,
136
137    /// User presence required
138    UpRequired = 0x3B,
139
140    /// User verification blocked
141    UvBlocked = 0x3C,
142
143    /// Integrity failure
144    IntegrityFailure = 0x3D,
145
146    /// Invalid subcommand
147    InvalidSubcommand = 0x3E,
148
149    /// User verification invalid
150    UvInvalid = 0x3F,
151
152    /// Unauthorized permission
153    UnauthorizedPermission = 0x40,
154
155    /// Other unspecified error
156    Other = 0x7F,
157}
158
159impl fmt::Display for StatusCode {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        let msg = match self {
162            Self::Success => "Success",
163            Self::InvalidCommand => "Invalid command",
164            Self::InvalidParameter => "Invalid parameter",
165            Self::InvalidLength => "Invalid length",
166            Self::InvalidSeq => "Invalid sequence",
167            Self::Timeout => "Timeout",
168            Self::ChannelBusy => "Channel busy",
169            Self::LockRequired => "Lock required",
170            Self::InvalidChannel => "Invalid channel",
171            Self::CborUnexpectedType => "CBOR unexpected type",
172            Self::InvalidCbor => "Invalid CBOR",
173            Self::MissingParameter => "Missing parameter",
174            Self::LimitExceeded => "Limit exceeded",
175            Self::UnsupportedExtension => "Unsupported extension",
176            Self::CredentialExcluded => "Credential excluded",
177            Self::Processing => "Processing",
178            Self::InvalidCredential => "Invalid credential",
179            Self::UserActionPending => "User action pending",
180            Self::OperationPending => "Operation pending",
181            Self::NoOperations => "No operations",
182            Self::UnsupportedAlgorithm => "Unsupported algorithm",
183            Self::OperationDenied => "Operation denied",
184            Self::KeyStoreFull => "Key store full",
185            Self::NotBusy => "Not busy",
186            Self::NoOperationPending => "No operation pending",
187            Self::UnsupportedOption => "Unsupported option",
188            Self::InvalidOption => "Invalid option",
189            Self::KeepaliveCancel => "Keepalive cancel",
190            Self::NoCredentials => "No credentials",
191            Self::UserActionTimeout => "User action timeout",
192            Self::NotAllowed => "Not allowed",
193            Self::PinInvalid => "PIN invalid",
194            Self::PinBlocked => "PIN blocked",
195            Self::PinAuthInvalid => "PIN auth invalid",
196            Self::PinAuthBlocked => "PIN auth blocked",
197            Self::PinNotSet => "PIN not set",
198            Self::PinRequired => "PIN required",
199            Self::PinPolicyViolation => "PIN policy violation",
200            Self::PinTokenExpired => "PIN token expired",
201            Self::RequestTooLarge => "Request too large",
202            Self::ActionTimeout => "Action timeout",
203            Self::UpRequired => "UP required",
204            Self::UvBlocked => "UV blocked",
205            Self::IntegrityFailure => "Integrity failure",
206            Self::InvalidSubcommand => "Invalid subcommand",
207            Self::UvInvalid => "UV invalid",
208            Self::UnauthorizedPermission => "Unauthorized permission",
209            Self::Other => "Other error",
210        };
211        write!(f, "{}", msg)
212    }
213}
214
215/// Implement std::error::Error only when std is available
216#[cfg(feature = "std")]
217impl std::error::Error for StatusCode {}
218
219impl StatusCode {
220    /// Convert status code to byte value
221    pub fn to_u8(self) -> u8 {
222        self as u8
223    }
224
225    /// Create status code from byte value
226    pub fn from_u8(value: u8) -> Self {
227        match value {
228            0x00 => Self::Success,
229            0x01 => Self::InvalidCommand,
230            0x02 => Self::InvalidParameter,
231            0x03 => Self::InvalidLength,
232            0x04 => Self::InvalidSeq,
233            0x05 => Self::Timeout,
234            0x06 => Self::ChannelBusy,
235            0x0A => Self::LockRequired,
236            0x0B => Self::InvalidChannel,
237            0x11 => Self::CborUnexpectedType,
238            0x12 => Self::InvalidCbor,
239            0x14 => Self::MissingParameter,
240            0x15 => Self::LimitExceeded,
241            0x16 => Self::UnsupportedExtension,
242            0x19 => Self::CredentialExcluded,
243            0x21 => Self::Processing,
244            0x22 => Self::InvalidCredential,
245            0x23 => Self::UserActionPending,
246            0x24 => Self::OperationPending,
247            0x25 => Self::NoOperations,
248            0x26 => Self::UnsupportedAlgorithm,
249            0x27 => Self::OperationDenied,
250            0x28 => Self::KeyStoreFull,
251            0x29 => Self::NotBusy,
252            0x2A => Self::NoOperationPending,
253            0x2B => Self::UnsupportedOption,
254            0x2C => Self::InvalidOption,
255            0x2D => Self::KeepaliveCancel,
256            0x2E => Self::NoCredentials,
257            0x2F => Self::UserActionTimeout,
258            0x30 => Self::NotAllowed,
259            0x31 => Self::PinInvalid,
260            0x32 => Self::PinBlocked,
261            0x33 => Self::PinAuthInvalid,
262            0x34 => Self::PinAuthBlocked,
263            0x35 => Self::PinNotSet,
264            0x36 => Self::PinRequired,
265            0x37 => Self::PinPolicyViolation,
266            0x38 => Self::PinTokenExpired,
267            0x39 => Self::RequestTooLarge,
268            0x3A => Self::ActionTimeout,
269            0x3B => Self::UpRequired,
270            0x3C => Self::UvBlocked,
271            0x3D => Self::IntegrityFailure,
272            0x3E => Self::InvalidSubcommand,
273            0x3F => Self::UvInvalid,
274            0x40 => Self::UnauthorizedPermission,
275            _ => Self::Other,
276        }
277    }
278
279    /// Check if this is a success status
280    pub fn is_success(self) -> bool {
281        self == Self::Success
282    }
283}
284
285impl From<StatusCode> for u8 {
286    fn from(status: StatusCode) -> u8 {
287        status.to_u8()
288    }
289}
290
291impl From<u8> for StatusCode {
292    fn from(value: u8) -> Self {
293        Self::from_u8(value)
294    }
295}
296
297impl From<soft_fido2_crypto::CryptoError> for StatusCode {
298    fn from(err: soft_fido2_crypto::CryptoError) -> Self {
299        match err {
300            soft_fido2_crypto::CryptoError::InvalidPublicKey => Self::InvalidParameter,
301            soft_fido2_crypto::CryptoError::InvalidPrivateKey => Self::InvalidParameter,
302            soft_fido2_crypto::CryptoError::InvalidSignature => Self::InvalidParameter,
303            soft_fido2_crypto::CryptoError::DecryptionFailed => Self::PinAuthInvalid,
304            soft_fido2_crypto::CryptoError::EncryptionFailed => Self::Other,
305            soft_fido2_crypto::CryptoError::InvalidKeyLength { .. } => Self::InvalidParameter,
306            soft_fido2_crypto::CryptoError::KeyAgreementFailed => Self::Other,
307            soft_fido2_crypto::CryptoError::InvalidCoseKey => Self::InvalidParameter,
308        }
309    }
310}
311
312/// Result type for CTAP operations
313pub type Result<T> = core::result::Result<T, StatusCode>;
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn test_status_code_round_trip() {
321        let codes = vec![
322            StatusCode::Success,
323            StatusCode::InvalidCommand,
324            StatusCode::PinInvalid,
325            StatusCode::OperationDenied,
326        ];
327
328        for code in codes {
329            let byte = code.to_u8();
330            let recovered = StatusCode::from_u8(byte);
331            assert_eq!(code, recovered);
332        }
333    }
334
335    #[test]
336    fn test_unknown_status_code() {
337        let unknown = StatusCode::from_u8(0xFF);
338        assert_eq!(unknown, StatusCode::Other);
339    }
340
341    #[test]
342    fn test_is_success() {
343        assert!(StatusCode::Success.is_success());
344        assert!(!StatusCode::InvalidCommand.is_success());
345    }
346
347    #[test]
348    fn test_from_crypto_error() {
349        let status: StatusCode = soft_fido2_crypto::CryptoError::InvalidPublicKey.into();
350        assert_eq!(status, StatusCode::InvalidParameter);
351
352        let status: StatusCode = soft_fido2_crypto::CryptoError::DecryptionFailed.into();
353        assert_eq!(status, StatusCode::PinAuthInvalid);
354    }
355}