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 = 0xe7 # 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)
Beispiel #2
0
    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)
Beispiel #3
0
    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
Beispiel #4
0
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])
Beispiel #5
0
    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
Beispiel #6
0
    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_Security_Environment(TCOS_Security_Environment):
    def __init__(self, card):
        TCOS_Security_Environment.__init__(self, card)
        self.last_vanilla_c_apdu = None
        
        # Set up a fake SE config to be able to reuse the TCOS code
        self.set_key( 1, self.card.KSenc)
        enc_config = "\x80\x01\x0d\x83\x01\x01\x85\x00"
        self.set_key( 2, self.card.KSmac)
        mac_config = "\x80\x01\x0d\x83\x01\x02\x85\x00"
        
        self.set_config( tcos_card.SE_APDU,  tcos_card.TEMPLATE_CCT, SE_Config(mac_config) )
        self.set_config( tcos_card.SE_RAPDU, tcos_card.TEMPLATE_CCT, SE_Config(mac_config) )
        
        self.set_config( tcos_card.SE_APDU,  tcos_card.TEMPLATE_CT, SE_Config(enc_config) )
        self.set_config( tcos_card.SE_RAPDU, tcos_card.TEMPLATE_CT, SE_Config(enc_config) )
    
    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 = 0xe7 # 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 after_send(self, result):
        if (self.last_vanilla_c_apdu.cla & 0x80 != 0x80) and (self.last_vanilla_c_apdu.CLA & 0x0C != 0x0C):
            # Inject fake response descriptor so that TCOS_Security_Environment.after_send sees the need to authenticate/decrypt
            response_descriptor = "\x99\x00\x8e\x00"
            if self.last_vanilla_c_apdu.case() in (2,4):
                response_descriptor = "\x87\x00" + response_descriptor
            response_descriptor = "\xba" + chr(len(response_descriptor)) + response_descriptor
            
            self.last_c_apdu.data = self.last_c_apdu.data + response_descriptor
        
        return TCOS_Security_Environment.after_send(self, result)

    
    def _mac(self, config, data):
        (ssc,) = struct.unpack(">Q", self.card.ssc)
        ssc = ssc + 1
        self.card.ssc = struct.pack(">Q", ssc)
        return Passport_Application._mac(self.card.KSmac, data, self.card.ssc, dopad=False)
Beispiel #8
0
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"),
    }
Beispiel #9
0
class Passport_Security_Environment(TCOS_Security_Environment):
    def __init__(self, card):
        TCOS_Security_Environment.__init__(self, card)
        self.last_vanilla_c_apdu = None

        # Set up a fake SE config to be able to reuse the TCOS code
        self.set_key(1, self.card.KSenc)
        enc_config = "\x80\x01\x0d\x83\x01\x01\x85\x00"
        self.set_key(2, self.card.KSmac)
        mac_config = "\x80\x01\x0d\x83\x01\x02\x85\x00"

        self.set_config(tcos_card.SE_APDU, tcos_card.TEMPLATE_CCT,
                        SE_Config(mac_config))
        self.set_config(tcos_card.SE_RAPDU, tcos_card.TEMPLATE_CCT,
                        SE_Config(mac_config))

        self.set_config(tcos_card.SE_APDU, tcos_card.TEMPLATE_CT,
                        SE_Config(enc_config))
        self.set_config(tcos_card.SE_RAPDU, tcos_card.TEMPLATE_CT,
                        SE_Config(enc_config))

    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 after_send(self, result):
        if (self.last_vanilla_c_apdu.cla & 0x80 !=
                0x80) and (self.last_vanilla_c_apdu.CLA & 0x0C != 0x0C):
            # Inject fake response descriptor so that TCOS_Security_Environment.after_send sees the need to authenticate/decrypt
            response_descriptor = "\x99\x00\x8e\x00"
            if self.last_vanilla_c_apdu.case() in (2, 4):
                response_descriptor = "\x87\x00" + response_descriptor
            response_descriptor = "\xba" + chr(
                len(response_descriptor)) + response_descriptor

            self.last_c_apdu.data = self.last_c_apdu.data + response_descriptor

        return TCOS_Security_Environment.after_send(self, result)

    def _mac(self, config, data):
        (ssc, ) = struct.unpack(">Q", self.card.ssc)
        ssc = ssc + 1
        self.card.ssc = struct.pack(">Q", ssc)
        return Passport_Application._mac(self.card.KSmac,
                                         data,
                                         self.card.ssc,
                                         dopad=False)
Beispiel #10
0
    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)
Beispiel #11
0
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,
    }
Beispiel #12
0
 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)