Import Mbed OS hard-float snapshot

This commit is contained in:
Beslan
2026-06-01 20:15:04 +03:00
commit d3738e2f89
16278 changed files with 10628036 additions and 0 deletions

16
tools/psa/tfm/__init__.py Normal file
View File

@@ -0,0 +1,16 @@
#! /usr/bin/env python
# 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.

View File

@@ -0,0 +1,21 @@
# Copyright (c) 2017-2018 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 .assemble import Assembly
__all__ = [
'Assembly'
]

View File

@@ -0,0 +1,93 @@
#! /usr/bin/env python3
#
# Copyright 2017 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.
# 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.
"""
Assemble multiple images into a single image that can be flashed on the device.
"""
import argparse
import errno
import io
import re
import os
import shutil
import macro_parser
offset_re = re.compile(r"^\s*RE_([0-9A-Z_]+)_IMAGE_OFFSET\s*=\s*(.*)")
size_re = re.compile(r"^\s*RE_([0-9A-Z_]+)_IMAGE_MAX_SIZE\s*=\s*(.*)")
class Assembly():
def __init__(self, layout_path, output):
self.output = output
self.layout_path = layout_path
self.find_slots()
try:
os.unlink(output)
except OSError as e:
if e.errno != errno.ENOENT:
raise
def find_slots(self):
offsets = {}
sizes = {}
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")
if 'NON_SECURE' not in offsets:
raise Exception("Image config does not have non-secure partition")
self.offsets = offsets
self.sizes = sizes
def add_image(self, source, partition):
with open(self.output, 'ab') as ofd:
ofd.seek(0, os.SEEK_END)
pos = ofd.tell()
if pos > self.offsets[partition]:
raise Exception("Partitions not in order, unsupported")
if pos < self.offsets[partition]:
ofd.write(b'\xFF' * (self.offsets[partition] - pos))
statinfo = os.stat(source)
if statinfo.st_size > self.sizes[partition]:
raise Exception("Image {} is too large for partition".format(source))
with open(source, 'rb') as rfd:
shutil.copyfileobj(rfd, ofd, 0x10000)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--layout', required=True,
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',
help='Unsigned non-secure image')
parser.add_argument('-o', '--output', required=True,
help='Filename to write full image to')
args = parser.parse_args()
output = Assembly(args.layout, args.output)
output.add_image(args.secure, "SECURE")
output.add_image(args.non_secure, "NON_SECURE")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,254 @@
#! /usr/bin/env python3
#
# Copyright 2017 Linaro 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.
# 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 re
import argparse
from imgtool_lib import keys
from imgtool_lib import image
from imgtool_lib import version
import sys
import macro_parser
import fileinput
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*(.*)")
# Returns the last version number if present, or None if not
def get_last_version(path):
if (os.path.isfile(path) == False): # Version file not present
return None
else: # Version file is present, check it has a valid number inside it
with open(path, "r") as oldFile:
fileContents = oldFile.read()
if version.version_re.match(fileContents): # number is valid
return version.decode_version(fileContents)
else:
return None
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):
newVersion = version.increment_build_num(lastVersion)
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),
b=str(newVersion.minor),
c=str(newVersion.revision),
d=str(newVersion.build)
)
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.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-3072': gen_rsa3072, }
def do_keygen(args):
if args.type not in keygens:
msg = "Unexpected key type: {}".format(args.type)
raise argparse.ArgumentTypeError(msg)
keygens[args.type](args)
def do_getpub(args):
key = keys.load(args.key)
if args.lang == 'c':
key.emit_c()
else:
msg = "Unsupported language, valid are: c"
raise argparse.ArgumentTypeError(msg)
def do_sign(args):
if args.rsa_pkcs1_15:
keys.sign_rsa_pss = False
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 = macro_parser.evaluate_macro(args.layout, sign_bin_size_re, 0, 1)
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 = macro_parser.evaluate_macro(args.layout, image_load_address_re, 0, 1)
img.sign(sw_type, key, ram_load_address, args.dependencies)
if pad_size:
img.pad_to(pad_size, args.align)
img.save(args.outfile)
def do_flash(args):
image_value_re = re.compile(r"^\s*"+args.macro+"\s*=\s*(.*)")
value = macro_parser.evaluate_macro(args.layout, image_value_re, 0, 1,
True)
if args.setting == 1:
begin_line="set "+args.begin
else:
begin_line=args.begin
for line in fileinput.input(args.infile, inplace=True):
if line.startswith(begin_line):
if args.division:
value = int(value/int(args.division))
if args.phexa == 0:
line = begin_line+"="+str(value)+"\n"
else:
line = begin_line+"="+hex(value)+"\n"
sys.stdout.write(line)
subcmds = {
'keygen': do_keygen,
'getpub': do_getpub,
'sign': do_sign,
'flash': do_flash, }
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]:
msg = "{} must be one of 1, 2, 4 or 8".format(value)
raise argparse.ArgumentTypeError(msg)
return value
def intparse(text):
"""Parse a command line argument as an integer.
Accepts 0x and other prefixes to allow other bases to be used."""
return int(text, 0)
def args():
parser = argparse.ArgumentParser()
subs = parser.add_subparsers(help='subcommand help', dest='subcmd')
keygenp = subs.add_parser('keygen', help='Generate pub/private keypair')
keygenp.add_argument('-k', '--key', metavar='filename', required=True)
keygenp.add_argument('-t', '--type', metavar='type',
choices=keygens.keys(), required=True)
getpub = subs.add_parser('getpub', help='Get public key from keypair')
getpub.add_argument('-k', '--key', metavar='filename', required=True)
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('-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("--rsa-pkcs1-15",
help='Use old PKCS#1 v1.5 signature algorithm',
default=False, action='store_true')
sign.add_argument("infile")
sign.add_argument("outfile")
flash = subs.add_parser('flash', help='modify flash script')
flash.add_argument("infile")
flash.add_argument('-l', '--layout', required=True,
help='Location of the file that contains preprocessed macros')
flash.add_argument('-m', '--macro', required =True,
help='macro symbol string to grep in preprocessed file')
flash.add_argument('-b', '--begin', required=True,
help='begin of line to replace ')
flash.add_argument('-s', '--setting',type=intparse,required=False,default=0,
help='search for window batch set variable')
flash.add_argument('-d', '--division',
required=False,type=intparse,default=0,
help='search for window batch set variable')
flash.add_argument('-p', '--phexa',
required=False,type=intparse,default=1,
help='print value in hexa')
args = parser.parse_args()
if args.subcmd is None:
print('Must specify a subcommand', file=sys.stderr)
sys.exit(1)
subcmds[args.subcmd](args)
if __name__ == '__main__':
args()

