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..21a053cec1d502cda88802761e193bb9bf793fe8 |
--- /dev/null |
+++ b/components/crx_file/crx_verifier.cc |
@@ -0,0 +1,171 @@ |
+// 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 "base/memory/ptr_util.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. |
+constexpr uint32_t kMaxPublicKeySize = 1 << 16; |
+ |
+// The maximum size the crx2 parser will tolerate for a signature. |
+constexpr uint32_t kMaxSignatureSize = 1 << 16; |
+ |
+int ReadAndHashBuffer(uint8_t* buffer, |
+ int length, |
+ base::File* file, |
+ crypto::SecureHash* hash) { |
+ static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size."); |
+ 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, else |
+// returns the read uint32. |
+uint32_t ReadAndHashLittleEndianUInt32(base::File* file, |
+ crypto::SecureHash* hash) { |
+ uint8_t buffer[4] = {}; |
+ if (ReadAndHashBuffer(buffer, 4, file, hash) != 4) |
+ return UINT32_MAX; |
+ return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; |
+} |
+ |
+VerifierResult VerifyCrx3( |
+ base::File* file, |
+ crypto::SecureHash* hash, |
+ const std::vector<std::vector<uint8_t>>& required_key_hashes, |
+ std::string* public_key, |
+ std::string* crx_id) { |
+ // Crx3 files are not yet supported - treat this as a malformed header. |
+ return VerifierResult::ERROR_HEADER_INVALID; |
+} |
+ |
+VerifierResult VerifyCrx2( |
+ base::File* file, |
+ crypto::SecureHash* hash, |
+ const std::vector<std::vector<uint8_t>>& required_key_hashes, |
+ std::string* public_key, |
+ std::string* crx_id) { |
+ const uint32_t key_size = ReadAndHashLittleEndianUInt32(file, hash); |
+ if (key_size > kMaxPublicKeySize) |
+ return VerifierResult::ERROR_HEADER_INVALID; |
+ const uint32_t sig_size = ReadAndHashLittleEndianUInt32(file, hash); |
+ if (sig_size > kMaxSignatureSize) |
+ return VerifierResult::ERROR_HEADER_INVALID; |
+ std::vector<uint8_t> key(key_size); |
+ if (ReadAndHashBuffer(key.data(), key_size, file, hash) != |
+ static_cast<int>(key_size)) |
+ return VerifierResult::ERROR_HEADER_INVALID; |
+ for (const auto& expected_hash : required_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 = |
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256); |
+ sha256->Update(key.data(), key.size()); |
+ sha256->Finish(hash.data(), hash.size()); |
+ if (hash != expected_hash) |
+ return VerifierResult::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 VerifierResult::ERROR_HEADER_INVALID; |
+ crypto::SignatureVerifier verifier; |
+ if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, |
+ sig.data(), sig.size(), key.data(), key.size())) { |
+ return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED; |
+ } |
+ |
+ // Read the rest of the file. |
+ uint8_t buffer[1 << 12] = {}; |
+ size_t len = 0; |
+ while ((len = ReadAndHashBuffer(buffer, arraysize(buffer), file, hash)) > 0) |
+ verifier.VerifyUpdate(buffer, len); |
+ if (!verifier.VerifyFinal()) |
+ return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED; |
+ |
+ const std::string public_key_bytes(key.begin(), key.end()); |
+ if (public_key) |
+ base::Base64Encode(public_key_bytes, public_key); |
+ if (crx_id) |
+ *crx_id = id_util::GenerateId(public_key_bytes); |
+ return VerifierResult::OK_FULL; |
+} |
+ |
+} // namespace |
+ |
+VerifierResult Verify( |
+ const base::FilePath& crx_path, |
+ const VerifierFormat& format, |
+ const std::vector<std::vector<uint8_t>>& required_key_hashes, |
+ const std::vector<uint8_t>& required_file_hash, |
+ std::string* public_key, |
+ std::string* crx_id) { |
+ base::File file(crx_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
+ if (!file.IsValid()) |
+ return VerifierResult::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] = {}; |
+ if (file.ReadAtCurrentPos(buffer, kCrx2FileHeaderMagicSize) != |
+ kCrx2FileHeaderMagicSize) |
+ return VerifierResult::ERROR_HEADER_INVALID; |
+ if (!strncmp(buffer, kCrxDiffFileHeaderMagic, kCrx2FileHeaderMagicSize)) |
+ diff = true; |
+ else if (strncmp(buffer, kCrx2FileHeaderMagic, kCrx2FileHeaderMagicSize)) |
+ return VerifierResult::ERROR_HEADER_INVALID; |
+ file_hash->Update(buffer, sizeof(buffer)); |
+ |
+ // Version number. |
+ const uint32_t version = |
+ ReadAndHashLittleEndianUInt32(&file, file_hash.get()); |
+ VerifierResult result; |
+ if (format == VerifierFormat::CRX2_OR_CRX3 && |
+ (version == 2 || (diff && version == 0))) |
+ result = VerifyCrx2(&file, file_hash.get(), required_key_hashes, public_key, |
+ crx_id); |
+ else if (version == 3) |
+ result = VerifyCrx3(&file, file_hash.get(), required_key_hashes, public_key, |
+ crx_id); |
+ else |
+ result = VerifierResult::ERROR_HEADER_INVALID; |
+ if (result != VerifierResult::OK_FULL) |
+ return result; |
+ |
+ // Finalize file hash. |
+ uint8_t final_hash[crypto::kSHA256Length] = {}; |
+ file_hash->Finish(final_hash, sizeof(final_hash)); |
+ if (!required_file_hash.empty()) { |
+ if (required_file_hash.size() != crypto::kSHA256Length) |
+ return VerifierResult::ERROR_EXPECTED_HASH_INVALID; |
+ if (!crypto::SecureMemEqual(final_hash, required_file_hash.data(), |
+ crypto::kSHA256Length)) |
+ return VerifierResult::ERROR_FILE_HASH_FAILED; |
+ } |
+ return diff ? VerifierResult::OK_DELTA : VerifierResult::OK_FULL; |
+} |
+ |
+} // namespace crx_file |