blob: 90fee88b97fec16f2464127a570186f1d8f1b13a [file] [log] [blame]
David Brownc8751d72019-01-29 11:04:35 -07001# Copyright 2019 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"""
16SUIT Image signing and management.
17"""
18
19from . import image
20import cbor2
21import hashlib
22import os.path
23import struct
24
25IMAGE_MAGIC = 0x96f3b83e
26IMAGE_HEADER_SIZE = 32
27BIN_EXT = "bin"
28INTEL_HEX_EXT = "hex"
29DEFAULT_MAX_SECTORS = 128
30
31MANIFEST_INFO_SIZE = 4
32MANIFEST_INFO_MAGIC = 0x6917
33
34STRUCT_ENDIAN_DICT = {
35 'little': '<',
36 'big:': '>'
37}
38
39class Suit():
40
41 def __init__(self, endian):
42 self.payload = None
43 self.sequence = 0
44 self.endian = endian
45
46 def add_payload(self, payload):
47 self.payload = payload
48
49 def set_sequence(self, sequence):
50 self.sequence = sequence
51
52 def generate(self, key):
53 manifest = self._gen_manifest(self.payload)
54 sig = self._gen_sig(manifest, key)
55
56 body = cbor2.dumps({1: sig, 2: manifest})
57
58 return (struct.pack(STRUCT_ENDIAN_DICT[self.endian] +
59 "HH", MANIFEST_INFO_MAGIC, MANIFEST_INFO_SIZE + len(body)) +
60 body)
61
62 def _get_digest(self, payload, prot):
63 """Make a COSE Digest of the payload, with the given
64 CBOR-encoded protected data. This is defined by SUIT."""
65 block = cbor2.dumps(["Digest", prot, payload])
66 sha = hashlib.sha256()
67 sha.update(block)
68 digest = sha.digest()
69
70 return [prot, {}, None, digest]
71
72 def _gen_manifest(self, payload):
73 """Generate the actual SUIT manifest itself."""
74 prot = cbor2.dumps({1: 41})
75 digest = self._get_digest(payload, prot)
76
77 return cbor2.dumps({
78 1: 1,
79 2: self.sequence,
80 3: [ { 1: [b"0"], 2: len(payload), 3: digest } ] })
81
82 def _gen_sig(self, body, key):
83 """Generate a COSE signature wrapper for the given payload,
84 signed with the given key. This doesn't return CBOR, but a
85 Python data structure intended to be included within CBOR."""
86 body_prot = cbor2.dumps({3: 0})
87 sig_prot = cbor2.dumps({1: -37}) # TODO: Hardcoded, -37 is RS256.
88
89 # The block we actually sign.
90 sig_block = cbor2.dumps(["Signature", body_prot, sig_prot, b"", body])
91
92 sig = key.sign(bytes(sig_block))
93
94 return cbor2.CBORTag(98, [body_prot, {}, None, [
95 [sig_prot, {4: b"key-id-here"}, sig]]])
96
97class Image(image.Image):
98
99 def __init__(self, **kwargs):
100 image.Image.__init__(self, **kwargs)
101
102 def check(self):
103 """Perform some sanity checking of the image."""
104 # If there is a header requested, make sure that the image
105 # starts with all zeros.
106 if self.header_size > 0:
107 if any(v != 0 for v in self.payload[0:self.header_size]):
108 raise Exception("Padding requested, but image does not start with zeros")
109 if self.slot_size > 0:
110 tsize = self._trailer_size(self.align, self.max_sectors,
111 self.overwrite_only)
112 padding = self.slot_size - (len(self.payload) + tsize)
113 if padding < 0:
114 msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds requested size 0x{:x}".format(
115 len(self.payload), tsize, self.slot_size)
116 raise Exception(msg)
117
118 def create(self, key, enckey):
119 if enckey is not None:
120 raise Exception("SUIT support does not yet support encryption")
121
122 self.add_header(None)
123
124 s = Suit(self.endian)
125 s.add_payload(self.payload)
126 # TODO: Where does this sequence number come from?
127 s.set_sequence(self.version.build)
128
129 self.payload += s.generate(key)
130
131 def _image_magic(self):
132 return IMAGE_MAGIC