blob: 7e3ef56c00a4a959e20f2bf9e97c9d9d2901a130 [file] [log] [blame]
Marti Bolivard1780aa2019-01-30 20:30:42 -07001# Copyright (c) 2018 Foundries.io
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import abc
6import argparse
7import os
8import subprocess
9
10from west import cmake
11from west import log
12from west.build import is_zephyr_build
13from west.util import quote_sh_list
14
15from runners.core import BuildConfiguration
16
17from zephyr_ext_common import find_build_dir, Forceable, \
18 BUILD_DIR_DESCRIPTION, cached_runner_config
19
20SIGN_DESCRIPTION = '''\
21This command automates some of the drudgery of creating signed Zephyr
22binaries for chain-loading by a bootloader.
23
24In the simplest usage, run this from your build directory:
25
26 west sign -t your_tool -- ARGS_FOR_YOUR_TOOL
27
28Assuming your binary was properly built for processing and handling by
29tool "your_tool", this creates zephyr.signed.bin and zephyr.signed.hex
30files (if supported by "your_tool") which are ready for use by your
31bootloader. The "ARGS_FOR_YOUR_TOOL" value can be any additional
32arguments you want to pass to the tool, such as the location of a
33signing key, a version identifier, etc.
34
35See tool-specific help below for details.'''
36
37SIGN_EPILOG = '''\
38imgtool
39-------
40
41Currently, MCUboot's 'imgtool' tool is supported. To build a signed
42binary you can load with MCUboot using imgtool, run this from your
43build directory:
44
45 west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem
46
47The image header size, alignment, and slot sizes are determined from
48the build directory using board information and the device tree. A
49default version number of 0.0.0+0 is used (which can be overridden by
50passing "--version x.y.z+w" after "--key"). As shown above, extra
51arguments after a '--' are passed to imgtool directly.'''
52
53
54class ToggleAction(argparse.Action):
55
56 def __call__(self, parser, args, ignored, option):
57 setattr(args, self.dest, not option.startswith('--no-'))
58
59
60class 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
137class 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
151class 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