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
Devaraj Ranganna 2020-01-21 10:25:56 +00:00
parent a81f016abc
commit bc7331b96e
12 changed files with 467 additions and 134 deletions

View File

@ -10,7 +10,7 @@ These images were compiled by the following command:
### Repository ### Repository
https://git.trustedfirmware.org/trusted-firmware-m.git https://git.trustedfirmware.org/trusted-firmware-m.git
### Commit SHA ### Commit SHA
8da7f102a6a6a1a99462f7f32edbd1565096c2f3 6c5be4a98e4d7055ee49076ca4e515fb4b172e66
```sh ```sh
cmake ../ -G"Unix Makefiles" -DTARGET_PLATFORM=MUSCA_A -DCOMPILER=ARMCLANG -DCMAKE_BUILD_TYPE=Debug cmake ../ -G"Unix Makefiles" -DTARGET_PLATFORM=MUSCA_A -DCOMPILER=ARMCLANG -DCMAKE_BUILD_TYPE=Debug
make make

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# #
# Copyright 2017 Linaro Limited # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -25,6 +25,7 @@ import io
import re import re
import os import os
import shutil import shutil
from . import macro_parser
offset_re = re.compile(r"^#define ([0-9A-Z_]+)_IMAGE_OFFSET\s+((0x)?[0-9a-fA-F]+)") 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]+)") 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 = {} offsets = {}
sizes = {} sizes = {}
if os.path.isabs(self.layout_path): offsets = macro_parser.evaluate_macro(self.layout_path, offset_re, 1, 2)
configFile = self.layout_path sizes = macro_parser.evaluate_macro(self.layout_path, size_re, 1, 2)
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)
if 'SECURE' not in offsets: if 'SECURE' not in offsets:
raise Exception("Image config does not have secure partition") raise Exception("Image config does not have secure partition")
@ -86,7 +75,7 @@ def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-l', '--layout', required=True, 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, parser.add_argument('-s', '--secure', required=True,
help='Unsigned secure image') help='Unsigned secure image')
parser.add_argument('-n', '--non_secure', parser.add_argument('-n', '--non_secure',
@ -97,7 +86,6 @@ def main():
args = parser.parse_args() args = parser.parse_args()
output = Assembly(args.layout, args.output) output = Assembly(args.layout, args.output)
output.add_image(args.secure, "SECURE") output.add_image(args.secure, "SECURE")
output.add_image(args.non_secure, "NON_SECURE") output.add_image(args.non_secure, "NON_SECURE")

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# #
# Copyright 2017 Linaro Limited # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 from .imgtool_lib import version
import sys 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): def find_load_address(args):
load_address_re = re.compile(r"^#define\sIMAGE_LOAD_ADDRESS\s+(0x[0-9a-fA-F]+)") 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): def next_version_number(args, defaultVersion, path):
newVersion = None newVersion = None
versionProvided = False
if (version.compare(args.version, defaultVersion) == 0): # Default version if (version.compare(args.version, defaultVersion) == 0): # Default version
lastVersion = get_last_version(path) lastVersion = get_last_version(path)
if (lastVersion is not None): if (lastVersion is not None):
@ -67,6 +71,7 @@ def next_version_number(args, defaultVersion, path):
else: else:
newVersion = version.increment_build_num(defaultVersion) newVersion = version.increment_build_num(defaultVersion)
else: # Version number has been explicitly provided (not using the default) else: # Version number has been explicitly provided (not using the default)
versionProvided = True
newVersion = args.version newVersion = args.version
versionString = "{a}.{b}.{c}+{d}".format( versionString = "{a}.{b}.{c}+{d}".format(
a=str(newVersion.major), a=str(newVersion.major),
@ -74,16 +79,21 @@ def next_version_number(args, defaultVersion, path):
c=str(newVersion.revision), c=str(newVersion.revision),
d=str(newVersion.build) d=str(newVersion.build)
) )
with open(path, "w") as newFile: if not versionProvided:
newFile.write(versionString) with open(path, "w") as newFile:
newFile.write(versionString)
print("**[INFO]** Image version number set to " + versionString) print("**[INFO]** Image version number set to " + versionString)
return newVersion return newVersion
def gen_rsa2048(args): 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 = { keygens = {
'rsa-2048': gen_rsa2048, } 'rsa-2048': gen_rsa2048,
'rsa-3072': gen_rsa3072, }
def do_keygen(args): def do_keygen(args):
if args.type not in keygens: if args.type not in keygens:
@ -102,18 +112,38 @@ def do_getpub(args):
def do_sign(args): def do_sign(args):
if args.rsa_pkcs1_15: if args.rsa_pkcs1_15:
keys.sign_rsa_pss = False 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: version_num = next_version_number(args,
img.pad_to(args.pad, args.align) 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) img.save(args.outfile)
@ -122,6 +152,30 @@ subcmds = {
'getpub': do_getpub, 'getpub': do_getpub,
'sign': do_sign, } '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): def alignment_value(text):
value = int(text) value = int(text)
if value not in [1, 2, 4, 8]: if value not in [1, 2, 4, 8]:
@ -149,17 +203,23 @@ def args():
getpub.add_argument('-l', '--lang', metavar='lang', default='c') getpub.add_argument('-l', '--lang', metavar='lang', default='c')
sign = subs.add_parser('sign', help='Sign an image with a private key') sign = subs.add_parser('sign', help='Sign an image with a private key')
sign.add_argument('--layout', required=True, sign.add_argument('-l', '--layout', required=True,
help='Location of the memory layout file') help='Location of the file that contains preprocessed macros')
sign.add_argument('-k', '--key', metavar='filename') 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("--align", type=alignment_value, required=True)
sign.add_argument("-v", "--version", type=version.decode_version, sign.add_argument("-v", "--version", type=version.decode_version,
default="0.0.0+0") 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("-H", "--header-size", type=intparse, required=True)
sign.add_argument("--included-header", default=False, action='store_true', sign.add_argument("--included-header", default=False, action='store_true',
help='Image has gap for header') 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", sign.add_argument("--rsa-pkcs1-15",
help='Use old PKCS#1 v1.5 signature algorithm', help='Use old PKCS#1 v1.5 signature algorithm',
default=False, action='store_true') default=False, action='store_true')
@ -174,4 +234,4 @@ def args():
subcmds[args.subcmd](args) subcmds[args.subcmd](args)
if __name__ == '__main__': if __name__ == '__main__':
args() args()

