Skip to content

Commit 7016ac7

Browse files
author
Michael Schwarcz
committed
Add image signing scripts from TF-M bl2 library
1 parent d2c433c commit 7016ac7

File tree

9 files changed

+671
-0
lines changed

9 files changed

+671
-0
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ fuzzywuzzy>=0.11,<=0.17
2121
pyelftools>=0.24,<=0.25
2222
git+https://github.com/armmbed/[email protected]
2323
icetea>=1.2.1,<1.3
24+
pycryptodome>=3.7.2,<=3.7.3

tools/psa/tfm/__init__.py

Whitespace-only changes.

tools/psa/tfm/bin_utils/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) 2017-2018 ARM Limited
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
from .assemble import Assembly
18+
19+
__all__ = [
20+
'Assembly'
21+
]

tools/psa/tfm/bin_utils/assemble.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#! /usr/bin/env python3
2+
#
3+
# Copyright 2017 Linaro Limited
4+
# Copyright (c) 2017-2018, Arm Limited.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
"""
19+
Assemble multiple images into a single image that can be flashed on the device.
20+
"""
21+
22+
import argparse
23+
import errno
24+
import io
25+
import re
26+
import os
27+
import shutil
28+
29+
offset_re = re.compile(r"^#define ([0-9A-Z_]+)_IMAGE_OFFSET\s+((0x)?[0-9a-fA-F]+)")
30+
size_re = re.compile(r"^#define ([0-9A-Z_]+)_IMAGE_MAX_SIZE\s+((0x)?[0-9a-fA-F]+)")
31+
32+
class Assembly():
33+
def __init__(self, layout_path, output):
34+
self.output = output
35+
self.layout_path = layout_path
36+
self.find_slots()
37+
try:
38+
os.unlink(output)
39+
except OSError as e:
40+
if e.errno != errno.ENOENT:
41+
raise
42+
43+
def find_slots(self):
44+
offsets = {}
45+
sizes = {}
46+
47+
if os.path.isabs(self.layout_path):
48+
configFile = self.layout_path
49+
else:
50+
scriptsDir = os.path.dirname(os.path.abspath(__file__))
51+
configFile = os.path.join(scriptsDir, self.layout_path)
52+
53+
with open(configFile, 'r') as fd:
54+
for line in fd:
55+
m = offset_re.match(line)
56+
if m is not None:
57+
offsets[m.group(1)] = int(m.group(2), 0)
58+
m = size_re.match(line)
59+
if m is not None:
60+
sizes[m.group(1)] = int(m.group(2), 0)
61+
62+
if 'SECURE' not in offsets:
63+
raise Exception("Image config does not have secure partition")
64+
65+
if 'NON_SECURE' not in offsets:
66+
raise Exception("Image config does not have non-secure partition")
67+
68+
self.offsets = offsets
69+
self.sizes = sizes
70+
71+
def add_image(self, source, partition):
72+
with open(self.output, 'ab') as ofd:
73+
ofd.seek(0, os.SEEK_END)
74+
pos = ofd.tell()
75+
if pos > self.offsets[partition]:
76+
raise Exception("Partitions not in order, unsupported")
77+
if pos < self.offsets[partition]:
78+
ofd.write(b'\xFF' * (self.offsets[partition] - pos))
79+
statinfo = os.stat(source)
80+
if statinfo.st_size > self.sizes[partition]:
81+
raise Exception("Image {} is too large for partition".format(source))
82+
with open(source, 'rb') as rfd:
83+
shutil.copyfileobj(rfd, ofd, 0x10000)
84+
85+
def main():
86+
parser = argparse.ArgumentParser()
87+
88+
parser.add_argument('-l', '--layout', required=True,
89+
help='Location of the memory layout file')
90+
parser.add_argument('-s', '--secure', required=True,
91+
help='Unsigned secure image')
92+
parser.add_argument('-n', '--non_secure',
93+
help='Unsigned non-secure image')
94+
parser.add_argument('-o', '--output', required=True,
95+
help='Filename to write full image to')
96+
97+
args = parser.parse_args()
98+
output = Assembly(args.layout, args.output)
99+
100+
101+
output.add_image(args.secure, "SECURE")
102+
output.add_image(args.non_secure, "NON_SECURE")
103+
104+
if __name__ == '__main__':
105+
main()

