FIDO2 Error Handling - FeitianTech/postquantum-webauthn-platform GitHub Wiki

Error Handling

Table of Contents

  1. Introduction
  2. CTAP2 Error Classification
  3. CtapError Class Implementation
  4. ClientError Class and Error Mapping
  5. Transport Layer Error Handling
  6. Command Execution Error Propagation
  7. Common Error Scenarios
  8. Testing Error Conditions
  9. Best Practices for Error Handling
  10. Logging and Debugging

Introduction

The CTAP2 (Client to Authenticator Protocol) implementation in this WebAuthn platform provides a comprehensive error handling system that propagates errors from the authenticator through multiple layers of abstraction, ultimately reaching the application layer. The system is designed to handle various types of failures gracefully, from low-level transport errors to high-level application logic errors.

The error handling architecture follows a layered approach where each layer can transform, log, or propagate errors according to its role in the system. This ensures that applications receive meaningful error information while maintaining the flexibility to handle different types of failures appropriately.

CTAP2 Error Classification

The CTAP2 protocol defines a comprehensive set of status codes that categorize different types of errors that can occur during authenticator operations. These errors are systematically organized into logical groups for easier handling and interpretation.

Core CTAP2 Error Codes

The CtapError.ERR enumeration defines the standard CTAP2 error codes:

Error Code Constant Description
0x00 SUCCESS Operation successful
0x01 INVALID_COMMAND Command not supported
0x02 INVALID_PARAMETER Parameter invalid
0x03 INVALID_LENGTH Data length error
0x04 INVALID_SEQ Invalid sequence number
0x05 TIMEOUT Operation timed out
0x06 CHANNEL_BUSY Channel temporarily busy
0x0A LOCK_REQUIRED Device locked
0x0B INVALID_CHANNEL Invalid channel ID
0x11 CBOR_UNEXPECTED_TYPE CBOR type mismatch
0x12 INVALID_CBOR Malformed CBOR data
0x14 MISSING_PARAMETER Required parameter missing
0x15 LIMIT_EXCEEDED Resource limit exceeded
0x17 FP_DATABASE_FULL Fingerprint database full
0x18 LARGE_BLOB_STORAGE_FULL Large blob storage full
0x19 CREDENTIAL_EXCLUDED Credential excluded
0x21 PROCESSING Processing in progress
0x22 INVALID_CREDENTIAL Credential invalid
0x23 USER_ACTION_PENDING User action required
0x24 OPERATION_PENDING Operation pending
0x25 NO_OPERATIONS No operations pending
0x26 UNSUPPORTED_ALGORITHM Algorithm not supported
0x27 OPERATION_DENIED Operation denied
0x28 KEY_STORE_FULL Key storage full
0x2B UNSUPPORTED_OPTION Option not supported
0x2C INVALID_OPTION Invalid option
0x2D KEEPALIVE_CANCEL Keep-alive canceled
0x2E NO_CREDENTIALS No credentials available
0x2F USER_ACTION_TIMEOUT User action timeout
0x30 NOT_ALLOWED Operation not allowed
0x31-0x35 PIN_* PIN-related errors
0x36 PUAT_REQUIRED Platform U2F token required
0x37 PIN_POLICY_VIOLATION PIN policy violation
0x38 PIN_TOKEN_EXPIRED PIN token expired
0x39 REQUEST_TOO_LARGE Request exceeds size limits
0x3A ACTION_TIMEOUT Action timeout
0x3B UP_REQUIRED User presence required
0x3C UV_BLOCKED User verification blocked
0x3D INTEGRITY_FAILURE Data integrity check failed
0x3E INVALID_SUBCOMMAND Invalid subcommand
0x3F UV_INVALID User verification invalid
0x40 UNAUTHORIZED_PERMISSION Unauthorized permission

Extension and Vendor Error Codes

The protocol also reserves ranges for extension and vendor-specific error codes:

  • Extension Range: 0xE0-0xEF
  • Vendor Range: 0xF0-0xFF
  • Other: 0x7F (generic error)

Section sources

  • fido2/ctap.py

CtapError Class Implementation

The CtapError class serves as the primary exception type for CTAP2 protocol errors. It provides a structured way to handle and represent authenticator-reported errors throughout the system.

Class Architecture

