Merge branch 'master' into debian/bookworm

This commit is contained in:
Serge Schneider
2024-04-05 14:49:16 +01:00
8 changed files with 422 additions and 64 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,23 @@
# Raspberry Pi5 bootloader EEPROM release notes # Raspberry Pi5 bootloader EEPROM release notes
2024-04-05: HAT+ fixes for max-current, custom CA cert for net install and enable over-clocking to > 3GHz (latest)
* bootloader: clock_2712: Remove restriction on arm_freq <= 3000
See: https://github.com/raspberrypi/firmware/issues/1876
* arm_dt: Update max_current to match HAT value
* arm_dt: Remove unused legacy parameters (core_freq, arm_freq, uart0_clkrate and cache_line_size)
* Add support for custom CA cert for network install
You need to specify
HTTP_HOST=myhost.com
HTTP_PATH=/path/to/files
HTTP_CACERT_HASH=<hash>
where <hash> is a sha256 hash of the der encoded ca certificate.
CA cert is added using rpi-eeprom-config.
* Optimise Vbat current draw with charging disabled
* Display OTP boot status in UART log messages.
* Preliminary support for secure-boot OTP provisioning.
* Update PCIE DET_WAKE pinmux for D0 products
2024-02-16: u-boot loading and thermal throttling fixes (latest) (default) 2024-02-16: u-boot loading and thermal throttling fixes (latest) (default)
* arm_loader: Move non-kernels back to 512KB * arm_loader: Move non-kernels back to 512KB
See: https://github.com/raspberrypi/firmware/issues/1868 See: https://github.com/raspberrypi/firmware/issues/1868

View File

