Skip to content

Commit a36f951

Browse files
committed
imgtool: Add support for HMAC/HKDF-SHA512 with ECIES-X25519
Commit adds imgtool command line option --hmac-sha allowing to select between SHA256 and SHA512 for HMAC/HKDF. Signed-off-by: Dominik Ermel <[email protected]>
1 parent 3771916 commit a36f951

File tree

2 files changed

+34
-14
lines changed

2 files changed

+34
-14
lines changed

scripts/imgtool/image.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
'ENCKW': 0x31,
8989
'ENCEC256': 0x32,
9090
'ENCX25519': 0x33,
91+
'ENCX25519_SHA512': 0x34,
9192
'DEPENDENCY': 0x40,
9293
'SEC_CNT': 0x50,
9394
'BOOT_RECORD': 0x60,
@@ -436,21 +437,21 @@ def check_trailer(self):
436437
len(self.payload), tsize, self.slot_size)
437438
raise click.UsageError(msg)
438439

439-
def ecies_hkdf(self, enckey, plainkey):
440+
def ecies_hkdf(self, enckey, plainkey, hmac_sha_alg):
440441
if isinstance(enckey, ecdsa.ECDSA256P1Public):
441442
newpk = ec.generate_private_key(ec.SECP256R1(), default_backend())
442443
shared = newpk.exchange(ec.ECDH(), enckey._get_public())
443444
else:
444445
newpk = X25519PrivateKey.generate()
445446
shared = newpk.exchange(enckey._get_public())
446447
derived_key = HKDF(
447-
algorithm=hashes.SHA256(), length=48, salt=None,
448+
algorithm=hmac_sha_alg, length=48, salt=None,
448449
info=b'MCUBoot_ECIES_v1', backend=default_backend()).derive(shared)
449450
encryptor = Cipher(algorithms.AES(derived_key[:16]),
450451
modes.CTR(bytes([0] * 16)),
451452
backend=default_backend()).encryptor()
452453
cipherkey = encryptor.update(plainkey) + encryptor.finalize()
453-
mac = hmac.HMAC(derived_key[16:], hashes.SHA256(),
454+
mac = hmac.HMAC(derived_key[16:], hmac_sha_alg,
454455
backend=default_backend())
455456
mac.update(cipherkey)
456457
ciphermac = mac.finalize()
@@ -468,7 +469,8 @@ def create(self, key, public_key_format, enckey, dependencies=None,
468469
sw_type=None, custom_tlvs=None, compression_tlvs=None,
469470
compression_type=None, encrypt_keylen=128, clear=False,
470471
fixed_sig=None, pub_key=None, vector_to_sign=None,
471-
user_sha='auto', is_pure=False, keep_comp_size=False, dont_encrypt=False):
472+
user_sha='auto', hmac_sha='auto', is_pure=False, keep_comp_size=False,
473+
dont_encrypt=False):
472474
self.enckey = enckey
473475

