def _send_with_retry(self, apdu): result = self._real_send(apdu) if self.check_sw(result.sw, self.PURPOSE_GET_RESPONSE): ## Need to call GetResponse gr_apdu = C_APDU(self.COMMAND_GET_RESPONSE, le=result.sw2, cla=apdu.cla) # FIXME result = R_APDU(self._real_send(gr_apdu)) elif self.check_sw(result.sw, self.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
class Card_with_80_aa(Card_with_ls): APDU_LIST_X = C_APDU("\x80\xaa\x01\x00\x00") LIST_X_DF = 1 LIST_X_EF = 2 LS_L_SIZE_TAG = 0x81 def list_x(self, x): "Get a list of x objects, where x is one of 1 (DFs) or 2 (EFs) or 3 (DFs and EFs)" result = self.send_apdu(C_APDU(self.APDU_LIST_X, p1=x)) tail = result.data result_list = [] while len(tail) > 0: head, tail = tail[:2], tail[2:] result_list.append(head) return result_list def cmd_listdirs(self): "List DFs in current DF" result = self.list_x(1) print "DFs: " + ", ".join( [utils.hexdump(a, short=True) for a in result]) def cmd_listfiles(self): "List EFs in current DF" result = self.list_x(2) print "EFs: " + ", ".join( [utils.hexdump(a, short=True) for a in result])
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 self.last_size = -1 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 self.last_size == len(contents): break else: self.last_size = len(contents) 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 self.last_delta = None return contents, result.sw
def before_send(self, apdu): self.last_vanilla_c_apdu = C_APDU(apdu) if (apdu.cla & 0x80 != 0x80) and (apdu.CLA & 0x0C != 0x0C): # Transform for SM apdu.CLA = apdu.CLA | 0x0C apdu_string = binascii.b2a_hex(apdu.render()) new_apdu = [apdu_string[:8]] new_apdu.append("YY") if apdu.case() in (3, 4): new_apdu.append("87[01") new_apdu.append(binascii.b2a_hex(apdu.data)) new_apdu.append("]") if apdu.case() in (2, 4): if apdu.Le == 0: apdu.Le = 0xdf # FIXME: Probably not the right way new_apdu.append("97(%02x)" % apdu.Le) new_apdu.append("8E()00") new_apdu_string = "".join(new_apdu) apdu = C_APDU.parse_fancy(new_apdu_string) return TCOS_Security_Environment.before_send(self, apdu)
def list_x(self, x): "Get a list of x objects, where x is one of 1 (DFs) or 2 (EFs) or 3 (DFs and EFs)" result = self.send_apdu(C_APDU(self.APDU_LIST_X, p1=x)) tail = result.data result_list = [] while len(tail) > 0: head, tail = tail[:2], tail[2:] result_list.append(head) return result_list
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) APDU_SELECT_FILE = C_APDU(INS=0xa4) DEFAULT_CONTEXT = context_mrtd SELECT_FILE_LE = None AID_LIST = ["a0000002471001"] STATUS_MAP = {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)] + [ ( "CVCA", "\x01\x1c", ), ] def __init__(self, *args, **kwargs): self.ssc = None self.KSenc = None self.KSmac = None self.se = None def derive_key(Kseed, c): """Derive a key according to TR-PKI mrtds ICC read-only access v1.1 annex E.1. c is either 1 for encryption or 2 for MAC computation. Returns: Ka + Kb Note: Does not adjust parity. Nobody uses that anyway ...""" D = Kseed + struct.pack(">i", c) H = sha1(D).digest() Ka = H[0:8] Kb = H[8:16] return Ka + Kb derive_key = staticmethod(derive_key) def derive_seed(mrz2, verbose=0): """Derive Kseed from the second line of the MRZ according to TR-PKI mrtds ICC read-only access v1.1 annex F.1.1""" if verbose: print "MRZ_information: '%s' + '%s' + '%s'" % ( mrz2[0:10], mrz2[13:20], mrz2[21:28]) MRZ_information = mrz2[0:10] + mrz2[13:20] + mrz2[21:28] H = sha1(MRZ_information).digest() Kseed = H[:16] if verbose: print "SHA1('%s')[:16] =\nKseed = %s" % (MRZ_information, hexdump(Kseed)) return Kseed derive_seed = staticmethod(derive_seed) def cmd_perform_bac(self, mrz2, verbose=1): "Perform the Basic Acess Control authentication and establishment of session keys" mrz2 = mrz2.upper() Kseed = self.derive_seed(mrz2, verbose) Kenc = self.derive_key(Kseed, 1) Kmac = self.derive_key(Kseed, 2) if verbose: print "Kenc = %s" % hexdump(Kenc) print "Kmac = %s" % hexdump(Kmac) print result = self.send_apdu(self.APDU_GET_RANDOM) if not self.check_sw(result.sw): raise BACError, "SW after GET RANDOM was %02x%02x. Card refused to send rcd_icc. Should NEVER happen." % ( result.sw1, result.sw2) rnd_icc = result.data if verbose: print "RND.icc = %s" % hexdump(rnd_icc) rndtmp = self._make_random(8 + 16) rnd_ifd = rndtmp[:8] Kifd = rndtmp[8:] if verbose: print "RND.ifd = %s" % hexdump(rnd_ifd) print "Kifd = %s" % hexdump(Kifd, indent=10) S = rnd_ifd + rnd_icc + Kifd Eifd = crypto_utils.cipher(True, "des3-cbc", Kenc, S, "\x00" * 8) Mifd = self._mac(Kmac, Eifd) if verbose: print "Eifd = %s" % hexdump(Eifd, indent=10) print "Mifd = %s" % hexdump(Mifd) print auth_apdu = C_APDU(self.APDU_MUTUAL_AUTHENTICATE, data=Eifd + Mifd) result = self.send_apdu(auth_apdu) if not self.check_sw(result.sw): raise BACError, "SW after MUTUAL AUTHENTICATE was %02x%02x. Card did not accept our BAC attempt" % ( result.sw1, result.sw2) resp_data = result.data Eicc = resp_data[:-8] Micc = self._mac(Kmac, Eicc) if not Micc == resp_data[-8:]: raise ValueError, "Passport authentication failed: Wrong MAC on incoming data during Mutual Authenticate" if verbose: print "Eicc = %s" % hexdump(Eicc, indent=10) print "Micc = %s" % hexdump(Micc) print "Micc verified OK" R = crypto_utils.cipher(False, "des3-cbc", Kenc, Eicc, "\x00" * 8) if verbose: print "R = %s" % hexdump(R, indent=10) if not R[:8] == rnd_icc: raise BACError, "Passport authentication failed: Wrong RND.icc on incoming data during Mutual Authenticate" if not R[8:16] == rnd_ifd: raise BACError, "Passport authentication failed: Wrong RND.ifd on incoming data during Mutual Authenticate" Kicc = R[16:] if verbose: print "Kicc = %s" % hexdump(Kicc) print KSseed = crypto_utils.operation_on_string(Kicc, Kifd, lambda a, b: a ^ b) self.KSenc = self.derive_key(KSseed, 1) self.KSmac = self.derive_key(KSseed, 2) self.ssc = rnd_icc[-4:] + rnd_ifd[-4:] if False: self.KSenc = binascii.a2b_hex("979EC13B1CBFE9DCD01AB0FED307EAE5") self.KSmac = binascii.a2b_hex("F1CB1F1FB5ADF208806B89DC579DC1F8") self.ssc = binascii.a2b_hex("887022120C06C226") if verbose: print "KSseed = %s" % hexdump(KSseed) print "KSenc = %s" % hexdump(self.KSenc) print "KSmac = %s" % hexdump(self.KSmac) print "ssc = %s" % hexdump(self.ssc) self.se = Passport_Security_Environment(self) def verify_cms(self, data): """Verify a pkcs7 SMIME message""" from M2Crypto import SMIME, X509, BIO import base64, TLV_utils, os data3 = "-----BEGIN PKCS7-----\n" data3 += base64.encodestring(data) data3 += "-----END PKCS7-----" #print data3 p7_bio = BIO.MemoryBuffer(data3) # Instantiate an SMIME object. s = SMIME.SMIME() # TODO: ugly hack for M2Crypto body = TLV_utils.tlv_find_tag(TLV_utils.unpack(data), 0xA0, 1)[0][2] thecert = TLV_utils.tlv_find_tag(body, 0xA0, 2)[1][2] cert_bio = BIO.MemoryBuffer(TLV_utils.pack(thecert)) # Load the signer's cert. x509 = X509.load_cert_bio(cert_bio, format=0) sk = X509.X509_Stack() sk.push(x509) s.set_x509_stack(sk) country = str(x509.get_issuer()).split('/')[1][2:] #print country cacert = country + "-cacert.der" #print cacert msgErr = "couldn't parse certificate to determine URL for CACert, search the intertubes," msg = "download CACert (convert to DER if necessary) and save it as \n\"%s\"" % cacert if not os.path.isfile(cacert): try: v = x509.get_ext("certificatePolicies").get_value() start = v.find("CPS: ") if start != -1: url = v[start + 5:-1] print "visit %s" % url, msg else: print msgErr, msg except Exception: print msgErr, msg return "" # Load the signer's CA cert. st = X509.X509_Store() #st.load_info('main') x509CA = X509.load_cert(cacert, format=0) st.add_x509(x509CA) s.set_x509_store(st) # Load the data, verify it. #p7, data = SMIME.smime_load_pkcs7_bio(p7_bio) p7 = SMIME.load_pkcs7_bio(p7_bio) v = s.verify(p7) return v def cmd_passive_auth(self, verbose=1): "Perform passive authentication" hashes = {} result = "" i = 0 for name in ("DG1", "DG2", "SOD"): fid = None for n, f in self.INTERESTING_FILES: if n == name: fid = f break if fid is None: return i += 1 result = self.open_file(fid, 0x0c) if self.check_sw(result.sw): contents, sw = self.read_binary_file() #self.last_result = R_APDU(contents + self.last_sw) if name != "SOD": hashes[i] = crypto_utils.hash("SHA", contents) else: result = self.verify_cms(contents[4:]) #print hexdump(result) #print "DG1: %s" % hexdump(hashes[i]) #print "DG2: %s" % hexdump(hashes[2]) res = TLV_utils.tlv_find_tag(TLV_utils.unpack(result), 0x04) if len(res) == 0: print "failed to verify EF.SOD" return else: print "verified EF.SOD" i = 0 for tag, length, hash in res: i += 1 if hexdump(hashes[i]) == hexdump(hash): print "DG%d hash verified: %s" % (i, binascii.b2a_hex(hash)) else: print "DG%d hash failed:" % i print "was: %s" % binascii.b2a_hex(hashes[i]) print "expected: %s" % binascii.b2a_hex(hash) return def before_send(self, apdu): if self.se: apdu = self.se.before_send(apdu) return apdu def after_send(self, result): if self.se: result = self.se.after_send(result) return result def _mac(key, data, ssc=None, dopad=True): if ssc: data = ssc + data if dopad: topad = 8 - len(data) % 8 data = data + "\x80" + ("\x00" * (topad - 1)) a = crypto_utils.cipher(True, "des-cbc", key[:8], data, "\x00" * 8) b = crypto_utils.cipher(False, "des-ecb", key[8:16], a[-8:]) c = crypto_utils.cipher(True, "des-ecb", key[:8], b) return c _mac = staticmethod(_mac) def _make_random(len): "Get len random bytes" return os.urandom(len) _make_random = staticmethod(_make_random) def get_prompt(self): return "(%s)%s" % (self.get_driver_name(), self.se and "[SM]" or "") def check_sw(self, sw, purpose=None): if purpose is not Card.PURPOSE_SM_OK: return Card.check_sw(self, sw, purpose) else: return sw not in ("\x69\x87", "\x69\x88") def cmd_parse_biometrics(self): "Parse the biometric information contained in the last result." cbeff = CBEFF.from_data(self.last_result.data) basename = datetime.datetime.now().isoformat() for index, biometric in enumerate(cbeff.biometrics): biometric.store(basename="biometric_%s_%02i" % (basename, index)) 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: fid = f break if fid is None: return result = self.open_file(fid, 0x0c) if self.check_sw(result.sw): self.cmd_cat() self.cmd_parsetlv() def cmd_read_com(self): "Read EF.COM" return self._read_ef("COM") def cmd_read_sod(self): "Read EF.SOD" return self._read_ef("SOD") def cmd_read_cvca(self): "Read EF.CVCA" return self._read_ef("CVCA") def cmd_read_dg(self, dg): "Read EF.DGx" return self._read_ef("DG%s" % int(dg, 0)) COMMANDS = { "perform_bac": cmd_perform_bac, "read_com": cmd_read_com, "read_sod": cmd_read_sod, "read_cvca": cmd_read_cvca, "read_dg": cmd_read_dg, "parse_biometrics": cmd_parse_biometrics, "parse_passport": cmd_parse_passport, "passive_authenticate": cmd_passive_auth, } DATA_GROUPS = { 0x61: (1, "Machine readable zone information"), 0x75: (2, "Encoded face"), 0x63: (3, "Encoded finger(s)"), 0x76: (4, "Encoded iris(s)"), 0x65: (5, "Displayed portrait"), 0x66: (6, "Reserved for future use"), 0x67: (7, "Displayed signature or usual mark"), 0x68: (8, "Data feature(s)"), 0x69: (9, "Structure feature(s)"), 0x6A: (10, "Substance feature(s)"), 0x6B: (11, "Additional personal data elements"), 0x6C: (12, "Additional document data elements"), 0x6D: (13, "Discretionary data elements"), 0x6E: (14, "Reserved for future use"), 0x6F: (15, "Active authentication public key info"), 0x70: (16, "Persons to notify data element(s)"), } TLV_OBJECTS = { context_mrtd: { 0x60: (TLV_utils.recurse, "EF.COM - Common data elements", context_EFcom), 0x77: (TLV_utils.recurse, "EF.SOD - Security Data", context_EFsod), } } for _key, (_a, _b) in DATA_GROUPS.items(): TLV_OBJECTS[context_mrtd][_key] = (TLV_utils.recurse, "EF.DG%i - %s" % (_a, _b), globals()["context_EFdg%i" % _a]) del _key, _a, _b def decode_version_number(value): #print "|||||||", value, "|", repr(value), "|" try: result = [] while len(value) > 0: v, value = value[:2], value[2:] result.append(str(int(v))) return " " + ".".join(result) except: return "".join(["|||||||", value, "|", repr(value), "|"]) def decode_tag_list(value): result = [] for t in value: e = Passport_Application.DATA_GROUPS.get(ord(t)) hex = "0x%02x: " % ord(t) if e is None: result.append(hex + "Error: '%02X' is an unknown Data Group tag" % ord(t)) else: result.append(hex + "DG%-2i - %s" % e) return "\n" + "\n".join(result) TLV_OBJECTS[context_EFcom] = { 0x5F01: (decode_version_number, "LDS version number"), 0x5F36: (decode_version_number, "Unicode version number"), 0x5C: (decode_tag_list, "List of all data groups present") } def decode_mrz(value): l = len(value) / 2 return "\n" + value[:l] + "\n" + value[l:] TLV_OBJECTS[context_EFdg1] = { 0x5F1F: (decode_mrz, "Machine Readable Zone data"), } identifier("context_biometric_group") identifier("context_biometric") identifier("context_biometric_header") TLV_OBJECTS[context_EFdg2] = { 0x7F61: (TLV_utils.recurse, "Biometric Information Group", context_biometric_group) } TLV_OBJECTS[context_biometric_group] = { 0x02: (TLV_utils.number, "Number of instances of this biometric"), 0x7f60: (TLV_utils.recurse, "Biometric Information Template", context_biometric), } TLV_OBJECTS[context_biometric] = { 0xA1: (TLV_utils.recurse, "Biometric Header Template (BHT)", context_biometric_header), 0x5F2E: (TLV_utils.binary, "Biometric data"), } TLV_OBJECTS[context_biometric_header] = { 0x80: (decode_version_number, "ICAO header version"), 0x81: (TLV_utils.binary, "Biometric type"), # FIXME parse these datafields 0x82: (TLV_utils.binary, "Biometric feature"), 0x83: (TLV_utils.binary, "Creation date and time"), 0x84: (TLV_utils.binary, "Validity period"), 0x86: (TLV_utils.binary, "Creator of the biometric reference data (PID)"), 0x87: (TLV_utils.binary, "Format owner"), 0x88: (TLV_utils.binary, "Format type"), }
def cmd_perform_bac(self, mrz2, verbose=1): "Perform the Basic Acess Control authentication and establishment of session keys" mrz2 = mrz2.upper() Kseed = self.derive_seed(mrz2, verbose) Kenc = self.derive_key(Kseed, 1) Kmac = self.derive_key(Kseed, 2) if verbose: print "Kenc = %s" % hexdump(Kenc) print "Kmac = %s" % hexdump(Kmac) print result = self.send_apdu(self.APDU_GET_RANDOM) if not self.check_sw(result.sw): raise BACError, "SW after GET RANDOM was %02x%02x. Card refused to send rcd_icc. Should NEVER happen." % ( result.sw1, result.sw2) rnd_icc = result.data if verbose: print "RND.icc = %s" % hexdump(rnd_icc) rndtmp = self._make_random(8 + 16) rnd_ifd = rndtmp[:8] Kifd = rndtmp[8:] if verbose: print "RND.ifd = %s" % hexdump(rnd_ifd) print "Kifd = %s" % hexdump(Kifd, indent=10) S = rnd_ifd + rnd_icc + Kifd Eifd = crypto_utils.cipher(True, "des3-cbc", Kenc, S, "\x00" * 8) Mifd = self._mac(Kmac, Eifd) if verbose: print "Eifd = %s" % hexdump(Eifd, indent=10) print "Mifd = %s" % hexdump(Mifd) print auth_apdu = C_APDU(self.APDU_MUTUAL_AUTHENTICATE, data=Eifd + Mifd) result = self.send_apdu(auth_apdu) if not self.check_sw(result.sw): raise BACError, "SW after MUTUAL AUTHENTICATE was %02x%02x. Card did not accept our BAC attempt" % ( result.sw1, result.sw2) resp_data = result.data Eicc = resp_data[:-8] Micc = self._mac(Kmac, Eicc) if not Micc == resp_data[-8:]: raise ValueError, "Passport authentication failed: Wrong MAC on incoming data during Mutual Authenticate" if verbose: print "Eicc = %s" % hexdump(Eicc, indent=10) print "Micc = %s" % hexdump(Micc) print "Micc verified OK" R = crypto_utils.cipher(False, "des3-cbc", Kenc, Eicc, "\x00" * 8) if verbose: print "R = %s" % hexdump(R, indent=10) if not R[:8] == rnd_icc: raise BACError, "Passport authentication failed: Wrong RND.icc on incoming data during Mutual Authenticate" if not R[8:16] == rnd_ifd: raise BACError, "Passport authentication failed: Wrong RND.ifd on incoming data during Mutual Authenticate" Kicc = R[16:] if verbose: print "Kicc = %s" % hexdump(Kicc) print KSseed = crypto_utils.operation_on_string(Kicc, Kifd, lambda a, b: a ^ b) self.KSenc = self.derive_key(KSseed, 1) self.KSmac = self.derive_key(KSseed, 2) self.ssc = rnd_icc[-4:] + rnd_ifd[-4:] if False: self.KSenc = binascii.a2b_hex("979EC13B1CBFE9DCD01AB0FED307EAE5") self.KSmac = binascii.a2b_hex("F1CB1F1FB5ADF208806B89DC579DC1F8") self.ssc = binascii.a2b_hex("887022120C06C226") if verbose: print "KSseed = %s" % hexdump(KSseed) print "KSenc = %s" % hexdump(self.KSenc) print "KSmac = %s" % hexdump(self.KSmac) print "ssc = %s" % hexdump(self.ssc) self.se = Passport_Security_Environment(self)
class ISO_Card(Card): DRIVER_NAME = ["ISO"] COMMAND_GET_RESPONSE = C_APDU(ins=0xc0) COMMAND_CLASS = C_APDU APDU_VERIFY_PIN = C_APDU(ins=0x20) ## Map for check_sw() STATUS_MAP = { Card.PURPOSE_SUCCESS: ("\x90\x00", ), Card.PURPOSE_GET_RESPONSE: ("61??", ), ## If this is received then GET RESPONSE should be called with SW2 Card.PURPOSE_SM_OK: ("\x90\x00", ), Card.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 } ATRS = list(Card.ATRS) STOP_ATRS = list(Card.STOP_ATRS) ## Note: a key in this dictionary may either be a one- or two-byte string containing ## a binary status word, or a two or four-byte string containing a hexadecimal ## status word, possibly with ? characters marking variable nibbles. ## Hexadecimal characters MUST be in uppercase. The values that two- or four-byte ## strings map to may be either format strings, that can make use of the ## keyword substitutions for SW1 and SW2 or a callable accepting two arguments ## (SW1, SW2) that returns a string. STATUS_WORDS = { '\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) } ## For the format of this dictionary of dictionaries see TLV_utils.tags TLV_OBJECTS = dict(Card.TLV_OBJECTS) DEFAULT_CONTEXT = None ## Format: "AID (binary)": ("name", [optional: description, {more information}]) APPLICATIONS = { "\xa0\x00\x00\x01\x67\x45\x53\x49\x47\x4e": ("DF.ESIGN", ), "\xa0\x00\x00\x00\x63\x50\x4b\x43\x53\x2d\x31\x35": ("DF_PKCS15", ), "\xD2\x76\x00\x01\x24\x01": ("DF_OpenPGP", "OpenPGP card", { "significant_length": 6 }), "\xa0\x00\x00\x02\x47\x10\x01": ("DF_LDS", "Machine Readable Travel Document", { "alias": ("mrtd", ) }), ## The following are from 0341a.pdf: BSI-DSZ-CC-0341-2006 "\xD2\x76\x00\x00\x66\x01": ("DF_SIG", "Signature application", { "fid": "\xAB\x00" }), "\xD2\x76\x00\x00\x25\x5A\x41\x02\x00": ("ZA_MF_NEU", "Zusatzanwendungen", { "fid": "\xA7\x00" }), "\xD2\x76\x00\x00\x25\x45\x43\x02\x00": ("DF_EC_CASH_NEU", "ec-Cash", { "fid": "\xA1\x00" }), "\xD2\x76\x00\x00\x25\x45\x50\x02\x00": ("DF_BOERSE_NEU", "Geldkarte", { "fid": "\xA2\x00", "alias": ("geldkarte", ) }), "\xD2\x76\x00\x00\x25\x47\x41\x01\x00": ("DF_GA_MAESTRO", "GA-Maestro", { "fid": "\xAC\x00" }), "\xD2\x76\x00\x00\x25\x54\x44\x01\x00": ("DF_TAN", "TAN-Anwendung", { "fid": "\xAC\x02" }), "\xD2\x76\x00\x00\x25\x4D\x01\x02\x00": ("DF_MARKTPLATZ_NEU", "Marktplatz", { "fid": "\xB0\x01" }), "\xD2\x76\x00\x00\x25\x46\x53\x02\x00": ("DF_FAHRSCHEIN_NEU", "Fahrschein", { "fid": "\xB0\x00" }), "\xD2\x76\x00\x00\x25\x48\x42\x02\x00": ("DF_BANKING_20", "HBCI", { "fid": "\xA6\x00" }), "\xD2\x76\x00\x00\x25\x4E\x50\x01\x00": ("DF_NOTEPAD", "Notepad", { "fid": "\xA6\x10" }), "\xd2\x76\x00\x00\x85\x01\x00": ( "NFC_TYPE_4", "NFC NDEF Application on tag type 4", { "alias": ("nfc", ) }, ), # From TR-03110_v201_pdf.pdf "\xE8\x07\x04\x00\x7f\x00\x07\x03\x02": ("DF_eID", "eID application"), "\xd2\x76\x00\x00\x25\x4b\x41\x4e\x4d\x30\x31\x00": ( "VRS_TICKET", "VRS Ticket", { "fid": "\xad\x00", "alias": ("vrs", ) }, ), "\xd2\x76\x00\x01\x35\x4b\x41\x4e\x4d\x30\x31\x00": ( "VRS_TICKET", "VRS Ticket", { "fid": "\xad\x00", }, ), } # Alias for DF_BOERSE_NEU APPLICATIONS[ "\xA0\x00\x00\x00\x59\x50\x41\x43\x45\x01\x00"] = APPLICATIONS[ "\xD2\x76\x00\x00\x25\x45\x50\x02\x00"] # Alias for DF_GA_MAESTRO APPLICATIONS["\xA0\x00\x00\x00\x04\x30\x60"] = APPLICATIONS[ "\xD2\x76\x00\x00\x25\x47\x41\x01\x00"] ## Format: "RID (binary)": ("vendor name", [optional: {more information}]) VENDORS = { "\xD2\x76\x00\x01\x24": ("Free Software Foundation Europe", ), "\xD2\x76\x00\x00\x25": ("Bankenverlag", ), "\xD2\x76\x00\x00\x60": ("Wolfgang Rankl", ), "\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", ), } TLV_OBJECTS[TLV_utils.context_FCP] = { 0x84: (Card.decode_df_name, "DF name"), } TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FCP] def __init__(self, reader): Card.__init__(self, reader) self.last_sw = None self.sw_changed = False def post_merge(self): ## Called after cards.__init__.Cardmultiplexer._merge_attributes self.TLV_OBJECTS[TLV_utils.context_FCP][0x84] = (self._decode_df_name, "DF name") self.TLV_OBJECTS[TLV_utils.context_FCI][0x84] = (self._decode_df_name, "DF name") def decode_statusword(self): if self.last_sw is None: return "No command executed so far" else: retval = None 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: return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw) else: return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw)) def _real_send(self, apdu): result = Card._real_send(self, apdu) self.last_sw = result.sw self.sw_changed = True return result def _send_with_retry(self, apdu): result = self._real_send(apdu) if self.check_sw(result.sw, self.PURPOSE_GET_RESPONSE): ## Need to call GetResponse gr_apdu = C_APDU(self.COMMAND_GET_RESPONSE, le=result.sw2, cla=apdu.cla) # FIXME result = R_APDU(self._real_send(gr_apdu)) elif self.check_sw(result.sw, self.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 def verify_pin(self, pin_number, pin_value): apdu = C_APDU(self.APDU_VERIFY_PIN, P2=pin_number, data=pin_value) result = self.send_apdu(apdu) return self.check_sw(result.sw) def cmd_verify(self, pin_number, pin_value): """Verify a PIN.""" pin_number = int(pin_number, 0) pin_value = binascii.a2b_hex("".join(pin_value.split())) self.verify_pin(pin_number, pin_value) COMMANDS = { "verify": cmd_verify, }
def verify_pin(self, pin_number, pin_value): apdu = C_APDU(self.APDU_VERIFY_PIN, P2=pin_number, data=pin_value) result = self.send_apdu(apdu) return self.check_sw(result.sw)