@@ -19,6 +19,8 @@ VALID_IMAGE_SIZES = [512 * 1024, 2 * 1024 * 1024]
BOOTCONF_TXT = 'bootconf.txt' BOOTCONF_TXT = 'bootconf.txt'
BOOTCONF_SIG = 'bootconf.sig' BOOTCONF_SIG = 'bootconf.sig'
PUBKEY_BIN = 'pubkey.bin' PUBKEY_BIN = 'pubkey.bin'
CACERT_DER = 'cacert.der'
BOOTCODE_BIN = 'bootcode.bin'
# Each section starts with a magic number followed by a 32 bit offset to the # Each section starts with a magic number followed by a 32 bit offset to the
# next section (big-endian). # next section (big-endian).
@@ -296,14 +298,22 @@ class BootloaderImage(object):
length = -1 length = -1
is_last = False is_last = False
next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page if filename == BOOTCODE_BIN:
for i in range(0, len(self._sections)): next_offset = 0
dst_filename = filename
i = 0
s = self._sections[i] s = self._sections[i]
if s.magic == FILE_MAGIC and s.filename == filename: offset = s.offset
is_last = (i == len(self._sections) - 1) length = s.length
offset = s.offset else:
length = s.length next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page
break for i in range(0, len(self._sections)):
s = self._sections[i]
if s.magic == FILE_MAGIC and s.filename == filename:
is_last = (i == len(self._sections) - 1)
offset = s.offset
length = s.length
break
# Find the start of the next non padding section # Find the start of the next non padding section
i += 1 i += 1
@@ -317,30 +327,43 @@ class BootloaderImage(object):
debug('%s offset %d length %d is-last %d next %d' % (filename, ret[0], ret[1], ret[2], ret[3])) debug('%s offset %d length %d is-last %d next %d' % (filename, ret[0], ret[1], ret[2], ret[3]))
return ret return ret
def update(self, src_bytes, dst_filename): def update(self, src_bytes, dst_filename, bootcode = False):
""" """
Replaces a modifiable file with specified byte array. Replaces a modifiable file with specified byte array.
""" """
hdr_offset, length, is_last, next_offset = self.find_file(dst_filename) if bootcode:
update_len = len(src_bytes) + FILE_HDR_LEN hdr_offset, length, is_last, next_offset = self.find_file(dst_filename)
struct.pack_into('>L', self._bytes, hdr_offset + 4, len(src_bytes))
struct.pack_into(("%ds" % len(src_bytes)), self._bytes, hdr_offset + 8, src_bytes)
pad_start = hdr_offset + len(src_bytes) + 8
is_last = False
debug("bootcode padded to %d" % next_offset);
if next_offset < 128 * 1024:
raise Exception("update-bootcode: Can't update image - 128K must be reserved for bootcode")
if next_offset < 0:
raise Exception("update-bootcode: Failed to find next section")
if hdr_offset + update_len > self._image_size - ERASE_ALIGN_SIZE: else:
raise Exception('No space available - image past EOF.') hdr_offset, length, is_last, next_offset = self.find_file(dst_filename)
update_len = len(src_bytes) + FILE_HDR_LEN
if hdr_offset < 0: if hdr_offset + update_len > self._image_size - ERASE_ALIGN_SIZE:
raise Exception('Update target %s not found' % dst_filename) raise Exception('No space available - image past EOF.')
if hdr_offset + update_len > next_offset: if hdr_offset < 0:
raise Exception('Update %d bytes is larger than section size %d' % (update_len, next_offset - hdr_offset)) raise Exception('Update target %s not found' % dst_filename)
new_len = len(src_bytes) + FILENAME_LEN + 4 if hdr_offset + update_len > next_offset:
struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) raise Exception('Update %d bytes is larger than section size %d' % (update_len, next_offset - hdr_offset))
struct.pack_into(("%ds" % len(src_bytes)), self._bytes,
hdr_offset + 4 + FILE_HDR_LEN, src_bytes)
# If the new file is smaller than the old file then set any old new_len = len(src_bytes) + FILENAME_LEN + 4
# data which is now unused to all ones (erase value) struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len)
pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes) struct.pack_into(("%ds" % len(src_bytes)), self._bytes,
hdr_offset + 4 + FILE_HDR_LEN, src_bytes)
# If the new file is smaller than the old file then set any old
# data which is now unused to all ones (erase value)
pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes)
# Add padding up to 8-byte boundary # Add padding up to 8-byte boundary
while pad_start % 8 != 0: while pad_start % 8 != 0:
@@ -379,10 +402,11 @@ class BootloaderImage(object):
Replaces the contents of dst_filename in the EEPROM with the contents of src_file. Replaces the contents of dst_filename in the EEPROM with the contents of src_file.
""" """
src_bytes = open(src_filename, 'rb').read() src_bytes = open(src_filename, 'rb').read()
if len(src_bytes) > MAX_FILE_SIZE: bootcode = dst_filename == BOOTCODE_BIN
if not bootcode and len(src_bytes) > MAX_FILE_SIZE:
raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes." raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes."
% (src_filename, len(src_bytes), MAX_FILE_SIZE)) % (src_filename, len(src_bytes), MAX_FILE_SIZE))
self.update(src_bytes, dst_filename) self.update(src_bytes, dst_filename, bootcode)
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
""" """
@@ -408,14 +432,22 @@ class BootloaderImage(object):
def get_file(self, filename): def get_file(self, filename):
hdr_offset, length, is_last, next_offset = self.find_file(filename) hdr_offset, length, is_last, next_offset = self.find_file(filename)
offset = hdr_offset + 4 + FILE_HDR_LEN if filename == BOOTCODE_BIN:
file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] offset = hdr_offset + 8
file_bytes = self._bytes[offset:offset+length]
else:
offset = hdr_offset + 4 + FILE_HDR_LEN
file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4]
return file_bytes return file_bytes
def extract_files(self): def extract_files(self):
for i in range(0, len(self._sections)): for i in range(0, len(self._sections)):
s = self._sections[i] s = self._sections[i]
if s.magic == FILE_MAGIC: if s.magic == MAGIC and s.offset == 0:
file_bytes = self.get_file(BOOTCODE_BIN)
open(BOOTCODE_BIN, 'wb').write(file_bytes)
elif s.magic == FILE_MAGIC:
file_bytes = self.get_file(s.filename) file_bytes = self.get_file(s.filename)
open(s.filename, 'wb').write(file_bytes) open(s.filename, 'wb').write(file_bytes)
@@ -514,7 +546,9 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
parser.add_argument('-d', '--digest', help='Signed boot only. The name of the .sig file generated by rpi-eeprom-dgst for config.txt ', required=False) parser.add_argument('-d', '--digest', help='Signed boot only. The name of the .sig file generated by rpi-eeprom-dgst for config.txt ', required=False)
parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False) parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False)
parser.add_argument('-x', '--extract', action='store_true', default=False, help='Extract the modifiable files (boot.conf, pubkey, signature)', required=False) parser.add_argument('-x', '--extract', action='store_true', default=False, help='Extract the modifiable files (boot.conf, pubkey, signature)', required=False)
parser.add_argument('-b', '--bootcode', help='Signed boot 2712 only. The name of the customer signed bootcode.bin file to store in the EEPROM', required=False)
parser.add_argument('-t', '--timestamp', help='Set the timestamp in the EEPROM image file', required=False) parser.add_argument('-t', '--timestamp', help='Set the timestamp in the EEPROM image file', required=False)
parser.add_argument('--cacertder', help='The name of a CA Certificate DER encoded file to store in the EEPROM', required=False)
parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input')
args = parser.parse_args() args = parser.parse_args()
@@ -537,7 +571,10 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
image = BootloaderImage(args.eeprom, args.out) image = BootloaderImage(args.eeprom, args.out)
if args.timestamp is not None: if args.timestamp is not None:
image.set_timestamp(args.timestamp) image.set_timestamp(args.timestamp)
if args.config is not None: if args.bootcode is not None:
image.update_file(args.bootcode, BOOTCODE_BIN)
image.write()
elif args.config is not None:
if not os.path.exists(args.config): if not os.path.exists(args.config):
exit_error("config file '%s' not found" % args.config) exit_error("config file '%s' not found" % args.config)
image.update_file(args.config, BOOTCONF_TXT) image.update_file(args.config, BOOTCONF_TXT)
@@ -545,6 +582,8 @@ See 'rpi-eeprom-update -h' for more information about the available EEPROM image
image.update_file(args.digest, BOOTCONF_SIG) image.update_file(args.digest, BOOTCONF_SIG)
if args.pubkey is not None: if args.pubkey is not None:
image.update_key(args.pubkey, PUBKEY_BIN) image.update_key(args.pubkey, PUBKEY_BIN)
if args.cacertder is not None:
image.update_file(args.cacertder, CACERT_DER)
image.write() image.write()
elif args.config is None and args.timestamp is not None: elif args.config is None and args.timestamp is not None:
image.write() image.write()

