API change: substitute new PURPOSE_GET_RESPONSE for PURPOSE_RETRY to match the actual semantics, add a different value for PURPOSE_RETRY to match the description

Refactor READ BINARY functionality out of iso_7816_4 into a building block
Implement READ BINARY (and "cat" command) for RFID storage cards using the new building block


git-svn-id: svn+ssh://localhost/home/henryk/svn/cyberflex-shell/trunk@207 f711b948-2313-0410-aaa9-d29f33439f0b
This commit is contained in:
hploetz 2007-06-02 06:14:18 +00:00
parent 85335da1b6
commit 62a06241c0
5 changed files with 76 additions and 42 deletions

View File

@ -93,3 +93,43 @@ class Card_with_80_aa(Card_with_ls):
"List EFs in current DF"
result = self.list_x(2)
print "EFs: " + ", ".join([utils.hexdump(a, short=True) for a in result])
class Card_with_read_binary:
DATA_UNIT_SIZE=1
def read_binary_file(self, offset = 0):
"""Read from the currently selected EF.
Repeat calls to READ BINARY as necessary to get the whole EF."""
if offset >= 1<<15:
raise ValueError, "offset is limited to 15 bits"
contents = ""
had_one = False
while True:
command = C_APDU(self.APDU_READ_BINARY, p1 = offset >> 8, p2 = (offset & 0xff))
result = self.send_apdu(command)
if len(result.data) > 0:
contents = contents + result.data
offset = offset + (len(result.data) / self.DATA_UNIT_SIZE)
if not self.check_sw(result.sw):
break
else:
had_one = True
if had_one: ## If there was at least one successful pass, ignore any error SW. It probably only means "end of file"
self.sw_changed = False
return contents
def cmd_cat(self):
"Print a hexdump of the currently selected file (e.g. consecutive READ BINARY)"
contents = self.read_binary_file()
self.last_result = R_APDU(contents + self.last_sw)
print utils.hexdump(contents)
COMMANDS = {
"cat": cmd_cat,
}

View File

@ -16,20 +16,23 @@ DEBUG = True
## Constants for check_sw()
PURPOSE_SUCCESS = 1 # Command executed successful
PURPOSE_RETRY = 2 # Command executed successful but needs retry with correct length
PURPOSE_GET_RESPONSE = 2 # Command executed successful but needs GET RESPONSE with correct length
PURPOSE_SM_OK = 3 # Command not executed successful or with warnings, but response still contains SM objects
PURPOSE_RETRY = 4 # Command would be executed successful but needs retry with correct length
_GENERIC_NAME = "Generic"
class Card:
DRIVER_NAME = [_GENERIC_NAME]
APDU_GET_RESPONSE = C_APDU(ins=0xc0)
APDU_VERIFY_PIN = C_APDU(ins=0x20)
PURPOSE_SUCCESS, PURPOSE_RETRY, PURPOSE_SM_OK = PURPOSE_SUCCESS, PURPOSE_RETRY, PURPOSE_SM_OK
PURPOSE_SUCCESS, PURPOSE_GET_RESPONSE, PURPOSE_SM_OK, PURPOSE_RETRY = PURPOSE_SUCCESS, PURPOSE_GET_RESPONSE, PURPOSE_SM_OK, PURPOSE_RETRY
## Map for check_sw()
STATUS_MAP = {
PURPOSE_SUCCESS: ("\x90\x00", ),
PURPOSE_RETRY: ("61??", ), ## If this is received then GET RESPONSE should be called with SW2
PURPOSE_SM_OK: ("\x90\x00",)
PURPOSE_GET_RESPONSE: ("61??", ), ## If this is received then GET RESPONSE should be called with SW2
PURPOSE_SM_OK: ("\x90\x00",),
PURPOSE_RETRY: (), ## Theoretically this would contain "6C??", but I dare not automatically resending a command for _all_ card types
## Instead, card types for which this is safe should set it in their own STATUS_MAP
}
## Note: an item in this list must be a tuple of (atr, mask) where atr is a binary
## string and mask a binary mask. Alternatively mask may be None, then ATR must be a regex
@ -86,6 +89,7 @@ class Card:
"\xD2\x76\x00\x00\x05": ("Giesecke & Devrient", ),
"\xD2\x76\x00\x00\x40": ("Zentralinstitut fuer die Kassenaerztliche Versorgung in der Bundesrepublik Deutschland", ), # hpc-use-cases-01.pdf
"\xa0\x00\x00\x02\x47": ("ICAO", ),
"\xa0\x00\x00\x03\x06": ("PC/SC Workgroup", ),
}
def _decode_df_name(self, value):
@ -216,10 +220,14 @@ class Card:
def _send_with_retry(self, apdu):
result = self._real_send(apdu)
if self.check_sw(result.sw, PURPOSE_RETRY):
if self.check_sw(result.sw, PURPOSE_GET_RESPONSE):
## Need to call GetResponse
gr_apdu = C_APDU(self.APDU_GET_RESPONSE, le = result.sw2) # FIXME
result = R_APDU(self._real_send(gr_apdu))
elif self.check_sw(result.sw, PURPOSE_RETRY) and apdu.Le == 0:
## Retry with correct Le
gr_apdu = C_APDU(apdu, le = result.sw2)
result = R_APDU(self._real_send(gr_apdu))
return result

