Authentication with NDEF message on Desfire containing signed UID via Ed25519

This commit is contained in:
Ondrej Mikle 2017-10-22 22:39:55 +02:00
parent 892b69f939
commit d0121aaed9
12 changed files with 286 additions and 41 deletions

View file

@ -28,10 +28,16 @@ It is much more general in use than to use it as authenthicator to open door.
You need just to run `make`. Additional dependencies: You need just to run `make`. Additional dependencies:
- [libnfc](https://github.com/nfc-tools/libnfc/releases), already present in Raspbian 8 repositories - [libnfc](https://github.com/nfc-tools/libnfc/releases), in Debian and Ubuntu as libnfc-dev
- [libfreefare](https://github.com/nfc-tools/libfreefare), in Debian and Ubuntu install libfreefare-bin and libfreefare-dev
- [python-axolotl-curve25519](https://github.com/tgalal/python-axolotl-curve25519), in Ubuntu and Debian install python-axolotl-curve25519
- [SWIG](http://www.swig.org/) - [SWIG](http://www.swig.org/)
- [WiringPi2 pythonic binding](https://github.com/WiringPi/WiringPi2-Python) (for switching lock on Raspberry) - [WiringPi2 pythonic binding](https://github.com/WiringPi/WiringPi2-Python) (for switching lock on Raspberry)
All dependencies except for wiring can be installed via:
`apt install libnfc-dev libfreefare-bin and libfreefare-dev python-axolotl-curve25519 swig3.0`
## Howto ## Howto
1. Create the database 1. Create the database
@ -44,18 +50,29 @@ You need just to run `make`. Additional dependencies:
3. Add some users 3. Add some users
- either authenthication by UID, e.g.: - either authentication by UID, e.g.:
brmdoor_adduser.py -c brmdoor_nfc.config -a uid 34795FCC SomeUserName ./brmdoor_adduser.py -c brmdoor_nfc.config -a uid 34795FCC SomeUserName
- authenthication by Yubikey's HMAC-SHA1 programmed on slot 2 - authentication by Yubikey's HMAC-SHA1 programmed on slot 2
brmdoor_adduser.py -c brmdoor_nfc.config -a hmac 40795FCCAB0701 SomeUserName 000102030405060708090a0b0c0d0e0f31323334 ./brmdoor_adduser.py -c brmdoor_nfc.config -a hmac 40795FCCAB0701 SomeUserName 000102030405060708090a0b0c0d0e0f31323334
- to program Yubikey slot 2 to use HMAC with given key, use: - to program Yubikey slot 2 to use HMAC with given key (requires package `yubikey-personalization`), use:
ykpersonalize -2 -ochal-resp -ohmac-sha1 -ohmac-lt64 -oserial-api-visible ykpersonalize -2 -ochal-resp -ohmac-sha1 -ohmac-lt64 -oserial-api-visible
- authentication using signed UID as NDEF message on Desfire:
./brmdoor_adduser.py -c brmdoor.config -a ndef 04631982cc2280 SomeUserName"
- you need to generate Ed25519 keypair, store the private key somewhere safe and put the public in config file
./generate_ed25519_keypair.py
- you need to program the Desfire card to have the signature
Finally, run the daemon: Finally, run the daemon:
sudo python brmdoor_nfc_daemon.py brmdoor_nfc.config sudo python brmdoor_nfc_daemon.py brmdoor_nfc.config

View file

@ -27,7 +27,6 @@ def addUidAuth(cursor, uid_hex, nick):
print >> sys.stderr, "UID must be in proper hex encoding" print >> sys.stderr, "UID must be in proper hex encoding"
sys.exit(1) sys.exit(1)
def addHmacAuth(cursor, uid_hex, nick, key_hex): def addHmacAuth(cursor, uid_hex, nick, key_hex):
""" """
Add user authenticated by Yubikey HMAC-SHA1. UID should be in hex, 4, 7 Add user authenticated by Yubikey HMAC-SHA1. UID should be in hex, 4, 7
@ -48,12 +47,28 @@ def addHmacAuth(cursor, uid_hex, nick, key_hex):
print >> sys.stderr, "UID and key must be in proper hex encoding" print >> sys.stderr, "UID and key must be in proper hex encoding"
sys.exit(1) sys.exit(1)
def addNdefAuth(cursor, uid_hex, nick):
"""
Add user authenticated by NDEF message on Desfire. UID should be in hex, 4, 7 or 10 bytes long.
"""
try:
uid_hex.decode("hex")
sql = """INSERT INTO authorized_desfires
(uid_hex, nick)
values (?, ?)
"""
sql_data = (uid_hex, nick)
cursor.execute(sql, sql_data)
except TypeError:
print >> sys.stderr, "UID must be in proper hex encoding"
sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
parser = OptionParser() parser = OptionParser()
parser.add_option("-c", "--config", action="store", type="string", dest="config", parser.add_option("-c", "--config", action="store", type="string", dest="config",
help="Configuration file") help="Configuration file")
parser.add_option("-a", "--authtype", action="store", type="string", dest="authtype", parser.add_option("-a", "--authtype", action="store", type="string", dest="authtype",
help="Authenthication type - uid or hmac") help="Authenthication type - uid, hmac or ndef")
(opts, args) = parser.parse_args() (opts, args) = parser.parse_args()
if opts.config is None: if opts.config is None:
@ -61,9 +76,9 @@ if __name__ == "__main__":
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
if opts.authtype not in ["uid", "hmac"]: if opts.authtype not in ["uid", "hmac", "ndef"]:
print >> sys.stderr, "You must specify authentication type via -a option!" print >> sys.stderr, "You must specify authentication type via -a option!"
print >> sys.stderr, "Acceptable choices: uid, hmac" print >> sys.stderr, "Acceptable choices: uid, hmac, ndef"
sys.exit(1) sys.exit(1)
config = BrmdoorConfig(opts.config) config = BrmdoorConfig(opts.config)
@ -83,6 +98,13 @@ if __name__ == "__main__":
print >> sys.stderr, "brmdoor_adduser.py -c brmdoor.config -a hmac 40795FCCAB0701 SomeUserName 000102030405060708090a0b0c0d0e0f31323334" print >> sys.stderr, "brmdoor_adduser.py -c brmdoor.config -a hmac 40795FCCAB0701 SomeUserName 000102030405060708090a0b0c0d0e0f31323334"
sys.exit(1) sys.exit(1)
addHmacAuth(cursor, args[0], args[1], args[2]) addHmacAuth(cursor, args[0], args[1], args[2])
elif opts.authtype == "ndef":
if len(args) < 2:
print >> sys.stderr, "You must two additional arguments, hex UID and nick"
print >> sys.stderr, "Example:"
print >> sys.stderr, "brmdoor_adduser.py -c brmdoor.config -a ndef 34795FCC SomeUserName"
sys.exit(1)
addNdefAuth(cursor, args[0], args[1])
conn.commit() conn.commit()
conn.close() conn.close()

View file

@ -4,6 +4,8 @@ import hmac
import hashlib import hashlib
import logging import logging
import axolotl_curve25519 as curve
from nfc_smartcard import NFCError from nfc_smartcard import NFCError
@ -128,7 +130,7 @@ class YubikeyHMACAuthenthicator(object):
if not rapdu.valid or rapdu.sw() != 0x9000: if not rapdu.valid or rapdu.sw() != 0x9000:
raise NFCError("HMAC - response SW is not 0x9000") raise NFCError("HMAC - response SW is not 0x9000")
except NFCError, e: except NFCError, e:
logging.debug("Yubikey HMAC command failed: %s" % e.what()) logging.info("Yubikey HMAC command failed: %s" % e.what())
return None return None
if not self.hmacCheck(secretKey, challenge, rapdu.data()): if not self.hmacCheck(secretKey, challenge, rapdu.data()):
@ -141,6 +143,61 @@ class YubikeyHMACAuthenthicator(object):
"""Closes connection to database""" """Closes connection to database"""
self.conn.close() self.conn.close()
class DesfireEd25519Authenthicator(object):
"""
Reads NDEF message from Desfire and it must be signed binary value of UID
"""
def __init__(self, filename, nfcReader, pubKey):
"""
Connects to database by given filename and later checks UIDs
using the pubkey (given as binary string).
"""
#again autocommit mode
self.conn = sqlite3.connect(filename, isolation_level=None)
self.nfcReader = nfcReader
self.pubKey = pubKey
def signatureCheck(self, uid, signature):
"""
Returns true iff uid (as binary) is the message signed by signature (binary string)
"""
verified = curve.verifySignature(self.pubKey, uid, signature) == 0
return verified
def checkUIDSignature(self, uid_hex):
"""
Checks if UID is in database. If so, it retrieves NDEF which should be signature of the UID
@param uid_hex: uid to match in hex
@returns UidRecord instance if found, None otherwise
"""
cursor = self.conn.cursor()
sql = "SELECT nick FROM authorized_desfires WHERE UPPER(uid_hex)=?"
sql_data =(uid_hex.upper(),)
cursor.execute(sql, sql_data)
record = cursor.fetchone()
if record is None:
return None
nick = record[0]
try:
ndefSignature = self.nfcReader.readDesfireNDEF()
if self.signatureCheck(uid_hex.decode("hex"), ndefSignature):
return UidRecord(uid_hex, nick)
else:
logging.info("Signature check failed for Desfire NDEF for UID %s", uid_hex)
return None
except NFCError, e:
logging.info("Desfire read NDEF failed: %s" % e.what())
return None
def shutdown(self):
"""Closes connection to database"""
self.conn.close()
#test routine #test routine
if __name__ == "__main__": if __name__ == "__main__":
authenticator = UidAuthenticator("test_uids_db.sqlite") authenticator = UidAuthenticator("test_uids_db.sqlite")

View file

@ -8,6 +8,8 @@
# Unlocker is just dummy test class. # Unlocker is just dummy test class.
[brmdoor] [brmdoor]
auth_db_filename = test_uids_db.sqlite auth_db_filename = test_uids_db.sqlite
#corresponding private key = 10ee85f987e7682d9acf24ab07ff0e302ee4cdd426f83a055d3a337a4f01314b
desfire_ed25519_pubkey = 4c625187d79fdee97a6af48cb8f854e7f313c8158de94e667e1509bd26617d27
#lock_opened_secs = 5 #lock_opened_secs = 5
#unknown_uid_timeout_secs = 5 #unknown_uid_timeout_secs = 5
log_file = - log_file = -

View file

@ -9,7 +9,7 @@ from binascii import hexlify
from nfc_smartcard import NFCDevice, NFCError from nfc_smartcard import NFCDevice, NFCError
from brmdoor_authenticator import UidAuthenticator, YubikeyHMACAuthenthicator from brmdoor_authenticator import UidAuthenticator, YubikeyHMACAuthenthicator, DesfireEd25519Authenthicator
import unlocker import unlocker
class BrmdoorConfigError(ConfigParser.Error): class BrmdoorConfigError(ConfigParser.Error):
@ -40,6 +40,7 @@ class BrmdoorConfig(object):
self.config.read(filename) self.config.read(filename)
self.authDbFilename = self.config.get("brmdoor", "auth_db_filename") self.authDbFilename = self.config.get("brmdoor", "auth_db_filename")
self.desfirePubkey = self.config.get("brmdoor", "desfire_ed25519_pubkey")
self.lockOpenedSecs = self.config.getint("brmdoor", "lock_opened_secs") self.lockOpenedSecs = self.config.getint("brmdoor", "lock_opened_secs")
self.unknownUidTimeoutSecs = self.config.getint("brmdoor", "unknown_uid_timeout_secs") self.unknownUidTimeoutSecs = self.config.getint("brmdoor", "unknown_uid_timeout_secs")
self.logFile = self.config.get("brmdoor", "log_file") self.logFile = self.config.get("brmdoor", "log_file")
@ -65,6 +66,7 @@ class NFCScanner(object):
""" """
self.authenticator = UidAuthenticator(config.authDbFilename) self.authenticator = UidAuthenticator(config.authDbFilename)
self.hmacAuthenticator = None self.hmacAuthenticator = None
self.desfireAuthenticator = None
self.unknownUidTimeoutSecs = config.unknownUidTimeoutSecs self.unknownUidTimeoutSecs = config.unknownUidTimeoutSecs
self.lockOpenedSecs = config.lockOpenedSecs self.lockOpenedSecs = config.lockOpenedSecs
@ -82,6 +84,10 @@ class NFCScanner(object):
self.hmacAuthenticator = YubikeyHMACAuthenthicator( self.hmacAuthenticator = YubikeyHMACAuthenthicator(
config.authDbFilename, self.nfc config.authDbFilename, self.nfc
) )
self.desfireAuthenticator = DesfireEd25519Authenthicator(
config.authDbFilename, self.nfc,
config.desfirePubkey
)
#self.nfc.pollNr = 0xFF #poll indefinitely #self.nfc.pollNr = 0xFF #poll indefinitely
while True: while True:
try: try:
@ -125,6 +131,14 @@ class NFCScanner(object):
self.unlocker.unlock() self.unlocker.unlock()
return return
#test for Desfire NDEF auth
record = self.desfireAuthenticator.checkUIDSignature(uid_hex)
if record is not None:
logging.info("Unlocking after Desfire NDEF ed25519 check for UID %s", record)
self.unlocker.unlock()
return
logging.info("Unknown UID %s", uid_hex) logging.info("Unknown UID %s", uid_hex)
time.sleep(self.unknownUidTimeoutSecs) time.sleep(self.unknownUidTimeoutSecs)

View file

@ -28,5 +28,10 @@ if __name__ == "__main__":
key_hex TEXT, key_hex TEXT,
nick TEXT) nick TEXT)
""") """)
cursor.execute("""CREATE TABLE authorized_desfires(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid_hex TEXT,
nick TEXT)
""")
conn.commit() conn.commit()
conn.close() conn.close()

19
generate_ed25519_keypair.py Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env python2
"""
Used to generate keypair for signing NDEF messages for Mifare NDEF authentication
"""
import os
import axolotl_curve25519 as curve
from binascii import hexlify
random32 = os.urandom(32)
private_key = curve.generatePrivateKey(random32)
public_key = curve.generatePublicKey(private_key)
print "private key in hex:", hexlify(private_key)
print "public key in hex :", hexlify(public_key)

27
sign_uid.py Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env python2
import sys
import os
import axolotl_curve25519 as curve
def signUid(private_key, uid):
"""
Create an Ed25519 signature for UID
:param private_key: Binary representation of Ed25519 key
:param uid: UID, decoded to binary from hex
:return: singature in binary format
"""
random64 = os.urandom(64)
signature = curve.calculateSignature(random64, private_key, uid)
return signature
if __name__ == "__main__":
if len(sys.argv) < 3:
print >> sys.stderr, "Usage: sign_uid.py uid_hex ed25519_private_key_hex"
print >> sys.stderr, "Outputs binary signature, you will probably want to redirect it to a file"
sys.exit(1)
private_key = sys.argv[3].decode("hex")
uid_bin = sys.argv[1].decode("hex")
sys.stdout.write(signUid(private_key, uid_bin))

View file

@ -51,6 +51,7 @@ print "Available tests: %s" % ", ".join(sorted(tests.keys()))
print "Selected test: %s" % apdu_test print "Selected test: %s" % apdu_test
# select apdus according to test name # select apdus according to test name
if apdu_test in tests:
hex_apdus = tests[apdu_test] hex_apdus = tests[apdu_test]
apdus = [hex_apdu.replace(" ","").decode("hex") for hex_apdu in hex_apdus] apdus = [hex_apdu.replace(" ","").decode("hex") for hex_apdu in hex_apdus]
@ -78,3 +79,20 @@ try:
nfc.unload() nfc.unload()
except NFCError, e: except NFCError, e:
print "Reading UID failed:", e.what() print "Reading UID failed:", e.what()
elif apdu_test == "desfire-ndef4":
try:
nfc = NFCDevice()
uid = nfc.scanUID()
print "UID", hexlify(uid)
#nfc.close()
#nfc.open()
ndef = nfc.readDesfireNDEF()
print ndef
print "Device is opened:", nfc.opened()
print "Closing device"
nfc.close()
print "Device is opened:", nfc.opened()
nfc.unload()
except NFCError, e:
print "Reading UID failed:", e.what()

Binary file not shown.

View file

@ -1,5 +1,4 @@
import time import time
import wiringpi2 as wiringpi
class Unlocker(object): class Unlocker(object):
"""Abstract class/interface for Unlocker object. """Abstract class/interface for Unlocker object.
@ -35,6 +34,7 @@ class UnlockerWiringPi(Unlocker):
""" """
def __init__(self, config): def __init__(self, config):
import wiringpi2 as wiringpi
Unlocker.__init__(self, config) Unlocker.__init__(self, config)
wiringpi.wiringPiSetupGpio() # pin numbers follow P1 GPIO header wiringpi.wiringPiSetupGpio() # pin numbers follow P1 GPIO header
self.lockPin = self.config.getint("UnlockerWiringPi", "lock_pin") self.lockPin = self.config.getint("UnlockerWiringPi", "lock_pin")
@ -43,6 +43,7 @@ class UnlockerWiringPi(Unlocker):
def unlock(self): def unlock(self):
"""Unlocks lock at configured pin by pulling it high. """Unlocks lock at configured pin by pulling it high.
""" """
import wiringpi2 as wiringpi
wiringpi.digitalWrite(self.lockPin, 1) wiringpi.digitalWrite(self.lockPin, 1)
time.sleep(self.lockOpenedSecs) time.sleep(self.lockOpenedSecs)
wiringpi.digitalWrite(self.lockPin, 0) wiringpi.digitalWrite(self.lockPin, 0)
@ -52,5 +53,6 @@ class UnlockerWiringPi(Unlocker):
Lock the lock back. Meant to be used when program is shut down Lock the lock back. Meant to be used when program is shut down
so that lock is not left disengaged. so that lock is not left disengaged.
""" """
import wiringpi2 as wiringpi
wiringpi.digitalWrite(self.lockPin, 0) wiringpi.digitalWrite(self.lockPin, 0)

62
write_signed_ndef_on_desfire.py Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python
import sys
import os
import tempfile
from binascii import hexlify
from nfc_smartcard import NFCDevice, NFCError
from sign_uid import signUid
if len(sys.argv) < 2:
print "Usage: write_signed_ndef_on_desfire.py private_key_in_hex"
sys.exit(3)
tempFile = None
tempFname = None
try:
print "Opening NFC reader"
nfc = NFCDevice()
nfc.pollNr = 0xFF #poll indefinitely
print "Waiting for Desfire card to appear in the reader"
uid_hex = hexlify(nfc.scanUID())
key = sys.argv[1].decode("hex")
print("Got UID %s", uid_hex)
signature = signUid(key, uid_hex.decode("hex"))
(tempFile, tempFname) = tempfile.mkstemp(dir="/tmp")
with tempFile:
tempFile.write(signature)
except NFCError, e:
#this exception happens also when scanUID times out
print("Failed to wait for Desfire card: %s" % e)
sys.exit(1)
except Exception, e:
print("Something went wrong when writing the signature to file:", e)
sys.exit(2)
finally:
nfc.close()
nfc.unload()
if tempFname:
os.unlink(tempFname)
# We'll just call the command line tools so that we don't need to copy&paste the NDEF writing code to nfc_smartcard.cpp
print "Formatting card"
res = os.system("mifare-desfire-format -y")
if res != 0:
print "Formatting failed"
sys.exit(4)
print "Creating NDEF file/application"
res = os.system("mifare-desfire-create-ndef -y")
if res != 0:
print "Creating NDEF failed"
sys.exit(4)
print "Writing NDEF with signature onto Desfire"
res = os.system("mifare-desfire-create-ndef -y -i '%'" % tempFname)
if res != 0:
print "Writing NDEF failed"
sys.exit(4)
print "All done"