-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
Showing
2 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
# Copyright (c) 2018 Foundries.io | ||
# | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import abc | ||
import argparse | ||
import os | ||
import subprocess | ||
|
||
from west import cmake | ||
from west import log | ||
from west.build import is_zephyr_build | ||
from west.util import quote_sh_list | ||
|
||
from runners.core import BuildConfiguration | ||
|
||
from zephyr_ext_common import find_build_dir, Forceable, \ | ||
BUILD_DIR_DESCRIPTION, cached_runner_config | ||
|
||
SIGN_DESCRIPTION = '''\ | ||
This command automates some of the drudgery of creating signed Zephyr | ||
binaries for chain-loading by a bootloader. | ||
In the simplest usage, run this from your build directory: | ||
west sign -t your_tool -- ARGS_FOR_YOUR_TOOL | ||
Assuming your binary was properly built for processing and handling by | ||
tool "your_tool", this creates zephyr.signed.bin and zephyr.signed.hex | ||
files (if supported by "your_tool") which are ready for use by your | ||
bootloader. The "ARGS_FOR_YOUR_TOOL" value can be any additional | ||
arguments you want to pass to the tool, such as the location of a | ||
signing key, a version identifier, etc. | ||
See tool-specific help below for details.''' | ||
|
||
SIGN_EPILOG = '''\ | ||
imgtool | ||
------- | ||
Currently, MCUboot's 'imgtool' tool is supported. To build a signed | ||
binary you can load with MCUboot using imgtool, run this from your | ||
build directory: | ||
west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem | ||
The image header size, alignment, and slot sizes are determined from | ||
the build directory using board information and the device tree. A | ||
default version number of 0.0.0+0 is used (which can be overridden by | ||
passing "--version x.y.z+w" after "--key"). As shown above, extra | ||
arguments after a '--' are passed to imgtool directly.''' | ||
|
||
|
||
class ToggleAction(argparse.Action): | ||
|
||
def __call__(self, parser, args, ignored, option): | ||
setattr(args, self.dest, not option.startswith('--no-')) | ||
|
||
|
||
class Sign(Forceable): | ||
def __init__(self): | ||
super(Sign, self).__init__( | ||
'sign', | ||
# Keep this in sync with the string in west-commands.yml. | ||
'sign a Zephyr binary for bootloader chain-loading', | ||
SIGN_DESCRIPTION, | ||
accepts_unknown_args=False) | ||
|
||
def do_add_parser(self, parser_adder): | ||
parser = parser_adder.add_parser( | ||
self.name, | ||
epilog=SIGN_EPILOG, | ||
help=self.help, | ||
formatter_class=argparse.RawDescriptionHelpFormatter, | ||
description=self.description) | ||
|
||
parser.add_argument('-d', '--build-dir', help=BUILD_DIR_DESCRIPTION) | ||
self.add_force_arg(parser) | ||
|
||
# general options | ||
group = parser.add_argument_group('tool control options') | ||
group.add_argument('-t', '--tool', choices=['imgtool'], | ||
help='image signing tool name') | ||
group.add_argument('-p', '--tool-path', default='imgtool', | ||
help='''path to the tool itself, if needed''') | ||
group.add_argument('tool_args', nargs='*', metavar='tool_opt', | ||
help='extra option(s) to pass to the signing tool') | ||
|
||
# bin file options | ||
group = parser.add_argument_group('binary (.bin) file options') | ||
group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0, | ||
action=ToggleAction, | ||
help='''produce a signed .bin file? | ||
(default: yes, if supported)''') | ||
group.add_argument('-B', '--sbin', metavar='BIN', | ||
default='zephyr.signed.bin', | ||
help='''signed .bin file name | ||
(default: zephyr.signed.bin)''') | ||
|
||
# hex file options | ||
group = parser.add_argument_group('Intel HEX (.hex) file options') | ||
group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0, | ||
action=ToggleAction, | ||
help='''produce a signed .hex file? | ||
(default: yes, if supported)''') | ||
group.add_argument('-H', '--shex', metavar='HEX', | ||
default='zephyr.signed.hex', | ||
help='''signed .hex file name | ||
(default: zephyr.signed.hex)''') | ||
|
||
# defaults for hex/bin generation | ||
parser.set_defaults(gen_bin=True, gen_hex=True) | ||
|
||
return parser | ||
|
||
def do_run(self, args, ignored): | ||
if not (args.gen_bin or args.gen_hex): | ||
return | ||
|
||
self.check_force(os.path.isdir(args.build_dir), | ||
'no such build directory {}'.format(args.build_dir)) | ||
self.check_force(is_zephyr_build(args.build_dir), | ||
"build directory {} doesn't look like a Zephyr build " | ||
'directory'.format(args.build_dir)) | ||
|
||
if args.tool == 'imgtool': | ||
signer = ImgtoolSigner() | ||
# (Add support for other signers here in elif blocks) | ||
else: | ||
raise RuntimeError("can't happen") | ||
|
||
# Provide the build directory if not given, and defer to the signer. | ||
args.build_dir = find_build_dir(args.build_dir) | ||
signer.sign(args) | ||
|
||
|
||
class Signer(abc.ABC): | ||
'''Common abstract superclass for signers. | ||
To add support for a new tool, subclass this and add support for | ||
it in the Sign.do_run() method.''' | ||
|
||
@abc.abstractmethod | ||
def sign(self, args): | ||
'''Abstract method to perform a signature; subclasses must implement. | ||
:param args: parsed arguments from Sign command | ||
''' | ||
|
||
|
||
class ImgtoolSigner(Signer): | ||
|
||
def sign(self, args): | ||
cache = cmake.CMakeCache.from_build_dir(args.build_dir) | ||
runner_config = cached_runner_config(args.build_dir, cache) | ||
bcfg = BuildConfiguration(args.build_dir) | ||
|
||
# Build a signed .bin | ||
if args.gen_bin and runner_config.bin_file: | ||
sign_bin = self.sign_cmd(args, bcfg, runner_config.bin_file, | ||
args.sbin) | ||
log.dbg(quote_sh_list(sign_bin)) | ||
subprocess.check_call(sign_bin) | ||
|
||
# Build a signed .hex | ||
if args.gen_hex and runner_config.hex_file: | ||
sign_hex = self.sign_cmd(args, bcfg, runner_config.hex_file, | ||
args.shex) | ||
log.dbg(quote_sh_list(sign_hex)) | ||
subprocess.check_call(sign_hex) | ||
|
||
def sign_cmd(self, args, bcfg, infile, outfile): | ||
align = str(bcfg['FLASH_WRITE_BLOCK_SIZE']) | ||
vtoff = str(bcfg['CONFIG_TEXT_SECTION_OFFSET']) | ||
slot_size = str(bcfg['FLASH_AREA_IMAGE_0_SIZE']) | ||
|
||
sign_command = [args.tool_path or 'imgtool', | ||
'sign', | ||
'--align', align, | ||
'--header-size', vtoff, | ||
'--slot-size', slot_size, | ||
# We provide a default --version in case the | ||
# user is just messing around and doesn't want | ||
# to set one. It will be overridden if there is | ||
# a --version in args.tool_args. | ||
'--version', '0.0.0+0', | ||
infile, | ||
outfile] | ||
|
||
sign_command.extend(args.tool_args) | ||
|
||
return sign_command |