View File

@@ -41,11 +41,20 @@ usage() {
cat <<EOF cat <<EOF
rpi-eeprom-digest [-k RSA_KEY] -i IMAGE -o OUTPUT rpi-eeprom-digest [-k RSA_KEY] -i IMAGE -o OUTPUT
Creates a .sig file containing the sha256 digest of the IMAGE and an optional Tool to generate .sig files containing the SHA256 digest and optional
RSA signature of that hash. RSA signature. Typically this tool is used by rpi-eeprom-update to
generate a hash to guard against file-system corruption for EEPROM updates
OR for signing OS images (boot.img) for secure-boot.
This tool CANNOT be used directly to sign an bootloader EEPROM image
for secure-boot because the signed data is bootloader configuration file
rather than the entire flash image.
To create signed bootloader images please see
https://github.com/raspberrypi/usbboot/tree/master/secure-boot-recovery/README.md
Options: Options:
-i The source image. -i The source image e.g. boot.img
-o The name of the digest/signature file. -o The name of the digest/signature file.
-k Optional RSA private key. -k Optional RSA private key.
@@ -58,16 +67,20 @@ The bootloader only verifies RSA signatures in signed boot mode
Examples: Examples:
# Generate RSA signature for the EEPROM config file.
rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
# Generate the normal sha256 hash to guard against file-system corruption # Generate the normal sha256 hash to guard against file-system corruption
rpi-eeprom-digest -i pieeprom.bin -o pieeprom.sig rpi-eeprom-digest -i pieeprom.bin -o pieeprom.sig
rpi-eeprom-digest -i vl805.bin -o vl805.sig rpi-eeprom-digest -i vl805.bin -o vl805.sig
# Generate a signed OS ramdisk image for secure-boot
rpi-eeprom-digest -k private.pem -i boot.img -o boot.sig
# Generate RSA signature for the EEPROM config file
# As used by update-pieeprom.sh in usbboot/secure-boot-recovery
rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
# To verify the signature of an existing .sig file using the public key. # To verify the signature of an existing .sig file using the public key.
# N.B The key file must be the PUBLIC key in PEM format. # N.B The key file must be the PUBLIC key in PEM format.
rpi-eeprom-digest -k public.pem -i pieeprom.bin -v pieeprom.sig rpi-eeprom-digest -k public.pem -i boot.bin -v boot.sig
EOF EOF
exit 0 exit 0

View File

@@ -110,6 +110,10 @@ die() {
exit ${EXIT_FAILED} exit ${EXIT_FAILED}
} }
warn() {
echo "$@" >&2
}
getBootloaderConfig() { getBootloaderConfig() {
# Prefer extracting bootloader's config from DT. # Prefer extracting bootloader's config from DT.
# #
@@ -192,9 +196,9 @@ applyRecoveryUpdate()
getBootloaderCurrentVersion getBootloaderCurrentVersion
BOOTLOADER_UPDATE_VERSION=$(strings "${BOOTLOADER_UPDATE_IMAGE}" | grep BUILD_TIMESTAMP | sed 's/.*=//g') BOOTLOADER_UPDATE_VERSION=$(strings "${BOOTLOADER_UPDATE_IMAGE}" | grep BUILD_TIMESTAMP | sed 's/.*=//g')
if [ "${BOOTLOADER_CURRENT_VERSION}" -gt "${BOOTLOADER_UPDATE_VERSION}" ]; then if [ "${BOOTLOADER_CURRENT_VERSION}" -gt "${BOOTLOADER_UPDATE_VERSION}" ]; then
echo " WARNING: Installing an older bootloader version." warn " WARNING: Installing an older bootloader version."
echo " Update the rpi-eeprom package to fetch the latest bootloader images." warn " Update the rpi-eeprom package to fetch the latest bootloader images."
echo warn
fi fi
echo " CURRENT: $(date -u "-d@${BOOTLOADER_CURRENT_VERSION}") (${BOOTLOADER_CURRENT_VERSION})" echo " CURRENT: $(date -u "-d@${BOOTLOADER_CURRENT_VERSION}") (${BOOTLOADER_CURRENT_VERSION})"
echo " UPDATE: $(date -u "-d@${BOOTLOADER_UPDATE_VERSION}") (${BOOTLOADER_UPDATE_VERSION})" echo " UPDATE: $(date -u "-d@${BOOTLOADER_UPDATE_VERSION}") (${BOOTLOADER_UPDATE_VERSION})"
@@ -254,9 +258,10 @@ applyRecoveryUpdate()
# of power loss. # of power loss.
if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then
echo echo
echo "UPDATING bootloader." echo "UPDATING bootloader. This could take up to a minute. Please wait"
echo
echo "*** Do not disconnect the power until the update is complete ***"
echo echo
echo "*** WARNING: Do not disconnect the power until the update is complete ***"
echo "If a problem occurs then the Raspberry Pi Imager may be used to create" echo "If a problem occurs then the Raspberry Pi Imager may be used to create"
echo "a bootloader rescue SD card image which restores the default bootloader image." echo "a bootloader rescue SD card image which restores the default bootloader image."
echo echo
@@ -310,11 +315,11 @@ applyUpdate() {
if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then if [ "${RPI_EEPROM_USE_FLASHROM}" = 1 ]; then
flashrom_probe_ok=0 flashrom_probe_ok=0
if ! [ -e "${SPIDEV}" ]; then if ! [ -e "${SPIDEV}" ]; then
echo "WARNING: SPI device ${SPIDEV} not found. Setting RPI_EEPROM_USE_FLASHROM to 0" warn "WARNING: SPI device ${SPIDEV} not found. Setting RPI_EEPROM_USE_FLASHROM to 0"
fi fi
if ! flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 > /dev/null 2>&1; then if ! flashrom -p linux_spi:dev=${SPIDEV},spispeed=16000 > /dev/null 2>&1; then
echo "WARNING: Flashrom probe of ${SPIDEV} failed" warn "WARNING: Flashrom probe of ${SPIDEV} failed"
else else
flashrom_probe_ok=1 flashrom_probe_ok=1
fi fi
@@ -405,10 +410,12 @@ checkDependencies() {
# Default to off - in the future Raspberry Pi 5 may default to using flashrom if # Default to off - in the future Raspberry Pi 5 may default to using flashrom if
# flashrom is available. # flashrom is available.
[ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0 if [ "${AUTO_UPDATE_BOOTLOADER}" = 1 ] || [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then
if [ "${RPI_EEPROM_USE_FLASHROM}" -eq 1 ] && ! command -v flashrom > /dev/null; then [ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0
echo "WARNING: flashrom not found. Setting RPI_EEPROM_USE_FLASHROM to 0" if [ "${RPI_EEPROM_USE_FLASHROM}" -eq 1 ] && ! command -v flashrom > /dev/null; then
RPI_EEPROM_USE_FLASHROM=0 warn "WARNING: flashrom not found. Setting RPI_EEPROM_USE_FLASHROM to 0"
RPI_EEPROM_USE_FLASHROM=0
fi
fi fi
FIRMWARE_IMAGE_DIR="${FIRMWARE_ROOT}-${BCM_CHIP}/${FIRMWARE_RELEASE_STATUS}" FIRMWARE_IMAGE_DIR="${FIRMWARE_ROOT}-${BCM_CHIP}/${FIRMWARE_RELEASE_STATUS}"
@@ -710,11 +717,11 @@ findBootFS()
if [ "${BCM_CHIP}" = 2712 ]; then if [ "${BCM_CHIP}" = 2712 ]; then
if ! [ -e "${BOOTFS}/config.txt" ]; then if ! [ -e "${BOOTFS}/config.txt" ]; then
echo "WARNING: BOOTFS: \"${BOOTFS}/config.txt\" not found. Please check boot directory" warn "WARNING: BOOTFS: \"${BOOTFS}/config.txt\" not found. Please check boot directory"
fi fi
else else
if [ "$(find "${BOOTFS}/" -name "*.elf" | wc -l)" = 0 ]; then if [ "$(find "${BOOTFS}/" -name "*.elf" | wc -l)" = 0 ]; then
echo "WARNING: BOOTFS: \"${BOOTFS}\" contains no .elf files. Please check boot directory" warn "WARNING: BOOTFS: \"${BOOTFS}\" contains no .elf files. Please check boot directory"
fi fi
fi fi
} }

View File

@@ -6,6 +6,8 @@ FORCE=0
READ_KEY="" READ_KEY=""
WRITE_KEY="" WRITE_KEY=""
OUTPUT_BINARY=0 OUTPUT_BINARY=0
ROW_COUNT=8
ROW_OFFSET=0
die() { die() {
echo "$@" >&2 echo "$@" >&2
@@ -25,10 +27,12 @@ usage() {
N.B. OTP bits can never change from 1 to 0. N.B. OTP bits can never change from 1 to 0.
-w Writes the new key to OTP memory. -w Writes the new key to OTP memory.
-y Skip the confirmation prompt when writing to OTP. -y Skip the confirmation prompt when writing to OTP.
-l Specify key length in words. Defaults to 8 words (32 bytes). Pi 5 supports up to 16 words (64 bytes).
-o word Offset into the keystore to use, e.g. 0-7 for Pi 4, 0-15 for Pi 5. Defaults to zero.
<key> is a 64 digit hex number (256 bit) e.g. to generate a 256 random number run 'openssl rand -hex 32' <key> is usually a 64 digit hex number (256 bit) e.g. to generate a 256 random number run 'openssl rand -hex 32'
IMPORTANT: Raspberry Pi 4 and earlier revisions do not have a hardware secure key store. These OTP rows are visible IMPORTANT: Raspberry Pi 5 and earlier revisions do not have a hardware secure key store. These OTP rows are visible
to any user in the 'video' group via vcmailbox. Therefore this functionality is only suitable for key to any user in the 'video' group via vcmailbox. Therefore this functionality is only suitable for key
storage if the OS has already been restricted using the signed boot functionality. storage if the OS has already been restricted using the signed boot functionality.
@@ -46,8 +50,8 @@ check_key_set() {
} }
read_key() { read_key() {
out=READ_KEY="$(vcmailbox 0x00030081 40 40 0 8 0 0 0 0 0 0 0 0)" || die "Failed to read the current key from OTP" out=READ_KEY="$(vcmailbox 0x00030081 $((8 + $ROW_COUNT * 4)) $((8 + $ROW_COUNT * 4)) $ROW_OFFSET $ROW_COUNT 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)" || die "Failed to read the current key from OTP"
READ_KEY="$(echo "${out}" | sed 's/0x//g' | awk '{for(i=8;i<16;i++) printf $i; print ""}')" READ_KEY="$(echo "${out}" | sed 's/0x//g' | awk -v last=$((8 + $ROW_COUNT)) '{for(i=8;i<last;i++) printf $i; print ""}')"
} }
write_key() { write_key() {
@@ -55,19 +59,19 @@ write_key() {
# Normalize formatting and check the length # Normalize formatting and check the length
key="$(echo "${key}" | tr 'A-Z' 'a-z')" key="$(echo "${key}" | tr 'A-Z' 'a-z')"
key="$(echo "${key}" | sed 's/[^a-f0-9]//g')" key="$(echo "${key}" | sed 's/[^a-f0-9]//g')"
[ "$(echo -n "${key}" | wc -c)" = 64 ] || die "Invalid key parameter" [ "$(echo -n "${key}" | wc -c)" = $(($ROW_COUNT * 4 * 2)) ] || die "Invalid key parameter"
count=0 count=0
key_params="" key_params=""
while [ ${count} -lt 8 ]; do while [ ${count} -lt $ROW_COUNT ]; do
start=$(((count * 8) + 1)) start=$(((count * 8) + 1))
end=$((start + 7)) end=$((start + 7))
key_params="${key_params} 0x$(echo -n "${key}" | cut -c${start}-${end})" key_params="${key_params} 0x$(echo -n "${key}" | cut -c${start}-${end})"
count=$((count + 1)) count=$((count + 1))
done done
if [ "${YES}" = 0 ] && [ -t 0 ]; then if [ "${YES}" = 0 ] && [ -t 0 ]; then
echo "Write ${key} to OTP?" echo "Write ${key} of $ROW_COUNT words to OTP starting at word offset $ROW_OFFSET?"
echo echo
echo "WARNING: Updates to OTP registers are permanent and cannot be undone." echo "WARNING: Updates to OTP registers are permanent and cannot be undone."
@@ -79,13 +83,13 @@ write_key() {
fi fi
fi fi
vcmailbox 0x38081 40 40 0 8 ${key_params} || die "Failed to write key" vcmailbox 0x38081 $((8 + $ROW_COUNT * 4)) $((8 + $ROW_COUNT * 4)) $ROW_OFFSET $ROW_COUNT ${key_params} || die "Failed to write key"
read_key read_key
[ "${READ_KEY}" = "${key}" ] || die "Key readback check failed. ${out}" [ "${READ_KEY}" = "${key}" ] || die "Key readback check failed. ${out}"
} }
YES=0 YES=0
while getopts bcfhw:y option; do while getopts bcfhw:yl:o: option; do
case "${option}" in case "${option}" in
b) OUTPUT_BINARY=1 b) OUTPUT_BINARY=1
;; ;;
@@ -102,13 +106,61 @@ while getopts bcfhw:y option; do
w) WRITE_KEY="${OPTARG}" w) WRITE_KEY="${OPTARG}"
;; ;;
y) YES=1 y) YES=1
;; ;;
l) ROW_COUNT="${OPTARG}"
;;
o) ROW_OFFSET="${OPTARG}"
;;
*) echo "Unknown argument \"${option}\"" *) echo "Unknown argument \"${option}\""
usage usage
;; ;;
esac esac
done done
if [ -f "/sys/firmware/devicetree/base/system/linux,revision" ]; then
BOARD_INFO="$(od -v -An -t x1 /sys/firmware/devicetree/base/system/linux,revision | tr -d ' \n')"
elif grep -q Revision /proc/cpuinfo; then
BOARD_INFO="$(sed -n '/^Revision/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo)"
elif command -v vcgencmd > /dev/null; then
BOARD_INFO="$(vcgencmd otp_dump | grep '30:' | sed 's/.*://')"
else
die "No Raspberry Pi board info found"
fi
if [ $(((0x$BOARD_INFO >> 23) & 1)) = 0 ]; then
die "Chip not supported"
fi
if [ $(((0x$BOARD_INFO >> 12) & 15)) = 3 ]; then
MAX_ROW_COUNT=8
elif [ $(((0x$BOARD_INFO >> 12) & 15)) = 4 ]; then
MAX_ROW_COUNT=16
else
die "Chip not supported"
fi
if [ -z "$ROW_COUNT" ] || [ "$ROW_COUNT" -ne "$ROW_COUNT" ] 2>/dev/null; then
die "Key length not a number"
fi
if [ $ROW_COUNT -lt 1 ]; then
die "Length too small"
fi
if [ $ROW_COUNT -gt $MAX_ROW_COUNT ]; then
die "Length too big"
fi
if [ -z "$ROW_OFFSET" ] || [ "$ROW_OFFSET" -ne "$ROW_OFFSET" ] 2>/dev/null; then
die "Offset is not a number"
fi
if [ $ROW_OFFSET -lt 0 ]; then
die "Offset too small"
fi
if [ ${ROW_OFFSET} -gt $(($MAX_ROW_COUNT - $ROW_COUNT)) ]; then
die "Offset too big"
fi
if [ -z $(which vcmailbox) ]; then
die "vcmailbox command missing"
fi
if [ -z $(which xxd) ] && [ "$OUTPUT_BINARY" -eq "1" ]; then
die "xxd command missing"
fi
if [ -n "${WRITE_KEY}" ]; then if [ -n "${WRITE_KEY}" ]; then
if [ "${FORCE}" = 0 ] && check_key_set; then if [ "${FORCE}" = 0 ] && check_key_set; then
die "Current key is non-zero. Specify -f to write anyway" die "Current key is non-zero. Specify -f to write anyway"

