passkey_types/ctap2/
error.rs

1//! Error responses
2
3use crate::utils::repr_enum::CodeOutOfRange;
4
5/// <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#error-responses>
6#[derive(Debug, PartialEq, Eq)]
7pub enum StatusCode {
8    /// Ctap1 or U2F error codes
9    Ctap1(U2FError),
10    /// CTAP2 error codes
11    Ctap2(Ctap2Code),
12}
13
14impl From<u8> for StatusCode {
15    fn from(value: u8) -> Self {
16        // Default to trying Ctap2, otherwise it must be a U2F error
17        Ctap2Code::try_from(value)
18            .map(Self::from)
19            .or_else(|err| U2FError::try_from(err.0).map(Self::from))
20            // SAFETY: this unwrap is safe because at this point we have exhausted all values of a byte.
21            .unwrap()
22    }
23}
24
25impl From<StatusCode> for u8 {
26    fn from(src: StatusCode) -> Self {
27        match src {
28            StatusCode::Ctap1(u2f) => u2f.into(),
29            StatusCode::Ctap2(ctap2) => ctap2.into(),
30        }
31    }
32}
33
34repr_enum! {
35    /// U2F or CTAP1 error variants
36    U2FError: u8 {
37        /// Indicates successful response.
38        Success : 0x00,
39        /// The command is not a valid CTAP command.
40        InvalidCommand : 0x01,
41        /// The command included an invalid parameter.
42        InvalidParameter : 0x02,
43        /// Invalid message or item length.
44        InvalidLength : 0x03,
45        /// Invalid message sequencing.
46        InvalidSequence : 0x04,
47        /// Message timed out.
48        Timeout : 0x05,
49        /// Channel busy. Client SHOULD retry the request after a short delay. Note that the client MAY
50        /// abort the transaction if the command is no longer relevant.
51        ChannelBusy : 0x06,
52        /// Command requires channel lock.
53        LockRequired : 0x0A,
54        /// Command not allowed on this cid.
55        InvalidChannel : 0x0B,
56        /// Other unspecified error.
57        Other : 0x7F,
58    }
59}
60
61impl From<U2FError> for StatusCode {
62    fn from(ctap1: U2FError) -> Self {
63        StatusCode::Ctap1(ctap1)
64    }
65}
66
67/// Ctap2 error which may or may not be explicitly defined
68#[derive(Debug, PartialEq, Eq)]
69pub enum Ctap2Code {
70    /// Known error codes
71    Known(Ctap2Error),
72    /// last spec reserved number 0xDF
73    Other(UnknownSpecError),
74    /// Range 0xE0..=0xEF
75    Extension(ExtensionError),
76    /// Range 0xF0..=0xFF
77    Vendor(VendorError),
78}
79
80impl From<Ctap2Code> for StatusCode {
81    fn from(src: Ctap2Code) -> Self {
82        Self::Ctap2(src)
83    }
84}
85
86impl TryFrom<u8> for Ctap2Code {
87    type Error = CodeOutOfRange<u8>;
88
89    fn try_from(value: u8) -> Result<Self, Self::Error> {
90        Ctap2Error::try_from(value)
91            .map(Self::from)
92            .or_else(|err| ExtensionError::try_from(err.0).map(Self::from))
93            .or_else(|err| VendorError::try_from(err.0).map(Self::from))
94            .or_else(|err| UnknownSpecError::try_from(err.0).map(Self::from))
95    }
96}
97
98impl From<Ctap2Code> for u8 {
99    fn from(src: Ctap2Code) -> Self {
100        match src {
101            Ctap2Code::Known(known) => known.into(),
102            Ctap2Code::Other(other) => other.into(),
103            Ctap2Code::Extension(extension) => extension.into(),
104            Ctap2Code::Vendor(vendor) => vendor.into(),
105        }
106    }
107}
108
109repr_enum! {
110    /// Explicitly defined CTAP2 error variants
111    Ctap2Error: u8 {
112        /// Indicates successful response.
113        ///
114        /// > Note that this clashes with [`U2FError::Success`] but when deserializing from
115        /// > [`StatusCode`] we will default to this one.
116        Ok : 0x00,
117        /// Invalid/unexpected CBOR error.
118        CborUnexpectedType : 0x11,
119        /// Error when parsing CBOR.
120        InvalidCbor : 0x12,
121        /// Missing non-optional parameter.
122        MissingParameter : 0x14,
123        /// Limit for number of items exceeded.
124        LimitExceeded : 0x15,
125        /// Fingerprint database is full, e.g. during enrollment.
126        FingerprintDatabaseFull : 0x17,
127        /// Large blob storage is full. (See [§ 6.10.3 Large, per-credential blobs.][1])
128        ///
129        /// [1]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#large-blob
130        LargeBlobStorageFull : 0x18,
131        /// Valid credential found in the exclude list.
132        CredentialExcluded : 0x19,
133        /// Processing (Lengthy operation is in progress).
134        Processing : 0x21,
135        /// Credential not valid for the authenticator.
136        InvalidCredential : 0x22,
137        /// Authentication is waiting for user interaction.
138        UserActionPending : 0x23,
139        /// Processing, lengthy operation is in progress.
140        OperationPending : 0x24,
141        /// No request is pending.
142        NoOperations : 0x25,
143        /// Authenticator does not support requested algorithm.
144        UnsupportedAlgorithm : 0x26,
145        /// Not authorized for requested operation.
146        OperationDenied : 0x27,
147        /// Internal key storage is full.
148        KeyStoreFull : 0x28,
149        /// Unsupported option.
150        UnsupportedOption : 0x2B,
151        /// Not a valid option for current operation.
152        InvalidOption : 0x2C,
153        /// Pending keep alive was cancelled.
154        KeepAliveCancel : 0x2D,
155        /// No valid credentials provided.
156        NoCredentials : 0x2E,
157        /// A [user action timeout][1] occurred.
158        ///
159        /// [1]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#user-action-timeout
160        UserActionTimeout : 0x2F,
161        /// Continuation command, such as, authenticatorGetNextAssertion[^1] not allowed.
162        ///
163        /// [^1]: Coming soon to an MR near you
164        NotAllowed : 0x30,
165        /// PIN Invalid.
166        PinInvalid : 0x31,
167        /// PIN Blocked.
168        PinBlocked : 0x32,
169        /// PIN authentication,pinUvAuthParam, verification failed.
170        PinAuthInvalid : 0x33,
171        /// PIN authentication using [pinUvAuthToken] blocked. Requires [power cycle] to reset.
172        ///
173        /// [pinUvAuthToken]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#puatoken-pinuvauthtoken
174        /// [power cycle]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticator-power-up-configuration
175        PinAuthBlocked : 0x34,
176        /// No PIN has been set.
177        PinNotSet : 0x35,
178        /// A [pinUvAuthToken] is required for the selected operation. See also the pinUvAuthToken
179        /// [option ID].
180        ///
181        /// [pinUvAuthToken]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#puatoken-pinuvauthtoken
182        /// [option ID]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#option-id
183        PuatRequired : 0x36,
184        /// PIN policy violation. Currently only enforces minimum length.
185        PinPolicyViolation : 0x37,
186        /// Authenticator cannot handle this request due to memory constraints.
187        RequestTooLarge : 0x39,
188        /// The current operation has timed out.
189        ActionTimeout : 0x3A,
190        /// User presence is required for the requested operation.
191        UserPresenceRequired : 0x3B,
192        /// [Built-in user verification][1] is disabled.
193        ///
194        /// [1]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#built-in-user-verification-method
195        UserVerificationBlocked : 0x3C,
196        /// A checksum did not match.
197        IntegrityFailure : 0x3D,
198        /// The requested subcommand is either invalid or not implemented.
199        InvalidSubcommand : 0x3E,
200        /// [Built-in user verification][1] unsuccessful. The platform SHOULD retry.
201        ///
202        /// [1]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#built-in-user-verification-method
203        UserVerificationInvalid : 0x3F,
204        /// The [permissions] parameter contains an unauthorized permission.
205        ///
206        /// [permissions]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#perms-param-permissions
207        UnauthorizedPermission : 0x40,
208    }
209}
210
211impl From<Ctap2Error> for Ctap2Code {
212    fn from(src: Ctap2Error) -> Self {
213        Ctap2Code::Known(src)
214    }
215}
216
217impl From<Ctap2Error> for StatusCode {
218    fn from(src: Ctap2Error) -> Self {
219        StatusCode::Ctap2(src.into())
220    }
221}
222
223/// Error values that are not defined or reserved for future use at the time of writing
224#[derive(Debug, PartialEq, Eq)]
225pub struct UnknownSpecError(u8);
226
227impl TryFrom<u8> for UnknownSpecError {
228    type Error = CodeOutOfRange<u8>;
229
230    fn try_from(value: u8) -> Result<Self, Self::Error> {
231        match value {
232            0x07..=0x09
233            | 0x0C..=0x10
234            | 0x13
235            | 0x16
236            | 0x1A..=0x20
237            | 0x29
238            | 0x2A
239            | 0x38 // Explicitly marked reserved for future use
240            | 0x41..=0x7E
241            | 0x80..=0xDF => Ok(UnknownSpecError(value)),
242            _ => Err(CodeOutOfRange(value)),
243        }
244    }
245}
246impl From<UnknownSpecError> for u8 {
247    fn from(src: UnknownSpecError) -> Self {
248        src.0
249    }
250}
251
252impl From<UnknownSpecError> for Ctap2Code {
253    fn from(src: UnknownSpecError) -> Self {
254        Ctap2Code::Other(src)
255    }
256}
257
258impl From<UnknownSpecError> for StatusCode {
259    fn from(src: UnknownSpecError) -> Self {
260        StatusCode::Ctap2(src.into())
261    }
262}
263
264/// Extension error codes
265#[derive(Debug, PartialEq, Eq)]
266pub struct ExtensionError(u8);
267
268impl TryFrom<u8> for ExtensionError {
269    type Error = CodeOutOfRange<u8>;
270
271    fn try_from(value: u8) -> Result<Self, Self::Error> {
272        match value {
273            0xE0..=0xEF => Ok(Self(value)),
274            _ => Err(CodeOutOfRange(value)),
275        }
276    }
277}
278
279impl From<ExtensionError> for u8 {
280    fn from(src: ExtensionError) -> Self {
281        src.0
282    }
283}
284
285impl From<ExtensionError> for Ctap2Code {
286    fn from(src: ExtensionError) -> Self {
287        Ctap2Code::Extension(src)
288    }
289}
290
291impl From<ExtensionError> for StatusCode {
292    fn from(src: ExtensionError) -> Self {
293        StatusCode::Ctap2(src.into())
294    }
295}
296
297/// Vendor specific error codes
298#[derive(Debug, PartialEq, Eq)]
299pub struct VendorError(u8);
300
301impl TryFrom<u8> for VendorError {
302    type Error = CodeOutOfRange<u8>;
303
304    fn try_from(value: u8) -> Result<Self, Self::Error> {
305        match value {
306            0xF0..=0xFF => Ok(Self(value)),
307            _ => Err(CodeOutOfRange(value)),
308        }
309    }
310}
311
312impl From<VendorError> for u8 {
313    fn from(src: VendorError) -> Self {
314        src.0
315    }
316}
317
318impl From<VendorError> for Ctap2Code {
319    fn from(src: VendorError) -> Self {
320        Ctap2Code::Vendor(src)
321    }
322}
323
324impl From<VendorError> for StatusCode {
325    fn from(src: VendorError) -> Self {
326        StatusCode::Ctap2(src.into())
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use crate::ctap2::error::{ExtensionError, U2FError, UnknownSpecError, VendorError};
333
334    use super::{Ctap2Error, StatusCode};
335
336    #[test]
337    fn from_byte_conversions() {
338        // Assert success defaults to ctap2
339        let success = StatusCode::from(0x00);
340        assert_eq!(success, Ctap2Error::Ok.into());
341
342        let invalid_len = StatusCode::from(0x03);
343        assert_eq!(invalid_len, U2FError::InvalidLength.into());
344
345        let unsupported_alg = StatusCode::from(0x26);
346        assert_eq!(unsupported_alg, Ctap2Error::UnsupportedAlgorithm.into());
347
348        let unknown = StatusCode::from(0x1B);
349        assert_eq!(unknown, UnknownSpecError(0x1B).into());
350
351        let first_extension_err = StatusCode::from(0xE0);
352        assert_eq!(first_extension_err, ExtensionError(0xE0).into());
353        let last_extension_err = StatusCode::from(0xEF);
354        assert_eq!(last_extension_err, ExtensionError(0xEF).into());
355
356        let first_vendor_err = StatusCode::from(0xF0);
357        assert_eq!(first_vendor_err, VendorError(0xF0).into());
358        let last_vendor_err = StatusCode::from(0xFF);
359        assert_eq!(last_vendor_err, VendorError(0xFF).into());
360    }
361
362    #[test]
363    fn all_byte_values() {
364        // iterate through all byte values, it should not panic. Iterating through 256 cases should
365        // be fairly quick
366        for i in u8::MIN..=u8::MAX {
367            let _code = StatusCode::from(i);
368        }
369    }
370}