Added Yubikey Neo HMAC authenthication

This commit is contained in:
Ondrej Mikle 2014-07-23 13:12:15 +02:00
parent 5b77dff18d
commit b281c7a0bc
4 changed files with 109 additions and 8 deletions

View file

@ -1,4 +1,10 @@
import sqlite3 import sqlite3
import hmac
import hashlib
import logging
from brmdoor_nfc import NFCError
class UidRecord(object): class UidRecord(object):
"""Represents UID<->nick pair""" """Represents UID<->nick pair"""
@ -61,6 +67,78 @@ class UidAuthenticator(object):
self.conn.close() self.conn.close()
class YubikeyHMACAuthenthicator(object):
"""
Uses Yubikey Neo's built-in HMAC functionality on slot 2 (needs to be
configured using Yubikey tools to be on this slot).
"""
def __init__(self, filename, nfcReader):
"""
Connects to database by given filename and later checks UIDs
against that database.
"""
#again autocommit mode
self.conn = sqlite3.connect(filename, isolation_level=None)
self.nfcReader = nfcReader
def hmacCheck(self, key, challenge, result):
"""
Returns true iff HMAC-SHA1 with given key and challenge string
transforms into given result.
"""
hashed = hmac.new(key, challenge, hashlib.sha1)
#We should use hmac.compare_digest(), but that's in new Python
#version only. Here timing side channels are not much of concern.
return hashed.digest() == result
def checkHMACforUID(self, uid_hex):
"""
Checks if UID is in database. If so
@param uid_hex: uid to match in hex
@returns UidRecord instance if found, None otherwise
"""
cursor = self.conn.cursor()
sql = "SELECT nick, key_hex FROM authorized_hmac_keys 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]
secretKey = record[1].decode("hex")
challenge = 'Sample #2'
# Select HMAC-SHA1 on slot 2 from Yubikey
apdusHex = [
"00 A4 04 00 07 A0 00 00 05 27 20 01",
"00 01 38 00 %02x %s" % (len(challenge), challenge.encode("hex"))
]
rapdu = None
for apduHex in apdusHex:
try:
apdu = apduHex.replace(" ", "").decode("hex")
rapdu = self.nfcReader.sendAPDU(apdu)
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())
return None
if not self.hmacCheck(secretKey, challenge, rapdu.data()):
return None
return UidRecord(uid_hex, nick)
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

@ -9,7 +9,7 @@ from binascii import hexlify
from brmdoor_nfc import NFCDevice, NFCError from brmdoor_nfc import NFCDevice, NFCError
from brmdoor_authenticator import UidAuthenticator from brmdoor_authenticator import UidAuthenticator, YubikeyHMACAuthenthicator
import unlocker import unlocker
class BrmdoorConfigError(ConfigParser.Error): class BrmdoorConfigError(ConfigParser.Error):
@ -64,6 +64,7 @@ class NFCScanner(object):
"""Create worker reading UIDs from PN53x reader. """Create worker reading UIDs from PN53x reader.
""" """
self.authenticator = UidAuthenticator(config.authDbFilename) self.authenticator = UidAuthenticator(config.authDbFilename)
self.hmacAuthenticator = None
self.unknownUidTimeoutSecs = config.unknownUidTimeoutSecs self.unknownUidTimeoutSecs = config.unknownUidTimeoutSecs
self.lockOpenedSecs = config.lockOpenedSecs self.lockOpenedSecs = config.lockOpenedSecs
@ -78,6 +79,9 @@ class NFCScanner(object):
authorized. authorized.
""" """
self.nfc = NFCDevice() self.nfc = NFCDevice()
self.hmacAuthenticator = YubikeyHMACAuthenthicator(
config.authDbFilename, self.nfc
)
#self.nfc.pollNr = 0xFF #poll indefinitely #self.nfc.pollNr = 0xFF #poll indefinitely
while True: while True:
try: try:
@ -106,14 +110,23 @@ class NFCScanner(object):
""" """
record = self.authenticator.fetchUidRecord(uid_hex) record = self.authenticator.fetchUidRecord(uid_hex)
#no match #direct UID match
if record is None: if record is not None:
logging.info("Unknown UID %s", uid_hex) logging.info("Unlocking for UID %s", record)
time.sleep(self.unknownUidTimeoutSecs) self.unlocker.unlock()
return return
logging.info("Unlocking for UID %s", record) #test for Yubikey HMAC auth
self.unlocker.unlock() record = self.hmacAuthenticator.checkHMACforUID(uid_hex)
if record is not None:
logging.info("Unlocking after HMAC for UID %s", record)
self.unlocker.unlock()
return
logging.info("Unknown UID %s", uid_hex)
time.sleep(self.unknownUidTimeoutSecs)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -17,6 +17,16 @@ if __name__ == "__main__":
conn = sqlite3.connect(filename) conn = sqlite3.connect(filename)
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("CREATE TABLE authorized_uids(id INTEGER PRIMARY KEY AUTOINCREMENT, uid_hex TEXT, nick TEXT)") cursor.execute("""CREATE TABLE authorized_uids(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid_hex TEXT,
nick TEXT)
""")
cursor.execute("""CREATE TABLE authorized_hmac_keys(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid_hex TEXT,
key_hex TEXT,
nick TEXT)
""")
conn.commit() conn.commit()
conn.close() conn.close()

Binary file not shown.