Skip to content

Commit

Permalink
add initial files
Browse files Browse the repository at this point in the history
  • Loading branch information
maz3max committed Feb 12, 2022
1 parent acd495f commit 951538f
Show file tree
Hide file tree
Showing 9 changed files with 7,937 additions and 0 deletions.
3 changes: 3 additions & 0 deletions defaults/main.yml
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'
80 changes: 80 additions & 0 deletions files/ble_gen_coin.py
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")
164 changes: 164 additions & 0 deletions files/ble_helpers.py
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")
Loading

0 comments on commit 951538f

Please sign in to comment.