-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
7,937 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
--- | ||
door_open_command: '/home/pi/door-open.sh' | ||
ble_keykeeper_dir: '/home/pi/ble_keykeeper' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
#!/usr/bin/python3 | ||
import binascii | ||
from intelhex import IntelHex | ||
from ble_helpers import KeykeeperDB, str_to_addr, str_to_key, addr_to_str | ||
import sys | ||
import time | ||
|
||
# CRC-8-CCITT with initial value 0xFF: checksum used in FCB | ||
def fcb_crc8(data): | ||
crc8_ccitt_small_table = bytes([0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, | ||
0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d]) | ||
val = 0xFF | ||
for b in data: | ||
val ^= b | ||
val = ((val << 4) & 0xFF) ^ crc8_ccitt_small_table[val >> 4] | ||
val = ((val << 4) & 0xFF) ^ crc8_ccitt_small_table[val >> 4] | ||
return val | ||
|
||
|
||
# generate FCB storage item from data | ||
def gen_storage_item(data): | ||
assert len(data) < 0x4000 | ||
if len(data) < 0x80: | ||
data_w_len = bytes([len(data)]) + data | ||
else: | ||
data_w_len = bytes([(len(data) & 0x7f) | 0x80, len(data) >> 7]) + data | ||
return data_w_len + bytes([fcb_crc8(data_w_len)]) | ||
|
||
|
||
# generate storage partition | ||
def periph_storage_partition(periph_addr, periph_irk, central_addr, central_irk, ltk, | ||
spacekey): | ||
magic_header = b'\xee\xee\xff\xc0\x01\xff\x00\x00' | ||
bt_id = b'bt/id=\x01' + bytes(periph_addr) | ||
bt_irk = b'bt/irk=' + bytes(periph_irk) | ||
bt_keys = b'bt/keys/' + binascii.hexlify(central_addr[::-1]) + b'1=\x10\x11"\x00' + b'\x00' * 10 + \ | ||
bytes(ltk) + bytes(central_irk) + b'\x00' * 6 | ||
space_key = b'space/key=' + bytes(spacekey) | ||
data = magic_header + \ | ||
gen_storage_item(bt_id) + \ | ||
gen_storage_item(bt_irk) + \ | ||
gen_storage_item(bt_keys) + \ | ||
gen_storage_item(space_key) | ||
return data + b'\xff' * (0x6000 - len(data)) # partition length from DTS | ||
|
||
|
||
if __name__ == '__main__': | ||
db = KeykeeperDB() | ||
|
||
if len(sys.argv) == 2: | ||
name = sys.argv[1] | ||
else: | ||
name = f"namehere_{int(time.time())}" | ||
|
||
p_addr, p_irk, ltk, spacekey = db.new_coin(name) | ||
|
||
# prepare IDs | ||
c_addr_str, c_irk_str = db.identity | ||
c_addr = str_to_addr(c_addr_str) | ||
c_irk = str_to_key(c_irk_str) | ||
|
||
print("Central: " + c_addr_str) | ||
print("Peripheral: " + addr_to_str(p_addr)) | ||
|
||
# create storage partition | ||
storage_bytes = periph_storage_partition( | ||
p_addr, p_irk, c_addr, c_irk, ltk, spacekey) | ||
|
||
addr_hex = binascii.hexlify(p_addr[::-1]).decode() | ||
|
||
# create merged hex file for easy programming | ||
storage = IntelHex() | ||
# partition address from DTS | ||
storage[0x32000:0x38000] = list(storage_bytes) | ||
# storage.tofile("storage_%s.hex" % addr_hex, format="hex") | ||
coin = IntelHex("coin.hex") | ||
coin.merge(storage, overlap="replace") | ||
coin[0x10001208:0x10001208 + 4] = [0x00] * \ | ||
4 # enable Access Port Protection | ||
coin.tofile("coin_%s.hex" % addr_hex, format="hex") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import secrets | ||
import re | ||
import logging | ||
import time | ||
import os | ||
import binascii | ||
from typing import Tuple | ||
|
||
|
||
def addr_to_str(addr: bytes) -> str: | ||
hex_arr = ["%02X" % b for b in addr[::-1]] | ||
return ":".join(hex_arr) | ||
|
||
|
||
def str_to_addr(addr: str) -> bytes: | ||
hex_arr = addr.split(":") | ||
return bytes([int(b, 16) for b in hex_arr[::-1]]) | ||
|
||
def key_to_str(key: bytes) -> str: | ||
return key.hex().upper() | ||
|
||
def str_to_key(key: str) -> bytes: | ||
return binascii.unhexlify(key) | ||
|
||
|
||
def new_addr() -> bytes: | ||
addr = bytearray(secrets.token_bytes(6)) | ||
addr[5] |= 0xc0 | ||
return bytes(addr) | ||
|
||
|
||
def new_keys() -> Tuple[bytes, bytes, bytes]: | ||
irk = secrets.token_bytes(16) | ||
ltk = secrets.token_bytes(16) | ||
spacekey = secrets.token_bytes(32) | ||
return irk, ltk, spacekey | ||
|
||
|
||
class KeykeeperDB: | ||
''' | ||
This data structure is a helper struct that manages addresses, keys and names. | ||
It is meant to be replaceable in case someone wants to keep their data in a different manner. | ||
''' | ||
|
||
def __init__(self, coins_file="coins.txt", | ||
central_file="central.txt", | ||
names_file="names.txt"): | ||
''' | ||
self.coins is: {"<addr>":("<irk>", "<ltk>", "<spacekey>"),...} | ||
self.identity is: ["<addr>", "<irk>"] | ||
self.names is: {"<addr>":"<name>",...} | ||
''' | ||
self.coins = {} | ||
self.identity = [] | ||
self.names = {} | ||
self.coins_file = coins_file | ||
self.central_file = central_file | ||
self.names_file = names_file | ||
self.load() | ||
|
||
def new_coin(self, name : str=None): | ||
# new addr has to be unique | ||
while True: | ||
addr = new_addr() | ||
addr_str = addr_to_str(addr) | ||
if addr_str != self.identity[0] and addr_str not in self.coins: | ||
break | ||
|
||
irk, ltk, spacekey = new_keys() | ||
|
||
self.coins[addr_str] = ( | ||
irk.hex().upper(), | ||
ltk.hex().upper(), | ||
spacekey.hex().upper(), | ||
) | ||
|
||
self.append_coin(addr_str) | ||
|
||
self.names[addr_str] = name | ||
self.append_name(addr_str) | ||
|
||
return addr, irk, ltk, spacekey | ||
|
||
def append_coin(self, address: str): | ||
''' | ||
This appends a new coin to the database. | ||
''' | ||
irk, ltk, spacekey = self.coins[address] | ||
with open(self.coins_file, "a") as f: | ||
f.write(f"{address} {irk} {ltk} {spacekey}\n") | ||
|
||
def append_name(self, address: str): | ||
''' | ||
This appends a name to the database. | ||
''' | ||
name = self.names[address] | ||
with open(self.names_file, "a") as f: | ||
f.write(f"{address} {name}\n") | ||
|
||
def _init_central(self): | ||
logging.warning("init database") | ||
suffix = str(int(time.time())) | ||
|
||
for f in [self.coins_file, self.central_file, self.names_file]: | ||
# back up old files | ||
if os.path.exists(f): | ||
dest = f"{f}.bak-{suffix}" | ||
os.rename(f, dest) | ||
logging.warning("move {f} -> {dest}") | ||
# create new file | ||
open(f, 'a').close() | ||
|
||
# generate central keys | ||
addr = new_addr() | ||
irk, _, _ = new_keys() | ||
self.identity = (addr_to_str(addr), irk.hex().upper()) | ||
|
||
# create central file | ||
with open(self.central_file, "w") as f: | ||
f.write(f"{self.identity[0]} {self.identity[1]}") | ||
|
||
def load(self): | ||
''' | ||
This loads the database from three text files: | ||
[central.txt] contains one line: "<central_addr> <central_irk>" | ||
[coins.txt] has a line for every coin: "<addr> <irk> <ltk> <spacekey>" | ||
[names.txt] has a line for every coin: "<addr> <name>" | ||
It is optional but helpful to specify a name for every coin. | ||
''' | ||
|
||
if not os.path.exists(self.coins_file): | ||
self._init_central() | ||
return | ||
|
||
# load central info | ||
with open(self.central_file, "r") as f: | ||
line = f.readline() | ||
m = re.match(r"(.{17})\s+(.{32})", line) | ||
if m: | ||
self.identity = m.groups() | ||
else: | ||
self._init_central() | ||
return | ||
logging.info(f"loaded [central] info") | ||
|
||
# load coins file | ||
with open(self.coins_file, "r") as f: | ||
for line in f: | ||
m = re.match(r"(.{17})\s+(.{32})\s+(.{32})\s+(.{64})", line) | ||
if m: | ||
self.coins[m.group(1)] = m.groups()[1:] | ||
logging.info(f"loaded {len(self.coins)} coins") | ||
|
||
# load names file (optional) | ||
try: | ||
with open(self.names_file, "r") as f: | ||
for line in f: | ||
m = re.match(r"(.{17})\s+(.+)", line) | ||
if m: | ||
self.names[m.group(1)] = m.group(2) | ||
except: | ||
logging.warning("could not load [names] file") |
Oops, something went wrong.