blob: 3d06461ee056adc6ff96c0b1f843f0be589dbfad [file] [log] [blame]
Greg Kerr019d59c2016-11-17 14:28:49 -08001// Copyright 2016 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#include "component.h"
6
7#include <fcntl.h>
8
9#include <algorithm>
10#include <string>
Eric Caruso089bbff2017-03-21 11:34:15 -070011#include <utility>
Greg Kerr019d59c2016-11-17 14:28:49 -080012#include <vector>
13
14#include <base/files/file.h>
Eric Caruso089bbff2017-03-21 11:34:15 -070015#include <base/files/file_enumerator.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080016#include <base/files/file_path.h>
17#include <base/files/file_util.h>
18#include <base/files/scoped_file.h>
19#include <base/json/json_string_value_serializer.h>
20#include <base/logging.h>
21#include <base/numerics/safe_conversions.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080022#include <base/posix/eintr_wrapper.h>
Eric Caruso089bbff2017-03-21 11:34:15 -070023#include <base/strings/string_number_conversions.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080024#include <base/strings/string_util.h>
25#include <crypto/secure_hash.h>
26#include <crypto/sha2.h>
27#include <crypto/signature_verifier.h>
28
Greg Kerr9944e242017-01-26 15:09:31 -080029#include "helper_process.h"
30
Greg Kerr019d59c2016-11-17 14:28:49 -080031namespace imageloader {
32
33namespace {
34
35// The name of the imageloader manifest file.
36constexpr char kManifestName[] = "imageloader.json";
37// The name of the fingerprint file.
38constexpr char kFingerprintName[] = "manifest.fingerprint";
39// The manifest signature.
Eric Caruso0b79bc82017-03-21 13:44:34 -070040constexpr char kManifestSignatureNamePattern[] = "imageloader.sig.[1-2]";
Greg Kerr019d59c2016-11-17 14:28:49 -080041// The current version of the manifest file.
42constexpr int kCurrentManifestVersion = 1;
43// The name of the version field in the manifest.
44constexpr char kManifestVersionField[] = "manifest-version";
45// The name of the component version field in the manifest.
46constexpr char kVersionField[] = "version";
47// The name of the field containing the image hash.
48constexpr char kImageHashField[] = "image-sha256-hash";
49// The name of the image file.
50constexpr char kImageFileName[] = "image.squash";
51// The name of the field containing the table hash.
52constexpr char kTableHashField[] = "table-sha256-hash";
53// The name of the table file.
54constexpr char kTableFileName[] = "table";
55// The maximum size of any file to read into memory.
56constexpr size_t kMaximumFilesize = 4096 * 10;
57
58base::FilePath GetManifestPath(const base::FilePath& component_dir) {
59 return component_dir.Append(kManifestName);
60}
61
Eric Caruso089bbff2017-03-21 11:34:15 -070062bool GetSignaturePath(const base::FilePath& component_dir,
63 base::FilePath* signature_path,
Eric Caruso9588e642017-04-07 15:18:45 -070064 size_t* key_number) {
Eric Caruso089bbff2017-03-21 11:34:15 -070065 DCHECK(signature_path);
66 DCHECK(key_number);
67
68 base::FileEnumerator files(component_dir,
69 false,
70 base::FileEnumerator::FileType::FILES,
71 kManifestSignatureNamePattern);
72 for (base::FilePath path = files.Next(); !path.empty(); path = files.Next()) {
73 // Extract the key number.
74 std::string key_ext = path.FinalExtension();
75 if (key_ext.empty())
76 continue;
77
Eric Caruso9588e642017-04-07 15:18:45 -070078 size_t ext_number;
79 if (!base::StringToSizeT(key_ext.substr(1), &ext_number))
Eric Caruso089bbff2017-03-21 11:34:15 -070080 continue;
81
82 *signature_path = path;
83 *key_number = ext_number;
84 return true;
85 }
86 return false;
87}
88
89base::FilePath GetSignaturePathForKey(const base::FilePath& component_dir,
Eric Caruso9588e642017-04-07 15:18:45 -070090 size_t key_number) {
Eric Caruso089bbff2017-03-21 11:34:15 -070091 std::string signature_name(kManifestSignatureNamePattern);
92 signature_name =
93 signature_name.substr(0, signature_name.find_last_of('.') + 1);
Eric Caruso9588e642017-04-07 15:18:45 -070094 return component_dir.Append(signature_name + base::SizeTToString(key_number));
Greg Kerr019d59c2016-11-17 14:28:49 -080095}
96
97base::FilePath GetFingerprintPath(const base::FilePath& component_dir) {
98 return component_dir.Append(kFingerprintName);
99}
100
101base::FilePath GetTablePath(const base::FilePath& component_dir) {
102 return component_dir.Append(kTableFileName);
103}
104
105base::FilePath GetImagePath(const base::FilePath& component_dir) {
106 return component_dir.Append(kImageFileName);
107}
108
109bool WriteFileToDisk(const base::FilePath& path, const std::string& contents) {
110 base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(),
111 O_CREAT | O_WRONLY | O_EXCL,
112 kComponentFilePerms)));
113 if (!fd.is_valid()) {
114 PLOG(ERROR) << "Error creating file for " << path.value();
115 return false;
116 }
117
118 base::File file(fd.release());
119 int size = base::checked_cast<int>(contents.size());
120 return file.Write(0, contents.data(), contents.size()) == size;
121}
122
123bool GetSHA256FromString(const std::string& hash_str,
124 std::vector<uint8_t>* bytes) {
Eric Caruso355e37c2017-03-15 14:31:41 -0700125 if (!base::HexStringToBytes(hash_str, bytes))
126 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800127 return bytes->size() == crypto::kSHA256Length;
128}
129
130bool GetAndVerifyTable(const base::FilePath& path,
131 const std::vector<uint8_t>& hash,
132 std::string* out_table) {
133 std::string table;
134 if (!base::ReadFileToStringWithMaxSize(path, &table, kMaximumFilesize)) {
135 return false;
136 }
137
138 std::vector<uint8_t> table_hash(crypto::kSHA256Length);
139 crypto::SHA256HashString(table, table_hash.data(), table_hash.size());
140 if (table_hash != hash) {
141 LOG(ERROR) << "dm-verity table file has the wrong hash.";
142 return false;
143 }
144
145 out_table->assign(table);
146 return true;
147}
148
149} // namespace
150
Eric Caruso089bbff2017-03-21 11:34:15 -0700151Component::Component(const base::FilePath& component_dir, int key_number)
152 : component_dir_(component_dir), key_number_(key_number) {}
Greg Kerr019d59c2016-11-17 14:28:49 -0800153
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700154std::unique_ptr<Component> Component::Create(
155 const base::FilePath& component_dir,
Eric Caruso0b79bc82017-03-21 13:44:34 -0700156 const Keys& public_keys) {
Eric Caruso089bbff2017-03-21 11:34:15 -0700157 base::FilePath signature_path;
Eric Caruso9588e642017-04-07 15:18:45 -0700158 size_t key_number;
Eric Caruso089bbff2017-03-21 11:34:15 -0700159 if (!GetSignaturePath(component_dir, &signature_path, &key_number)) {
160 LOG(ERROR) << "Could not find manifest signature";
161 return nullptr;
162 }
Eric Caruso0b79bc82017-03-21 13:44:34 -0700163 if (key_number < 1 || key_number > public_keys.size()) {
164 LOG(ERROR) << "Invalid key number";
165 return nullptr;
166 }
Eric Caruso089bbff2017-03-21 11:34:15 -0700167
168 std::unique_ptr<Component> component(
169 new Component(component_dir, key_number));
Eric Caruso0b79bc82017-03-21 13:44:34 -0700170 if (!component->LoadManifest(public_keys[key_number - 1]))
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700171 return nullptr;
172 return component;
Greg Kerr019d59c2016-11-17 14:28:49 -0800173}
174
175const Component::Manifest& Component::manifest() {
Greg Kerr019d59c2016-11-17 14:28:49 -0800176 return manifest_;
177}
178
Greg Kerr9944e242017-01-26 15:09:31 -0800179bool Component::Mount(HelperProcess* mounter, const base::FilePath& dest_dir) {
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700180 // Read the table in and verify the hash.
Greg Kerr019d59c2016-11-17 14:28:49 -0800181 std::string table;
182 if (!GetAndVerifyTable(GetTablePath(component_dir_), manifest_.table_sha256,
183 &table)) {
184 LOG(ERROR) << "Could not read and verify dm-verity table.";
185 return false;
186 }
187
188 base::FilePath image_path(GetImagePath(component_dir_));
189 base::File image(image_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
190 if (!image.IsValid()) {
191 LOG(ERROR) << "Could not open image file.";
192 return false;
193 }
194 base::ScopedFD image_fd(image.TakePlatformFile());
195
Greg Kerr9944e242017-01-26 15:09:31 -0800196 return mounter->SendMountCommand(image_fd.get(), dest_dir.value(), table);
Greg Kerr019d59c2016-11-17 14:28:49 -0800197}
198
199bool Component::ParseManifest() {
200 // Now deserialize the manifest json and read out the rest of the component.
201 int error_code;
202 std::string error_message;
203 JSONStringValueDeserializer deserializer(manifest_raw_);
204 std::unique_ptr<base::Value> value =
205 deserializer.Deserialize(&error_code, &error_message);
206
207 if (!value) {
208 LOG(ERROR) << "Could not deserialize the manifest file. Error "
209 << error_code << ": " << error_message;
210 return false;
211 }
212
213 base::DictionaryValue* manifest_dict = nullptr;
214 if (!value->GetAsDictionary(&manifest_dict)) {
215 LOG(ERROR) << "Could not parse manifest file as JSON.";
216 return false;
217 }
218
219 // This will have to be changed if the manifest version is bumped.
220 int version;
221 if (!manifest_dict->GetInteger(kManifestVersionField, &version)) {
222 LOG(ERROR) << "Could not parse manifest version field from manifest.";
223 return false;
224 }
225 if (version != kCurrentManifestVersion) {
226 LOG(ERROR) << "Unsupported version of the manifest.";
227 return false;
228 }
229 manifest_.manifest_version = version;
230
231 std::string image_hash_str;
232 if (!manifest_dict->GetString(kImageHashField, &image_hash_str)) {
233 LOG(ERROR) << "Could not parse image hash from manifest.";
234 return false;
235 }
236
237 if (!GetSHA256FromString(image_hash_str, &(manifest_.image_sha256))) {
238 LOG(ERROR) << "Could not convert image hash to bytes.";
239 return false;
240 }
241
242 std::string table_hash_str;
243 if (!manifest_dict->GetString(kTableHashField, &table_hash_str)) {
244 LOG(ERROR) << "Could not parse table hash from manifest.";
245 return false;
246 }
247
248 if (!GetSHA256FromString(table_hash_str, &(manifest_.table_sha256))) {
249 LOG(ERROR) << "Could not convert table hash to bytes.";
250 return false;
251 }
252
253 if (!manifest_dict->GetString(kVersionField, &(manifest_.version))) {
254 LOG(ERROR) << "Could not parse component version from manifest.";
255 return false;
256 }
257
258 return true;
259}
260
261bool Component::LoadManifest(const std::vector<uint8_t>& public_key) {
262 if (!base::ReadFileToStringWithMaxSize(GetManifestPath(component_dir_),
263 &manifest_raw_, kMaximumFilesize)) {
264 LOG(ERROR) << "Could not read manifest file.";
265 return false;
266 }
Eric Caruso089bbff2017-03-21 11:34:15 -0700267 if (!base::ReadFileToStringWithMaxSize(
268 GetSignaturePathForKey(component_dir_, key_number_),
269 &manifest_sig_, kMaximumFilesize)) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800270 LOG(ERROR) << "Could not read signature file.";
271 return false;
272 }
273
274 crypto::SignatureVerifier verifier;
275
276 if (!verifier.VerifyInit(
277 crypto::SignatureVerifier::ECDSA_SHA256,
278 reinterpret_cast<const uint8_t*>(manifest_sig_.data()),
279 base::checked_cast<int>(manifest_sig_.size()), public_key.data(),
280 base::checked_cast<int>(public_key.size()))) {
281 LOG(ERROR) << "Failed to initialize signature verification.";
282 return false;
283 }
284
285 verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(manifest_raw_.data()),
286 base::checked_cast<int>(manifest_raw_.size()));
287
288 if (!verifier.VerifyFinal()) {
289 LOG(ERROR) << "Manifest failed signature verification.";
290 return false;
291 }
292 return ParseManifest();
293}
294
295bool Component::CopyTo(const base::FilePath& dest_dir) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800296 if (!WriteFileToDisk(GetManifestPath(dest_dir), manifest_raw_) ||
Eric Caruso089bbff2017-03-21 11:34:15 -0700297 !WriteFileToDisk(GetSignaturePathForKey(dest_dir, key_number_),
298 manifest_sig_)) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800299 LOG(ERROR) << "Could not write manifest and signature to disk.";
300 return false;
301 }
302
303 base::FilePath table_src(GetTablePath(component_dir_));
304 base::FilePath table_dest(GetTablePath(dest_dir));
305 if (!CopyComponentFile(table_src, table_dest, manifest_.table_sha256)) {
306 LOG(ERROR) << "Could not copy table file.";
307 return false;
308 }
309
310 base::FilePath image_src(GetImagePath(component_dir_));
311 base::FilePath image_dest(GetImagePath(dest_dir));
312 if (!CopyComponentFile(image_src, image_dest, manifest_.image_sha256)) {
313 LOG(ERROR) << "Could not copy image file.";
314 return false;
315 }
316
317 if (!CopyFingerprintFile(component_dir_, dest_dir)) {
318 LOG(ERROR) << "Could not copy manifest.fingerprint file.";
319 return false;
320 }
321
322 return true;
323}
324
325bool Component::CopyComponentFile(const base::FilePath& src,
Eric Caruso355e37c2017-03-15 14:31:41 -0700326 const base::FilePath& dest_path,
327 const std::vector<uint8_t>& expected_hash) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800328 base::File file(src, base::File::FLAG_OPEN | base::File::FLAG_READ);
Eric Caruso355e37c2017-03-15 14:31:41 -0700329 if (!file.IsValid())
330 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800331
332 base::ScopedFD dest(
333 HANDLE_EINTR(open(dest_path.value().c_str(), O_CREAT | O_WRONLY | O_EXCL,
334 kComponentFilePerms)));
Eric Caruso355e37c2017-03-15 14:31:41 -0700335 if (!dest.is_valid())
336 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800337
338 base::File out_file(dest.release());
339 std::unique_ptr<crypto::SecureHash> sha256(
340 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
341
342 std::vector<uint8_t> file_hash(crypto::kSHA256Length);
343 if (!ReadHashAndCopyFile(&file, &file_hash, &out_file)) {
344 LOG(ERROR) << "Failed to read image file.";
345 return false;
346 }
347
348 if (expected_hash != file_hash) {
349 LOG(ERROR) << "Image is corrupt or modified.";
350 return false;
351 }
352 return true;
353}
354
355bool Component::ReadHashAndCopyFile(base::File* file,
356 std::vector<uint8_t>* file_hash,
357 base::File* out_file) {
358 std::unique_ptr<crypto::SecureHash> sha256(
359 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
360 int size = file->GetLength();
Eric Caruso355e37c2017-03-15 14:31:41 -0700361 if (size <= 0)
362 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800363
364 int rv = 0, bytes_read = 0;
365 char buf[4096];
366 do {
367 int remaining = size - bytes_read;
368 int bytes_to_read =
369 std::min(remaining, base::checked_cast<int>(sizeof(buf)));
370
371 rv = file->ReadAtCurrentPos(buf, bytes_to_read);
372 if (rv <= 0) break;
373
374 bytes_read += rv;
375 sha256->Update(buf, rv);
376 if (out_file) {
377 out_file->WriteAtCurrentPos(buf, rv);
378 }
379 } while (bytes_read <= size);
380
381 sha256->Finish(file_hash->data(), file_hash->size());
382 return bytes_read == size;
383}
384
385bool Component::CopyFingerprintFile(const base::FilePath& src,
386 const base::FilePath& dest) {
387 base::FilePath fingerprint_path(GetFingerprintPath(src));
388 if (base::PathExists(fingerprint_path)) {
389 std::string fingerprint_contents;
390 if (!base::ReadFileToStringWithMaxSize(
391 fingerprint_path, &fingerprint_contents, kMaximumFilesize)) {
392 return false;
393 }
394
Eric Caruso355e37c2017-03-15 14:31:41 -0700395 if (!IsValidFingerprintFile(fingerprint_contents))
396 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800397
398 if (!WriteFileToDisk(GetFingerprintPath(dest), fingerprint_contents)) {
399 return false;
400 }
401 }
402 return true;
403}
404
405// The client inserts manifest.fingerprint into components after unpacking the
406// CRX. The file is used for delta updates. Since Chrome OS doesn't rely on it
407// for security of the disk image, we are fine with sanity checking the contents
408// and then preserving the unsigned file.
409bool Component::IsValidFingerprintFile(const std::string& contents) {
410 return contents.size() <= 256 &&
411 std::find_if_not(contents.begin(), contents.end(), [](char ch) {
412 return base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch) || ch == '.';
413 }) == contents.end();
414}
415
416} // namespace imageloader