View File

@@ -0,0 +1,18 @@
# Copyright 2017 Linaro Limited
#
# 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.
# This file is intentionally empty.
#
# The __init__.py files are required to make Python treat the directories as
# containing packages.

View File

@@ -0,0 +1,79 @@
# 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.
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

@@ -0,0 +1,267 @@
# Copyright 2017 Linaro 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.
# 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.
"""
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 = {
'PIC': 0x0000001,
'NON_BOOTABLE': 0x0000010,
'RAM_LOAD': 0x0000020, }
TLV_VALUES = {
'KEYHASH': 0x01,
'KEY' : 0x02,
'SHA256' : 0x10,
'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 = {
write_size: 128 * 3 * write_size + 8 * 2 + 16
for write_size in [1, 2, 4, 8]
}
boot_magic = bytearray([
0x77, 0xc2, 0x95, 0xf3,
0x60, 0xd2, 0xef, 0x7f,
0x35, 0x52, 0x50, 0x0f,
0x2c, 0xb6, 0x79, 0x80, ])
class TLV():
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.
"""
buf = struct.pack('<BBH', TLV_VALUES[kind], 0, len(payload))
self.buf += buf
self.buf += payload
def get(self):
if len(self.buf) == 0:
return bytes()
header = struct.pack('<HH', self.magic, len(self))
return header + bytes(self.buf)
class Image():
@classmethod
def load(cls, path, included_header=False, **kwargs):
"""Load an image from a given file"""
with open(path, 'rb') as f:
payload = f.read()
obj = cls(**kwargs)
obj.payload = payload
# Add the image header if needed.
if not included_header and obj.header_size > 0:
obj.payload = (b'\000' * obj.header_size) + obj.payload
obj.check()
return obj
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={}, security_counter={}, \
pad={}, payloadlen=0x{:x}>".format(
self.version,
self.header_size,
self.security_cnt,
self.pad,
len(self.payload))
def save(self, path):
with open(path, 'wb') as f:
f.write(self.payload)
def check(self):
"""Perform some sanity checking of the image."""
# If there is a header requested, make sure that the image
# starts with all zeros.
if self.header_size > 0:
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, 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()
else:
pubbytes = bytes(KEYHASH_SIZE)
# 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(self.payload)
tlv.add(key.sig_tlv(), sig)
self.payload += tlv.get()
def add_header(self, key, protected_tlv_size, ramLoadAddress):
"""Install the image header.
The key is needed to know the type of signature, and
approximate the size of the signature."""
flags = 0
if ramLoadAddress is not None:
# add the load address flag to the header to indicate that an SRAM
# load address macro has been defined
flags |= IMAGE_F["RAM_LOAD"]
fmt = ('<' +
# type ImageHdr struct {
'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,
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
def pad_to(self, size, align):
"""Pad the image to the given size, with the given flash alignment."""
tsize = trailer_sizes[align]
padding = size - (len(self.payload) + tsize)
if padding < 0:
msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds requested size 0x{:x}".format(
len(self.payload), tsize, size)
raise Exception(msg)
pbytes = b'\xff' * padding
pbytes += b'\xff' * (tsize - len(boot_magic))
pbytes += boot_magic
self.payload += pbytes

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env python3
# 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.
# 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.
"""
Cryptographic key management for imgtool.
"""
from __future__ import print_function
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 RSAUsageError(Exception):
pass
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(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.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()))
def get_public_bytes(self):
return self.key.public_key().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.PKCS1)
def emit_c(self):
print(AUTOGEN_MESSAGE)
print("const unsigned char rsa_pub_key[] = {", end='')
encoded = self.get_public_bytes()
for count, b in enumerate(encoded):
if count % 8 == 0:
print("\n\t", end='')
else:
print(" ", end='')
print("0x{:02x},".format(b), end='')
print("\n};")
print("const unsigned int rsa_pub_key_len = {};".format(len(encoded)))
def sig_type(self):
"""Return the type of this signature (as a string)"""
if sign_rsa_pss:
return "PKCS1_PSS_RSA{}_SHA256".format(self.key_size())
else:
return "PKCS15_RSA{}_SHA256".format(self.key_size())
def sig_len(self):
return 256 if self.key_size() == 2048 else 384
def sig_tlv(self):
return "RSA2048" if self.key_size() == 2048 else "RSA3072"
def sign(self, payload):
if sign_rsa_pss:
signature = self.key.sign(
data=payload,
padding=PSS(
mgf=MGF1(SHA256()),
salt_length=32
),
algorithm=SHA256()
)
else:
signature = self.key.sign(
data=payload,
padding=PKCS1v15(),
algorithm=SHA256()
)
assert len(signature) == self.sig_len()
return signature
def load(path, public_key_format='hash'):
with open(path, 'rb') as f:
pem = f.read()
try:
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")

