Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Unified Diff: components/crx_file/crx_verifier.cc

Issue 2874503002: Refactor CRX verification in preparation to support CRX₃ files. (Closed)
Patch Set: No subclass Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/crx_file/crx_verifier.cc
diff --git a/components/crx_file/crx_verifier.cc b/components/crx_file/crx_verifier.cc
new file mode 100644
index 0000000000000000000000000000000000000000..766d6b744ed7399fba54d90b3eca47ef55089b84
--- /dev/null
+++ b/components/crx_file/crx_verifier.cc
@@ -0,0 +1,183 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crx_file/crx_verifier.h"
+
+#include <cstring>
+#include <memory>
+
+#include "base/base64.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "components/crx_file/crx2_file.h"
+#include "components/crx_file/id_util.h"
+#include "crypto/secure_hash.h"
+#include "crypto/secure_util.h"
+#include "crypto/sha2.h"
+#include "crypto/signature_verifier.h"
+
+namespace crx_file {
+
+namespace {
+
+// The maximum size the crx2 parser will tolerate for a public key.
+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.
+
+// The maximum size the crx2 parser will tolerate for a signature.
+static const uint32_t kMaxSignatureSize = 1 << 16;
+
+int ReadAndHashBuffer(uint8_t* buffer,
+ int length,
+ base::File* file,
+ crypto::SecureHash* hash) {
+ // 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.
+ int read = file->ReadAtCurrentPos(reinterpret_cast<char*>(buffer), length);
+ hash->Update(buffer, read);
+ return read;
+}
+
+// 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
+uint32_t ReadAndHashLittleEndianUInt32(base::File* file,
+ crypto::SecureHash* hash) {
+ 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.
+ if (ReadAndHashBuffer(buffer, 4, file, hash) != 4)
+ return UINT32_MAX;
+ return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0];
+}
+
+} // namespace
+
+CrxVerifier::CrxVerifier() {}
+
+CrxVerifier::~CrxVerifier() {}
+
+void CrxVerifier::RequireKeyProof(const std::vector<uint8_t>& key_hash) {
+ 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.
+}
+
+void CrxVerifier::RequireFileHash(const std::vector<uint8_t>& expected_hash) {
+ DCHECK(!expected_hash.empty());
+ expected_hash_ = expected_hash;
+}
+
+void CrxVerifier::GetPublicKey(std::string* public_key) {
+ public_key_ = public_key;
+}
+
+void CrxVerifier::GetCrxId(std::string* crx_id) {
+ crx_id_ = crx_id;
+}
+
+void CrxVerifier::RequireCrx3() {
+ allow_crx2_ = false;
+}
+
+void CrxVerifier::RequirePublisherProof() {
+ DCHECK(!allow_crx2_);
+ require_publisher_proof_ = true;
+}
+
+CrxVerifier::Result CrxVerifier::Verify(const base::FilePath& crx_path) const {
+ base::File file(crx_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid())
+ return Result::ERROR_FILE_NOT_READABLE;
+
+ std::unique_ptr<crypto::SecureHash> file_hash =
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256);
+
+ // Magic number.
+ bool diff = false;
+ char buffer[kCrx2FileHeaderMagicSize];
Sorin Jianu 2017/05/15 19:49:52 init?
waffles 2017/05/16 00:29:02 Done.
+ 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.
+ if (read != kCrx2FileHeaderMagicSize)
+ return Result::ERROR_HEADER_INVALID;
+ if (!strncmp(buffer, kCrxDiffFileHeaderMagic, kCrx2FileHeaderMagicSize))
+ diff = true;
+ else if (strncmp(buffer, kCrx2FileHeaderMagic, kCrx2FileHeaderMagicSize))
+ return Result::ERROR_HEADER_INVALID;
+ file_hash->Update(buffer, sizeof(buffer));
+
+ // Version number.
+ 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.
+ Result result;
+ if (allow_crx2_ && (version == 2 || (diff && version == 0)))
+ result = VerifyCrx2(&file, file_hash.get());
+ else if (version == 3)
+ result = VerifyCrx3(&file, file_hash.get());
+ else
+ return Result::ERROR_HEADER_INVALID;
+ if (result != Result::OK_FULL)
+ return result;
+
+ // Finalize file hash.
+ uint8_t final_hash[crypto::kSHA256Length];
Sorin Jianu 2017/05/15 19:49:52 init
waffles 2017/05/16 00:29:02 Done.
+ file_hash->Finish(final_hash, sizeof(final_hash));
+ if (!expected_hash_.empty()) {
+ if (expected_hash_.size() != crypto::kSHA256Length)
+ return Result::ERROR_EXPECTED_HASH_INVALID;
+ if (!crypto::SecureMemEqual(final_hash, expected_hash_.data(),
+ crypto::kSHA256Length))
+ 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
+ }
+ return diff ? Result::OK_DELTA : Result::OK_FULL;
+}
+
+CrxVerifier::Result CrxVerifier::VerifyCrx3(base::File* file,
+ crypto::SecureHash* hash) const {
+ // Crx3 files are not yet supported - treat this as a malformed header.
+ return Result::ERROR_HEADER_INVALID;
+}
+
+CrxVerifier::Result CrxVerifier::VerifyCrx2(base::File* file,
+ crypto::SecureHash* hash) const {
+ DCHECK(!require_publisher_proof_);
+ 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.
+ if (key_size > kMaxPublicKeySize)
+ return Result::ERROR_HEADER_INVALID;
+ uint32_t sig_size = ReadAndHashLittleEndianUInt32(file, hash);
+ if (sig_size > kMaxSignatureSize)
+ return Result::ERROR_HEADER_INVALID;
+ std::vector<uint8_t> key(key_size);
+ if (ReadAndHashBuffer(key.data(), key_size, file, hash) !=
+ static_cast<int>(key_size))
+ return Result::ERROR_HEADER_INVALID;
+ for (const auto& expected_hash : key_hashes_) {
+ // In practice we expect zero or one key_hashes_ for CRX2 files.
+ std::vector<uint8_t> hash(crypto::kSHA256Length);
+ std::unique_ptr<crypto::SecureHash> sha256(
Sorin Jianu 2017/05/15 19:49:52 prefer = syntax
waffles 2017/05/16 00:29:02 Done.
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ sha256->Update(key.data(), key.size());
+ sha256->Finish(hash.data(), hash.size());
+ if (hash != expected_hash)
+ return Result::ERROR_REQUIRED_PROOF_MISSING;
+ }
+
+ std::vector<uint8_t> sig(sig_size);
+ if (ReadAndHashBuffer(sig.data(), sig_size, file, hash) !=
+ static_cast<int>(sig_size))
+ return Result::ERROR_HEADER_INVALID;
+ crypto::SignatureVerifier verifier;
+ if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ sig.data(), sig.size(), key.data(), key.size())) {
+ return Result::ERROR_SIGNATURE_INITIALIZATION_FAILED;
+ }
+
+ // Read the rest of the file.
+ uint8_t buffer[1 << 12] = {};
+ size_t len;
Sorin Jianu 2017/05/15 19:49:52 init
waffles 2017/05/16 00:29:02 Done.
+ while ((len = ReadAndHashBuffer(buffer, arraysize(buffer), file, hash)) > 0)
+ verifier.VerifyUpdate(buffer, len);
+ if (!verifier.VerifyFinal())
+ return Result::ERROR_SIGNATURE_VERIFICATION_FAILED;
+
+ 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.
+ std::string(reinterpret_cast<char*>(&key.front()), key.size());
+ if (public_key_)
+ base::Base64Encode(public_key_bytes, public_key_);
+ if (crx_id_)
+ *crx_id_ = id_util::GenerateId(public_key_bytes);
+ return Result::OK_FULL;
+}
+
+} // namespace crx_file

Powered by Google App Engine
This is Rietveld 408576698