Big change: Replace SW_OK and SW1_RETRY with a more generic check_sw() that gets its data from STATUS_MAP

Small improvements in brutefid, including the ability to brute subdirectories
More fixes for passport secure messaging, using check_sw, can now decrypt error responses ("End of file reached before Le bytes read", etc.)
Add support for tuples/lists in dictionaries in MERGE_DICTS_RECURSIVE in cards/__init__.py


git-svn-id: svn+ssh://localhost/home/henryk/svn/cyberflex-shell/trunk@177 f711b948-2313-0410-aaa9-d29f33439f0b
This commit is contained in:
hploetz 2007-02-13 00:32:04 +00:00
parent f01836762b
commit d685765fa0
10 changed files with 90 additions and 51 deletions

View File

@ -86,7 +86,9 @@ if __name__ == "__main__":
del exit_now
if len(arguments) > 0:
top_level = binascii.unhexlify("".join( ["".join(e.split()) for e in arguments] ))
top_level = ("".join( ["".join(e.split()) for e in arguments] )).split("/")
top_level = [binascii.unhexlify(e) for e in top_level]
pycsc_card = connect(reader)
card = cards.new_card_object(pycsc_card)
@ -96,10 +98,11 @@ if __name__ == "__main__":
card.change_dir()
if top_level is not None:
card.change_dir(top_level)
for e in top_level: card.change_dir(e)
#objective = (0x2f00, 0x5015) ## Test cases on an OpenSC formatted PKCS#15 card
objective = range(0xffff)
objective = range(0xffff+1)
#objective = range(0x3fff+1) + range(0x7000,0x7fff+1) + range(0xc000,0xd4ff+1) + range(0xd600+1,0xd7ff+1) + range(0xdc00+1,0xffff+1)
for fid in objective:
data = chr(fid >> 8) + chr(fid & 0xff)
if loop % STATUS_INTERVAL == 0:
@ -113,16 +116,16 @@ if __name__ == "__main__":
loop = loop + 1
result = card.change_dir(data)
if result.sw == card.SW_OK:
if card.check_sw(result.sw):
results_dir[fid] = result
card.change_dir()
if top_level is not None:
card.change_dir(top_level)
for e in top_level: card.change_dir(e)
print >>sys.stderr, "\rDir %04X -> %02X%02X %s" % (fid, result.sw1, result.sw2, status),
result = card.open_file(data)
if result.sw == card.SW_OK:
if card.check_sw(result.sw):
results_file[fid] = result
print >>sys.stderr, "\rFile %04X -> %02X%02X %s" % (fid, result.sw1, result.sw2, status),
@ -140,12 +143,12 @@ if __name__ == "__main__":
print "Dir\t%04X" % fid
if len(result.data) > 0:
print utils.hexdump(result.data)
print TLV_utils.decode(result.data)
print TLV_utils.decode(result.data,tags=card.TLV_OBJECTS)
for fid, result in results_file.items():
print "-"*80
print "File\t%04X" % fid
if len(result.data) > 0:
print utils.hexdump(result.data)
print TLV_utils.decode(result.data)
print TLV_utils.decode(result.data,tags=card.TLV_OBJECTS)

View File

