blob: efa4b6667e208341535f2f0636bb6ee28609b16b [file] [log] [blame]
David Brown22a6fe32017-01-10 10:40:43 -07001#!/usr/bin/python2
2
3from __future__ import print_function
4
5import mmap
6import os
7import struct
8import sys
9from argparse import ArgumentParser
10import newtimg
11from ctypes import *
12from Crypto.Signature import PKCS1_v1_5
13from Crypto.Hash import SHA256
14from Crypto.PublicKey import RSA
15
16DEBUG = False
17
18def get_args():
Marti Bolivar47c69d02017-01-31 10:14:19 -050019 parser = ArgumentParser(description='Convert Zephyr binaries to mcuboot format')
David Brown22a6fe32017-01-10 10:40:43 -070020
21 parser.add_argument('--bin', required=True, dest='binary_file', \
22 help='Name of *.bin file (input)')
23
24 parser.add_argument('--key', required=False, dest='key_file', \
25 help='Name of private key file (*.pem format)')
26
27 parser.add_argument('--out', required=False, dest='image_file', \
28 default='zephyr.img.bin', \
29 help='Name of *.img file (output)')
30
31 parser.add_argument('--sig', required=False, dest='sig_type', \
32 default='SHA256', \
33 help='Type of signature <SHA256|RSA|EC>')
34
David Brown22a6fe32017-01-10 10:40:43 -070035 parser.add_argument('--word-size', required=False, dest='word_size',
36 default=1,
37 help='Writable size of flash device (1, 2, 4, or 8)')
38
39 parser.add_argument('--vtoff', required=False, dest='vtable_offs', \
40 default=str(hex(newtimg.OFFSET_VECTOR_TABLE)), \
41 help='Offset to vector table in HEX (default: 0x80)')
42
43 parser.add_argument('--pad', required=False, \
44 help='Pad file with 0xff up to this size (in hex)')
45
46 parser.add_argument('--bit', required=False, action="store_true", \
47 default=False, \
48 help='Whether to add the Boot Image Trailer to the \
49 padded image or not (default: False)')
50
51 parser.add_argument('--verbose', required=False, action="store_true", \
52 default=False, \
53 help='Enable verbose mode')
54
55 parser.add_argument('--version', action='version', version='%(prog)s 1.0')
56
57 parser.add_argument('-f', required=False, action="store_true", \
58 default=False, \
59 help='Flash using JLinkExe')
David Brown06446dd2017-01-23 10:19:13 -070060 parser.add_argument('--image-version', dest='image_version',
61 default=1,
62 help='Major part of version number')
David Brown22a6fe32017-01-10 10:40:43 -070063
64 return parser.parse_args()
65
66def trailer_size(word_size):
67 """
68 Compute the size of the image trailer.
69
70 The image trailer size depends on the writable unit size of the
71 flash in question. Given a word size of 1, 2, 4 or 8, return the
72 size, in bytes, of the image trailer. The magic number should be
73 placed at this particular offset from the end of the segment
74 """
75 sizes = { 1: 402, 2: 788, 4: 1560, 8: 3104 }
76 return sizes[word_size]
77
78class Signature(object):
79 """
80 Sign an image appropriately.
81 """
82
83 def compute(self, payload, key_file):
84 # Base computes sha256.
85 ctx = SHA256.new()
86 ctx.update(payload)
87 self.hash = ctx.digest()
88 self.ctx = ctx
89
90 def get_trailer(self):
91 return struct.pack('bxh32s', newtimg.IMAGE_TLV_SHA256,
92 len(self.hash),
93 self.hash)
94
95 def trailer_len(self):
96 return 32 + 4
97
98 def get_flags(self):
99 return newtimg.IMAGE_F_SHA256
100
101class RSASignature(Signature):
102
103 def compute(self, payload, key_file):
104 super(RSASignature, self).compute(payload, key_file)
105 with open(key_file, 'rb') as f:
106 rsa_key = RSA.importKey(f.read())
107 rsa = PKCS1_v1_5.new(rsa_key)
108 self.signature = rsa.sign(self.ctx)
109
110 def trailer_len(self):
111 return super(RSASignature, self).trailer_len() + newtimg.RSA_SIZE
112
113 def get_trailer(self):
114 buf = bytearray(super(RSASignature, self).get_trailer())
115 buf.extend(struct.pack('bxh', newtimg.IMAGE_TLV_RSA2048,
116 newtimg.RSA_SIZE))
117 buf.extend(self.signature)
118 return buf
119
120 def get_flags(self):
121 return newtimg.IMAGE_F_PKCS15_RSA2048_SHA256 | newtimg.IMAGE_F_SHA256
122
123sigs = {
124 'SHA256': Signature,
125 'RSA': RSASignature,
126 }
127
128class Convert():
129 def __init__(self, args):
130 self.args = args
131 if args.verbose:
132 for a in vars(args):
133 print("Arg -> {}: {}".format(a, getattr(args, a)))
134 self.debug = True
135 else:
136 self.debug = False
137
138 self.vtable_offs = int(args.vtable_offs, 16)
139 self.word_size = int(args.word_size)
140
141 self.load_image(args.binary_file)
142 self.validate_header()
143
144 sig = sigs[args.sig_type]()
145 header = self.make_header(sig)
146 assert len(header) == 32
147 self.image[:len(header)] = header
148
149 sig.compute(self.image, args.key_file)
150 self.trailer = sig.get_trailer()
151
152 self.image.extend(self.trailer)
153
154 if args.bit:
155 self.add_trailer(args.pad)
156
157 self.save_image(args.image_file)
158
159 def load_image(self, name):
160 with open(name, 'rb') as f:
161 image = f.read()
162 self.image = bytearray(image)
163
164 def save_image(self, name):
165 with open(name, 'wb') as f:
166 f.write(self.image)
167
168 def validate_header(self):
169 """Ensure that the image has space for a header
170
171 If the image is build with CONFIG_BOOT_HEADER off, the vector
172 table will be at the beginning, rather than the zero padding.
173 Verify that the padding is present.
174 """
175 if self.image[:self.vtable_offs] != bytearray(self.vtable_offs):
176 raise Exception("Image does not have space for header")
177
178 def make_header(self, sig):
179 image_size = len(self.image) - self.vtable_offs
180 tlv_size = sig.trailer_len()
181 key_id = 0
182 hd = struct.pack('IHBxHxxIIBBHI4x',
183 newtimg.IMAGE_MAGIC,
184 tlv_size,
185 key_id,
186 self.vtable_offs,
187 image_size,
188 sig.get_flags(),
David Brown06446dd2017-01-23 10:19:13 -0700189 int(self.args.image_version), 0, 0, 0)
David Brown22a6fe32017-01-10 10:40:43 -0700190 return hd
191
192 def add_trailer(self, pad):
193 """
194 Add the image trailer, to indicate to the bootloader that this
195 image should be flashed
196 """
197 if not pad:
198 raise Exception("Must specify image length with --pad to use --bit")
199 pad = int(pad, 16)
200
201 if len(self.image) > pad:
202 raise Exception("Image is too large for padding")
203
204 self.image.extend(b'\xFF' * (pad - len(self.image)))
205
206 magic = struct.pack('4I', *newtimg.BOOT_IMG_MAGIC)
207 pos = pad - trailer_size(self.word_size)
208 self.image[pos:pos + len(magic)] = magic
209
210def main(argv):
211 args = get_args()
David Brown22a6fe32017-01-10 10:40:43 -0700212
213 conv = Convert(args)
214
215if __name__ == "__main__":
216 main(sys.argv)