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}