[email protected] | de0fdca2 | 2014-08-19 05:26:09 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
sorin | 52ac088 | 2015-01-24 01:15:00 | [diff] [blame] | 5 | #include "components/update_client/component_unpacker.h" |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 6 | |
sorin | 5cb1f549 | 2014-09-23 04:07:44 | [diff] [blame] | 7 | #include <stdint.h> |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 8 | #include <string> |
| 9 | #include <vector> |
| 10 | |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 11 | #include "base/base64.h" |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 12 | #include "base/bind.h" |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 13 | #include "base/files/file_path.h" |
thestig | 819adcc8 | 2014-09-10 22:24:53 | [diff] [blame] | 14 | #include "base/files/file_util.h" |
[email protected] | 0910bae | 2014-06-10 17:53:21 | [diff] [blame] | 15 | #include "base/files/scoped_file.h" |
[email protected] | ffbec69 | 2012-02-26 20:26:42 | [diff] [blame] | 16 | #include "base/json/json_file_value_serializer.h" |
[email protected] | 9e1685a | 2014-02-13 15:08:34 | [diff] [blame] | 17 | #include "base/location.h" |
[email protected] | 871bdf1 | 2013-10-26 10:52:06 | [diff] [blame] | 18 | #include "base/logging.h" |
avi | 5dd91f8 | 2015-12-25 22:30:46 | [diff] [blame] | 19 | #include "base/macros.h" |
[email protected] | de0fdca2 | 2014-08-19 05:26:09 | [diff] [blame] | 20 | #include "base/numerics/safe_conversions.h" |
[email protected] | 3ea1b18 | 2013-02-08 22:38:41 | [diff] [blame] | 21 | #include "base/strings/string_number_conversions.h" |
[email protected] | e746341 | 2013-06-10 22:53:46 | [diff] [blame] | 22 | #include "base/strings/stringprintf.h" |
[email protected] | 871bdf1 | 2013-10-26 10:52:06 | [diff] [blame] | 23 | #include "base/values.h" |
[email protected] | 8c83fe0 | 2014-08-15 19:07:35 | [diff] [blame] | 24 | #include "components/crx_file/crx_file.h" |
sorin | 52ac088 | 2015-01-24 01:15:00 | [diff] [blame] | 25 | #include "components/update_client/component_patcher.h" |
| 26 | #include "components/update_client/component_patcher_operation.h" |
| 27 | #include "components/update_client/update_client.h" |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 28 | #include "components/update_client/update_client_errors.h" |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 29 | #include "crypto/secure_hash.h" |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 30 | #include "crypto/sha2.h" |
[email protected] | 4170d3a | 2013-05-03 23:02:57 | [diff] [blame] | 31 | #include "third_party/zlib/google/zip.h" |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 32 | |
| 33 | using crypto::SecureHash; |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 34 | using crx_file::CrxFile; |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 35 | |
sorin | 52ac088 | 2015-01-24 01:15:00 | [diff] [blame] | 36 | namespace update_client { |
[email protected] | 055981f | 2014-01-17 20:22:32 | [diff] [blame] | 37 | |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 38 | // TODO(cpu): add a specific attribute check to a component json that the |
| 39 | // extension unpacker will reject, so that a component cannot be installed |
| 40 | // as an extension. |
dcheng | d0fc6aa9 | 2016-04-22 18:03:12 | [diff] [blame] | 41 | std::unique_ptr<base::DictionaryValue> ReadManifest( |
[email protected] | 871bdf1 | 2013-10-26 10:52:06 | [diff] [blame] | 42 | const base::FilePath& unpack_path) { |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 43 | base::FilePath manifest = |
| 44 | unpack_path.Append(FILE_PATH_LITERAL("manifest.json")); |
[email protected] | 756748414 | 2013-07-11 17:36:07 | [diff] [blame] | 45 | if (!base::PathExists(manifest)) |
dcheng | d0fc6aa9 | 2016-04-22 18:03:12 | [diff] [blame] | 46 | return std::unique_ptr<base::DictionaryValue>(); |
prashhir | 54a99450 | 2015-03-05 09:30:57 | [diff] [blame] | 47 | JSONFileValueDeserializer deserializer(manifest); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 48 | std::string error; |
dcheng | d0fc6aa9 | 2016-04-22 18:03:12 | [diff] [blame] | 49 | std::unique_ptr<base::Value> root = deserializer.Deserialize(NULL, &error); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 50 | if (!root.get()) |
dcheng | d0fc6aa9 | 2016-04-22 18:03:12 | [diff] [blame] | 51 | return std::unique_ptr<base::DictionaryValue>(); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 52 | if (!root->IsType(base::Value::TYPE_DICTIONARY)) |
dcheng | d0fc6aa9 | 2016-04-22 18:03:12 | [diff] [blame] | 53 | return std::unique_ptr<base::DictionaryValue>(); |
| 54 | return std::unique_ptr<base::DictionaryValue>( |
dcheng | 51ace48a | 2015-12-26 22:45:17 | [diff] [blame] | 55 | static_cast<base::DictionaryValue*>(root.release())); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 56 | } |
| 57 | |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 58 | ComponentUnpacker::Result::Result() {} |
| 59 | |
| 60 | ComponentUnpacker::ComponentUnpacker( |
| 61 | const std::vector<uint8_t>& pk_hash, |
| 62 | const base::FilePath& path, |
| 63 | const scoped_refptr<CrxInstaller>& installer, |
| 64 | const scoped_refptr<OutOfProcessPatcher>& oop_patcher, |
| 65 | const scoped_refptr<base::SequencedTaskRunner>& task_runner) |
| 66 | : pk_hash_(pk_hash), |
| 67 | path_(path), |
| 68 | is_delta_(false), |
| 69 | installer_(installer), |
| 70 | oop_patcher_(oop_patcher), |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 71 | error_(UnpackerError::kNone), |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 72 | extended_error_(0), |
| 73 | task_runner_(task_runner) {} |
| 74 | |
| 75 | ComponentUnpacker::~ComponentUnpacker() {} |
| 76 | |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 77 | bool ComponentUnpacker::UnpackInternal() { |
| 78 | return Verify() && Unzip() && BeginPatching(); |
| 79 | } |
| 80 | |
[email protected] | 94a481b | 2014-03-28 19:41:55 | [diff] [blame] | 81 | void ComponentUnpacker::Unpack(const Callback& callback) { |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 82 | callback_ = callback; |
| 83 | if (!UnpackInternal()) |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 84 | EndUnpacking(); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | bool ComponentUnpacker::Verify() { |
[email protected] | fb53e65 | 2014-04-30 11:27:19 | [diff] [blame] | 88 | VLOG(1) << "Verifying component: " << path_.value(); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 89 | if (pk_hash_.empty() || path_.empty()) { |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 90 | error_ = UnpackerError::kInvalidParams; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 91 | return false; |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 92 | } |
| 93 | // First, validate the CRX header and signature. As of today |
| 94 | // this is SHA1 with RSA 1024. |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 95 | std::string public_key_bytes; |
| 96 | std::string public_key_base64; |
| 97 | CrxFile::Header header; |
| 98 | CrxFile::ValidateError error = CrxFile::ValidateSignature( |
| 99 | path_, std::string(), &public_key_base64, nullptr, &header); |
| 100 | if (error != CrxFile::ValidateError::NONE || |
| 101 | !base::Base64Decode(public_key_base64, &public_key_bytes)) { |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 102 | error_ = UnpackerError::kInvalidFile; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 103 | return false; |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 104 | } |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 105 | is_delta_ = CrxFile::HeaderIsDelta(header); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 106 | |
[email protected] | f105043 | 2012-02-15 01:35:46 | [diff] [blame] | 107 | // File is valid and the digital signature matches. Now make sure |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 108 | // the public key hash matches the expected hash. If they do we fully |
| 109 | // trust this CRX. |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 110 | uint8_t hash[crypto::kSHA256Length] = {}; |
dcheng | d0fc6aa9 | 2016-04-22 18:03:12 | [diff] [blame] | 111 | std::unique_ptr<SecureHash> sha256(SecureHash::Create(SecureHash::SHA256)); |
asargent | 710893f | 2015-07-27 21:22:32 | [diff] [blame] | 112 | sha256->Update(public_key_bytes.data(), public_key_bytes.size()); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 113 | sha256->Finish(hash, arraysize(hash)); |
| 114 | |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 115 | if (!std::equal(pk_hash_.begin(), pk_hash_.end(), hash)) { |
[email protected] | fb53e65 | 2014-04-30 11:27:19 | [diff] [blame] | 116 | VLOG(1) << "Hash mismatch: " << path_.value(); |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 117 | error_ = UnpackerError::kInvalidId; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 118 | return false; |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 119 | } |
[email protected] | fb53e65 | 2014-04-30 11:27:19 | [diff] [blame] | 120 | VLOG(1) << "Verification successful: " << path_.value(); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 121 | return true; |
| 122 | } |
| 123 | |
| 124 | bool ComponentUnpacker::Unzip() { |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 125 | // Mind the reference to non-const type, passed as an argument below. |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 126 | base::FilePath& destination = is_delta_ ? unpack_diff_path_ : unpack_path_; |
[email protected] | 03d9afc0 | 2013-12-03 17:55:52 | [diff] [blame] | 127 | if (!base::CreateNewTempDirectory(base::FilePath::StringType(), |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 128 | &destination)) { |
[email protected] | fb53e65 | 2014-04-30 11:27:19 | [diff] [blame] | 129 | VLOG(1) << "Unable to create temporary directory for unpacking."; |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 130 | error_ = UnpackerError::kUnzipPathError; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 131 | return false; |
| 132 | } |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 133 | VLOG(1) << "Unpacking in: " << destination.value(); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 134 | if (!zip::Unzip(path_, destination)) { |
[email protected] | fb53e65 | 2014-04-30 11:27:19 | [diff] [blame] | 135 | VLOG(1) << "Unzipping failed."; |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 136 | error_ = UnpackerError::kUnzipFailed; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 137 | return false; |
| 138 | } |
[email protected] | fb53e65 | 2014-04-30 11:27:19 | [diff] [blame] | 139 | VLOG(1) << "Unpacked successfully"; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 140 | return true; |
| 141 | } |
| 142 | |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 143 | bool ComponentUnpacker::BeginPatching() { |
| 144 | if (is_delta_) { // Package is a diff package. |
| 145 | // Use a different temp directory for the patch output files. |
| 146 | if (!base::CreateNewTempDirectory(base::FilePath::StringType(), |
| 147 | &unpack_path_)) { |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 148 | error_ = UnpackerError::kUnzipPathError; |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 149 | return false; |
| 150 | } |
sorin | 52ac088 | 2015-01-24 01:15:00 | [diff] [blame] | 151 | patcher_ = new ComponentPatcher(unpack_diff_path_, unpack_path_, installer_, |
| 152 | oop_patcher_, task_runner_); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 153 | task_runner_->PostTask( |
[email protected] | 94a481b | 2014-03-28 19:41:55 | [diff] [blame] | 154 | FROM_HERE, |
sorin | 52ac088 | 2015-01-24 01:15:00 | [diff] [blame] | 155 | base::Bind(&ComponentPatcher::Start, patcher_, |
[email protected] | 94a481b | 2014-03-28 19:41:55 | [diff] [blame] | 156 | base::Bind(&ComponentUnpacker::EndPatching, |
| 157 | scoped_refptr<ComponentUnpacker>(this)))); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 158 | } else { |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 159 | task_runner_->PostTask(FROM_HERE, |
| 160 | base::Bind(&ComponentUnpacker::EndPatching, |
| 161 | scoped_refptr<ComponentUnpacker>(this), |
| 162 | UnpackerError::kNone, 0)); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 163 | } |
| 164 | return true; |
| 165 | } |
| 166 | |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 167 | void ComponentUnpacker::EndPatching(UnpackerError error, int extended_error) { |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 168 | error_ = error; |
| 169 | extended_error_ = extended_error; |
[email protected] | 94a481b | 2014-03-28 19:41:55 | [diff] [blame] | 170 | patcher_ = NULL; |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 171 | |
| 172 | EndUnpacking(); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 173 | } |
| 174 | |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 175 | void ComponentUnpacker::EndUnpacking() { |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 176 | if (!unpack_diff_path_.empty()) |
| 177 | base::DeleteFile(unpack_diff_path_, true); |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 178 | if (error_ != UnpackerError::kNone && !unpack_path_.empty()) |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 179 | base::DeleteFile(unpack_path_, true); |
[email protected] | f5d27e3 | 2014-01-31 06:48:53 | [diff] [blame] | 180 | |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 181 | Result result; |
| 182 | result.error = error_; |
| 183 | result.extended_error = extended_error_; |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 184 | if (error_ == UnpackerError::kNone) |
sorin | cbb7c09 | 2016-10-27 01:39:20 | [diff] [blame] | 185 | result.unpack_path = unpack_path_; |
| 186 | |
| 187 | task_runner_->PostTask(FROM_HERE, base::Bind(callback_, result)); |
[email protected] | 2e114e73 | 2011-07-22 02:55:04 | [diff] [blame] | 188 | } |
[email protected] | 055981f | 2014-01-17 20:22:32 | [diff] [blame] | 189 | |
sorin | 52ac088 | 2015-01-24 01:15:00 | [diff] [blame] | 190 | } // namespace update_client |