Skip to content

Commit 042f458

Browse files
de-nordicmichalek-no
authored andcommitted
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. Signed-off-by: Dominik Ermel <[email protected]>
1 parent a2bc982 commit 042f458

File tree

2 files changed

+80
-20
lines changed

2 files changed

+80
-20
lines changed

scripts/imgtool/image.py

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,15 @@ def tlv_sha_to_sha(tlv):
190190
keys.X25519 : ['256', '512']
191191
}
192192

193-
def key_and_user_sha_to_alg_and_tlv(key, user_sha):
193+
ALLOWED_PURE_KEY_SHA = {
194+
keys.Ed25519 : ['512']
195+
}
196+
197+
ALLOWED_PURE_SIG_TLVS = [
198+
TLV_VALUES['ED25519']
199+
]
200+
201+
def key_and_user_sha_to_alg_and_tlv(key, user_sha, is_pure = False):
194202
"""Matches key and user requested sha to sha alogrithm and TLV name.
195203
196204
The returned tuple will contain hash functions and TVL name.
@@ -204,12 +212,16 @@ def key_and_user_sha_to_alg_and_tlv(key, user_sha):
204212

205213
# If key is not None, then we have to filter hash to only allowed
206214
allowed = None
215+
allowed_key_ssh = ALLOWED_PURE_KEY_SHA if is_pure else ALLOWED_KEY_SHA
207216
try:
208-
allowed = ALLOWED_KEY_SHA[type(key)]
217+
allowed = allowed_key_ssh[type(key)]
218+
209219
except KeyError:
210220
raise click.UsageError("Colud not find allowed hash algorithms for {}"
211221
.format(type(key)))
212-
if user_sha == 'auto':
222+
223+
# Pure enforces auto, and user selection is ignored
224+
if user_sha == 'auto' or is_pure:
213225
return USER_SHA_TO_ALG_AND_TLV[allowed[0]]
214226

215227
if user_sha in allowed:
@@ -447,12 +459,13 @@ def ecies_hkdf(self, enckey, plainkey):
447459
def create(self, key, public_key_format, enckey, dependencies=None,
448460
sw_type=None, custom_tlvs=None, compression_tlvs=None,
449461
compression_type=None, encrypt_keylen=128, clear=False,
450-
fixed_sig=None, pub_key=None, vector_to_sign=None, user_sha='auto'):
462+
fixed_sig=None, pub_key=None, vector_to_sign=None,
463+
user_sha='auto', is_pure=False):
451464
self.enckey = enckey
452465

453466
# key decides on sha, then pub_key; of both are none default is used
454467
check_key = key if key is not None else pub_key
455-
hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha)
468+
hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha, is_pure)
456469

457470
# Calculate the hash of the public key
458471
if key is not None:
@@ -592,9 +605,17 @@ def create(self, key, public_key_format, enckey, dependencies=None,
592605
sha = hash_algorithm()
593606
sha.update(self.payload)
594607
digest = sha.digest()
595-
message = digest;
596608
tlv.add(hash_tlv, digest)
597609
self.image_hash = digest
610+
# Unless pure, we are signing digest.
611+
message = digest
612+
613+
if is_pure:
614+
# Note that when Pure signature is used, hash TLV is not present.
615+
message = bytes(self.payload)
616+
e = STRUCT_ENDIAN_DICT[self.endian]
617+
sig_pure = struct.pack(e + '?', True)
618+
tlv.add('SIG_PURE', sig_pure)
598619

599620
if vector_to_sign == 'payload':
600621
# Stop amending data to the image
@@ -786,7 +807,7 @@ def verify(imgfile, key):
786807
version = struct.unpack('BBHI', b[20:28])
787808

788809
if magic != IMAGE_MAGIC:
789-
return VerifyResult.INVALID_MAGIC, None, None
810+
return VerifyResult.INVALID_MAGIC, None, None, None
790811

791812
tlv_off = header_size + img_size
792813
tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE]
@@ -797,27 +818,43 @@ def verify(imgfile, key):
797818
magic, tlv_tot = struct.unpack('HH', tlv_info)
798819

799820
if magic != TLV_INFO_MAGIC:
800-
return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None
821+
return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None, None
822+
823+
# This is set by existence of TLV SIG_PURE
824+
is_pure = False
801825

802826
prot_tlv_size = tlv_off
803827
hash_region = b[:prot_tlv_size]
828+
tlv_end = tlv_off + tlv_tot
829+
tlv_off += TLV_INFO_SIZE # skip tlv info
830+
831+
# First scan all TLVs in search of SIG_PURE
832+
while tlv_off < tlv_end:
833+
tlv = b[tlv_off:tlv_off + TLV_SIZE]
834+
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
835+
if tlv_type == TLV_VALUES['SIG_PURE']:
836+
is_pure = True
837+
break
838+
tlv_off += TLV_SIZE + tlv_len
839+
804840
digest = None
841+
tlv_off = header_size + img_size
805842
tlv_end = tlv_off + tlv_tot
806843
tlv_off += TLV_INFO_SIZE # skip tlv info
807844
while tlv_off < tlv_end:
808845
tlv = b[tlv_off:tlv_off + TLV_SIZE]
809846
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
810847
if is_sha_tlv(tlv_type):
811848
if not tlv_matches_key_type(tlv_type, key):
812-
return VerifyResult.KEY_MISMATCH, None, None
849+
return VerifyResult.KEY_MISMATCH, None, None, None
813850
off = tlv_off + TLV_SIZE
814851
digest = get_digest(tlv_type, hash_region)
815852
if digest == b[off:off + tlv_len]:
816853
if key is None:
817-
return VerifyResult.OK, version, digest
854+
return VerifyResult.OK, version, digest, None
818855
else:
819-
return VerifyResult.INVALID_HASH, None, None
820-
elif key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
856+
return VerifyResult.INVALID_HASH, None, None, None
857+
elif not is_pure and key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
821858
off = tlv_off + TLV_SIZE
822859
tlv_sig = b[off:off + tlv_len]
823860
payload = b[:prot_tlv_size]
@@ -826,9 +863,18 @@ def verify(imgfile, key):
826863
key.verify(tlv_sig, payload)
827864
else:
828865
key.verify_digest(tlv_sig, digest)
829-
return VerifyResult.OK, version, digest
866+
return VerifyResult.OK, version, digest, None
867+
except InvalidSignature:
868+
# continue to next TLV
869+
pass
870+
elif is_pure and key is not None and tlv_type in ALLOWED_PURE_SIG_TLVS:
871+
off = tlv_off + TLV_SIZE
872+
tlv_sig = b[off:off + tlv_len]
873+
try:
874+
key.verify_digest(tlv_sig, hash_region)
875+
return VerifyResult.OK, version, None, tlv_sig
830876
except InvalidSignature:
831877
# continue to next TLV
832878
pass
833879
tlv_off += TLV_SIZE + tlv_len
834-
return VerifyResult.INVALID_SIGNATURE, None, None
880+
return VerifyResult.INVALID_SIGNATURE, None, None, None

scripts/imgtool/main.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,14 @@ def getpriv(key, minimal, format):
226226
@click.command(help="Check that signed image can be verified by given key")
227227
def verify(key, imgfile):
228228
key = load_key(key) if key else None
229-
ret, version, digest = image.Image.verify(imgfile, key)
229+
ret, version, digest, signature = image.Image.verify(imgfile, key)
230230
if ret == image.VerifyResult.OK:
231231
print("Image was correctly validated")
232232
print("Image version: {}.{}.{}+{}".format(*version))
233-
print("Image digest: {}".format(digest.hex()))
233+
if digest:
234+
print("Image digest: {}".format(digest.hex()))
235+
if signature and digest is None:
236+
print("Image signature over image: {}".format(signature.hex()))
234237
return
235238
elif ret == image.VerifyResult.INVALID_MAGIC:
236239
print("Invalid image magic; is this an MCUboot image?")
@@ -423,6 +426,10 @@ def convert(self, value, param, ctx):
423426
'the signature calculated using the public key')
424427
@click.option('--fix-sig-pubkey', metavar='filename',
425428
help='public key relevant to fixed signature')
429+
@click.option('--pure', 'is_pure', is_flag=True, default=False, show_default=True,
430+
help='Expected Pure variant of signature; the Pure variant is '
431+
'expected to be signature done over an image rather than hash of '
432+
'that image.')
426433
@click.option('--sig-out', metavar='filename',
427434
help='Path to the file to which signature will be written. '
428435
'The image signature will be encoded as base64 formatted string')
@@ -441,8 +448,8 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
441448
endian, encrypt_keylen, encrypt, compression, infile, outfile,
442449
dependencies, load_addr, hex_addr, erased_val, save_enctlv,
443450
security_counter, boot_record, custom_tlv, rom_fixed, max_align,
444-
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, vector_to_sign,
445-
non_bootable):
451+
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, is_pure,
452+
vector_to_sign, non_bootable):
446453

447454
if confirm:
448455
# Confirmed but non-padded images don't make much sense, because
@@ -509,9 +516,15 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
509516
'value': raw_signature
510517
}
511518

519+
if is_pure and user_sha != 'auto':
520+
raise click.UsageError(
521+
'Pure signatures, currently, enforces preferred hash algorithm, '
522+
'and forbids sha selection by user.')
523+
512524
img.create(key, public_key_format, enckey, dependencies, boot_record,
513525
custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear,
514-
baked_signature, pub_key, vector_to_sign, user_sha)
526+
baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
527+
is_pure=is_pure)
515528

516529
if compression in ["lzma2", "lzma2armthumb"]:
517530
compressed_img = image.Image(version=decode_version(version),
@@ -552,7 +565,8 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
552565
compressed_img.create(key, public_key_format, enckey,
553566
dependencies, boot_record, custom_tlvs, compression_tlvs,
554567
compression, int(encrypt_keylen), clear, baked_signature,
555-
pub_key, vector_to_sign, user_sha=user_sha)
568+
pub_key, vector_to_sign, user_sha=user_sha,
569+
is_pure=is_pure)
556570
img = compressed_img
557571
img.save(outfile, hex_addr)
558572
if sig_out is not None:

0 commit comments

Comments
 (0)