mirror of
https://github.com/raspberrypi/rpi-eeprom.git
synced 2026-01-20 21:13:36 +08:00
Merge branch 'master' into debian/bookworm
This commit is contained in:
BIN
firmware-2712/latest/pieeprom-2024-04-05.bin
Normal file
BIN
firmware-2712/latest/pieeprom-2024-04-05.bin
Normal file
Binary file not shown.
Binary file not shown.
@@ -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
|
||||||
|
|||||||
@@ -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,6 +298,14 @@ class BootloaderImage(object):
|
|||||||
length = -1
|
length = -1
|
||||||
is_last = False
|
is_last = False
|
||||||
|
|
||||||
|
if filename == BOOTCODE_BIN:
|
||||||
|
next_offset = 0
|
||||||
|
dst_filename = filename
|
||||||
|
i = 0
|
||||||
|
s = self._sections[i]
|
||||||
|
offset = s.offset
|
||||||
|
length = s.length
|
||||||
|
else:
|
||||||
next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page
|
next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page
|
||||||
for i in range(0, len(self._sections)):
|
for i in range(0, len(self._sections)):
|
||||||
s = self._sections[i]
|
s = self._sections[i]
|
||||||
@@ -317,10 +327,23 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
if bootcode:
|
||||||
|
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")
|
||||||
|
|
||||||
|
else:
|
||||||
hdr_offset, length, is_last, next_offset = self.find_file(dst_filename)
|
hdr_offset, length, is_last, next_offset = self.find_file(dst_filename)
|
||||||
update_len = len(src_bytes) + FILE_HDR_LEN
|
update_len = len(src_bytes) + FILE_HDR_LEN
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
if filename == BOOTCODE_BIN:
|
||||||
|
offset = hdr_offset + 8
|
||||||
|
file_bytes = self._bytes[offset:offset+length]
|
||||||
|
else:
|
||||||
offset = hdr_offset + 4 + FILE_HDR_LEN
|
offset = hdr_offset + 4 + FILE_HDR_LEN
|
||||||
file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4]
|
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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,11 +410,13 @@ 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.
|
||||||
|
if [ "${AUTO_UPDATE_BOOTLOADER}" = 1 ] || [ -n "${BOOTLOADER_UPDATE_IMAGE}" ]; then
|
||||||
[ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0
|
[ -z "${RPI_EEPROM_USE_FLASHROM}" ] && RPI_EEPROM_USE_FLASHROM=0
|
||||||
if [ "${RPI_EEPROM_USE_FLASHROM}" -eq 1 ] && ! command -v flashrom > /dev/null; then
|
if [ "${RPI_EEPROM_USE_FLASHROM}" -eq 1 ] && ! command -v flashrom > /dev/null; then
|
||||||
echo "WARNING: flashrom not found. Setting RPI_EEPROM_USE_FLASHROM to 0"
|
warn "WARNING: flashrom not found. Setting RPI_EEPROM_USE_FLASHROM to 0"
|
||||||
RPI_EEPROM_USE_FLASHROM=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}"
|
||||||
if ! [ -d "${FIRMWARE_IMAGE_DIR}" ]; then
|
if ! [ -d "${FIRMWARE_IMAGE_DIR}" ]; then
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,11 +59,11 @@ 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})"
|
||||||
@@ -67,7 +71,7 @@ write_key() {
|
|||||||
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
|
||||||
;;
|
;;
|
||||||
@@ -103,12 +107,60 @@ while getopts bcfhw:y option; do
|
|||||||
;;
|
;;
|
||||||
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
229
tools/rpi-sign-bootcode
Executable 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()
|
||||||
Reference in New Issue
Block a user