@ -56,7 +56,7 @@ class Cardmultiplexer:
of the participating classes instead of overriding them."""
MERGE_DICTS = ("APPLICATIONS", "COMMANDS", "STATUS_WORDS", "VENDORS")
MERGE_DICTS_RECURSIVE = ("TLV_OBJECTS", )
MERGE_DICTS_RECURSIVE = ("TLV_OBJECTS", "STATUS_MAP")
MERGE_LISTS = ()
def __init__(self, classes, *args, **kwargs):
@ -146,9 +146,8 @@ class Cardmultiplexer:
def _merge_attributes(self):
"""Update the local copy of merged attributes."""
is_descendant = lambda a,b: b in _inspect.getmro(a)
ordered_classes = list(self._classes)
ordered_classes.sort(cmp=lambda a,b: (a!=b and ( is_descendant(a,b) and 1 or -1 ) or 0))
ordered_classes.sort(cmp=lambda a,b: (a!=b and ( issubclass(a,b) and -1 or 1 ) or 0))
for attr in self.MERGE_DICTS + self.MERGE_DICTS_RECURSIVE:
tmpdict = {}
@ -165,6 +164,10 @@ class Cardmultiplexer:
recurse( target[key], value )
elif isinstance(value, dict):
target[key] = dict(value)
elif isinstance(value, (tuple,list)) and isinstance(target[key], list):
target[key].extend( [e for e in value if not e in target[key]] )
elif isinstance(value, (tuple,list)) and isinstance(target[key], tuple):
target[key] = tuple( list(target[key]) + [e for e in value if not e in target[key]] )
else:
target[key] = value
else:

View File

@ -117,7 +117,7 @@ class Cyberflex_Card(Java_Card):
self.session_key_mac = None
result = self.send_apdu(apdu)
if result.sw != self.SW_OK:
if not self.check_sw(result.sw):
raise Exception, "Statusword after InitializeUpdate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:])
card_challenge = result.data[12:20]
@ -143,7 +143,7 @@ class Cyberflex_Card(Java_Card):
result = self.send_apdu(apdu)
self.secure_channel_state = security_level
if result.sw != self.SW_OK:
if not self.check_sw(result.sw):
self.secure_channel_state = SECURE_CHANNEL_NONE
raise Exception, "Statusword after ExternalAuthenticate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:])
@ -151,7 +151,7 @@ class Cyberflex_Card(Java_Card):
def select_application(self, aid):
result = Java_Card.select_application(self, aid)
if self.last_sw == self.SW_OK and aid[:5] != DEFAULT_CARD_MANAGER_AID[:5]:
if self.check(self.last_sw) and aid[:5] != DEFAULT_CARD_MANAGER_AID[:5]:
self.secure_channel_state = SECURE_CHANNEL_NONE
return result

View File

@ -4,12 +4,22 @@ from utils import C_APDU, R_APDU
DEBUG = True
#DEBUG = False
## Constants for check_sw()
PURPOSE_SUCCESS = 1 # Command executed successful
PURPOSE_RETRY = 2 # Command executed successful but needs retry with correct length
PURPOSE_SM_OK = 3 # Command not executed successful or with warnings, but response still contains SM objects
class Card:
DRIVER_NAME = "Generic"
APDU_GET_RESPONSE = C_APDU(ins=0xc0)
APDU_VERIFY_PIN = C_APDU(ins=0x20)
SW_OK = '\x90\x00'
SW1_RETRY = 0x61 ## If this SW1 is received then GET RESPONSE should be called with SW2
PURPOSE_SUCCESS, PURPOSE_RETRY, PURPOSE_SM_OK = PURPOSE_SUCCESS, PURPOSE_RETRY, PURPOSE_SM_OK
## 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",)
}
## 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
## to match on the ATRs hexlify representation
@ -22,7 +32,7 @@ class Card:
## keyword substitutions for SW1 and SW2 or a callable accepting two arguments
## (SW1, SW2) that returns a string.
STATUS_WORDS = {
SW_OK: "Normal execution",
'\x90\x00': "Normal execution",
'61??': "%(SW2)i (0x%(SW2)02x) bytes of response data can be retrieved with GetResponse.",
'6C??': "Bad value for LE, 0x%(SW2)02x is the correct value.",
'63C?': lambda SW1,SW2: "The counter has reached the value '%i'" % (SW2%16)
@ -62,7 +72,7 @@ class Card:
"\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", ),
}
def _decode_df_name(self, value):
result = " " + utils.hexdump(value, short=True)
info = None
@ -126,7 +136,7 @@ class Card:
apdu = C_APDU(self.APDU_VERIFY_PIN, P2 = pin_number,
data = pin_value)
result = self.send_apdu(apdu)
return result.sw == self.SW_OK
return self.check_sw(result.sw)
def cmd_verify(self, pin_number, pin_value):
"""Verify a PIN."""
@ -191,7 +201,7 @@ class Card:
def _send_with_retry(self, apdu):
result = self._real_send(apdu)
if result.sw1 == self.SW1_RETRY:
if self.check_sw(result.sw, PURPOSE_RETRY):
## Need to call GetResponse
gr_apdu = C_APDU(self.APDU_GET_RESPONSE, le = result.sw2) # FIXME
result = R_APDU(self._real_send(gr_apdu))
@ -217,6 +227,10 @@ class Card:
self.last_result = result
return result
def check_sw(self, sw, purpose = None):
if purpose is None: purpose = Card.PURPOSE_SUCCESS
return self.match_statusword(self.STATUS_MAP[purpose], sw)
def can_handle(cls, card):
"""Determine whether this class can handle a given pycsc object."""
ATR = card.status().get("ATR","")
@ -235,33 +249,42 @@ class Card:
def get_prompt(self):
return "(%s)" % self.DRIVER_NAME
def match_statusword(swlist, sw):
"""Try to find sw in swlist.
swlist must be a list of either binary statuswords (two bytes), hexadecimal statuswords (four bytes) or fnmatch patterns on a hexadecimal statusword.
Returns: The element that matched (either two bytes, four bytes or the fnmatch pattern)."""
if sw in swlist:
return sw
sw = binascii.hexlify(sw).upper()
if sw in swlist:
return sw
for value in swlist:
if fnmatch.fnmatch(sw, value):
return value
return None
match_statusword = staticmethod(match_statusword)
def decode_statusword(self):
if self.last_sw is None:
return "No command executed so far"
else:
retval = None
retval = self.STATUS_WORDS.get(self.last_sw)
matched_sw = self.match_statusword(self.STATUS_WORDS.keys(), self.last_sw)
if matched_sw is not None:
retval = self.STATUS_WORDS.get(matched_sw)
if isinstance(retval, str):
retval = retval % { "SW1": ord(self.last_sw[0]),
"SW2": ord(self.last_sw[1]) }
elif callable(retval):
retval = retval( ord(self.last_sw[0]),
ord(self.last_sw[1]) )
if retval is None:
retval = self.STATUS_WORDS.get(binascii.hexlify(self.last_sw).upper())
if retval is None:
target = binascii.b2a_hex(self.last_sw).upper()
for (key, value) in self.STATUS_WORDS.items():
if fnmatch.fnmatch(target, key):
if isinstance(value, str):
retval = value % { "SW1": ord(self.last_sw[0]),
"SW2": ord(self.last_sw[1]) }
break
elif callable(value):
retval = value( ord(self.last_sw[0]),
ord(self.last_sw[1]) )
break
if retval is None:
return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw)
else:
return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw))
return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw)
else:
return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw))
def get_protocol(self):
return ((self.card.status()["Protocol"] == pycsc.SCARD_PROTOCOL_T0) and (0,) or (1,))[0]

View File

@ -4,7 +4,9 @@ from generic_card import *
class GSM_Card(Card):
DRIVER_NAME = "GSM"
APDU_GET_RESPONSE = C_APDU("\xa0\xC0\x00\x00")
SW1_RETRY = 0x9F
STATUS_MAP = {
PURPOSE_RETRY: ("9F??", )
}
ATRS = [
("3bff9500ffc00a1f438031e073f62113574a334861324147d6", None),

View File

@ -72,7 +72,7 @@ class ISO_7816_4_Card(Card):
contents = contents + result.data
offset = offset + len(result.data)
if result.sw != self.SW_OK:
if not self.check_sw(result.sw):
break
else:
had_one = True
@ -123,7 +123,7 @@ class ISO_7816_4_Card(Card):
result = self.send_apdu(
C_APDU(self.APDU_SELECT_APPLICATION,
data = aid, le = 0) ) ## FIXME With or without le
if result.sw == self.SW_OK:
if self.check_sw(result.sw):
Application.load_applications(self, aid)
return result

View File

@ -2,6 +2,7 @@ from generic_application import Application
import struct, sha, binascii
from utils import hexdump, C_APDU
from tcos_card import SE_Config, TCOS_Security_Environment
from generic_card import Card
import crypto_utils, tcos_card
class Passport_Security_Environment(TCOS_Security_Environment):
@ -37,7 +38,7 @@ class Passport_Security_Environment(TCOS_Security_Environment):
if apdu.case() in (2,4):
if apdu.Le == 0:
apdu.Le = 0xff # FIXME: Probably not the right way
apdu.Le = 0xe7 # FIXME: Probably not the right way
new_apdu.append("97(%02x)" % apdu.Le)
new_apdu.append("8E()00")
@ -70,11 +71,14 @@ class Passport_Application(Application):
DRIVER_NAME = "Passport"
APDU_GET_RANDOM = C_APDU(CLA=0, INS=0x84, Le=0x08)
APDU_MUTUAL_AUTHENTICATE = C_APDU(CLA=0, INS=0x82, Le=0x28)
SW_OK = "\x90\x00"
AID_LIST = [
"a0000002471001"
]
STATUS_MAP = {
Card.PURPOSE_SM_OK: ("6282", "6982")
}
def __init__(self, *args, **kwargs):
self.ssc = None
@ -116,7 +120,7 @@ class Passport_Application(Application):
print
result = self.send_apdu(self.APDU_GET_RANDOM)
assert result.sw == self.SW_OK
assert self.check_sw(result.sw)
rnd_icc = result.data
if verbose:
@ -139,7 +143,7 @@ class Passport_Application(Application):
print
auth_apdu = C_APDU(self.APDU_MUTUAL_AUTHENTICATE, data = Eifd + Mifd)
result = self.send_apdu(auth_apdu)
assert result.sw == self.SW_OK
assert self.check_sw(result.sw)
resp_data = result.data
Eicc = resp_data[:-8]

View File

@ -8,6 +8,9 @@ class Postcard_Card(ISO_7816_4_Card):
APDU_SELECT_FILE = C_APDU(cla=CLA,ins=0xa4)
APDU_READ_BINARY = C_APDU(cla=CLA,ins=0xb0,le=0x80)
STATUS_MAP = {
Card.PURPOSE_SUCCESS: ("90[124]0", )
}
ATRS = [
("3f65351002046c90..", None),
@ -17,7 +20,7 @@ class Postcard_Card(ISO_7816_4_Card):
def _get_binary(self, offset, length):
command = C_APDU(self.APDU_READ_BINARY, p1 = offset >> 8, p2 = offset & 0xff, le = length)
result = self.send_apdu(command)
assert result.sw == self.SW_OK
assert self.check_sw(result.sw)
return result.data
@ -93,6 +96,7 @@ class Postcard_Card(ISO_7816_4_Card):
print "\t%20s '%s'" % ("", binascii.a2b_hex(value))
def cmd_give_pin(self):
"Enter a pin"
old = sys.modules["cards.generic_card"].DEBUG
try:
pin = getpass.getpass("Enter PIN: ")

View File

@ -77,7 +77,7 @@ class TCOS_Security_Environment(object):
def after_send(self, result):
self.last_r_apdu = result
if result.sw == self.card.SW_OK:
if self.card.check_sw(result.sw, self.card.PURPOSE_SM_OK):
if (self.last_c_apdu.cla & 0xf0) == 0x00:
if self.last_c_apdu.ins == 0x22:
self.parse_mse(self.last_c_apdu)

View File

@ -80,10 +80,10 @@ class Cyberflex_Shell(Shell):
self.parse_and_execute(line)
if self.card.sw_changed and self.card.last_sw != self.card.SW_OK \
if self.card.sw_changed and not self.card.check_sw(self.card.last_sw) \
and self.card.last_sw not in ignored_SWs:
print "SW was not %s. Ignore (i) or Abort (a)? " % binascii.hexlify(self.card.SW_OK),
print "SW was not OK. Ignore (i) or Abort (a)? ",
answer = sys.stdin.readline()
if answer[0].lower() in ('i', "\n"):
pass