Skip to main content

ctap_fido2/
error.rs

1//! Typed errors for every CTAP status byte.
2
3use core::fmt;
4use std::{
5   error::Error as StdError,
6   io,
7   result,
8};
9
10/// CTAP2 / CTAPHID status codes that callers might want to distinguish.
11///
12/// Anything besides PIN retry, credential probing, and timeout
13/// handling collapses to [`CtapStatus::Other`] with the
14/// raw byte.
15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub enum CtapStatus {
17   /// 0x00: success.
18   Ok,
19   /// 0x01: invalid command.
20   InvalidCommand,
21   /// 0x06: CTAPHID channel busy. Only ever arrives in a `CTAPHID_ERROR`
22   /// frame (cmd=0xBF). Do not expect it inside a CBOR response.
23   ChannelBusy,
24   /// 0x11: CBOR map value had an unexpected major type.
25   CborUnexpectedType,
26   /// 0x12: CBOR parse failure.
27   InvalidCbor,
28   /// 0x13: required CBOR parameter was missing.
29   MissingParameter,
30   /// 0x27: operation denied (often "user touched cancel" or policy refusal).
31   OperationDenied,
32   /// 0x2D: in-flight operation was cancelled via `CTAPHID_CANCEL`.
33   KeepaliveCancel,
34   /// 0x2E: no credentials match the request.
35   NoCredentials,
36   /// 0x2F: user action timeout (no touch in time).
37   UserActionTimeout,
38   /// 0x31: PIN is invalid.
39   PinInvalid,
40   /// 0x32: PIN is blocked (all retries exhausted).
41   PinBlocked,
42   /// 0x33: pinUvAuthParam failed validation.
43   PinAuthInvalid,
44   /// 0x34: pinUvAuth blocked for this boot.
45   PinAuthBlocked,
46   /// 0x35: authenticator does not have a PIN set.
47   PinNotSet,
48   /// 0x36: PIN is required for this operation.
49   PinRequired,
50   /// 0x37: PIN policy violation (e.g. PIN too short).
51   PinPolicyViolation,
52   /// Any other CTAP byte.
53   Other(u8),
54}
55
56impl CtapStatus {
57   /// Build a status from a raw CTAP response byte.
58   #[must_use]
59   pub const fn from_byte(byte: u8) -> Self {
60      match byte {
61         0x00 => Self::Ok,
62         0x01 => Self::InvalidCommand,
63         0x06 => Self::ChannelBusy,
64         0x11 => Self::CborUnexpectedType,
65         0x12 => Self::InvalidCbor,
66         0x13 => Self::MissingParameter,
67         0x27 => Self::OperationDenied,
68         0x2D => Self::KeepaliveCancel,
69         0x2E => Self::NoCredentials,
70         0x2F => Self::UserActionTimeout,
71         0x31 => Self::PinInvalid,
72         0x32 => Self::PinBlocked,
73         0x33 => Self::PinAuthInvalid,
74         0x34 => Self::PinAuthBlocked,
75         0x35 => Self::PinNotSet,
76         0x36 => Self::PinRequired,
77         0x37 => Self::PinPolicyViolation,
78         other => Self::Other(other),
79      }
80   }
81
82   /// Raw byte the authenticator sent on the wire.
83   #[must_use]
84   pub const fn as_byte(self) -> u8 {
85      match self {
86         Self::Ok => 0x00,
87         Self::InvalidCommand => 0x01,
88         Self::ChannelBusy => 0x06,
89         Self::CborUnexpectedType => 0x11,
90         Self::InvalidCbor => 0x12,
91         Self::MissingParameter => 0x13,
92         Self::OperationDenied => 0x27,
93         Self::KeepaliveCancel => 0x2D,
94         Self::NoCredentials => 0x2E,
95         Self::UserActionTimeout => 0x2F,
96         Self::PinInvalid => 0x31,
97         Self::PinBlocked => 0x32,
98         Self::PinAuthInvalid => 0x33,
99         Self::PinAuthBlocked => 0x34,
100         Self::PinNotSet => 0x35,
101         Self::PinRequired => 0x36,
102         Self::PinPolicyViolation => 0x37,
103         Self::Other(byte) => byte,
104      }
105   }
106}
107
108impl From<u8> for CtapStatus {
109   fn from(byte: u8) -> Self {
110      Self::from_byte(byte)
111   }
112}
113
114impl From<CtapStatus> for u8 {
115   fn from(status: CtapStatus) -> Self {
116      status.as_byte()
117   }
118}
119
120impl fmt::Display for CtapStatus {
121   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122      write!(f, "CTAP status 0x{:02X}", self.as_byte())
123   }
124}
125
126/// Top-level error type.
127#[derive(Debug)]
128pub enum Error {
129   /// The authenticator returned a non-success CTAP status.
130   Ctap(CtapStatus),
131   /// An hmac-secret extension was requested but the device didn't return one.
132   MissingExtension(&'static str),
133   /// The host couldn't reach the device (USB / HID I/O).
134   Io(io::Error),
135   /// hidapi error during enumeration or open.
136   Hid(String),
137   /// CBOR encoding/decoding failure.
138   Cbor(String),
139   /// PIN protocol step produced an unexpected result.
140   Pin(&'static str),
141   /// A response field had an unexpected type or shape.
142   Parse(&'static str),
143}
144
145impl fmt::Display for Error {
146   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147      match *self {
148         Self::Ctap(ref status) => write!(f, "{status}"),
149         Self::MissingExtension(name) => write!(f, "device omitted required extension: {name}"),
150         Self::Io(ref err) => write!(f, "hid io: {err}"),
151         Self::Hid(ref msg) => write!(f, "hidapi: {msg}"),
152         Self::Cbor(ref msg) => write!(f, "cbor: {msg}"),
153         Self::Pin(msg) => write!(f, "pin protocol: {msg}"),
154         Self::Parse(msg) => write!(f, "parse: {msg}"),
155      }
156   }
157}
158
159impl StdError for Error {
160   fn source(&self) -> Option<&(dyn StdError + 'static)> {
161      match *self {
162         Self::Io(ref err) => Some(err),
163         _ => None,
164      }
165   }
166}
167
168impl From<io::Error> for Error {
169   fn from(err: io::Error) -> Self {
170      Self::Io(err)
171   }
172}
173
174/// Crate-wide result alias.
175pub type Result<T> = result::Result<T, Error>;