229
tools/rpi-sign-bootcode Executable file
View File

@@ -0,0 +1,229 @@
#!/usr/bin/env python3
import argparse
import base64
import struct
import sys
# 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('<i', keynum))
def append_version(self, version):
"""
Appends a version number, 0-32 to avoid firmware rollback, a Raspberry Pi
with OTP bit n set will not execute a firmware without bit n set.
"""
if version < 0 or version > 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('<i', version))
def append_length(self):
"""
Appends the current length to the image as a 32-bit LE integer
"""
length = len(self._bytes)
debug("%08x %20s: [%6d] %d" % (self.pos(), "LEN", 4, length))
self.append(struct.pack('<i', length))
def append_public_key(self, pem_file):
"""
Converts an RSA public key into the format expected by the ROM
and appends it to the image.
If a private key is passed then only the public key part is extracted.
"""
arr = bytearray()
key = RSA.importKey(open(pem_file, 'r').read())
if key.size_in_bits() != 2048:
raise Exception("RSA key size must be 2048")
# Export N and E in little endian format
arr.extend(key.n.to_bytes(256, byteorder='little'))
arr.extend(key.e.to_bytes(8, byteorder='little'))
debug("%08x %20s: [%6d] %s" % (self.pos(), 'RSA', len(arr), pem_file))
self.append(arr)
def append_rsa_signature(self, digest_alg, private_pem):
"""
Append a RSA 2048 signature of the SHA256 of the data so far
"""
key = RSA.importKey(open(private_pem, 'r').read())
if key.size_in_bits() != 2048:
raise Exception("RSA key size must be 2048")
if digest_alg == 'sha256':
digest = SHA256.new(self._bytes)
elif digest_alg == 'sha1':
digest = SHA1.new(self._bytes)
signature = pkcs1_15.new(key).sign(digest)
self.append(signature)
debug("%08x %20s: [%6d] digest %s signature %s" % (self.pos(), 'RSA2048 - SHA256', len(signature), digest.hexdigest(), signature.hex()))
def append_digest(self, digest_alg, hmac_keyfile):
"""
Appends the hash/digest to the image
"""
if hmac_keyfile is not None:
hmac_key = str(open(hmac_keyfile, 'r').read()).strip()
expected_keylen = 40
if len(hmac_key) != expected_keylen:
raise Exception("Bad key length %d expected %d" % (len(hmac_key), expected_keylen))
if digest_alg == 'hmac-sha256':
digest = HMAC.new(base64.b16decode(hmac_key, True), self._bytes, digestmod=SHA256)
elif digest_alg == 'hmac-sha1':
digest = HMAC.new(base64.b16decode(hmac_key, True), self._bytes, digestmod=SHA1)
elif digest_alg == 'sha256':
digest = SHA256.new(self._bytes)
elif digest_alg == 'sha1':
digest = SHA1.new(self._bytes)
else:
raise Exception("Digest not supported %s" % (digest_alg))
debug("%08x %20s: [%6d] %s" % (self.pos(), digest_alg, len(digest.digest()), digest.hexdigest()))
self.append(digest.digest())
def pos(self):
return len(self._bytes)
def write(self):
if len(self._bytes) > 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, private_keynum, hmac):
"""
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)
image.append_rsa_signature('sha1', private_key)
image.append_digest('hmac-sha1', hmac)
image.write()
image.close()
def create_2712_image(output, bootcode, private_key, private_keynum, private_version):
"""
Create 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)
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:
2711 mode:
rpi-sign-bootcode --debug -c 2711 -i bootcode.bin.clr -o bootcode.bin -k 2711_rsa_priv_0.pem -n 0 -m bootcode-production.key
2712 C1 and D0 mode:
* HMAC not included on 2712
* RSA public key included - ROM just contains the hashes of the RPi public keys.
Customer counter-signed 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
"""
parser = argparse.ArgumentParser(help_text)
parser.add_argument('-o', '--output', required=False, help='Output filename . If not specified the signed images 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 sign with the customer key. If NULLL 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=True, help='Path of RSA private 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('-d', '--debug', action='store_true')
parser.add_argument('-v', '--private-version', dest='private_version', required=True, 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, args.private_key, args.private_keynum, args.hmac)
elif args.chip == 2712:
create_2712_image(args.output, args.input, args.private_key, args.private_keynum, args.private_version)
if __name__ == '__main__':
main()