mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-04 04:27:34 +00:00
add fused device support and warnings to precursorupdater
This commit is contained in:
parent
e42c4c7b33
commit
2b4e47384e
2 changed files with 174 additions and 1 deletions
|
|
@ -12,6 +12,13 @@ try:
|
|||
except ImportError:
|
||||
from precursorusb import PrecursorUsb
|
||||
|
||||
from bip_utils import (
|
||||
Bip39MnemonicValidator, Bip39MnemonicDecoder
|
||||
)
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
QR_CODE = """
|
||||
|
||||
██████████████ ██ ████ ██████ ██ ██████████████
|
||||
|
|
@ -46,6 +53,163 @@ QR_CODE = """
|
|||
|
||||
"""
|
||||
|
||||
def bitflip(data_block, bitwidth=32):
|
||||
if bitwidth == 0:
|
||||
return data_block
|
||||
bytewidth = bitwidth // 8
|
||||
bitswapped = bytearray()
|
||||
i = 0
|
||||
while i < len(data_block):
|
||||
data = int.from_bytes(data_block[i:i+bytewidth], byteorder='big', signed=False)
|
||||
b = '{:0{width}b}'.format(data, width=bitwidth)
|
||||
bitswapped.extend(int(b[::-1], 2).to_bytes(bytewidth, byteorder='big'))
|
||||
i = i + bytewidth
|
||||
return bytes(bitswapped)
|
||||
|
||||
# assumes a, b are the same length eh?
|
||||
def xor_bytes(a, b):
|
||||
i = 0
|
||||
y = bytearray()
|
||||
while i < len(a):
|
||||
y.extend((a[i] ^ b[i]).to_bytes(1, byteorder='little'))
|
||||
i = i + 1
|
||||
|
||||
return bytes(y)
|
||||
|
||||
def try_key_to_bytes(input):
|
||||
if len(input.split(' ')) == 24: # 24 words is BIP-39
|
||||
# Get if a mnemonic is valid with automatic language detection, return bool
|
||||
assert(Bip39MnemonicValidator().IsValid(input))
|
||||
# Like before with automatic language detection
|
||||
key_bytes = Bip39MnemonicDecoder().Decode(input)
|
||||
else:
|
||||
key_bytes = int(input, 16).to_bytes(32, byteorder='big')
|
||||
return key_bytes
|
||||
|
||||
# binfile should be the input SoC file, already read in as bytes()
|
||||
# returns the encrypted version of binfile
|
||||
def encrypt_to_efuse(binfile, key):
|
||||
print("Encrypting gateware to target-specific key...")
|
||||
# extract the keys
|
||||
key_bytes = bytes([0] * 32)
|
||||
new_key = try_key_to_bytes(key)
|
||||
new_hmac = get_random_bytes(32)
|
||||
new_iv = get_random_bytes(16)
|
||||
|
||||
# search for structure
|
||||
# 0x3001_6004 -> specifies the CBC key
|
||||
# 4 words of CBC IV
|
||||
# 0x3003_4001 -> ciphertext len
|
||||
# 1 word of ciphertext len
|
||||
# then ciphertext
|
||||
|
||||
position = 0
|
||||
iv_pos = 0
|
||||
while position < len(binfile):
|
||||
cwd = int.from_bytes(binfile[position:position+4], 'big')
|
||||
if cwd == 0x3001_6004:
|
||||
iv_pos = position+4
|
||||
if cwd == 0x3003_4001:
|
||||
break
|
||||
position = position + 1
|
||||
|
||||
position = position + 4
|
||||
|
||||
ciphertext_len = 4* int.from_bytes(binfile[position:position+4], 'big')
|
||||
position = position + 4
|
||||
|
||||
active_area = binfile[position : position+ciphertext_len]
|
||||
postamble = binfile[position+ciphertext_len:]
|
||||
|
||||
iv_bytes = bitflip(binfile[iv_pos : iv_pos+0x10]) # note that the IV is embedded in the file
|
||||
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
plain_bitstream = cipher.decrypt(bitflip(active_area))
|
||||
|
||||
# now construct the output file and its hashes
|
||||
plaintext = bytearray()
|
||||
f = bytearray()
|
||||
|
||||
# fixed header that sets 66MHz config speed, x1, 1.8V, eFuse target
|
||||
device_header = [
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xaa, 0x99, 0x55, 0x66, 0x20, 0x00, 0x00, 0x00, 0x30, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x0b,
|
||||
0x30, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x12, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0xc0, 0x01,
|
||||
0x80, 0x00, 0x00, 0x40, 0x30, 0x00, 0xa0, 0x01, 0x80, 0x00, 0x00, 0x40, 0x30, 0x01, 0xc0, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x01, 0x60, 0x04,
|
||||
]
|
||||
|
||||
for item in device_header: # add the cleartext header
|
||||
f.extend(bytes([item]))
|
||||
|
||||
f.extend(bitflip(new_iv)) # insert the IV
|
||||
|
||||
ciphertext_header = [
|
||||
0x30, 0x03, 0x40, 0x01, 0x00, 0x08, 0x5b, 0x98,
|
||||
]
|
||||
for item in ciphertext_header: # add the cleartext length-of-ciphertext field before the ciphertext
|
||||
f.extend(bytes([item]))
|
||||
|
||||
# generate the header and footer hash keys.
|
||||
header = int(0x6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C6C).to_bytes(32, byteorder='big')
|
||||
keyed_header = xor_bytes(header, new_hmac)
|
||||
footer = int(0x3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A3A).to_bytes(32, byteorder='big')
|
||||
keyed_footer = xor_bytes(footer, new_hmac)
|
||||
|
||||
# add the header
|
||||
plaintext.extend(keyed_header)
|
||||
plaintext.extend(header)
|
||||
|
||||
# insert the bitstream plaintext, skipping the header and the trailing HMAC.
|
||||
plaintext.extend(plain_bitstream[64:-160])
|
||||
|
||||
# compute first HMAC of stream with new HMAC key
|
||||
h1 = SHA256.new()
|
||||
k = 0
|
||||
while k < len(plaintext) - 320: # HMAC does /not/ cover the whole file, it stops 320 bytes short of the end
|
||||
h1.update(bitflip(plaintext[k:k+16], 32))
|
||||
k = k + 16
|
||||
h1_digest = h1.digest()
|
||||
|
||||
# add the footer
|
||||
plaintext.extend(keyed_footer)
|
||||
plaintext.extend(footer)
|
||||
plaintext.extend(bytes(32)) # empty spot where hash #1 would be stored
|
||||
hash_pad = [ # sha-256 padding for the zero'd hash #1, which is in the bitstream and seems necessary for verification
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
|
||||
]
|
||||
plaintext.extend(hash_pad)
|
||||
|
||||
# compute the hash of the hash, presumably to prevent length extension attacks?
|
||||
h2 = SHA256.new()
|
||||
h2.update(bitflip(keyed_footer))
|
||||
h2.update(bitflip(footer))
|
||||
h2.update(h1_digest)
|
||||
h2_digest = h2.digest()
|
||||
|
||||
# commit the final HMAC to the bitstream plaintext
|
||||
plaintext.extend(bitflip(h2_digest))
|
||||
|
||||
# encrypt the bitstream
|
||||
newcipher = AES.new(new_key, AES.MODE_CBC, new_iv)
|
||||
|
||||
# finally generate the ciphertext block, which encapsulates the HMACs
|
||||
ciphertext = newcipher.encrypt(bytes(plaintext))
|
||||
|
||||
# add ciphertext to the bitstream
|
||||
f.extend(bitflip(ciphertext))
|
||||
|
||||
# add the cleartext postamble to the bitstream. These are a series of NOP commands + all of the csr.csv data & signatures
|
||||
f.extend(postamble)
|
||||
print("Encryption success! {} bytes generated.".format(len(f)))
|
||||
assert len(f) == 2621440, "Encryption length is incorrect; aborting!"
|
||||
|
||||
return f
|
||||
|
||||
def get_with_progress(url, name='Downloading'):
|
||||
r = requests.get(url, stream=True)
|
||||
total_length = int(r.headers.get('content-length'))
|
||||
|
|
@ -148,6 +312,9 @@ def main():
|
|||
parser.add_argument(
|
||||
"--dry-run", required=False, help="Don't actually burn anything, but print what would happen if we did", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--key", help="Backup key in hex or BIP-39 format. Used to factory-reset eFused devices. Specify BIP-39 phrase within double-quotes.", type=str
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
@ -210,6 +377,7 @@ def main():
|
|||
exit(0)
|
||||
|
||||
if args.factory_reset:
|
||||
print("\nWARNING: if a backup key is set, the correct key MUST be specified with `--key`, or else the device will be bricked.")
|
||||
if False == single_yes_or_no_question("This will permanently erase user data on the device. Proceed? "):
|
||||
print("Abort by user request.")
|
||||
exit(0)
|
||||
|
|
@ -271,6 +439,9 @@ def main():
|
|||
pc_usb.erase_region(work[2], work[3])
|
||||
break
|
||||
else:
|
||||
if args.factory_reset and args.key is not None and work[1] == "Overwriting boot gateware":
|
||||
# re-encrypt the gateware if we're doing a factory reset and a key was specified
|
||||
work[3] = encrypt_to_efuse(work[3], args.key)
|
||||
if args.dry_run:
|
||||
print("DRYRUN: Would write at 0x{:x}".format(work[2]))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ with open('README.md') as f:
|
|||
|
||||
setup(
|
||||
name="precursorupdater",
|
||||
version="0.0.9",
|
||||
version="0.1.0",
|
||||
author="bunnie",
|
||||
description="Precursor USB Updater",
|
||||
long_description=long_description,
|
||||
|
|
@ -33,6 +33,8 @@ setup(
|
|||
"requests >= 2",
|
||||
"pyusb >= 1",
|
||||
"progressbar2 >= 3",
|
||||
"bip-utils >= 2.5",
|
||||
"pycryptodome >= 3",
|
||||
],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
|
|
|
|||
Loading…
Reference in a new issue