classDiagram
class CtapError {
+ERR code
+__init__(code : int)
+__str__() str
}
class ERR {
<<enumeration>>
+SUCCESS : 0x00
+INVALID_COMMAND : 0x01
+INVALID_PARAMETER : 0x02
+TIMEOUT : 0x05
+CHANNEL_BUSY : 0x06
+PIN_INVALID : 0x31
+USER_ACTION_TIMEOUT : 0x2F
+OTHER : 0x7F
+__str__() str
}
class UNKNOWN_ERR {
+value : int
+name : "UNKNOWN_ERR"
+__repr__() str
+__str__() str
}
CtapError --> ERR : "uses"
CtapError --> UNKNOWN_ERR : "fallback for unknown codes"
Loading

Diagram sources

  • fido2/ctap.py

Error Code Validation

The constructor implements robust error code validation:

  1. Known Error Codes: Attempts to convert the integer code to a predefined CtapError.ERR enumeration value
  2. Unknown Error Codes: Falls back to CtapError.UNKNOWN_ERR for unrecognized codes
  3. String Representation: Provides human-readable error descriptions through the __str__ method

Unknown Error Handling

The UNKNOWN_ERR class provides graceful handling for unrecognized error codes:

  • Fallback Mechanism: Ensures all error codes are represented
  • Consistent Interface: Maintains the same interface as known error codes
  • Debugging Support: Preserves the original numeric value for debugging

Section sources

  • fido2/ctap.py

ClientError Class and Error Mapping

The ClientError class provides a higher-level abstraction for application-facing errors, transforming CTAP2 errors into more meaningful categories for client applications.

Error Categories

The ClientError.ERR enumeration defines five primary error categories:

Error Code Category Description
OTHER_ERROR Generic Unspecified errors
BAD_REQUEST Client-side Invalid requests or parameters
CONFIGURATION_UNSUPPORTED Configuration Unsupported features or options
DEVICE_INELIGIBLE Device Device-specific issues
TIMEOUT Timing Timeout-related failures

Error Transformation Logic

The _ctap2client_err function implements sophisticated error mapping logic:

flowchart TD
A[CTAP2 Error] --> B{Error Classification}
B --> |CREDENTIAL_EXCLUDED,<br/>NO_CREDENTIALS| C[DEVICE_INELIGIBLE]
B --> |KEEPALIVE_CANCEL,<br/>ACTION_TIMEOUT,<br/>USER_ACTION_TIMEOUT| D[TIMEOUT]
B --> |UNSUPPORTED_ALGORITHM,<br/>UNSUPPORTED_OPTION,<br/>KEY_STORE_FULL| E[CONFIGURATION_UNSUPPORTED]
B --> |INVALID_COMMAND,<br/>PIN_* errors,<br/>REQUEST_TOO_LARGE| F[BAD_REQUEST]
B --> |Other| G[OTHER_ERROR]
C --> H[ClientError Instance]
D --> H
E --> H
F --> H
G --> H
Loading

Diagram sources

  • fido2/client/init.py

Specific Error Mappings

Device Eligibility Errors

  • CREDENTIAL_EXCLUDED: Credential is excluded from operations
  • NO_CREDENTIALS: No suitable credentials available
  • Result: Transformed to CLIENTERROR.ERR.DEVICE_INELIGIBLE

Timeout Errors

  • KEEPALIVE_CANCEL: User canceled operation via keep-alive
  • ACTION_TIMEOUT: Action timed out
  • USER_ACTION_TIMEOUT: User action timeout
  • Result: Transformed to CLIENTERROR.ERR.TIMEOUT

Configuration Errors

  • UNSUPPORTED_ALGORITHM: Algorithm not supported by device
  • UNSUPPORTED_OPTION: Feature not supported
  • KEY_STORE_FULL: Storage capacity exceeded
  • Result: Transformed to CLIENTERROR.ERR.CONFIGURATION_UNSUPPORTED

Request Errors

  • INVALID_COMMAND: Invalid command sent to device
  • PIN_ errors*: Various PIN-related failures
  • REQUEST_TOO_LARGE: Request exceeds size limits
  • Result: Transformed to CLIENTERROR.ERR.BAD_REQUEST

Section sources

  • fido2/client/init.py

Transport Layer Error Handling

The HID transport layer implements robust error handling mechanisms to deal with communication failures, connection issues, and protocol violations.

Connection Failure Detection

