Skip to content

Commit c894d04

Browse files
de-nordicnordicjm
authored andcommitted
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 <[email protected]>
1 parent 3a195f2 commit c894d04

File tree

2 files changed

+97
-22
lines changed

2 files changed

+97
-22
lines changed

scripts/imgtool/image.py

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
from .boot_record import create_sw_component_data
4141
from .keys import rsa, ecdsa, x25519
4242

43+
from collections import namedtuple
44+
4345
IMAGE_MAGIC = 0x96f3b83d
4446
IMAGE_HEADER_SIZE = 32
4547
BIN_EXT = "bin"
@@ -65,6 +67,7 @@
6567
'PUBKEY': 0x02,
6668
'SHA256': 0x10,
6769
'SHA384': 0x11,
70+
'SHA512': 0x12,
6871
'RSA2048': 0x20,
6972
'ECDSASIG': 0x22,
7073
'RSA3072': 0x23,
@@ -135,21 +138,90 @@ def get(self):
135138
return header + bytes(self.buf)
136139

137140

141+
SHAAndAlgT = namedtuple('SHAAndAlgT', ['sha', 'alg'])
142+
143+
TLV_SHA_TO_SHA_AND_ALG = {
144+
TLV_VALUES['SHA256'] : SHAAndAlgT('256', hashlib.sha256),
145+
TLV_VALUES['SHA384'] : SHAAndAlgT('384', hashlib.sha384),
146+
TLV_VALUES['SHA512'] : SHAAndAlgT('512', hashlib.sha512),
147+
}
148+
149+
150+
USER_SHA_TO_ALG_AND_TLV = {
151+
'auto' : (hashlib.sha256, 'SHA256'),
152+
'256' : (hashlib.sha256, 'SHA256'),
153+
'384' : (hashlib.sha384, 'SHA384'),
154+
'512' : (hashlib.sha512, 'SHA512')
155+
}
156+
157+
158+
def is_sha_tlv(tlv):
159+
return tlv in TLV_SHA_TO_SHA_AND_ALG.keys()
160+
161+
162+
def tlv_sha_to_sha(tlv):
163+
return TLV_SHA_TO_SHA_AND_ALG[tlv].sha
164+
165+
166+
# Auto selecting hash algorithm for type(key)
167+
ALLOWED_KEY_SHA = {
168+
keys.ECDSA384P1 : ['384'],
169+
keys.ECDSA384P1Public : ['384'],
170+
keys.ECDSA256P1 : ['256'],
171+
keys.RSA : ['256'],
172+
# This two are set to 256 for compatibility, the right would be 512
173+
keys.Ed25519 : ['256', '512'],
174+
keys.X25519 : ['256', '512']
175+
}
176+
177+
def key_and_user_sha_to_alg_and_tlv(key, user_sha):
178+
"""Matches key and user requested sha to sha alogrithm and TLV name.
179+
180+
The returned tuple will contain hash functions and TVL name.
181+
The function is designed to succeed or completely fail execution,
182+
as providing incorrect pair here basically prevents doing
183+
any more work.
184+
"""
185+
if key is None:
186+
# If key is none, we allow whatever user has selected for sha
187+
return USER_SHA_TO_ALG_AND_TLV[user_sha]
188+
189+
# If key is not None, then we have to filter hash to only allowed
190+
allowed = None
191+
try:
192+
allowed = ALLOWED_KEY_SHA[type(key)]
193+
except KeyError:
194+
raise click.UsageError("Colud not find allowed hash algorithms for {}"
195+
.format(type(key)))
196+
if user_sha == 'auto':
197+
return USER_SHA_TO_ALG_AND_TLV[allowed[0]]
198+
199+
if user_sha in allowed:
200+
return USER_SHA_TO_ALG_AND_TLV[user_sha]
201+
202+
raise click.UsageError("Key {} can not be used with --sha {}; allowed sha are one of {}"
203+
.format(key.sig_type(), user_sha, allowed))
204+
205+
138206
def get_digest(tlv_type, hash_region):
139-
if tlv_type == TLV_VALUES["SHA384"]:
140-
sha = hashlib.sha384()
141-
elif tlv_type == TLV_VALUES["SHA256"]:
142-
sha = hashlib.sha256()
207+
sha = TLV_SHA_TO_SHA_AND_ALG[tlv_type].alg()
143208

144209
sha.update(hash_region)
145210
return sha.digest()
146211

147212

148213
def tlv_matches_key_type(tlv_type, key):
149214
"""Check if provided key matches to TLV record in the image"""
150-
return (key is None or
151-
type(key) == keys.ECDSA384P1 and tlv_type == TLV_VALUES["SHA384"] or
152-
type(key) != keys.ECDSA384P1 and tlv_type == TLV_VALUES["SHA256"])
215+
try:
216+
# We do not need the result here, and the key_and_user_sha_to_alg_and_tlv
217+
# will either succeed finding match or rise exception, so on success we
218+
# return True, on exception we return False.
219+
_, _ = key_and_user_sha_to_alg_and_tlv(key, tlv_sha_to_sha(tlv_type))
220+
return True
221+
except:
222+
pass
223+
224+
return False
153225

