0
# Status Word Handling
1
2
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.
3
4
## Capabilities
5
6
### Status Word Exceptions
7
8
Exception hierarchy for handling different categories of smart card status word errors.
9
10
```python { .api }
11
class SWException(SmartcardException):
12
"""
13
Base exception for smart card status word errors.
14
15
Args:
16
data (list[int]): Response data from APDU
17
sw1 (int): First status word byte (0x00-0xFF)
18
sw2 (int): Second status word byte (0x00-0xFF)
19
"""
20
21
class WarningProcessingException(SWException):
22
"""
23
Non-volatile memory unchanged (SW1=62) or corrupted (SW1=63).
24
Indicates warnings during command processing.
25
"""
26
27
class ExecutionErrorException(SWException):
28
"""
29
Non-volatile memory changed (SW1=64) or unchanged (SW1=65).
30
Indicates execution errors during command processing.
31
"""
32
33
class SecurityRelatedException(SWException):
34
"""
35
Security-related errors (SW1=66).
36
Indicates authentication or access control failures.
37
"""
38
39
class CheckingErrorException(SWException):
40
"""
41
Wrong length or instruction errors (SW1=67-6F).
42
Indicates parameter or command format errors.
43
"""
44
```
45
46
### Error Checker Classes
47
48
Base classes and implementations for checking status words and raising appropriate exceptions.
49
50
```python { .api }
51
class ErrorChecker:
52
"""Abstract base class for status word error checking. Concrete implementations include ISO7816_4ErrorChecker, ISO7816_8ErrorChecker, and ISO7816_9ErrorChecker."""
53
54
def __call__(self, data, sw1, sw2):
55
"""
56
Check status words and raise exception if error detected.
57
58
Args:
59
data (list[int]): Response data from APDU
60
sw1 (int): First status word byte
61
sw2 (int): Second status word byte
62
63
Raises:
64
SWException: If error condition is detected
65
"""
66
67
class ISO7816_4ErrorChecker(ErrorChecker):
68
"""Error checker for ISO 7816-4 status words."""
69
70
class ISO7816_4_SW1ErrorChecker(ErrorChecker):
71
"""Error checker focusing on SW1 values per ISO 7816-4."""
72
73
class ISO7816_8ErrorChecker(ErrorChecker):
74
"""Error checker for ISO 7816-8 (cryptographic) status words."""
75
76
class ISO7816_9ErrorChecker(ErrorChecker):
77
"""Error checker for ISO 7816-9 (enhanced cryptographic) status words."""
78
79
class op21_ErrorChecker(ErrorChecker):
80
"""Error checker for Open Platform 2.1 status words."""
81
```
82
83
### Error Checking Chain
84
85
Mechanism for combining multiple error checkers in a processing chain.
86
87
```python { .api }
88
class ErrorCheckingChain:
89
"""Chain multiple error checkers for comprehensive status word analysis."""
90
91
def __init__(self, chain, strategy):
92
"""
93
Initialize error checking chain.
94
95
Args:
96
chain (list): List to append this error checker to
97
strategy (ErrorChecker): Error checking strategy to add to chain
98
"""
99
100
def addErrorChecker(self, errorchecker):
101
"""
102
Add an error checker to the chain.
103
104
Args:
105
errorchecker (ErrorChecker): Error checker to add
106
"""
107
108
def removeErrorChecker(self, errorchecker):
109
"""
110
Remove an error checker from the chain.
111
112
Args:
113
errorchecker (ErrorChecker): Error checker to remove
114
"""
115
116
def __call__(self, data, sw1, sw2):
117
"""
118
Execute all error checkers in the chain.
119
120
Args:
121
data (list[int]): Response data
122
sw1 (int): First status word
123
sw2 (int): Second status word
124
125
Raises:
126
SWException: If any checker detects an error
127
"""
128
```
129
130
## Usage Examples
131
132
### Basic Status Word Checking
133
134
```python
135
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
136
from smartcard.sw.SWExceptions import SWException
137
from smartcard import Session
138
139
def check_apdu_response(command, session):
140
"""Send APDU and check status words."""
141
142
# Create error checker
143
error_checker = ISO7816_4ErrorChecker()
144
145
try:
146
# Send command
147
response, sw1, sw2 = session.sendCommandAPDU(command)
148
149
# Check for errors
150
error_checker(response, sw1, sw2)
151
152
print(f"✓ Command successful: {sw1:02X} {sw2:02X}")
153
return response
154
155
except SWException as e:
156
print(f"✗ Status word error: {e}")
157
print(f" SW1 SW2: {e.sw1:02X} {e.sw2:02X}")
158
if hasattr(e, 'data') and e.data:
159
print(f" Data: {' '.join(f'{b:02X}' for b in e.data)}")
160
raise
161
162
# Example usage
163
try:
164
session = Session()
165
166
# Valid command (should succeed)
167
SELECT_MF = [0x00, 0xA4, 0x00, 0x00]
168
response = check_apdu_response(SELECT_MF, session)
169
170
# Invalid command (will likely fail)
171
INVALID_CMD = [0xFF, 0xFF, 0xFF, 0xFF]
172
response = check_apdu_response(INVALID_CMD, session)
173
174
session.close()
175
176
except Exception as e:
177
print(f"Session error: {e}")
178
```
179
180
### Error Checking Chain
181
182
```python
183
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
184
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
185
from smartcard.sw.ISO7816_8ErrorChecker import ISO7816_8ErrorChecker
186
from smartcard.sw.SWExceptions import *
187
from smartcard import Session
188
189
def comprehensive_error_checking():
190
"""Demonstrate error checking chain usage."""
191
192
# Create error checking chain
193
chain = ErrorCheckingChain()
194
chain.addErrorChecker(ISO7816_4ErrorChecker())
195
chain.addErrorChecker(ISO7816_8ErrorChecker())
196
197
session = Session()
198
199
commands = [
200
([0x00, 0xA4, 0x00, 0x00], "SELECT Master File"),
201
([0x00, 0xCA, 0x9F, 0x7F, 0x00], "GET DATA"),
202
([0x00, 0x84, 0x00, 0x00, 0x08], "GET CHALLENGE"),
203
([0xFF, 0xFF, 0xFF, 0xFF], "Invalid Command")
204
]
205
206
for command, description in commands:
207
try:
208
print(f"\nTesting: {description}")
209
print(f"Command: {' '.join(f'{b:02X}' for b in command)}")
210
211
response, sw1, sw2 = session.sendCommandAPDU(command)
212
213
# Apply error checking chain
214
chain(response, sw1, sw2)
215
216
print(f"✓ Success: {sw1:02X} {sw2:02X}")
217
if response:
218
print(f" Response: {' '.join(f'{b:02X}' for b in response)}")
219
220
except WarningProcessingException as e:
221
print(f"⚠ Warning: {e}")
222
except ExecutionErrorException as e:
223
print(f"✗ Execution Error: {e}")
224
except SecurityRelatedException as e:
225
print(f"🔒 Security Error: {e}")
226
except CheckingErrorException as e:
227
print(f"📋 Parameter Error: {e}")
228
except SWException as e:
229
print(f"❌ Status Word Error: {e}")
230
231
session.close()
232
233
comprehensive_error_checking()
234
```
235
236
### Custom Error Checker
237
238
```python
239
from smartcard.sw.ErrorChecker import ErrorChecker
240
from smartcard.sw.SWExceptions import SWException
241
242
class CustomApplicationException(SWException):
243
"""Custom exception for application-specific errors."""
244
pass
245
246
class ApplicationErrorChecker(ErrorChecker):
247
"""Custom error checker for application-specific status words."""
248
249
def __call__(self, data, sw1, sw2):
250
# Check for application-specific error patterns
251
252
# Example: Custom application returns 0x90 0x01 for partial success
253
if sw1 == 0x90 and sw2 == 0x01:
254
raise CustomApplicationException(
255
data, sw1, sw2,
256
"Partial success - some data may be incomplete"
257
)
258
259
# Example: Application-specific authentication error
260
if sw1 == 0x63 and 0xC0 <= sw2 <= 0xCF:
261
remaining_tries = sw2 & 0x0F
262
raise CustomApplicationException(
263
data, sw1, sw2,
264
f"Authentication failed - {remaining_tries} tries remaining"
265
)
266
267
# Example: Application-specific file errors
268
if sw1 == 0x94:
269
error_messages = {
270
0x00: "No current EF",
271
0x02: "Address range exceeded",
272
0x04: "File ID not found",
273
0x08: "File incompatible with command"
274
}
275
message = error_messages.get(sw2, f"File error: {sw2:02X}")
276
raise CustomApplicationException(data, sw1, sw2, message)
277
278
def test_custom_error_checker():
279
"""Test custom error checker implementation."""
280
281
checker = ApplicationErrorChecker()
282
283
test_cases = [
284
([], 0x90, 0x00, "Should pass"),
285
([], 0x90, 0x01, "Partial success"),
286
([], 0x63, 0xC3, "Auth failed, 3 tries left"),
287
([], 0x94, 0x04, "File not found"),
288
([], 0x94, 0xFF, "Unknown file error")
289
]
290
291
for data, sw1, sw2, description in test_cases:
292
try:
293
print(f"\nTesting: {description} ({sw1:02X} {sw2:02X})")
294
checker(data, sw1, sw2)
295
print("✓ No error detected")
296
297
except CustomApplicationException as e:
298
print(f"🔍 Custom error: {e}")
299
except Exception as e:
300
print(f"❌ Unexpected error: {e}")
301
302
test_custom_error_checker()
303
```
304
305
### Connection-Level Error Checking
306
307
```python
308
from smartcard.CardRequest import CardRequest
309
from smartcard.CardType import AnyCardType
310
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
311
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
312
313
def connection_with_error_checking():
314
"""Demonstrate setting error checking at connection level."""
315
316
# Wait for card
317
cardrequest = CardRequest(timeout=10, cardType=AnyCardType())
318
319
try:
320
cardservice = cardrequest.waitforcard()
321
322
with cardservice:
323
connection = cardservice.connection
324
325
# Set up error checking chain for this connection
326
error_chain = ErrorCheckingChain()
327
error_chain.addErrorChecker(ISO7816_4ErrorChecker())
328
connection.setErrorCheckingChain(error_chain)
329
330
print("Error checking enabled for connection")
331
332
# Now all transmit operations will automatically check status words
333
try:
334
# This will automatically check status words
335
response, sw1, sw2 = connection.transmit([0x00, 0xA4, 0x00, 0x00])
336
print(f"✓ SELECT successful: {sw1:02X} {sw2:02X}")
337
338
# This might fail and raise an exception automatically
339
response, sw1, sw2 = connection.transmit([0xFF, 0xFF, 0xFF, 0xFF])
340
print(f"✓ Invalid command succeeded: {sw1:02X} {sw2:02X}")
341
342
except Exception as e:
343
print(f"🚫 Connection error checking caught: {e}")
344
345
except Exception as e:
346
print(f"Card request failed: {e}")
347
348
connection_with_error_checking()
349
```
350
351
### Status Word Analysis
352
353
```python
354
def analyze_status_words(sw1, sw2):
355
"""Analyze and explain status word meanings."""
356
357
print(f"Status Words: {sw1:02X} {sw2:02X}")
358
359
# Success cases
360
if sw1 == 0x90 and sw2 == 0x00:
361
print("✓ Success - Command completed successfully")
362
return
363
364
if sw1 == 0x61:
365
print(f"✓ Success - {sw2} bytes available with GET RESPONSE")
366
return
367
368
# Warning cases (SW1 = 62, 63)
369
if sw1 == 0x62:
370
warnings = {
371
0x00: "No information given (NV-RAM not changed)",
372
0x81: "Part of returned data may be corrupted",
373
0x82: "End of file/record reached before reading Le bytes",
374
0x83: "Selected file invalidated",
375
0x84: "Selected file is not valid (FCI not formated)"
376
}
377
msg = warnings.get(sw2, f"Warning: Non-volatile memory unchanged ({sw2:02X})")
378
print(f"⚠ {msg}")
379
380
elif sw1 == 0x63:
381
if 0xC0 <= sw2 <= 0xCF:
382
tries = sw2 & 0x0F
383
print(f"⚠ Authentication failed - {tries} tries remaining")
384
else:
385
warnings = {
386
0x00: "No information given (NV-RAM changed)",
387
0x81: "File filled up by the last write"
388
}
389
msg = warnings.get(sw2, f"Warning: Non-volatile memory changed ({sw2:02X})")
390
print(f"⚠ {msg}")
391
392
# Error cases (SW1 = 64, 65, 66, 67-6F)
393
elif sw1 == 0x64:
394
print(f"❌ Execution error: Non-volatile memory changed ({sw2:02X})")
395
396
elif sw1 == 0x65:
397
print(f"❌ Execution error: Non-volatile memory unchanged ({sw2:02X})")
398
399
elif sw1 == 0x66:
400
print(f"🔒 Security error: {sw2:02X}")
401
402
elif 0x67 <= sw1 <= 0x6F:
403
error_types = {
404
0x67: "Wrong length",
405
0x68: "Function in CLA not supported",
406
0x69: "Command not allowed",
407
0x6A: "Wrong parameters P1-P2",
408
0x6B: "Wrong parameters P1-P2",
409
0x6C: f"Wrong Le field (correct length: {sw2})",
410
0x6D: "Instruction code not supported",
411
0x6E: "Class not supported",
412
0x6F: "No precise diagnosis"
413
}
414
error_msg = error_types.get(sw1, f"Parameter error ({sw1:02X})")
415
print(f"📋 {error_msg}: {sw2:02X}")
416
417
else:
418
print(f"❓ Unknown status words: {sw1:02X} {sw2:02X}")
419
420
# Test status word analysis
421
test_status_words = [
422
(0x90, 0x00), (0x61, 0x10), (0x62, 0x83), (0x63, 0xC2),
423
(0x64, 0x00), (0x66, 0x00), (0x67, 0x00), (0x6C, 0x08),
424
(0x6D, 0x00), (0x6E, 0x00), (0x9F, 0x10)
425
]
426
427
print("Status Word Analysis:")
428
for sw1, sw2 in test_status_words:
429
print()
430
analyze_status_words(sw1, sw2)
431
```
432
433
## Related Types
434
435
```python { .api }
436
# Exception hierarchy
437
class SmartcardException(Exception):
438
"""Base exception for smartcard operations."""
439
440
class SWException(SmartcardException):
441
"""Base class for status word exceptions."""
442
def __init__(self, data, sw1, sw2, message=""):
443
self.data = data
444
self.sw1 = sw1
445
self.sw2 = sw2
446
447
# Type aliases
448
StatusWord = int # SW1 or SW2 (0x00-0xFF)
449
ResponseData = list[int] # APDU response data
450
ErrorChecker = callable # Function/class that checks status words
451
```