Smartcard library for Python providing PC/SC interface for smart card communication
—
Structured error checking and exception handling for smart card status words (SW1, SW2) with support for various ISO standards and custom error checking chains. Status words indicate the result of APDU command execution.
Exception hierarchy for handling different categories of smart card status word errors.
class SWException(SmartcardException):
"""
Base exception for smart card status word errors.
Args:
data (list[int]): Response data from APDU
sw1 (int): First status word byte (0x00-0xFF)
sw2 (int): Second status word byte (0x00-0xFF)
"""
class WarningProcessingException(SWException):
"""
Non-volatile memory unchanged (SW1=62) or corrupted (SW1=63).
Indicates warnings during command processing.
"""
class ExecutionErrorException(SWException):
"""
Non-volatile memory changed (SW1=64) or unchanged (SW1=65).
Indicates execution errors during command processing.
"""
class SecurityRelatedException(SWException):
"""
Security-related errors (SW1=66).
Indicates authentication or access control failures.
"""
class CheckingErrorException(SWException):
"""
Wrong length or instruction errors (SW1=67-6F).
Indicates parameter or command format errors.
"""Base classes and implementations for checking status words and raising appropriate exceptions.
class ErrorChecker:
"""Abstract base class for status word error checking. Concrete implementations include ISO7816_4ErrorChecker, ISO7816_8ErrorChecker, and ISO7816_9ErrorChecker."""
def __call__(self, data, sw1, sw2):
"""
Check status words and raise exception if error detected.
Args:
data (list[int]): Response data from APDU
sw1 (int): First status word byte
sw2 (int): Second status word byte
Raises:
SWException: If error condition is detected
"""
class ISO7816_4ErrorChecker(ErrorChecker):
"""Error checker for ISO 7816-4 status words."""
class ISO7816_4_SW1ErrorChecker(ErrorChecker):
"""Error checker focusing on SW1 values per ISO 7816-4."""
class ISO7816_8ErrorChecker(ErrorChecker):
"""Error checker for ISO 7816-8 (cryptographic) status words."""
class ISO7816_9ErrorChecker(ErrorChecker):
"""Error checker for ISO 7816-9 (enhanced cryptographic) status words."""
class op21_ErrorChecker(ErrorChecker):
"""Error checker for Open Platform 2.1 status words."""Mechanism for combining multiple error checkers in a processing chain.
class ErrorCheckingChain:
"""Chain multiple error checkers for comprehensive status word analysis."""
def __init__(self, chain, strategy):
"""
Initialize error checking chain.
Args:
chain (list): List to append this error checker to
strategy (ErrorChecker): Error checking strategy to add to chain
"""
def addErrorChecker(self, errorchecker):
"""
Add an error checker to the chain.
Args:
errorchecker (ErrorChecker): Error checker to add
"""
def removeErrorChecker(self, errorchecker):
"""
Remove an error checker from the chain.
Args:
errorchecker (ErrorChecker): Error checker to remove
"""
def __call__(self, data, sw1, sw2):
"""
Execute all error checkers in the chain.
Args:
data (list[int]): Response data
sw1 (int): First status word
sw2 (int): Second status word
Raises:
SWException: If any checker detects an error
"""from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
from smartcard.sw.SWExceptions import SWException
from smartcard import Session
def check_apdu_response(command, session):
"""Send APDU and check status words."""
# Create error checker
error_checker = ISO7816_4ErrorChecker()
try:
# Send command
response, sw1, sw2 = session.sendCommandAPDU(command)
# Check for errors
error_checker(response, sw1, sw2)
print(f"✓ Command successful: {sw1:02X} {sw2:02X}")
return response
except SWException as e:
print(f"✗ Status word error: {e}")
print(f" SW1 SW2: {e.sw1:02X} {e.sw2:02X}")
if hasattr(e, 'data') and e.data:
print(f" Data: {' '.join(f'{b:02X}' for b in e.data)}")
raise
# Example usage
try:
session = Session()
# Valid command (should succeed)
SELECT_MF = [0x00, 0xA4, 0x00, 0x00]
response = check_apdu_response(SELECT_MF, session)
# Invalid command (will likely fail)
INVALID_CMD = [0xFF, 0xFF, 0xFF, 0xFF]
response = check_apdu_response(INVALID_CMD, session)
session.close()
except Exception as e:
print(f"Session error: {e}")from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
from smartcard.sw.ISO7816_8ErrorChecker import ISO7816_8ErrorChecker
from smartcard.sw.SWExceptions import *
from smartcard import Session
def comprehensive_error_checking():
"""Demonstrate error checking chain usage."""
# Create error checking chain
chain = ErrorCheckingChain()
chain.addErrorChecker(ISO7816_4ErrorChecker())
chain.addErrorChecker(ISO7816_8ErrorChecker())
session = Session()
commands = [
([0x00, 0xA4, 0x00, 0x00], "SELECT Master File"),
([0x00, 0xCA, 0x9F, 0x7F, 0x00], "GET DATA"),
([0x00, 0x84, 0x00, 0x00, 0x08], "GET CHALLENGE"),
([0xFF, 0xFF, 0xFF, 0xFF], "Invalid Command")
]
for command, description in commands:
try:
print(f"\nTesting: {description}")
print(f"Command: {' '.join(f'{b:02X}' for b in command)}")
response, sw1, sw2 = session.sendCommandAPDU(command)
# Apply error checking chain
chain(response, sw1, sw2)
print(f"✓ Success: {sw1:02X} {sw2:02X}")
if response:
print(f" Response: {' '.join(f'{b:02X}' for b in response)}")
except WarningProcessingException as e:
print(f"⚠ Warning: {e}")
except ExecutionErrorException as e:
print(f"✗ Execution Error: {e}")
except SecurityRelatedException as e:
print(f"🔒 Security Error: {e}")
except CheckingErrorException as e:
print(f"📋 Parameter Error: {e}")
except SWException as e:
print(f"❌ Status Word Error: {e}")
session.close()
comprehensive_error_checking()from smartcard.sw.ErrorChecker import ErrorChecker
from smartcard.sw.SWExceptions import SWException
class CustomApplicationException(SWException):
"""Custom exception for application-specific errors."""
pass
class ApplicationErrorChecker(ErrorChecker):
"""Custom error checker for application-specific status words."""
def __call__(self, data, sw1, sw2):
# Check for application-specific error patterns
# Example: Custom application returns 0x90 0x01 for partial success
if sw1 == 0x90 and sw2 == 0x01:
raise CustomApplicationException(
data, sw1, sw2,
"Partial success - some data may be incomplete"
)
# Example: Application-specific authentication error
if sw1 == 0x63 and 0xC0 <= sw2 <= 0xCF:
remaining_tries = sw2 & 0x0F
raise CustomApplicationException(
data, sw1, sw2,
f"Authentication failed - {remaining_tries} tries remaining"
)
# Example: Application-specific file errors
if sw1 == 0x94:
error_messages = {
0x00: "No current EF",
0x02: "Address range exceeded",
0x04: "File ID not found",
0x08: "File incompatible with command"
}
message = error_messages.get(sw2, f"File error: {sw2:02X}")
raise CustomApplicationException(data, sw1, sw2, message)
def test_custom_error_checker():
"""Test custom error checker implementation."""
checker = ApplicationErrorChecker()
test_cases = [
([], 0x90, 0x00, "Should pass"),
([], 0x90, 0x01, "Partial success"),
([], 0x63, 0xC3, "Auth failed, 3 tries left"),
([], 0x94, 0x04, "File not found"),
([], 0x94, 0xFF, "Unknown file error")
]
for data, sw1, sw2, description in test_cases:
try:
print(f"\nTesting: {description} ({sw1:02X} {sw2:02X})")
checker(data, sw1, sw2)
print("✓ No error detected")
except CustomApplicationException as e:
print(f"🔍 Custom error: {e}")
except Exception as e:
print(f"❌ Unexpected error: {e}")
test_custom_error_checker()from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
def connection_with_error_checking():
"""Demonstrate setting error checking at connection level."""
# Wait for card
cardrequest = CardRequest(timeout=10, cardType=AnyCardType())
try:
cardservice = cardrequest.waitforcard()
with cardservice:
connection = cardservice.connection
# Set up error checking chain for this connection
error_chain = ErrorCheckingChain()
error_chain.addErrorChecker(ISO7816_4ErrorChecker())
connection.setErrorCheckingChain(error_chain)
print("Error checking enabled for connection")
# Now all transmit operations will automatically check status words
try:
# This will automatically check status words
response, sw1, sw2 = connection.transmit([0x00, 0xA4, 0x00, 0x00])
print(f"✓ SELECT successful: {sw1:02X} {sw2:02X}")
# This might fail and raise an exception automatically
response, sw1, sw2 = connection.transmit([0xFF, 0xFF, 0xFF, 0xFF])
print(f"✓ Invalid command succeeded: {sw1:02X} {sw2:02X}")
except Exception as e:
print(f"🚫 Connection error checking caught: {e}")
except Exception as e:
print(f"Card request failed: {e}")
connection_with_error_checking()def analyze_status_words(sw1, sw2):
"""Analyze and explain status word meanings."""
print(f"Status Words: {sw1:02X} {sw2:02X}")
# Success cases
if sw1 == 0x90 and sw2 == 0x00:
print("✓ Success - Command completed successfully")
return
if sw1 == 0x61:
print(f"✓ Success - {sw2} bytes available with GET RESPONSE")
return
# Warning cases (SW1 = 62, 63)
if sw1 == 0x62:
warnings = {
0x00: "No information given (NV-RAM not changed)",
0x81: "Part of returned data may be corrupted",
0x82: "End of file/record reached before reading Le bytes",
0x83: "Selected file invalidated",
0x84: "Selected file is not valid (FCI not formated)"
}
msg = warnings.get(sw2, f"Warning: Non-volatile memory unchanged ({sw2:02X})")
print(f"⚠ {msg}")
elif sw1 == 0x63:
if 0xC0 <= sw2 <= 0xCF:
tries = sw2 & 0x0F
print(f"⚠ Authentication failed - {tries} tries remaining")
else:
warnings = {
0x00: "No information given (NV-RAM changed)",
0x81: "File filled up by the last write"
}
msg = warnings.get(sw2, f"Warning: Non-volatile memory changed ({sw2:02X})")
print(f"⚠ {msg}")
# Error cases (SW1 = 64, 65, 66, 67-6F)
elif sw1 == 0x64:
print(f"❌ Execution error: Non-volatile memory changed ({sw2:02X})")
elif sw1 == 0x65:
print(f"❌ Execution error: Non-volatile memory unchanged ({sw2:02X})")
elif sw1 == 0x66:
print(f"🔒 Security error: {sw2:02X}")
elif 0x67 <= sw1 <= 0x6F:
error_types = {
0x67: "Wrong length",
0x68: "Function in CLA not supported",
0x69: "Command not allowed",
0x6A: "Wrong parameters P1-P2",
0x6B: "Wrong parameters P1-P2",
0x6C: f"Wrong Le field (correct length: {sw2})",
0x6D: "Instruction code not supported",
0x6E: "Class not supported",
0x6F: "No precise diagnosis"
}
error_msg = error_types.get(sw1, f"Parameter error ({sw1:02X})")
print(f"📋 {error_msg}: {sw2:02X}")
else:
print(f"❓ Unknown status words: {sw1:02X} {sw2:02X}")
# Test status word analysis
test_status_words = [
(0x90, 0x00), (0x61, 0x10), (0x62, 0x83), (0x63, 0xC2),
(0x64, 0x00), (0x66, 0x00), (0x67, 0x00), (0x6C, 0x08),
(0x6D, 0x00), (0x6E, 0x00), (0x9F, 0x10)
]
print("Status Word Analysis:")
for sw1, sw2 in test_status_words:
print()
analyze_status_words(sw1, sw2)# Exception hierarchy
class SmartcardException(Exception):
"""Base exception for smartcard operations."""
class SWException(SmartcardException):
"""Base class for status word exceptions."""
def __init__(self, data, sw1, sw2, message=""):
self.data = data
self.sw1 = sw1
self.sw2 = sw2
# Type aliases
StatusWord = int # SW1 or SW2 (0x00-0xFF)
ResponseData = list[int] # APDU response data
ErrorChecker = callable # Function/class that checks status wordsInstall with Tessl CLI
npx tessl i tessl/pypi-pyscard