feat(espsecure): Add support for secure boot v2 using ECDSA-P384 signatures

This commit is contained in:
harshal.patil
2024-05-27 11:36:04 +05:30
parent 74ce28698e
commit f014cad6ec

View File

@@ -31,9 +31,14 @@ SIG_BLOCK_MAGIC = 0xE7
SIG_BLOCK_VERSION_RSA = 0x02 SIG_BLOCK_VERSION_RSA = 0x02
SIG_BLOCK_VERSION_ECDSA = 0x03 SIG_BLOCK_VERSION_ECDSA = 0x03
# SHA scheme used in Secure Boot V2 ECDSA signature blocks
ECDSA_SHA_256 = 0x0
ECDSA_SHA_384 = 0x1
# Curve IDs used in Secure Boot V2 ECDSA signature blocks # Curve IDs used in Secure Boot V2 ECDSA signature blocks
CURVE_ID_P192 = 1 CURVE_ID_P192 = 1
CURVE_ID_P256 = 2 CURVE_ID_P256 = 2
CURVE_ID_P384 = 3
SECTOR_SIZE = 4096 SECTOR_SIZE = 4096
SIG_BLOCK_SIZE = ( SIG_BLOCK_SIZE = (
@@ -182,23 +187,21 @@ def generate_signing_key(args):
) )
with open(args.keyfile, "wb") as f: with open(args.keyfile, "wb") as f:
f.write(private_key) f.write(private_key)
print("RSA 3072 private key in PEM format written to %s" % args.keyfile) print(f"RSA 3072 private key in PEM format written to {args.keyfile}")
elif args.scheme == "ecdsa192": elif args.scheme == "ecdsa192":
"""Generate a ECDSA 192 signing key for signing secure boot images""" """Generate a ECDSA 192 signing key for signing secure boot images"""
_generate_ecdsa_signing_key(ecdsa.NIST192p, args.keyfile) _generate_ecdsa_signing_key(ecdsa.NIST192p, args.keyfile)
print( print(f"ECDSA NIST192p private key in PEM format written to {args.keyfile}")
"ECDSA NIST192p private key in PEM format written to %s" % args.keyfile
)
elif args.scheme == "ecdsa256": elif args.scheme == "ecdsa256":
"""Generate a ECDSA 256 signing key for signing secure boot images""" """Generate a ECDSA 256 signing key for signing secure boot images"""
_generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile) _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile)
print( print(f"ECDSA NIST256p private key in PEM format written to {args.keyfile}")
"ECDSA NIST256p private key in PEM format written to %s" % args.keyfile elif args.scheme == "ecdsa384":
) """Generate a ECDSA 384 signing key for signing secure boot images"""
_generate_ecdsa_signing_key(ecdsa.NIST384p, args.keyfile)
print(f"ECDSA NIST384p private key in PEM format written to {args.keyfile}")
else: else:
raise esptool.FatalError( raise esptool.FatalError("ERROR: Unsupported signing scheme {args.scheme}")
"ERROR: Unsupported signing scheme (%s)" % args.scheme
)
def load_ecdsa_signing_key(keyfile): def load_ecdsa_signing_key(keyfile):
@@ -260,12 +263,10 @@ def _load_sbv2_signing_key(keydata):
) )
return sk return sk
if isinstance(sk, ec.EllipticCurvePrivateKey): if isinstance(sk, ec.EllipticCurvePrivateKey):
if not ( if not isinstance(sk.curve, (ec.SECP192R1, ec.SECP256R1, ec.SECP384R1)):
isinstance(sk.curve, ec.SECP192R1) or isinstance(sk.curve, ec.SECP256R1)
):
raise esptool.FatalError( raise esptool.FatalError(
"Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports " "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports "
"NIST192p, NIST256p (aka prime192v1, prime256v1)" "NIST192p, NIST256p, NIST384p (aka prime192v1 / secp192r1, prime256v1 / secp256r1, secp384r1)"
) )
return sk return sk
@@ -285,12 +286,10 @@ def _load_sbv2_pub_key(keydata):
) )
return vk return vk
if isinstance(vk, ec.EllipticCurvePublicKey): if isinstance(vk, ec.EllipticCurvePublicKey):
if not ( if not isinstance(vk.curve, (ec.SECP192R1, ec.SECP256R1, ec.SECP384R1)):
isinstance(vk.curve, ec.SECP192R1) or isinstance(vk.curve, ec.SECP256R1)
):
raise esptool.FatalError( raise esptool.FatalError(
"Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports " "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports "
"NIST192p, NIST256p (aka prime192v1, prime256v1)" "NIST192p, NIST256p, NIST384p (aka prime192v1 / secp192r1, prime256v1 / secp256r1, secp384r1)"
) )
return vk return vk
@@ -338,7 +337,7 @@ def _microecc_format(a, b, curve_len):
""" """
byte_len = int(curve_len / 8) byte_len = int(curve_len / 8)
ab = int_to_bytes(a, byte_len)[::-1] + int_to_bytes(b, byte_len)[::-1] ab = int_to_bytes(a, byte_len)[::-1] + int_to_bytes(b, byte_len)[::-1]
assert len(ab) == 48 or len(ab) == 64 assert len(ab) in [48, 64, 96]
return ab return ab
@@ -402,7 +401,7 @@ def sign_secure_boot_v1(args):
def sign_secure_boot_v2(args): def sign_secure_boot_v2(args):
""" """
Sign a firmware app image with an RSA private key using RSA-PSS, Sign a firmware app image with an RSA private key using RSA-PSS,
or ECDSA private key using P192 or P256. or ECDSA private key using P192 or P256 or P384.
Write output file with a Secure Boot V2 header appended. Write output file with a Secure Boot V2 header appended.
""" """
@@ -496,20 +495,16 @@ def sign_secure_boot_v2(args):
) )
print(f"{key_count} signing key(s) found.") print(f"{key_count} signing key(s) found.")
# Calculate digest of data file
digest = hashlib.sha256()
digest.update(contents)
digest = digest.digest()
# Generate signature block using pre-calculated signatures # Generate signature block using pre-calculated signatures
if signature: if signature:
signature_block = generate_signature_block_using_pre_calculated_signature( signature_block = generate_signature_block_using_pre_calculated_signature(
signature, pub_key, digest signature, pub_key, contents
) )
# Generate signature block by signing using private keys # Generate signature block by signing using private keys
else: else:
signature_block = generate_signature_block_using_private_key( signature_block = generate_signature_block_using_private_key(
args.keyfile, digest args.keyfile, contents
) )
if signature_block is None or len(signature_block) == 0: if signature_block is None or len(signature_block) == 0:
@@ -560,13 +555,17 @@ def generate_signature_using_hsm(config, contents):
return [temp_signature_file] return [temp_signature_file]
def generate_signature_block_using_pre_calculated_signature(signature, pub_key, digest): def generate_signature_block_using_pre_calculated_signature(
signature, pub_key, contents
):
signature_blocks = b"" signature_blocks = b""
for sig, pk in zip(signature, pub_key): for sig, pk in zip(signature, pub_key):
try: try:
public_key = _get_sbv2_pub_key(pk) public_key = _get_sbv2_pub_key(pk)
signature = sig.read() signature = sig.read()
if isinstance(public_key, rsa.RSAPublicKey): if isinstance(public_key, rsa.RSAPublicKey):
# Calculate digest of data file
digest = _sha256_digest(contents)
# RSA signature # RSA signature
rsa_primitives = _get_sbv2_rsa_primitives(public_key) rsa_primitives = _get_sbv2_rsa_primitives(public_key)
# Verify the signature # Verify the signature
@@ -586,15 +585,24 @@ def generate_signature_block_using_pre_calculated_signature(signature, pub_key,
if isinstance(numbers.curve, ec.SECP192R1): if isinstance(numbers.curve, ec.SECP192R1):
curve_len = 192 curve_len = 192
curve_id = CURVE_ID_P192 curve_id = CURVE_ID_P192
hash_type = hashes.SHA256()
digest = _sha256_digest(contents)
elif isinstance(numbers.curve, ec.SECP256R1): elif isinstance(numbers.curve, ec.SECP256R1):
curve_len = 256 curve_len = 256
curve_id = CURVE_ID_P256 curve_id = CURVE_ID_P256
hash_type = hashes.SHA256()
digest = _sha256_digest(contents)
elif isinstance(numbers.curve, ec.SECP384R1):
curve_len = 384
curve_id = CURVE_ID_P384
hash_type = hashes.SHA384()
digest = _sha384_digest(contents)
else: else:
raise esptool.FatalError("Invalid ECDSA curve instance.") raise esptool.FatalError("Invalid ECDSA curve instance.")
# Verify the signature # Verify the signature
public_key.verify( public_key.verify(
signature, digest, ec.ECDSA(utils.Prehashed(hashes.SHA256())) signature, digest, ec.ECDSA(utils.Prehashed(hash_type))
) )
pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len) pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len)
@@ -619,13 +627,14 @@ def generate_signature_block_using_pre_calculated_signature(signature, pub_key,
return signature_blocks return signature_blocks
def generate_signature_block_using_private_key(keyfiles, digest): def generate_signature_block_using_private_key(keyfiles, contents):
signature_blocks = b"" signature_blocks = b""
for keyfile in keyfiles: for keyfile in keyfiles:
private_key = _load_sbv2_signing_key(keyfile.read()) private_key = _load_sbv2_signing_key(keyfile.read())
# Sign # Sign
if isinstance(private_key, rsa.RSAPrivateKey): if isinstance(private_key, rsa.RSAPrivateKey):
digest = _sha256_digest(contents)
# RSA signature # RSA signature
signature = private_key.sign( signature = private_key.sign(
digest, digest,
@@ -640,21 +649,28 @@ def generate_signature_block_using_private_key(keyfiles, digest):
digest, rsa_primitives, signature digest, rsa_primitives, signature
) )
else: else:
# ECDSA signature
signature = private_key.sign(
digest, ec.ECDSA(utils.Prehashed(hashes.SHA256()))
)
numbers = private_key.public_key().public_numbers() numbers = private_key.public_key().public_numbers()
if isinstance(private_key.curve, ec.SECP192R1): if isinstance(private_key.curve, ec.SECP192R1):
curve_len = 192 curve_len = 192
curve_id = CURVE_ID_P192 curve_id = CURVE_ID_P192
hash_type = hashes.SHA256()
digest = _sha256_digest(contents)
elif isinstance(numbers.curve, ec.SECP256R1): elif isinstance(numbers.curve, ec.SECP256R1):
curve_len = 256 curve_len = 256
curve_id = CURVE_ID_P256 curve_id = CURVE_ID_P256
hash_type = hashes.SHA256()
digest = _sha256_digest(contents)
elif isinstance(numbers.curve, ec.SECP384R1):
curve_len = 384
curve_id = CURVE_ID_P384
hash_type = hashes.SHA384()
digest = _sha384_digest(contents)
else: else:
raise esptool.FatalError("Invalid ECDSA curve instance.") raise esptool.FatalError("Invalid ECDSA curve instance.")
# ECDSA signatures
signature = private_key.sign(digest, ec.ECDSA(utils.Prehashed(hash_type)))
pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len) pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len)
r, s = utils.decode_dss_signature(signature) r, s = utils.decode_dss_signature(signature)
@@ -703,15 +719,34 @@ def generate_ecdsa_signature_block(digest, curve_id, pubkey_point, signature_rs)
# block is padded out to the much larger size # block is padded out to the much larger size
# of the RSA version of this structure # of the RSA version of this structure
""" """
signature_block = struct.pack(
"<BBxx32sB64s64s1031x", if curve_id in [CURVE_ID_P192, CURVE_ID_P256]:
SIG_BLOCK_MAGIC, signature_block = struct.pack(
SIG_BLOCK_VERSION_ECDSA, "<BBBx32sB64s64s1031x",
digest, SIG_BLOCK_MAGIC,
curve_id, SIG_BLOCK_VERSION_ECDSA,
pubkey_point, ECDSA_SHA_256,
signature_rs, digest,
) curve_id,
pubkey_point,
signature_rs,
)
elif curve_id == CURVE_ID_P384:
signature_block = struct.pack(
"<BBBx48sB96s96s951x",
SIG_BLOCK_MAGIC,
SIG_BLOCK_VERSION_ECDSA,
ECDSA_SHA_384,
digest,
curve_id,
pubkey_point,
signature_rs,
)
else:
raise esptool.FatalError(
"Invalid ECDSA curve ID detected while generating ECDSA signature block."
)
return signature_block return signature_block
@@ -818,10 +853,6 @@ def verify_signature_v2(args):
"Invalid datafile. Data size should be non-zero & a multiple of 4096." "Invalid datafile. Data size should be non-zero & a multiple of 4096."
) )
digest = digest = hashlib.sha256()
digest.update(image_content[:-SECTOR_SIZE])
digest = digest.digest()
valid = False valid = False
for sig_blk_num in range(SIG_BLOCK_MAX_COUNT): for sig_blk_num in range(SIG_BLOCK_MAX_COUNT):
@@ -829,7 +860,14 @@ def verify_signature_v2(args):
if sig_blk is None: if sig_blk is None:
print(f"Signature block {sig_blk_num} invalid. Skipping.") print(f"Signature block {sig_blk_num} invalid. Skipping.")
continue continue
_, version, blk_digest = struct.unpack("<BBxx32s", sig_blk[:36]) _, version, ecdsa_sha_version = struct.unpack("<BBBx", sig_blk[:4])
if version == SIG_BLOCK_VERSION_ECDSA and ecdsa_sha_version == ECDSA_SHA_384:
blk_digest = struct.unpack("<48s", sig_blk[4:52])[0]
digest = _sha384_digest(image_content[:-SECTOR_SIZE])
else:
blk_digest = struct.unpack("<32s", sig_blk[4:36])[0]
digest = _sha256_digest(image_content[:-SECTOR_SIZE])
if blk_digest != digest: if blk_digest != digest:
raise esptool.FatalError( raise esptool.FatalError(
@@ -849,22 +887,34 @@ def verify_signature_v2(args):
utils.Prehashed(hashes.SHA256()), utils.Prehashed(hashes.SHA256()),
) )
else: else:
curve_id, _pubkey, encoded_rs = struct.unpack( if ecdsa_sha_version == ECDSA_SHA_256:
"B64s64s1031x4x16x", sig_blk[36:] curve_id, _pubkey, encoded_rs = struct.unpack(
) "B64s64s1031x4x16x", sig_blk[36:]
)
elif ecdsa_sha_version == ECDSA_SHA_384:
curve_id, _pubkey, encoded_rs = struct.unpack(
"B96s96s951x4x16x", sig_blk[52:]
)
assert curve_id in (CURVE_ID_P192, CURVE_ID_P256) assert curve_id in (CURVE_ID_P192, CURVE_ID_P256, CURVE_ID_P384)
keylen = ( # length of each number in the keypair
24 if curve_id == CURVE_ID_P192 else 32 if curve_id == CURVE_ID_P192:
) # length of each number in the keypair keylen = 24
hash_type = hashes.SHA256()
elif curve_id == CURVE_ID_P256:
keylen = 32
hash_type = hashes.SHA256()
elif curve_id == CURVE_ID_P384:
keylen = 48
hash_type = hashes.SHA384()
r = int.from_bytes(encoded_rs[:keylen], "little") r = int.from_bytes(encoded_rs[:keylen], "little")
s = int.from_bytes(encoded_rs[keylen : keylen * 2], "little") s = int.from_bytes(encoded_rs[keylen : keylen * 2], "little")
signature = utils.encode_dss_signature(r, s) signature = utils.encode_dss_signature(r, s)
vk.verify(signature, digest, ec.ECDSA(utils.Prehashed(hashes.SHA256()))) vk.verify(signature, digest, ec.ECDSA(utils.Prehashed(hash_type)))
key_type = "RSA" if isinstance(vk, rsa.RSAPublicKey) else "ECDSA" key_type = "RSA" if isinstance(vk, rsa.RSAPublicKey) else "ECDSA"
@@ -937,6 +987,13 @@ def _sha256_digest(data):
return digest.digest() return digest.digest()
def _sha384_digest(contents):
# Calculate digest of data file
digest = hashlib.sha384()
digest.update(contents)
return digest.digest()
def signature_info_v2(args): def signature_info_v2(args):
""" """
Validates the signature block and prints the RSA/ECDSA public key Validates the signature block and prints the RSA/ECDSA public key
@@ -950,8 +1007,6 @@ def signature_info_v2(args):
"Invalid datafile. Data size should be non-zero & a multiple of 4096." "Invalid datafile. Data size should be non-zero & a multiple of 4096."
) )
digest = _sha256_digest(image_content[:-SECTOR_SIZE])
for sig_blk_num in range(SIG_BLOCK_MAX_COUNT): for sig_blk_num in range(SIG_BLOCK_MAX_COUNT):
sig_blk = validate_signature_block(image_content, sig_blk_num) sig_blk = validate_signature_block(image_content, sig_blk_num)
if sig_blk is None: if sig_blk is None:
@@ -961,7 +1016,15 @@ def signature_info_v2(args):
) )
return return
sig_data = struct.unpack("<BBxx32s384sI384sI384sI16x", sig_blk) _, version, ecdsa_sha_version = struct.unpack("<BBBx", sig_blk[:4])
if version == SIG_BLOCK_VERSION_ECDSA and ecdsa_sha_version == ECDSA_SHA_384:
sig_data = struct.unpack("<BBxx48s1164x", sig_blk)
digest = _sha384_digest(image_content[:-SECTOR_SIZE])
else:
sig_data = struct.unpack("<BBxx32s1180x", sig_blk)
digest = _sha256_digest(image_content[:-SECTOR_SIZE])
if sig_data[2] != digest: if sig_data[2] != digest:
raise esptool.FatalError( raise esptool.FatalError(
"Digest in signature block %d doesn't match the image digest." "Digest in signature block %d doesn't match the image digest."
@@ -973,7 +1036,10 @@ def signature_info_v2(args):
if sig_data[1] == SIG_BLOCK_VERSION_RSA: if sig_data[1] == SIG_BLOCK_VERSION_RSA:
key_digest = _sha256_digest(sig_blk[36:812]) key_digest = _sha256_digest(sig_blk[36:812])
elif sig_data[1] == SIG_BLOCK_VERSION_ECDSA: elif sig_data[1] == SIG_BLOCK_VERSION_ECDSA:
key_digest = _sha256_digest(sig_blk[36:101]) if ecdsa_sha_version == ECDSA_SHA_384:
key_digest = _sha256_digest(sig_blk[52:149])
else:
key_digest = _sha256_digest(sig_blk[36:101])
else: else:
raise esptool.FatalError( raise esptool.FatalError(
"Unsupported scheme in signature block %d" % (sig_blk_num) "Unsupported scheme in signature block %d" % (sig_blk_num)
@@ -1009,16 +1075,27 @@ def _digest_sbv2_public_key(keyfile):
if isinstance(public_key.curve, ec.SECP192R1): if isinstance(public_key.curve, ec.SECP192R1):
curve_len = 192 curve_len = 192
curve_id = CURVE_ID_P192 curve_id = CURVE_ID_P192
else: elif isinstance(public_key.curve, ec.SECP256R1):
curve_len = 256 curve_len = 256
curve_id = CURVE_ID_P256 curve_id = CURVE_ID_P256
elif isinstance(public_key.curve, ec.SECP384R1):
curve_len = 384
curve_id = CURVE_ID_P384
pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len) pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len)
binary_format = struct.pack( if curve_id == CURVE_ID_P384:
"<B64s", binary_format = struct.pack(
curve_id, "<B96s",
pubkey_point, curve_id,
) pubkey_point,
)
else:
binary_format = struct.pack(
"<B64s",
curve_id,
pubkey_point,
)
return hashlib.sha256(binary_format).digest() return hashlib.sha256(binary_format).digest()
@@ -1465,7 +1542,7 @@ def main(custom_commandline=None):
"as per the secure boot version. " "as per the secure boot version. "
"Key file is generated in PEM format, " "Key file is generated in PEM format, "
"Secure Boot V1 - ECDSA NIST256p private key. " "Secure Boot V1 - ECDSA NIST256p private key. "
"Secure Boot V2 - RSA 3072, ECDSA NIST256p, ECDSA NIST192p private key.", "Secure Boot V2 - RSA 3072, ECDSA NIST384p, ECDSA NIST256p, ECDSA NIST192p private key.",
) )
p.add_argument( p.add_argument(
"--version", "--version",
@@ -1478,7 +1555,7 @@ def main(custom_commandline=None):
"--scheme", "--scheme",
"-s", "-s",
help="Scheme of secure boot signing.", help="Scheme of secure boot signing.",
choices=["rsa3072", "ecdsa192", "ecdsa256"], choices=["rsa3072", "ecdsa192", "ecdsa256", "ecdsa384"],
required=False, required=False,
) )
p.add_argument( p.add_argument(
@@ -1489,7 +1566,7 @@ def main(custom_commandline=None):
"sign_data", "sign_data",
help="Sign a data file for use with secure boot. " help="Sign a data file for use with secure boot. "
"Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) " "Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) "
"or either RSA-PSS or ECDSA w/ SHA-256 (V2).", "or either RSA-PSS or ECDSA w/ SHA-256 or ECDSA w/ SHA-384 (V2).",
) )
p.add_argument( p.add_argument(
"--version", "--version",
@@ -1509,7 +1586,7 @@ def main(custom_commandline=None):
"--append_signatures", "--append_signatures",
"-a", "-a",
help="Append signature block(s) to already signed image. " help="Append signature block(s) to already signed image. "
"Valid only for ESP32-S2.", "Not valid for ESP32 and ESP32-C2.",
action="store_true", action="store_true",
) )
p.add_argument( p.add_argument(