1#[cfg(feature = "std")]
4use std::fmt;
5
6#[cfg(not(feature = "std"))]
7use core::fmt;
8
9use alloc::string::String;
10
11#[non_exhaustive]
13#[derive(Debug, Clone, PartialEq)]
14pub enum Error {
15 Success,
17 DoesAlreadyExist,
19 DoesNotExist,
21 KeyStoreFull,
23 OutOfMemory,
25 Timeout,
27 Other,
29 InitializationFailed,
31 InvalidCallbackResult,
33 CborCommandFailed(i32),
35 InvalidClientDataHash,
37 NoCredentials,
43 PinAuthRequired,
45 UnauthorizedPermission,
50 InvalidRpIdHash,
54 PinTokenExpired,
56 InvalidSubcommand,
58 CtapError(u8),
60 IoError(String),
62 InvalidPinLength,
64}
65
66impl fmt::Display for Error {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Error::Success => write!(f, "Success"),
70 Error::DoesAlreadyExist => write!(f, "Value already exists"),
71 Error::DoesNotExist => write!(f, "Value does not exist"),
72 Error::KeyStoreFull => write!(f, "Key store is full"),
73 Error::OutOfMemory => write!(f, "Out of memory"),
74 Error::Timeout => write!(f, "Operation timed out"),
75 Error::Other => write!(f, "Unspecified error"),
76 Error::InitializationFailed => write!(f, "Initialization failed"),
77 Error::InvalidCallbackResult => write!(f, "Invalid callback result"),
78 Error::CborCommandFailed(code) => {
79 write!(f, "CBOR command failed with code {}", code)
80 }
81 Error::InvalidClientDataHash => {
82 write!(f, "Invalid client data hash (must be 32 bytes)")
83 }
84 Error::NoCredentials => write!(f, "No credentials found"),
85 Error::PinAuthRequired => write!(f, "PIN/UV authentication required"),
86 Error::UnauthorizedPermission => write!(f, "Insufficient permissions"),
87 Error::InvalidRpIdHash => write!(f, "Invalid RP ID hash (must be 32 bytes)"),
88 Error::PinTokenExpired => write!(f, "PIN/UV auth token expired"),
89 Error::InvalidSubcommand => write!(f, "Invalid subcommand"),
90 Error::CtapError(code) => write!(f, "CTAP error: 0x{:02X}", code),
91 Error::IoError(msg) => write!(f, "IO error: {}", msg),
92 Error::InvalidPinLength => write!(f, "Invalid PIN length (must be 4-63 characters)"),
93 }
94 }
95}
96
97#[cfg(feature = "std")]
98impl std::error::Error for Error {}
99
100impl From<i32> for Error {
101 fn from(value: i32) -> Self {
102 match value {
103 0 => Error::Success,
104 -1 => Error::DoesAlreadyExist,
105 -2 => Error::DoesNotExist,
106 -3 => Error::KeyStoreFull,
107 -4 => Error::OutOfMemory,
108 -5 => Error::Timeout,
109 -6 => Error::Other,
110 _ => Error::CborCommandFailed(value),
111 }
112 }
113}
114
115impl From<soft_fido2_ctap::StatusCode> for Error {
116 fn from(status: soft_fido2_ctap::StatusCode) -> Self {
117 use soft_fido2_ctap::StatusCode;
118
119 match status {
120 StatusCode::Success => Error::Success,
121 StatusCode::InvalidCommand => Error::CtapError(0x01),
122 StatusCode::InvalidParameter => Error::CtapError(0x02),
123 StatusCode::InvalidLength => Error::CtapError(0x03),
124 StatusCode::InvalidSeq => Error::CtapError(0x04),
125 StatusCode::Timeout => Error::Timeout,
126 StatusCode::ChannelBusy => Error::CtapError(0x06),
127 StatusCode::LockRequired => Error::CtapError(0x0A),
128 StatusCode::InvalidChannel => Error::CtapError(0x0B),
129 StatusCode::CborUnexpectedType => Error::CtapError(0x11),
130 StatusCode::InvalidCbor => Error::CtapError(0x12),
131 StatusCode::MissingParameter => Error::CtapError(0x14),
132 StatusCode::LimitExceeded => Error::CtapError(0x15),
133 StatusCode::UnsupportedExtension => Error::CtapError(0x16),
134 StatusCode::CredentialExcluded => Error::CtapError(0x19),
135 StatusCode::Processing => Error::CtapError(0x21),
136 StatusCode::InvalidCredential => Error::CtapError(0x22),
137 StatusCode::UserActionPending => Error::CtapError(0x23),
138 StatusCode::OperationPending => Error::CtapError(0x24),
139 StatusCode::NoOperations => Error::CtapError(0x25),
140 StatusCode::UnsupportedAlgorithm => Error::CtapError(0x26),
141 StatusCode::OperationDenied => Error::CtapError(0x27),
142 StatusCode::KeyStoreFull => Error::KeyStoreFull,
143 StatusCode::NotBusy => Error::CtapError(0x29),
144 StatusCode::NoOperationPending => Error::CtapError(0x2A),
145 StatusCode::UnsupportedOption => Error::CtapError(0x2B),
146 StatusCode::InvalidOption => Error::CtapError(0x2C),
147 StatusCode::KeepaliveCancel => Error::CtapError(0x2D),
148 StatusCode::NoCredentials => Error::NoCredentials,
149 StatusCode::UserActionTimeout => Error::Timeout,
150 StatusCode::NotAllowed => Error::CtapError(0x30),
151 StatusCode::PinInvalid => Error::CtapError(0x31),
152 StatusCode::PinBlocked => Error::CtapError(0x32),
153 StatusCode::PinAuthInvalid => Error::CtapError(0x33),
154 StatusCode::PinAuthBlocked => Error::CtapError(0x34),
155 StatusCode::PinNotSet => Error::CtapError(0x35),
156 StatusCode::PinRequired => Error::CtapError(0x36),
157 StatusCode::PinPolicyViolation => Error::CtapError(0x37),
158 StatusCode::PinTokenExpired => Error::CtapError(0x38),
159 StatusCode::RequestTooLarge => Error::CtapError(0x39),
160 StatusCode::ActionTimeout => Error::Timeout,
161 StatusCode::UpRequired => Error::CtapError(0x3A),
162 StatusCode::UvBlocked => Error::CtapError(0x3C),
163 StatusCode::IntegrityFailure => Error::CtapError(0x3D),
164 StatusCode::InvalidSubcommand => Error::CtapError(0x3E),
165 StatusCode::UvInvalid => Error::CtapError(0x3F),
166 StatusCode::UnauthorizedPermission => Error::CtapError(0x40),
167 StatusCode::PuatRequired => Error::CtapError(0x41),
168 StatusCode::Other => Error::Other,
169 _ => Error::Other,
170 }
171 }
172}
173
174impl From<Error> for soft_fido2_ctap::StatusCode {
175 fn from(error: Error) -> Self {
176 use soft_fido2_ctap::StatusCode;
177
178 match error {
179 Error::Success => StatusCode::Success,
180 Error::DoesNotExist => StatusCode::NoCredentials,
181 Error::KeyStoreFull => StatusCode::KeyStoreFull,
182 Error::Timeout => StatusCode::Timeout,
183 Error::Other => StatusCode::Other,
184 Error::CtapError(code) => {
185 match code {
187 0x01 => StatusCode::InvalidCommand,
188 0x02 => StatusCode::InvalidParameter,
189 0x03 => StatusCode::InvalidLength,
190 0x04 => StatusCode::InvalidSeq,
191 0x06 => StatusCode::ChannelBusy,
192 0x0A => StatusCode::LockRequired,
193 0x0B => StatusCode::InvalidChannel,
194 0x11 => StatusCode::CborUnexpectedType,
195 0x12 => StatusCode::InvalidCbor,
196 0x14 => StatusCode::MissingParameter,
197 0x15 => StatusCode::LimitExceeded,
198 0x31 => StatusCode::PinInvalid,
199 0x33 => StatusCode::PinAuthInvalid,
200 0x35 => StatusCode::PinNotSet,
201 0x36 => StatusCode::PinRequired,
202 _ => StatusCode::Other,
203 }
204 }
205 Error::InvalidPinLength => StatusCode::PinPolicyViolation,
206 _ => StatusCode::Other,
207 }
208 }
209}
210
211#[cfg(feature = "std")]
213impl From<std::io::Error> for Error {
214 fn from(error: std::io::Error) -> Self {
215 Error::IoError(error.to_string())
216 }
217}
218
219impl Error {
220 pub fn parse_ctap_response(data: &[u8]) -> Result<&[u8]> {
228 if data.is_empty() {
229 return Err(Error::Other);
230 }
231
232 let status_byte = data[0];
233 if status_byte == 0x00 {
234 Ok(&data[1..])
236 } else {
237 Err(soft_fido2_ctap::StatusCode::from(status_byte).into())
239 }
240 }
241}
242
243#[cfg(feature = "std")]
245pub type Result<T> = std::result::Result<T, Error>;
246
247#[cfg(not(feature = "std"))]
248pub type Result<T> = core::result::Result<T, Error>;