blob: 58f7088652a787d5c6810df1aa2aec9b840ff904 [file] [log] [blame]
Tamas Banf70ef8c2017-12-19 15:35:09 +00001# Copyright 2017 Linaro Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""
16Image signing and management.
17"""
18
19from . import version as versmod
20import hashlib
21import struct
22
23IMAGE_MAGIC = 0x96f3b83d
24IMAGE_HEADER_SIZE = 32
25
26# Image header flags.
27IMAGE_F = {
28 'PIC': 0x0000001,
29 'NON_BOOTABLE': 0x0000010, }
30
31TLV_VALUES = {
32 'KEYHASH': 0x01,
Tamas Ban581034a2017-12-19 19:54:37 +000033 'SHA256' : 0x10,
34 'RSA2048': 0x20, }
Tamas Banf70ef8c2017-12-19 15:35:09 +000035
36TLV_INFO_SIZE = 4
37TLV_INFO_MAGIC = 0x6907
Tamas Banf70ef8c2017-12-19 15:35:09 +000038
39# Sizes of the image trailer, depending on flash write size.
40trailer_sizes = {
41 write_size: 128 * 3 * write_size + 8 * 2 + 16
42 for write_size in [1, 2, 4, 8]
43}
44
45boot_magic = bytes([
46 0x77, 0xc2, 0x95, 0xf3,
47 0x60, 0xd2, 0xef, 0x7f,
48 0x35, 0x52, 0x50, 0x0f,
49 0x2c, 0xb6, 0x79, 0x80, ])
50
51class TLV():
52 def __init__(self):
53 self.buf = bytearray()
54
55 def add(self, kind, payload):
56 """Add a TLV record. Kind should be a string found in TLV_VALUES above."""
57 buf = struct.pack('<BBH', TLV_VALUES[kind], 0, len(payload))
58 self.buf += buf
59 self.buf += payload
60
61 def get(self):
62 header = struct.pack('<HH', TLV_INFO_MAGIC, TLV_INFO_SIZE + len(self.buf))
63 return header + bytes(self.buf)
64
65class Image():
66 @classmethod
67 def load(cls, path, included_header=False, **kwargs):
68 """Load an image from a given file"""
69 with open(path, 'rb') as f:
70 payload = f.read()
71 obj = cls(**kwargs)
72 obj.payload = payload
73
74 # Add the image header if needed.
75 if not included_header and obj.header_size > 0:
76 obj.payload = (b'\000' * obj.header_size) + obj.payload
77
78 obj.check()
79 return obj
80
Oliver Swede21440442018-07-10 09:31:32 +010081 def __init__(self, version, header_size=IMAGE_HEADER_SIZE, pad=0):
82 self.version = version
Tamas Banf70ef8c2017-12-19 15:35:09 +000083 self.header_size = header_size or IMAGE_HEADER_SIZE
84 self.pad = pad
85
86 def __repr__(self):
87 return "<Image version={}, header_size={}, pad={}, payloadlen=0x{:x}>".format(
88 self.version,
89 self.header_size,
90 self.pad,
91 len(self.payload))
92
93 def save(self, path):
94 with open(path, 'wb') as f:
95 f.write(self.payload)
96
97 def check(self):
98 """Perform some sanity checking of the image."""
99 # If there is a header requested, make sure that the image
100 # starts with all zeros.
101 if self.header_size > 0:
102 if any(v != 0 for v in self.payload[0:self.header_size]):
103 raise Exception("Padding requested, but image does not start with zeros")
104
105 def sign(self, key):
106 self.add_header(key)
107
108 tlv = TLV()
109
Tamas Banf70ef8c2017-12-19 15:35:09 +0000110 sha = hashlib.sha256()
111 sha.update(self.payload)
112 digest = sha.digest()
113
114 tlv.add('SHA256', digest)
115
116 if key is not None:
117 pub = key.get_public_bytes()
118 sha = hashlib.sha256()
119 sha.update(pub)
120 pubbytes = sha.digest()
121 tlv.add('KEYHASH', pubbytes)
122
123 sig = key.sign(self.payload)
124 tlv.add(key.sig_tlv(), sig)
125
126 self.payload += tlv.get()
127
128 def add_header(self, key):
129 """Install the image header.
130
131 The key is needed to know the type of signature, and
132 approximate the size of the signature."""
133
134 flags = 0
Tamas Banf70ef8c2017-12-19 15:35:09 +0000135
136 fmt = ('<' +
137 # type ImageHdr struct {
138 'I' + # Magic uint32
Oliver Swede285dacd2018-08-08 10:12:47 +0100139 'I' + # LoadAddr uint32
Tamas Banf70ef8c2017-12-19 15:35:09 +0000140 'H' + # HdrSz uint16
Oliver Swede285dacd2018-08-08 10:12:47 +0100141 'H' + # Pad1 uint16
Tamas Banf70ef8c2017-12-19 15:35:09 +0000142 'I' + # ImgSz uint32
143 'I' + # Flags uint32
144 'BBHI' + # Vers ImageVersion
Oliver Swede285dacd2018-08-08 10:12:47 +0100145 'I' # Pad2 uint32
Tamas Banf70ef8c2017-12-19 15:35:09 +0000146 ) # }
147 assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
148 header = struct.pack(fmt,
149 IMAGE_MAGIC,
Oliver Swede285dacd2018-08-08 10:12:47 +0100150 0, # LoadAddr
Tamas Banf70ef8c2017-12-19 15:35:09 +0000151 self.header_size,
Oliver Swede285dacd2018-08-08 10:12:47 +0100152 0, # Pad1
Tamas Banf70ef8c2017-12-19 15:35:09 +0000153 len(self.payload) - self.header_size, # ImageSz
154 flags, # Flags
155 self.version.major,
156 self.version.minor or 0,
157 self.version.revision or 0,
158 self.version.build or 0,
Oliver Swede285dacd2018-08-08 10:12:47 +0100159 0) # Pad2
Tamas Banf70ef8c2017-12-19 15:35:09 +0000160 self.payload = bytearray(self.payload)
161 self.payload[:len(header)] = header
162
163 def pad_to(self, size, align):
164 """Pad the image to the given size, with the given flash alignment."""
165 tsize = trailer_sizes[align]
166 padding = size - (len(self.payload) + tsize)
167 if padding < 0:
168 msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds requested size 0x{:x}".format(
169 len(self.payload), tsize, size)
170 raise Exception(msg)
171 pbytes = b'\xff' * padding
172 pbytes += b'\xff' * (tsize - len(boot_magic))
173 pbytes += boot_magic
Oliver Swede21440442018-07-10 09:31:32 +0100174 self.payload += pbytes