Table of Contents
- Error Handling
- Table of Contents
- Introduction
- CTAP2 Error Classification
- CtapError Class Implementation
- ClientError Class and Error Mapping
- Transport Layer Error Handling
- Command Execution Error Propagation
- Common Error Scenarios
- Testing Error Conditions
- Best Practices for Error Handling
- Logging and Debugging
Error Handling
Table of Contents
- Introduction
- CTAP2 Error Classification
- CtapError Class Implementation
- ClientError Class and Error Mapping
- Transport Layer Error Handling
- Command Execution Error Propagation
- Common Error Scenarios
- Testing Error Conditions
- Best Practices for Error Handling
- 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
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"
Diagram sources
Error Code Validation
The constructor implements robust error code validation:
- Known Error Codes: Attempts to convert the integer code to a predefined
CtapError.ERRenumeration value - Unknown Error Codes: Falls back to
CtapError.UNKNOWN_ERRfor unrecognized codes - 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
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
Diagram sources
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
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
Diagram sources
Keep-Alive Message Processing
The transport layer handles KEEPALIVE messages for long-running operations:
- Status Extraction: Parses keep-alive status from response packets
- Callback Invocation: Calls application-provided callbacks
- Duplicate Filtering: Prevents redundant callback invocations
- Error Handling: Validates keep-alive status codes
Section sources
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
Diagram sources
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
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_REQUIREDorCtapError.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.PROCESSINGtimeouts - 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
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
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