mirror of https://github.com/ARMmbed/mbed-os.git
Import latest python scripts and MCUBoot image
To help with the integration of Musca B1 into Mbed OS, python signing scripts and MCUBoot image and RSA private key for Musca A has been updated from latest TF-M (https://git.trustedfirmware.org/trusted-firmware-m.git/commit/?id=6c5be4a98e4d7055ee49076ca4e515fb4b172e66). Signed-off-by: Devaraj Ranganna <devaraj.ranganna@arm.com>pull/12231/head
parent
a81f016abc
commit
bc7331b96e
|
@ -10,7 +10,7 @@ These images were compiled by the following command:
|
|||
### Repository
|
||||
https://git.trustedfirmware.org/trusted-firmware-m.git
|
||||
### Commit SHA
|
||||
8da7f102a6a6a1a99462f7f32edbd1565096c2f3
|
||||
6c5be4a98e4d7055ee49076ca4e515fb4b172e66
|
||||
```sh
|
||||
cmake ../ -G"Unix Makefiles" -DTARGET_PLATFORM=MUSCA_A -DCOMPILER=ARMCLANG -DCMAKE_BUILD_TYPE=Debug
|
||||
make
|
||||
|
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
#! /usr/bin/env python3
|
||||
#
|
||||
# Copyright 2017 Linaro Limited
|
||||
# Copyright (c) 2017-2018, Arm Limited.
|
||||
# Copyright (c) 2017-2019, Arm Limited.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -25,6 +25,7 @@ import io
|
|||
import re
|
||||
import os
|
||||
import shutil
|
||||
from . import macro_parser
|
||||
|
||||
offset_re = re.compile(r"^#define ([0-9A-Z_]+)_IMAGE_OFFSET\s+((0x)?[0-9a-fA-F]+)")
|
||||
size_re = re.compile(r"^#define ([0-9A-Z_]+)_IMAGE_MAX_SIZE\s+((0x)?[0-9a-fA-F]+)")
|
||||
|
@ -44,20 +45,8 @@ class Assembly():
|
|||
offsets = {}
|
||||
sizes = {}
|
||||
|
||||
if os.path.isabs(self.layout_path):
|
||||
configFile = self.layout_path
|
||||
else:
|
||||
scriptsDir = os.path.dirname(os.path.abspath(__file__))
|
||||
configFile = os.path.join(scriptsDir, self.layout_path)
|
||||
|
||||
with open(configFile, 'r') as fd:
|
||||
for line in fd:
|
||||
m = offset_re.match(line)
|
||||
if m is not None:
|
||||
offsets[m.group(1)] = int(m.group(2), 0)
|
||||
m = size_re.match(line)
|
||||
if m is not None:
|
||||
sizes[m.group(1)] = int(m.group(2), 0)
|
||||
offsets = macro_parser.evaluate_macro(self.layout_path, offset_re, 1, 2)
|
||||
sizes = macro_parser.evaluate_macro(self.layout_path, size_re, 1, 2)
|
||||
|
||||
if 'SECURE' not in offsets:
|
||||
raise Exception("Image config does not have secure partition")
|
||||
|
@ -86,7 +75,7 @@ def main():
|
|||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('-l', '--layout', required=True,
|
||||
help='Location of the memory layout file')
|
||||
help='Location of the file that contains preprocessed macros')
|
||||
parser.add_argument('-s', '--secure', required=True,
|
||||
help='Unsigned secure image')
|
||||
parser.add_argument('-n', '--non_secure',
|
||||
|
@ -97,7 +86,6 @@ def main():
|
|||
args = parser.parse_args()
|
||||
output = Assembly(args.layout, args.output)
|
||||
|
||||
|
||||
output.add_image(args.secure, "SECURE")
|
||||
output.add_image(args.non_secure, "NON_SECURE")
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#! /usr/bin/env python3
|
||||
#
|
||||
# Copyright 2017 Linaro Limited
|
||||
# Copyright (c) 2018, Arm Limited.
|
||||
# Copyright (c) 2018-2019, Arm Limited.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -24,6 +24,9 @@ from .imgtool_lib import image
|
|||
from .imgtool_lib import version
|
||||
import sys
|
||||
|
||||
sign_bin_size_re = re.compile(r"^\s*RE_SIGN_BIN_SIZE\s*=\s*(.*)")
|
||||
image_load_address_re = re.compile(r"^\s*RE_IMAGE_LOAD_ADDRESS\s*=\s*(.*)")
|
||||
|
||||
def find_load_address(args):
|
||||
load_address_re = re.compile(r"^#define\sIMAGE_LOAD_ADDRESS\s+(0x[0-9a-fA-F]+)")
|
||||
|
||||
|
@ -60,6 +63,7 @@ def get_last_version(path):
|
|||
|
||||
def next_version_number(args, defaultVersion, path):
|
||||
newVersion = None
|
||||
versionProvided = False
|
||||
if (version.compare(args.version, defaultVersion) == 0): # Default version
|
||||
lastVersion = get_last_version(path)
|
||||
if (lastVersion is not None):
|
||||
|
@ -67,6 +71,7 @@ def next_version_number(args, defaultVersion, path):
|
|||
else:
|
||||
newVersion = version.increment_build_num(defaultVersion)
|
||||
else: # Version number has been explicitly provided (not using the default)
|
||||
versionProvided = True
|
||||
newVersion = args.version
|
||||
versionString = "{a}.{b}.{c}+{d}".format(
|
||||
a=str(newVersion.major),
|
||||
|
@ -74,16 +79,21 @@ def next_version_number(args, defaultVersion, path):
|
|||
c=str(newVersion.revision),
|
||||
d=str(newVersion.build)
|
||||
)
|
||||
with open(path, "w") as newFile:
|
||||
newFile.write(versionString)
|
||||
if not versionProvided:
|
||||
with open(path, "w") as newFile:
|
||||
newFile.write(versionString)
|
||||
print("**[INFO]** Image version number set to " + versionString)
|
||||
return newVersion
|
||||
|
||||
def gen_rsa2048(args):
|
||||
keys.RSA2048.generate().export_private(args.key)
|
||||
keys.RSAutil.generate().export_private(args.key)
|
||||
|
||||
def gen_rsa3072(args):
|
||||
keys.RSAutil.generate(key_size=3072).export_private(args.key)
|
||||
|
||||
keygens = {
|
||||
'rsa-2048': gen_rsa2048, }
|
||||
'rsa-2048': gen_rsa2048,
|
||||
'rsa-3072': gen_rsa3072, }
|
||||
|
||||
def do_keygen(args):
|
||||
if args.type not in keygens:
|
||||
|
@ -102,18 +112,38 @@ def do_getpub(args):
|
|||
def do_sign(args):
|
||||
if args.rsa_pkcs1_15:
|
||||
keys.sign_rsa_pss = False
|
||||
img = image.Image.load(args.infile,
|
||||
version=next_version_number(args,
|
||||
version.decode_version("0"),
|
||||
"lastVerNum.txt"),
|
||||
header_size=args.header_size,
|
||||
included_header=args.included_header,
|
||||
pad=args.pad)
|
||||
key = keys.load(args.key) if args.key else None
|
||||
img.sign(key, find_load_address(args))
|
||||
|
||||
if args.pad:
|
||||
img.pad_to(args.pad, args.align)
|
||||
version_num = next_version_number(args,
|
||||
version.decode_version("0"),
|
||||
"lastVerNum.txt")
|
||||
|
||||
if args.security_counter is None:
|
||||
# Security counter has not been explicitly provided,
|
||||
# generate it from the version number
|
||||
args.security_counter = ((version_num.major << 24)
|
||||
+ (version_num.minor << 16)
|
||||
+ version_num.revision)
|
||||
|
||||
if "_s.c" in args.layout:
|
||||
sw_type = "SPE"
|
||||
elif "_ns.c" in args.layout:
|
||||
sw_type = "NSPE"
|
||||
else:
|
||||
sw_type = "NSPE_SPE"
|
||||
|
||||
pad_size = args.pad
|
||||
img = image.Image.load(args.infile,
|
||||
version=version_num,
|
||||
header_size=args.header_size,
|
||||
security_cnt=args.security_counter,
|
||||
included_header=args.included_header,
|
||||
pad=pad_size)
|
||||
key = keys.load(args.key, args.public_key_format) if args.key else None
|
||||
ram_load_address = find_load_address(args)
|
||||
img.sign(sw_type, key, ram_load_address, args.dependencies)
|
||||
|
||||
if pad_size:
|
||||
img.pad_to(pad_size, args.align)
|
||||
|
||||
img.save(args.outfile)
|
||||
|
||||
|
@ -122,6 +152,30 @@ subcmds = {
|
|||
'getpub': do_getpub,
|
||||
'sign': do_sign, }
|
||||
|
||||
|
||||
def get_dependencies(text):
|
||||
if text is not None:
|
||||
versions = []
|
||||
images = re.findall(r"\((\d+)", text)
|
||||
if len(images) == 0:
|
||||
msg = "Image dependency format is invalid: {}".format(text)
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
raw_versions = re.findall(r",\s*([0-9.+]+)\)", text)
|
||||
if len(images) != len(raw_versions):
|
||||
msg = '''There's a mismatch between the number of dependency images
|
||||
and versions in: {}'''.format(text)
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
for raw_version in raw_versions:
|
||||
try:
|
||||
versions.append(version.decode_version(raw_version))
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
dependencies = dict()
|
||||
dependencies[image.DEP_IMAGES_KEY] = images
|
||||
dependencies[image.DEP_VERSIONS_KEY] = versions
|
||||
return dependencies
|
||||
|
||||
|
||||
def alignment_value(text):
|
||||
value = int(text)
|
||||
if value not in [1, 2, 4, 8]:
|
||||
|
@ -149,17 +203,23 @@ def args():
|
|||
getpub.add_argument('-l', '--lang', metavar='lang', default='c')
|
||||
|
||||
sign = subs.add_parser('sign', help='Sign an image with a private key')
|
||||
sign.add_argument('--layout', required=True,
|
||||
help='Location of the memory layout file')
|
||||
sign.add_argument('-l', '--layout', required=True,
|
||||
help='Location of the file that contains preprocessed macros')
|
||||
sign.add_argument('-k', '--key', metavar='filename')
|
||||
sign.add_argument("-K", "--public-key-format",
|
||||
help='In what format to add the public key to the image manifest: full or hash',
|
||||
metavar='pub_key_format', choices=['full', 'hash'], default='hash')
|
||||
sign.add_argument("--align", type=alignment_value, required=True)
|
||||
sign.add_argument("-v", "--version", type=version.decode_version,
|
||||
default="0.0.0+0")
|
||||
sign.add_argument("-d", "--dependencies", type=get_dependencies,
|
||||
required=False, help='''Add dependence on another image,
|
||||
format: "(<image_ID>,<image_version>), ... "''')
|
||||
sign.add_argument("-s", "--security-counter", type=intparse,
|
||||
help='Specify explicitly the security counter value')
|
||||
sign.add_argument("-H", "--header-size", type=intparse, required=True)
|
||||
sign.add_argument("--included-header", default=False, action='store_true',
|
||||
help='Image has gap for header')
|
||||
sign.add_argument("--pad", type=intparse,
|
||||
help='Pad image to this many bytes, adding trailer magic')
|
||||
sign.add_argument("--rsa-pkcs1-15",
|
||||
help='Use old PKCS#1 v1.5 signature algorithm',
|
||||
default=False, action='store_true')
|
||||
|
@ -174,4 +234,4 @@ def args():
|
|||
subcmds[args.subcmd](args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
args()
|
||||
args()
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
# Copyright (c) 2019, Arm Limited.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import cbor
|
||||
|
||||
|
||||
# SW component IDs
|
||||
SW_COMPONENT_RANGE = 0
|
||||
SW_COMPONENT_TYPE = SW_COMPONENT_RANGE + 1
|
||||
MEASUREMENT_VALUE = SW_COMPONENT_RANGE + 2
|
||||
SW_COMPONENT_VERSION = SW_COMPONENT_RANGE + 4
|
||||
SIGNER_ID = SW_COMPONENT_RANGE + 5
|
||||
MEASUREMENT_DESCRIPTION = SW_COMPONENT_RANGE + 6
|
||||
|
||||
|
||||
def create_sw_component_data(sw_type, sw_version, sw_measurement_type,
|
||||
sw_measurement_value, sw_signer_id):
|
||||
|
||||
# List of SW component claims (key ID + value)
|
||||
key_value_list = [
|
||||
SW_COMPONENT_TYPE, sw_type,
|
||||
SW_COMPONENT_VERSION, sw_version,
|
||||
SIGNER_ID, sw_signer_id,
|
||||
MEASUREMENT_DESCRIPTION, sw_measurement_type,
|
||||
MEASUREMENT_VALUE, sw_measurement_value
|
||||
]
|
||||
# The measurement value should be the last item (key + value) in the list
|
||||
# to make it easier to modify its value later in the bootloader.
|
||||
# A dictionary would be the best suited data structure to store these
|
||||
# key-value pairs (claims), however dictionaries are not sorted, but for
|
||||
# example the lists do keep to order of items which we care about now.
|
||||
# An ordered dictionary could be used instead, but it would be converted
|
||||
# to a dict before the encoding and this conversion may not keep the order
|
||||
# of the items.
|
||||
|
||||
if (len(key_value_list) % 2) != 0:
|
||||
print('Error: The length of the sw component claim list must '
|
||||
'be even (key + value).', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
claim_number = (int)(len(key_value_list) / 2)
|
||||
|
||||
# The output of this function must be a CBOR encoded map (dictionary) of
|
||||
# the SW component claims. The CBOR representation of an array and a map
|
||||
# (dictionary) is quite similar. To convert the encoded list to a map, it
|
||||
# is enough to modify the first byte (CBOR data item header) of the
|
||||
# data. This applies up to 23 items (11 claims in this case) - until the 5
|
||||
# lower bits of the item header are used as an item count specifier.
|
||||
|
||||
if claim_number > 11:
|
||||
print('Error: There are more than 11 claims in the '
|
||||
'list of sw component claims.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
record_array = bytearray(cbor.dumps(key_value_list))
|
||||
# Modify the CBOR data item header (from array to map)
|
||||
# 7..5 bits : Major type
|
||||
# Array - 0x80
|
||||
# Map - 0xA0
|
||||
# 4..0 bits : Number of items
|
||||
record_array[0] = 0xA0 + claim_number
|
||||
|
||||
return bytes(record_array)
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright 2017 Linaro Limited
|
||||
# Copyright (c) 2018, Arm Limited.
|
||||
# Copyright (c) 2018-2019, Arm Limited.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -18,11 +18,17 @@ Image signing and management.
|
|||
"""
|
||||
|
||||
from . import version as versmod
|
||||
from . import boot_record as br
|
||||
import hashlib
|
||||
import struct
|
||||
|
||||
IMAGE_MAGIC = 0x96f3b83d
|
||||
IMAGE_HEADER_SIZE = 32
|
||||
TLV_HEADER_SIZE = 4
|
||||
PAYLOAD_DIGEST_SIZE = 32 # SHA256 hash
|
||||
KEYHASH_SIZE = 32
|
||||
DEP_IMAGES_KEY = "images"
|
||||
DEP_VERSIONS_KEY = "versions"
|
||||
|
||||
# Image header flags.
|
||||
IMAGE_F = {
|
||||
|
@ -31,11 +37,17 @@ IMAGE_F = {
|
|||
'RAM_LOAD': 0x0000020, }
|
||||
TLV_VALUES = {
|
||||
'KEYHASH': 0x01,
|
||||
'KEY' : 0x02,
|
||||
'SHA256' : 0x10,
|
||||
'RSA2048': 0x20, }
|
||||
'RSA2048': 0x20,
|
||||
'RSA3072': 0x23,
|
||||
'DEPENDENCY': 0x40,
|
||||
'SEC_CNT': 0x50,
|
||||
'BOOT_RECORD': 0x60, }
|
||||
|
||||
TLV_INFO_SIZE = 4
|
||||
TLV_INFO_MAGIC = 0x6907
|
||||
TLV_PROT_INFO_MAGIC = 0x6908
|
||||
|
||||
# Sizes of the image trailer, depending on flash write size.
|
||||
trailer_sizes = {
|
||||
|
@ -50,17 +62,25 @@ boot_magic = bytearray([
|
|||
0x2c, 0xb6, 0x79, 0x80, ])
|
||||
|
||||
class TLV():
|
||||
def __init__(self):
|
||||
def __init__(self, magic=TLV_INFO_MAGIC):
|
||||
self.magic = magic
|
||||
self.buf = bytearray()
|
||||
|
||||
def __len__(self):
|
||||
return TLV_INFO_SIZE + len(self.buf)
|
||||
|
||||
def add(self, kind, payload):
|
||||
"""Add a TLV record. Kind should be a string found in TLV_VALUES above."""
|
||||
"""
|
||||
Add a TLV record. Kind should be a string found in TLV_VALUES above.
|
||||
"""
|
||||
buf = struct.pack('<BBH', TLV_VALUES[kind], 0, len(payload))
|
||||
self.buf += buf
|
||||
self.buf += payload
|
||||
|
||||
def get(self):
|
||||
header = struct.pack('<HH', TLV_INFO_MAGIC, TLV_INFO_SIZE + len(self.buf))
|
||||
if len(self.buf) == 0:
|
||||
return bytes()
|
||||
header = struct.pack('<HH', self.magic, len(self))
|
||||
return header + bytes(self.buf)
|
||||
|
||||
class Image():
|
||||
|
@ -79,15 +99,19 @@ class Image():
|
|||
obj.check()
|
||||
return obj
|
||||
|
||||
def __init__(self, version, header_size=IMAGE_HEADER_SIZE, pad=0):
|
||||
def __init__(self, version, header_size=IMAGE_HEADER_SIZE, security_cnt=0,
|
||||
pad=0):
|
||||
self.version = version
|
||||
self.header_size = header_size or IMAGE_HEADER_SIZE
|
||||
self.security_cnt = security_cnt
|
||||
self.pad = pad
|
||||
|
||||
def __repr__(self):
|
||||
return "<Image version={}, header_size={}, pad={}, payloadlen=0x{:x}>".format(
|
||||
return "<Image version={}, header_size={}, security_counter={}, \
|
||||
pad={}, payloadlen=0x{:x}>".format(
|
||||
self.version,
|
||||
self.header_size,
|
||||
self.security_cnt,
|
||||
self.pad,
|
||||
len(self.payload))
|
||||
|
||||
|
@ -103,30 +127,94 @@ class Image():
|
|||
if any(v != 0 and v != b'\000' for v in self.payload[0:self.header_size]):
|
||||
raise Exception("Padding requested, but image does not start with zeros")
|
||||
|
||||
def sign(self, key, ramLoadAddress):
|
||||
self.add_header(key, ramLoadAddress)
|
||||
|
||||
tlv = TLV()
|
||||
|
||||
sha = hashlib.sha256()
|
||||
sha.update(self.payload)
|
||||
digest = sha.digest()
|
||||
|
||||
tlv.add('SHA256', digest)
|
||||
def sign(self, sw_type, key, ramLoadAddress, dependencies=None):
|
||||
image_version = (str(self.version.major) + '.'
|
||||
+ str(self.version.minor) + '.'
|
||||
+ str(self.version.revision))
|
||||
|
||||
# Calculate the hash of the public key
|
||||
if key is not None:
|
||||
pub = key.get_public_bytes()
|
||||
sha = hashlib.sha256()
|
||||
sha.update(pub)
|
||||
pubbytes = sha.digest()
|
||||
tlv.add('KEYHASH', pubbytes)
|
||||
else:
|
||||
pubbytes = bytes(KEYHASH_SIZE)
|
||||
|
||||
sig = key.sign(self.payload)
|
||||
# The image hash is computed over the image header, the image itself
|
||||
# and the protected TLV area. However, the boot record TLV (which is
|
||||
# part of the protected area) should contain this hash before it is
|
||||
# even calculated. For this reason the script fills this field with
|
||||
# zeros and the bootloader will insert the right value later.
|
||||
image_hash = bytes(PAYLOAD_DIGEST_SIZE)
|
||||
|
||||
# Create CBOR encoded boot record
|
||||
boot_record = br.create_sw_component_data(sw_type, image_version,
|
||||
"SHA256", image_hash,
|
||||
pubbytes)
|
||||
|
||||
# Mandatory protected TLV area: TLV info header
|
||||
# + security counter TLV
|
||||
# + boot record TLV
|
||||
# Size of the security counter TLV: header ('BBH') + payload ('I')
|
||||
# = 8 Bytes
|
||||
protected_tlv_size = TLV_INFO_SIZE + 8 + TLV_HEADER_SIZE \
|
||||
+ len(boot_record)
|
||||
|
||||
if dependencies is None:
|
||||
dependencies_num = 0
|
||||
else:
|
||||
# Size of a dependency TLV:
|
||||
# header ('BBH') + payload('IBBHI') = 16 Bytes
|
||||
dependencies_num = len(dependencies[DEP_IMAGES_KEY])
|
||||
protected_tlv_size += (dependencies_num * 16)
|
||||
|
||||
# At this point the image is already on the payload, this adds
|
||||
# the header to the payload as well
|
||||
self.add_header(key, protected_tlv_size, ramLoadAddress)
|
||||
|
||||
prot_tlv = TLV(TLV_PROT_INFO_MAGIC)
|
||||
|
||||
# Protected TLVs must be added first, because they are also included
|
||||
# in the hash calculation
|
||||
payload = struct.pack('I', self.security_cnt)
|
||||
prot_tlv.add('SEC_CNT', payload)
|
||||
prot_tlv.add('BOOT_RECORD', boot_record)
|
||||
|
||||
if dependencies_num != 0:
|
||||
for i in range(dependencies_num):
|
||||
payload = struct.pack(
|
||||
'<'+'B3x'+'BBHI',
|
||||
int(dependencies[DEP_IMAGES_KEY][i]),
|
||||
dependencies[DEP_VERSIONS_KEY][i].major,
|
||||
dependencies[DEP_VERSIONS_KEY][i].minor,
|
||||
dependencies[DEP_VERSIONS_KEY][i].revision,
|
||||
dependencies[DEP_VERSIONS_KEY][i].build
|
||||
)
|
||||
prot_tlv.add('DEPENDENCY', payload)
|
||||
|
||||
self.payload += prot_tlv.get()
|
||||
|
||||
sha = hashlib.sha256()
|
||||
sha.update(self.payload)
|
||||
image_hash = sha.digest()
|
||||
|
||||
tlv = TLV()
|
||||
|
||||
tlv.add('SHA256', image_hash)
|
||||
|
||||
if key is not None:
|
||||
if key.get_public_key_format() == 'hash':
|
||||
tlv.add('KEYHASH', pubbytes)
|
||||
else:
|
||||
tlv.add('KEY', pub)
|
||||
|
||||
sig = key.sign(bytes(self.payload))
|
||||
tlv.add(key.sig_tlv(), sig)
|
||||
|
||||
self.payload += tlv.get()
|
||||
|
||||
def add_header(self, key, ramLoadAddress):
|
||||
def add_header(self, key, protected_tlv_size, ramLoadAddress):
|
||||
"""Install the image header.
|
||||
|
||||
The key is needed to know the type of signature, and
|
||||
|
@ -140,28 +228,28 @@ class Image():
|
|||
|
||||
fmt = ('<' +
|
||||
# type ImageHdr struct {
|
||||
'I' + # Magic uint32
|
||||
'I' + # LoadAddr uint32
|
||||
'H' + # HdrSz uint16
|
||||
'H' + # Pad1 uint16
|
||||
'I' + # ImgSz uint32
|
||||
'I' + # Flags uint32
|
||||
'BBHI' + # Vers ImageVersion
|
||||
'I' # Pad2 uint32
|
||||
'I' + # Magic uint32
|
||||
'I' + # LoadAddr uint32
|
||||
'H' + # HdrSz uint16
|
||||
'H' + # PTLVSz uint16
|
||||
'I' + # ImgSz uint32
|
||||
'I' + # Flags uint32
|
||||
'BBHI' + # Vers ImageVersion
|
||||
'I' # Pad1 uint32
|
||||
) # }
|
||||
assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
|
||||
header = struct.pack(fmt,
|
||||
IMAGE_MAGIC,
|
||||
0 if (ramLoadAddress is None) else ramLoadAddress, # LoadAddr
|
||||
self.header_size,
|
||||
0, # Pad1
|
||||
len(self.payload) - self.header_size, # ImageSz
|
||||
flags, # Flags
|
||||
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) # Pad2
|
||||
0) # Pad1
|
||||
self.payload = bytearray(self.payload)
|
||||
self.payload[:len(header)] = header
|
||||
|
||||
|
@ -176,4 +264,4 @@ class Image():
|
|||
pbytes = b'\xff' * padding
|
||||
pbytes += b'\xff' * (tsize - len(boot_magic))
|
||||
pbytes += boot_magic
|
||||
self.payload += pbytes
|
||||
self.payload += pbytes
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright 2017 Linaro Limited
|
||||
# Copyright (c) 2017-2018, Arm Limited.
|
||||
# Copyright (c) 2017,2019 Linaro Limited.
|
||||
# Copyright (c) 2017-2019, Arm Limited.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -18,42 +18,64 @@ Cryptographic key management for imgtool.
|
|||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.hashes import SHA256
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15
|
||||
from cryptography.hazmat.primitives.asymmetric.padding import MGF1
|
||||
import hashlib
|
||||
from pyasn1.type import namedtype, univ
|
||||
from pyasn1.codec.der.encoder import encode
|
||||
|
||||
# Sizes that bootutil will recognize
|
||||
RSA_KEY_SIZES = [2048, 3072]
|
||||
|
||||
# Public exponent
|
||||
PUBLIC_EXPONENT = 65537
|
||||
|
||||
# By default, we use RSA-PSS (PKCS 2.1). That can be overridden on
|
||||
# the command line to support the older (less secure) PKCS1.5
|
||||
sign_rsa_pss = True
|
||||
|
||||
AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
|
||||
|
||||
class RSAPublicKey(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('modulus', univ.Integer()),
|
||||
namedtype.NamedType('publicExponent', univ.Integer()))
|
||||
class RSAUsageError(Exception):
|
||||
pass
|
||||
|
||||
class RSA2048():
|
||||
def __init__(self, key):
|
||||
"""Construct an RSA2048 key with the given key data"""
|
||||
class RSAutil():
|
||||
def __init__(self, key, public_key_format='hash'):
|
||||
"""Construct an RSA key with the given key data"""
|
||||
self.key = key
|
||||
self.public_key_format = public_key_format
|
||||
|
||||
def key_size(self):
|
||||
return self.key.key_size
|
||||
|
||||
def get_public_key_format(self):
|
||||
return self.public_key_format
|
||||
|
||||
@staticmethod
|
||||
def generate():
|
||||
return RSA2048(RSA.generate(2048))
|
||||
def generate(key_size=2048):
|
||||
if key_size not in RSA_KEY_SIZES:
|
||||
raise RSAUsageError("Key size {} is not supported by MCUboot"
|
||||
.format(key_size))
|
||||
return RSAutil(rsa.generate_private_key(
|
||||
public_exponent=PUBLIC_EXPONENT,
|
||||
key_size=key_size,
|
||||
backend=default_backend()))
|
||||
|
||||
def export_private(self, path):
|
||||
with open(path, 'wb') as f:
|
||||
f.write(self.key.exportKey('PEM'))
|
||||
f.write(self.key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()))
|
||||
|
||||
def get_public_bytes(self):
|
||||
node = RSAPublicKey()
|
||||
node['modulus'] = self.key.n
|
||||
node['publicExponent'] = self.key.e
|
||||
return bytearray(encode(node))
|
||||
return self.key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.PKCS1)
|
||||
|
||||
def emit_c(self):
|
||||
print(AUTOGEN_MESSAGE)
|
||||
|
@ -71,34 +93,44 @@ class RSA2048():
|
|||
def sig_type(self):
|
||||
"""Return the type of this signature (as a string)"""
|
||||
if sign_rsa_pss:
|
||||
return "PKCS1_PSS_RSA2048_SHA256"
|
||||
return "PKCS1_PSS_RSA{}_SHA256".format(self.key_size())
|
||||
else:
|
||||
return "PKCS15_RSA2048_SHA256"
|
||||
return "PKCS15_RSA{}_SHA256".format(self.key_size())
|
||||
|
||||
def sig_len(self):
|
||||
return 256
|
||||
return 256 if self.key_size() == 2048 else 384
|
||||
|
||||
def sig_tlv(self):
|
||||
return "RSA2048"
|
||||
return "RSA2048" if self.key_size() == 2048 else "RSA3072"
|
||||
|
||||
def sign(self, payload):
|
||||
converted_payload = bytes(payload)
|
||||
sha = SHA256.new(converted_payload)
|
||||
if sign_rsa_pss:
|
||||
signer = PKCS1_PSS.new(self.key)
|
||||
signature = self.key.sign(
|
||||
data=payload,
|
||||
padding=PSS(
|
||||
mgf=MGF1(SHA256()),
|
||||
salt_length=32
|
||||
),
|
||||
algorithm=SHA256()
|
||||
)
|
||||
else:
|
||||
signer = PKCS1_v1_5.new(self.key)
|
||||
signature = signer.sign(sha)
|
||||
signature = self.key.sign(
|
||||
data=payload,
|
||||
padding=PKCS1v15(),
|
||||
algorithm=SHA256()
|
||||
)
|
||||
assert len(signature) == self.sig_len()
|
||||
return signature
|
||||
|
||||
def load(path):
|
||||
def load(path, public_key_format='hash'):
|
||||
with open(path, 'rb') as f:
|
||||
pem = f.read()
|
||||
try:
|
||||
key = RSA.importKey(pem)
|
||||
if key.n.bit_length() != 2048:
|
||||
raise Exception("Unsupported RSA bit length, only 2048 supported")
|
||||
return RSA2048(key)
|
||||
key = serialization.load_pem_private_key(
|
||||
pem,
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
return RSAutil(key, public_key_format)
|
||||
except ValueError:
|
||||
raise Exception("Unsupported RSA key file")
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
#! /usr/bin/env python3
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copyright (c) 2019, Arm Limited. All rights reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
expression_re = re.compile(r"[(]?(([(]?(((0x)[0-9a-fA-F]+)|([0-9]+))[)]?)\s*([\+\-]\s*([(]?(((0x)[0-9a-fA-F]+)|([0-9]+))[)]?)\s*)*)[)]?")
|
||||
|
||||
# Simple parser that takes a string and evaluates an expression from it.
|
||||
# The expression might contain additions and subtractions amongst numbers that
|
||||
# are written in decimal or hexadecimal form.
|
||||
# The parses can process expressions in which the parentheses does not change
|
||||
# the sign of the following number or numbers in an expression.
|
||||
# Thus the parser can process the following expression: (x + y)
|
||||
# However it will not calculate the correct sum for the expression below:
|
||||
# (x - (y + z))
|
||||
def parse_and_sum(text):
|
||||
m = expression_re.match(text)
|
||||
if m is None:
|
||||
msg = "The script was probably invoked manually"
|
||||
msg += " with having certain macros nested in flash_layouts.h.\n"
|
||||
msg += "Please revisit the flash_layout.h file and hardcode values"
|
||||
msg += " for the (NON-)SECURE_IMAGE_OFFSET and"
|
||||
msg += " (NON-)SECURE_IMAGE_MAX_SIZE macros"
|
||||
raise Exception(msg)
|
||||
|
||||
nums = re.findall(r'(0x[A-Fa-f0-9]+)|[\d]+', m.group(0))
|
||||
for i in range(len(nums)):
|
||||
nums[i] = int(nums[i], 0)
|
||||
ops = re.findall(r'\+|\-', m.group(0))
|
||||
sum = nums[0]
|
||||
for i in range(len(ops)):
|
||||
if ops[i] == '+':
|
||||
sum += nums[i+1]
|
||||
else:
|
||||
sum -= nums[i+1]
|
||||
return sum
|
||||
|
||||
|
||||
# Opens a file that contains the macro of interest, then finds the macro with
|
||||
# a regular expression, parses the expression that is defined for the given
|
||||
# macro. Lastly it evaluates the expression with the parse_and_sum function
|
||||
def evaluate_macro(file, regexp, matchGroupKey, matchGroupData):
|
||||
regexp_compiled = re.compile(regexp)
|
||||
|
||||
if os.path.isabs(file):
|
||||
configFile = file
|
||||
else:
|
||||
scriptsDir = os.path.dirname(os.path.abspath(__file__))
|
||||
configFile = os.path.join(scriptsDir, file)
|
||||
|
||||
macroValue = {}
|
||||
with open(configFile, 'r') as macros_preprocessed_file:
|
||||
for line in macros_preprocessed_file:
|
||||
m = regexp_compiled.match(line)
|
||||
if m is not None:
|
||||
macroValue[m.group(matchGroupKey)] = \
|
||||
parse_and_sum(m.group(matchGroupData))
|
||||
|
||||
if (matchGroupKey == 0 and not macroValue):
|
||||
macroValue["None"] = None
|
||||
|
||||
return list(macroValue.values())[0] if (matchGroupKey == 0) else macroValue
|
|
@ -54,11 +54,14 @@ def musca_tfm_bin(t_self, non_secure_bin, secure_bin):
|
|||
#2. Run imgtool to sign the concatenated binary
|
||||
sign_args = Namespace(
|
||||
layout=flash_layout,
|
||||
key=path_join(SCRIPT_DIR, 'musca_a1-root-rsa-2048.pem'),
|
||||
key=path_join(SCRIPT_DIR, 'musca_a1-root-rsa-3072.pem'),
|
||||
public_key_format=None,
|
||||
align=1,
|
||||
dependencies=None,
|
||||
version=version.decode_version('1.0'),
|
||||
header_size=0x400,
|
||||
pad=0x100000,
|
||||
security_counter=None,
|
||||
rsa_pkcs1_15=False,
|
||||
included_header=False,
|
||||
infile=concatenated_bin,
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA0QYIGhhELBjo+/33DaNPH7vuXvmq0ksY01rpbRiAGfnwnDQb
|
||||
y/O8dNtC54x/EFN+Q14NVyxE0WcIDw27XO7ss5nf4E2EC6p3QWDtFShJpwG0PBDm
|
||||
aYwvX6xBTZ5cFN/y+M89Hm/nW7q0qciIfkc8lMN3Z1RLqo04NcpiYX634RXbd3PU
|
||||
vntyIYlpJPv4ZW5kPsgO14XVXErkUw0v/7f98xM5gz+jrtIPp2qd+f64zvoqvq+4
|
||||
4PqCN1T0PuEr0NMIWBj2XkzIiIExrV+wghfyimknI/Orhz6TGh3+6PgaJGZZ+Byr
|
||||
3M5oG2ZkNez6DRGdr1w6p9FnxkfvsUssYuHRyQIDAQABAoIBAEahFCHFK1v/OtLT
|
||||
eSSZl0Xw2dYr5QXULFpWsOOVUMv2QdB2ZyIehQKziEL3nYPlwpd+82EOa16awwVb
|
||||
LYF0lnUFvLltV/4dJtjnqJTqnSCamc1mJIVrwiJA8XwJ07GWDuL2G//p7jJ3v05T
|
||||
nZOV/KmD9xfqSvshZun+LgolqHqcrAa1f4cmuP9C9oqenZryljyfj7piaIZGI0JR
|
||||
PrJJ5kImYJqRcMgKTyHP4L8nwQ4moMJr6zbfbWxxb5TC7KVZSQ9UKZZ+ZLuy/pkU
|
||||
Qe4G8XSE0r+R9u4JCg87I1vgHhn8WJSxVX027OVUq5HfOzg2skQBTcExph5V9B2b
|
||||
onNxd8UCgYEA/32PW+ZwRcdKXMj+QVkxXUd6xkXy7mTXPEaQuOLWZQgrSqAFH1l4
|
||||
5/6d099KAJrjM6kR8pKXtz72IIyMHTPm332ghymjKvaEl2XP9sF+f6FmYURar4y6
|
||||
8Zh3eivP86+Q/YzOGKwtRSziBMzrAfoIXgtwH8pwIPYLP3zBV4449ZsCgYEA0XC/
|
||||
gu2ub5M6EXBnjq9K2d4LlTyAPsIbAcMSwkhOUH4YJFS22qXLYQUA9zM+DUyLCrl/
|
||||
PKN2G0HQVgMb4DIbeHv8kXB5oGm5zfbWorWqOomXB3AsI7X8YDMtf/PsZV2amBei
|
||||
qVskmPJQV21qFyeOcHlT+dHuRb0O0un3dK8RHmsCgYEApDCH4dJ80osZoflVVJ/C
|
||||
VqTqJOOtFEFgBQ+AUCEPEQyn7aRaxmPUjJsXyKJVx3/ChV+g9hf5Qj1HJXHNVbMW
|
||||
KwhsEpDSmHimizlV5clBxzntNpMcCHdTaJHILo5bbMqmThugE0ELMsp+UgFzAeky
|
||||
WWXWX8fUOYqFff5prh/rQQMCgYBQQ8FhT+113Rp37HgDerJY5HvT6afMZV8sQbJC
|
||||
uqsotepSohShnsBeoihIlF7HgfoXVhepCYwNzh8ll3NrbEiS2BFnO4+hJmOKx3pi
|
||||
SPTAElLLCvYfiXL6+yII01ZZUpIYj5ZLCR7xbovTtZ7e2M4B1L2WFBoYp+eydO/c
|
||||
y+rnmQKBgCh0gfyBT/OKPkfKv+Bbt8HcoxgEj+TyH+vYdeTbP9ZSJ6y5utMbPg7z
|
||||
iLLbJ+9IcSwPCwJSmI+I0Om4xEp4ZblCrzAG7hWvG2NNzxQjmoOOrAANyTvJR/ap
|
||||
N+UkQA4WrMSKEYyBlRS/hR9Unz31vMc2k9Re0ukWhWh/QksQGDfJ
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
A default RSA key pair is given to the Musca-A1 target.
|
||||
|
||||
Public key was pre-compiled to `targets/TARGET_ARM_SSG/TARGET_MUSCA_A1/TARGET_MUSCA_A1_NS/device/mcuboot.bin` and private key is in `musca_a1-root-rsa-2048.pem`.
|
||||
Public key was pre-compiled to `targets/TARGET_ARM_SSG/TARGET_MUSCA_A1/TARGET_MUSCA_A1_NS/device/mcuboot.bin` and private key is in `root-rsa-3072.pem`.
|
||||
|
||||
DO NOT use them in production code, they are exclusively for testing!
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
Loading…
Reference in New Issue