154226

155227
class Image:
@@ -336,17 +408,13 @@ def ecies_hkdf(self, enckey, plainkey):
336408

337409
def create(self, key, public_key_format, enckey, dependencies=None,
338410
sw_type=None, custom_tlvs=None, encrypt_keylen=128, clear=False,
339-
fixed_sig=None, pub_key=None, vector_to_sign=None):
411+
fixed_sig=None, pub_key=None, vector_to_sign=None, user_sha='auto'):
340412
self.enckey = enckey
341413

342-
# Check what hashing algorithm should be used
343-
if (key and isinstance(key, ecdsa.ECDSA384P1)
344-
or pub_key and isinstance(pub_key, ecdsa.ECDSA384P1Public)):
345-
hash_algorithm = hashlib.sha384
346-
hash_tlv = "SHA384"
347-
else:
348-
hash_algorithm = hashlib.sha256
349-
hash_tlv = "SHA256"
414+
# key decides on sha, then pub_key; of both are none default is used
415+
check_key = key if key is not None else pub_key
416+
hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha)
417+
350418
# Calculate the hash of the public key
351419
if key is not None:
352420
pub = key.get_public_bytes()
@@ -466,11 +534,14 @@ def create(self, key, public_key_format, enckey, dependencies=None,
466534

467535
tlv = TLV(self.endian)
468536

469-
# Note that ecdsa wants to do the hashing itself, which means
470-
# we get to hash it twice.
537+
# These signature is done over sha of image. In case of
538+
# EC signatures so called Pure algorithm, designated to be run
539+
# over entire message is used with sha of image as message,
540+
# so, for example, in case of ED25519 we have here SHAxxx-ED25519-SHA512.
471541
sha = hash_algorithm()
472542
sha.update(self.payload)
473543
digest = sha.digest()
544+
message = digest;
474545
tlv.add(hash_tlv, digest)
475546

476547
if vector_to_sign == 'payload':
@@ -499,7 +570,7 @@ def create(self, key, public_key_format, enckey, dependencies=None,
499570
sig = key.sign(bytes(self.payload))
500571
else:
501572
print(os.path.basename(__file__) + ": sign the digest")
502-
sig = key.sign_digest(digest)
573+
sig = key.sign_digest(message)
503574
tlv.add(key.sig_tlv(), sig)
504575
self.signature = sig
505576
elif fixed_sig is not None and key is None:
@@ -678,7 +749,7 @@ def verify(imgfile, key):
678749
while tlv_off < tlv_end:
679750
tlv = b[tlv_off:tlv_off + TLV_SIZE]
680751
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
681-
if tlv_type == TLV_VALUES["SHA256"] or tlv_type == TLV_VALUES["SHA384"]:
752+
if is_sha_tlv(tlv_type):
682753
if not tlv_matches_key_type(tlv_type, key):
683754
return VerifyResult.KEY_MISMATCH, None, None
684755
off = tlv_off + TLV_SIZE

scripts/imgtool/main.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def gen_x25519(keyfile, passwd):
7272
'x25519': gen_x25519,
7373
}
7474
valid_formats = ['openssl', 'pkcs8']
75+
valid_sha = [ 'auto', '256', '384', '512' ]
7576

7677

7778
def load_signature(sigfile):
@@ -401,6 +402,9 @@ def convert(self, value, param, ctx):
401402
@click.option('--sig-out', metavar='filename',
402403
help='Path to the file to which signature will be written. '
403404
'The image signature will be encoded as base64 formatted string')
405+
@click.option('--sha', 'user_sha', type=click.Choice(valid_sha), default='auto',
406+
help='selected sha algorithm to use; defaults to "auto" which is 256 if '
407+
'no cryptographic signature is used, or default for signature type')
404408
@click.option('--vector-to-sign', type=click.Choice(['payload', 'digest']),
405409
help='send to OUTFILE the payload or payload''s digest instead '
406410
'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,
413417
endian, encrypt_keylen, encrypt, infile, outfile, dependencies,
414418
load_addr, hex_addr, erased_val, save_enctlv, security_counter,
415419
boot_record, custom_tlv, rom_fixed, max_align, clear, fix_sig,
416-
fix_sig_pubkey, sig_out, vector_to_sign, non_bootable):
420+
fix_sig_pubkey, sig_out, user_sha, vector_to_sign, non_bootable):
417421

418422
if confirm:
419423
# 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,
481485

482486
img.create(key, public_key_format, enckey, dependencies, boot_record,
483487
custom_tlvs, int(encrypt_keylen), clear, baked_signature,
484-
pub_key, vector_to_sign)
488+
pub_key, vector_to_sign, user_sha)
485489
img.save(outfile, hex_addr)
486490

487491
if sig_out is not None:

0 commit comments

Comments
 (0)