Skip to content

Commit d1780aa

Browse files
Marti Bolivarnashif
authored andcommitted
scripts: west_commands: add sign command
This command is useful for signing binaries for loading by a bootloader. At present, only MCUboot's "imgtool" is supported, but it would be straightforward to add support for additional tools. Using this command instead of "plain" imgtool avoids looking up any numbers for the flash write block size, text section offset, or slot size to get a signed binary. All users need to specify is the location of the signing key. This greatly improves usability for those unfamiliar with MCUboot, or even experienced users who have to deal with multiple flash partition layouts, boards, etc. The command works by inspecting state in the Zephyr build system, some of which is also provided by the runner package. Signed-off-by: Marti Bolivar <[email protected]>
1 parent 8c09528 commit d1780aa

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

scripts/west-commands.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ west-commands:
55
- name: build
66
class: Build
77
help: compile a Zephyr application
8+
- file: scripts/west_commands/sign.py
9+
commands:
10+
- name: sign
11+
class: Sign
12+
help: sign a Zephyr binary for bootloader chain-loading
813
- file: scripts/west_commands/flash.py
914
commands:
1015
- name: flash

scripts/west_commands/sign.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# Copyright (c) 2018 Foundries.io
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import abc
6+
import argparse
7+
import os
8+
import subprocess
9+
10+
from west import cmake
11+
from west import log
12+
from west.build import is_zephyr_build
13+
from west.util import quote_sh_list
14+
15+
from runners.core import BuildConfiguration
16+
17+
from zephyr_ext_common import find_build_dir, Forceable, \
18+
BUILD_DIR_DESCRIPTION, cached_runner_config
19+
20+
SIGN_DESCRIPTION = '''\
21+
This command automates some of the drudgery of creating signed Zephyr
22+
binaries for chain-loading by a bootloader.
23+
24+
In the simplest usage, run this from your build directory:
25+
26+
west sign -t your_tool -- ARGS_FOR_YOUR_TOOL
27+
28+
Assuming your binary was properly built for processing and handling by
29+
tool "your_tool", this creates zephyr.signed.bin and zephyr.signed.hex
30+
files (if supported by "your_tool") which are ready for use by your
31+
bootloader. The "ARGS_FOR_YOUR_TOOL" value can be any additional
32+
arguments you want to pass to the tool, such as the location of a
33+
signing key, a version identifier, etc.
34+
35+
See tool-specific help below for details.'''
36+
37+
SIGN_EPILOG = '''\
38+
imgtool
39+
-------
40+
41+
Currently, MCUboot's 'imgtool' tool is supported. To build a signed
42+
binary you can load with MCUboot using imgtool, run this from your
43+
build directory:
44+
45+
west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem
46+
47+
The image header size, alignment, and slot sizes are determined from
48+
the build directory using board information and the device tree. A
49+
default version number of 0.0.0+0 is used (which can be overridden by
50+
passing "--version x.y.z+w" after "--key"). As shown above, extra
51+
arguments after a '--' are passed to imgtool directly.'''
52+
53+
54+
class ToggleAction(argparse.Action):
55+
56+
def __call__(self, parser, args, ignored, option):
57+
setattr(args, self.dest, not option.startswith('--no-'))
58+
59+
60+
class Sign(Forceable):
61+
def __init__(self):
62+
super(Sign, self).__init__(
63+
'sign',
64+
# Keep this in sync with the string in west-commands.yml.
65+
'sign a Zephyr binary for bootloader chain-loading',
66+
SIGN_DESCRIPTION,
67+
accepts_unknown_args=False)
68+
69+
def do_add_parser(self, parser_adder):
70+
parser = parser_adder.add_parser(
71+
self.name,
72+
epilog=SIGN_EPILOG,
73+
help=self.help,
74+
formatter_class=argparse.RawDescriptionHelpFormatter,
75+
description=self.description)
76+
77+
parser.add_argument('-d', '--build-dir', help=BUILD_DIR_DESCRIPTION)
78+
self.add_force_arg(parser)
79+
80+
# general options
81+
group = parser.add_argument_group('tool control options')
82+
group.add_argument('-t', '--tool', choices=['imgtool'],
83+
help='image signing tool name')
84+
group.add_argument('-p', '--tool-path', default='imgtool',
85+
help='''path to the tool itself, if needed''')
86+
group.add_argument('tool_args', nargs='*', metavar='tool_opt',
87+
help='extra option(s) to pass to the signing tool')
88+
89+
# bin file options
90+
group = parser.add_argument_group('binary (.bin) file options')
91+
group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0,
92+
action=ToggleAction,
93+
help='''produce a signed .bin file?
94+
(default: yes, if supported)''')
95+
group.add_argument('-B', '--sbin', metavar='BIN',
96+
default='zephyr.signed.bin',
97+
help='''signed .bin file name
98+
(default: zephyr.signed.bin)''')
99+
100+
# hex file options
101+
group = parser.add_argument_group('Intel HEX (.hex) file options')
102+
group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0,
103+
action=ToggleAction,
104+
help='''produce a signed .hex file?
105+
(default: yes, if supported)''')
106+
group.add_argument('-H', '--shex', metavar='HEX',
107+
default='zephyr.signed.hex',
108+
help='''signed .hex file name
109+
(default: zephyr.signed.hex)''')
110+
111+
# defaults for hex/bin generation
112+
parser.set_defaults(gen_bin=True, gen_hex=True)
113+
114+
return parser
115+
116+
def do_run(self, args, ignored):
117+
if not (args.gen_bin or args.gen_hex):
118+
return
119+
120+
self.check_force(os.path.isdir(args.build_dir),
121+
'no such build directory {}'.format(args.build_dir))
122+
self.check_force(is_zephyr_build(args.build_dir),
123+
"build directory {} doesn't look like a Zephyr build "
124+
'directory'.format(args.build_dir))
125+
126+
if args.tool == 'imgtool':
127+
signer = ImgtoolSigner()
128+
# (Add support for other signers here in elif blocks)
129+
else:
130+
raise RuntimeError("can't happen")
131+
132+
# Provide the build directory if not given, and defer to the signer.
133+
args.build_dir = find_build_dir(args.build_dir)
134+
signer.sign(args)
135+
136+
137+
class Signer(abc.ABC):
138+
'''Common abstract superclass for signers.
139+
140+
To add support for a new tool, subclass this and add support for
141+
it in the Sign.do_run() method.'''
142+
143+
@abc.abstractmethod
144+
def sign(self, args):
145+
'''Abstract method to perform a signature; subclasses must implement.
146+
147+
:param args: parsed arguments from Sign command
148+
'''
149+
150+
151+
class ImgtoolSigner(Signer):
152+
153+
def sign(self, args):
154+
cache = cmake.CMakeCache.from_build_dir(args.build_dir)
155+
runner_config = cached_runner_config(args.build_dir, cache)
156+
bcfg = BuildConfiguration(args.build_dir)
157+
158+
# Build a signed .bin
159+
if args.gen_bin and runner_config.bin_file:
160+
sign_bin = self.sign_cmd(args, bcfg, runner_config.bin_file,
161+
args.sbin)
162+
log.dbg(quote_sh_list(sign_bin))
163+
subprocess.check_call(sign_bin)
164+
165+
# Build a signed .hex
166+
if args.gen_hex and runner_config.hex_file:
167+
sign_hex = self.sign_cmd(args, bcfg, runner_config.hex_file,
168+
args.shex)
169+
log.dbg(quote_sh_list(sign_hex))
170+
subprocess.check_call(sign_hex)
171+
172+
def sign_cmd(self, args, bcfg, infile, outfile):
173+
align = str(bcfg['FLASH_WRITE_BLOCK_SIZE'])
174+
vtoff = str(bcfg['CONFIG_TEXT_SECTION_OFFSET'])
175+
slot_size = str(bcfg['FLASH_AREA_IMAGE_0_SIZE'])
176+
177+
sign_command = [args.tool_path or 'imgtool',
178+
'sign',
179+
'--align', align,
180+
'--header-size', vtoff,
181+
'--slot-size', slot_size,
182+
# We provide a default --version in case the
183+
# user is just messing around and doesn't want
184+
# to set one. It will be overridden if there is
185+
# a --version in args.tool_args.
186+
'--version', '0.0.0+0',
187+
infile,
188+
outfile]
189+
190+
sign_command.extend(args.tool_args)
191+
192+
return sign_command

0 commit comments

Comments
 (0)