tools/psa/tfm/bin_utils/imgtool.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#! /usr/bin/env python3
2+
#
3+
# Copyright 2017 Linaro Limited
4+
# Copyright (c) 2018, Arm Limited.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
from __future__ import print_function
19+
import os
20+
import re
21+
import argparse
22+
from imgtool_lib import keys
23+
from imgtool_lib import image
24+
from imgtool_lib import version
25+
import sys
26+
27+
def find_load_address(args):
28+
load_address_re = re.compile(r"^#define\sIMAGE_LOAD_ADDRESS\s+(0x[0-9a-fA-F]+)")
29+
30+
if os.path.isabs(args.layout):
31+
configFile = args.layout
32+
else:
33+
scriptsDir = os.path.dirname(os.path.abspath(__file__))
34+
configFile = os.path.join(scriptsDir, args.layout)
35+
36+
ramLoadAddress = None
37+
with open(configFile, 'r') as flash_layout_file:
38+
for line in flash_layout_file:
39+
m = load_address_re.match(line)
40+
if m is not None:
41+
ramLoadAddress = int(m.group(1), 0)
42+
print("**[INFO]** Writing load address from the macro in "
43+
"flash_layout.h to the image header.. "
44+
+ hex(ramLoadAddress)
45+
+ " (dec. " + str(ramLoadAddress) + ")")
46+
break
47+
return ramLoadAddress
48+
49+
# Returns the last version number if present, or None if not
50+
def get_last_version(path):
51+
if (os.path.isfile(path) == False): # Version file not present
52+
return None
53+
else: # Version file is present, check it has a valid number inside it
54+
with open(path, "r") as oldFile:
55+
fileContents = oldFile.read()
56+
if version.version_re.match(fileContents): # number is valid
57+
return version.decode_version(fileContents)
58+
else:
59+
return None
60+
61+
def next_version_number(args, defaultVersion, path):
62+
newVersion = None
63+
if (version.compare(args.version, defaultVersion) == 0): # Default version
64+
lastVersion = get_last_version(path)
65+
if (lastVersion is not None):
66+
newVersion = version.increment_build_num(lastVersion)
67+
else:
68+
newVersion = version.increment_build_num(defaultVersion)
69+
else: # Version number has been explicitly provided (not using the default)
70+
newVersion = args.version
71+
versionString = "{a}.{b}.{c}+{d}".format(
72+
a=str(newVersion.major),
73+
b=str(newVersion.minor),
74+
c=str(newVersion.revision),
75+
d=str(newVersion.build)
76+
)
77+
with open(path, "w") as newFile:
78+
newFile.write(versionString)
79+
print("**[INFO]** Image version number set to " + versionString)
80+
return newVersion
81+
82+
def gen_rsa2048(args):
83+
keys.RSA2048.generate().export_private(args.key)
84+
85+
keygens = {
86+
'rsa-2048': gen_rsa2048, }
87+
88+
def do_keygen(args):
89+
if args.type not in keygens:
90+
msg = "Unexpected key type: {}".format(args.type)
91+
raise argparse.ArgumentTypeError(msg)
92+
keygens[args.type](args)
93+
94+
def do_getpub(args):
95+
key = keys.load(args.key)
96+
if args.lang == 'c':
97+
key.emit_c()
98+
else:
99+
msg = "Unsupported language, valid are: c"
100+
raise argparse.ArgumentTypeError(msg)
101+
102+
def do_sign(args):
103+
if args.rsa_pkcs1_15:
104+
keys.sign_rsa_pss = False
105+
img = image.Image.load(args.infile,
106+
version=next_version_number(args,
107+
version.decode_version("0"),
108+
"lastVerNum.txt"),
109+
header_size=args.header_size,
110+
included_header=args.included_header,
111+
pad=args.pad)
112+
key = keys.load(args.key) if args.key else None
113+
img.sign(key, find_load_address(args))
114+
115+
if args.pad:
116+
img.pad_to(args.pad, args.align)
117+
118+
img.save(args.outfile)
119+
120+
subcmds = {
121+
'keygen': do_keygen,
122+
'getpub': do_getpub,
123+
'sign': do_sign, }
124+
125+
def alignment_value(text):
126+
value = int(text)
127+
if value not in [1, 2, 4, 8]:
128+
msg = "{} must be one of 1, 2, 4 or 8".format(value)
129+
raise argparse.ArgumentTypeError(msg)
130+
return value
131+
132+
def intparse(text):
133+
"""Parse a command line argument as an integer.
134+
135+
Accepts 0x and other prefixes to allow other bases to be used."""
136+
return int(text, 0)
137+
138+
def args():
139+
parser = argparse.ArgumentParser()
140+
subs = parser.add_subparsers(help='subcommand help', dest='subcmd')
141+
142+
keygenp = subs.add_parser('keygen', help='Generate pub/private keypair')
143+
keygenp.add_argument('-k', '--key', metavar='filename', required=True)
144+
keygenp.add_argument('-t', '--type', metavar='type',
145+
choices=keygens.keys(), required=True)
146+
147+
getpub = subs.add_parser('getpub', help='Get public key from keypair')
148+
getpub.add_argument('-k', '--key', metavar='filename', required=True)
149+
getpub.add_argument('-l', '--lang', metavar='lang', default='c')
150+
151+
sign = subs.add_parser('sign', help='Sign an image with a private key')
152+
sign.add_argument('--layout', required=True,
153+
help='Location of the memory layout file')
154+
sign.add_argument('-k', '--key', metavar='filename')
155+
sign.add_argument("--align", type=alignment_value, required=True)
156+
sign.add_argument("-v", "--version", type=version.decode_version,
157+
default="0.0.0+0")
158+
sign.add_argument("-H", "--header-size", type=intparse, required=True)
159+
sign.add_argument("--included-header", default=False, action='store_true',
160+
help='Image has gap for header')
161+
sign.add_argument("--pad", type=intparse,
162+
help='Pad image to this many bytes, adding trailer magic')
163+
sign.add_argument("--rsa-pkcs1-15",
164+
help='Use old PKCS#1 v1.5 signature algorithm',
165+
default=False, action='store_true')
166+
sign.add_argument("infile")
167+
sign.add_argument("outfile")
168+
169+
args = parser.parse_args()
170+
if args.subcmd is None:
171+
print('Must specify a subcommand', file=sys.stderr)
172+
sys.exit(1)
173+
174+
subcmds[args.subcmd](args)
175+
176+
if __name__ == '__main__':
177+
args()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2017 Linaro Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# This file is intentionally empty.
16+
#
17+
# The __init__.py files are required to make Python treat the directories as
18+
# containing packages.

0 commit comments

Comments
 (0)