From 0d4248192547cb209b0e0b84f97637202d948040 Mon Sep 17 00:00:00 2001 From: Dominik Ermel Date: Thu, 22 Aug 2024 14:58:15 +0000 Subject: [PATCH] imgtool: Add support for calculating SHA512 The adds support for hashing image with SHA512. The PureEdDSA that is used here to sing the hash, not a perfect usage, uses sha512 internally so change at least allows to compile in single hash algorithm, the sha512, instead of sha512 and sha256. It also improves, slightly, collision resistance as longer message is used for signature. To support above --sha parameter has been added that can take value: auto, 256, 384, 512 to select sha, where auto brings the default behaviour, or current, behaviour. The sha provided here is tested against key so not all combinations are supported. Signed-off-by: Dominik Ermel --- scripts/imgtool/image.py | 107 +++++++++++++++++++++++++++++++-------- scripts/imgtool/main.py | 8 ++- 2 files changed, 93 insertions(+), 22 deletions(-) diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index 5c4732b53..0c45d6b87 100644 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -40,6 +40,8 @@ from .boot_record import create_sw_component_data from .keys import rsa, ecdsa, x25519 +from collections import namedtuple + IMAGE_MAGIC = 0x96f3b83d IMAGE_HEADER_SIZE = 32 BIN_EXT = "bin" @@ -65,6 +67,7 @@ 'PUBKEY': 0x02, 'SHA256': 0x10, 'SHA384': 0x11, + 'SHA512': 0x12, 'RSA2048': 0x20, 'ECDSASIG': 0x22, 'RSA3072': 0x23, @@ -135,11 +138,71 @@ def get(self): return header + bytes(self.buf) +SHAAndAlgT = namedtuple('SHAAndAlgT', ['sha', 'alg']) + +TLV_SHA_TO_SHA_AND_ALG = { + TLV_VALUES['SHA256'] : SHAAndAlgT('256', hashlib.sha256), + TLV_VALUES['SHA384'] : SHAAndAlgT('384', hashlib.sha384), + TLV_VALUES['SHA512'] : SHAAndAlgT('512', hashlib.sha512), +} + + +USER_SHA_TO_ALG_AND_TLV = { + 'auto' : (hashlib.sha256, 'SHA256'), + '256' : (hashlib.sha256, 'SHA256'), + '384' : (hashlib.sha384, 'SHA384'), + '512' : (hashlib.sha512, 'SHA512') +} + + +def is_sha_tlv(tlv): + return tlv in TLV_SHA_TO_SHA_AND_ALG.keys() + + +def tlv_sha_to_sha(tlv): + return TLV_SHA_TO_SHA_AND_ALG[tlv].sha + + +# Auto selecting hash algorithm for type(key) +ALLOWED_KEY_SHA = { + keys.ECDSA384P1 : ['384'], + keys.ECDSA384P1Public : ['384'], + keys.ECDSA256P1 : ['256'], + keys.RSA : ['256'], + # This two are set to 256 for compatibility, the right would be 512 + keys.Ed25519 : ['256', '512'], + keys.X25519 : ['256', '512'] +} + +def key_and_user_sha_to_alg_and_tlv(key, user_sha): + """Matches key and user requested sha to sha alogrithm and TLV name. + The returned tuple will contain hash functions and TVL name. + The function is designed to succeed or completely fail execution, + as providing incorrect pair here basically prevents doing + any more work.""" + if key is None: + # If key is none, we allow whatever user has selected for sha + return USER_SHA_TO_ALG_AND_TLV[user_sha] + + # If key is not None, then we have to filter hash to only allowed + allowed = None + try: + allowed = ALLOWED_KEY_SHA[type(key)] + except KeyError: + raise click.UsageError("Colud not find allowed hash algorithms for {}" + .format(type(key))) + if user_sha == 'auto': + return USER_SHA_TO_ALG_AND_TLV[allowed[0]] + + if user_sha in allowed: + return USER_SHA_TO_ALG_AND_TLV[user_sha] + + raise click.UsageError("Key {} can not be used with --sha {}; allowed sha are one of {}" + .format(key.sig_type(), user_sha, allowed)) + + def get_digest(tlv_type, hash_region): - if tlv_type == TLV_VALUES["SHA384"]: - sha = hashlib.sha384() - elif tlv_type == TLV_VALUES["SHA256"]: - sha = hashlib.sha256() + sha = TLV_SHA_TO_SHA_AND_ALG[tlv_type].alg() sha.update(hash_region) return sha.digest() @@ -147,9 +210,16 @@ def get_digest(tlv_type, hash_region): def tlv_matches_key_type(tlv_type, key): """Check if provided key matches to TLV record in the image""" - return (key is None or - type(key) == keys.ECDSA384P1 and tlv_type == TLV_VALUES["SHA384"] or - type(key) != keys.ECDSA384P1 and tlv_type == TLV_VALUES["SHA256"]) + try: + # We do not need the result here, and the key_and_user_sha_to_alg_and_tlv + # will either succeed finding match or rise exception, so on success we + # return True, on exception we return False. + _, _ = key_and_user_sha_to_alg_and_tlv(key, tlv_sha_to_sha(tlv_type)) + return True + except: + pass + + return False class Image: @@ -336,17 +406,13 @@ def ecies_hkdf(self, enckey, plainkey): def create(self, key, public_key_format, enckey, dependencies=None, sw_type=None, custom_tlvs=None, encrypt_keylen=128, clear=False, - fixed_sig=None, pub_key=None, vector_to_sign=None): + fixed_sig=None, pub_key=None, vector_to_sign=None, user_sha='auto'): self.enckey = enckey - # Check what hashing algorithm should be used - if (key and isinstance(key, ecdsa.ECDSA384P1) - or pub_key and isinstance(pub_key, ecdsa.ECDSA384P1Public)): - hash_algorithm = hashlib.sha384 - hash_tlv = "SHA384" - else: - hash_algorithm = hashlib.sha256 - hash_tlv = "SHA256" + # key decides on sha, then pub_key; of both are none default is used + check_key = key if key is not None else pub_key + hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha) + # Calculate the hash of the public key if key is not None: pub = key.get_public_bytes() @@ -466,11 +532,12 @@ def create(self, key, public_key_format, enckey, dependencies=None, tlv = TLV(self.endian) - # Note that ecdsa wants to do the hashing itself, which means - # we get to hash it twice. + # The PureEdDSA requires full image to be passed as message, + # which we do not do here, we pass sha of image instead. sha = hash_algorithm() sha.update(self.payload) digest = sha.digest() + message = digest; tlv.add(hash_tlv, digest) if vector_to_sign == 'payload': @@ -499,7 +566,7 @@ def create(self, key, public_key_format, enckey, dependencies=None, sig = key.sign(bytes(self.payload)) else: print(os.path.basename(__file__) + ": sign the digest") - sig = key.sign_digest(digest) + sig = key.sign_digest(message) tlv.add(key.sig_tlv(), sig) self.signature = sig elif fixed_sig is not None and key is None: @@ -678,7 +745,7 @@ def verify(imgfile, key): while tlv_off < tlv_end: tlv = b[tlv_off:tlv_off + TLV_SIZE] tlv_type, _, tlv_len = struct.unpack('BBH', tlv) - if tlv_type == TLV_VALUES["SHA256"] or tlv_type == TLV_VALUES["SHA384"]: + if is_sha_tlv(tlv_type): if not tlv_matches_key_type(tlv_type, key): return VerifyResult.KEY_MISMATCH, None, None off = tlv_off + TLV_SIZE diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index cc2cf9c58..848fd3110 100755 --- a/scripts/imgtool/main.py +++ b/scripts/imgtool/main.py @@ -72,6 +72,7 @@ def gen_x25519(keyfile, passwd): 'x25519': gen_x25519, } valid_formats = ['openssl', 'pkcs8'] +valid_sha = [ 'auto', '256', '384', '512' ] def load_signature(sigfile): @@ -401,6 +402,9 @@ def convert(self, value, param, ctx): @click.option('--sig-out', metavar='filename', help='Path to the file to which signature will be written. ' 'The image signature will be encoded as base64 formatted string') +@click.option('--sha', 'user_sha', type=click.Choice(valid_sha), default='auto', + help='selected sha algorithm to use; defaults to "auto" which is 256 if ' + 'no cryptographic signature is used, or default for signature type') @click.option('--vector-to-sign', type=click.Choice(['payload', 'digest']), help='send to OUTFILE the payload or payload''s digest instead ' 'of complied image. These data can be used for external image ' @@ -413,7 +417,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, endian, encrypt_keylen, encrypt, infile, outfile, dependencies, load_addr, hex_addr, erased_val, save_enctlv, security_counter, boot_record, custom_tlv, rom_fixed, max_align, clear, fix_sig, - fix_sig_pubkey, sig_out, vector_to_sign, non_bootable): + fix_sig_pubkey, sig_out, user_sha, vector_to_sign, non_bootable): if confirm: # Confirmed but non-padded images don't make much sense, because @@ -481,7 +485,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, img.create(key, public_key_format, enckey, dependencies, boot_record, custom_tlvs, int(encrypt_keylen), clear, baked_signature, - pub_key, vector_to_sign) + pub_key, vector_to_sign, user_sha) img.save(outfile, hex_addr) if sig_out is not None: