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