Chromium Code Reviews| 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) |