mirror of
https://github.com/brmlab/brmdoor_libnfc.git
synced 2025-06-07 08:04:06 +02:00
Authentication with NDEF message on Desfire containing signed UID via Ed25519
This commit is contained in:
parent
892b69f939
commit
d0121aaed9
12 changed files with 286 additions and 41 deletions
29
README.md
29
README.md
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 = -
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
19
generate_ed25519_keypair.py
Executable 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
27
sign_uid.py
Normal 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))
|
26
test_nfc.py
26
test_nfc.py
|
@ -51,10 +51,11 @@ print "Available tests: %s" % ", ".join(sorted(tests.keys()))
|
|||
print "Selected test: %s" % apdu_test
|
||||
|
||||
# select apdus according to test name
|
||||
hex_apdus = tests[apdu_test]
|
||||
apdus = [hex_apdu.replace(" ","").decode("hex") for hex_apdu in hex_apdus]
|
||||
if apdu_test in tests:
|
||||
hex_apdus = tests[apdu_test]
|
||||
apdus = [hex_apdu.replace(" ","").decode("hex") for hex_apdu in hex_apdus]
|
||||
|
||||
try:
|
||||
try:
|
||||
nfc = NFCDevice()
|
||||
uid = nfc.scanUID()
|
||||
print "UID", hexlify(uid)
|
||||
|
@ -76,5 +77,22 @@ try:
|
|||
nfc.close()
|
||||
print "Device is opened:", nfc.opened()
|
||||
nfc.unload()
|
||||
except NFCError, e:
|
||||
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.
|
@ -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
62
write_signed_ndef_on_desfire.py
Executable 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"
|
Loading…
Add table
Add a link
Reference in a new issue