0
# Exception Handling
1
2
Comprehensive error handling with specific exception types for different failure modes including device communication errors, configuration errors, timeout conditions, and input validation failures.
3
4
## Capabilities
5
6
### Exception Hierarchy
7
8
All YubiKey exceptions inherit from the base `YubicoError` class, allowing unified error handling.
9
10
```python { .api }
11
class YubicoError(Exception):
12
"""
13
Base class for all Yubico exceptions.
14
15
All exceptions raised by YubiKey operations inherit from this class,
16
enabling comprehensive error handling with a single except clause.
17
18
Attributes:
19
- reason (str): Human-readable explanation of the error
20
"""
21
22
def __init__(self, reason):
23
"""
24
Initialize exception with error reason.
25
26
Parameters:
27
- reason (str): Explanation of the error
28
"""
29
self.reason = reason
30
31
def __str__(self):
32
"""
33
String representation of the exception.
34
35
Returns:
36
str: Formatted error message with class name and reason
37
"""
38
```
39
40
Base exception usage:
41
42
```python
43
import yubico
44
45
try:
46
yk = yubico.find_yubikey()
47
# Perform YubiKey operations...
48
49
except yubico.yubico_exception.YubicoError as e:
50
print(f"YubiKey error: {e.reason}")
51
# This will catch all YubiKey-related exceptions
52
```
53
54
### Input Validation Exceptions
55
56
Exceptions raised for invalid input parameters and data validation failures.
57
58
```python { .api }
59
class InputError(YubicoError):
60
"""
61
Exception raised for input validation errors.
62
63
Raised when function parameters fail validation, such as incorrect
64
data types, invalid lengths, or out-of-range values.
65
"""
66
67
def __init__(self, reason='input validation error'):
68
"""
69
Initialize input error exception.
70
71
Parameters:
72
- reason (str): Specific validation error description
73
"""
74
```
75
76
Input validation example:
77
78
```python
79
import yubico
80
from yubico.yubikey_config import YubiKeyConfig
81
82
try:
83
cfg = YubiKeyConfig()
84
85
# Invalid AES key length (must be 16 bytes)
86
cfg.aes_key(b"short_key")
87
88
except yubico.yubico_exception.InputError as e:
89
print(f"Input validation error: {e.reason}")
90
91
try:
92
yk = yubico.find_yubikey()
93
94
# Invalid challenge length for OTP mode (must be 6 bytes)
95
response = yk.challenge(b"too_long_challenge", mode='OTP')
96
97
except yubico.yubico_exception.InputError as e:
98
print(f"Challenge validation error: {e.reason}")
99
```
100
101
### Device Communication Exceptions
102
103
Exceptions related to YubiKey device communication and hardware errors.
104
105
```python { .api }
106
class YubiKeyError(YubicoError):
107
"""
108
Exception raised for YubiKey device operation errors.
109
110
Base class for device-specific errors including communication
111
failures, device not found, and operational errors.
112
"""
113
114
def __init__(self, reason='no details'):
115
"""
116
Initialize YubiKey error.
117
118
Parameters:
119
- reason (str): Error description
120
"""
121
122
class YubiKeyUSBHIDError(YubicoError):
123
"""
124
Exception raised for USB HID communication errors.
125
126
Specific to USB HID transport layer failures, device detection
127
issues, and low-level communication problems.
128
"""
129
```
130
131
Device communication example:
132
133
```python
134
import yubico
135
136
try:
137
yk = yubico.find_yubikey()
138
139
except yubico.yubikey_base.YubiKeyError as e:
140
if "No YubiKey found" in str(e):
141
print("No YubiKey device connected")
142
else:
143
print(f"YubiKey device error: {e.reason}")
144
145
except yubico.yubikey_usb_hid.YubiKeyUSBHIDError as e:
146
print(f"USB communication error: {e.reason}")
147
print("Try unplugging and reconnecting the YubiKey")
148
```
149
150
### Timeout Exceptions
151
152
Exceptions raised when operations exceed time limits or require user interaction.
153
154
```python { .api }
155
class YubiKeyTimeout(YubiKeyError):
156
"""
157
Exception raised when YubiKey operations time out.
158
159
Occurs when operations requiring user interaction (button press)
160
exceed the timeout period, or when device communication stalls.
161
"""
162
163
def __init__(self, reason='no details'):
164
"""
165
Initialize timeout error.
166
167
Parameters:
168
- reason (str): Timeout-specific error description
169
"""
170
```
171
172
Timeout handling example:
173
174
```python
175
import yubico
176
177
yk = yubico.find_yubikey()
178
179
try:
180
# This may timeout if button press is required
181
serial = yk.serial(may_block=True)
182
print(f"Serial number: {serial}")
183
184
except yubico.yubikey_base.YubiKeyTimeout:
185
print("Timeout waiting for user interaction")
186
print("Please touch the YubiKey button when it blinks")
187
188
try:
189
# Challenge-response with button press requirement
190
response = yk.challenge(
191
b"test challenge",
192
mode='HMAC',
193
slot=1,
194
may_block=True
195
)
196
197
except yubico.yubikey_base.YubiKeyTimeout:
198
print("Challenge-response timed out")
199
print("Check if button press is required for this configuration")
200
```
201
202
### Version Compatibility Exceptions
203
204
Exceptions raised when operations are not supported by the connected YubiKey version.
205
206
```python { .api }
207
class YubiKeyVersionError(YubiKeyError):
208
"""
209
Exception raised when YubiKey version doesn't support requested operation.
210
211
Occurs when attempting to use features not available on the connected
212
YubiKey model or firmware version.
213
"""
214
215
def __init__(self, reason='no details'):
216
"""
217
Initialize version error.
218
219
Parameters:
220
- reason (str): Version compatibility error description
221
"""
222
```
223
224
Version compatibility example:
225
226
```python
227
import yubico
228
229
yk = yubico.find_yubikey()
230
print(f"YubiKey version: {yk.version()}")
231
232
try:
233
# Serial number reading requires YubiKey 2.2+
234
serial = yk.serial()
235
print(f"Serial number: {serial}")
236
237
except yubico.yubikey_base.YubiKeyVersionError:
238
print("Serial number reading not supported on this YubiKey version")
239
print("Required: YubiKey 2.2 or later")
240
241
try:
242
# Challenge-response requires YubiKey 2.2+
243
response = yk.challenge_response(b"test", mode='HMAC', slot=1)
244
245
except yubico.yubikey_base.YubiKeyVersionError:
246
print("Challenge-response not supported on this YubiKey version")
247
print("Required: YubiKey 2.2 or later")
248
```
249
250
### Configuration Exceptions
251
252
Exceptions specific to YubiKey configuration operations.
253
254
```python { .api }
255
class YubiKeyConfigError(YubicoError):
256
"""
257
Exception raised for YubiKey configuration errors.
258
259
Occurs when configuration parameters are invalid, incompatible
260
with the target YubiKey, or when configuration write operations fail.
261
"""
262
```
263
264
Configuration error example:
265
266
```python
267
import yubico
268
from yubico.yubikey_config import YubiKeyConfig, YubiKeyConfigError
269
270
try:
271
yk = yubico.find_yubikey()
272
cfg = yk.init_config()
273
274
# Invalid configuration (example)
275
cfg.mode_oath_hotp(b"secret", digits=10) # Invalid digit count
276
277
yk.write_config(cfg, slot=1)
278
279
except YubiKeyConfigError as e:
280
print(f"Configuration error: {e.reason}")
281
282
except yubico.yubikey_base.YubiKeyVersionError as e:
283
print(f"Configuration not supported: {e.reason}")
284
```
285
286
### Comprehensive Error Handling
287
288
Best practices for handling all YubiKey exceptions in applications.
289
290
```python
291
import yubico
292
from yubico.yubikey_config import YubiKeyConfig, YubiKeyConfigError
293
294
def safe_yubikey_operation():
295
"""
296
Demonstrate comprehensive YubiKey error handling.
297
"""
298
try:
299
# Find and connect to YubiKey
300
yk = yubico.find_yubikey(debug=False)
301
print(f"Connected to YubiKey version {yk.version()}")
302
303
# Get device information
304
try:
305
serial = yk.serial(may_block=False)
306
print(f"Serial number: {serial}")
307
except yubico.yubikey_base.YubiKeyVersionError:
308
print("Serial number not available on this YubiKey version")
309
except yubico.yubikey_base.YubiKeyTimeout:
310
print("Serial number requires button press")
311
312
# Attempt challenge-response
313
try:
314
challenge = b"test challenge"
315
response = yk.challenge_response(challenge, slot=1, may_block=False)
316
print(f"Challenge-response successful: {response.hex()}")
317
318
except yubico.yubikey_base.YubiKeyVersionError:
319
print("Challenge-response not supported")
320
except yubico.yubikey_base.YubiKeyTimeout:
321
print("Challenge-response requires button press")
322
except yubico.yubikey_base.YubiKeyError as e:
323
print(f"Challenge-response failed: {e.reason}")
324
325
return True
326
327
except yubico.yubikey_base.YubiKeyError as e:
328
if "No YubiKey found" in str(e):
329
print("ERROR: No YubiKey device connected")
330
else:
331
print(f"ERROR: YubiKey device error: {e.reason}")
332
333
except yubico.yubikey_usb_hid.YubiKeyUSBHIDError as e:
334
print(f"ERROR: USB communication error: {e.reason}")
335
print("Try reconnecting the YubiKey")
336
337
except yubico.yubico_exception.InputError as e:
338
print(f"ERROR: Input validation error: {e.reason}")
339
340
except yubico.yubico_exception.YubicoError as e:
341
print(f"ERROR: General YubiKey error: {e.reason}")
342
343
except Exception as e:
344
print(f"ERROR: Unexpected error: {e}")
345
346
return False
347
348
# Run safe operation
349
success = safe_yubikey_operation()
350
if success:
351
print("YubiKey operations completed successfully")
352
else:
353
print("YubiKey operations failed")
354
```
355
356
### Error Recovery Strategies
357
358
Common strategies for recovering from YubiKey errors.
359
360
```python
361
import yubico
362
import time
363
364
def robust_yubikey_connect(max_retries=3, retry_delay=1.0):
365
"""
366
Robustly connect to YubiKey with retry logic.
367
368
Parameters:
369
- max_retries (int): Maximum connection attempts
370
- retry_delay (float): Delay between retry attempts in seconds
371
372
Returns:
373
YubiKey: Connected YubiKey instance or None
374
"""
375
for attempt in range(max_retries):
376
try:
377
yk = yubico.find_yubikey()
378
print(f"Connected to YubiKey on attempt {attempt + 1}")
379
return yk
380
381
except yubico.yubikey_usb_hid.YubiKeyUSBHIDError:
382
print(f"USB error on attempt {attempt + 1}")
383
if attempt < max_retries - 1:
384
print(f"Retrying in {retry_delay} seconds...")
385
time.sleep(retry_delay)
386
387
except yubico.yubikey_base.YubiKeyError as e:
388
if "No YubiKey found" in str(e):
389
print(f"No YubiKey found on attempt {attempt + 1}")
390
if attempt < max_retries - 1:
391
print("Please connect YubiKey and wait...")
392
time.sleep(retry_delay * 2) # Longer delay for device connection
393
else:
394
print(f"YubiKey error: {e.reason}")
395
break
396
397
print("Failed to connect to YubiKey after all retry attempts")
398
return None
399
400
def handle_timeout_with_retry(operation_func, max_retries=2):
401
"""
402
Handle timeout operations with user guidance.
403
404
Parameters:
405
- operation_func: Function that may timeout
406
- max_retries (int): Maximum retry attempts
407
408
Returns:
409
Result of operation_func or None if all attempts fail
410
"""
411
for attempt in range(max_retries):
412
try:
413
return operation_func()
414
415
except yubico.yubikey_base.YubiKeyTimeout:
416
if attempt < max_retries - 1:
417
print("Operation timed out. Please touch YubiKey button when it blinks.")
418
print("Retrying...")
419
else:
420
print("Operation timed out after all retry attempts")
421
422
return None
423
424
# Usage examples
425
yk = robust_yubikey_connect()
426
if yk:
427
# Retry timeout-prone operations
428
def get_serial():
429
return yk.serial(may_block=True)
430
431
serial = handle_timeout_with_retry(get_serial)
432
if serial:
433
print(f"Serial number: {serial}")
434
```
435
436
### Logging and Debugging
437
438
Enable detailed error information for troubleshooting.
439
440
```python
441
import yubico
442
import logging
443
444
# Enable debug logging
445
logging.basicConfig(level=logging.DEBUG)
446
447
try:
448
# Connect with debug output
449
yk = yubico.find_yubikey(debug=True)
450
451
# Operations will show detailed debug information
452
response = yk.challenge_response(b"debug test", slot=1)
453
454
except yubico.yubico_exception.YubicoError as e:
455
# Log detailed error information
456
logging.error(f"YubiKey operation failed: {e}")
457
logging.error(f"Error type: {type(e).__name__}")
458
logging.error(f"Error reason: {e.reason}")
459
460
# Print exception details for debugging
461
import traceback
462
traceback.print_exc()
463
```