The ConnectionFailure exception handles transport-level failures:

  • Nonce Mismatch: Invalid initialization response
  • Channel Errors: Wrong channel ID in responses
  • Sequence Violations: Incorrect packet sequencing
  • Protocol Violations: Invalid packet formats

Channel Busy Handling

The transport layer implements automatic retry logic for CHANNEL_BUSY errors:

sequenceDiagram
participant App as Application
participant Transport as HID Transport
participant Device as Authenticator
App->>Transport : send_cbor(command)
Transport->>Device : send packet
Device-->>Transport : CHANNEL_BUSY
Transport->>Transport : wait(0.1s)
Transport->>Device : retry packet
Device-->>Transport : success/error
Transport-->>App : result/error
Loading

Diagram sources

  • fido2/hid/init.py

Keep-Alive Message Processing

The transport layer handles KEEPALIVE messages for long-running operations:

  1. Status Extraction: Parses keep-alive status from response packets
  2. Callback Invocation: Calls application-provided callbacks
  3. Duplicate Filtering: Prevents redundant callback invocations
  4. Error Handling: Validates keep-alive status codes

Section sources

  • fido2/hid/init.py

Command Execution Error Propagation

The send_cbor method demonstrates comprehensive error propagation through the CTAP2 command execution pipeline.

Command Execution Flow

sequenceDiagram
participant App as Application
participant CTAP2 as Ctap2 Class
participant Device as CtapDevice
participant Transport as HID Transport
App->>CTAP2 : send_cbor(cmd, data)
CTAP2->>CTAP2 : validate_message_size()
CTAP2->>Device : call(CTAPHID.CBOR, request)
Device->>Transport : write_packet(request)
Transport->>Transport : send_request()
Transport-->>Device : response_packet
Device->>Device : parse_response()
alt Status == SUCCESS
Device-->>CTAP2 : response_data
CTAP2->>CTAP2 : decode_cbor(response_data)
CTAP2-->>App : decoded_result
else Status != SUCCESS
Device-->>CTAP2 : error_status
CTAP2->>CTAP2 : raise CtapError(error_status)
CTAP2-->>App : CtapError
end
Loading

Diagram sources

  • fido2/ctap2/base.py

Error Propagation Stages

1. Message Size Validation

The send_cbor method validates message size before transmission:

if len(request) > self._max_msg_size:
    raise CtapError(CtapError.ERR.REQUEST_TOO_LARGE)

2. Response Status Checking

After receiving the response, the system checks the status byte:

status = response[0]
if status != 0x00:
    raise CtapError(status)

3. CBOR Decoding Validation

The system performs strict CBOR validation when strict_cbor is enabled:

if self._strict_cbor:
    expected = cbor.encode(decoded)
    if expected != enc:
        raise ValueError("Non-canonical CBOR from Authenticator")

Command-Specific Error Handling

Different CTAP2 commands implement specialized error handling:

Make Credential Errors

  • Credential Exclusion: CtapError.ERR.CREDENTIAL_EXCLUDED
  • Algorithm Unsupported: CtapError.ERR.UNSUPPORTED_ALGORITHM
  • Storage Full: CtapError.ERR.KEY_STORE_FULL

Get Assertion Errors

  • No Credentials: CtapError.ERR.NO_CREDENTIALS
  • User Presence Required: CtapError.ERR.UP_REQUIRED

Section sources

  • fido2/ctap2/base.py

Common Error Scenarios

Timeout Errors

Timeout errors are among the most common failure modes in CTAP2 operations. They can occur at multiple levels:

User Presence Check Failures

  • Symptoms: CtapError.ERR.UP_REQUIRED or CtapError.ERR.USER_ACTION_TIMEOUT
  • Causes: User didn't provide required biometric or touch input
  • Resolution: Implement retry logic with user guidance

Cryptographic Operation Failures

  • Symptoms: CtapError.ERR.PROCESSING timeouts
  • Causes: Complex cryptographic operations exceeding timeout limits
  • Resolution: Increase timeout values or simplify operations

PIN-Related Errors

PIN-related errors require careful handling for security and usability:

PIN Validation Errors

  • PIN_INVALID: CtapError.ERR.PIN_INVALID
  • PIN_BLOCKED: CtapError.ERR.PIN_BLOCKED
  • PIN_NOT_SET: CtapError.ERR.PIN_NOT_SET

