Passport parse code
git-svn-id: svn+ssh://localhost/home/henryk/svn/cyberflex-shell/trunk@221 f711b948-2313-0410-aaa9-d29f33439f0b
This commit is contained in:
parent
7d2f9b4da5
commit
94fbb4190c
|
@ -3,7 +3,8 @@ import struct, sha, binascii, os, datetime, sys
|
||||||
from utils import hexdump, C_APDU
|
from utils import hexdump, C_APDU
|
||||||
from tcos_card import SE_Config, TCOS_Security_Environment
|
from tcos_card import SE_Config, TCOS_Security_Environment
|
||||||
from generic_card import Card
|
from generic_card import Card
|
||||||
import crypto_utils, tcos_card, TLV_utils
|
from iso_7816_4_card import ISO_7816_4_Card
|
||||||
|
import crypto_utils, tcos_card, TLV_utils, generic_card
|
||||||
from TLV_utils import identifier
|
from TLV_utils import identifier
|
||||||
|
|
||||||
identifier("context_mrtd")
|
identifier("context_mrtd")
|
||||||
|
@ -88,7 +89,11 @@ class Passport_Application(Application):
|
||||||
STATUS_MAP = {
|
STATUS_MAP = {
|
||||||
Card.PURPOSE_SM_OK: ("6282", "6982", "6A82")
|
Card.PURPOSE_SM_OK: ("6282", "6982", "6A82")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INTERESTING_FILES = [
|
||||||
|
("COM", "\x01\x1e",),
|
||||||
|
("SOD", "\x01\x1d",),
|
||||||
|
] + [ ("DG%s" % e, "\x01"+chr(e)) for e in range(1,19) ]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.ssc = None
|
self.ssc = None
|
||||||
|
@ -115,7 +120,8 @@ class Passport_Application(Application):
|
||||||
MRZ_information = mrz2[0:10] + mrz2[13:20] + mrz2[21:28]
|
MRZ_information = mrz2[0:10] + mrz2[13:20] + mrz2[21:28]
|
||||||
H = sha.sha(MRZ_information).digest()
|
H = sha.sha(MRZ_information).digest()
|
||||||
Kseed = H[:16]
|
Kseed = H[:16]
|
||||||
print "SHA1('%s')[:16] =\nKseed = %s" % (MRZ_information, hexdump(Kseed))
|
if verbose:
|
||||||
|
print "SHA1('%s')[:16] =\nKseed = %s" % (MRZ_information, hexdump(Kseed))
|
||||||
return Kseed
|
return Kseed
|
||||||
derive_seed = staticmethod(derive_seed)
|
derive_seed = staticmethod(derive_seed)
|
||||||
|
|
||||||
|
@ -240,21 +246,34 @@ class Passport_Application(Application):
|
||||||
for index, biometric in enumerate(cbeff.biometrics):
|
for index, biometric in enumerate(cbeff.biometrics):
|
||||||
biometric.store(basename= "biometric_%s_%02i" % (basename, index))
|
biometric.store(basename= "biometric_%s_%02i" % (basename, index))
|
||||||
|
|
||||||
def _read_ef(self, fid):
|
def cmd_parse_passport(self, mrz2=None):
|
||||||
|
"Test the Passport class"
|
||||||
|
if mrz2 is None:
|
||||||
|
p = Passport.from_card(self)
|
||||||
|
else:
|
||||||
|
p = Passport.from_card(self, ["",mrz2])
|
||||||
|
|
||||||
|
def _read_ef(self, name):
|
||||||
|
fid = None
|
||||||
|
for n, f in self.INTERESTING_FILES:
|
||||||
|
if n == name: break
|
||||||
|
if fid is None:
|
||||||
|
return
|
||||||
|
|
||||||
result = self.open_file(fid, 0x0c)
|
result = self.open_file(fid, 0x0c)
|
||||||
if not result.sw == "\x6a\x82":
|
if self.check_sw(result.sw):
|
||||||
self.cmd_cat()
|
self.cmd_cat()
|
||||||
self.cmd_parsetlv()
|
self.cmd_parsetlv()
|
||||||
|
|
||||||
def cmd_read_com(self):
|
def cmd_read_com(self):
|
||||||
"Read EF.COM"
|
"Read EF.COM"
|
||||||
return self._read_ef("\x01\x1e")
|
return self._read_ef("COM")
|
||||||
def cmd_read_sod(self):
|
def cmd_read_sod(self):
|
||||||
"Read EF.SOD"
|
"Read EF.SOD"
|
||||||
return self._read_ef("\x01\x1d")
|
return self._read_ef("SOD")
|
||||||
def cmd_read_dg(self, dg):
|
def cmd_read_dg(self, dg):
|
||||||
"Read EF.DGx"
|
"Read EF.DGx"
|
||||||
return self._read_ef("\x01"+chr(int(dg,0)))
|
return self._read_ef("DG%s" % int(dg,0))
|
||||||
|
|
||||||
COMMANDS = {
|
COMMANDS = {
|
||||||
"perform_bac": cmd_perform_bac,
|
"perform_bac": cmd_perform_bac,
|
||||||
|
@ -262,6 +281,7 @@ class Passport_Application(Application):
|
||||||
"read_sod": cmd_read_sod,
|
"read_sod": cmd_read_sod,
|
||||||
"read_dg": cmd_read_dg,
|
"read_dg": cmd_read_dg,
|
||||||
"parse_biometrics": cmd_parse_biometrics,
|
"parse_biometrics": cmd_parse_biometrics,
|
||||||
|
"parse_passport": cmd_parse_passport,
|
||||||
}
|
}
|
||||||
|
|
||||||
DATA_GROUPS = {
|
DATA_GROUPS = {
|
||||||
|
@ -745,23 +765,25 @@ class Passport(object):
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
"UTO": ("Utopia", ""),
|
"UTO": ("Utopia", ""),
|
||||||
|
"BDR": ("Bundesdruckerei", ""),
|
||||||
}
|
}
|
||||||
def __init__(self, mrz_data = _default_empty_mrz_data):
|
def __init__(self, mrz_data = _default_empty_mrz_data, ignore_mrz_parse_error = False):
|
||||||
"""Initialize an instance.
|
"""Initialize an instance.
|
||||||
Optional argument mrz_data must be a sequence of strings representing the individual lines (at least
|
Optional argument mrz_data must be a sequence of strings representing the individual lines (at least
|
||||||
two) from the machine readable zone."""
|
two) from the machine readable zone."""
|
||||||
self.given_mrz = mrz_data
|
self.given_mrz = mrz_data
|
||||||
|
self.dg1_mrz = self.dg2_cbeff = None
|
||||||
|
self.parse_failed = False
|
||||||
|
self.parse_error = ""
|
||||||
|
self.card = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if mrz_data is not _default_empty_mrz_data:
|
if mrz_data is not _default_empty_mrz_data:
|
||||||
self._parse_mrz(mrz_data)
|
self._parse_mrz(mrz_data)
|
||||||
except PassportParseError:
|
except PassportParseError:
|
||||||
self.parse_failed = True
|
if not ignore_mrz_parse_error:
|
||||||
self.parse_error = sys.exc_info()[1]
|
self.parse_failed = True
|
||||||
else:
|
self.parse_error = sys.exc_info()[1]
|
||||||
self.parse_failed = False
|
|
||||||
self.parse_error = ""
|
|
||||||
|
|
||||||
print self.parse_error
|
|
||||||
|
|
||||||
def from_card(cls, card, mrz_data = _default_empty_mrz_data):
|
def from_card(cls, card, mrz_data = _default_empty_mrz_data):
|
||||||
"""Initialize an instance and populate it from a card.
|
"""Initialize an instance and populate it from a card.
|
||||||
|
@ -769,6 +791,46 @@ class Passport(object):
|
||||||
(to which a select_application() call will be issued). This card object will then be used to fetch
|
(to which a select_application() call will be issued). This card object will then be used to fetch
|
||||||
all data before returning from the constructor. Note that for a BAC protected passport you will need
|
all data before returning from the constructor. Note that for a BAC protected passport you will need
|
||||||
to specify at least the second element in mrz_data."""
|
to specify at least the second element in mrz_data."""
|
||||||
|
|
||||||
|
if not isinstance(card, Passport_Application):
|
||||||
|
if not isinstance(card, ISO_7816_4_Card):
|
||||||
|
raise ValueError, "card must be a Passport_Application object or a ISO_7816_4_Card object, not %s" % type(card)
|
||||||
|
else:
|
||||||
|
result = card.select_application("mrtd")
|
||||||
|
if not card.check_sw(result.sw):
|
||||||
|
raise EnvironmentError, "card did not accept SELECT APPLICATION, sw was %s" % result.sw
|
||||||
|
assert isinstance(card, Passport_Application)
|
||||||
|
|
||||||
|
p = cls(mrz_data, ignore_mrz_parse_error=True)
|
||||||
|
p.card = card
|
||||||
|
tried_bac = False
|
||||||
|
p.result_map_select = {}
|
||||||
|
p.result_map_read = {}
|
||||||
|
|
||||||
|
generic_card.DEBUG = False
|
||||||
|
|
||||||
|
for name, fid in card.INTERESTING_FILES:
|
||||||
|
result = card.open_file(fid, 0x0C)
|
||||||
|
if not card.check_sw(result.sw) and not tried_bac and not mrz_data is _default_empty_mrz_data:
|
||||||
|
tried_bac = True
|
||||||
|
card.cmd_perform_bac(mrz_data[1], verbose=0)
|
||||||
|
result = card.open_file(fid, 0x0C)
|
||||||
|
|
||||||
|
p.result_map_select[fid] = result.sw
|
||||||
|
if card.check_sw(result.sw):
|
||||||
|
contents, sw = card.read_binary_file()
|
||||||
|
if not card.check_sw(sw) and not tried_bac and not mrz_data is _default_empty_mrz_data:
|
||||||
|
tried_bac = True
|
||||||
|
card.cmd_perform_bac(mrz_data[1], verbose=0)
|
||||||
|
contents, sw = card.read_binary_file()
|
||||||
|
|
||||||
|
p.result_map_read[fid] = sw
|
||||||
|
if contents != "":
|
||||||
|
setattr(p, "contents_%s" % name, contents)
|
||||||
|
if hasattr(p, "parse_%s" % name):
|
||||||
|
getattr(p, "parse_%s" % name)(contents)
|
||||||
|
|
||||||
|
return p
|
||||||
from_card = classmethod(from_card)
|
from_card = classmethod(from_card)
|
||||||
|
|
||||||
def from_file(cls, filename):
|
def from_file(cls, filename):
|
||||||
|
@ -786,6 +848,19 @@ class Passport(object):
|
||||||
One of basename or filemap _must_ be specified."""
|
One of basename or filemap _must_ be specified."""
|
||||||
from_files = classmethod(from_files)
|
from_files = classmethod(from_files)
|
||||||
|
|
||||||
|
def parse_DG1(self, contents):
|
||||||
|
structure = TLV_utils.unpack(contents)
|
||||||
|
try:
|
||||||
|
mrz = TLV_utils.tlv_find_tag(structure, 0x5F1F, 1)[0][2]
|
||||||
|
except IndexError:
|
||||||
|
raise PassportParseError, "Could not find MRZ information in DG1"
|
||||||
|
mrz_data = (mrz[:44], mrz[44:88])
|
||||||
|
self.dg1_mrz = mrz_data
|
||||||
|
self._parse_mrz(mrz_data)
|
||||||
|
|
||||||
|
def parse_DG2(self, contents):
|
||||||
|
self.dg2_cbeff = CBEFF.from_data(contents)
|
||||||
|
|
||||||
def calculate_check_digit(data, digit=None, field=None):
|
def calculate_check_digit(data, digit=None, field=None):
|
||||||
"""Calculate a check digit. If digit is not None then it will be compared to the calculated
|
"""Calculate a check digit. If digit is not None then it will be compared to the calculated
|
||||||
check digit and a PassportParseError will be raised on a mismatch. Optional argument field
|
check digit and a PassportParseError will be raised on a mismatch. Optional argument field
|
||||||
|
@ -795,7 +870,6 @@ class Passport(object):
|
||||||
for e in data
|
for e in data
|
||||||
]
|
]
|
||||||
checksum = sum([ e * [7,3,1][i%3] for i,e in enumerate(numbers) ]) % 10
|
checksum = sum([ e * [7,3,1][i%3] for i,e in enumerate(numbers) ]) % 10
|
||||||
print "checksum(%s)=%s =?= %s" % (data, checksum, digit)
|
|
||||||
if not digit is None and not (digit.isdigit() and checksum == int(digit)):
|
if not digit is None and not (digit.isdigit() and checksum == int(digit)):
|
||||||
raise PassportParseError, "Incorrect check digit%s. Is %s, should be %s." % ((field is not None and " in field '%s'" % field or ""), checksum, digit)
|
raise PassportParseError, "Incorrect check digit%s. Is %s, should be %s." % ((field is not None and " in field '%s'" % field or ""), checksum, digit)
|
||||||
return checksum
|
return checksum
|
||||||
|
@ -846,10 +920,7 @@ class Passport(object):
|
||||||
self.calculate_check_digit(splitted_opt_field[1], mrz2[-2], "Optional data")
|
self.calculate_check_digit(splitted_opt_field[1], mrz2[-2], "Optional data")
|
||||||
|
|
||||||
self.calculate_check_digit(mrz2[0:10]+mrz2[13:20]+mrz2[21:43], mrz2[-1], "Second line of machine readable zone")
|
self.calculate_check_digit(mrz2[0:10]+mrz2[13:20]+mrz2[21:43], mrz2[-1], "Second line of machine readable zone")
|
||||||
|
|
||||||
print self.type, self.issuer, self.name
|
|
||||||
print self.document_no, self.nationality, self.date_of_birth, self.sex, self.expiration_date, self.optional
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
mrz1 = "P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<"
|
mrz1 = "P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<"
|
||||||
|
|
Loading…
Reference in New Issue