Merge remote-tracking branch 'origin/master' into pios/bookworm

This commit is contained in:
Tim Gover
2025-05-07 17:53:59 +01:00
8 changed files with 158 additions and 46 deletions

31
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Test EEPROM Config
on:
pull_request:
branches: [ 'master' ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Create and activate virtual environment
run: |
python -m venv venv
source venv/bin/activate
- name: Install dependencies
run: |
pip install pycryptodomex
- name: Run EEPROM Config Tests
run: |
cd test
chmod +x test-rpi-eeprom-config
./test-rpi-eeprom-config

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,22 @@
# Raspberry Pi5 bootloader EEPROM release notes
## 2025-04-07: arm_dt: Revert to using the max fan speed (latest)
* arm_dt: Revert to using the max fan speed
It has been reported that the presence of a cooling fan at boot time
can lead to a maximum observed fan speed of ~300 but a current speed
of 0. The absence of a fan results in 0s for both metrics.
See: https://github.com/raspberrypi/rpi-eeprom/issues/690
## 2025-03-27: os_check: cm5: Check for CM5 specific dtbs (latest)
* os_check: cm5: Check for CM5 specific dtbs
Check for BCM2712 support in bcm2712-rpi-cm5-cm5io.dtb
or bcm2712-rpi-cm5l-cm5io.dtb on CM5 instead of bcm2712-rpi-5-b.dtb.
This avoids needing to put os_check=1 or specifying device_tree
in config.txt in minimal images for CM5.
See: https://github.com/raspberrypi/rpi-eeprom/issues/682
## 2025-03-19: Log the fan speed at boot (latest)
* Log the fan speed at boot

View File

@@ -6,8 +6,12 @@
# a hard dependency on OpenSSL.
set -e
set -u
OPENSSL=${OPENSSL:-openssl}
KEY=""
SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-""}
HSM_WRAPPER=""
die() {
echo "$@" >&2
@@ -46,29 +50,30 @@ 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
This tool CANNOT be used directly to sign a bootloader EEPROM image
for secure-boot because the signed data is the bootloader configuration file
rather than the entire flash image.
To create signed bootloader images please see
To create signed bootloader images, please see
https://github.com/raspberrypi/usbboot/tree/master/secure-boot-recovery/README.md
Options:
-i The source image e.g. boot.img
-o The name of the digest/signature file.
-k Optional RSA private key.
-i The source image, e.g., boot.img
-o The name of the digest/signature file
-k Optional RSA private key
-H The name of the HSM wrapper script to invoke - default ""
RSA signing
If a private key in PEM format or a pkcs#11 URI is supplied then the
RSA signature of the sha256 digest is included in the .sig
file. Currently, the bootloader only supports sha256 digests signed
with a 2048bit RSA key. The bootloader only verifies RSA signatures
RSA signing:
If a private key in PEM format or a PKCS#11 URI is supplied, then the
RSA signature of the SHA256 digest is included in the .sig
file. Currently, the bootloader only supports SHA256 digests signed
with a 2048-bit RSA key. The bootloader only verifies RSA signatures
in signed boot mode and only for the EEPROM config file and the signed
image.
Examples:
# 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 vl805.bin -o vl805.sig
@@ -77,9 +82,14 @@ 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
rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
# Generate RSA signature for the EEPROM config file and delegate
# the signing process to a HSM wrapper script instead of using the private key directly.
rpi-eeprom-digest -H hsm-wrapper -i bootconf.txt -o bootconf.sig
# Similarly, but specifying the key with a PKCS#11 URI
# (Deprecated - use HSM wrapper instead)
rpi-eeprom-digest -k pkcs11:token=deadbeef;object=bl-key;type=private;pin-value=1234 -i bootconf.txt -o bootconf.sig
# To verify the signature of an existing .sig file using the public key.
@@ -102,7 +112,9 @@ writeSig() {
echo "ts: $(date -u +%s)" >> "${OUTPUT}"
fi
if [ -n "${KEY}" ]; then
if [ -n "${HSM_WRAPPER}" ]; then
echo "rsa2048: $("${HSM_WRAPPER}" -a rsa2048-sha256 "${IMAGE}")" >> "${OUTPUT}"
elif [ -n "${KEY}" ]; then
"${OPENSSL}" dgst ${ENGINE_OPTS} -sign "${KEY}" -sha256 -out "${SIG_TMP}" "${IMAGE}"
echo "rsa2048: $(xxd -c 4096 -p < "${SIG_TMP}")" >> "${OUTPUT}"
fi
@@ -115,18 +127,20 @@ verifySig() {
sig_hex="$(grep rsa2048 "${sig_file}" | cut -f 2 -d ' ')"
[ -n "${sig_hex}" ] || die "No RSA signature in ${sig_file}"
echo ${sig_hex} | xxd -c 4096 -p -r > "${TMP_DIR}/sig.bin"
echo "${sig_hex}" | xxd -c 4096 -p -r > "${TMP_DIR}/sig.bin"
"${OPENSSL}" dgst ${ENGINE_OPTS} -verify "${KEY}" -signature "${TMP_DIR}/sig.bin" "${IMAGE}" || die "${IMAGE} not verified"
}
OUTPUT=""
VERIFY=0
while getopts i:k:ho:v: option; do
while getopts i:H:k:ho:v: option; do
case "${option}" in
i) IMAGE="${OPTARG}"
;;
k) KEY="${OPTARG}"
;;
H) HSM_WRAPPER="${OPTARG}"
;;
o) OUTPUT="${OPTARG}"
;;
v) SIGNATURE="${OPTARG}"

