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

Unified Diff: components/crx_file/crx_verifier.cc

Issue 2888853003: Expand CRX verifier to verify CRX₃ files. (Closed)
Patch Set: Through #5 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
« no previous file with comments | « components/crx_file/crx3.proto ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: components/crx_file/crx_verifier.cc
diff --git a/components/crx_file/crx_verifier.cc b/components/crx_file/crx_verifier.cc
index 21a053cec1d502cda88802761e193bb9bf793fe8..b032d4818e5034cb642b66565c302b57d0a6c5c0 100644
--- a/components/crx_file/crx_verifier.cc
+++ b/components/crx_file/crx_verifier.cc
@@ -5,13 +5,20 @@
#include "components/crx_file/crx_verifier.h"
#include <cstring>
+#include <iterator>
#include <memory>
+#include <set>
+#include <utility>
#include "base/base64.h"
+#include "base/bind.h"
+#include "base/callback.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
#include "components/crx_file/crx2_file.h"
+#include "components/crx_file/crx3.pb.h"
#include "components/crx_file/id_util.h"
#include "crypto/secure_hash.h"
#include "crypto/secure_util.h"
@@ -22,12 +29,24 @@ namespace crx_file {
namespace {
-// The maximum size the crx2 parser will tolerate for a public key.
+// 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.
+// The maximum size the Crx2 parser will tolerate for a signature.
constexpr uint32_t kMaxSignatureSize = 1 << 16;
+// The maximum size the Crx3 parser will tolerate for a header.
+constexpr uint32_t kMaxHeaderSize = 1 << 18;
+
+// The context for Crx3 signing, encoded in UTF8.
+constexpr unsigned char kSignatureContext[] = u8"CRX3 SignedData\0";
+
+// The SHA256 hash of the "ecdsa_2017_public" Crx3 key.
+constexpr uint8_t kPublisherKeyHash[] = {
+ 0x61, 0xf7, 0xf2, 0xa6, 0xbf, 0xcf, 0x74, 0xcd, 0x0b, 0xc1, 0xfe,
+ 0x24, 0x97, 0xcc, 0x9b, 0x04, 0x25, 0x4c, 0x65, 0x8f, 0x79, 0xf2,
+ 0x14, 0x53, 0x92, 0x86, 0x7e, 0xa8, 0x36, 0x63, 0x67, 0xcf};
+
int ReadAndHashBuffer(uint8_t* buffer,
int length,
base::File* file,
@@ -48,14 +67,115 @@ uint32_t ReadAndHashLittleEndianUInt32(base::File* file,
return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0];
}
+bool ReadHashAndVerifyArchive(
+ base::File* file,
+ crypto::SecureHash* hash,
+ const std::vector<std::unique_ptr<crypto::SignatureVerifier>>& verifiers) {
+ uint8_t buffer[1 << 12] = {};
+ size_t len = 0;
+ while ((len = ReadAndHashBuffer(buffer, arraysize(buffer), file, hash)) > 0) {
+ for (auto& verifier : verifiers)
+ verifier->VerifyUpdate(buffer, len);
+ }
+ for (auto& verifier : verifiers) {
+ if (!verifier->VerifyFinal())
+ return false;
+ }
+ return true;
+}
+
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;
+ std::string* crx_id,
+ bool require_publisher_key) {
+ // Parse header data.
+ const uint32_t header_size = ReadAndHashLittleEndianUInt32(file, hash);
+ if (header_size > kMaxHeaderSize)
+ return VerifierResult::ERROR_HEADER_INVALID;
+ std::vector<uint8_t> header_bytes(header_size);
+ if (ReadAndHashBuffer(header_bytes.data(), header_size, file, hash) !=
+ static_cast<int>(header_size))
+ return VerifierResult::ERROR_HEADER_INVALID;
+ CrxFileHeader header;
+ if (!header.ParseFromArray(header_bytes.data(), header_size))
+ return VerifierResult::ERROR_HEADER_INVALID;
+
+ // Parse signed header data.
+ const std::string& signed_header_data_str = header.signed_header_data();
+ SignedData signed_header_data;
+ if (!signed_header_data.ParseFromString(signed_header_data_str))
+ return VerifierResult::ERROR_HEADER_INVALID;
+ const std::string& crx_id_encoded = signed_header_data.crx_id();
+ const std::string declared_crx_id =
+ base::HexEncode(crx_id_encoded.data(), crx_id_encoded.size());
+
+ // Compute the little-endian signed header data size.
+ const int signed_header_size = signed_header_data_str.size();
+ const uint8_t header_size_octets[] = {
+ signed_header_size, signed_header_size >> 8, signed_header_size >> 16,
+ signed_header_size >> 24};
+
+ // Set up the required keys.
+ std::set<std::vector<uint8_t>> required_key_set(required_key_hashes.begin(),
+ required_key_hashes.end());
+ if (require_publisher_key) {
+ const std::vector<uint8_t> publisher_key(std::begin(kPublisherKeyHash),
+ std::end(kPublisherKeyHash));
+ required_key_set.insert(publisher_key);
+ }
+ std::string public_key_bytes;
+ std::vector<std::unique_ptr<crypto::SignatureVerifier>> verifiers;
+ const std::vector<
+ std::pair<std::function<const google::protobuf::RepeatedPtrField<
+ AsymmetricKeyProof>&()>,
+ crypto::SignatureVerifier::SignatureAlgorithm>>
+ proof_types = {
+ std::make_pair([&header] { return header.sha256_with_rsa(); },
+ crypto::SignatureVerifier::RSA_PKCS1_SHA256),
+ std::make_pair([&header] { return header.sha256_with_ecdsa(); },
+ crypto::SignatureVerifier::ECDSA_SHA256)};
+ // Initialize all verifiers and update them with the contexts / prefixes.
+ for (const auto& proof_type : proof_types) {
+ for (const auto& proof : proof_type.first()) {
+ const std::string& key = proof.public_key();
+ const std::string& sig = proof.signature();
+ if (id_util::GenerateId(key) == declared_crx_id)
+ public_key_bytes = key;
+ std::vector<uint8_t> key_hash(crypto::kSHA256Length);
+ crypto::SHA256HashString(key, key_hash.data(), key_hash.size());
+ required_key_set.erase(key_hash);
+ std::unique_ptr<crypto::SignatureVerifier> v =
+ base::MakeUnique<crypto::SignatureVerifier>();
+ static_assert(sizeof(unsigned char) == sizeof(uint8_t),
+ "Unsupported char size.");
+ if (!v->VerifyInit(
+ proof_type.second, reinterpret_cast<const uint8_t*>(sig.data()),
+ sig.size(), reinterpret_cast<const uint8_t*>(key.data()),
+ key.size()))
+ return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED;
+ v->VerifyUpdate(kSignatureContext, arraysize(kSignatureContext));
+ v->VerifyUpdate(header_size_octets, arraysize(header_size_octets));
+ v->VerifyUpdate(
+ reinterpret_cast<const uint8_t*>(signed_header_data_str.data()),
+ signed_header_data_str.size());
+ verifiers.push_back(std::move(v));
+ }
+ }
+ if (!public_key_bytes.empty() || !required_key_set.empty())
+ return VerifierResult::ERROR_REQUIRED_PROOF_MISSING;
+
+ // Verify the entire archive.
+ if (!ReadHashAndVerifyArchive(file, hash, verifiers))
+ return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED;
+
+ if (public_key)
+ base::Base64Encode(public_key_bytes, public_key);
+ if (crx_id)
+ *crx_id = declared_crx_id;
+ return VerifierResult::OK_FULL;
}
VerifierResult VerifyCrx2(
@@ -75,7 +195,7 @@ VerifierResult VerifyCrx2(
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.
+ // 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);
@@ -89,18 +209,15 @@ VerifierResult VerifyCrx2(
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())) {
+ std::vector<std::unique_ptr<crypto::SignatureVerifier>> verifiers;
+ verifiers.push_back(base::MakeUnique<crypto::SignatureVerifier>());
Sorin Jianu 2017/05/18 00:53:31 we might be able to initialize the vector with the
waffles 2017/05/18 17:47:32 Unfortunately I don't think you can use an initial
+ if (!verifiers[0]->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())
+ if (!ReadHashAndVerifyArchive(file, hash, verifiers))
return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED;
const std::string public_key_bytes(key.begin(), key.end());
@@ -148,8 +265,9 @@ VerifierResult Verify(
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);
+ result =
+ VerifyCrx3(&file, file_hash.get(), required_key_hashes, public_key,
+ crx_id, format == VerifierFormat::CRX3_WITH_PUBLISHER_PROOF);
else
result = VerifierResult::ERROR_HEADER_INVALID;
if (result != VerifierResult::OK_FULL)
« no previous file with comments | « components/crx_file/crx3.proto ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698