PIN Policy Violations

  • PIN_POLICY_VIOLATION: CtapError.ERR.PIN_POLICY_VIOLATION
  • PIN_TOKEN_EXPIRED: CtapError.ERR.PIN_TOKEN_EXPIRED

Transport Layer Errors

Transport-level errors can occur due to hardware or communication issues:

Connection Failures

  • Device Disconnected: Lost USB/HID connection
  • Packet Corruption: Malformed packets during transmission
  • Channel Conflicts: Multiple applications competing for device access

Buffer and Size Issues

  • Request Too Large: CtapError.ERR.REQUEST_TOO_LARGE
  • Response Buffer Overflow: Insufficient buffer space for responses

Section sources

  • fido2/ctap.py

Testing Error Conditions

The test suite provides comprehensive coverage of error handling scenarios through mocked authenticator responses.

Mock Error Responses

Test cases simulate various error conditions by configuring mock device responses:

# Simulate timeout error
device.call.return_value = b"\x05"  # TIMEOUT error code

# Simulate PIN invalid error  
device.call.return_value = b"\x31"  # PIN_INVALID error code

# Simulate credential excluded error
device.call.return_value = b"\x19"  # CREDENTIAL_EXCLUDED error code

Error Propagation Testing

The TestCtap2 class demonstrates error propagation testing:

def test_send_cbor_error(self):
    ctap = self.mock_ctap()
    ctap.device.call.return_value = b"\x05"  # TIMEOUT error
    
    with self.assertRaises(CtapError) as cm:
        ctap.send_cbor(2, b"foobar")
    
    self.assertEqual(cm.exception.code, CtapError.ERR.TIMEOUT)

Keep-Alive Error Testing

The transport layer includes specific tests for keep-alive error handling:

def test_invalid_keepalive_status(self):
    # Test malformed keep-alive status
    recv = struct.pack(">IB", self._channel_id, 
                      TYPE_INIT | CTAPHID.KEEPALIVE)
    recv += b"\xFF"  # Invalid status code
    
    with self.assertRaises(ConnectionFailure):
        self._process_keepalive(recv)

Section sources

  • tests/test_ctap2.py
  • tests/test_hid.py

Best Practices for Error Handling

Application-Level Error Handling

Graceful Degradation

Implement fallback mechanisms for non-critical operations:

try:
    result = authenticator.make_credential(client_data, rp, user)
except ClientError as e:
    if e.code == ClientError.ERR.TIMEOUT:
        # Fallback to simpler authentication
        result = fallback_authentication()
    elif e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED:
        # Disable unsupported features
        disable_unsupported_features()
    else:
        raise  # Re-raise unexpected errors

User-Friendly Error Messages

Convert technical error codes to user-understandable messages:

def get_user_friendly_error(client_error):
    error_map = {
        ClientError.ERR.TIMEOUT: "Operation timed out. Please try again.",
        ClientError.ERR.DEVICE_INELIGIBLE: "No suitable credentials found.",
        ClientError.ERR.BAD_REQUEST: "Invalid request parameters.",
        ClientError.ERR.CONFIGURATION_UNSUPPORTED: "Feature not supported by your device."
    }
    return error_map.get(client_error.code, "An unexpected error occurred.")

Retry Strategies

Implement intelligent retry logic for transient errors:

def execute_with_retry(operation, max_retries=3, backoff_factor=1.5):
    for attempt in range(max_retries):
        try:
            return operation()
        except CtapError as e:
            if e.code == CtapError.ERR.CHANNEL_BUSY and attempt < max_retries - 1:
                time.sleep(backoff_factor ** attempt)
                continue
            raise

Error Recovery Patterns

State Reset

For recoverable errors, implement state reset mechanisms:

def handle_recovery_error(error):
    if isinstance(error, CtapError) and error.code in [
        CtapError.ERR.CHANNEL_BUSY,
        CtapError.ERR.INVALID_CHANNEL
    ]:
        # Reset device connection
        authenticator.reset_connection()
        return True
    return False

Partial Failure Handling

Handle partial failures gracefully:

def process_multiple_credentials(operations):
    successes = []
    failures = []
    
    for op in operations:
        try:
            result = op.execute()
            successes.append(result)
        except CtapError as e:
            if e.code == CtapError.ERR.NO_CREDENTIALS:
                # Continue with next credential
                continue
            failures.append((op, e))
    
    return successes, failures