474476
# key decides on sha, then pub_key; of both are none default is used
@@ -675,6 +677,17 @@ def create(self, key, public_key_format, enckey, dependencies=None,
675677
else:
676678
plainkey = os.urandom(16)
677679

680+
if not isinstance(enckey, rsa.RSAPublic):
681+
if hmac_sha == 'auto' or hmac_sha == '256':
682+
hmac_sha = '256'
683+
hmac_sha_alg = hashes.SHA256()
684+
elif hmac_sha == '512':
685+
if not isinstance(enckey, x25519.X25519Public):
686+
raise click.UsageError("Currently only ECIES-X25519 supports HMAC-SHA512")
687+
hmac_sha_alg = hashes.SHA512()
688+
else:
689+
raise click.UsageError("Unsupported HMAC-SHA")
690+
678691
if isinstance(enckey, rsa.RSAPublic):
679692
cipherkey = enckey._get_public().encrypt(
680693
plainkey, padding.OAEP(
@@ -683,15 +696,19 @@ def create(self, key, public_key_format, enckey, dependencies=None,
683696
label=None))
684697
self.enctlv_len = len(cipherkey)
685698
tlv.add('ENCRSA2048', cipherkey)
686-
elif isinstance(enckey, (ecdsa.ECDSA256P1Public,
687-
x25519.X25519Public)):
688-
cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey)
699+
elif isinstance(enckey, ecdsa.ECDSA256P1Public):
700+
cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg)
689701
enctlv = pubk + mac + cipherkey
690702
self.enctlv_len = len(enctlv)
691-
if isinstance(enckey, ecdsa.ECDSA256P1Public):
692-
tlv.add('ENCEC256', enctlv)
693-
else:
703+
tlv.add('ENCEC256', enctlv)
704+
elif isinstance(enckey, x25519.X25519Public):
705+
cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg)
706+
enctlv = pubk + mac + cipherkey
707+
self.enctlv_len = len(enctlv)
708+
if (hmac_sha == '256'):
694709
tlv.add('ENCX25519', enctlv)
710+
else:
711+
tlv.add('ENCX25519_SHA512', enctlv)
695712

696713
if not clear:
697714
nonce = bytes([0] * 16)

scripts/imgtool/main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def gen_x25519(keyfile, passwd):
8484
}
8585
valid_formats = ['openssl', 'pkcs8']
8686
valid_sha = [ 'auto', '256', '384', '512' ]
87+
valid_hmac_sha = [ 'auto', '256', '512' ]
8788

8889

8990
def load_signature(sigfile):
@@ -437,6 +438,8 @@ def convert(self, value, param, ctx):
437438
@click.option('--sha', 'user_sha', type=click.Choice(valid_sha), default='auto',
438439
help='selected sha algorithm to use; defaults to "auto" which is 256 if '
439440
'no cryptographic signature is used, or default for signature type')
441+
@click.option('--hmac-sha', 'hmac_sha', type=click.Choice(valid_hmac_sha), default='auto',
442+
help='sha algorithm used in HKDF/HMAC in ECIES key exchange TLV')
440443
@click.option('--vector-to-sign', type=click.Choice(['payload', 'digest']),
441444
help='send to OUTFILE the payload or payload''s digest instead '
442445
'of complied image. These data can be used for external image '
@@ -449,7 +452,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
449452
endian, encrypt_keylen, encrypt, compression, infile, outfile,
450453
dependencies, load_addr, hex_addr, erased_val, save_enctlv,
451454
security_counter, boot_record, custom_tlv, rom_fixed, max_align,
452-
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, is_pure,
455+
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, hmac_sha, is_pure,
453456
vector_to_sign, non_bootable):
454457

455458
if confirm:
@@ -526,7 +529,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
526529
img.create(key, public_key_format, enckey, dependencies, boot_record,
527530
custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear,
528531
baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
529-
is_pure=is_pure, keep_comp_size=False, dont_encrypt=True)
532+
hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=False, dont_encrypt=True)
530533
compressed_img = image.Image(version=decode_version(version),
531534
header_size=header_size, pad_header=pad_header,
532535
pad=pad, confirm=confirm, align=int(align),
@@ -570,14 +573,14 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
570573
compressed_img.create(key, public_key_format, enckey,
571574
dependencies, boot_record, custom_tlvs, compression_tlvs,
572575
compression, int(encrypt_keylen), clear, baked_signature,
573-
pub_key, vector_to_sign, user_sha=user_sha,
576+
pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha,
574577
is_pure=is_pure, keep_comp_size=keep_comp_size)
575578
img = compressed_img
576579
else:
577580
img.create(key, public_key_format, enckey, dependencies, boot_record,
578581
custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear,
579582
baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
580-
is_pure=is_pure)
583+
hmac_sha=hmac_sha, is_pure=is_pure)
581584
img.save(outfile, hex_addr)
582585
if sig_out is not None:
583586
new_signature = img.get_signature()

0 commit comments

Comments
 (0)