Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic ECDSA TLV Signature type & P384 to imgtool #1617

Merged
merged 7 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion boot/bootutil/include/bootutil/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ struct flash_area;
#define IMAGE_TLV_SHA256 0x10 /* SHA256 of image hdr and body */
#define IMAGE_TLV_RSA2048_PSS 0x20 /* RSA2048 of hash output */
#define IMAGE_TLV_ECDSA224 0x21 /* ECDSA of hash output - Not supported anymore */
#define IMAGE_TLV_ECDSA256 0x22 /* ECDSA of hash output */
Roolli marked this conversation as resolved.
Show resolved Hide resolved
#define IMAGE_TLV_ECDSA256 0x22 /* ECDSA of hash output - Not supported anymore */
#define IMAGE_TLV_RSA3072_PSS 0x23 /* RSA3072 of hash output */
#define IMAGE_TLV_ED25519 0x24 /* ed25519 of hash output */
#define IMAGE_TLV_ECDSA_SIG 0x25 /* ECDSA of hash output */
#define IMAGE_TLV_ENC_RSA2048 0x30 /* Key encrypted with RSA-OAEP-2048 */
#define IMAGE_TLV_ENC_KW 0x31 /* Key encrypted with AES-KW 128 or 256*/
#define IMAGE_TLV_ENC_EC256 0x32 /* Key encrypted with ECIES-EC256 */
Expand Down
7 changes: 4 additions & 3 deletions boot/bootutil/src/image_validate.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,11 @@ bootutil_img_hash(struct enc_key_data *enc_state, int image_index,
# endif
# define SIG_BUF_SIZE (MCUBOOT_SIGN_RSA_LEN / 8)
# define EXPECTED_SIG_LEN(x) ((x) == SIG_BUF_SIZE) /* 2048 bits */
#elif defined(MCUBOOT_SIGN_EC256)
# define EXPECTED_SIG_TLV IMAGE_TLV_ECDSA256
#elif defined(MCUBOOT_SIGN_EC256) || \
defined(MCUBOOT_SIGN_EC)
# define EXPECTED_SIG_TLV IMAGE_TLV_ECDSA_SIG
# define SIG_BUF_SIZE 128
# define EXPECTED_SIG_LEN(x) (1) /* always true, ASN.1 will validate */
# define EXPECTED_SIG_LEN(x) (1) /* always true, ASN.1 will validate */
#elif defined(MCUBOOT_SIGN_ED25519)
# define EXPECTED_SIG_TLV IMAGE_TLV_ED25519
# define SIG_BUF_SIZE 64
Expand Down
3 changes: 2 additions & 1 deletion docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,10 @@ struct image_tlv {
#define IMAGE_TLV_SHA256 0x10 /* SHA256 of image hdr and body */
#define IMAGE_TLV_RSA2048_PSS 0x20 /* RSA2048 of hash output */
#define IMAGE_TLV_ECDSA224 0x21 /* ECDSA of hash output - Not supported anymore */
#define IMAGE_TLV_ECDSA256 0x22 /* ECDSA of hash output */
#define IMAGE_TLV_ECDSA256 0x22 /* ECDSA of hash output - Not supported anymore */
#define IMAGE_TLV_RSA3072_PSS 0x23 /* RSA3072 of hash output */
#define IMAGE_TLV_ED25519 0x24 /* ED25519 of hash output */
#define IMAGE_TLV_ECDSA_SIG 0x25 /* ECDSA of hash output */
#define IMAGE_TLV_ENC_RSA2048 0x30 /* Key encrypted with RSA-OAEP-2048 */
#define IMAGE_TLV_ENC_KW 0x31 /* Key encrypted with AES-KW-128 or
256 */
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes.d/ecdsa-tlv-p384.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add generic ECDSA TLV, remove the ECDSA256 and ECDSA224 curve TLVs.
- Add P384 support to imgtool.
90 changes: 61 additions & 29 deletions scripts/imgtool/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@
'KEYHASH': 0x01,
'PUBKEY': 0x02,
'SHA256': 0x10,
'SHA384': 0x11,
'RSA2048': 0x20,
'ECDSA256': 0x22,
'RSA3072': 0x23,
'ED25519': 0x24,
'ECDSASIG': 0x25,
'ENCRSA2048': 0x30,
'ENCKW': 0x31,
'ENCEC256': 0x32,
Expand Down Expand Up @@ -94,10 +96,12 @@
INVALID_SIGNATURE
""")


def align_up(num, align):
assert (align & (align - 1) == 0) and align != 0
return (num + (align - 1)) & ~(align - 1)


class TLV():
def __init__(self, endian, magic=TLV_INFO_MAGIC):
self.magic = magic
Expand All @@ -116,7 +120,7 @@ def add(self, kind, payload):
if not TLV_VENDOR_RES_MIN <= kind <= TLV_VENDOR_RES_MAX:
msg = "Invalid custom TLV type value '0x{:04x}', allowed " \
"value should be between 0x{:04x} and 0x{:04x}".format(
kind, TLV_VENDOR_RES_MIN, TLV_VENDOR_RES_MAX)
kind, TLV_VENDOR_RES_MIN, TLV_VENDOR_RES_MAX)
raise click.UsageError(msg)
buf = struct.pack(e + 'HH', kind, len(payload))
else:
Expand Down Expand Up @@ -250,11 +254,13 @@ def save(self, path, hex_addr=None):
self.enctlv_len)
trailer_addr = (self.base_addr + self.slot_size) - trailer_size
if self.confirm and not self.overwrite_only:
magic_align_size = align_up(len(self.boot_magic), self.max_align)
magic_align_size = align_up(len(self.boot_magic),
self.max_align)
image_ok_idx = -(magic_align_size + self.max_align)
flag = bytearray([self.erased_val] * self.max_align)
flag[0] = 0x01 # image_ok = 0x01
h.puts(trailer_addr + trailer_size + image_ok_idx, bytes(flag))
flag[0] = 0x01 # image_ok = 0x01
h.puts(trailer_addr + trailer_size + image_ok_idx,
bytes(flag))
h.puts(trailer_addr + (trailer_size - len(self.boot_magic)),
bytes(self.boot_magic))
h.tofile(path, 'hex')
Expand Down Expand Up @@ -311,20 +317,31 @@ def ecies_hkdf(self, enckey, plainkey):
return cipherkey, ciphermac, pubk

def create(self, key, public_key_format, enckey, dependencies=None,
sw_type=None, custom_tlvs=None, encrypt_keylen=128, clear=False, fixed_sig=None, pub_key=None, vector_to_sign=None):
sw_type=None, custom_tlvs=None, encrypt_keylen=128, clear=False,
fixed_sig=None, pub_key=None, vector_to_sign=None,
use_legacy_tlv=False):
self.enckey = enckey

# Check what hashing algorithm should be used
if (key is not None and isinstance(key, ecdsa.ECDSA384P1) or
pub_key is not None and isinstance(pub_key,
ecdsa.ECDSA384P1Public)):
hash_algorithm = hashlib.sha384
hash_tlv = "SHA384"
else:
hash_algorithm = hashlib.sha256
hash_tlv = "SHA256"
# Calculate the hash of the public key
if key is not None:
pub = key.get_public_bytes()
sha = hashlib.sha256()
sha = hash_algorithm()
sha.update(pub)
pubbytes = sha.digest()
elif pub_key is not None:
if hasattr(pub_key, 'sign'):
print(os.path.basename(__file__) + ": sign the payload")
pub = pub_key.get_public_bytes()
sha = hashlib.sha256()
sha = hash_algorithm()
sha.update(pub)
pubbytes = sha.digest()
else:
Expand Down Expand Up @@ -354,11 +371,11 @@ def create(self, key, public_key_format, enckey, dependencies=None,
# before it is even calculated. For this reason the script fills
# this field with zeros and the bootloader will insert the right
# value later.
digest = bytes(hashlib.sha256().digest_size)
digest = bytes(hash_algorithm().digest_size)

# Create CBOR encoded boot record
boot_record = create_sw_component_data(sw_type, image_version,
"SHA256", digest,
hash_tlv, digest,
pubbytes)

protected_tlv_size += TLV_SIZE + len(boot_record)
Expand Down Expand Up @@ -435,11 +452,10 @@ def create(self, key, public_key_format, enckey, dependencies=None,

# Note that ecdsa wants to do the hashing itself, which means
# we get to hash it twice.
sha = hashlib.sha256()
sha = hash_algorithm()
sha.update(self.payload)
digest = sha.digest()

tlv.add('SHA256', digest)
tlv.add(hash_tlv, digest)

if vector_to_sign == 'payload':
# Stop amending data to the image
Expand All @@ -458,19 +474,28 @@ def create(self, key, public_key_format, enckey, dependencies=None,
tlv.add('PUBKEY', pub)

if key is not None and fixed_sig is None:
# `sign` expects the full image payload (sha256 done internally),
# while `sign_digest` expects only the digest of the payload
# `sign` expects the full image payload (hashing done
# internally), while `sign_digest` expects only the digest
# of the payload

if hasattr(key, 'sign'):
print(os.path.basename(__file__) + ": sign the payload")
sig = key.sign(bytes(self.payload))
else:
print(os.path.basename(__file__) + ": sign the digest")
sig = key.sign_digest(digest)
tlv.add(key.sig_tlv(), sig)
# only ecdsa256 has legacy tlv type
if use_legacy_tlv and isinstance(key, ecdsa.ECDSA256P1):
tlv.add(key.legacy_sig_tlv(), sig)
else:
tlv.add(key.sig_tlv(), sig)
self.signature = sig
elif fixed_sig is not None and key is None:
tlv.add(pub_key.sig_tlv(), fixed_sig['value'])
if use_legacy_tlv and isinstance(pub_key,
ecdsa.ECDSA256P1Public):
tlv.add(pub_key.legacy_sig_tlv(), fixed_sig['value'])
else:
tlv.add(pub_key.sig_tlv(), fixed_sig['value'])
self.signature = fixed_sig['value']
else:
raise click.UsageError("Can not sign using key and provide fixed-signature at the same time")
Expand Down Expand Up @@ -551,17 +576,18 @@ def add_header(self, enckey, protected_tlv_size, aes_length=128):
) # }
assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
header = struct.pack(fmt,
IMAGE_MAGIC,
self.rom_fixed or self.load_addr,
self.header_size,
protected_tlv_size, # TLV Info header + Protected TLVs
len(self.payload) - self.header_size, # ImageSz
flags,
self.version.major,
self.version.minor or 0,
self.version.revision or 0,
self.version.build or 0,
0) # Pad1
IMAGE_MAGIC,
self.rom_fixed or self.load_addr,
self.header_size,
protected_tlv_size, # TLV Info header +
# Protected TLVs
len(self.payload) - self.header_size, # ImageSz
flags,
self.version.major,
self.version.minor or 0,
self.version.revision or 0,
self.version.build or 0,
0) # Pad1
self.payload = bytearray(self.payload)
self.payload[:len(header)] = header

Expand Down Expand Up @@ -627,7 +653,13 @@ def verify(imgfile, key):
if magic != TLV_INFO_MAGIC:
return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None

sha = hashlib.sha256()
if isinstance(key, ecdsa.ECDSA384P1Public):
sha = hashlib.sha384()
hash_tlv = "SHA384"
else:
sha = hashlib.sha256()
hash_tlv = "SHA256"

prot_tlv_size = tlv_off
sha.update(b[:prot_tlv_size])
digest = sha.digest()
Expand All @@ -637,7 +669,7 @@ def verify(imgfile, key):
while tlv_off < tlv_end:
tlv = b[tlv_off:tlv_off+TLV_SIZE]
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
if tlv_type == TLV_VALUES["SHA256"]:
if tlv_type == TLV_VALUES[hash_tlv]:
off = tlv_off + TLV_SIZE
if digest == b[off:off+tlv_len]:
if key is None:
Expand Down
25 changes: 17 additions & 8 deletions scripts/imgtool/keys/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2017 Linaro Limited
# Copyright 2023 Arm Limited
#
# SPDX-License-Identifier: Apache-2.0
#
Expand Down Expand Up @@ -30,7 +31,8 @@
X25519PrivateKey, X25519PublicKey)

from .rsa import RSA, RSAPublic, RSAUsageError, RSA_KEY_SIZES
from .ecdsa import ECDSA256P1, ECDSA256P1Public, ECDSAUsageError
from .ecdsa import (ECDSA256P1, ECDSA256P1Public,
ECDSA384P1, ECDSA384P1Public, ECDSAUsageError)
from .ed25519 import Ed25519, Ed25519Public, Ed25519UsageError
from .x25519 import X25519, X25519Public, X25519UsageError

Expand All @@ -42,7 +44,8 @@ class PasswordRequired(Exception):


def load(path, passwd=None):
"""Try loading a key from the given path. Returns None if the password wasn't specified."""
"""Try loading a key from the given path.
Returns None if the password wasn't specified."""
with open(path, 'rb') as f:
raw_pem = f.read()
try:
Expand Down Expand Up @@ -73,17 +76,23 @@ def load(path, passwd=None):
raise Exception("Unsupported RSA key size: " + pk.key_size)
return RSAPublic(pk)
elif isinstance(pk, EllipticCurvePrivateKey):
if pk.curve.name != 'secp256r1':
if pk.curve.name not in ('secp256r1', 'secp384r1'):
raise Exception("Unsupported EC curve: " + pk.curve.name)
if pk.key_size != 256:
if pk.key_size not in (256, 384):
raise Exception("Unsupported EC size: " + pk.key_size)
return ECDSA256P1(pk)
if pk.curve.name == 'secp256r1':
return ECDSA256P1(pk)
elif pk.curve.name == 'secp384r1':
return ECDSA384P1(pk)
elif isinstance(pk, EllipticCurvePublicKey):
if pk.curve.name != 'secp256r1':
if pk.curve.name not in ('secp256r1', 'secp384r1'):
raise Exception("Unsupported EC curve: " + pk.curve.name)
if pk.key_size != 256:
if pk.key_size not in (256, 384):
raise Exception("Unsupported EC size: " + pk.key_size)
return ECDSA256P1Public(pk)
if pk.curve.name == 'secp256r1':
return ECDSA256P1Public(pk)
elif pk.curve.name == 'secp384r1':
return ECDSA384P1Public(pk)
elif isinstance(pk, Ed25519PrivateKey):
return Ed25519(pk)
elif isinstance(pk, Ed25519PublicKey):
Expand Down
Loading