Skip to content

Commit 914dd0f

Browse files
committed
rpi-sign-bootcode: Add optional callout to HSM wrapper script from PKCS#1 v1.5 signature
1 parent 7f66ffe commit 914dd0f

File tree

2 files changed

+97
-47
lines changed

2 files changed

+97
-47
lines changed

rpi-eeprom-digest

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
# a hard dependency on OpenSSL.
77

88
set -e
9+
set -u
910

1011
OPENSSL=${OPENSSL:-openssl}
12+
KEY=""
13+
SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-""}
14+
HSM_WRAPPER=""
1115

1216
die() {
1317
echo "$@" >&2
@@ -46,29 +50,30 @@ RSA signature. Typically this tool is used by rpi-eeprom-update to
4650
generate a hash to guard against file-system corruption for EEPROM updates
4751
OR for signing OS images (boot.img) for secure-boot.
4852
49-
This tool CANNOT be used directly to sign an bootloader EEPROM image
50-
for secure-boot because the signed data is bootloader configuration file
53+
This tool CANNOT be used directly to sign a bootloader EEPROM image
54+
for secure-boot because the signed data is the bootloader configuration file
5155
rather than the entire flash image.
52-
To create signed bootloader images please see
56+
To create signed bootloader images, please see
5357
https://github.com/raspberrypi/usbboot/tree/master/secure-boot-recovery/README.md
5458
5559
5660
Options:
57-
-i The source image e.g. boot.img
58-
-o The name of the digest/signature file.
59-
-k Optional RSA private key.
60-
61-
RSA signing
62-
If a private key in PEM format or a pkcs#11 URI is supplied then the
63-
RSA signature of the sha256 digest is included in the .sig
64-
file. Currently, the bootloader only supports sha256 digests signed
65-
with a 2048bit RSA key. The bootloader only verifies RSA signatures
61+
-i The source image, e.g., boot.img
62+
-o The name of the digest/signature file
63+
-k Optional RSA private key
64+
-H The name of the HSM wrapper script to invoke - default ""
65+
66+
RSA signing:
67+
If a private key in PEM format or a PKCS#11 URI is supplied, then the
68+
RSA signature of the SHA256 digest is included in the .sig
69+
file. Currently, the bootloader only supports SHA256 digests signed
70+
with a 2048-bit RSA key. The bootloader only verifies RSA signatures
6671
in signed boot mode and only for the EEPROM config file and the signed
6772
image.
6873
6974
Examples:
7075
71-
# Generate the normal sha256 hash to guard against file-system corruption
76+
# Generate the normal SHA256 hash to guard against file-system corruption
7277
rpi-eeprom-digest -i pieeprom.bin -o pieeprom.sig
7378
rpi-eeprom-digest -i vl805.bin -o vl805.sig
7479
@@ -77,9 +82,14 @@ rpi-eeprom-digest -k private.pem -i boot.img -o boot.sig
7782
7883
# Generate RSA signature for the EEPROM config file
7984
# As used by update-pieeprom.sh in usbboot/secure-boot-recovery
80-
rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
85+
rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
86+
87+
# Generate RSA signature for the EEPROM config file and delegate
88+
# the signing process to a HSM wrapper script instead of using the private key directly.
89+
rpi-eeprom-digest -H hsm-wrapper -i bootconf.txt -o bootconf.sig
8190
8291
# Similarly, but specifying the key with a PKCS#11 URI
92+
# (Deprecated - use HSM wrapper instead)
8393
rpi-eeprom-digest -k pkcs11:token=deadbeef;object=bl-key;type=private;pin-value=1234 -i bootconf.txt -o bootconf.sig
8494
8595
# To verify the signature of an existing .sig file using the public key.
@@ -102,7 +112,9 @@ writeSig() {
102112
echo "ts: $(date -u +%s)" >> "${OUTPUT}"
103113
fi
104114

105-
if [ -n "${KEY}" ]; then
115+
if [ -n "${HSM_WRAPPER}" ]; then
116+
echo "rsa2048: $("${HSM_WRAPPER}" -a rsa2048-sha256 "${IMAGE}")" >> "${OUTPUT}"
117+
elif [ -n "${KEY}" ]; then
106118
"${OPENSSL}" dgst ${ENGINE_OPTS} -sign "${KEY}" -sha256 -out "${SIG_TMP}" "${IMAGE}"
107119
echo "rsa2048: $(xxd -c 4096 -p < "${SIG_TMP}")" >> "${OUTPUT}"
108120
fi
@@ -115,18 +127,20 @@ verifySig() {
115127
sig_hex="$(grep rsa2048 "${sig_file}" | cut -f 2 -d ' ')"
116128
[ -n "${sig_hex}" ] || die "No RSA signature in ${sig_file}"
117129

118-
echo ${sig_hex} | xxd -c 4096 -p -r > "${TMP_DIR}/sig.bin"
130+
echo "${sig_hex}" | xxd -c 4096 -p -r > "${TMP_DIR}/sig.bin"
119131
"${OPENSSL}" dgst ${ENGINE_OPTS} -verify "${KEY}" -signature "${TMP_DIR}/sig.bin" "${IMAGE}" || die "${IMAGE} not verified"
120132
}
121133

122134
OUTPUT=""
123135
VERIFY=0
124-
while getopts i:k:ho:v: option; do
136+
while getopts i:H:k:ho:v: option; do
125137
case "${option}" in
126138
i) IMAGE="${OPTARG}"
127139
;;
128140
k) KEY="${OPTARG}"
129141
;;
142+
H) HSM_WRAPPER="${OPTARG}"
143+
;;
130144
o) OUTPUT="${OPTARG}"
131145
;;
132146
v) SIGNATURE="${OPTARG}"

