131 lines
3.1 KiB
Python
131 lines
3.1 KiB
Python
|
from time import time
|
||
|
from base64 import b16encode
|
||
|
from hashlib import sha3_512
|
||
|
import os.path
|
||
|
from security_const import *
|
||
|
import logging
|
||
|
import binascii
|
||
|
import ndef
|
||
|
from io import BytesIO
|
||
|
from nfc.tag.tt2 import Type2TagCommandError
|
||
|
|
||
|
logging.basicConfig(level='INFO')
|
||
|
log = logging.getLogger('badge')
|
||
|
|
||
|
START_PAGE = {
|
||
|
"NXP NTAG213": 0x11,
|
||
|
"NXP NTAG215": 0x6B,
|
||
|
"NXP NTAG216": 0xCB
|
||
|
}
|
||
|
|
||
|
USER_PAGE_BOUNDS = {
|
||
|
"NXP NTAG213": (0x04, 0x27),
|
||
|
"NXP NTAG215": (0x04, 0x81),
|
||
|
"NXP NTAG216": (0x04, 0xE1),
|
||
|
}
|
||
|
|
||
|
CONFIG_PAGES = {
|
||
|
"NXP NTAG213": (0x29, 0x2A, 0x2B, 0x2C),
|
||
|
"NXP NTAG215": (0x83, 0x84, 0x85, 0x86),
|
||
|
"NXP NTAG216": (0xE3, 0xE4, 0xE5, 0xE6),
|
||
|
}
|
||
|
|
||
|
class Badge:
|
||
|
def __init__(self, tag, uuid=None):
|
||
|
self.tag = tag
|
||
|
self.uuid = uuid
|
||
|
|
||
|
def read_transaction(self):
|
||
|
bounds = USER_PAGE_BOUNDS[self.tag.product]
|
||
|
start_page = START_PAGE[self.tag.product] # We assume transactions are 22 pages wide
|
||
|
|
||
|
data = b""
|
||
|
offset = 0
|
||
|
|
||
|
while 1:
|
||
|
print("Reading", hex(start_page+offset), end=' ')
|
||
|
|
||
|
chunk = self.tag.read(start_page+offset)
|
||
|
print(b16encode(chunk))
|
||
|
if len(data) == 0:
|
||
|
if bytes(chunk[:4]) != HEADER:
|
||
|
print('header is', HEADER)
|
||
|
print('chunk 4', bytes(chunk[:4]))
|
||
|
print('Does not start with header :(')
|
||
|
|
||
|
data += chunk
|
||
|
offset += 4
|
||
|
|
||
|
if FOOTER in chunk:
|
||
|
data = data[:data.find(FOOTER)+4]
|
||
|
break
|
||
|
|
||
|
return data
|
||
|
|
||
|
def write_transaction(self, tx, verify=True):
|
||
|
bounds = USER_PAGE_BOUNDS[self.tag.product]
|
||
|
start = START_PAGE[self.tag.product]
|
||
|
|
||
|
with BytesIO(tx.get_bytes()) as data:
|
||
|
|
||
|
offset = 0
|
||
|
|
||
|
# Write the transaction
|
||
|
while 1:
|
||
|
chunk = data.read(4)
|
||
|
if not chunk: break
|
||
|
|
||
|
print("Writing", hex(start+offset), b16encode(chunk))
|
||
|
assert bounds[0] <= start+offset <= bounds[1]
|
||
|
|
||
|
self.tag.write(start+offset, chunk)
|
||
|
|
||
|
offset += 1
|
||
|
|
||
|
# Read the transaction back
|
||
|
if verify:
|
||
|
print('expected:::', tx.get_bytes())
|
||
|
print('read:::::', self.read_transaction())
|
||
|
assert tx.get_bytes() == self.read_transaction()
|
||
|
|
||
|
def enable_count(self):
|
||
|
|
||
|
page_addr = CONFIG_PAGES[self.tag.product][1]
|
||
|
|
||
|
page = self.tag.read(page_addr)[:4]
|
||
|
if not page[0] & (1<<4):
|
||
|
log.info("Enabling tag counter")
|
||
|
page[0] |= 1 << 4 # Set NFC_CNT_EN = 1
|
||
|
self.tag.write(page_addr, page)
|
||
|
else:
|
||
|
log.info("Tag counter is already enabled!")
|
||
|
|
||
|
try:
|
||
|
count = self.tag.transceive(binascii.unhexlify('3902'), timeout=0.05) # READ_CNT command
|
||
|
except Type2TagCommandError:
|
||
|
log.error("Tag count is enabled, but didn't get any response anyway.")
|
||
|
else:
|
||
|
log.info(f"Tag count is {int.from_bytes(count, byteorder='little')}")
|
||
|
|
||
|
def protect(self):
|
||
|
self.tag.protect(self.password())
|
||
|
|
||
|
def password(self, with_uuid=True):
|
||
|
x = self.tag.identifier
|
||
|
for i in range(ITERATIONS):
|
||
|
x = sha3_512(x + (self.uuid if with_uuid and self.uuid else bytes(0)) + SALT).digest()
|
||
|
return x[:6]
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
|
||
|
from types import SimpleNamespace
|
||
|
|
||
|
tag = SimpleNamespace()
|
||
|
tag.identifier = bytes(8)
|
||
|
|
||
|
pt = Protect(tag, uuid=b"bye")
|
||
|
start = time()
|
||
|
print('with uuid', b16encode(pt.password()))
|
||
|
print('without uuid', b16encode(pt.password(with_uuid=False)))
|
||
|
print("took", (time()-start)*1000, 'ms')
|