#!/usr/bin/env python3 import argparse import base64 import os import struct import subprocess import sys import tempfile # python3 -m pip install pycryptodomex from Cryptodome.Hash import HMAC, SHA1, SHA256 from Cryptodome.PublicKey import RSA from Cryptodome.Signature import pkcs1_15 _CONFIG = {'DEBUG': False} MAX_BIN_SIZE = 110 * 1024 def debug(msg): """ Outputs the msg string to stdout if DEBUG is enabled (via -d) """ if _CONFIG['DEBUG']: sys.stderr.write(str(msg) + '\n') class ImageFile: """ Signed binary image """ def __init__(self, filename, max_size_kb): self._filename = filename self._bytes_written = 0 if self._filename is None: self._of = sys.stdout else: self._of = open(self._filename, "wb") self._max_size_kb = max_size_kb self._bytes = bytearray() debug("%8s %20s: [%6s] %s" % ('OFFSET', 'TYPE', 'SIZE', 'DESCRIPTION')) debug("") def append(self, data): """ Appends a blob of binary data to the image """ self._bytes.extend(data) def append_file(self, source_file): """ Appends the binary contents of source_file to the current image. If source_file is None then a base64 encoded blob is read from stdin. """ if source_file is None: b64 = "" for l in sys.stdin.readlines(): b64 += l file_bytes = base64.b64decode(b64) else: file_bytes = bytearray(open(source_file, 'rb').read()) size = len(file_bytes) debug("%08x %20s: [%6d] %s" % (self.pos(), 'FILE', size, source_file)) self.append(file_bytes) def append_keynum(self, keynum): """ Appends a given key number as a 32-bit LE integer. """ if (keynum < 0 or keynum > 4) and keynum != 16: raise Exception("Bad key number %d" % keynum) debug("%08x %20s: [%6d] %d" % (self.pos(), "KEYNUM", 4, keynum)) self.append(struct.pack(' 32: raise Exception("Bad version number %d must be between 0-32" % version) debug("%08x %20s: [%6d] %d" % (self.pos(), "VERSION", 4, version)) self.append(struct.pack(' self._max_size_kb: raise Exception("Signed binary size %d is too large. Max size %d" % (len(self._bytes), MAX_BIN_SIZE)) debug("Image size %d" % len(self._bytes)) if self._filename is None: self._of.buffer.write(base64.b64encode(self._bytes)) else: self._of.write(self._bytes) def close(self): self._of.close() def create_2711_image(output, bootcode, private_key=None, private_keynum=0, hmac=None, hsm_wrapper=None): """ Create a 2711 C0 secure-boot compatible seconds stage signed binary. """ image = ImageFile(output, MAX_BIN_SIZE) image.append_file(bootcode) image.append_length() image.append_keynum(private_keynum) if hsm_wrapper: image.append_rsa_signature_pkcs11(hsm_wrapper) else: image.append_rsa_signature('sha1', private_key) image.append_digest('hmac-sha1', hmac) image.write() image.close() def create_2712_image(output, bootcode, private_version=0, public_key=None, private_key=None, private_keynum=0, hsm_wrapper=None): """ Create a prototype 2712 signed bootloader. The HMAC is removed and the full public key is appended. """ image = ImageFile(output, MAX_BIN_SIZE) image.append_file(bootcode) image.append_length() image.append_keynum(private_keynum) image.append_version(private_version) if hsm_wrapper is not None: debug(f"Call HSM wrapper {hsm_wrapper}") image.append_rsa_signature_pkcs11(hsm_wrapper) image.append_public_key(public_key) else: image.append_rsa_signature('sha256', private_key) image.append_public_key(private_key) image.write() image.close() def extract_2711_image(input_file): """ Extract the components from a 2711 signed image and print as hex strings. Format: file | length | keynum | rsa_sig (256 bytes) | hmac-sha1 (20 bytes) """ data = bytearray(open(input_file, 'rb').read()) total_len = len(data) hmac_offset = total_len - 20 rsa_offset = hmac_offset - 256 keynum_offset = rsa_offset - 4 length_offset = keynum_offset - 4 hmac_digest = data[hmac_offset:] rsa_sig = data[rsa_offset:hmac_offset] keynum = struct.unpack('