Skip to content

Commit 5322612

Browse files
committed
[nrf fromlist] imgtool: Add pure signature support
Adds PureEdDSA signature support. The change includes implementation of SIG_PURE TLV that, when present, indicates the signature that is present is Pure type. Upstream PR: mcu-tools/mcuboot#2063 Signed-off-by: Dominik Ermel <[email protected]>
1 parent de524e9 commit 5322612

File tree

2 files changed

+85
-22
lines changed

2 files changed

+85
-22
lines changed

scripts/imgtool/image.py

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
'ECDSASIG': 0x22,
7373
'RSA3072': 0x23,
7474
'ED25519': 0x24,
75+
'SIG_PURE' : 0x25,
7576
'ENCRSA2048': 0x30,
7677
'ENCKW': 0x31,
7778
'ENCEC256': 0x32,
@@ -174,7 +175,15 @@ def tlv_sha_to_sha(tlv):
174175
keys.X25519 : ['256', '512']
175176
}
176177

177-
def key_and_user_sha_to_alg_and_tlv(key, user_sha):
178+
ALLOWED_PURE_KEY_SHA = {
179+
keys.Ed25519 : ['512']
180+
}
181+
182+
ALLOWED_PURE_SIG_TLVS = [
183+
TLV_VALUES['ED25519']
184+
]
185+
186+
def key_and_user_sha_to_alg_and_tlv(key, user_sha, is_pure = False):
178187
"""Matches key and user requested sha to sha alogrithm and TLV name.
179188
180189
The returned tuple will contain hash functions and TVL name.
@@ -189,11 +198,17 @@ def key_and_user_sha_to_alg_and_tlv(key, user_sha):
189198
# If key is not None, then we have to filter hash to only allowed
190199
allowed = None
191200
try:
192-
allowed = ALLOWED_KEY_SHA[type(key)]
201+
if is_pure:
202+
allowed = ALLOWED_PURE_KEY_SHA[type(key)]
203+
else:
204+
allowed = ALLOWED_KEY_SHA[type(key)]
205+
193206
except KeyError:
194207
raise click.UsageError("Colud not find allowed hash algorithms for {}"
195208
.format(type(key)))
196-
if user_sha == 'auto':
209+
210+
# Pure enforces auto, and user selection is ignored
211+
if user_sha == 'auto' or is_pure:
197212
return USER_SHA_TO_ALG_AND_TLV[allowed[0]]
198213

199214
if user_sha in allowed:
@@ -408,12 +423,13 @@ def ecies_hkdf(self, enckey, plainkey):
408423

409424
def create(self, key, public_key_format, enckey, dependencies=None,
410425
sw_type=None, custom_tlvs=None, encrypt_keylen=128, clear=False,
411-
fixed_sig=None, pub_key=None, vector_to_sign=None, user_sha='auto'):
426+
fixed_sig=None, pub_key=None, vector_to_sign=None, user_sha='auto',
427+
is_pure=False):
412428
self.enckey = enckey
413429

414430
# key decides on sha, then pub_key; of both are none default is used
415431
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)
432+
hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha, is_pure)
417433

418434
# Calculate the hash of the public key
419435
if key is not None:
@@ -538,11 +554,18 @@ def create(self, key, public_key_format, enckey, dependencies=None,
538554
# EC signatures so called Pure algorithm, designated to be run
539555
# over entire message is used with sha of image as message,
540556
# so, for example, in case of ED25519 we have here SHAxxx-ED25519-SHA512.
541-
sha = hash_algorithm()
542-
sha.update(self.payload)
543-
digest = sha.digest()
544-
message = digest;
545-
tlv.add(hash_tlv, digest)
557+
if not is_pure:
558+
sha = hash_algorithm()
559+
sha.update(self.payload)
560+
digest = sha.digest()
561+
message = digest
562+
tlv.add(hash_tlv, digest)
563+
else:
564+
# Note that when Pure signature is used, there is not hash encoded
565+
message = bytes(self.payload)
566+
e = STRUCT_ENDIAN_DICT[self.endian]
567+
sig_pure = struct.pack(e + '?', True)
568+
tlv.add('SIG_PURE', sig_pure)
546569

547570
if vector_to_sign == 'payload':
548571
# Stop amending data to the image
@@ -728,7 +751,7 @@ def verify(imgfile, key):
728751
version = struct.unpack('BBHI', b[20:28])
729752

730753
if magic != IMAGE_MAGIC:
731-
return VerifyResult.INVALID_MAGIC, None, None
754+
return VerifyResult.INVALID_MAGIC, None, None, None
732755

733756
tlv_off = header_size + img_size
734757
tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE]
@@ -739,27 +762,46 @@ def verify(imgfile, key):
739762
magic, tlv_tot = struct.unpack('HH', tlv_info)
740763

741764
if magic != TLV_INFO_MAGIC:
742-
return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None
765+
return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None, None
766+
767+
# This is set by existence of TLV SIG_PURE
768+
is_pure = False
743769

744770
prot_tlv_size = tlv_off
745771
hash_region = b[:prot_tlv_size]
772+
tlv_end = tlv_off + tlv_tot
773+
tlv_off += TLV_INFO_SIZE # skip tlv info
774+
775+
# First scan all TLVs in search of SIG_PURE
776+
while tlv_off < tlv_end:
777+
tlv = b[tlv_off:tlv_off + TLV_SIZE]
778+
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
779+
if tlv_type == TLV_VALUES['SIG_PURE']:
780+
is_pure = True
781+
break
782+
tlv_off += TLV_SIZE + tlv_len
783+
746784
digest = None
785+
tlv_off = header_size + img_size
747786
tlv_end = tlv_off + tlv_tot
748787
tlv_off += TLV_INFO_SIZE # skip tlv info
749788
while tlv_off < tlv_end:
750789
tlv = b[tlv_off:tlv_off + TLV_SIZE]
751790
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
752791
if is_sha_tlv(tlv_type):
792+
if is_pure:
793+
print("SHA is not expected for signature over image (pure), ignoring")
794+
continue
753795
if not tlv_matches_key_type(tlv_type, key):
754-
return VerifyResult.KEY_MISMATCH, None, None
796+
return VerifyResult.KEY_MISMATCH, None, None, None
755797
off = tlv_off + TLV_SIZE
756798
digest = get_digest(tlv_type, hash_region)
757799
if digest == b[off:off + tlv_len]:
758800
if key is None:
759-
return VerifyResult.OK, version, digest
801+
return VerifyResult.OK, version, digest, None
760802
else:
761-
return VerifyResult.INVALID_HASH, None, None
762-
elif key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
803+
return VerifyResult.INVALID_HASH, None, None, None
804+
elif not is_pure and key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
763805
off = tlv_off + TLV_SIZE
764806
tlv_sig = b[off:off + tlv_len]
765807
payload = b[:prot_tlv_size]
@@ -768,9 +810,18 @@ def verify(imgfile, key):
768810
key.verify(tlv_sig, payload)
769811
else:
770812
key.verify_digest(tlv_sig, digest)
771-
return VerifyResult.OK, version, digest
813+
return VerifyResult.OK, version, digest, None
814+
except InvalidSignature:
815+
# continue to next TLV
816+
pass
817+
elif is_pure and key is not None and tlv_type in ALLOWED_PURE_SIG_TLVS:
818+
off = tlv_off + TLV_SIZE
819+
tlv_sig = b[off:off + tlv_len]
820+
try:
821+
key.verify_digest(tlv_sig, hash_region)
822+
return VerifyResult.OK, version, None, tlv_sig
772823
except InvalidSignature:
773824
# continue to next TLV
774825
pass
775826
tlv_off += TLV_SIZE + tlv_len
776-
return VerifyResult.INVALID_SIGNATURE, None, None
827+
return VerifyResult.INVALID_SIGNATURE, None, None, None

scripts/imgtool/main.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,14 @@ def getpriv(key, minimal, format):
215215
@click.command(help="Check that signed image can be verified by given key")
216216
def verify(key, imgfile):
217217
key = load_key(key) if key else None
218-
ret, version, digest = image.Image.verify(imgfile, key)
218+
ret, version, digest, signature = image.Image.verify(imgfile, key)
219219
if ret == image.VerifyResult.OK:
220220
print("Image was correctly validated")
221221
print("Image version: {}.{}.{}+{}".format(*version))
222-
print("Image digest: {}".format(digest.hex()))
222+
if digest:
223+
print("Image digest: {}".format(digest.hex()))
224+
if signature and digest is None:
225+
print("Image signature over image: {}".format(signature.hex()))
223226
return
224227
elif ret == image.VerifyResult.INVALID_MAGIC:
225228
print("Invalid image magic; is this an MCUboot image?")
@@ -399,6 +402,10 @@ def convert(self, value, param, ctx):
399402
'the signature calculated using the public key')
400403
@click.option('--fix-sig-pubkey', metavar='filename',
401404
help='public key relevant to fixed signature')
405+
@click.option('--pure', 'is_pure', is_flag=True, default=False, show_default=True,
406+
help='Expected Pure variant of signature; the Pure variant is '
407+
'expected to be signature done over an image rather than hash of '
408+
'that image.')
402409
@click.option('--sig-out', metavar='filename',
403410
help='Path to the file to which signature will be written. '
404411
'The image signature will be encoded as base64 formatted string')
@@ -417,7 +424,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
417424
endian, encrypt_keylen, encrypt, infile, outfile, dependencies,
418425
load_addr, hex_addr, erased_val, save_enctlv, security_counter,
419426
boot_record, custom_tlv, rom_fixed, max_align, clear, fix_sig,
420-
fix_sig_pubkey, sig_out, user_sha, vector_to_sign, non_bootable):
427+
fix_sig_pubkey, sig_out, user_sha, is_pure, vector_to_sign, non_bootable):
421428

422429
if confirm:
423430
# Confirmed but non-padded images don't make much sense, because
@@ -483,9 +490,14 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
483490
'value': raw_signature
484491
}
485492

493+
if is_pure and user_sha != 'auto':
494+
raise click.UsageError(
495+
'Pure signatures, currently, enforces preferred hash algorithm, '
496+
'and forbids sha selection by user.')
497+
486498
img.create(key, public_key_format, enckey, dependencies, boot_record,
487499
custom_tlvs, int(encrypt_keylen), clear, baked_signature,
488-
pub_key, vector_to_sign, user_sha)
500+
pub_key, vector_to_sign, user_sha, is_pure)
489501
img.save(outfile, hex_addr)
490502

491503
if sig_out is not None:

0 commit comments

Comments
 (0)