Python library for communicating with Yubico YubiKey hardware authentication tokens
—
Comprehensive error handling with specific exception types for different failure modes including device communication errors, configuration errors, timeout conditions, and input validation failures.
All YubiKey exceptions inherit from the base YubicoError class, allowing unified error handling.
class YubicoError(Exception):
"""
Base class for all Yubico exceptions.
All exceptions raised by YubiKey operations inherit from this class,
enabling comprehensive error handling with a single except clause.
Attributes:
- reason (str): Human-readable explanation of the error
"""
def __init__(self, reason):
"""
Initialize exception with error reason.
Parameters:
- reason (str): Explanation of the error
"""
self.reason = reason
def __str__(self):
"""
String representation of the exception.
Returns:
str: Formatted error message with class name and reason
"""Base exception usage:
import yubico
try:
yk = yubico.find_yubikey()
# Perform YubiKey operations...
except yubico.yubico_exception.YubicoError as e:
print(f"YubiKey error: {e.reason}")
# This will catch all YubiKey-related exceptionsExceptions raised for invalid input parameters and data validation failures.
class InputError(YubicoError):
"""
Exception raised for input validation errors.
Raised when function parameters fail validation, such as incorrect
data types, invalid lengths, or out-of-range values.
"""
def __init__(self, reason='input validation error'):
"""
Initialize input error exception.
Parameters:
- reason (str): Specific validation error description
"""Input validation example:
import yubico
from yubico.yubikey_config import YubiKeyConfig
try:
cfg = YubiKeyConfig()
# Invalid AES key length (must be 16 bytes)
cfg.aes_key(b"short_key")
except yubico.yubico_exception.InputError as e:
print(f"Input validation error: {e.reason}")
try:
yk = yubico.find_yubikey()
# Invalid challenge length for OTP mode (must be 6 bytes)
response = yk.challenge(b"too_long_challenge", mode='OTP')
except yubico.yubico_exception.InputError as e:
print(f"Challenge validation error: {e.reason}")Exceptions related to YubiKey device communication and hardware errors.
class YubiKeyError(YubicoError):
"""
Exception raised for YubiKey device operation errors.
Base class for device-specific errors including communication
failures, device not found, and operational errors.
"""
def __init__(self, reason='no details'):
"""
Initialize YubiKey error.
Parameters:
- reason (str): Error description
"""
class YubiKeyUSBHIDError(YubicoError):
"""
Exception raised for USB HID communication errors.
Specific to USB HID transport layer failures, device detection
issues, and low-level communication problems.
"""Device communication example:
import yubico
try:
yk = yubico.find_yubikey()
except yubico.yubikey_base.YubiKeyError as e:
if "No YubiKey found" in str(e):
print("No YubiKey device connected")
else:
print(f"YubiKey device error: {e.reason}")
except yubico.yubikey_usb_hid.YubiKeyUSBHIDError as e:
print(f"USB communication error: {e.reason}")
print("Try unplugging and reconnecting the YubiKey")Exceptions raised when operations exceed time limits or require user interaction.
class YubiKeyTimeout(YubiKeyError):
"""
Exception raised when YubiKey operations time out.
Occurs when operations requiring user interaction (button press)
exceed the timeout period, or when device communication stalls.
"""
def __init__(self, reason='no details'):
"""
Initialize timeout error.
Parameters:
- reason (str): Timeout-specific error description
"""Timeout handling example:
import yubico
yk = yubico.find_yubikey()
try:
# This may timeout if button press is required
serial = yk.serial(may_block=True)
print(f"Serial number: {serial}")
except yubico.yubikey_base.YubiKeyTimeout:
print("Timeout waiting for user interaction")
print("Please touch the YubiKey button when it blinks")
try:
# Challenge-response with button press requirement
response = yk.challenge(
b"test challenge",
mode='HMAC',
slot=1,
may_block=True
)
except yubico.yubikey_base.YubiKeyTimeout:
print("Challenge-response timed out")
print("Check if button press is required for this configuration")Exceptions raised when operations are not supported by the connected YubiKey version.
class YubiKeyVersionError(YubiKeyError):
"""
Exception raised when YubiKey version doesn't support requested operation.
Occurs when attempting to use features not available on the connected
YubiKey model or firmware version.
"""
def __init__(self, reason='no details'):
"""
Initialize version error.
Parameters:
- reason (str): Version compatibility error description
"""Version compatibility example:
import yubico
yk = yubico.find_yubikey()
print(f"YubiKey version: {yk.version()}")
try:
# Serial number reading requires YubiKey 2.2+
serial = yk.serial()
print(f"Serial number: {serial}")
except yubico.yubikey_base.YubiKeyVersionError:
print("Serial number reading not supported on this YubiKey version")
print("Required: YubiKey 2.2 or later")
try:
# Challenge-response requires YubiKey 2.2+
response = yk.challenge_response(b"test", mode='HMAC', slot=1)
except yubico.yubikey_base.YubiKeyVersionError:
print("Challenge-response not supported on this YubiKey version")
print("Required: YubiKey 2.2 or later")Exceptions specific to YubiKey configuration operations.
class YubiKeyConfigError(YubicoError):
"""
Exception raised for YubiKey configuration errors.
Occurs when configuration parameters are invalid, incompatible
with the target YubiKey, or when configuration write operations fail.
"""Configuration error example:
import yubico
from yubico.yubikey_config import YubiKeyConfig, YubiKeyConfigError
try:
yk = yubico.find_yubikey()
cfg = yk.init_config()
# Invalid configuration (example)
cfg.mode_oath_hotp(b"secret", digits=10) # Invalid digit count
yk.write_config(cfg, slot=1)
except YubiKeyConfigError as e:
print(f"Configuration error: {e.reason}")
except yubico.yubikey_base.YubiKeyVersionError as e:
print(f"Configuration not supported: {e.reason}")Best practices for handling all YubiKey exceptions in applications.
import yubico
from yubico.yubikey_config import YubiKeyConfig, YubiKeyConfigError
def safe_yubikey_operation():
"""
Demonstrate comprehensive YubiKey error handling.
"""
try:
# Find and connect to YubiKey
yk = yubico.find_yubikey(debug=False)
print(f"Connected to YubiKey version {yk.version()}")
# Get device information
try:
serial = yk.serial(may_block=False)
print(f"Serial number: {serial}")
except yubico.yubikey_base.YubiKeyVersionError:
print("Serial number not available on this YubiKey version")
except yubico.yubikey_base.YubiKeyTimeout:
print("Serial number requires button press")
# Attempt challenge-response
try:
challenge = b"test challenge"
response = yk.challenge_response(challenge, slot=1, may_block=False)
print(f"Challenge-response successful: {response.hex()}")
except yubico.yubikey_base.YubiKeyVersionError:
print("Challenge-response not supported")
except yubico.yubikey_base.YubiKeyTimeout:
print("Challenge-response requires button press")
except yubico.yubikey_base.YubiKeyError as e:
print(f"Challenge-response failed: {e.reason}")
return True
except yubico.yubikey_base.YubiKeyError as e:
if "No YubiKey found" in str(e):
print("ERROR: No YubiKey device connected")
else:
print(f"ERROR: YubiKey device error: {e.reason}")
except yubico.yubikey_usb_hid.YubiKeyUSBHIDError as e:
print(f"ERROR: USB communication error: {e.reason}")
print("Try reconnecting the YubiKey")
except yubico.yubico_exception.InputError as e:
print(f"ERROR: Input validation error: {e.reason}")
except yubico.yubico_exception.YubicoError as e:
print(f"ERROR: General YubiKey error: {e.reason}")
except Exception as e:
print(f"ERROR: Unexpected error: {e}")
return False
# Run safe operation
success = safe_yubikey_operation()
if success:
print("YubiKey operations completed successfully")
else:
print("YubiKey operations failed")Common strategies for recovering from YubiKey errors.
import yubico
import time
def robust_yubikey_connect(max_retries=3, retry_delay=1.0):
"""
Robustly connect to YubiKey with retry logic.
Parameters:
- max_retries (int): Maximum connection attempts
- retry_delay (float): Delay between retry attempts in seconds
Returns:
YubiKey: Connected YubiKey instance or None
"""
for attempt in range(max_retries):
try:
yk = yubico.find_yubikey()
print(f"Connected to YubiKey on attempt {attempt + 1}")
return yk
except yubico.yubikey_usb_hid.YubiKeyUSBHIDError:
print(f"USB error on attempt {attempt + 1}")
if attempt < max_retries - 1:
print(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
except yubico.yubikey_base.YubiKeyError as e:
if "No YubiKey found" in str(e):
print(f"No YubiKey found on attempt {attempt + 1}")
if attempt < max_retries - 1:
print("Please connect YubiKey and wait...")
time.sleep(retry_delay * 2) # Longer delay for device connection
else:
print(f"YubiKey error: {e.reason}")
break
print("Failed to connect to YubiKey after all retry attempts")
return None
def handle_timeout_with_retry(operation_func, max_retries=2):
"""
Handle timeout operations with user guidance.
Parameters:
- operation_func: Function that may timeout
- max_retries (int): Maximum retry attempts
Returns:
Result of operation_func or None if all attempts fail
"""
for attempt in range(max_retries):
try:
return operation_func()
except yubico.yubikey_base.YubiKeyTimeout:
if attempt < max_retries - 1:
print("Operation timed out. Please touch YubiKey button when it blinks.")
print("Retrying...")
else:
print("Operation timed out after all retry attempts")
return None
# Usage examples
yk = robust_yubikey_connect()
if yk:
# Retry timeout-prone operations
def get_serial():
return yk.serial(may_block=True)
serial = handle_timeout_with_retry(get_serial)
if serial:
print(f"Serial number: {serial}")Enable detailed error information for troubleshooting.
import yubico
import logging
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
try:
# Connect with debug output
yk = yubico.find_yubikey(debug=True)
# Operations will show detailed debug information
response = yk.challenge_response(b"debug test", slot=1)
except yubico.yubico_exception.YubicoError as e:
# Log detailed error information
logging.error(f"YubiKey operation failed: {e}")
logging.error(f"Error type: {type(e).__name__}")
logging.error(f"Error reason: {e.reason}")
# Print exception details for debugging
import traceback
traceback.print_exc()Install with Tessl CLI
npx tessl i tessl/pypi-python-yubico