View File

@@ -0,0 +1,66 @@
# Copyright 2017 Linaro Limited
# Copyright (c) 2018, Arm Limited.
#
# 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.
"""
Semi Semantic Versioning
Implements a subset of semantic versioning that is supportable by the image header.
"""
import argparse
from collections import namedtuple
import re
SemiSemVersion = namedtuple('SemiSemVersion', ['major', 'minor', 'revision', 'build'])
def increment_build_num(lastVer):
newVer = SemiSemVersion(lastVer.major, lastVer.minor, lastVer.revision, lastVer.build + 1)
return newVer
# -1 if a is older than b; 0 if they're the same version; 1 if a is newer than b
def compare(a, b):
if (a.major > b.major): return 1
elif (a.major < b.major): return -1
else:
if (a.minor > b.minor): return 1
elif (a.minor < b.minor): return -1
else:
if (a.revision > b.revision): return 1
elif (a.revision < b.revision): return -1
else:
if (a.build > b.build): return 1
elif (a.build < b.build): return -1
else: return 0
version_re = re.compile(r"""^([1-9]\d*|0)(\.([1-9]\d*|0)(\.([1-9]\d*|0)(\+([1-9]\d*|0))?)?)?$""")
def decode_version(text):
"""Decode the version string, which should be of the form maj.min.rev+build"""
m = version_re.match(text)
if m:
result = SemiSemVersion(
int(m.group(1)) if m.group(1) else 0,
int(m.group(3)) if m.group(3) else 0,
int(m.group(5)) if m.group(5) else 0,
int(m.group(7)) if m.group(7) else 0)
return result
else:
msg = "Invalid version number, should be maj.min.rev+build with later parts optional"
raise argparse.ArgumentTypeError(msg)
if __name__ == '__main__':
print(decode_version("1.2"))
print(decode_version("1.0"))
print(decode_version("0.0.2+75"))
print(decode_version("0.0.0+00"))

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