#!/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 main(): help_text = """ Signs a second stage bootloader image. Examples: Customer counter-signed: * Exactly the same as Raspberry Pi signing but the input is the Raspberry Pi signed bootcode.bin * The key number will probably always be 16 to indicate a customer signing rpi-sign-bootcode --debug -c 2712 -i bootcode.bin.sign2 -o bootcode.bin -k customer.pem PKCS#1 v1.5 - HSM wrapper: * hsm-wrapper takes a single argument which is a temporary filename containing the data to sign * hsm-wrapper outputs the PKCS#1 v1.5 signature in hex format * hsm-wrapper must return a non-zero exit code if signing fails * hsm-wrapper requires the -a rsa2048-sha256 parameter to specify the algorithm * There is no facility to pass the private key or custom HSM arguments - the caller should generate a dedicated wrapper script * The public key in PEM format MUST be specified with the -p option rpi-sign-bootcode --debug -c 2712 -i bootcode.bin.sign2 -o bootcode.bin -p public.pem -H hsm-wrapper """ parser = argparse.ArgumentParser(help_text) parser.add_argument('-o', '--output', required=False, help='Output filename. If not specified, the signed image is written to stdout in base64 format') parser.add_argument('-c', '--chip', required=True, type=int, help='Chip number') parser.add_argument('-i', '--input', required=False, help='Path of the unsigned bootcode.bin file OR RPi signed bootcode file to be signed with the customer key. If NULL, the binary is read from stdin in base64 format') parser.add_argument('-m', '--hmac', required=False, help='Path of the HMAC key file') parser.add_argument('-k', '--private-key', dest='private_key', required=False, default=None, help='Path of RSA private key (PEM format)') parser.add_argument('-p', '--public-key', dest='public_key', required=False, default=None, help='Path of RSA public key (PEM format)') parser.add_argument('-n', '--private-keynum', dest='private_keynum', required=False, default=0, type=int, help='ROM key index for RPi signing stage') parser.add_argument('-H', '--hsm-wrapper', default=None, required=False, help='Filename of HSM wrapper script which generates a PKCSv1.1 signature as hex') parser.add_argument('-d', '--debug', action='store_true') parser.add_argument('-v', '--private-version', dest='private_version', required=False, default=0, type=int, help='Version of firmware, stops firmware rollback, only valid 0-31') args = parser.parse_args() _CONFIG['DEBUG'] = args.debug if args.chip == 2711: if args.hmac is None: raise Exception("HMAC key requried for 2711") create_2711_image(args.output, args.input, private_key=args.private_key, private_keynum=args.private_keynum, hmac=args.hmac, hsm_wrapper=args.hsm_wrapper) elif args.chip == 2712: create_2712_image(args.output, args.input, private_version=args.private_version, public_key=args.public_key, private_key=args.private_key, private_keynum=args.private_keynum, hsm_wrapper=args.hsm_wrapper) if __name__ == '__main__': main()