Skip to main content

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