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..0a26b77d6f99fb8cadbcdeae0437bcedd739003a 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,28 @@ 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"; |
+ |
+// 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}; |
+ |
+using VerifierCollection = |
+ std::vector<std::unique_ptr<crypto::SignatureVerifier>>; |
+using RepeatedProof = google::protobuf::RepeatedPtrField<AsymmetricKeyProof>; |
+ |
int ReadAndHashBuffer(uint8_t* buffer, |
int length, |
base::File* file, |
@@ -48,14 +71,124 @@ uint32_t ReadAndHashLittleEndianUInt32(base::File* file, |
return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; |
} |
+// Read to the end of the file, updating the hash and all verifiers. |
+bool ReadHashAndVerifyArchive(base::File* file, |
+ crypto::SecureHash* hash, |
+ const VerifierCollection& 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; |
+} |
+ |
+// The remaining contents of a Crx3 file are [header-size][header][archive]. |
+// [header] is an encoded protocol buffer and contains both a signed and |
+// unsigned section. The unsigned section contains a set of key/signature pairs, |
+// and the signed section is the encoding of another protocol buffer. All |
+// signatures cover [prefix][signed-header-size][signed-header][archive]. |
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-size] and [header]. |
+ 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); |
+ // Assuming kMaxHeaderSize can fit in an int, the following cast is safe. |
+ 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]. |
+ 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 = id_util::GenerateIdFromHex( |
+ base::HexEncode(crx_id_encoded.data(), crx_id_encoded.size())); |
+ |
+ // Create a little-endian representation of [signed-header-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}; |
+ |
+ // Create a set of all required key hashes. |
+ std::set<std::vector<uint8_t>> required_key_set(required_key_hashes.begin(), |
+ required_key_hashes.end()); |
+ if (require_publisher_key) { |
+ required_key_set.emplace(std::begin(kPublisherKeyHash), |
+ std::end(kPublisherKeyHash)); |
+ } |
+ |
+ using ProofFetcher = const RepeatedProof& (CrxFileHeader::*)() const; |
+ ProofFetcher rsa = &CrxFileHeader::sha256_with_rsa; |
+ ProofFetcher ecdsa = &CrxFileHeader::sha256_with_ecdsa; |
+ |
+ std::string public_key_bytes; |
+ VerifierCollection verifiers; |
+ verifiers.reserve(header.sha256_with_rsa_size() + |
+ header.sha256_with_ecdsa_size()); |
+ const std::vector< |
+ std::pair<ProofFetcher, crypto::SignatureVerifier::SignatureAlgorithm>> |
+ proof_types = { |
+ std::make_pair(rsa, crypto::SignatureVerifier::RSA_PKCS1_SHA256), |
+ std::make_pair(ecdsa, crypto::SignatureVerifier::ECDSA_SHA256)}; |
+ |
+ // Initialize all verifiers and update them with |
+ // [prefix][signed-header-size][signed-header]. |
+ // Clear any elements of required_key_set that are encountered, and watch for |
+ // the developer key. |
+ for (const auto& proof_type : proof_types) { |
+ for (const auto& proof : (header.*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); |
+ auto 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; |
+ |
+ // Update and finalize the verifiers with [archive]. |
+ if (!ReadHashAndVerifyArchive(file, hash, verifiers)) |
+ return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED; |
+ |
+ base::Base64Encode(public_key_bytes, public_key); |
+ *crx_id = declared_crx_id; |
+ return VerifierResult::OK_FULL; |
} |
VerifierResult VerifyCrx2( |
@@ -75,7 +208,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,25 +222,20 @@ 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>()); |
+ 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()); |
- if (public_key) |
- base::Base64Encode(public_key_bytes, public_key); |
- if (crx_id) |
- *crx_id = id_util::GenerateId(public_key_bytes); |
+ base::Base64Encode(public_key_bytes, public_key); |
+ *crx_id = id_util::GenerateId(public_key_bytes); |
return VerifierResult::OK_FULL; |
} |
@@ -120,6 +248,8 @@ VerifierResult Verify( |
const std::vector<uint8_t>& required_file_hash, |
std::string* public_key, |
std::string* crx_id) { |
+ std::string public_key_local; |
+ std::string crx_id_local; |
base::File file(crx_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
if (!file.IsValid()) |
return VerifierResult::ERROR_FILE_NOT_READABLE; |
@@ -145,11 +275,12 @@ VerifierResult Verify( |
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); |
+ result = VerifyCrx2(&file, file_hash.get(), required_key_hashes, |
+ &public_key_local, &crx_id_local); |
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_local, &crx_id_local, |
+ format == VerifierFormat::CRX3_WITH_PUBLISHER_PROOF); |
else |
result = VerifierResult::ERROR_HEADER_INVALID; |
if (result != VerifierResult::OK_FULL) |
@@ -165,6 +296,12 @@ VerifierResult Verify( |
crypto::kSHA256Length)) |
return VerifierResult::ERROR_FILE_HASH_FAILED; |
} |
+ |
+ // All is well. Set the out-params and return. |
+ if (public_key) |
+ *public_key = public_key_local; |
+ if (crx_id) |
+ *crx_id = crx_id_local; |
return diff ? VerifierResult::OK_DELTA : VerifierResult::OK_FULL; |
} |