OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 The Chromium 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 "components/crx_file/crx_verifier.h" | |
6 | |
7 #include <cstring> | |
8 #include <memory> | |
9 | |
10 #include "base/base64.h" | |
11 #include "base/files/file.h" | |
12 #include "base/files/file_path.h" | |
13 #include "components/crx_file/crx2_file.h" | |
14 #include "components/crx_file/id_util.h" | |
15 #include "crypto/secure_hash.h" | |
16 #include "crypto/secure_util.h" | |
17 #include "crypto/sha2.h" | |
18 #include "crypto/signature_verifier.h" | |
19 | |
20 namespace crx_file { | |
21 | |
22 namespace { | |
23 | |
24 // The maximum size the crx2 parser will tolerate for a public key. | |
25 static const uint32_t kMaxPublicKeySize = 1 << 16; | |
Sorin Jianu
2017/05/15 19:49:52
static not needed here, also maybe use constexpr.
waffles
2017/05/16 00:29:02
Done.
| |
26 | |
27 // The maximum size the crx2 parser will tolerate for a signature. | |
28 static const uint32_t kMaxSignatureSize = 1 << 16; | |
29 | |
30 int ReadAndHashBuffer(uint8_t* buffer, | |
31 int length, | |
32 base::File* file, | |
33 crypto::SecureHash* hash) { | |
34 // We assume a char is 8 bits long. | |
Sorin Jianu
2017/05/15 19:49:52
we can compile time assert on the size of the char
waffles
2017/05/16 00:29:02
Done.
| |
35 int read = file->ReadAtCurrentPos(reinterpret_cast<char*>(buffer), length); | |
36 hash->Update(buffer, read); | |
37 return read; | |
38 } | |
39 | |
40 // Returns UINT32_MAX in the case of an unexpected EOF or read error. | |
Sorin Jianu
2017/05/15 19:49:52
Can the hash on an int32 it be UINT32_MAX?
waffles
2017/05/16 00:29:02
I don't understand what you mean. Can you rephrase
| |
41 uint32_t ReadAndHashLittleEndianUInt32(base::File* file, | |
42 crypto::SecureHash* hash) { | |
43 uint8_t buffer[4]; | |
Sorin Jianu
2017/05/15 19:49:52
4 as a magic number used a several places, maybe u
Sorin Jianu
2017/05/15 19:49:52
not initialized
waffles
2017/05/16 00:29:02
Do you feel strongly about this? The return expres
waffles
2017/05/16 00:29:02
Done.
| |
44 if (ReadAndHashBuffer(buffer, 4, file, hash) != 4) | |
45 return UINT32_MAX; | |
46 return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; | |
47 } | |
48 | |
49 } // namespace | |
50 | |
51 CrxVerifier::CrxVerifier() {} | |
52 | |
53 CrxVerifier::~CrxVerifier() {} | |
54 | |
55 void CrxVerifier::RequireKeyProof(const std::vector<uint8_t>& key_hash) { | |
56 key_hashes_.push_back(key_hash); | |
Sorin Jianu
2017/05/15 19:49:52
We could handle the case where the key_hash lenght
waffles
2017/05/16 00:29:01
Obsolete.
| |
57 } | |
58 | |
59 void CrxVerifier::RequireFileHash(const std::vector<uint8_t>& expected_hash) { | |
60 DCHECK(!expected_hash.empty()); | |
61 expected_hash_ = expected_hash; | |
62 } | |
63 | |
64 void CrxVerifier::GetPublicKey(std::string* public_key) { | |
65 public_key_ = public_key; | |
66 } | |
67 | |
68 void CrxVerifier::GetCrxId(std::string* crx_id) { | |
69 crx_id_ = crx_id; | |
70 } | |
71 | |
72 void CrxVerifier::RequireCrx3() { | |
73 allow_crx2_ = false; | |
74 } | |
75 | |
76 void CrxVerifier::RequirePublisherProof() { | |
77 DCHECK(!allow_crx2_); | |
78 require_publisher_proof_ = true; | |
79 } | |
80 | |
81 CrxVerifier::Result CrxVerifier::Verify(const base::FilePath& crx_path) const { | |
82 base::File file(crx_path, base::File::FLAG_OPEN | base::File::FLAG_READ); | |
83 if (!file.IsValid()) | |
84 return Result::ERROR_FILE_NOT_READABLE; | |
85 | |
86 std::unique_ptr<crypto::SecureHash> file_hash = | |
87 crypto::SecureHash::Create(crypto::SecureHash::SHA256); | |
88 | |
89 // Magic number. | |
90 bool diff = false; | |
91 char buffer[kCrx2FileHeaderMagicSize]; | |
Sorin Jianu
2017/05/15 19:49:52
init?
waffles
2017/05/16 00:29:02
Done.
| |
92 int read = file.ReadAtCurrentPos(buffer, kCrx2FileHeaderMagicSize); | |
Sorin Jianu
2017/05/15 19:49:52
Maybe rename read to num_bytes or something more s
Sorin Jianu
2017/05/15 19:49:52
const?
waffles
2017/05/16 00:29:02
Acknowledged.
waffles
2017/05/16 00:29:02
Inlined it.
| |
93 if (read != kCrx2FileHeaderMagicSize) | |
94 return Result::ERROR_HEADER_INVALID; | |
95 if (!strncmp(buffer, kCrxDiffFileHeaderMagic, kCrx2FileHeaderMagicSize)) | |
96 diff = true; | |
97 else if (strncmp(buffer, kCrx2FileHeaderMagic, kCrx2FileHeaderMagicSize)) | |
98 return Result::ERROR_HEADER_INVALID; | |
99 file_hash->Update(buffer, sizeof(buffer)); | |
100 | |
101 // Version number. | |
102 uint32_t version = ReadAndHashLittleEndianUInt32(&file, file_hash.get()); | |
Sorin Jianu
2017/05/15 19:49:52
const?
waffles
2017/05/16 00:29:02
Done.
| |
103 Result result; | |
104 if (allow_crx2_ && (version == 2 || (diff && version == 0))) | |
105 result = VerifyCrx2(&file, file_hash.get()); | |
106 else if (version == 3) | |
107 result = VerifyCrx3(&file, file_hash.get()); | |
108 else | |
109 return Result::ERROR_HEADER_INVALID; | |
110 if (result != Result::OK_FULL) | |
111 return result; | |
112 | |
113 // Finalize file hash. | |
114 uint8_t final_hash[crypto::kSHA256Length]; | |
Sorin Jianu
2017/05/15 19:49:52
init
waffles
2017/05/16 00:29:02
Done.
| |
115 file_hash->Finish(final_hash, sizeof(final_hash)); | |
116 if (!expected_hash_.empty()) { | |
117 if (expected_hash_.size() != crypto::kSHA256Length) | |
118 return Result::ERROR_EXPECTED_HASH_INVALID; | |
119 if (!crypto::SecureMemEqual(final_hash, expected_hash_.data(), | |
120 crypto::kSHA256Length)) | |
121 return Result::ERROR_FILE_HASH_FAILED; | |
Sorin Jianu
2017/05/15 19:49:52
It would be nice if the entire if can be rewritten
waffles
2017/05/16 00:29:02
Acknowledged. Unfortunately we have to tolerate th
| |
122 } | |
123 return diff ? Result::OK_DELTA : Result::OK_FULL; | |
124 } | |
125 | |
126 CrxVerifier::Result CrxVerifier::VerifyCrx3(base::File* file, | |
127 crypto::SecureHash* hash) const { | |
128 // Crx3 files are not yet supported - treat this as a malformed header. | |
129 return Result::ERROR_HEADER_INVALID; | |
130 } | |
131 | |
132 CrxVerifier::Result CrxVerifier::VerifyCrx2(base::File* file, | |
133 crypto::SecureHash* hash) const { | |
134 DCHECK(!require_publisher_proof_); | |
135 uint32_t key_size = ReadAndHashLittleEndianUInt32(file, hash); | |
Sorin Jianu
2017/05/15 19:49:52
some const here and below.
waffles
2017/05/16 00:29:02
Done.
| |
136 if (key_size > kMaxPublicKeySize) | |
137 return Result::ERROR_HEADER_INVALID; | |
138 uint32_t sig_size = ReadAndHashLittleEndianUInt32(file, hash); | |
139 if (sig_size > kMaxSignatureSize) | |
140 return Result::ERROR_HEADER_INVALID; | |
141 std::vector<uint8_t> key(key_size); | |
142 if (ReadAndHashBuffer(key.data(), key_size, file, hash) != | |
143 static_cast<int>(key_size)) | |
144 return Result::ERROR_HEADER_INVALID; | |
145 for (const auto& expected_hash : key_hashes_) { | |
146 // In practice we expect zero or one key_hashes_ for CRX2 files. | |
147 std::vector<uint8_t> hash(crypto::kSHA256Length); | |
148 std::unique_ptr<crypto::SecureHash> sha256( | |
Sorin Jianu
2017/05/15 19:49:52
prefer = syntax
waffles
2017/05/16 00:29:02
Done.
| |
149 crypto::SecureHash::Create(crypto::SecureHash::SHA256)); | |
150 sha256->Update(key.data(), key.size()); | |
151 sha256->Finish(hash.data(), hash.size()); | |
152 if (hash != expected_hash) | |
153 return Result::ERROR_REQUIRED_PROOF_MISSING; | |
154 } | |
155 | |
156 std::vector<uint8_t> sig(sig_size); | |
157 if (ReadAndHashBuffer(sig.data(), sig_size, file, hash) != | |
158 static_cast<int>(sig_size)) | |
159 return Result::ERROR_HEADER_INVALID; | |
160 crypto::SignatureVerifier verifier; | |
161 if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, | |
162 sig.data(), sig.size(), key.data(), key.size())) { | |
163 return Result::ERROR_SIGNATURE_INITIALIZATION_FAILED; | |
164 } | |
165 | |
166 // Read the rest of the file. | |
167 uint8_t buffer[1 << 12] = {}; | |
168 size_t len; | |
Sorin Jianu
2017/05/15 19:49:52
init
waffles
2017/05/16 00:29:02
Done.
| |
169 while ((len = ReadAndHashBuffer(buffer, arraysize(buffer), file, hash)) > 0) | |
170 verifier.VerifyUpdate(buffer, len); | |
171 if (!verifier.VerifyFinal()) | |
172 return Result::ERROR_SIGNATURE_VERIFICATION_FAILED; | |
173 | |
174 std::string public_key_bytes = | |
Sorin Jianu
2017/05/15 19:49:52
can you use a ctor or initializer instead of initi
Sorin Jianu
2017/05/15 19:49:52
const?
waffles
2017/05/16 00:29:02
Done.
waffles
2017/05/16 00:29:02
Done.
| |
175 std::string(reinterpret_cast<char*>(&key.front()), key.size()); | |
176 if (public_key_) | |
177 base::Base64Encode(public_key_bytes, public_key_); | |
178 if (crx_id_) | |
179 *crx_id_ = id_util::GenerateId(public_key_bytes); | |
180 return Result::OK_FULL; | |
181 } | |
182 | |
183 } // namespace crx_file | |
OLD | NEW |