14
test/README.md Normal file
View File

@@ -0,0 +1,14 @@
# rpi-eeprom - unit tests
## test-rpi-eeprom-config
Unit test for rpi-eeprom-config which verifies:
* rpi-eeprom-config is compatible with all EEPROM binaries
* unit tests for modifying the boot.conf file
* simple code signing test
To run on Linux:
```
cd test
./test-rpi-eeprom-config
```

View File

@@ -2,8 +2,11 @@
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
@@ -105,6 +108,30 @@ class ImageFile:
debug("%08x %20s: [%6d] %s" % (self.pos(), 'RSA', len(arr), pem_file))
self.append(arr)
h = SHA256.new()
h.update(key.n.to_bytes(256, byteorder='little'))
h.update(key.e.to_bytes(8, byteorder='little'))
d = h.hexdigest()
pub_str = ""
for i in range(int(len(d)/8)):
pub_str += "0x%s%s%s%s, " % (d[i*8+6:i*8+8], d[i*8+4:i*8+6], d[i*8+2:i*8+4], d[i*8+0:i*8+2])
debug("Public key SHA256(N,e) = %s" % pub_str)
def append_rsa_signature_pkcs11(self, hsm_wrapper):
temp = tempfile.NamedTemporaryFile(delete=False)
temp.write(self._bytes)
temp.close() # close and flush before spawning PKCS#11 wrapper
res = subprocess.run([hsm_wrapper, "-a", "rsa2048-sha256", temp.name], capture_output=True)
debug(res.stderr)
if res.returncode != 0:
os.unlink(temp.name)
raise Exception(f"HSM wrapper failed with exit code {res.returncode}: {res.stderr.decode()}")
signature = res.stdout.decode()
os.unlink(temp.name)
self.append(bytearray.fromhex(signature))
debug("PKCS11 %08x %20s: [%6d] signature %s" % (self.pos(), 'RSA2048 - SHA256', len(signature), signature))
def append_rsa_signature(self, digest_alg, private_pem):
"""
Append a RSA 2048 signature of the SHA256 of the data so far
@@ -132,19 +159,13 @@ class ImageFile:
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)
if digest_alg == 'hmac-sha1':
h = HMAC.new(base64.b16decode(hmac_key, True), self._bytes, digestmod=SHA1)
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())
debug("%08x %20s: [%6d] %s" % (self.pos(), digest_alg, len(h.digest()), h.hexdigest()))
self.append(h.digest())
def pos(self):
return len(self._bytes)
@@ -161,7 +182,7 @@ class ImageFile:
def close(self):
self._of.close()
def create_2711_image(output, bootcode, private_key, private_keynum, hmac):
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.
"""
@@ -169,22 +190,31 @@ def create_2711_image(output, bootcode, private_key, private_keynum, hmac):
image.append_file(bootcode)
image.append_length()
image.append_keynum(private_keynum)
image.append_rsa_signature('sha1', private_key)
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_key, private_keynum, private_version):
def create_2712_image(output, bootcode, private_version=0, public_key=None, private_key=None, private_keynum=0, hsm_wrapper=None):
"""
Create 2712 signed bootloader. The HMAC is removed and the full public key is appended.
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)
image.append_rsa_signature('sha256', private_key)
image.append_public_key(private_key)
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()
@@ -193,37 +223,43 @@ def main():
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:
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 images is written to stdout in base64 format')
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 sign with the customer key. If NULLL the binary is read from stdin in base64 format')
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=True, help='Path of RSA private key (PEM format)')
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=True, type=int, help='Version of firmware, stops firmware rollback, only valid 0-31')
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, args.private_key, args.private_keynum, args.hmac)
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, args.private_key, args.private_keynum, args.private_version)
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()