Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ Session.vim
# Auto-generated tag files
tags

# VSCode-Settings
.vscode

# Build files
DESFfire.egg-info/*
build
dist

82 changes: 82 additions & 0 deletions Desfire/DESFire.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
171 changes: 171 additions & 0 deletions example_transaction.py
Original file line number Diff line number Diff line change
@@ -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()
40 changes: 37 additions & 3 deletions interacive.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -179,15 +196,32 @@ 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: '))

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: ')))

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down