Initial commit
This commit is contained in:
commit
ec1d89298f
|
@ -0,0 +1,130 @@
|
||||||
|
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')
|
|
@ -0,0 +1,147 @@
|
||||||
|
import nfc
|
||||||
|
from nfc.clf import RemoteTarget
|
||||||
|
import logging
|
||||||
|
from time import time
|
||||||
|
import os.path
|
||||||
|
import coloredlogs
|
||||||
|
coloredlogs.install()
|
||||||
|
from base64 import b16encode, b16decode
|
||||||
|
import sys
|
||||||
|
import ndef
|
||||||
|
import binascii
|
||||||
|
from badge import Badge
|
||||||
|
from os import system
|
||||||
|
|
||||||
|
CONFIG_PAGES = {
|
||||||
|
"NXP NTAG213": (0x29, 0x2A, 0x2B, 0x2C),
|
||||||
|
"NXP NTAG215": (0x83, 0x84, 0x85, 0x86),
|
||||||
|
"NXP NTAG216": (0xE3, 0xE4, 0xE5, 0xE6),
|
||||||
|
}
|
||||||
|
|
||||||
|
session_start = time()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
log = logging.getLogger('nfc')
|
||||||
|
|
||||||
|
nfc_type = sys.argv[1]
|
||||||
|
balance = 0
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
balance = sys.argv[2]
|
||||||
|
elif nfc_type == 'coin':
|
||||||
|
log.error("You cannot create empty coins.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
clf = nfc.ContactlessFrontend()
|
||||||
|
|
||||||
|
log.info(f"Enrolling mode: {nfc_type}")
|
||||||
|
log.info(f"Initial balance: {balance}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert clf.open('tty:USB0:pn532')
|
||||||
|
except TimeoutError:
|
||||||
|
log.error('Got TimeoutError on reader connection attempt.')
|
||||||
|
except AssertionError:
|
||||||
|
log.error('There is no reader connected.')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# Set baud rate to 115200 to prevent reader from dying
|
||||||
|
clf.device.chipset.set_serial_baudrate(115200)
|
||||||
|
clf.device.chipset.transport.baudrate = 115200
|
||||||
|
|
||||||
|
try:
|
||||||
|
while 1:
|
||||||
|
target = clf.sense(RemoteTarget('106A'), iterations=10)
|
||||||
|
if not target: continue
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
|
||||||
|
tag = nfc.tag.activate(clf, target)
|
||||||
|
if not tag:
|
||||||
|
print("Tag was gone while writing.")
|
||||||
|
break
|
||||||
|
|
||||||
|
if tag.product not in ["NXP NTAG215"]:
|
||||||
|
raise Exception(f"This tag ({tag.product}) is not compatible with the barcard system.")
|
||||||
|
|
||||||
|
#print("\n".join(tag.dump()))
|
||||||
|
#break
|
||||||
|
|
||||||
|
badge = Badge(tag)
|
||||||
|
|
||||||
|
password = badge.password()
|
||||||
|
try:
|
||||||
|
has_password = tag.read(CONFIG_PAGES[tag.product][0])[3] < 0xFF
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_password:
|
||||||
|
log.warning("TAG has a password already! Trying to re-enroll anyway.")
|
||||||
|
tag.authenticate(password)
|
||||||
|
|
||||||
|
#tx = Transaction(tag_id=tag.identifier)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Disable the mirror, for now
|
||||||
|
page_addr = CONFIG_PAGES[tag.product][0]
|
||||||
|
page = tag.read(page_addr)[:4]
|
||||||
|
page[0] &= 0b111111 # Disable UID + COUNT mirror
|
||||||
|
tag.write(page_addr, page)
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Activate it again
|
||||||
|
tag = nfc.tag.activate(clf, target)
|
||||||
|
if has_password:
|
||||||
|
tag.authenticate(password)
|
||||||
|
|
||||||
|
# Write the correct url depending on the type of wanted badge
|
||||||
|
if nfc_type == 'badge':
|
||||||
|
url = "https://go.foxo.me/fz23/bxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
else:
|
||||||
|
url = f"https://go.foxo.me/fz23/cxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
|
||||||
|
tag.format()
|
||||||
|
tag.ndef.records = [ndef.uri.UriRecord(url), ]
|
||||||
|
if tag.ndef.records[0].uri != url:
|
||||||
|
raise Exception("Written uri is different than expected.")
|
||||||
|
|
||||||
|
boundary = tag.read(0x08)
|
||||||
|
if not boundary.startswith(b"e/fz23/"):
|
||||||
|
raise Exception(f"This tag does not seem to correctly store ndef data. Got wrong {boundary}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
page_addr = CONFIG_PAGES[tag.product][0]
|
||||||
|
page = tag.read(page_addr)[:4]
|
||||||
|
page[0] |= 0b11 << 6 # Enable UID + COUNT mirror
|
||||||
|
if tag.product == 'NXP NTAG215':
|
||||||
|
page[2] = 0x0A # Location of UID + COUNT
|
||||||
|
elif tag.product == 'NXP NTAG213':
|
||||||
|
page[2] = 0x0B
|
||||||
|
tag.write(page_addr, page)
|
||||||
|
log.info("UID + COUNT mirror enabled.")
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
break
|
||||||
|
|
||||||
|
print('EC')
|
||||||
|
badge.enable_count()
|
||||||
|
tag = nfc.tag.activate(clf, target)
|
||||||
|
if not has_password:
|
||||||
|
print('PRT')
|
||||||
|
badge.protect()
|
||||||
|
|
||||||
|
with open(f'log_{int(session_start)}.txt', 'a') as f:
|
||||||
|
f.write(f"{b16encode(tag.identifier).decode()},{nfc_type},{balance}\n")
|
||||||
|
|
||||||
|
system('mpv success.wav')
|
||||||
|
log.info(f"{time()-start:.2f} tag write done.")
|
||||||
|
while clf.sense(RemoteTarget('106A')): pass
|
||||||
|
|
||||||
|
system
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
clf.close()
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
# ⚠️ DO NOT USE THIS! READ BELOW! ⚠️
|
||||||
|
|
||||||
|
Due to a flaw of nfcpy (or maybe not?) tags enrolled by this script will have the capability bit set to 0x31. **This for some reason makes the NDEF payload only compatible with Android phones**!
|
||||||
|
|
||||||
|
This script can be used to enroll your NFC tags in the badge system. Effectively, it will:
|
||||||
|
|
||||||
|
1. Enable the READ_CNT flag, to enable the read counter
|
||||||
|
2. Add a NDEF Uri so that the badge can be scanned by attendants
|
||||||
|
3. Protect the badge with a password, generated uniquely for every tag
|
||||||
|
4. Log the UID and badge type, so you can import it in your backend
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Check the requirements.txt file for python lib requirements
|
||||||
|
2. Install mpv to play a sound at the end of every succesfull write, or don't (and remove the row)
|
||||||
|
3. Change the SALT to a very secure password and adjust the iteration count to a sensible number for your computer
|
||||||
|
4. Connect a PN532 scanner via USB
|
||||||
|
5. Enjoy!
|
||||||
|
|
||||||
|
## Run the script
|
||||||
|
|
||||||
|
```python3 enroll.py [badge/coin] [initial balance]```
|
||||||
|
|
||||||
|
The badge/coin changes the url, while initial balance changes the column on the data export (the log_*.txt files)
|
||||||
|
|
||||||
|
## The ASCII mirror!
|
||||||
|
|
||||||
|
You will notice that every badge has a generic "https://go.foxo.me/fz23/bxxxxxxxxxxxxxxxxxxxxx" url written to it.
|
||||||
|
This is on purpose since after this write the UID + COUNT mirror is enabled.
|
||||||
|
This is a function of the NDEF tags that lets you "mirror" various registers in ascii format to other arbitrary addresses.
|
||||||
|
The code following the ndef part will effectively make so that when you read the link will actually become `"/fz23/b[TAG IDENTIFIER]x[READ COUNT]"` (e.g. `"/fz23/b048E77929A7181x0000EB"`) without any manual intervention and in a completely tear-free way.
|
||||||
|
|
||||||
|
## The "toys" folder
|
||||||
|
|
||||||
|
Inside of the toys folder there are two files which were a proof of concept for the storage of monetary transactions inside of NFC tags, for use to buy drinks and similar. Eventually, the system was moved completely server-side (due to slowness in writing the cards and risk of tearing), and the entire system scrapped.
|
||||||
|
|
||||||
|
The system consisted in ed25519-based transaction signatures, where offline clients (aka the PoS) would be able to verify and write transactions. The cards would actually store two transactions and then change a pointer atomically to prevent half-written transaction from being considered.
|
||||||
|
|
||||||
|
The transaction format is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ CA$H ]
|
||||||
|
[signature ] * 16
|
||||||
|
[load][used]
|
||||||
|
[keyi][txid]
|
||||||
|
[timestamp ]
|
||||||
|
[ uuid ]
|
||||||
|
[ F0X0 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
- CA$H and F0X0 are header and footer of the transaction (4 bytes each)
|
||||||
|
- signature = ed25519 signature of the transaction (4 * 16 = 64 bytes)
|
||||||
|
- load (2 bytes) + used (2 bytes) = the amount of loaded and used money. These two values can only increase and the balance is calculated from subtracting them so that "replay" attacks are harder
|
||||||
|
- keyi = id of the key used to sign the transaction (2 bytes)
|
||||||
|
- txid = increasing id of the transaction (2 bytes)
|
||||||
|
- timestamp = unix ts of the transaction (4 bytes)
|
||||||
|
- uuid = a custom uuid to identify the card (this was added because at one point we accidentally bought counterfeit cards with duplicated NFC identifiers)
|
|
@ -0,0 +1,2 @@
|
||||||
|
coloredlogs
|
||||||
|
nfcpy
|
|
@ -0,0 +1,2 @@
|
||||||
|
SALT = b"This is the salt to generate the badge password :)"
|
||||||
|
ITERATIONS = 10000
|
Binary file not shown.
|
@ -0,0 +1,176 @@
|
||||||
|
import nfc
|
||||||
|
import sys
|
||||||
|
from nfc.clf import RemoteTarget
|
||||||
|
from nfc.tag.tt2 import Type2TagCommandError
|
||||||
|
import logging
|
||||||
|
from base64 import b64encode, b16encode
|
||||||
|
from os.path import join
|
||||||
|
from time import time
|
||||||
|
from hashlib import sha3_256
|
||||||
|
import ndef
|
||||||
|
import coloredlogs
|
||||||
|
import binascii
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from colorama import init, Fore, Back, Style
|
||||||
|
|
||||||
|
coloredlogs.install(fmt='%(levelname)s %(message)s')
|
||||||
|
init()
|
||||||
|
|
||||||
|
uids = json.load(open('uids.json'))
|
||||||
|
|
||||||
|
def get_hash(payload, count=10000, salt=b"x!"):
|
||||||
|
data = sha3_256(payload + (salt or bytes(0)))
|
||||||
|
for _ in range(count):
|
||||||
|
data = sha3_256(data.digest() + (salt or bytes(0)))
|
||||||
|
return data.digest()
|
||||||
|
|
||||||
|
def activate_and_unlock(*args, **kwargs):
|
||||||
|
tag = nfc.tag.activate(*args, **kwargs)
|
||||||
|
tag.has_password = False
|
||||||
|
assert tag.signature
|
||||||
|
if not tag.ndef.is_writeable:
|
||||||
|
tag.has_password = True
|
||||||
|
unlock_result = tag.authenticate(password=get_hash(tag.identifier+tag.signature))
|
||||||
|
if not unlock_result:
|
||||||
|
log.error("This tag is locked and password is incorrect!")
|
||||||
|
return
|
||||||
|
return tag
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
log = logging.getLogger('nfc')
|
||||||
|
clf = nfc.ContactlessFrontend()
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert clf.open('tty:USB0:pn532')
|
||||||
|
except TimeoutError:
|
||||||
|
log.error('Got TimeoutError on reader connection attempt.')
|
||||||
|
except AssertionError:
|
||||||
|
log.error('There is no reader connected.')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
clf.device.chipset.set_serial_baudrate(115200)
|
||||||
|
clf.device.chipset.transport.baudrate = 115200
|
||||||
|
log.info(f"Setting device baud rate to {clf.device.chipset.transport.baudrate}.")
|
||||||
|
|
||||||
|
print(Style.BRIGHT + "What mode would you like to work in?\ns = single enroll\nm = mass enroll\n>>> ", end='')
|
||||||
|
mode = input()[0]
|
||||||
|
|
||||||
|
if mode == 'm':
|
||||||
|
print(Style.BRIGHT + "Choose the number from where to begin enrolling. To stop enrolling, just use CTRL+C to exit the program.\n>>> ", end='')
|
||||||
|
|
||||||
|
try:
|
||||||
|
uid = int(input())
|
||||||
|
except KeyboardInterrupt: exit()
|
||||||
|
except:
|
||||||
|
log.error("The supplied number is not valid.")
|
||||||
|
exit()
|
||||||
|
print(Fore.GREEN + "✔ Mass enroll mode" + Style.RESET_ALL)
|
||||||
|
else:
|
||||||
|
print(Fore.GREEN + "✔ Single enroll mode" + Style.RESET_ALL)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while 1:
|
||||||
|
|
||||||
|
# Ask for a number if in single enroll mode.
|
||||||
|
if mode == 's':
|
||||||
|
while 1:
|
||||||
|
print(Style.BRIGHT + "Input the attendee number\n>>> ", end='')
|
||||||
|
try:
|
||||||
|
uid = int(input())
|
||||||
|
except KeyboardInterrupt: exit()
|
||||||
|
except:
|
||||||
|
log.error("The supplied number is not valid.")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
user_id = uids[str(uid)][0]
|
||||||
|
url = "http://go.foxo.me/" + user_id
|
||||||
|
|
||||||
|
print(Fore.GREEN + "Attendee info:")
|
||||||
|
print(f"NAME: {uids[str(uid)][1]}")
|
||||||
|
print(f" INT: {uid}")
|
||||||
|
print(f"CODE: {user_id}\n")
|
||||||
|
|
||||||
|
print(Fore.YELLOW + "⌛ Waiting for nfc tag", end='')
|
||||||
|
|
||||||
|
# Wait for a successful reading
|
||||||
|
while 1:
|
||||||
|
target = clf.sense(RemoteTarget('106A'), iterations=10)
|
||||||
|
if not target:
|
||||||
|
print('.', end='')
|
||||||
|
sys.stdout.flush()
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
|
||||||
|
print(Style.RESET_ALL)
|
||||||
|
|
||||||
|
# Tag was found, write the details
|
||||||
|
start = time()
|
||||||
|
tag = activate_and_unlock(clf, target)
|
||||||
|
|
||||||
|
if not tag:
|
||||||
|
log.error("Tag was gone mid-write.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if tag.product != 'NXP NTAG215':
|
||||||
|
log.error("Incorrect tag type detected! Tag must be a NXP 215!")
|
||||||
|
while clf.sense(RemoteTarget('106A')): pass
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not tag.signature:
|
||||||
|
log.error("Tag signature was not read correctly. Try again.")
|
||||||
|
while clf.sense(RemoteTarget('106A')): pass
|
||||||
|
continue
|
||||||
|
|
||||||
|
password = get_hash(tag.identifier+tag.signature, 10000)[:6]
|
||||||
|
|
||||||
|
# If tag count is disabled, enable it
|
||||||
|
try:
|
||||||
|
count = tag.transceive(binascii.unhexlify('3902'), timeout=0.05) # READ_CNT command
|
||||||
|
except Type2TagCommandError:
|
||||||
|
page = tag.read(0x84)[:4]
|
||||||
|
if not page[0] & (1<<4):
|
||||||
|
log.info("Enabling tag counter")
|
||||||
|
page[0] |= 1 << 4 # Set NFC_CNT_EN = 1
|
||||||
|
self.tag.write(0x84, page)
|
||||||
|
|
||||||
|
tag = activate_and_unlock(clf, target)
|
||||||
|
count = tag.transceive(binascii.unhexlify('3902'), timeout=0.05) # READ_CNT command
|
||||||
|
|
||||||
|
print(Fore.CYAN+Style.BRIGHT)
|
||||||
|
print(f"UUID: {b16encode(tag.identifier).decode()}")
|
||||||
|
print(f"SIGN: {b16encode(tag.signature).decode()}")
|
||||||
|
print(f"PASS: {b16encode(password[:4]).decode()}")
|
||||||
|
print(f"PWCK: {b16encode(password[4:]).decode()}")
|
||||||
|
print(" CNT:", int.from_bytes(count, byteorder='little'))
|
||||||
|
print(Style.RESET_ALL)
|
||||||
|
|
||||||
|
tag.ndef.records = [ndef.UriRecord(url), ]
|
||||||
|
try:
|
||||||
|
assert tag.ndef.has_changed is False
|
||||||
|
except AssertionError:
|
||||||
|
log.error("NDEF data was written incorrectly!")
|
||||||
|
while clf.sense(RemoteTarget('106A')): pass
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
log.info("Writing URL")
|
||||||
|
|
||||||
|
if not tag.has_password:
|
||||||
|
log.info("Setting password")
|
||||||
|
tag.protect(password=password, read_protect=False, protect_from=0)
|
||||||
|
|
||||||
|
print(Back.GREEN + Fore.BLACK + f"\n✔ Badge enrolled with success in {time()-start:.2f}s!" + Style.RESET_ALL)
|
||||||
|
uid += 1
|
||||||
|
|
||||||
|
while clf.sense(RemoteTarget('106A')): pass
|
||||||
|
|
||||||
|
os.system('clear')
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
clf.close()
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
from time import time
|
||||||
|
from base64 import b16encode
|
||||||
|
import os.path
|
||||||
|
import libnacl.utils, libnacl.sign
|
||||||
|
from transaction_const import *
|
||||||
|
from io import BytesIO
|
||||||
|
from hashlib import sha3_256
|
||||||
|
import logging
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
logging.basicConfig(level='DEBUG')
|
||||||
|
log = logging.getLogger('tx')
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
[ CA$H ]
|
||||||
|
[signature ] * 16
|
||||||
|
[load][used]
|
||||||
|
[keyi][txid]
|
||||||
|
[timestamp ]
|
||||||
|
[ uuid ]
|
||||||
|
[ F0X0 ]
|
||||||
|
|
||||||
|
|
||||||
|
21*4 = 84bytes
|
||||||
|
76 bytes
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
FIELDS = {
|
||||||
|
'signature': (0, 63),
|
||||||
|
'enc_key': 64,
|
||||||
|
'key_id': 65,
|
||||||
|
'load_amt': (66, 67),
|
||||||
|
'used_amt': (68, 69),
|
||||||
|
'tx_id': (70, 71),
|
||||||
|
'ts': (72, 75),
|
||||||
|
'uuid': (76, 79)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Transaction:
|
||||||
|
def __init__(self, tag_id, nfc_bytes=None):
|
||||||
|
|
||||||
|
# These are needed for proper randomness
|
||||||
|
self.tag_id = tag_id
|
||||||
|
|
||||||
|
self.data = bytearray(max([(x[1] if isinstance(x, tuple) else x) for x in FIELDS.values()])+1)
|
||||||
|
|
||||||
|
self.set_field('key_id', KEY_ID)
|
||||||
|
self.set_field('ts', time())
|
||||||
|
self.set_field('uuid', libnacl.randombytes(4))
|
||||||
|
|
||||||
|
if nfc_bytes:
|
||||||
|
self.load(nfc_bytes)
|
||||||
|
|
||||||
|
def set_field(self, fid, val):
|
||||||
|
|
||||||
|
if isinstance(FIELDS[fid], int):
|
||||||
|
flen = 1
|
||||||
|
sbyte = FIELDS[fid]
|
||||||
|
else:
|
||||||
|
flen = 1 + FIELDS[fid][1] - FIELDS[fid][0]
|
||||||
|
sbyte = FIELDS[fid][0]
|
||||||
|
|
||||||
|
if isinstance(val, float):
|
||||||
|
val = int(val)
|
||||||
|
|
||||||
|
if isinstance(val, int):
|
||||||
|
val = val.to_bytes(flen)
|
||||||
|
|
||||||
|
assert len(val) <= flen
|
||||||
|
|
||||||
|
if flen > 1:
|
||||||
|
log.info(f"Writing {fid} at bytes {sbyte}:{sbyte+flen-1}: 0x{b16encode(val).decode()}")
|
||||||
|
self.data[sbyte:sbyte+flen] = val
|
||||||
|
else:
|
||||||
|
log.info(f"Writing {fid} at byte {sbyte}: 0x{b16encode(val).decode()}")
|
||||||
|
self.data[sbyte] = val[0]
|
||||||
|
|
||||||
|
def get_field(self, fid):
|
||||||
|
if isinstance(FIELDS[fid], int):
|
||||||
|
flen = 1
|
||||||
|
sbyte = FIELDS[fid]
|
||||||
|
else:
|
||||||
|
flen = 1 + FIELDS[fid][1] - FIELDS[fid][0]
|
||||||
|
sbyte = FIELDS[fid][0]
|
||||||
|
|
||||||
|
if flen > 1:
|
||||||
|
val = bytes(self.data[sbyte:sbyte+flen])
|
||||||
|
else:
|
||||||
|
val = self.data[sbyte]
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
def load(self, data):
|
||||||
|
if data.startswith(HEADER):
|
||||||
|
data = data[len(HEADER):]
|
||||||
|
|
||||||
|
if data.find(FOOTER):
|
||||||
|
data = data[:-data.find(FOOTER)]
|
||||||
|
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def sign(self):
|
||||||
|
self.set_field('key_id', KEY_ID)
|
||||||
|
signer = libnacl.sign.Signer(KEY_SEED)
|
||||||
|
signature = signer.signature(bytes(self.data[FIELDS['signature'][1]+1:]+self.tag_id))
|
||||||
|
self.set_field('signature', signature)
|
||||||
|
|
||||||
|
log.info("Signed message with success!")
|
||||||
|
|
||||||
|
self.verify()
|
||||||
|
|
||||||
|
def get_bytes(self):
|
||||||
|
return HEADER + self.data + FOOTER
|
||||||
|
|
||||||
|
# TODO: implement these
|
||||||
|
def topup(self, amt):
|
||||||
|
self.load_amount += amt
|
||||||
|
self.tx_bytes
|
||||||
|
|
||||||
|
def spend(self, amt):
|
||||||
|
self.used_amount += amt
|
||||||
|
|
||||||
|
def verify(self):
|
||||||
|
verifier = libnacl.sign.Verifier(KEYS[self.get_field('key_id')])
|
||||||
|
verifier.verify(bytes(self.data + self.tag_id))
|
||||||
|
log.info("Signature has passed!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Testing transaction.")
|
||||||
|
tx = Transaction(tag_id=bytes(8))
|
||||||
|
tx.sign()
|
||||||
|
|
||||||
|
#for fname in FIELDS:
|
||||||
|
# print(' ', fname, tx.get_field(fname), 'len')
|
||||||
|
|
||||||
|
#res = tx.get_bytes()
|
||||||
|
#tx.verify()
|
||||||
|
|
||||||
|
#tx = Transaction(tag_id=bytes(8), nfc_bytes=res)
|
||||||
|
#res2 = tx.get_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue