Skip to content

Commit

Permalink
imgtool: Add support for calculating SHA512
Browse files Browse the repository at this point in the history
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 <dominik.ermel@nordicsemi.no>
  • Loading branch information
de-nordic committed Sep 5, 2024
1 parent 956311d commit 0d42481
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 22 deletions.
107 changes: 87 additions & 20 deletions scripts/imgtool/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -65,6 +67,7 @@
'PUBKEY': 0x02,
'SHA256': 0x10,
'SHA384': 0x11,
'SHA512': 0x12,
'RSA2048': 0x20,
'ECDSASIG': 0x22,
'RSA3072': 0x23,
Expand Down Expand Up @@ -135,21 +138,88 @@ 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()


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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions scripts/imgtool/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 '
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 0d42481

Please sign in to comment.