View File

@ -5,7 +5,7 @@ class GSM_Card(Card):
DRIVER_NAME = ["GSM"]
APDU_GET_RESPONSE = C_APDU("\xa0\xC0\x00\x00")
STATUS_MAP = {
PURPOSE_RETRY: ("9F??", )
PURPOSE_GET_RESPONSE: ("9F??", )
}
ATRS = [

View File

@ -1,8 +1,9 @@
import TLV_utils
from generic_card import *
from generic_application import Application
import building_blocks
class ISO_7816_4_Card(Card):
class ISO_7816_4_Card(building_blocks.Card_with_read_binary,Card):
APDU_SELECT_APPLICATION = C_APDU(ins=0xa4,p1=0x04)
APDU_SELECT_FILE = C_APDU(ins=0xa4, le=0)
APDU_READ_BINARY = C_APDU(ins=0xb0,le=0)
@ -56,32 +57,6 @@ class ISO_7816_4_Card(Card):
if len(result.data) > 0:
print utils.hexdump(result.data)
print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS)
def read_binary_file(self, offset = 0):
"""Read from the currently selected EF.
Repeat calls to READ BINARY as necessary to get the whole EF."""
if offset >= 1<<15:
raise ValueError, "offset is limited to 15 bits"
contents = ""
had_one = False
while True:
command = C_APDU(self.APDU_READ_BINARY, p1 = offset >> 8, p2 = (offset & 0xff))
result = self.send_apdu(command)
if len(result.data) > 0:
contents = contents + result.data
offset = offset + len(result.data)
if not self.check_sw(result.sw):
break
else:
had_one = True
if had_one: ## If there was at least one successful pass, ignore any error SW. It probably only means "end of file"
self.sw_changed = False
return contents
def read_record(self, p1 = 0, p2 = 0, le = 0):
"Read a record from the currently selected file"
@ -89,12 +64,6 @@ class ISO_7816_4_Card(Card):
result = self.send_apdu(command)
return result.data
def cmd_cat(self):
"Print a hexdump of the currently selected file (e.g. consecutive READ BINARY)"
contents = self.read_binary_file()
self.last_result = R_APDU(contents + self.last_sw)
print utils.hexdump(contents)
def cmd_read_record(self, p1 = None, p2 = None, le = "0"):
"Read a record"
if p1 is None and p2 is None:
@ -163,11 +132,11 @@ class ISO_7816_4_Card(Card):
] )
COMMANDS = dict(Card.COMMANDS)
COMMANDS.update(building_blocks.Card_with_read_binary.COMMANDS)
COMMANDS.update( {
"select_application": cmd_selectapplication,
"select_file": cmd_selectfile,
"cd": cmd_cd,
"cat": cmd_cat,
"open": cmd_open,
"read_record": cmd_read_record,
"next_record": cmd_next_record,

View File

@ -1,5 +1,6 @@
import utils
from generic_card import *
import building_blocks
class RFID_Card(Card):
DRIVER_NAME = ["RFID"]
@ -28,13 +29,29 @@ class RFID_Card(Card):
COMMANDS = {
"get_uid": cmd_get_uid,
}
STATUS_WORDS = dict(Card.STATUS_WORDS)
STATUS_WORDS.update( {
"\x67\x00": "Wrong Length",
"\x68\x00": "Class byte is not correct",
"\x6a\x81": "Function not supported",
"\x6b\x00": "Wrong parameters P1-P2",
} )
class RFID_Storage_Card(RFID_Card):
class RFID_Storage_Card(building_blocks.Card_with_read_binary,RFID_Card):
STOP_ATRS = []
ATRS = []
STATUS_MAP = dict(RFID_Card.STATUS_MAP)
STATUS_MAP.update( {
Card.PURPOSE_RETRY: ("6C??", ),
} )
APDU_READ_BINARY = utils.C_APDU(CLA=0xff, INS=0xb0, Le=0)
COMMANDS = dict(building_blocks.Card_with_read_binary.COMMANDS)
COMMANDS.update(RFID_Card.COMMANDS)
class Mifare_Card(RFID_Storage_Card):
pass
DATA_UNIT_SIZE=4
class Mifare_Classic_Card(Mifare_Card):
DRIVER_NAME = ["Mifare Classic"]