CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-python-yubico

Python library for communicating with Yubico YubiKey hardware authentication tokens

Pending
Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

Comprehensive error handling with specific exception types for different failure modes including device communication errors, configuration errors, timeout conditions, and input validation failures.

Capabilities

Exception Hierarchy

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 exceptions

Input Validation Exceptions

Exceptions 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}")

Device Communication Exceptions

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")

Timeout Exceptions

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")

Version Compatibility Exceptions

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")

Configuration Exceptions

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}")

Comprehensive Error Handling

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")

Error Recovery Strategies

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}")

Logging and Debugging

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

docs

challenge-response.md

configuration.md

device-discovery.md

device-interface.md

exceptions.md

index.md

utilities.md

tile.json