Security Considerations

Error Information Leakage

Avoid exposing sensitive information in error messages:

def secure_error_handling(operation):
    try:
        return operation()
    except CtapError as e:
        # Log technical details privately
        logger.debug(f"CTAP2 error: {e}")
        
        # Return generic error to user
        raise ClientError(ERR.OTHER_ERROR, "Authentication failed")

Rate Limiting

Implement rate limiting to prevent abuse:

import time
from collections import defaultdict

class ErrorRateLimiter:
    def __init__(self, max_errors=5, window_seconds=60):
        self.max_errors = max_errors
        self.window_seconds = window_seconds
        self.error_counts = defaultdict(list)
    
    def record_error(self, client_id):
        now = time.time()
        self.error_counts[client_id].append(now)
        
        # Remove old errors outside time window
        cutoff = now - self.window_seconds
        self.error_counts[client_id] = [
            timestamp for timestamp in self.error_counts[client_id]
            if timestamp > cutoff
        ]
    
    def is_allowed(self, client_id):
        return len(self.error_counts[client_id]) < self.max_errors

Logging and Debugging

Structured Logging

The system implements structured logging for comprehensive error tracking:

Traffic Logging

The HID transport layer logs all packet traffic for debugging:

logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex())
logger.log(LOG_LEVEL_TRAFFIC, "RECV: %s", recv.hex())

Error Context Logging

Capture contextual information with errors:

try:
    result = authenticator.make_credential(client_data, rp, user)
except CtapError as e:
    logger.error(f"Make credential failed for user {user['id']}: {e}")
    logger.debug(f"RP: {rp}, Client data hash: {client_data.hash.hex()}")
    raise

Debug Information Collection

Device Capabilities Logging

Log device capabilities for troubleshooting:

def log_device_capabilities(authenticator):
    info = authenticator.get_info()
    logger.info(f"Device capabilities: {info.options}")
    logger.info(f"Supported algorithms: {[a['alg'] for a in info.algorithms]}")
    logger.info(f"Max message size: {info.max_msg_size}")

Transaction Tracing

Track individual transactions for debugging:

class TransactionTracer:
    def __init__(self):
        self.transactions = []
    
    def start_transaction(self, operation, params):
        transaction = {
            'operation': operation,
            'params': params,
            'start_time': time.time(),
            'steps': []
        }
        self.current_transaction = transaction
        return transaction
    
    def add_step(self, step_name, result):
        self.current_transaction['steps'].append({
            'step': step_name,
            'result': result,
            'timestamp': time.time()
        })
    
    def complete_transaction(self, success, error=None):
        self.current_transaction['duration'] = time.time() - self.current_transaction['start_time']
        self.current_transaction['success'] = success
        self.current_transaction['error'] = error
        
        if not success:
            logger.error(f"Transaction failed: {self.current_transaction}")
        else:
            logger.debug(f"Transaction completed: {self.current_transaction}")

Diagnostic Tools

Error Analysis Dashboard

Implement diagnostic tools for error analysis:

class ErrorHandlerAnalyzer:
    def __init__(self):
        self.error_stats = defaultdict(lambda: {'count': 0, 'last_seen': None})
    
    def record_error(self, error_code, context=None):
        self.error_stats[error_code]['count'] += 1
        self.error_stats[error_code]['last_seen'] = time.time()
        self.error_stats[error_code]['context'] = context
    
    def get_error_summary(self):
        return {
            code: {
                'count': stats['count'],
                'rate': stats['count'] / (time.time() - stats['last_seen'])
                if stats['last_seen'] else 0
            }
            for code, stats in self.error_stats.items()
        }

Health Monitoring

Monitor system health through error patterns:

class HealthMonitor:
    def __init__(self, alert_threshold=10):
        self.alert_threshold = alert_threshold
        self.error_counts = defaultdict(int)
    
    def check_health(self):
        critical_errors = [
            code for code, count in self.error_counts.items()
            if count > self.alert_threshold
        ]
        
        if critical_errors:
            return {
                'status': 'critical',
                'errors': critical_errors,
                'message': f"High error rate detected: {critical_errors}"
            }
        
        return {'status': 'healthy'}

Section sources

  • fido2/hid/init.py
  • fido2/client/init.py
⚠️ **GitHub.com Fallback** ⚠️