View File

@ -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)

View File

@ -1,5 +1,5 @@
# Copyright 2017 Linaro Limited # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 version as versmod
from . import boot_record as br
import hashlib import hashlib
import struct import struct
IMAGE_MAGIC = 0x96f3b83d IMAGE_MAGIC = 0x96f3b83d
IMAGE_HEADER_SIZE = 32 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 header flags.
IMAGE_F = { IMAGE_F = {
@ -31,11 +37,17 @@ IMAGE_F = {
'RAM_LOAD': 0x0000020, } 'RAM_LOAD': 0x0000020, }
TLV_VALUES = { TLV_VALUES = {
'KEYHASH': 0x01, 'KEYHASH': 0x01,
'KEY' : 0x02,
'SHA256' : 0x10, 'SHA256' : 0x10,
'RSA2048': 0x20, } 'RSA2048': 0x20,
'RSA3072': 0x23,
'DEPENDENCY': 0x40,
'SEC_CNT': 0x50,
'BOOT_RECORD': 0x60, }
TLV_INFO_SIZE = 4 TLV_INFO_SIZE = 4
TLV_INFO_MAGIC = 0x6907 TLV_INFO_MAGIC = 0x6907
TLV_PROT_INFO_MAGIC = 0x6908
# Sizes of the image trailer, depending on flash write size. # Sizes of the image trailer, depending on flash write size.
trailer_sizes = { trailer_sizes = {
@ -50,17 +62,25 @@ boot_magic = bytearray([
0x2c, 0xb6, 0x79, 0x80, ]) 0x2c, 0xb6, 0x79, 0x80, ])
class TLV(): class TLV():
def __init__(self): def __init__(self, magic=TLV_INFO_MAGIC):
self.magic = magic
self.buf = bytearray() self.buf = bytearray()
def __len__(self):
return TLV_INFO_SIZE + len(self.buf)
def add(self, kind, payload): 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)) buf = struct.pack('<BBH', TLV_VALUES[kind], 0, len(payload))
self.buf += buf self.buf += buf
self.buf += payload self.buf += payload
def get(self): 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) return header + bytes(self.buf)
class Image(): class Image():
@ -79,15 +99,19 @@ class Image():
obj.check() obj.check()
return obj 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.version = version
self.header_size = header_size or IMAGE_HEADER_SIZE self.header_size = header_size or IMAGE_HEADER_SIZE
self.security_cnt = security_cnt
self.pad = pad self.pad = pad
def __repr__(self): 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.version,
self.header_size, self.header_size,
self.security_cnt,
self.pad, self.pad,
len(self.payload)) 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]): 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") raise Exception("Padding requested, but image does not start with zeros")
def sign(self, key, ramLoadAddress): def sign(self, sw_type, key, ramLoadAddress, dependencies=None):
self.add_header(key, ramLoadAddress) image_version = (str(self.version.major) + '.'
+ str(self.version.minor) + '.'
tlv = TLV() + str(self.version.revision))
sha = hashlib.sha256()
sha.update(self.payload)
digest = sha.digest()
tlv.add('SHA256', digest)
# Calculate the hash of the public key
if key is not None: if key is not None:
pub = key.get_public_bytes() pub = key.get_public_bytes()
sha = hashlib.sha256() sha = hashlib.sha256()
sha.update(pub) sha.update(pub)
pubbytes = sha.digest() 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) tlv.add(key.sig_tlv(), sig)
self.payload += tlv.get() self.payload += tlv.get()
def add_header(self, key, ramLoadAddress): def add_header(self, key, protected_tlv_size, ramLoadAddress):
"""Install the image header. """Install the image header.
The key is needed to know the type of signature, and The key is needed to know the type of signature, and
@ -140,28 +228,28 @@ class Image():
fmt = ('<' + fmt = ('<' +
# type ImageHdr struct { # type ImageHdr struct {
'I' + # Magic uint32 'I' + # Magic uint32
'I' + # LoadAddr uint32 'I' + # LoadAddr uint32
'H' + # HdrSz uint16 'H' + # HdrSz uint16
'H' + # Pad1 uint16 'H' + # PTLVSz uint16
'I' + # ImgSz uint32 'I' + # ImgSz uint32
'I' + # Flags uint32 'I' + # Flags uint32
'BBHI' + # Vers ImageVersion 'BBHI' + # Vers ImageVersion
'I' # Pad2 uint32 'I' # Pad1 uint32
) # } ) # }
assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
header = struct.pack(fmt, header = struct.pack(fmt,
IMAGE_MAGIC, IMAGE_MAGIC,
0 if (ramLoadAddress is None) else ramLoadAddress, # LoadAddr 0 if (ramLoadAddress is None) else ramLoadAddress, # LoadAddr
self.header_size, self.header_size,
0, # Pad1 protected_tlv_size, # TLV info header + Protected TLVs
len(self.payload) - self.header_size, # ImageSz len(self.payload) - self.header_size, # ImageSz
flags, # Flags flags,
self.version.major, self.version.major,
self.version.minor or 0, self.version.minor or 0,
self.version.revision or 0, self.version.revision or 0,
self.version.build or 0, self.version.build or 0,
0) # Pad2 0) # Pad1
self.payload = bytearray(self.payload) self.payload = bytearray(self.payload)
self.payload[:len(header)] = header self.payload[:len(header)] = header
@ -176,4 +264,4 @@ class Image():
pbytes = b'\xff' * padding pbytes = b'\xff' * padding
pbytes += b'\xff' * (tsize - len(boot_magic)) pbytes += b'\xff' * (tsize - len(boot_magic))
pbytes += boot_magic pbytes += boot_magic
self.payload += pbytes self.payload += pbytes

View File

@ -1,5 +1,5 @@
# Copyright 2017 Linaro Limited # Copyright (c) 2017,2019 Linaro Limited.
# Copyright (c) 2017-2018, Arm Limited. # Copyright (c) 2017-2019, Arm Limited.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 __future__ import print_function
from Crypto.Hash import SHA256 from cryptography.hazmat.backends import default_backend
from Crypto.PublicKey import RSA from cryptography.hazmat.primitives import serialization
from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS 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 import hashlib
from pyasn1.type import namedtype, univ from pyasn1.type import namedtype, univ
from pyasn1.codec.der.encoder import encode 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 # 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 # the command line to support the older (less secure) PKCS1.5
sign_rsa_pss = True sign_rsa_pss = True
AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */" AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
class RSAPublicKey(univ.Sequence): class RSAUsageError(Exception):
componentType = namedtype.NamedTypes( pass
namedtype.NamedType('modulus', univ.Integer()),
namedtype.NamedType('publicExponent', univ.Integer()))
class RSA2048(): class RSAutil():
def __init__(self, key): def __init__(self, key, public_key_format='hash'):
"""Construct an RSA2048 key with the given key data""" """Construct an RSA key with the given key data"""
self.key = key 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 @staticmethod
def generate(): def generate(key_size=2048):
return RSA2048(RSA.generate(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): def export_private(self, path):
with open(path, 'wb') as f: 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): def get_public_bytes(self):
node = RSAPublicKey() return self.key.public_key().public_bytes(
node['modulus'] = self.key.n encoding=serialization.Encoding.DER,
node['publicExponent'] = self.key.e format=serialization.PublicFormat.PKCS1)
return bytearray(encode(node))
def emit_c(self): def emit_c(self):
print(AUTOGEN_MESSAGE) print(AUTOGEN_MESSAGE)
@ -71,34 +93,44 @@ class RSA2048():
def sig_type(self): def sig_type(self):
"""Return the type of this signature (as a string)""" """Return the type of this signature (as a string)"""
if sign_rsa_pss: if sign_rsa_pss:
return "PKCS1_PSS_RSA2048_SHA256" return "PKCS1_PSS_RSA{}_SHA256".format(self.key_size())
else: else:
return "PKCS15_RSA2048_SHA256" return "PKCS15_RSA{}_SHA256".format(self.key_size())
def sig_len(self): def sig_len(self):
return 256 return 256 if self.key_size() == 2048 else 384
def sig_tlv(self): def sig_tlv(self):
return "RSA2048" return "RSA2048" if self.key_size() == 2048 else "RSA3072"
def sign(self, payload): def sign(self, payload):
converted_payload = bytes(payload)
sha = SHA256.new(converted_payload)
if sign_rsa_pss: 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: else:
signer = PKCS1_v1_5.new(self.key) signature = self.key.sign(
signature = signer.sign(sha) data=payload,
padding=PKCS1v15(),
algorithm=SHA256()
)
assert len(signature) == self.sig_len() assert len(signature) == self.sig_len()
return signature return signature
def load(path): def load(path, public_key_format='hash'):
with open(path, 'rb') as f: with open(path, 'rb') as f:
pem = f.read() pem = f.read()
try: try:
key = RSA.importKey(pem) key = serialization.load_pem_private_key(
if key.n.bit_length() != 2048: pem,
raise Exception("Unsupported RSA bit length, only 2048 supported") password=None,
return RSA2048(key) backend=default_backend()
)
return RSAutil(key, public_key_format)
except ValueError: except ValueError:
raise Exception("Unsupported RSA key file") raise Exception("Unsupported RSA key file")

View 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

View File

@ -54,11 +54,14 @@ def musca_tfm_bin(t_self, non_secure_bin, secure_bin):
#2. Run imgtool to sign the concatenated binary #2. Run imgtool to sign the concatenated binary
sign_args = Namespace( sign_args = Namespace(
layout=flash_layout, 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, align=1,
dependencies=None,
version=version.decode_version('1.0'), version=version.decode_version('1.0'),
header_size=0x400, header_size=0x400,
pad=0x100000, pad=0x100000,
security_counter=None,
rsa_pkcs1_15=False, rsa_pkcs1_15=False,
included_header=False, included_header=False,
infile=concatenated_bin, infile=concatenated_bin,

View File

@ -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-----

View File

@ -2,7 +2,7 @@
A default RSA key pair is given to the Musca-A1 target. 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! DO NOT use them in production code, they are exclusively for testing!

View File

@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
MIIG4gIBAAKCAYEAnLrCWr/MxU8gDE9vbFFPXAqrgLhrEMSbK8RSMglLOyeUah3V
TKhcoMB2lXsmBLETfngn1gy06LAtklKK+2n/QhCqVgyDyGVuug1fjvcrKZL8Qi0t
+YD1hSGH6qxAqMvQqDvi0uzwFEgOzyuKS6TNoQVbF2Yd3m5E/kajDdBpv4ytqRZo
Uet5kSDmgQMHiUBVS+vPZ/gxxxxUTlILYOiiUAfRz84SJs2Ogo1OZKn3xyGZJQfd
xdVf9GP6zCvaBlxZZ7AGNemqkkU15aAD/xwCtcdOlEturXOdzm8Js7GPYGyi+s13
D8wn5jZYs1L3j75JmLfpYP2XV83q0wvfokL3RNOH3uAQA5Ta/LzdvpOzSitY3JYS
8m8jujs3/vwYH3V9VAEOvj0YE7MouTQs1fvFM72HvTvkHdcCPRxyZXJDQzao+uZz
LaRh6AKcOlZNHNF2nIyqXxvrHEr1ubhvQUsnh972lB/d5vGpwgLCT6P8pANa2W94
/YTw5f09pU0brVtLAgMBAAECggGAG786mltbctEL0PIdPVV10cs3yq2bktfjys9S
Z/ZaQcpDjbfjY9NotrLsK5GmTO1WkKzQDKaqPom2P7HqVhFRdg5CQcKscAV5IWot
sT9T/mO90i9ydLoefWfOyr6dIeUXdzlG8mWtKUIKkSXZsYOnPesXUeCryA3InCXA
RzlPB3Dt68ICTQJ9vrJO7KcvJd7kWvEQAo2frmr3B/iheBInbji8LeiDMShyIu3G
Y67tpWzu0m3+lsAsYTV0GMJosniVulaZ3hYQQazHUk+zDzMSC7zryICrpjEbgzWU
HZI9EGi1B890nwUtdhlCpkr8zoWDb0BjawpftiGz7fRm7q2TQkYAWGzNKm3DZlIS
4LsRACvHnPZ17wUSze9tqP14Pb593WR3nOTiVjrJWm+4Z5hgV3QfoEqW5swOAYl4
6QmKZsCXAfGkozJiHnYcyaULkGBVegn1LQ5rcb8JUMribQddrHZxCVHrbgwh2zm/
v9CYfTtpWCnKHq+wF3mwjl6w7m4JAoHBALolVbgs919Dx0xjcPnW5MSxW3ctflI9
2ZE1BOH/Rtg5gfBwR/aToUM3a/ZgIJHQYhVty2TzUVtthtmLNTRKu2FSqWN8//GJ
wmj4bcNBshMgniHEfkutlBiP9exhdvCZX4bYpdTkJAyvOmUGjEM8QBFsod60u0z7
Bd0EIXs7PIURP0fNAUXCgSHMPjdICLljhwHinr31VEIU2/xehw8DBIJwkR/rCsPq
xBmlIwPWVjzCRTnYUxQuxCAYf+qvgNylKQKBwQDXi3UGI7t30rYNMdIjMn1GPkhW
o62BOJNCusoXiGnbVOkj8qBayf7kPu10ONBzHcYL7+SQYeVVXQY+DH033ji8oa0J
p1xMGIlx4JZEduQYlk0ke4hUNrcBQczTRA47DmMm2kIdWlaTHtB7aCJNx72IrwWn
lVTY9TWm6+yOPcpV5JfyCMM6GqoRycikgNS5IQug5hl2pFVLw+UTfxo6msYaAOnp
ICUjoeDUKS0Z8+FtzGhAkWTk8GXIiPbfu7RoN1MCgcAcah6Poq2QKTR/AJ76REdf
jwM7SgKCY1aWx9Ua+nDCCOVA4qLZjOeM7yTX0wyltX2Db+MgYdQFdM6k3o8ckFvS
G2AoA6i+Ih0/EM0QhTK9oLkCxo/Q1YpJxY/wqWASkhb26pNF0B2Aoi7zxPAcQ1I0
VrTO3h/JPHhEqKDDwuMWHO/f8fdDwtEba6YDokdSpVKygvlgXdaiz7RU7ckIDZne
n3hHuwVFqsyMbZzOtSUs2SrgDZmA9zKRA6xjEq9E/yECgcAnm7XecfSCGVNg61XN
J/sDTHCokx1QEKBm88ItPuEM7/aDp5M1+8Z+FN43rDUJ4l/BU8zxhzvISvbZshvU
h15vs1oD2yBHz356UaXrYNmbdwsn+BdeOku4zGmiLPBcg9FOk27wy+f60v/GnaUo
G9tFYbwtRnC4CZ9ZVCM9JDepPv9494lAhSPZbvYS3KW6e0sSvxXQynPuH0paIdIl
EMn0f1R8hW6ttJKHCiYCjeFP9u71ZoJe25oolpqfFHQbbocCgcAuBR4w3Qmnbscm
3b7fyy8n3AXa1gIfYjjPpR35qyp1K9thiLyj66YZIl0ACC/dt08lmI9/lguRoNIQ
AfjzZ8DByZa0caiSiFIMlgNZXdh7N3BUNNbIQk98Wd91gBlWDAiFEhrJKFPpRkmv
FySATPYcq0lcrjJb3IW2GDK4uo/jb4Nb7Cfog95W6T76XcSKHS5O8k1aI4kFPRsr
1wGZw64OkA8VXVaCaEBQ4brZ1YKB3mx4/tDqwn0I6bqkGRX3RJg=
-----END RSA PRIVATE KEY-----