tools/rpi-sign-bootcode

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import argparse
44
import base64
5+
import os
56
import struct
7+
import subprocess
68
import sys
9+
import tempfile
710

811
# python3 -m pip install pycryptodomex
912
from Cryptodome.Hash import HMAC, SHA1, SHA256
@@ -105,6 +108,30 @@ class ImageFile:
105108
debug("%08x %20s: [%6d] %s" % (self.pos(), 'RSA', len(arr), pem_file))
106109
self.append(arr)
107110

111+
h = SHA256.new()
112+
h.update(key.n.to_bytes(256, byteorder='little'))
113+
h.update(key.e.to_bytes(8, byteorder='little'))
114+
d = h.hexdigest()
115+
pub_str = ""
116+
for i in range(int(len(d)/8)):
117+
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])
118+
debug("Public key SHA256(N,e) = %s" % pub_str)
119+
120+
def append_rsa_signature_pkcs11(self, hsm_wrapper):
121+
temp = tempfile.NamedTemporaryFile(delete=False)
122+
temp.write(self._bytes)
123+
temp.close() # close and flush before spawning PKCS#11 wrapper
124+
125+
res = subprocess.run([hsm_wrapper, "-a", "rsa2048-sha256", temp.name], capture_output=True)
126+
debug(res.stderr)
127+
if res.returncode != 0:
128+
os.unlink(temp.name)
129+
raise Exception(f"HSM wrapper failed with exit code {res.returncode}: {res.stderr.decode()}")
130+
signature = res.stdout.decode()
131+
os.unlink(temp.name)
132+
self.append(bytearray.fromhex(signature))
133+
debug("PKCS11 %08x %20s: [%6d] signature %s" % (self.pos(), 'RSA2048 - SHA256', len(signature), signature))
134+
108135
def append_rsa_signature(self, digest_alg, private_pem):
109136
"""
110137
Append a RSA 2048 signature of the SHA256 of the data so far
@@ -132,19 +159,13 @@ class ImageFile:
132159
if len(hmac_key) != expected_keylen:
133160
raise Exception("Bad key length %d expected %d" % (len(hmac_key), expected_keylen))
134161

135-
if digest_alg == 'hmac-sha256':
136-
digest = HMAC.new(base64.b16decode(hmac_key, True), self._bytes, digestmod=SHA256)
137-
elif digest_alg == 'hmac-sha1':
138-
digest = HMAC.new(base64.b16decode(hmac_key, True), self._bytes, digestmod=SHA1)
139-
elif digest_alg == 'sha256':
140-
digest = SHA256.new(self._bytes)
141-
elif digest_alg == 'sha1':
142-
digest = SHA1.new(self._bytes)
162+
if digest_alg == 'hmac-sha1':
163+
h = HMAC.new(base64.b16decode(hmac_key, True), self._bytes, digestmod=SHA1)
143164
else:
144165
raise Exception("Digest not supported %s" % (digest_alg))
145166

146-
debug("%08x %20s: [%6d] %s" % (self.pos(), digest_alg, len(digest.digest()), digest.hexdigest()))
147-
self.append(digest.digest())
167+
debug("%08x %20s: [%6d] %s" % (self.pos(), digest_alg, len(h.digest()), h.hexdigest()))
168+
self.append(h.digest())
148169

149170
def pos(self):
150171
return len(self._bytes)
@@ -161,30 +182,39 @@ class ImageFile:
161182
def close(self):
162183
self._of.close()
163184

164-
def create_2711_image(output, bootcode, private_key, private_keynum, hmac):
185+
def create_2711_image(output, bootcode, private_key=None, private_keynum=0, hmac=None, hsm_wrapper=None):
165186
"""
166187
Create a 2711 C0 secure-boot compatible seconds stage signed binary.
167188
"""
168189
image = ImageFile(output, MAX_BIN_SIZE)
169190
image.append_file(bootcode)
170191
image.append_length()
171192
image.append_keynum(private_keynum)
172-
image.append_rsa_signature('sha1', private_key)
193+
if hsm_wrapper:
194+
image.append_rsa_signature_pkcs11(hsm_wrapper)
195+
else:
196+
image.append_rsa_signature('sha1', private_key)
173197
image.append_digest('hmac-sha1', hmac)
174198
image.write()
175199
image.close()
176200

177-
def create_2712_image(output, bootcode, private_key, private_keynum, private_version):
201+
def create_2712_image(output, bootcode, private_version=0, public_key=None, private_key=None, private_keynum=0, hsm_wrapper=None):
178202
"""
179-
Create 2712 signed bootloader. The HMAC is removed and the full public key is appended.
203+
Create a prototype 2712 signed bootloader. The HMAC is removed and the
204+
full public key is appended.
180205
"""
181206
image = ImageFile(output, MAX_BIN_SIZE)
182207
image.append_file(bootcode)
183208
image.append_length()
184209
image.append_keynum(private_keynum)
185210
image.append_version(private_version)
186-
image.append_rsa_signature('sha256', private_key)
187-
image.append_public_key(private_key)
211+
if hsm_wrapper is not None:
212+
debug(f"Call HSM wrapper {hsm_wrapper}")
213+
image.append_rsa_signature_pkcs11(hsm_wrapper)
214+
image.append_public_key(public_key)
215+
else:
216+
image.append_rsa_signature('sha256', private_key)
217+
image.append_public_key(private_key)
188218
image.write()
189219
image.close()
190220

@@ -193,37 +223,43 @@ def main():
193223
Signs a second stage bootloader image.
194224
195225
Examples:
196-
2711 mode:
197-
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
198-
199-
2712 C1 and D0 mode:
200-
* HMAC not included on 2712
201-
* RSA public key included - ROM just contains the hashes of the RPi public keys.
202-
203-
Customer counter-signed signed:
226+
227+
Customer counter-signed:
204228
* Exactly the same as Raspberry Pi signing but the input is the Raspberry Pi signed bootcode.bin
205229
* The key number will probably always be 16 to indicate a customer signing
206230
207231
rpi-sign-bootcode --debug -c 2712 -i bootcode.bin.sign2 -o bootcode.bin -k customer.pem
232+
233+
PKCS#1 v1.5 - HSM wrapper:
234+
* hsm-wrapper takes a single argument which is a temporary filename containing the data to sign
235+
* hsm-wrapper outputs the PKCS#1 v1.5 signature in hex format
236+
* hsm-wrapper must return a non-zero exit code if signing fails
237+
* hsm-wrapper requires the -a rsa2048-sha256 parameter to specify the algorithm
238+
* There is no facility to pass the private key or custom HSM arguments - the caller should generate a dedicated wrapper script
239+
* The public key in PEM format MUST be specified with the -p option
240+
241+
rpi-sign-bootcode --debug -c 2712 -i bootcode.bin.sign2 -o bootcode.bin -p public.pem -H hsm-wrapper
208242
"""
209243
parser = argparse.ArgumentParser(help_text)
210-
parser.add_argument('-o', '--output', required=False, help='Output filename . If not specified the signed images is written to stdout in base64 format')
244+
parser.add_argument('-o', '--output', required=False, help='Output filename. If not specified, the signed image is written to stdout in base64 format')
211245
parser.add_argument('-c', '--chip', required=True, type=int, help='Chip number')
212-
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')
246+
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')
213247
parser.add_argument('-m', '--hmac', required=False, help='Path of the HMAC key file')
214-
parser.add_argument('-k', '--private-key', dest='private_key', required=True, help='Path of RSA private key (PEM format)')
248+
parser.add_argument('-k', '--private-key', dest='private_key', required=False, default=None, help='Path of RSA private key (PEM format)')
249+
parser.add_argument('-p', '--public-key', dest='public_key', required=False, default=None, help='Path of RSA public key (PEM format)')
215250
parser.add_argument('-n', '--private-keynum', dest='private_keynum', required=False, default=0, type=int, help='ROM key index for RPi signing stage')
251+
parser.add_argument('-H', '--hsm-wrapper', default=None, required=False, help='Filename of HSM wrapper script which generates a PKCSv1.1 signature as hex')
216252
parser.add_argument('-d', '--debug', action='store_true')
217-
parser.add_argument('-v', '--private-version', dest='private_version', required=True, type=int, help='Version of firmware, stops firmware rollback, only valid 0-31')
253+
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')
218254

219255
args = parser.parse_args()
220256
_CONFIG['DEBUG'] = args.debug
221257
if args.chip == 2711:
222258
if args.hmac is None:
223259
raise Exception("HMAC key requried for 2711")
224-
create_2711_image(args.output, args.input, args.private_key, args.private_keynum, args.hmac)
260+
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)
225261
elif args.chip == 2712:
226-
create_2712_image(args.output, args.input, args.private_key, args.private_keynum, args.private_version)
262+
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)
227263

228264
if __name__ == '__main__':
229265
main()

0 commit comments

Comments
 (0)