blob: 7e3ef56c00a4a959e20f2bf9e97c9d9d2901a130 [file] [log] [blame]
# 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