forked from brmlab/brmdoor_libnfc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
brmdoor_authenticator.py
228 lines (183 loc) · 7.36 KB
/
brmdoor_authenticator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import os
import sqlite3
import hmac
import hashlib
import logging
import json
import axolotl_curve25519 as curve
from nfc_smartcard import NFCError
class UidRecord(object):
"""Represents UID<->nick pair"""
def __init__(self, uid_hex, nick):
"""
Create instance binding UID to nick. UIDs should be either 4, 7
or 10 bytes long, but that's ISO14443 thing - this object has
no such limitation.
UID will be stored in uppercase hex, converted if necessary.
@param uid_hex: uid in hex
@param nick: nickname this UID belongs to
"""
self.uid_hex = uid_hex.upper()
self.nick = nick
def __str__(self):
return "(uid: %s, nick: %s)" % (self.uid_hex, self.nick)
def __repr__(self):
return "<UidRecord: uid: %s, nick: %s>" % \
(repr(self.uid_hex), repr(self.nick))
class UidAuthenticator(object):
"""Checks UIDs of ISO14443 RFID cards against database."""
def __init__(self, filename):
"""
Connects to database by given filename and later checks UIDs
against that database.
"""
#open in autocommit mode - we are not changing anything
self.conn = sqlite3.connect(filename, isolation_level=None)
def fetchUidRecord(self, uid_hex):
"""
Returns first record that matches given UID or None if nothing
is found.
@param uid_hex: uid to match in hex
@returns UidRecord instance if found, None otherwise
"""
cursor = self.conn.cursor()
sql = "SELECT nick FROM authorized_uids 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]
return UidRecord(uid_hex, nick)
def shutdown(self):
"""Closes connection to database"""
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 = os.urandom(32)
# 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.info("Yubikey HMAC command failed: %s" % e.what())
return None
if not self.hmacCheck(secretKey, challenge, rapdu.data()):
logging.info("HMAC check failed for UID %s", uid_hex)
return None
return UidRecord(uid_hex, nick)
def shutdown(self):
"""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:
ndefJson = json.loads(self.nfcReader.readDesfireNDEF())
ndefSignature = ndefJson["brmdoorSignature"].decode("hex")
if len(ndefSignature) != 64:
logging.error("NDEF signature has wrong length")
return None
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.error("Desfire read NDEF failed: %s", e.what())
return None
except TypeError, e:
logging.error("Could not decode signature from JSON: %s", e)
return None
except ValueError, e:
logging.error("Could not decode JSON from NDEF: %s", e)
return None
except KeyError, e:
logging.error("Missing signature in JSON: %s", e)
return None
def shutdown(self):
"""Closes connection to database"""
self.conn.close()
#test routine
if __name__ == "__main__":
authenticator = UidAuthenticator("test_uids_db.sqlite")
record = authenticator.fetchUidRecord("043a1482cc2280")
print "For UID 043a1482cc2280 we found:", repr(record)
record = authenticator.fetchUidRecord("34795fad")
print "For UID 34795fad we found:", repr(record)
record = authenticator.fetchUidRecord("01020304")
print "For UID 01020304 we found:", repr(record)
authenticator.shutdown()