From e9041bab1ada63aaaf8d5a6595fccd82015f1b5b Mon Sep 17 00:00:00 2001 From: Holden Warriner Date: Fri, 30 Jun 2023 14:01:09 -0700 Subject: [PATCH] Add support to verify a Crx from a string (#780) With in-memory updates the Crx package will still be in memory when it needs be verified. This change duplicates a lot of Chromium code but this should only be temporary - please see the comments in crx_verifier.cc for more on this. b/158043520 (cherry picked from commit b62c600a18eb4a5e2dd9522afd0e9836a7f24398) --- components/crx_file/crx_verifier.cc | 306 ++++++++++++++++++- components/crx_file/crx_verifier.h | 13 +- components/crx_file/crx_verifier_unittest.cc | 252 +++++++++++++++ 3 files changed, 569 insertions(+), 2 deletions(-) diff --git a/components/crx_file/crx_verifier.cc b/components/crx_file/crx_verifier.cc index b8a875e48978..3ac941a9cdc4 100644 --- a/components/crx_file/crx_verifier.cc +++ b/components/crx_file/crx_verifier.cc @@ -1,4 +1,4 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2017 The Cobalt Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -64,6 +64,23 @@ int ReadAndHashBuffer(uint8_t* buffer, hash->Update(buffer, read); return read; } +#if defined(IN_MEMORY_UPDATES) +int ReadAndHashBufferFromString(uint8_t* buffer, + int length, + std::string::const_iterator* it, + crypto::SecureHash* hash) { + static_assert(sizeof(char) == sizeof(uint8_t), "Unsupported char size."); + memcpy(buffer, &(**it), length); + hash->Update(buffer, length); + + // TODO(b/158043520): consider wrapping the CRX string in a type that keeps + // track of how much of the string has already been copied so that the string + // can be "read" like a file, without direct management of the iterator. + std::advance(*it, length); + + return length; +} +#endif // Returns UINT32_MAX in the case of an unexpected EOF or read error, else // returns the read uint32. @@ -74,6 +91,16 @@ uint32_t ReadAndHashLittleEndianUInt32(base::File* file, return UINT32_MAX; return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; } +#if defined(IN_MEMORY_UPDATES) +// Returns the read uint32. +uint32_t ReadAndHashLittleEndianUInt32FromString( + std::string::const_iterator* it, + crypto::SecureHash* hash) { + uint8_t buffer[4] = {}; + ReadAndHashBufferFromString(buffer, sizeof(buffer), it, hash); + return buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; +} +#endif // Read to the end of the file, updating the hash and all verifiers. bool ReadHashAndVerifyArchive(base::File* file, @@ -92,6 +119,34 @@ bool ReadHashAndVerifyArchive(base::File* file, } return len == 0; } +#if defined(IN_MEMORY_UPDATES) +// Reads to the end of the string, updating the hash and all verifiers. +bool ReadHashAndVerifyArchiveFromString(const std::string& crx_str, + std::string::const_iterator* it, + crypto::SecureHash* hash, + const VerifierCollection& verifiers) { + int remaining_bytes = crx_str.end() - *it; + + uint8_t buffer[1 << 12] = {}; + while (remaining_bytes > 0) { + size_t len = remaining_bytes >= base::size(buffer) ? base::size(buffer) + : remaining_bytes; + + ReadAndHashBufferFromString(buffer, len, it, hash); + remaining_bytes -= len; + + for (auto& verifier : verifiers) { + verifier->VerifyUpdate(base::make_span(buffer, len)); + } + } + + for (auto& verifier : verifiers) { + if (!verifier->VerifyFinal()) + return false; + } + return true; +} +#endif // The remaining contents of a Crx3 file are [header-size][header][archive]. // [header] is an encoded protocol buffer and contains both a signed and @@ -208,6 +263,129 @@ VerifierResult VerifyCrx3( return VerifierResult::OK_FULL; } +#if defined(IN_MEMORY_UPDATES) +// TODO(b/158043520): because we want to leave the Chromium code relatively +// intact and also want to support both in-memory and legacy updates in the same +// build while we develop the feature, there is for now a lot of duplicated code +// in some of these functions. For these functions that implement longer +// algorithms that are mostly agnostic about the Crx's representation we could +// likely share code by making them generic, but it probably makes more sense to +// live with the duplication for now. Once the feature is complete and we only +// need to support in-memory updates in builds, we can have one copy of the +// function that uses preprocessor conditions to enlist the appropriate helpers. +VerifierResult VerifyCrx3FromString( + const std::string& crx_str, + std::string::const_iterator* it, + crypto::SecureHash* hash, + const std::vector>& required_key_hashes, + std::string* public_key, + std::string* crx_id, + bool require_publisher_key, + bool accept_publisher_test_key) { + // Parse [header-size] and [header]. + const uint32_t header_size = + ReadAndHashLittleEndianUInt32FromString(it, hash); + if (header_size > kMaxHeaderSize) + return VerifierResult::ERROR_HEADER_INVALID; + std::vector header_bytes(header_size); + // Assuming kMaxHeaderSize can fit in an int, the following cast is safe. + if (ReadAndHashBufferFromString(header_bytes.data(), header_size, it, hash) != + static_cast(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[] = { + static_cast(signed_header_size), + static_cast(signed_header_size >> 8), + static_cast(signed_header_size >> 16), + static_cast(signed_header_size >> 24)}; + + // Create a set of all required key hashes. + std::set> required_key_set(required_key_hashes.begin(), + required_key_hashes.end()); + + 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> + proof_types = { + std::make_pair(rsa, crypto::SignatureVerifier::RSA_PKCS1_SHA256), + std::make_pair(ecdsa, crypto::SignatureVerifier::ECDSA_SHA256)}; + + std::vector publisher_key(std::begin(kPublisherKeyHash), + std::end(kPublisherKeyHash)); + base::Optional> publisher_test_key; + if (accept_publisher_test_key) { + publisher_test_key.emplace(std::begin(kPublisherTestKeyHash), + std::end(kPublisherTestKeyHash)); + } + bool found_publisher_key = false; + + // 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 key_hash(crypto::kSHA256Length); + crypto::SHA256HashString(key, key_hash.data(), key_hash.size()); + required_key_set.erase(key_hash); + DCHECK_EQ(accept_publisher_test_key, publisher_test_key.has_value()); + found_publisher_key = + found_publisher_key || key_hash == publisher_key || + (accept_publisher_test_key && key_hash == *publisher_test_key); + auto v = std::make_unique(); + static_assert(sizeof(unsigned char) == sizeof(uint8_t), + "Unsupported char size."); + if (!v->VerifyInit(proof_type.second, + base::as_bytes(base::make_span(sig)), + base::as_bytes(base::make_span(key)))) + return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED; + v->VerifyUpdate(kSignatureContext); + v->VerifyUpdate(header_size_octets); + v->VerifyUpdate(base::as_bytes(base::make_span(signed_header_data_str))); + verifiers.push_back(std::move(v)); + } + } + if (public_key_bytes.empty() || !required_key_set.empty()) + return VerifierResult::ERROR_REQUIRED_PROOF_MISSING; + + if (require_publisher_key && !found_publisher_key) + return VerifierResult::ERROR_REQUIRED_PROOF_MISSING; + + // Update and finalize the verifiers with [archive]. + if (!ReadHashAndVerifyArchiveFromString(crx_str, it, hash, verifiers)) + return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED; + + base::Base64Encode(public_key_bytes, public_key); + *crx_id = declared_crx_id; + return VerifierResult::OK_FULL; +} +#endif + VerifierResult VerifyCrx2( base::File* file, crypto::SecureHash* hash, @@ -255,6 +433,56 @@ VerifierResult VerifyCrx2( return VerifierResult::OK_FULL; } +#if defined(IN_MEMORY_UPDATES) +VerifierResult VerifyCrx2FromString( + const std::string& crx_str, + std::string::const_iterator* it, + crypto::SecureHash* hash, + const std::vector>& required_key_hashes, + std::string* public_key, + std::string* crx_id) { + const uint32_t key_size = ReadAndHashLittleEndianUInt32FromString(it, hash); + if (key_size > kMaxPublicKeySize) + return VerifierResult::ERROR_HEADER_INVALID; + const uint32_t sig_size = ReadAndHashLittleEndianUInt32FromString(it, hash); + if (sig_size > kMaxSignatureSize) + return VerifierResult::ERROR_HEADER_INVALID; + std::vector key(key_size); + if (ReadAndHashBufferFromString(key.data(), key_size, it, hash) != + static_cast(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 hash(crypto::kSHA256Length); + std::unique_ptr 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 sig(sig_size); + if (ReadAndHashBufferFromString(sig.data(), sig_size, it, hash) != + static_cast(sig_size)) + return VerifierResult::ERROR_HEADER_INVALID; + std::vector> verifiers; + verifiers.push_back(std::make_unique()); + if (!verifiers[0]->VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, sig, + key)) { + return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED; + } + + if (!ReadHashAndVerifyArchiveFromString(crx_str, it, hash, verifiers)) + return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED; + + const std::string public_key_bytes(key.begin(), key.end()); + base::Base64Encode(public_key_bytes, public_key); + *crx_id = id_util::GenerateId(public_key_bytes); + return VerifierResult::OK_FULL; +} +#endif + } // namespace VerifierResult Verify( @@ -330,4 +558,80 @@ VerifierResult Verify( return diff ? VerifierResult::OK_DELTA : VerifierResult::OK_FULL; } +#if defined(IN_MEMORY_UPDATES) +VerifierResult Verify( + const std::string& crx_str, + const VerifierFormat& format, + const std::vector>& required_key_hashes, + const std::vector& required_file_hash, + std::string* public_key, + std::string* crx_id) { + std::string public_key_local; + std::string crx_id_local; + + std::unique_ptr file_hash = + crypto::SecureHash::Create(crypto::SecureHash::SHA256); + + // Magic number. + bool diff = false; + if (!strncmp(crx_str.c_str(), kCrxDiffFileHeaderMagic, + kCrxFileHeaderMagicSize)) { + diff = true; + } else if (strncmp(crx_str.c_str(), kCrxFileHeaderMagic, + kCrxFileHeaderMagicSize)) { + return VerifierResult::ERROR_HEADER_INVALID; + } + file_hash->Update(crx_str.c_str(), kCrxFileHeaderMagicSize); + + std::string::const_iterator it = crx_str.begin(); + // Advance the iterator past the magic string embedded in the header. + std::advance(it, kCrxFileHeaderMagicSize); + + // Version number. + const uint32_t version = + ReadAndHashLittleEndianUInt32FromString(&it, file_hash.get()); + VerifierResult result; + if (version == 2) + SB_LOG(WARNING) << "The string is in CRX2 format, which is deprecated and " + << "will not be supported in M78+"; + if (format == VerifierFormat::CRX2_OR_CRX3 && + (version == 2 || (diff && version == 0))) { + result = + VerifyCrx2FromString(crx_str, &it, file_hash.get(), required_key_hashes, + &public_key_local, &crx_id_local); + } else if (version == 3) { + bool require_publisher_key = + format == VerifierFormat::CRX3_WITH_PUBLISHER_PROOF || + format == VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF; + result = VerifyCrx3FromString( + crx_str, &it, file_hash.get(), required_key_hashes, &public_key_local, + &crx_id_local, require_publisher_key, + format == VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF); + } 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; + } + } + + // 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; +} +#endif + } // namespace crx_file diff --git a/components/crx_file/crx_verifier.h b/components/crx_file/crx_verifier.h index b770626323f4..2631a89da576 100644 --- a/components/crx_file/crx_verifier.h +++ b/components/crx_file/crx_verifier.h @@ -1,4 +1,4 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2017 The Cobalt Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -51,6 +51,17 @@ VerifierResult Verify( std::string* public_key, std::string* crx_id); +#if defined(IN_MEMORY_UPDATES) +// An overload that instead verifies |crx_str| as a valid Crx. +VerifierResult Verify( + const std::string& crx_str, + const VerifierFormat& format, + const std::vector>& required_key_hashes, + const std::vector& required_file_hash, + std::string* public_key, + std::string* crx_id); +#endif + } // namespace crx_file #endif // COMPONENTS_CRX_FILE_CRX_VERIFIER_H_ diff --git a/components/crx_file/crx_verifier_unittest.cc b/components/crx_file/crx_verifier_unittest.cc index 4c41e4e8034c..05969dbfff11 100644 --- a/components/crx_file/crx_verifier_unittest.cc +++ b/components/crx_file/crx_verifier_unittest.cc @@ -5,6 +5,9 @@ #include "components/crx_file/crx_verifier.h" #include "base/base_paths.h" #include "base/files/file_path.h" +#if defined(IN_MEMORY_UPDATES) +#include "base/files/file_util.h" +#endif #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #if defined(STARBOARD) @@ -76,6 +79,23 @@ TEST_F(CrxVerifierTest, ValidFullCrx2) { EXPECT_EQ(std::string(kOjjKey), public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, ValidFullCrx2FromString) { + const std::vector> keys; + const std::vector hash; + std::string public_key; + std::string crx_id; + std::string crx_str; + ASSERT_TRUE(base::ReadFileToString(TestFile("valid.crx2"), &crx_str)); + + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(crx_str, VerifierFormat::CRX2_OR_CRX3, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); +} +#endif + TEST_F(CrxVerifierTest, ValidFullCrx3) { const std::vector> keys; const std::vector hash; @@ -97,6 +117,30 @@ TEST_F(CrxVerifierTest, ValidFullCrx3) { EXPECT_EQ(std::string(kOjjKey), public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, ValidFullCrx3FromString) { + const std::vector> keys; + const std::vector hash; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + std::string crx_str; + ASSERT_TRUE( + base::ReadFileToString(TestFile("valid_no_publisher.crx3"), &crx_str)); + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(crx_str, VerifierFormat::CRX2_OR_CRX3, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); + + public_key = "UNSET"; + crx_id = "UNSET"; + EXPECT_EQ(VerifierResult::OK_FULL, Verify(crx_str, VerifierFormat::CRX3, keys, + hash, &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); +} +#endif + TEST_F(CrxVerifierTest, Crx3RejectsCrx2) { const std::vector> keys; const std::vector hash; @@ -110,6 +154,23 @@ TEST_F(CrxVerifierTest, Crx3RejectsCrx2) { EXPECT_EQ("UNSET", public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, Crx3RejectsCrx2FromString) { + const std::vector> keys; + const std::vector hash; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + std::string crx_str; + ASSERT_TRUE(base::ReadFileToString(TestFile("valid.crx2"), &crx_str)); + + EXPECT_EQ( + VerifierResult::ERROR_HEADER_INVALID, + Verify(crx_str, VerifierFormat::CRX3, keys, hash, &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} +#endif + TEST_F(CrxVerifierTest, VerifiesFileHash) { const std::vector> keys; std::vector hash; @@ -146,6 +207,48 @@ TEST_F(CrxVerifierTest, VerifiesFileHash) { EXPECT_EQ("UNSET", public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, VerifiesFileHashForCrxFromString) { + const std::vector> keys; + std::vector hash; + std::string crx_str; + ASSERT_TRUE( + base::ReadFileToString(TestFile("valid_no_publisher.crx3"), &crx_str)); + + EXPECT_TRUE(base::HexStringToBytes( + "d033c510f9e4ee081ccb60ea2bf530dc2e5cb0e71085b55503c8b13b74515fe4", + &hash)); + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(crx_str, VerifierFormat::CRX2_OR_CRX3, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); + + hash.clear(); + EXPECT_TRUE(base::HexStringToBytes(std::string(32, '0'), &hash)); + public_key = "UNSET"; + crx_id = "UNSET"; + EXPECT_EQ( + VerifierResult::ERROR_EXPECTED_HASH_INVALID, + Verify(crx_str, VerifierFormat::CRX3, keys, hash, &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); + + hash.clear(); + EXPECT_TRUE(base::HexStringToBytes(std::string(64, '0'), &hash)); + public_key = "UNSET"; + crx_id = "UNSET"; + EXPECT_EQ( + VerifierResult::ERROR_FILE_HASH_FAILED, + Verify(crx_str, VerifierFormat::CRX3, keys, hash, &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} +#endif + TEST_F(CrxVerifierTest, ChecksRequiredKeyHashes) { const std::vector hash; @@ -175,6 +278,39 @@ TEST_F(CrxVerifierTest, ChecksRequiredKeyHashes) { EXPECT_EQ("UNSET", public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, ChecksRequiredKeyHashesForCrxFromString) { + const std::vector hash; + std::string crx_str; + ASSERT_TRUE( + base::ReadFileToString(TestFile("valid_no_publisher.crx3"), &crx_str)); + + std::vector good_key; + EXPECT_TRUE(base::HexStringToBytes( + "e996dfa8eed34bc6614a57bb7308cd7e519bcc690841e1969f7cb173ef16800a", + &good_key)); + const std::vector> good_keys = {good_key}; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(crx_str, VerifierFormat::CRX2_OR_CRX3, good_keys, hash, + &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); + + std::vector bad_key; + EXPECT_TRUE(base::HexStringToBytes(std::string(64, '0'), &bad_key)); + const std::vector> bad_keys = {bad_key}; + public_key = "UNSET"; + crx_id = "UNSET"; + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(crx_str, VerifierFormat::CRX3, bad_keys, hash, &public_key, + &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} +#endif + TEST_F(CrxVerifierTest, ChecksPinnedKey) { const std::vector hash; const std::vector> keys; @@ -206,6 +342,49 @@ TEST_F(CrxVerifierTest, ChecksPinnedKey) { EXPECT_EQ("UNSET", public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, ChecksPinnedKeyForCrxFromString) { + const std::vector hash; + const std::vector> keys; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + std::string valid_publisher_crx_str; + EXPECT_TRUE(base::ReadFileToString(TestFile("valid_publisher.crx3"), + &valid_publisher_crx_str)); + + EXPECT_EQ( + VerifierResult::OK_FULL, + Verify(valid_publisher_crx_str, VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, + keys, hash, &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); + + public_key = "UNSET"; + crx_id = "UNSET"; + std::string valid_test_publisher_crx_str; + EXPECT_TRUE(base::ReadFileToString(TestFile("valid_test_publisher.crx3"), + &valid_test_publisher_crx_str)); + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(valid_test_publisher_crx_str, + VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); + + public_key = "UNSET"; + crx_id = "UNSET"; + std::string valid_no_publisher_crx_str; + EXPECT_TRUE(base::ReadFileToString(TestFile("valid_no_publisher.crx3"), + &valid_no_publisher_crx_str)); + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(valid_no_publisher_crx_str, + VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} +#endif + TEST_F(CrxVerifierTest, ChecksPinnedKeyAcceptsTest) { const std::vector hash; const std::vector> keys; @@ -237,6 +416,48 @@ TEST_F(CrxVerifierTest, ChecksPinnedKeyAcceptsTest) { EXPECT_EQ("UNSET", public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, ChecksPinnedKeyAcceptsTestForCrxFromString) { + const std::vector hash; + const std::vector> keys; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + std::string valid_publisher_crx_str; + EXPECT_TRUE(base::ReadFileToString(TestFile("valid_publisher.crx3"), + &valid_publisher_crx_str)); + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(valid_publisher_crx_str, + VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); + + public_key = "UNSET"; + crx_id = "UNSET"; + std::string valid_test_publisher_crx_str; + EXPECT_TRUE(base::ReadFileToString(TestFile("valid_test_publisher.crx3"), + &valid_test_publisher_crx_str)); + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(valid_test_publisher_crx_str, + VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ(std::string(kJlnHash), crx_id); + EXPECT_EQ(std::string(kJlnKey), public_key); + + public_key = "UNSET"; + crx_id = "UNSET"; + std::string valid_no_publisher_crx_str; + EXPECT_TRUE(base::ReadFileToString(TestFile("valid_no_publisher.crx3"), + &valid_no_publisher_crx_str)); + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(valid_no_publisher_crx_str, + VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} +#endif + TEST_F(CrxVerifierTest, NullptrSafe) { const std::vector hash; const std::vector> keys; @@ -246,6 +467,20 @@ TEST_F(CrxVerifierTest, NullptrSafe) { nullptr, nullptr)); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, NullptrSafeForCrxFromString) { + const std::vector hash; + const std::vector> keys; + std::string crx_str; + ASSERT_TRUE( + base::ReadFileToString(TestFile("valid_publisher.crx3"), &crx_str)); + + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(crx_str, VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, + hash, nullptr, nullptr)); +} +#endif + TEST_F(CrxVerifierTest, RequiresDeveloperKey) { const std::vector hash; const std::vector> keys; @@ -258,4 +493,21 @@ TEST_F(CrxVerifierTest, RequiresDeveloperKey) { EXPECT_EQ("UNSET", public_key); } +#if defined(IN_MEMORY_UPDATES) +TEST_F(CrxVerifierTest, RequiresDeveloperKeyForCrxFromString) { + const std::vector hash; + const std::vector> keys; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + std::string crx_str; + ASSERT_TRUE(base::ReadFileToString(TestFile("unsigned.crx3"), &crx_str)); + + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(crx_str, VerifierFormat::CRX2_OR_CRX3, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} +#endif + } // namespace crx_file