blob: 7733a4a91bbf9eedd66a22a81a1cca5bddb80d88 [file] [log] [blame]
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Tools for reading, verifying and applying Chrome OS update payloads."""
6
Sen Jiang349fd292015-11-16 17:28:09 -08007from __future__ import print_function
8
Gilad Arnold553b0ec2013-01-26 01:00:39 -08009import hashlib
10import struct
11
Sen Jiang5a652862017-09-27 16:35:03 -070012from update_payload import applier
Sen Jiang5a652862017-09-27 16:35:03 -070013from update_payload import checker
14from update_payload import common
Sen Jiang5a652862017-09-27 16:35:03 -070015from update_payload import update_metadata_pb2
Amin Hassanib05a65a2017-12-18 15:15:32 -080016from update_payload.error import PayloadError
Gilad Arnold553b0ec2013-01-26 01:00:39 -080017
18
19#
20# Helper functions.
21#
22def _ReadInt(file_obj, size, is_unsigned, hasher=None):
Gilad Arnold5502b562013-03-08 13:22:31 -080023 """Reads a binary-encoded integer from a file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080024
25 It will do the correct conversion based on the reported size and whether or
26 not a signed number is expected. Assumes a network (big-endian) byte
27 ordering.
28
29 Args:
30 file_obj: a file object
31 size: the integer size in bytes (2, 4 or 8)
32 is_unsigned: whether it is signed or not
33 hasher: an optional hasher to pass the value through
Sen Jiang349fd292015-11-16 17:28:09 -080034
Gilad Arnold553b0ec2013-01-26 01:00:39 -080035 Returns:
36 An "unpacked" (Python) integer value.
Sen Jiang349fd292015-11-16 17:28:09 -080037
Gilad Arnold553b0ec2013-01-26 01:00:39 -080038 Raises:
39 PayloadError if an read error occurred.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080040 """
Gilad Arnold5502b562013-03-08 13:22:31 -080041 return struct.unpack(common.IntPackingFmtStr(size, is_unsigned),
42 common.Read(file_obj, size, hasher=hasher))[0]
Gilad Arnold553b0ec2013-01-26 01:00:39 -080043
44
45#
46# Update payload.
47#
48class Payload(object):
49 """Chrome OS update payload processor."""
50
51 class _PayloadHeader(object):
52 """Update payload header struct."""
53
Alex Deymoef497352015-10-15 09:14:58 -070054 # Header constants; sizes are in bytes.
55 _MAGIC = 'CrAU'
56 _VERSION_SIZE = 8
57 _MANIFEST_LEN_SIZE = 8
58 _METADATA_SIGNATURE_LEN_SIZE = 4
Gilad Arnold553b0ec2013-01-26 01:00:39 -080059
Alex Deymoef497352015-10-15 09:14:58 -070060 def __init__(self):
61 self.version = None
62 self.manifest_len = None
63 self.metadata_signature_len = None
64 self.size = None
65
66 def ReadFromPayload(self, payload_file, hasher=None):
67 """Reads the payload header from a file.
68
69 Reads the payload header from the |payload_file| and updates the |hasher|
70 if one is passed. The parsed header is stored in the _PayloadHeader
71 instance attributes.
72
73 Args:
74 payload_file: a file object
75 hasher: an optional hasher to pass the value through
Sen Jiang349fd292015-11-16 17:28:09 -080076
Alex Deymoef497352015-10-15 09:14:58 -070077 Returns:
78 None.
Sen Jiang349fd292015-11-16 17:28:09 -080079
Alex Deymoef497352015-10-15 09:14:58 -070080 Raises:
81 PayloadError if a read error occurred or the header is invalid.
82 """
83 # Verify magic
84 magic = common.Read(payload_file, len(self._MAGIC), hasher=hasher)
85 if magic != self._MAGIC:
86 raise PayloadError('invalid payload magic: %s' % magic)
87
88 self.version = _ReadInt(payload_file, self._VERSION_SIZE, True,
89 hasher=hasher)
90 self.manifest_len = _ReadInt(payload_file, self._MANIFEST_LEN_SIZE, True,
91 hasher=hasher)
92 self.size = (len(self._MAGIC) + self._VERSION_SIZE +
93 self._MANIFEST_LEN_SIZE)
94 self.metadata_signature_len = 0
95
96 if self.version == common.BRILLO_MAJOR_PAYLOAD_VERSION:
97 self.size += self._METADATA_SIGNATURE_LEN_SIZE
98 self.metadata_signature_len = _ReadInt(
99 payload_file, self._METADATA_SIGNATURE_LEN_SIZE, True,
100 hasher=hasher)
101
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800102
103 def __init__(self, payload_file):
104 """Initialize the payload object.
105
106 Args:
107 payload_file: update payload file object open for reading
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800108 """
109 self.payload_file = payload_file
110 self.manifest_hasher = None
111 self.is_init = False
112 self.header = None
113 self.manifest = None
Alex Deymoef497352015-10-15 09:14:58 -0700114 self.data_offset = None
115 self.metadata_signature = None
116 self.metadata_size = None
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800117
118 def _ReadHeader(self):
119 """Reads and returns the payload header.
120
121 Returns:
122 A payload header object.
Sen Jiang349fd292015-11-16 17:28:09 -0800123
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800124 Raises:
125 PayloadError if a read error occurred.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800126 """
Alex Deymoef497352015-10-15 09:14:58 -0700127 header = self._PayloadHeader()
128 header.ReadFromPayload(self.payload_file, self.manifest_hasher)
129 return header
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800130
131 def _ReadManifest(self):
132 """Reads and returns the payload manifest.
133
134 Returns:
135 A string containing the payload manifest in binary form.
Sen Jiang349fd292015-11-16 17:28:09 -0800136
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800137 Raises:
138 PayloadError if a read error occurred.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800139 """
140 if not self.header:
141 raise PayloadError('payload header not present')
142
143 return common.Read(self.payload_file, self.header.manifest_len,
144 hasher=self.manifest_hasher)
145
Alex Deymoef497352015-10-15 09:14:58 -0700146 def _ReadMetadataSignature(self):
147 """Reads and returns the metadata signatures.
148
149 Returns:
150 A string containing the metadata signatures protobuf in binary form or
151 an empty string if no metadata signature found in the payload.
Sen Jiang349fd292015-11-16 17:28:09 -0800152
Alex Deymoef497352015-10-15 09:14:58 -0700153 Raises:
154 PayloadError if a read error occurred.
Alex Deymoef497352015-10-15 09:14:58 -0700155 """
156 if not self.header:
157 raise PayloadError('payload header not present')
158
159 return common.Read(
160 self.payload_file, self.header.metadata_signature_len,
161 offset=self.header.size + self.header.manifest_len)
162
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800163 def ReadDataBlob(self, offset, length):
164 """Reads and returns a single data blob from the update payload.
165
166 Args:
167 offset: offset to the beginning of the blob from the end of the manifest
168 length: the blob's length
Sen Jiang349fd292015-11-16 17:28:09 -0800169
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800170 Returns:
171 A string containing the raw blob data.
Sen Jiang349fd292015-11-16 17:28:09 -0800172
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800173 Raises:
174 PayloadError if a read error occurred.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800175 """
176 return common.Read(self.payload_file, length,
177 offset=self.data_offset + offset)
178
179 def Init(self):
180 """Initializes the payload object.
181
182 This is a prerequisite for any other public API call.
183
184 Raises:
185 PayloadError if object already initialized or fails to initialize
186 correctly.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800187 """
188 if self.is_init:
189 raise PayloadError('payload object already initialized')
190
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800191 self.manifest_hasher = hashlib.sha256()
192
193 # Read the file header.
194 self.header = self._ReadHeader()
195
196 # Read the manifest.
197 manifest_raw = self._ReadManifest()
198 self.manifest = update_metadata_pb2.DeltaArchiveManifest()
199 self.manifest.ParseFromString(manifest_raw)
200
Alex Deymoef497352015-10-15 09:14:58 -0700201 # Read the metadata signature (if any).
202 metadata_signature_raw = self._ReadMetadataSignature()
203 if metadata_signature_raw:
204 self.metadata_signature = update_metadata_pb2.Signatures()
205 self.metadata_signature.ParseFromString(metadata_signature_raw)
206
207 self.metadata_size = self.header.size + self.header.manifest_len
208 self.data_offset = self.metadata_size + self.header.metadata_signature_len
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800209
210 self.is_init = True
211
Don Garrett432d6012013-05-10 15:01:36 -0700212 def Describe(self):
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700213 """Emits the payload embedded description data to standard output."""
Don Garrett432d6012013-05-10 15:01:36 -0700214 def _DescribeImageInfo(description, image_info):
Sen Jiang5a652862017-09-27 16:35:03 -0700215 """Display info about the image."""
Don Garrett432d6012013-05-10 15:01:36 -0700216 def _DisplayIndentedValue(name, value):
Sen Jiang349fd292015-11-16 17:28:09 -0800217 print(' {:<14} {}'.format(name+':', value))
Don Garrett432d6012013-05-10 15:01:36 -0700218
Sen Jiang349fd292015-11-16 17:28:09 -0800219 print('%s:' % description)
Don Garrett432d6012013-05-10 15:01:36 -0700220 _DisplayIndentedValue('Channel', image_info.channel)
221 _DisplayIndentedValue('Board', image_info.board)
222 _DisplayIndentedValue('Version', image_info.version)
223 _DisplayIndentedValue('Key', image_info.key)
224
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700225 if image_info.build_channel != image_info.channel:
Don Garrett432d6012013-05-10 15:01:36 -0700226 _DisplayIndentedValue('Build channel', image_info.build_channel)
227
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700228 if image_info.build_version != image_info.version:
Don Garrett432d6012013-05-10 15:01:36 -0700229 _DisplayIndentedValue('Build version', image_info.build_version)
230
231 if self.manifest.HasField('old_image_info'):
232 _DescribeImageInfo('Old Image', self.manifest.old_image_info)
233
234 if self.manifest.HasField('new_image_info'):
235 _DescribeImageInfo('New Image', self.manifest.new_image_info)
236
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800237 def _AssertInit(self):
238 """Raises an exception if the object was not initialized."""
239 if not self.is_init:
240 raise PayloadError('payload object not initialized')
241
242 def ResetFile(self):
243 """Resets the offset of the payload file to right past the manifest."""
244 self.payload_file.seek(self.data_offset)
245
246 def IsDelta(self):
247 """Returns True iff the payload appears to be a delta."""
248 self._AssertInit()
249 return (self.manifest.HasField('old_kernel_info') or
Sen Jiang349fd292015-11-16 17:28:09 -0800250 self.manifest.HasField('old_rootfs_info') or
251 any(partition.HasField('old_partition_info')
252 for partition in self.manifest.partitions))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800253
254 def IsFull(self):
255 """Returns True iff the payload appears to be a full."""
256 return not self.IsDelta()
257
258 def Check(self, pubkey_file_name=None, metadata_sig_file=None,
259 report_out_file=None, assert_type=None, block_size=0,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700260 rootfs_part_size=0, kernel_part_size=0, allow_unhashed=False,
261 disabled_tests=()):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800262 """Checks the payload integrity.
263
264 Args:
265 pubkey_file_name: public key used for signature verification
266 metadata_sig_file: metadata signature, if verification is desired
267 report_out_file: file object to dump the report to
268 assert_type: assert that payload is either 'full' or 'delta'
269 block_size: expected filesystem / payload block size
Gilad Arnold382df5c2013-05-03 12:49:28 -0700270 rootfs_part_size: the size of (physical) rootfs partitions in bytes
271 kernel_part_size: the size of (physical) kernel partitions in bytes
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800272 allow_unhashed: allow unhashed operation blobs
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700273 disabled_tests: list of tests to disable
Sen Jiang349fd292015-11-16 17:28:09 -0800274
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800275 Raises:
276 PayloadError if payload verification failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800277 """
278 self._AssertInit()
279
280 # Create a short-lived payload checker object and run it.
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700281 helper = checker.PayloadChecker(
282 self, assert_type=assert_type, block_size=block_size,
283 allow_unhashed=allow_unhashed, disabled_tests=disabled_tests)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800284 helper.Run(pubkey_file_name=pubkey_file_name,
285 metadata_sig_file=metadata_sig_file,
Gilad Arnold382df5c2013-05-03 12:49:28 -0700286 rootfs_part_size=rootfs_part_size,
287 kernel_part_size=kernel_part_size,
Gilad Arnoldeaed0d12013-04-30 15:38:22 -0700288 report_out_file=report_out_file)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800289
Gilad Arnold16416602013-05-04 21:40:39 -0700290 def Apply(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
Gilad Arnold21a02502013-08-22 16:59:48 -0700291 old_rootfs_part=None, bsdiff_in_place=True, bspatch_path=None,
Amin Hassani6be71682017-12-01 10:46:45 -0800292 puffpatch_path=None, truncate_to_expected_size=True):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800293 """Applies the update payload.
294
295 Args:
Gilad Arnold16416602013-05-04 21:40:39 -0700296 new_kernel_part: name of dest kernel partition file
297 new_rootfs_part: name of dest rootfs partition file
298 old_kernel_part: name of source kernel partition file (optional)
299 old_rootfs_part: name of source rootfs partition file (optional)
Gilad Arnold272a4992013-05-08 13:12:53 -0700300 bsdiff_in_place: whether to perform BSDIFF operations in-place (optional)
Gilad Arnold21a02502013-08-22 16:59:48 -0700301 bspatch_path: path to the bspatch binary (optional)
Amin Hassani6be71682017-12-01 10:46:45 -0800302 puffpatch_path: path to the puffpatch binary (optional)
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700303 truncate_to_expected_size: whether to truncate the resulting partitions
304 to their expected sizes, as specified in the
305 payload (optional)
Sen Jiang349fd292015-11-16 17:28:09 -0800306
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800307 Raises:
308 PayloadError if payload application failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800309 """
310 self._AssertInit()
311
312 # Create a short-lived payload applier object and run it.
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700313 helper = applier.PayloadApplier(
Gilad Arnold21a02502013-08-22 16:59:48 -0700314 self, bsdiff_in_place=bsdiff_in_place, bspatch_path=bspatch_path,
Amin Hassani6be71682017-12-01 10:46:45 -0800315 puffpatch_path=puffpatch_path,
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700316 truncate_to_expected_size=truncate_to_expected_size)
Gilad Arnold16416602013-05-04 21:40:39 -0700317 helper.Run(new_kernel_part, new_rootfs_part,
318 old_kernel_part=old_kernel_part,
319 old_rootfs_part=old_rootfs_part)