Reading data from credit cards and Polish Electronic Student Cards (ELS)
Jacek Kowalski
2015-03-08 69d2540d3d3882eb742dfaa99ab055b2e57b0bc9
Support for Electronic Student Card
1 files modified
166 ■■■■■ changed files
sc.py 166 ●●●●● patch | view | raw | blame | history
sc.py
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
import smartcard.CardRequest
import smartcard.CardConnectionObserver
import smartcard.ExclusiveTransmitCardConnection
import smartcard.pcsc.PCSCPart10
from pyasn1.type import univ
from pyasn1.codec.ber import decoder
import subprocess
from smartcard.util import toHexString, toASCIIBytes, toBytes, toASCIIString
import collections, sys, traceback, time
@@ -17,7 +19,7 @@
    report = 0
    def __init__(self, level, message):
        if level >= SCardDebug.report:
            print(message)
            print message
class SCardConnectionObserver(smartcard.CardConnectionObserver.CardConnectionObserver):
    def update( self, cardconnection, ccevent ):
@@ -66,14 +68,7 @@
    MSB = 0x80
    CONSTRUCTED = 0x20
    
    debug = True
    tags = {
        0x02: ["Integer", ""],
        0x03: ["Bit string", ""],
        0x04: ["Octet string", ""],
        0x05: ["Null", ""],
        0x06: ["Object identifier", ""],
        0x42: ["Issuer Identification Number (IIN)", "The number that identifies the major industry and the card issuer and that forms the first part of the Primary Account Number (PAN)"],
        0x4F: ["Application Identifier (AID) - card", "Identifies the application as described in ISO/IEC 7816-5"],
        0x50: ["Application Label", "Mnemonic associated with the AID according to ISO/IEC 7816-5"],
@@ -215,8 +210,6 @@
        if tag == 0x9f8004:
            tag = 0x9f80
            data.append(0x04)
        if SCardTLV.debug:
            print('Got tag: ' + toHexString(tag))
        
        length = data.pop()
        if length == 0x80:
@@ -251,27 +244,24 @@
            if constructed:
                read = SCardTLV.decode(read)
            
            if tag in ret:
                ret[tag].push(read)
            else:
                ret[tag] = [ read ]
            ret[tag] = read
        
        return ret
    
    @staticmethod
    def displayTag(data, intend=""):
        if data[0] in SCardTLV.tags:
            print(intend + SCardTLV.tags[data[0]][0] + " [" + hex(data[0]) + "], length "+str(data[1]))
            print intend + SCardTLV.tags[data[0]][0] + " [" + hex(data[0]) + "], length "+str(data[1])
        else:
            print(intend + "UNKNOWN TAG [" + hex(data[0]) + "], length "+str(data[1]))
            print intend + "UNKNOWN TAG [" + hex(data[0]) + "], length "+str(data[1])
    
    @staticmethod
    def display(data, intend="", singleline = None):
        for tag, value in data.items():
            if tag in SCardTLV.tags:
                print(intend + SCardTLV.tags[tag][0] + " [" + hex(tag) + "]:")
                print intend + SCardTLV.tags[tag][0] + " [" + hex(tag) + "]:"
            else:
                print(intend + "UNKNOWN TAG [" + hex(tag) + "]:")
                print intend + "UNKNOWN TAG [" + hex(tag) + "]:"
            
            if tag in SCardTLV.specialTags:
                value.reverse()
@@ -280,7 +270,7 @@
            elif isinstance(value, dict):
                SCardTLV.display(value, intend+"\t")
            else:
                print(intend+"\t"+toHexString(value)+" ("+toASCIIString(value)+")")
                print intend+"\t"+toHexString(value)+" ("+toASCIIString(value)+")"
class SCardSelectFileBy:
    MFDFEF = 0x00
@@ -330,7 +320,7 @@
        self.connection = conn
    
    def getFeatures(self):
        print(smartcard.pcsc.PCSCPart10.getFeatureRequest(self.connection))
        print smartcard.pcsc.PCSCPart10.getFeatureRequest(self.connection)
class SCardEMVPINEncode:
    @staticmethod
@@ -391,7 +381,7 @@
        exclusive = smartcard.ExclusiveTransmitCardConnection.ExclusiveTransmitCardConnection(serv.connection)
        exclusive.lock()
        time.sleep(0.2)
        print("inserted", toHexString(serv.connection.getATR()))
        print "inserted", toHexString(serv.connection.getATR())
        
        self.service = serv
        self.connection = serv.connection
@@ -404,6 +394,7 @@
    
    def _sendAPDU(self, adpu = []):
        response, SW1, SW2 = self.exclusive.transmit(adpu)
        if SW1 != 0x90:
            raise SCardResponseException(SW1, SW2)
        
@@ -428,6 +419,9 @@
        
        return self._sendAPDU(adpu)
    
    def GetResponse(self, CLA = 0x00, P1 = 0x00, P2 = 0x00, Le = 0x00):
        return self._sendAPDU([CLA, 0xC0, P1, P2, Le])
    def ReadBinary(self, CLA = 0x00, offset = 0x00, Le = 0x00):
        try:
            return self._sendAPDU([CLA, 0xB0, (offset >> 8) & 0x7F, offset & 0xFF, Le])
@@ -437,18 +431,6 @@
            else:
                raise e
    
    def ReadBinaryAll(self, CLA = 0x00):
        data = []
        while len(data) <= 0x7FFF:
            try:
                response, SW1, SW2 = self.ReadBinary(CLA, len(data))
                data += response
            except SCardResponseException as e:
                if e.SW1 == 0x6B:
                    break
                raise e
        return data
    def ReadRecord(self, CLA = 0x00, record = 0x00, identifier = 0x00, Le = 0x00):
        try:
            return self._sendAPDU([CLA, 0xB2, record, identifier, Le])
@@ -485,12 +467,45 @@
        
        return self._sendAPDU(adpu)
class SCardActions:
    def __init__(self, card):
        self.card = card
    def cdRoot(self, CLA = 0x00):
        return self.cd("3F20")
    def cdAID(self, AID, CLA = 0x00):
        return self.card.SelectFile(CLA = CLA, by = SCardSelectFileBy.DFname, data = toBytes(AID), Le = 0)
    def cd(self, MFEFDF, CLA = 0x00):
        return self.card.SelectFile(CLA = CLA, by = SCardSelectFileBy.MFDFEF, data = toBytes(MFEFDF), Le = 0)
    def readBinaryEF(self, EF, blockSize = 4, CLA = 0x00):
        attrs = self.cd(EF, CLA = CLA)
        data = []
        offset = 0
        try:
            while True:
                offset = int(len(data) / blockSize)
                result, _, _ = self.card.ReadBinary(offset = offset, CLA = CLA)
                if result == []:
                    break
                data = data[0:offset * blockSize] + result
        except SCardResponseException as e:
            if e.SW1 == 0x6B:
                pass
            else:
                raise e
        return data
class SCardHandler:
    @staticmethod
    def getCard():
        request = smartcard.CardRequest.CardRequest(newcardonly=True, timeout=None)
        print("Insert card...")
        print "Insert card..."
        service = request.waitforcard()
        print "Card detected..."
        time.sleep(0.5)
        return SCard(service)
@@ -500,25 +515,24 @@
    ]
    
    def __init__(self, card):
        self.card = card
        self.card = SCardActions(card)
    
    def selectAID(self):
        #self.card.SelectFile(by = SCardSelectFileBy.MFDFEF, data = toBytes("3F20"))
        for aid in SCardELS.aids:
            try:
                self.card.SelectFile(by = SCardSelectFileBy.DFname, data = toBytes(aid), Le = 0)
                return True
                return self.card.cdAID(aid)
            except:
                pass
                raise Exception('ELS AID not available')
        return False
    
    def readCert(self):
        self.card.SelectFile(by = SCardSelectFileBy.childEF, data = toBytes("0001"), Le = 0)
        return self.card.ReadBinaryAll()
        data = self.card.readBinaryEF("0001")
        return data
    
    def readData(self):
        self.card.SelectFile(by = SCardSelectFileBy.childEF, data = toBytes("0002"), Le = 0)
        return self.card.ReadBinaryAll()
        data = self.card.readBinaryEF("0002")
        return data
