diff --git a/.gitignore b/.gitignore index a7fb625..fa511da 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,11 @@ Session.vim # Auto-generated tag files tags +# VSCode-Settings +.vscode + +# Build files +DESFfire.egg-info/* +build +dist + diff --git a/Desfire/DESFire.py b/Desfire/DESFire.py index bfb2825..0d5b3d4 100644 --- a/Desfire/DESFire.py +++ b/Desfire/DESFire.py @@ -493,6 +493,88 @@ def createStdDataFile(self, fileId, filePermissions, fileSize): apdu_command=self.command(DESFireCommand.DF_INS_CREATE_STD_DATA_FILE.value,params) self.communicate(apdu_command,'createStdDataFile', nativ=True, withTXCMAC=self.isAuthenticated) return + + ###### TRANSACTION FUNCTIONS + + def debit(self,fileId,amount): + """Prepare the File to debit the given amount. The changes must be commited by ``writeDebit()`` + The file must be generated by ``createValueFile()`` + + Args: + fileId (int): FileID to prepare the debit + amount (int): The debit amount + """ + fileId=getList(fileId,1) + + cmd=DESFireCommand.DF_INS_DEBIT.value + params=fileId + params+=getList(amount,4, 'little') + self.communicate(self.command(cmd, params),'Debit Card', nativ=True, withTXCMAC=self.isAuthenticated) + + def credit(self,fileId,amount): + """Prepare the File to Credit the given amount. The changes must be commited by ``writeDebit()`` + The file must be generated by ``createValueFile()`` + + Args: + fileId (int): FileID to prepare the credit + amount (int): The credit amount + """ + fileId=getList(fileId,1) + + cmd=DESFireCommand.DF_INS_CREDIT.value + params=fileId + params+=getList(amount,4, 'little') + self.communicate(self.command(cmd, params),'Debit Card', nativ=True, withTXCMAC=self.isAuthenticated) + + def commitTransaction(self): + """Commit the prepared transaction + """ + cmd=DESFireCommand.DF_COMMIT_TRANSACTION.value + self.communicate(self.command(cmd),'Commit Transactions', nativ=True, withTXCMAC=self.isAuthenticated) + + def abortTransaction(self): + """Abort the prepared transaction + """ + cmd=DESFireCommand.DF_INS_ABORT_TRANSACTION.value + self.communicate(self.command(cmd),'Commit Transactions', nativ=True, withTXCMAC=self.isAuthenticated) + + + def getValue(self,fileId) -> int: + """Gets the current value of the current file. + + Args: + fileId (int): FileID to get the settings for + + Returns: + int: The current value + """ + fileId=getList(fileId,1) + + cmd=DESFireCommand.DF_INS_GET_VALUE.value + params=getList(fileId) + ret = self.communicate(self.command(cmd, params),'Read value', nativ=True, withTXCMAC=self.isAuthenticated) + return int.from_bytes(ret, "little") + + def createValueFile(self, fileId, filePermissions, lowerLimit=0, upperLimit=10_000, value=0): + """Creates an file to manage transactions + + Args: + fileId (int): The File-ID to manage the transactions + filePermissions (DESFireFilePermissions): The filepermissions from the ``DESFireFilePermissions``-class + lowerLimit (int, optional): The lowest limit of the wallet. Defaults to 0. + upperLimit ([type], optional): The highest limit of the wallet. Defaults to 10_000. + value (int, optional): The start-credit of the wallet. Defaults to 0. + """ + params=getList(fileId,1,'big') + params+=[0x00] + params+=getList(filePermissions.pack(),2,'big') + params+=getList(getInt(lowerLimit,'big'),4, 'little') + params+=getList(getInt(upperLimit,'big'),4, 'little') + params+=getList(getInt(value,'big'),4, 'little') + params+=getList(0x00,1) + apdu_command=self.command(DESFireCommand.DF_INS_CREATE_VALUE_FILE.value,params) + self.communicate(apdu_command,'createValueFile', nativ=True, withTXCMAC=self.isAuthenticated) + return ###### CRYPTO KEYS RELATED FUNCTIONS diff --git a/example_transaction.py b/example_transaction.py new file mode 100644 index 0000000..b2fe1e1 --- /dev/null +++ b/example_transaction.py @@ -0,0 +1,171 @@ +############################################################################### +### This example need a card with default Masterkey +### Than do the follow things +### - Format card all data will be lost +### - Create app with ID 00AE17 +### - Select app +### - Change key 0, app masterkey +### - Change setting key 1 need to change other keys +### - Change key 1 +### - Auth key 1 +### - Change key 2 +### - Create value-file ID 0 +### - Auth key 2 +### - Prepare debit file ID 0, amount 100 +### - Auth key 2 +### - Read value file ID 0 +### - Commit transaction +### - Read value file ID 0 + +from __future__ import print_function + +import functools +import logging +import time +import sys + +from smartcard.System import readers +from smartcard.CardMonitoring import CardMonitor, CardObserver +from smartcard.util import toHexString +from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver + +from Desfire.DESFire import * +from Desfire.util import byte_array_to_human_readable_hex +from Desfire.pcsc import PCSCDevice + +IGNORE_EXCEPTIONS = (KeyboardInterrupt, MemoryError,) + + +def catch_gracefully(): + """Function decorator to show any Python exceptions occured inside a function. + + Use when the underlying thread main loop does not provide satisfying exception output. + """ + def _outer(func): + + @functools.wraps(func) + def _inner(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + if isinstance(e, IGNORE_EXCEPTIONS): + raise + else: + logger.error("Catched exception %s when running %s", e, func) + logger.exception(e) + + return _inner + + return _outer + + +class MyObserver(CardObserver): + """Observe when a card is inserted. Then try to run DESFire application listing against it.""" + + # We need to have our own exception handling for this as the + # # main loop of pyscard doesn't seem to do any exception output by default + @catch_gracefully() + def update(self, observable, actions): + + (addedcards, removedcards) = actions + + for card in addedcards: + if card.reader.startswith('Yubico'): + logger.info("Ignore Yubikey %s", toHexString(card.atr)) + return + logger.info("+ Inserted: %s", toHexString(card.atr)) + + connection = card.createConnection() + connection.connect() + + # This will log raw card traffic to console + connection.addObserver(ConsoleCardConnectionObserver()) + + # connection object itself is CardConnectionDecorator wrapper + # and we need to address the underlying connection object + # directly + logger.info("Opened connection %s", connection.component) + desfire = DESFire(PCSCDevice(connection.component)) + key_setting=desfire.getKeySetting() + logger.info('Auth Key %d',0) + desfire.authenticate(0,key_setting) + info=desfire.getCardVersion() + logger.info(info) + logger.info('Format card') + desfire.formatCard() + logger.info('Create application with ID 00AE17') + desfire.createApplication("00 AE 17",[DESFireKeySettings.KS_ALLOW_CHANGE_MK,DESFireKeySettings.KS_LISTING_WITHOUT_MK,DESFireKeySettings.KS_CONFIGURATION_CHANGEABLE],14,DESFireKeyType.DF_KEY_AES) + logger.info('Select application with ID 00AE17') + desfire.selectApplication('00 AE 17') + default_key=desfire.createKeySetting('00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00',0,DESFireKeyType.DF_KEY_AES,[]) + app_key=desfire.createKeySetting('00 10 20 31 40 50 60 70 80 90 A0 B0 B0 A0 90 80',0,DESFireKeyType.DF_KEY_AES,[]) + logger.info('Auth Key %d',0) + desfire.authenticate(0,default_key) + logger.info('Cange Key %d',0) + desfire.changeKey(0,app_key,default_key) + logger.info('Auth Key %d',0) + desfire.authenticate(0,app_key) + desfire.changeKeySettings([ DESFireKeySettings.KS_ALLOW_CHANGE_MK, DESFireKeySettings.KS_CONFIGURATION_CHANGEABLE, DESFireKeySettings.KS_CHANGE_KEY_WITH_KEY_1]) + app_key_1=desfire.createKeySetting('11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00',0,DESFireKeyType.DF_KEY_AES,[]) + logger.info('Cange Key %d',1) + desfire.changeKey(1,app_key_1,default_key) + logger.info('Auth Key %d',1) + desfire.authenticate(1,app_key_1) + app_key_2=desfire.createKeySetting('22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00 11',0,DESFireKeyType.DF_KEY_AES,[]) + logger.info('Cange Key %d',2) + desfire.changeKey(2,app_key_2,default_key) + logger.info('Auth Key %d',0) + desfire.authenticate(0,app_key) + filePerm=DESFireFilePermissions() + filePerm.setPerm(0x0F,0x0F,0x02,0x01) # no key read, no key write, key 2 read and write, key1 change permissions + logger.info('Creat Value-File with ID %d and start-amount of 1,000',0) + desfire.createValueFile(0,filePerm,value=1_000) # file Id 0, start-amount 1000 + logger.info('Auth Key %d',2) + desfire.authenticate(2,app_key_2) + logger.info('Data debit amount %d',100) + desfire.debit(0,100) + logger.info('Auth Key %d', 2) + desfire.authenticate(2, app_key_2) # reading also needs write capability + logger.info('Get current value of value-file %d', 0) + value = desfire.getValue(0) + logger.info('The current value is %d', value) #The value is still 1000, because the transaction is not commited + logger.info('Commit transaction') + desfire.commitTransaction() + desfire.authenticate(2, app_key_2) # reading also needs write capability + logger.info('Get current value of value-file %d', 0) + value = desfire.getValue(0) + logger.info('The current value is %d', value) # Now the value is updated + + + + + + +def main(): + global logger + + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Insert MIFARE Desfire card to any reader to get its applications.") + + available_reader = readers() + logger.info("Available readers: %s", available_reader) + if not available_reader: + sys.exit("No smartcard readers detected") + + cardmonitor = CardMonitor() + cardobserver = MyObserver() + cardmonitor.addObserver(cardobserver) + + while True: + time.sleep(1) + + # don't forget to remove§ observer, or the + # monitor will poll forever... + cardmonitor.deleteObserver(cardobserver) + + + +if __name__ == "__main__": + main() diff --git a/interacive.py b/interacive.py index 41ffdcd..994a399 100644 --- a/interacive.py +++ b/interacive.py @@ -82,10 +82,17 @@ def update(self, observable, actions): 41. Get key settings 42. Change key settings ---------------------------- -50. Craete file +50. Create file 51. List files 52. Write file 53. Read file +---------------------------- +60. Create wallet +61. Debit wallet +62. Credit wallet +63. Commit transactions +64. Get wallet value +---------------------------- 90. Exit """)) if num == 90: @@ -116,6 +123,16 @@ def update(self, observable, actions): self.writeFile() elif num == 53: self.readFile() + elif num == 60: + self.createWallet() + elif num == 61: + self.debitWallet() + elif num == 62: + self.creditWallet() + elif num == 63: + self.commitTransaction() + elif num == 64: + self.getWalletValue() def auth(self): key=self.desfire.getKeySetting() @@ -179,8 +196,8 @@ def getKeySettings(self): def createFile(self): filePerm=DESFireFilePermissions() - filePerm.setPerm(int(input('Read key number: ')),int(intput('Write key number')),int(input('read/write key number: ')),int(input('Change permmision key number: '))) # key 4 read, key3 write, no key read and write, key2 change permissions - self.desfire.createStdDataFile(int(input('File id: ')),filePerm,int(input('File lenght: '))) # file Id 0, length 32 byte + filePerm.setPerm(int(input('Read key number: ')),int(intput('Write key number')),int(input('read/write key number: ')),int(input('Change permmision key number: '))) + self.desfire.createStdDataFile(int(input('File id: ')),filePerm,int(input('File lenght: '))) def writeFile(self): self.desfire.writeFileData(int(input('File id: ')),int(input('Offset')),int(input('Length: ')),input('Data: ')) @@ -188,6 +205,23 @@ def writeFile(self): def readFile(self): print(byte_array_to_human_readable_hex(self.desfire.readFileData(int(input('File id: ')),int(input('Offset')),int(input('Length: '))))) + def createWallet(self): + filePerm=DESFireFilePermissions() + filePerm.setPerm(0x0F,0x0F,int(input('read/write key number: ')),int(input('Change permmision key number: '))) + self.desfire.createValueFile(int(input('File id: ')),filePerm) + + def debitWallet(self): + self.desfire.debit(int(input('File id: ')),int(input('Amount: '))) + + def creditWallet(self): + self.desfire.credit(int(input('File id: ')),int(input('Amount: '))) + + def commitTransaction(self): + self.desfire.commitTransaction() + + def getWalletValue(self): + print('Value: %d',self.desfire.getValue(int(input('File id: ')))) + def getFileSettings(self): self.desfire.getFileSettings(int(input('File id: '))) diff --git a/setup.py b/setup.py index 8c1e7ed..916487e 100644 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ packages=find_packages(exclude=['tests*','examples*']), license='MIT', description='DESFire library for python', - long_description=open('README.txt').read(), - install_requires=['pycrypto','enum34','pyscard','pydes'], + long_description=open('README.md').read(), + install_requires=['pycrypto','enum34','pyscard','pydes','scapy','crcmod'], url='https://github.com/patsys/desfire-python', author='Patrick Weber', author_email='pat.weber91@gmail.com'