| Amin Hassani | f94b643 | 2018-01-26 17:39:47 -0800 | [diff] [blame] | 1 | # | 
|  | 2 | # Copyright (C) 2013 The Android Open Source Project | 
|  | 3 | # | 
|  | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 5 | # you may not use this file except in compliance with the License. | 
|  | 6 | # You may obtain a copy of the License at | 
|  | 7 | # | 
|  | 8 | #      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 9 | # | 
|  | 10 | # Unless required by applicable law or agreed to in writing, software | 
|  | 11 | # distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 13 | # See the License for the specific language governing permissions and | 
|  | 14 | # limitations under the License. | 
|  | 15 | # | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 16 |  | 
|  | 17 | """Tools for reading, verifying and applying Chrome OS update payloads.""" | 
|  | 18 |  | 
| Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame] | 19 | from __future__ import absolute_import | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 20 | from __future__ import print_function | 
| Kelvin Zhang | b8d776c | 2022-09-07 03:12:49 +0000 | [diff] [blame] | 21 | import binascii | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 22 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 23 | import hashlib | 
| Kelvin Zhang | 9e7a6db | 2020-08-13 14:55:58 -0400 | [diff] [blame] | 24 | import io | 
| Kelvin Zhang | 7977564 | 2021-02-19 16:05:08 -0500 | [diff] [blame] | 25 | import mmap | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 26 | import struct | 
| Kelvin Zhang | 9e7a6db | 2020-08-13 14:55:58 -0400 | [diff] [blame] | 27 | import zipfile | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 28 |  | 
| Kelvin Zhang | 73202a9 | 2022-06-02 10:19:54 -0700 | [diff] [blame] | 29 | import update_metadata_pb2 | 
|  | 30 |  | 
| Sen Jiang | c2527f4 | 2017-09-27 16:35:03 -0700 | [diff] [blame] | 31 | from update_payload import checker | 
|  | 32 | from update_payload import common | 
| Amin Hassani | b05a65a | 2017-12-18 15:15:32 -0800 | [diff] [blame] | 33 | from update_payload.error import PayloadError | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 34 |  | 
|  | 35 |  | 
|  | 36 | # | 
|  | 37 | # Helper functions. | 
|  | 38 | # | 
|  | 39 | def _ReadInt(file_obj, size, is_unsigned, hasher=None): | 
| Gilad Arnold | 5502b56 | 2013-03-08 13:22:31 -0800 | [diff] [blame] | 40 | """Reads a binary-encoded integer from a file. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 41 |  | 
|  | 42 | It will do the correct conversion based on the reported size and whether or | 
|  | 43 | not a signed number is expected. Assumes a network (big-endian) byte | 
|  | 44 | ordering. | 
|  | 45 |  | 
|  | 46 | Args: | 
|  | 47 | file_obj: a file object | 
|  | 48 | size: the integer size in bytes (2, 4 or 8) | 
|  | 49 | is_unsigned: whether it is signed or not | 
|  | 50 | hasher: an optional hasher to pass the value through | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 51 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 52 | Returns: | 
|  | 53 | An "unpacked" (Python) integer value. | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 54 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 55 | Raises: | 
|  | 56 | PayloadError if an read error occurred. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 57 | """ | 
| Gilad Arnold | 5502b56 | 2013-03-08 13:22:31 -0800 | [diff] [blame] | 58 | return struct.unpack(common.IntPackingFmtStr(size, is_unsigned), | 
|  | 59 | common.Read(file_obj, size, hasher=hasher))[0] | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 60 |  | 
|  | 61 |  | 
|  | 62 | # | 
|  | 63 | # Update payload. | 
|  | 64 | # | 
|  | 65 | class Payload(object): | 
|  | 66 | """Chrome OS update payload processor.""" | 
|  | 67 |  | 
|  | 68 | class _PayloadHeader(object): | 
|  | 69 | """Update payload header struct.""" | 
|  | 70 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 71 | # Header constants; sizes are in bytes. | 
| Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame] | 72 | _MAGIC = b'CrAU' | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 73 | _VERSION_SIZE = 8 | 
|  | 74 | _MANIFEST_LEN_SIZE = 8 | 
|  | 75 | _METADATA_SIGNATURE_LEN_SIZE = 4 | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 76 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 77 | def __init__(self): | 
|  | 78 | self.version = None | 
|  | 79 | self.manifest_len = None | 
|  | 80 | self.metadata_signature_len = None | 
|  | 81 | self.size = None | 
|  | 82 |  | 
|  | 83 | def ReadFromPayload(self, payload_file, hasher=None): | 
|  | 84 | """Reads the payload header from a file. | 
|  | 85 |  | 
|  | 86 | Reads the payload header from the |payload_file| and updates the |hasher| | 
|  | 87 | if one is passed. The parsed header is stored in the _PayloadHeader | 
|  | 88 | instance attributes. | 
|  | 89 |  | 
|  | 90 | Args: | 
|  | 91 | payload_file: a file object | 
|  | 92 | hasher: an optional hasher to pass the value through | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 93 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 94 | Returns: | 
|  | 95 | None. | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 96 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 97 | Raises: | 
|  | 98 | PayloadError if a read error occurred or the header is invalid. | 
|  | 99 | """ | 
|  | 100 | # Verify magic | 
|  | 101 | magic = common.Read(payload_file, len(self._MAGIC), hasher=hasher) | 
|  | 102 | if magic != self._MAGIC: | 
|  | 103 | raise PayloadError('invalid payload magic: %s' % magic) | 
|  | 104 |  | 
|  | 105 | self.version = _ReadInt(payload_file, self._VERSION_SIZE, True, | 
|  | 106 | hasher=hasher) | 
|  | 107 | self.manifest_len = _ReadInt(payload_file, self._MANIFEST_LEN_SIZE, True, | 
|  | 108 | hasher=hasher) | 
|  | 109 | self.size = (len(self._MAGIC) + self._VERSION_SIZE + | 
|  | 110 | self._MANIFEST_LEN_SIZE) | 
|  | 111 | self.metadata_signature_len = 0 | 
|  | 112 |  | 
|  | 113 | if self.version == common.BRILLO_MAJOR_PAYLOAD_VERSION: | 
|  | 114 | self.size += self._METADATA_SIGNATURE_LEN_SIZE | 
|  | 115 | self.metadata_signature_len = _ReadInt( | 
|  | 116 | payload_file, self._METADATA_SIGNATURE_LEN_SIZE, True, | 
|  | 117 | hasher=hasher) | 
|  | 118 |  | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 119 | def __init__(self, payload_file, payload_file_offset=0): | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 120 | """Initialize the payload object. | 
|  | 121 |  | 
|  | 122 | Args: | 
|  | 123 | payload_file: update payload file object open for reading | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 124 | payload_file_offset: the offset of the actual payload | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 125 | """ | 
| Kelvin Zhang | 9e7a6db | 2020-08-13 14:55:58 -0400 | [diff] [blame] | 126 | if zipfile.is_zipfile(payload_file): | 
| Kelvin Zhang | 0a2493d | 2022-08-26 17:45:42 +0000 | [diff] [blame] | 127 | self.name = payload_file | 
| Kelvin Zhang | 9e7a6db | 2020-08-13 14:55:58 -0400 | [diff] [blame] | 128 | with zipfile.ZipFile(payload_file) as zfp: | 
| Kelvin Zhang | b7162c1 | 2022-08-24 23:15:04 +0000 | [diff] [blame] | 129 | if "payload.bin" not in zfp.namelist(): | 
|  | 130 | raise ValueError(f"payload.bin missing in archive {payload_file}") | 
| Kelvin Zhang | 7977564 | 2021-02-19 16:05:08 -0500 | [diff] [blame] | 131 | self.payload_file = zfp.open("payload.bin", "r") | 
| Kelvin Zhang | 576efc5 | 2020-12-01 12:06:40 -0500 | [diff] [blame] | 132 | elif isinstance(payload_file, str): | 
| Kelvin Zhang | 0a2493d | 2022-08-26 17:45:42 +0000 | [diff] [blame] | 133 | self.name = payload_file | 
| Kelvin Zhang | 7977564 | 2021-02-19 16:05:08 -0500 | [diff] [blame] | 134 | payload_fp = open(payload_file, "rb") | 
| Kelvin Zhang | faddb7e | 2021-06-23 15:53:48 -0400 | [diff] [blame] | 135 | payload_bytes = mmap.mmap( | 
|  | 136 | payload_fp.fileno(), 0, access=mmap.ACCESS_READ) | 
| Kelvin Zhang | 7977564 | 2021-02-19 16:05:08 -0500 | [diff] [blame] | 137 | self.payload_file = io.BytesIO(payload_bytes) | 
| Kelvin Zhang | 576efc5 | 2020-12-01 12:06:40 -0500 | [diff] [blame] | 138 | else: | 
| Kelvin Zhang | 0a2493d | 2022-08-26 17:45:42 +0000 | [diff] [blame] | 139 | self.name = payload_file.name | 
| Kelvin Zhang | 576efc5 | 2020-12-01 12:06:40 -0500 | [diff] [blame] | 140 | self.payload_file = payload_file | 
| Kelvin Zhang | c890e48 | 2023-03-29 13:22:04 -0700 | [diff] [blame] | 141 | self.payload_file_size = self.payload_file.seek(0, io.SEEK_END) | 
|  | 142 | self.payload_file.seek(0, io.SEEK_SET) | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 143 | self.payload_file_offset = payload_file_offset | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 144 | self.manifest_hasher = None | 
|  | 145 | self.is_init = False | 
|  | 146 | self.header = None | 
|  | 147 | self.manifest = None | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 148 | self.data_offset = None | 
|  | 149 | self.metadata_signature = None | 
| Kelvin Zhang | faddb7e | 2021-06-23 15:53:48 -0400 | [diff] [blame] | 150 | self.payload_signature = None | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 151 | self.metadata_size = None | 
| Kelvin Zhang | f9f99b0 | 2022-08-24 23:02:07 +0000 | [diff] [blame] | 152 | self.Init() | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 153 |  | 
| Kelvin Zhang | 8c85655 | 2021-09-07 21:15:49 -0700 | [diff] [blame] | 154 | @property | 
|  | 155 | def is_incremental(self): | 
|  | 156 | return any([part.HasField("old_partition_info") for part in self.manifest.partitions]) | 
|  | 157 |  | 
|  | 158 | @property | 
|  | 159 | def is_partial(self): | 
|  | 160 | return self.manifest.partial_update | 
|  | 161 |  | 
| Kelvin Zhang | b7162c1 | 2022-08-24 23:15:04 +0000 | [diff] [blame] | 162 | @property | 
|  | 163 | def total_data_length(self): | 
|  | 164 | """Return the total data length of this payload, excluding payload | 
|  | 165 | signature at the very end. | 
|  | 166 | """ | 
|  | 167 | # Operations are sorted in ascending data_offset order, so iterating | 
|  | 168 | # backwards and find the first one with non zero data_offset will tell | 
|  | 169 | # us total data length | 
|  | 170 | for partition in reversed(self.manifest.partitions): | 
|  | 171 | for op in reversed(partition.operations): | 
| Kelvin Zhang | 4decfcd | 2022-09-07 03:21:25 +0000 | [diff] [blame] | 172 | if op.data_length > 0: | 
| Kelvin Zhang | b7162c1 | 2022-08-24 23:15:04 +0000 | [diff] [blame] | 173 | return op.data_offset + op.data_length | 
|  | 174 | return 0 | 
|  | 175 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 176 | def _ReadHeader(self): | 
|  | 177 | """Reads and returns the payload header. | 
|  | 178 |  | 
|  | 179 | Returns: | 
|  | 180 | A payload header object. | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 181 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 182 | Raises: | 
|  | 183 | PayloadError if a read error occurred. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 184 | """ | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 185 | header = self._PayloadHeader() | 
|  | 186 | header.ReadFromPayload(self.payload_file, self.manifest_hasher) | 
|  | 187 | return header | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 188 |  | 
|  | 189 | def _ReadManifest(self): | 
|  | 190 | """Reads and returns the payload manifest. | 
|  | 191 |  | 
|  | 192 | Returns: | 
|  | 193 | A string containing the payload manifest in binary form. | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 194 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 195 | Raises: | 
|  | 196 | PayloadError if a read error occurred. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 197 | """ | 
|  | 198 | if not self.header: | 
|  | 199 | raise PayloadError('payload header not present') | 
|  | 200 |  | 
|  | 201 | return common.Read(self.payload_file, self.header.manifest_len, | 
|  | 202 | hasher=self.manifest_hasher) | 
|  | 203 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 204 | def _ReadMetadataSignature(self): | 
|  | 205 | """Reads and returns the metadata signatures. | 
|  | 206 |  | 
|  | 207 | Returns: | 
|  | 208 | A string containing the metadata signatures protobuf in binary form or | 
|  | 209 | an empty string if no metadata signature found in the payload. | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 210 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 211 | Raises: | 
|  | 212 | PayloadError if a read error occurred. | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 213 | """ | 
|  | 214 | if not self.header: | 
|  | 215 | raise PayloadError('payload header not present') | 
|  | 216 |  | 
|  | 217 | return common.Read( | 
|  | 218 | self.payload_file, self.header.metadata_signature_len, | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 219 | offset=self.payload_file_offset + self.header.size + | 
|  | 220 | self.header.manifest_len) | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 221 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 222 | def ReadDataBlob(self, offset, length): | 
|  | 223 | """Reads and returns a single data blob from the update payload. | 
|  | 224 |  | 
|  | 225 | Args: | 
|  | 226 | offset: offset to the beginning of the blob from the end of the manifest | 
|  | 227 | length: the blob's length | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 228 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 229 | Returns: | 
|  | 230 | A string containing the raw blob data. | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 231 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 232 | Raises: | 
|  | 233 | PayloadError if a read error occurred. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 234 | """ | 
|  | 235 | return common.Read(self.payload_file, length, | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 236 | offset=self.payload_file_offset + self.data_offset + | 
|  | 237 | offset) | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 238 |  | 
|  | 239 | def Init(self): | 
|  | 240 | """Initializes the payload object. | 
|  | 241 |  | 
|  | 242 | This is a prerequisite for any other public API call. | 
|  | 243 |  | 
|  | 244 | Raises: | 
|  | 245 | PayloadError if object already initialized or fails to initialize | 
|  | 246 | correctly. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 247 | """ | 
|  | 248 | if self.is_init: | 
| Kelvin Zhang | f9f99b0 | 2022-08-24 23:02:07 +0000 | [diff] [blame] | 249 | return | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 250 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 251 | self.manifest_hasher = hashlib.sha256() | 
|  | 252 |  | 
|  | 253 | # Read the file header. | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 254 | self.payload_file.seek(self.payload_file_offset) | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 255 | self.header = self._ReadHeader() | 
|  | 256 |  | 
|  | 257 | # Read the manifest. | 
|  | 258 | manifest_raw = self._ReadManifest() | 
|  | 259 | self.manifest = update_metadata_pb2.DeltaArchiveManifest() | 
|  | 260 | self.manifest.ParseFromString(manifest_raw) | 
|  | 261 |  | 
| Alex Deymo | ef49735 | 2015-10-15 09:14:58 -0700 | [diff] [blame] | 262 | # Read the metadata signature (if any). | 
|  | 263 | metadata_signature_raw = self._ReadMetadataSignature() | 
|  | 264 | if metadata_signature_raw: | 
|  | 265 | self.metadata_signature = update_metadata_pb2.Signatures() | 
|  | 266 | self.metadata_signature.ParseFromString(metadata_signature_raw) | 
|  | 267 |  | 
|  | 268 | self.metadata_size = self.header.size + self.header.manifest_len | 
|  | 269 | self.data_offset = self.metadata_size + self.header.metadata_signature_len | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 270 |  | 
| Kelvin Zhang | c890e48 | 2023-03-29 13:22:04 -0700 | [diff] [blame] | 271 | if self.manifest.signatures_offset and self.manifest.signatures_size and self.manifest.signatures_offset + self.manifest.signatures_size <= self.payload_file_size: | 
| Kelvin Zhang | faddb7e | 2021-06-23 15:53:48 -0400 | [diff] [blame] | 272 | payload_signature_blob = self.ReadDataBlob( | 
|  | 273 | self.manifest.signatures_offset, self.manifest.signatures_size) | 
|  | 274 | payload_signature = update_metadata_pb2.Signatures() | 
|  | 275 | payload_signature.ParseFromString(payload_signature_blob) | 
|  | 276 | self.payload_signature = payload_signature | 
|  | 277 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 278 | self.is_init = True | 
|  | 279 |  | 
|  | 280 | def _AssertInit(self): | 
|  | 281 | """Raises an exception if the object was not initialized.""" | 
|  | 282 | if not self.is_init: | 
|  | 283 | raise PayloadError('payload object not initialized') | 
|  | 284 |  | 
|  | 285 | def ResetFile(self): | 
|  | 286 | """Resets the offset of the payload file to right past the manifest.""" | 
| Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 287 | self.payload_file.seek(self.payload_file_offset + self.data_offset) | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 288 |  | 
|  | 289 | def IsDelta(self): | 
|  | 290 | """Returns True iff the payload appears to be a delta.""" | 
|  | 291 | self._AssertInit() | 
| Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 292 | return (any(partition.HasField('old_partition_info') | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 293 | for partition in self.manifest.partitions)) | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 294 |  | 
|  | 295 | def IsFull(self): | 
|  | 296 | """Returns True iff the payload appears to be a full.""" | 
|  | 297 | return not self.IsDelta() | 
|  | 298 |  | 
|  | 299 | def Check(self, pubkey_file_name=None, metadata_sig_file=None, | 
| Amin Hassani | a86b108 | 2018-03-08 15:48:59 -0800 | [diff] [blame] | 300 | metadata_size=0, report_out_file=None, assert_type=None, | 
| Tudor Brindus | 2d22c1a | 2018-06-15 13:07:13 -0700 | [diff] [blame] | 301 | block_size=0, part_sizes=None, allow_unhashed=False, | 
|  | 302 | disabled_tests=()): | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 303 | """Checks the payload integrity. | 
|  | 304 |  | 
|  | 305 | Args: | 
|  | 306 | pubkey_file_name: public key used for signature verification | 
|  | 307 | metadata_sig_file: metadata signature, if verification is desired | 
| Amin Hassani | a86b108 | 2018-03-08 15:48:59 -0800 | [diff] [blame] | 308 | metadata_size: metadata size, if verification is desired | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 309 | report_out_file: file object to dump the report to | 
|  | 310 | assert_type: assert that payload is either 'full' or 'delta' | 
|  | 311 | block_size: expected filesystem / payload block size | 
| Tudor Brindus | 2d22c1a | 2018-06-15 13:07:13 -0700 | [diff] [blame] | 312 | part_sizes: map of partition label to (physical) size in bytes | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 313 | allow_unhashed: allow unhashed operation blobs | 
| Gilad Arnold | eaed0d1 | 2013-04-30 15:38:22 -0700 | [diff] [blame] | 314 | disabled_tests: list of tests to disable | 
| Sen Jiang | 349fd29 | 2015-11-16 17:28:09 -0800 | [diff] [blame] | 315 |  | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 316 | Raises: | 
|  | 317 | PayloadError if payload verification failed. | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 318 | """ | 
|  | 319 | self._AssertInit() | 
|  | 320 |  | 
|  | 321 | # Create a short-lived payload checker object and run it. | 
| Gilad Arnold | eaed0d1 | 2013-04-30 15:38:22 -0700 | [diff] [blame] | 322 | helper = checker.PayloadChecker( | 
|  | 323 | self, assert_type=assert_type, block_size=block_size, | 
|  | 324 | allow_unhashed=allow_unhashed, disabled_tests=disabled_tests) | 
| Gilad Arnold | 553b0ec | 2013-01-26 01:00:39 -0800 | [diff] [blame] | 325 | helper.Run(pubkey_file_name=pubkey_file_name, | 
|  | 326 | metadata_sig_file=metadata_sig_file, | 
| Amin Hassani | a86b108 | 2018-03-08 15:48:59 -0800 | [diff] [blame] | 327 | metadata_size=metadata_size, | 
| Tudor Brindus | 2d22c1a | 2018-06-15 13:07:13 -0700 | [diff] [blame] | 328 | part_sizes=part_sizes, | 
| Gilad Arnold | eaed0d1 | 2013-04-30 15:38:22 -0700 | [diff] [blame] | 329 | report_out_file=report_out_file) | 
| Kelvin Zhang | b8d776c | 2022-09-07 03:12:49 +0000 | [diff] [blame] | 330 |  | 
|  | 331 | def CheckDataHash(self): | 
|  | 332 | for part in self.manifest.partitions: | 
|  | 333 | for op in part.operations: | 
|  | 334 | if op.data_length == 0: | 
|  | 335 | continue | 
|  | 336 | if not op.data_sha256_hash: | 
|  | 337 | raise PayloadError( | 
|  | 338 | f"Operation {op} in partition {part.partition_name} missing data_sha256_hash") | 
|  | 339 | blob = self.ReadDataBlob(op.data_offset, op.data_length) | 
|  | 340 | blob_hash = hashlib.sha256(blob) | 
|  | 341 | if blob_hash.digest() != op.data_sha256_hash: | 
|  | 342 | raise PayloadError( | 
|  | 343 | f"Operation {op} in partition {part.partition_name} has unexpected hash, expected: {binascii.hexlify(op.data_sha256_hash)}, actual: {blob_hash.hexdigest()}") |