class SCardEMV:
    aids = [
@@ -567,7 +581,7 @@
                    data, SW1, SW2 = self.card.ReadRecord(record = i, identifier = (sfi << 3) | SCardReadRecordIdentifier.P1isRecordNumber | SCardReadRecordIdentifier.ReadP1)
                    records.append(SCardTLV.decode(data))
            except SCardResponseException as e:
                if e.SW1 != 0x6A or e.SW2 != 0x83:
                if e.SW1 != 0x6A or (e.SW2 != 0x83 and e.SW2 != 0x88):
                    raise SCardResponseException(e.SW1, e.SW2)
            
            return self.card.SelectFile(by = SCardSelectFileBy.DFname, data = records[0][0x70][0x61][0x4f], Le = 0x00)
@@ -647,7 +661,7 @@
                data, SW1, SW2 = self.card.ReadRecord(record = i, identifier = (sfi << 3) | SCardReadRecordIdentifier.P1isRecordNumber | SCardReadRecordIdentifier.ReadP1)
                SCardTLV.display(SCardTLV.decode(data))
        except SCardResponseException as e:
            if e.SW1 != 0x6A or e.SW2 != 0x83:
            if e.SW1 != 0x6A or (e.SW2 != 0x83 and e.SW2 != 0x88):
                raise SCardResponseException(e.SW1, e.SW2)
    
    def readData(self):
@@ -662,14 +676,18 @@
            first = tag >> 8
            second = tag & 255
            
            try:
            data, SW1, SW2 = self.card.GetData(CLA = 0x80, P1 = first, P2 = second)
            SCardTLV.display(SCardTLV.decode(data))
            except SCardResponseException as e:
                if e.SW1 != 0x6A or (e.SW2 != 0x83 and e.SW2 != 0x88):
                    raise SCardResponseException(e.SW1, e.SW2)
    
    def PIN(self, data = ""):
        data, SW1, SW2 = self.card.Verify(codeType = SCardVerifyCodeTypeEMV.plaintext, data = SCardEMVPINEncode.plaintext(data))
ELS = 1
ELS = 0
if ELS:
    try:
@@ -678,8 +696,56 @@
        els = SCardELS(card)
        
        els.selectAID()
        SCardTLV.decode(els.readCert())
        SCardTLV.decode(els.readData())
        cert = toASCIIString(els.readCert())
        openssl = subprocess.Popen(['openssl', 'x509', '-text', '-noout', '-inform', 'DER'],
                stdin = subprocess.PIPE, stdout = subprocess.PIPE)
        stdout, stderr = openssl.communicate(cert)
        print ""
        print stdout
        print ""
        data = decoder.decode(toASCIIString(els.readData()))[0]
        data_signatureType = data.getComponentByPosition(0)
        data_signature = data.getComponentByPosition(1)
        assert(data_signatureType.asTuple() == univ.ObjectIdentifier('1.2.840.113549.1.7.2').asTuple())
        data_signature_version = data_signature.getComponentByPosition(0)
        assert(data_signature_version >= 3)
        data_signature_algorithms = data_signature.getComponentByPosition(1)
        data_signature_data = data_signature.getComponentByPosition(2)
        data_signature_info = data_signature.getComponentByPosition(data_signature_version)
        assert(data_signature_info != None)
        data_signature_data_type = data_signature_data.getComponentByPosition(0)
        data_signature_data_data = data_signature_data.getComponentByPosition(1)
        assert(data_signature_data_type.asTuple() == univ.ObjectIdentifier('1.2.616.1.101.4.1.1.1').asTuple())
        els = data_signature_data_data.asOctets()
        els = decoder.decode(els)[0]
        els_version = els.getComponentByPosition(0)
        assert(els_version == 1)
        els_serialNumber = els.getComponentByPosition(1)
        els_university = els.getComponentByPosition(2)
        els_surnames = els.getComponentByPosition(3)
        els_surnames_tuple = [str(els_surnames.getComponentByPosition(i)) for i in range(len(els_surnames))]
        els_surnames_printable = ' '.join(els_surnames_tuple)
        els_names = els.getComponentByPosition(4)
        els_names_tuple = [str(els_names.getComponentByPosition(i)) for i in range(len(els_names))]
        els_names_printable = ' '.join(els_names_tuple)
        els_studentNumber = els.getComponentByPosition(5)
        els_editId = els.getComponentByPosition(6)
        els_personalNumber = els.getComponentByPosition(7)
        els_expiry = els.getComponentByPosition(8)
        print "Uczelnia: " + els_university
        print "Nazwisko: " + els_surnames_printable
        print "Imię:     " + els_names_printable
        print "Album:    " + els_studentNumber
        print "PESEL:    " + els_personalNumber
        print "Ważność:  " + els_expiry
        print "Edycja:   " + els_editId
    except (KeyboardInterrupt, SystemExit):
        pass
    except: