Files
rpi-eeprom/rpi-eeprom-digest
Rasmus Villemoes 3a16bd016f rpi-eeprom-digest: support specifying keys via PKCS#11 URI
In production setups, it is quite normal that the private key does not
exist as a file in the file system, but is kept inside some HSM,
remote signing service or similar, and only accessed via some pkcs#11
interface; moreover, by design, the private key _cannot_ be extracted
from the HSM or signing service.

In such a case, the user will have set OPENSSL_CONF to some
configuration file setting up the appropriate engine, and the "key" is
simply the pkcs#11 URI, e.g. "pkcs11:model=foo;object=bar".

In order to support this use case, automatically infer the appropriate
options to pass to openssl-dgst if "${KEY}" begins with
"pkcs11:". Doing this at the top level avoids duplicating the logic in
both writeSig and verifySig. While here, this also adds a sanity check
that -v can only be used while also providing a (public) key to check
against.

This drops the -keyform argument in the non-pkcs#11 case, as openssl
automatically infers the type, and this then in fact allows one to use
a private key in e.g. DER format.

Signed-off-by: Rasmus Villemoes <ravi@prevas.dk>
2025-03-06 16:04:38 +00:00

167 lines
4.6 KiB
Bash
Executable File

#!/bin/sh
# Helper script to generate .sig files for use with the Raspberry Pi bootloader.
# This has been implemented in a separate script in order to have avoid having
# a hard dependency on OpenSSL.
set -e
OPENSSL=${OPENSSL:-openssl}
die() {
echo "$@" >&2
exit 1
}
TMP_DIR=""
cleanup() {
if [ -d "${TMP_DIR}" ]; then
rm -rf "${TMP_DIR}"
fi
}
checkDependencies() {
if ! command -v sha256sum > /dev/null; then
die "sha256sum not found. Try installing the coreutilities package."
fi
if [ -n "${KEY}" ] || [ "${VERIFY}" = 1 ]; then
if ! command -v openssl > /dev/null; then
die "openssl not found. Try installing the openssl package."
fi
if ! command -v xxd > /dev/null; then
die "xxd not found. Try installing the xxd package."
fi
fi
}
usage() {
cat <<EOF
rpi-eeprom-digest [-k RSA_KEY] -i IMAGE -o OUTPUT
Tool to generate .sig files containing the SHA256 digest and optional
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:
-i The source image e.g. boot.img
-o The name of the digest/signature file.
-k Optional RSA private key.
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
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
rpi-eeprom-digest -i pieeprom.bin -o pieeprom.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
# Similarly, but specifying the key with a PKCS#11 URI
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.
# N.B The key file must be the PUBLIC key in PEM format.
rpi-eeprom-digest -k public.pem -i boot.bin -v boot.sig
EOF
exit 0
}
writeSig() {
TMP_DIR=$(mktemp -d)
SIG_TMP="${TMP_DIR}/tmp.sig"
sha256sum "${IMAGE}" | awk '{print $1}' > "${OUTPUT}"
# Include the update-timestamp
if [ -n "${SOURCE_DATE_EPOCH}" ] ; then
echo "ts: ${SOURCE_DATE_EPOCH}" >> "${OUTPUT}"
else
echo "ts: $(date -u +%s)" >> "${OUTPUT}"
fi
if [ -n "${KEY}" ]; then
"${OPENSSL}" dgst ${ENGINE_OPTS} -sign "${KEY}" -sha256 -out "${SIG_TMP}" "${IMAGE}"
echo "rsa2048: $(xxd -c 4096 -p < "${SIG_TMP}")" >> "${OUTPUT}"
fi
}
verifySig() {
TMP_DIR=$(mktemp -d)
sig_file="${1}"
[ -f "${sig_file}" ] || die "Signature file ${sig_file} not found"
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"
"${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
case "${option}" in
i) IMAGE="${OPTARG}"
;;
k) KEY="${OPTARG}"
;;
o) OUTPUT="${OPTARG}"
;;
v) SIGNATURE="${OPTARG}"
VERIFY=1
;;
h) usage
;;
*) echo "Unknown argument \"${option}\""
usage
;;
esac
done
trap cleanup EXIT
checkDependencies
[ -n "${IMAGE}" ] || usage
[ -f "${IMAGE}" ] || die "Source image \"${IMAGE}\" not found"
[ "${VERIFY}" != 1 ] || [ -n "${KEY}" ] || die "Option -v also requires passing public key via -k"
if [ -n "${KEY}" ] ; then
if [ -f "${KEY}" ] ; then
ENGINE_OPTS=
elif echo "${KEY}" | grep -q "^pkcs11:" ; then
ENGINE_OPTS="-engine pkcs11 -keyform engine"
else
die "RSA key \"${KEY}\" not found"
fi
fi
if [ "${VERIFY}" = 1 ]; then
verifySig "${SIGNATURE}"
else
[ -n "${OUTPUT}" ] || usage
writeSig
fi