imgtool: Add support for calculating SHA512
The adds support for hashing image with SHA512, to allow
SHA512-ED25519-SHA512 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 <dominik.ermel@nordicsemi.no>
diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py
index 5c4732b..53b19ef 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,73 @@
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 +212,16 @@
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 +408,13 @@
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 +534,14 @@
tlv = TLV(self.endian)
- # Note that ecdsa wants to do the hashing itself, which means
- # we get to hash it twice.
+ # These signature is done over sha of image. In case of
+ # EC signatures so called Pure algorithm, designated to be run
+ # over entire message is used with sha of image as message,
+ # so, for example, in case of ED25519 we have here SHAxxx-ED25519-SHA512.
sha = hash_algorithm()
sha.update(self.payload)
digest = sha.digest()
+ message = digest;
tlv.add(hash_tlv, digest)
if vector_to_sign == 'payload':
@@ -499,7 +570,7 @@
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 +749,7 @@
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 cc2cf9c..848fd31 100755
--- a/scripts/imgtool/main.py
+++ b/scripts/imgtool/main.py
@@ -72,6 +72,7 @@
'x25519': gen_x25519,
}
valid_formats = ['openssl', 'pkcs8']
+valid_sha = [ 'auto', '256', '384', '512' ]
def load_signature(sigfile):
@@ -401,6 +402,9 @@
@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 @@
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 @@
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: