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    /// PIN/UV auth token required
156    PuatRequired = 0x41,
157
158    /// Other unspecified error
159    Other = 0x7F,
160}
161
162impl fmt::Display for StatusCode {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        let msg = match self {
165            Self::Success => "Success",
166            Self::InvalidCommand => "Invalid command",
167            Self::InvalidParameter => "Invalid parameter",
168            Self::InvalidLength => "Invalid length",
169            Self::InvalidSeq => "Invalid sequence",
170            Self::Timeout => "Timeout",
171            Self::ChannelBusy => "Channel busy",
172            Self::LockRequired => "Lock required",
173            Self::InvalidChannel => "Invalid channel",
174            Self::CborUnexpectedType => "CBOR unexpected type",
175            Self::InvalidCbor => "Invalid CBOR",
176            Self::MissingParameter => "Missing parameter",
177            Self::LimitExceeded => "Limit exceeded",
178            Self::UnsupportedExtension => "Unsupported extension",
179            Self::CredentialExcluded => "Credential excluded",
180            Self::Processing => "Processing",
181            Self::InvalidCredential => "Invalid credential",
182            Self::UserActionPending => "User action pending",
183            Self::OperationPending => "Operation pending",
184            Self::NoOperations => "No operations",
185            Self::UnsupportedAlgorithm => "Unsupported algorithm",
186            Self::OperationDenied => "Operation denied",
187            Self::KeyStoreFull => "Key store full",
188            Self::NotBusy => "Not busy",
189            Self::NoOperationPending => "No operation pending",
190            Self::UnsupportedOption => "Unsupported option",
191            Self::InvalidOption => "Invalid option",
192            Self::KeepaliveCancel => "Keepalive cancel",
193            Self::NoCredentials => "No credentials",
194            Self::UserActionTimeout => "User action timeout",
195            Self::NotAllowed => "Not allowed",
196            Self::PinInvalid => "PIN invalid",
197            Self::PinBlocked => "PIN blocked",
198            Self::PinAuthInvalid => "PIN auth invalid",
199            Self::PinAuthBlocked => "PIN auth blocked",
200            Self::PinNotSet => "PIN not set",
201            Self::PinRequired => "PIN required",
202            Self::PinPolicyViolation => "PIN policy violation",
203            Self::PinTokenExpired => "PIN token expired",
204            Self::RequestTooLarge => "Request too large",
205            Self::ActionTimeout => "Action timeout",
206            Self::UpRequired => "UP required",
207            Self::UvBlocked => "UV blocked",
208            Self::IntegrityFailure => "Integrity failure",
209            Self::InvalidSubcommand => "Invalid subcommand",
210            Self::UvInvalid => "UV invalid",
211            Self::UnauthorizedPermission => "Unauthorized permission",
212            Self::PuatRequired => "PIN/UV auth token required",
213            Self::Other => "Other error",
214        };
215        write!(f, "{}", msg)
216    }
217}
218
219/// Implement std::error::Error only when std is available
220#[cfg(feature = "std")]
221impl std::error::Error for StatusCode {}
222
223impl StatusCode {
224    /// Convert status code to byte value
225    pub fn to_u8(self) -> u8 {
226        self as u8
227    }
228
229    /// Create status code from byte value
230    pub fn from_u8(value: u8) -> Self {
231        match value {
232            0x00 => Self::Success,
233            0x01 => Self::InvalidCommand,
234            0x02 => Self::InvalidParameter,
235            0x03 => Self::InvalidLength,
236            0x04 => Self::InvalidSeq,
237            0x05 => Self::Timeout,
238            0x06 => Self::ChannelBusy,
239            0x0A => Self::LockRequired,
240            0x0B => Self::InvalidChannel,
241            0x11 => Self::CborUnexpectedType,
242            0x12 => Self::InvalidCbor,
243            0x14 => Self::MissingParameter,
244            0x15 => Self::LimitExceeded,
245            0x16 => Self::UnsupportedExtension,
246            0x19 => Self::CredentialExcluded,
247            0x21 => Self::Processing,
248            0x22 => Self::InvalidCredential,
249            0x23 => Self::UserActionPending,
250            0x24 => Self::OperationPending,
251            0x25 => Self::NoOperations,
252            0x26 => Self::UnsupportedAlgorithm,
253            0x27 => Self::OperationDenied,
254            0x28 => Self::KeyStoreFull,
255            0x29 => Self::NotBusy,
256            0x2A => Self::NoOperationPending,
257            0x2B => Self::UnsupportedOption,
258            0x2C => Self::InvalidOption,
259            0x2D => Self::KeepaliveCancel,
260            0x2E => Self::NoCredentials,
261            0x2F => Self::UserActionTimeout,
262            0x30 => Self::NotAllowed,
263            0x31 => Self::PinInvalid,
264            0x32 => Self::PinBlocked,
265            0x33 => Self::PinAuthInvalid,
266            0x34 => Self::PinAuthBlocked,
267            0x35 => Self::PinNotSet,
268            0x36 => Self::PinRequired,
269            0x37 => Self::PinPolicyViolation,
270            0x38 => Self::PinTokenExpired,
271            0x39 => Self::RequestTooLarge,
272            0x3A => Self::ActionTimeout,
273            0x3B => Self::UpRequired,
274            0x3C => Self::UvBlocked,
275            0x3D => Self::IntegrityFailure,
276            0x3E => Self::InvalidSubcommand,
277            0x3F => Self::UvInvalid,
278            0x40 => Self::UnauthorizedPermission,
279            0x41 => Self::PuatRequired,
280            _ => Self::Other,
281        }
282    }
283
284    /// Check if this is a success status
285    pub fn is_success(self) -> bool {
286        self == Self::Success
287    }
288}
289
290impl From<StatusCode> for u8 {
291    fn from(status: StatusCode) -> u8 {
292        status.to_u8()
293    }
294}
295
296impl From<u8> for StatusCode {
297    fn from(value: u8) -> Self {
298        Self::from_u8(value)
299    }
300}
301
302impl From<soft_fido2_crypto::CryptoError> for StatusCode {
303    fn from(err: soft_fido2_crypto::CryptoError) -> Self {
304        match err {
305            soft_fido2_crypto::CryptoError::InvalidPublicKey => Self::InvalidParameter,
306            soft_fido2_crypto::CryptoError::InvalidPrivateKey => Self::InvalidParameter,
307            soft_fido2_crypto::CryptoError::InvalidSignature => Self::InvalidParameter,
308            soft_fido2_crypto::CryptoError::DecryptionFailed => Self::PinAuthInvalid,
309            soft_fido2_crypto::CryptoError::EncryptionFailed => Self::Other,
310            soft_fido2_crypto::CryptoError::InvalidKeyLength { .. } => Self::InvalidParameter,
311            soft_fido2_crypto::CryptoError::KeyAgreementFailed => Self::Other,
312            soft_fido2_crypto::CryptoError::InvalidCoseKey => Self::InvalidParameter,
313        }
314    }
315}
316
317/// Result type for CTAP operations
318pub type Result<T> = core::result::Result<T, StatusCode>;
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn test_status_code_round_trip() {
326        let codes = vec![
327            StatusCode::Success,
328            StatusCode::InvalidCommand,
329            StatusCode::PinInvalid,
330            StatusCode::OperationDenied,
331        ];
332
333        for code in codes {
334            let byte = code.to_u8();
335            let recovered = StatusCode::from_u8(byte);
336            assert_eq!(code, recovered);
337        }
338    }
339
340    #[test]
341    fn test_unknown_status_code() {
342        let unknown = StatusCode::from_u8(0xFF);
343        assert_eq!(unknown, StatusCode::Other);
344    }
345
346    #[test]
347    fn test_is_success() {
348        assert!(StatusCode::Success.is_success());
349        assert!(!StatusCode::InvalidCommand.is_success());
350    }
351
352    #[test]
353    fn test_from_crypto_error() {
354        let status: StatusCode = soft_fido2_crypto::CryptoError::InvalidPublicKey.into();
355        assert_eq!(status, StatusCode::InvalidParameter);
356
357        let status: StatusCode = soft_fido2_crypto::CryptoError::DecryptionFailed.into();
358        assert_eq!(status, StatusCode::PinAuthInvalid);
359    }
360}