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:
- [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/)
- [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
1. Create the database
@ -44,18 +50,29 @@ You need just to run `make`. Additional dependencies:
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
- 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:
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"
sys.exit(1)
def addHmacAuth(cursor, uid_hex, nick, key_hex):
"""
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"
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__":
parser = OptionParser()
parser.add_option("-c", "--config", action="store", type="string", dest="config",
help="Configuration file")
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()
if opts.config is None:
@ -61,9 +76,9 @@ if __name__ == "__main__":
parser.print_help()
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, "Acceptable choices: uid, hmac"
print >> sys.stderr, "Acceptable choices: uid, hmac, ndef"
sys.exit(1)
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"
sys.exit(1)
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.close()

View file

@ -4,6 +4,8 @@ import hmac
import hashlib
import logging
import axolotl_curve25519 as curve
from nfc_smartcard import NFCError
@ -128,7 +130,7 @@ class YubikeyHMACAuthenthicator(object):
if not rapdu.valid or rapdu.sw() != 0x9000:
raise NFCError("HMAC - response SW is not 0x9000")
except NFCError, e:
logging.debug("Yubikey HMAC command failed: %s" % e.what())
logging.info("Yubikey HMAC command failed: %s" % e.what())
return None
if not self.hmacCheck(secretKey, challenge, rapdu.data()):
@ -141,6 +143,61 @@ class YubikeyHMACAuthenthicator(object):
"""Closes connection to database"""
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
if __name__ == "__main__":
authenticator = UidAuthenticator("test_uids_db.sqlite")

View file

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

View file

@ -9,7 +9,7 @@ from binascii import hexlify
from nfc_smartcard import NFCDevice, NFCError
from brmdoor_authenticator import UidAuthenticator, YubikeyHMACAuthenthicator
from brmdoor_authenticator import UidAuthenticator, YubikeyHMACAuthenthicator, DesfireEd25519Authenthicator
import unlocker
class BrmdoorConfigError(ConfigParser.Error):
@ -40,6 +40,7 @@ class BrmdoorConfig(object):
self.config.read(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.unknownUidTimeoutSecs = self.config.getint("brmdoor", "unknown_uid_timeout_secs")
self.logFile = self.config.get("brmdoor", "log_file")
@ -65,6 +66,7 @@ class NFCScanner(object):
"""
self.authenticator = UidAuthenticator(config.authDbFilename)
self.hmacAuthenticator = None
self.desfireAuthenticator = None
self.unknownUidTimeoutSecs = config.unknownUidTimeoutSecs
self.lockOpenedSecs = config.lockOpenedSecs
@ -82,6 +84,10 @@ class NFCScanner(object):
self.hmacAuthenticator = YubikeyHMACAuthenthicator(
config.authDbFilename, self.nfc
)
self.desfireAuthenticator = DesfireEd25519Authenthicator(
config.authDbFilename, self.nfc,
config.desfirePubkey
)
#self.nfc.pollNr = 0xFF #poll indefinitely
while True:
try:
@ -125,6 +131,14 @@ class NFCScanner(object):
self.unlocker.unlock()
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)
time.sleep(self.unknownUidTimeoutSecs)

View file

@ -28,5 +28,10 @@ if __name__ == "__main__":
key_hex TEXT,
nick TEXT)
""")
cursor.execute("""CREATE TABLE authorized_desfires(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid_hex TEXT,
nick TEXT)
""")
conn.commit()
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
# select apdus according to test name
if apdu_test in tests:
hex_apdus = tests[apdu_test]
apdus = [hex_apdu.replace(" ","").decode("hex") for hex_apdu in hex_apdus]
@ -78,3 +79,20 @@ try:
nfc.unload()
except NFCError, e:
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 wiringpi2 as wiringpi
class Unlocker(object):
"""Abstract class/interface for Unlocker object.
@ -35,6 +34,7 @@ class UnlockerWiringPi(Unlocker):
"""
def __init__(self, config):
import wiringpi2 as wiringpi
Unlocker.__init__(self, config)
wiringpi.wiringPiSetupGpio() # pin numbers follow P1 GPIO header
self.lockPin = self.config.getint("UnlockerWiringPi", "lock_pin")
@ -43,6 +43,7 @@ class UnlockerWiringPi(Unlocker):
def unlock(self):
"""Unlocks lock at configured pin by pulling it high.
"""
import wiringpi2 as wiringpi
wiringpi.digitalWrite(self.lockPin, 1)
time.sleep(self.lockOpenedSecs)
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
so that lock is not left disengaged.
"""
import wiringpi2 as wiringpi
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"