Commit a36f9513 authored by Dominik Ermel's avatar Dominik Ermel Committed by Dominik Ermel
Browse files

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: default avatarDominik Ermel <dominik.ermel@nordicsemi.no>
parent 37719169
Loading
Loading
Loading
Loading
+27 −10
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ TLV_VALUES = {
        'ENCKW': 0x31,
        'ENCEC256': 0x32,
        'ENCX25519': 0x33,
        'ENCX25519_SHA512': 0x34,
        'DEPENDENCY': 0x40,
        'SEC_CNT': 0x50,
        'BOOT_RECORD': 0x60,
@@ -436,7 +437,7 @@ class Image:
                          len(self.payload), tsize, self.slot_size)
                raise click.UsageError(msg)

    def ecies_hkdf(self, enckey, plainkey):
    def ecies_hkdf(self, enckey, plainkey, hmac_sha_alg):
        if isinstance(enckey, ecdsa.ECDSA256P1Public):
            newpk = ec.generate_private_key(ec.SECP256R1(), default_backend())
            shared = newpk.exchange(ec.ECDH(), enckey._get_public())
@@ -444,13 +445,13 @@ class Image:
            newpk = X25519PrivateKey.generate()
            shared = newpk.exchange(enckey._get_public())
        derived_key = HKDF(
            algorithm=hashes.SHA256(), length=48, salt=None,
            algorithm=hmac_sha_alg, length=48, salt=None,
            info=b'MCUBoot_ECIES_v1', backend=default_backend()).derive(shared)
        encryptor = Cipher(algorithms.AES(derived_key[:16]),
                           modes.CTR(bytes([0] * 16)),
                           backend=default_backend()).encryptor()
        cipherkey = encryptor.update(plainkey) + encryptor.finalize()
        mac = hmac.HMAC(derived_key[16:], hashes.SHA256(),
        mac = hmac.HMAC(derived_key[16:], hmac_sha_alg,
                        backend=default_backend())
        mac.update(cipherkey)
        ciphermac = mac.finalize()
@@ -468,7 +469,8 @@ class Image:
               sw_type=None, custom_tlvs=None, compression_tlvs=None,
               compression_type=None, encrypt_keylen=128, clear=False,
               fixed_sig=None, pub_key=None, vector_to_sign=None,
               user_sha='auto', is_pure=False, keep_comp_size=False, dont_encrypt=False):
               user_sha='auto', hmac_sha='auto', is_pure=False, keep_comp_size=False,
               dont_encrypt=False):
        self.enckey = enckey

        # key decides on sha, then pub_key; of both are none default is used
@@ -675,6 +677,17 @@ class Image:
            else:
                plainkey = os.urandom(16)

            if not isinstance(enckey, rsa.RSAPublic):
                if hmac_sha == 'auto' or hmac_sha == '256':
                    hmac_sha = '256'
                    hmac_sha_alg = hashes.SHA256()
                elif hmac_sha == '512':
                    if not isinstance(enckey, x25519.X25519Public):
                        raise click.UsageError("Currently only ECIES-X25519 supports HMAC-SHA512")
                    hmac_sha_alg = hashes.SHA512()
                else:
                    raise click.UsageError("Unsupported HMAC-SHA")

            if isinstance(enckey, rsa.RSAPublic):
                cipherkey = enckey._get_public().encrypt(
                    plainkey, padding.OAEP(
@@ -683,15 +696,19 @@ class Image:
                        label=None))
                self.enctlv_len = len(cipherkey)
                tlv.add('ENCRSA2048', cipherkey)
            elif isinstance(enckey, (ecdsa.ECDSA256P1Public,
                                     x25519.X25519Public)):
                cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey)
            elif isinstance(enckey, ecdsa.ECDSA256P1Public):
                cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg)
                enctlv = pubk + mac + cipherkey
                self.enctlv_len = len(enctlv)
                if isinstance(enckey, ecdsa.ECDSA256P1Public):
                tlv.add('ENCEC256', enctlv)
                else:
            elif isinstance(enckey, x25519.X25519Public):
                cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg)
                enctlv = pubk + mac + cipherkey
                self.enctlv_len = len(enctlv)
                if (hmac_sha == '256'):
                    tlv.add('ENCX25519', enctlv)
                else:
                    tlv.add('ENCX25519_SHA512', enctlv)

            if not clear:
                nonce = bytes([0] * 16)
+7 −4
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ keygens = {
}
valid_formats = ['openssl', 'pkcs8']
valid_sha = [ 'auto', '256', '384', '512' ]
valid_hmac_sha = [ 'auto', '256', '512' ]


def load_signature(sigfile):
@@ -437,6 +438,8 @@ class BasedIntParamType(click.ParamType):
@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('--hmac-sha', 'hmac_sha', type=click.Choice(valid_hmac_sha), default='auto',
              help='sha algorithm used in HKDF/HMAC in ECIES key exchange TLV')
@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 '
@@ -449,7 +452,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
         endian, encrypt_keylen, encrypt, compression, 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, user_sha, is_pure,
         clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, hmac_sha, is_pure,
         vector_to_sign, non_bootable):

    if confirm:
@@ -526,7 +529,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, compression_tlvs, None, int(encrypt_keylen), clear,
               baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
               is_pure=is_pure, keep_comp_size=False, dont_encrypt=True)
               hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=False, dont_encrypt=True)
        compressed_img = image.Image(version=decode_version(version),
                  header_size=header_size, pad_header=pad_header,
                  pad=pad, confirm=confirm, align=int(align),
@@ -570,14 +573,14 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
            compressed_img.create(key, public_key_format, enckey,
               dependencies, boot_record, custom_tlvs, compression_tlvs,
               compression, int(encrypt_keylen), clear, baked_signature,
               pub_key, vector_to_sign, user_sha=user_sha,
               pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha,
               is_pure=is_pure, keep_comp_size=keep_comp_size)
            img = compressed_img
    else:
        img.create(key, public_key_format, enckey, dependencies, boot_record,
               custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear,
               baked_signature, pub_key, vector_to_sign, user_sha=user_sha,
               is_pure=is_pure)
               hmac_sha=hmac_sha, is_pure=is_pure)
    img.save(outfile, hex_addr)
    if sig_out is not None:
        new_signature = img.get_signature()