diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-17 13:57:45 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-19 13:44:40 +0000 |
commit | 6ec7b8da05d21a3878bd21c691b41e675d74bb1c (patch) | |
tree | b87f250bc19413750b9bb9cdbf2da20ef5014820 /chromium/components/crx_file | |
parent | ec02ee4181c49b61fce1c8fb99292dbb8139cc90 (diff) | |
download | qtwebengine-chromium-6ec7b8da05d21a3878bd21c691b41e675d74bb1c.tar.gz |
BASELINE: Update Chromium to 60.0.3112.70
Change-Id: I9911c2280a014d4632f254857876a395d4baed2d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/components/crx_file')
-rw-r--r-- | chromium/components/crx_file/BUILD.gn | 35 | ||||
-rw-r--r-- | chromium/components/crx_file/OWNERS | 4 | ||||
-rw-r--r-- | chromium/components/crx_file/crx2_file.cc | 79 | ||||
-rw-r--r-- | chromium/components/crx_file/crx2_file.h | 76 | ||||
-rw-r--r-- | chromium/components/crx_file/crx3.proto | 55 | ||||
-rw-r--r-- | chromium/components/crx_file/crx_file.cc | 220 | ||||
-rw-r--r-- | chromium/components/crx_file/crx_file.h | 118 | ||||
-rw-r--r-- | chromium/components/crx_file/crx_verifier.cc | 308 | ||||
-rw-r--r-- | chromium/components/crx_file/crx_verifier.h | 53 | ||||
-rw-r--r-- | chromium/components/crx_file/crx_verifier_unittest.cc | 196 |
10 files changed, 802 insertions, 342 deletions
diff --git a/chromium/components/crx_file/BUILD.gn b/chromium/components/crx_file/BUILD.gn index 8c5cceb5db2..412528e188e 100644 --- a/chromium/components/crx_file/BUILD.gn +++ b/chromium/components/crx_file/BUILD.gn @@ -2,10 +2,14 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//third_party/protobuf/proto_library.gni") + static_library("crx_file") { sources = [ - "crx_file.cc", - "crx_file.h", + "crx2_file.cc", + "crx2_file.h", + "crx_verifier.cc", + "crx_verifier.h", "id_util.cc", "id_util.h", ] @@ -14,17 +18,44 @@ static_library("crx_file") { "//base", "//crypto", ] + + public_deps = [ + ":crx3_proto", + ] +} + +bundle_data("unit_tests_bundle_data") { + visibility = [ ":unit_tests" ] + testonly = true + sources = [ + "//components/test/data/crx_file/unsigned.crx3", + "//components/test/data/crx_file/valid.crx2", + "//components/test/data/crx_file/valid_no_publisher.crx3", + "//components/test/data/crx_file/valid_publisher.crx3", + ] + outputs = [ + "{{bundle_resources_dir}}/" + + "{{source_root_relative_dir}}/{{source_file_part}}", + ] } source_set("unit_tests") { testonly = true sources = [ + "crx_verifier_unittest.cc", "id_util_unittest.cc", ] deps = [ ":crx_file", + ":unit_tests_bundle_data", "//base", "//testing/gtest", ] } + +proto_library("crx3_proto") { + sources = [ + "crx3.proto", + ] +} diff --git a/chromium/components/crx_file/OWNERS b/chromium/components/crx_file/OWNERS index 5a014d15275..8c5a6dbd3ba 100644 --- a/chromium/components/crx_file/OWNERS +++ b/chromium/components/crx_file/OWNERS @@ -1,2 +1,2 @@ -asargent@chromium.org -rockot@chromium.org +file://extensions/OWNERS + diff --git a/chromium/components/crx_file/crx2_file.cc b/chromium/components/crx_file/crx2_file.cc new file mode 100644 index 00000000000..2c37bfe7979 --- /dev/null +++ b/chromium/components/crx_file/crx2_file.cc @@ -0,0 +1,79 @@ +// 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/crx2_file.h" + +#include "base/base64.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/numerics/safe_math.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "components/crx_file/id_util.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "crypto/signature_verifier.h" + +namespace crx_file { + +namespace { + +// The current version of the crx format. +static const uint32_t kCurrentVersion = 2; + +// The current version of the crx diff format. +static const uint32_t kCurrentDiffVersion = 0; + +// The maximum size the crx parser will tolerate for a public key. +static const uint32_t kMaxPublicKeySize = 1 << 16; + +// The maximum size the crx parser will tolerate for a signature. +static const uint32_t kMaxSignatureSize = 1 << 16; + +} // namespace + +std::unique_ptr<Crx2File> Crx2File::Create(const uint32_t key_size, + const uint32_t signature_size, + Crx2File::Error* error) { + Crx2File::Header header; + memcpy(&header.magic, kCrx2FileHeaderMagic, kCrx2FileHeaderMagicSize); + header.version = kCurrentVersion; + header.key_size = key_size; + header.signature_size = signature_size; + if (HeaderIsValid(header, error)) + return base::WrapUnique(new Crx2File(header)); + return nullptr; +} + +Crx2File::Crx2File(const Header& header) : header_(header) {} + +bool Crx2File::HeaderIsValid(const Crx2File::Header& header, + Crx2File::Error* error) { + bool valid = false; + bool diffCrx = false; + if (!strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic))) + diffCrx = true; + if (strncmp(kCrx2FileHeaderMagic, header.magic, sizeof(header.magic)) && + !diffCrx) + *error = kWrongMagic; + else if (header.version != kCurrentVersion && + !(diffCrx && header.version == kCurrentDiffVersion)) + *error = kInvalidVersion; + else if (header.key_size > kMaxPublicKeySize) + *error = kInvalidKeyTooLarge; + else if (header.key_size == 0) + *error = kInvalidKeyTooSmall; + else if (header.signature_size > kMaxSignatureSize) + *error = kInvalidSignatureTooLarge; + else if (header.signature_size == 0) + *error = kInvalidSignatureTooSmall; + else + valid = true; + return valid; +} + +} // namespace crx_file diff --git a/chromium/components/crx_file/crx2_file.h b/chromium/components/crx_file/crx2_file.h new file mode 100644 index 00000000000..6fe1584116f --- /dev/null +++ b/chromium/components/crx_file/crx2_file.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef COMPONENTS_CRX_FILE_CRX2_FILE_H_ +#define COMPONENTS_CRX_FILE_CRX2_FILE_H_ + +#include <stddef.h> +#include <stdint.h> +#include <sys/types.h> + +#include <memory> +#include <string> +#include <vector> + +namespace crx_file { + +// The magic string embedded in the header. +constexpr char kCrx2FileHeaderMagic[] = "Cr24"; +constexpr char kCrxDiffFileHeaderMagic[] = "CrOD"; +constexpr int kCrx2FileHeaderMagicSize = 4; + +// CRX files have a header that includes a magic key, version number, and +// some signature sizing information. Use Crx2File object to validate whether +// the header is valid or not. +class Crx2File { + public: + // This header is the first data at the beginning of an extension. Its + // contents are purposely 32-bit aligned so that it can just be slurped into + // a struct without manual parsing. + struct Header { + char magic[kCrx2FileHeaderMagicSize]; + uint32_t version; + uint32_t key_size; // The size of the public key, in bytes. + uint32_t signature_size; // The size of the signature, in bytes. + // An ASN.1-encoded PublicKeyInfo structure follows. + // The signature follows. + }; + + enum Error { + kWrongMagic, + kInvalidVersion, + kInvalidKeyTooLarge, + kInvalidKeyTooSmall, + kInvalidSignatureTooLarge, + kInvalidSignatureTooSmall, + kMaxValue, + }; + + // Construct a new header for the given key and signature sizes. + // Returns null if erroneous values of |key_size| and/or + // |signature_size| are provided. |error| contains an error code with + // additional information. + // Use this constructor and then .header() to obtain the Header + // for writing out to a CRX file. + static std::unique_ptr<Crx2File> Create(const uint32_t key_size, + const uint32_t signature_size, + Error* error); + + // Returns the header structure for writing out to a CRX file. + const Header& header() const { return header_; } + + private: + Header header_; + + // Constructor is private. Clients should use static factory methods above. + explicit Crx2File(const Header& header); + + // Checks the |header| for validity and returns true if the values are valid. + // If false is returned, more detailed error code is returned in |error|. + static bool HeaderIsValid(const Header& header, Error* error); +}; + +} // namespace crx_file + +#endif // COMPONENTS_CRX_FILE_CRX2_FILE_H_ diff --git a/chromium/components/crx_file/crx3.proto b/chromium/components/crx_file/crx3.proto new file mode 100644 index 00000000000..2ea91f1c937 --- /dev/null +++ b/chromium/components/crx_file/crx3.proto @@ -0,0 +1,55 @@ +// 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 + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package crx_file; + +// A CRX₃ file is a binary file of the following format: +// [4 octets]: "Cr24", a magic number. +// [4 octets]: The version of the *.crx file format used (currently 3). +// [4 octets]: N, little-endian, the length of the header section. +// [N octets]: The header (the binary encoding of a CrxFileHeader). +// [M octets]: The ZIP archive. +// Clients should reject CRX₃ files that contain an N that is too large for the +// client to safely handle in memory. + +message CrxFileHeader { + // PSS signature with RSA public key. The public key is formatted as a + // X.509 SubjectPublicKeyInfo block, as in CRX₂. In the common case of a + // developer key proof, the first 128 bits of the SHA-256 hash of the + // public key must equal the crx_id. + repeated AsymmetricKeyProof sha256_with_rsa = 2; + + // ECDSA signature, using the NIST P-256 curve. Public key appears in + // named-curve format. + // The pinned algorithm will be this, at least on 2017-01-01. + repeated AsymmetricKeyProof sha256_with_ecdsa = 3; + + // The binary form of a SignedData message. We do not use a nested + // SignedData message, as handlers of this message must verify the proofs + // on exactly these bytes, so it is convenient to parse in two steps. + // + // All proofs in this CrxFile message are on the value + // "CRX3 SignedData\x00" + signed_header_size + signed_header_data + + // archive, where "\x00" indicates an octet with value 0, "CRX3 SignedData" + // is encoded using UTF-8, signed_header_size is the size in octets of the + // contents of this field and is encoded using 4 octets in little-endian + // order, signed_header_data is exactly the content of this field, and + // archive is the remaining contents of the file following the header. + optional bytes signed_header_data = 10000; +} + +message AsymmetricKeyProof { + optional bytes public_key = 1; + optional bytes signature = 2; +} + +message SignedData { + // This is simple binary, not UTF-8 encoded mpdecimal; i.e. it is exactly + // 16 bytes long. + optional bytes crx_id = 1; +} diff --git a/chromium/components/crx_file/crx_file.cc b/chromium/components/crx_file/crx_file.cc deleted file mode 100644 index c2b05f8e80b..00000000000 --- a/chromium/components/crx_file/crx_file.cc +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2014 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_file.h" - -#include "base/base64.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/files/scoped_file.h" -#include "base/macros.h" -#include "base/memory/ptr_util.h" -#include "base/numerics/safe_math.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_util.h" -#include "components/crx_file/id_util.h" -#include "crypto/secure_hash.h" -#include "crypto/sha2.h" -#include "crypto/signature_verifier.h" - -namespace crx_file { - -namespace { - -// The current version of the crx format. -static const uint32_t kCurrentVersion = 2; - -// The current version of the crx diff format. -static const uint32_t kCurrentDiffVersion = 0; - -// The maximum size the crx parser will tolerate for a public key. -static const uint32_t kMaxPublicKeySize = 1 << 16; - -// The maximum size the crx parser will tolerate for a signature. -static const uint32_t kMaxSignatureSize = 1 << 16; - -// Helper function to read bytes into a buffer while also updating a hash with -// those bytes. Returns the number of bytes read. -size_t ReadAndHash(void* ptr, - size_t size, - size_t nmemb, - FILE* stream, - crypto::SecureHash* hash) { - size_t item_count = fread(ptr, size, nmemb, stream); - base::CheckedNumeric<size_t> byte_count(item_count); - byte_count *= size; - if (!byte_count.IsValid()) - return 0; - if (item_count > 0 && hash) { - hash->Update(ptr, byte_count.ValueOrDie()); - } - return byte_count.ValueOrDie(); -} - -// Helper function to finish computing a hash and return an error if the -// result of the hash didn't meet an expected base64-encoded value. -CrxFile::ValidateError FinalizeHash(const std::string& extension_id, - crypto::SecureHash* hash, - const std::string& expected_hash) { - CHECK(hash != nullptr); - uint8_t output[crypto::kSHA256Length] = {}; - hash->Finish(output, sizeof(output)); - std::string hash_base64 = - base::ToLowerASCII(base::HexEncode(output, sizeof(output))); - if (hash_base64 != expected_hash) { - LOG(ERROR) << "Hash check failed for extension: " << extension_id - << ", expected " << expected_hash << ", got " << hash_base64; - return CrxFile::ValidateError::CRX_HASH_VERIFICATION_FAILED; - } else { - return CrxFile::ValidateError::NONE; - } -} - -} // namespace - -// The magic string embedded in the header. -const char kCrxFileHeaderMagic[] = "Cr24"; -const char kCrxDiffFileHeaderMagic[] = "CrOD"; - -std::unique_ptr<CrxFile> CrxFile::Parse(const CrxFile::Header& header, - CrxFile::Error* error) { - if (HeaderIsValid(header, error)) - return base::WrapUnique(new CrxFile(header)); - return nullptr; -} - -std::unique_ptr<CrxFile> CrxFile::Create(const uint32_t key_size, - const uint32_t signature_size, - CrxFile::Error* error) { - CrxFile::Header header; - memcpy(&header.magic, kCrxFileHeaderMagic, kCrxFileHeaderMagicSize); - header.version = kCurrentVersion; - header.key_size = key_size; - header.signature_size = signature_size; - if (HeaderIsValid(header, error)) - return base::WrapUnique(new CrxFile(header)); - return nullptr; -} - -bool CrxFile::HeaderIsDelta(const CrxFile::Header& header) { - return !strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic)); -} - -// static -CrxFile::ValidateError CrxFile::ValidateSignature( - const base::FilePath& crx_path, - const std::string& expected_hash, - std::string* public_key, - std::string* extension_id, - CrxFile::Header* header_out) { - base::ScopedFILE file(base::OpenFile(crx_path, "rb")); - std::unique_ptr<crypto::SecureHash> hash; - if (!expected_hash.empty()) - hash = crypto::SecureHash::Create(crypto::SecureHash::SHA256); - - if (!file.get()) - return ValidateError::CRX_FILE_NOT_READABLE; - - CrxFile::Header header; - size_t len = ReadAndHash(&header, sizeof(header), 1, file.get(), hash.get()); - if (len != sizeof(header)) - return ValidateError::CRX_HEADER_INVALID; - if (header_out) - *header_out = header; - - CrxFile::Error error; - std::unique_ptr<CrxFile> crx(CrxFile::Parse(header, &error)); - if (!crx) { - switch (error) { - case CrxFile::kWrongMagic: - return ValidateError::CRX_MAGIC_NUMBER_INVALID; - case CrxFile::kInvalidVersion: - return ValidateError::CRX_VERSION_NUMBER_INVALID; - - case CrxFile::kInvalidKeyTooLarge: - case CrxFile::kInvalidSignatureTooLarge: - return ValidateError::CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE; - - case CrxFile::kInvalidKeyTooSmall: - return ValidateError::CRX_ZERO_KEY_LENGTH; - case CrxFile::kInvalidSignatureTooSmall: - return ValidateError::CRX_ZERO_SIGNATURE_LENGTH; - - default: - return ValidateError::CRX_HEADER_INVALID; - } - } - - std::vector<uint8_t> key(header.key_size); - len = ReadAndHash(&key.front(), sizeof(uint8_t), header.key_size, file.get(), - hash.get()); - if (len != header.key_size) - return ValidateError::CRX_PUBLIC_KEY_INVALID; - - std::vector<uint8_t> signature(header.signature_size); - len = ReadAndHash(&signature.front(), sizeof(uint8_t), header.signature_size, - file.get(), hash.get()); - if (len < header.signature_size) - return ValidateError::CRX_SIGNATURE_INVALID; - - crypto::SignatureVerifier verifier; - if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, - signature.data(), signature.size(), key.data(), - key.size())) { - // Signature verification initialization failed. This is most likely - // caused by a public key in the wrong format (should encode algorithm). - return ValidateError::CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED; - } - - uint8_t buf[1 << 12] = {}; - while ((len = ReadAndHash(buf, sizeof(buf[0]), arraysize(buf), file.get(), - hash.get())) > 0) - verifier.VerifyUpdate(buf, len); - - if (!verifier.VerifyFinal()) - return ValidateError::CRX_SIGNATURE_VERIFICATION_FAILED; - - std::string public_key_bytes = - std::string(reinterpret_cast<char*>(&key.front()), key.size()); - if (public_key) - base::Base64Encode(public_key_bytes, public_key); - - std::string id = id_util::GenerateId(public_key_bytes); - if (extension_id) - *extension_id = id; - - if (!expected_hash.empty()) - return FinalizeHash(id, hash.get(), expected_hash); - - return ValidateError::NONE; -} - -CrxFile::CrxFile(const Header& header) : header_(header) {} - -bool CrxFile::HeaderIsValid(const CrxFile::Header& header, - CrxFile::Error* error) { - bool valid = false; - bool diffCrx = false; - if (!strncmp(kCrxDiffFileHeaderMagic, header.magic, sizeof(header.magic))) - diffCrx = true; - if (strncmp(kCrxFileHeaderMagic, header.magic, sizeof(header.magic)) && - !diffCrx) - *error = kWrongMagic; - else if (header.version != kCurrentVersion - && !(diffCrx && header.version == kCurrentDiffVersion)) - *error = kInvalidVersion; - else if (header.key_size > kMaxPublicKeySize) - *error = kInvalidKeyTooLarge; - else if (header.key_size == 0) - *error = kInvalidKeyTooSmall; - else if (header.signature_size > kMaxSignatureSize) - *error = kInvalidSignatureTooLarge; - else if (header.signature_size == 0) - *error = kInvalidSignatureTooSmall; - else - valid = true; - return valid; -} - -} // namespace crx_file diff --git a/chromium/components/crx_file/crx_file.h b/chromium/components/crx_file/crx_file.h deleted file mode 100644 index cf9bce8a81c..00000000000 --- a/chromium/components/crx_file/crx_file.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2014 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. - -#ifndef COMPONENTS_CRX_FILE_CRX_FILE_H_ -#define COMPONENTS_CRX_FILE_CRX_FILE_H_ - -#include <stddef.h> -#include <stdint.h> -#include <sys/types.h> - -#include <memory> -#include <string> -#include <vector> - -namespace base { -class FilePath; -} - -namespace crx_file { - -// CRX files have a header that includes a magic key, version number, and -// some signature sizing information. Use CrxFile object to validate whether -// the header is valid or not. -class CrxFile { - public: - // The size of the magic character sequence at the beginning of each crx - // file, in bytes. This should be a multiple of 4. - static const size_t kCrxFileHeaderMagicSize = 4; - - // This header is the first data at the beginning of an extension. Its - // contents are purposely 32-bit aligned so that it can just be slurped into - // a struct without manual parsing. - struct Header { - char magic[kCrxFileHeaderMagicSize]; - uint32_t version; - uint32_t key_size; // The size of the public key, in bytes. - uint32_t signature_size; // The size of the signature, in bytes. - // An ASN.1-encoded PublicKeyInfo structure follows. - // The signature follows. - }; - - enum Error { - kWrongMagic, - kInvalidVersion, - kInvalidKeyTooLarge, - kInvalidKeyTooSmall, - kInvalidSignatureTooLarge, - kInvalidSignatureTooSmall, - }; - - // Construct a new CRX file header object with bytes of a header - // read from a CRX file. If a null scoped_ptr is returned, |error| - // contains an error code with additional information. - static std::unique_ptr<CrxFile> Parse(const Header& header, Error* error); - - // Construct a new header for the given key and signature sizes. - // Returns a null scoped_ptr if erroneous values of |key_size| and/or - // |signature_size| are provided. |error| contains an error code with - // additional information. - // Use this constructor and then .header() to obtain the Header - // for writing out to a CRX file. - static std::unique_ptr<CrxFile> Create(const uint32_t key_size, - const uint32_t signature_size, - Error* error); - - // Returns the header structure for writing out to a CRX file. - const Header& header() const { return header_; } - - // Checks a valid |header| to determine whether or not the CRX represents a - // differential CRX. - static bool HeaderIsDelta(const Header& header); - - enum class ValidateError { - NONE, - CRX_FILE_NOT_READABLE, - CRX_HEADER_INVALID, - CRX_MAGIC_NUMBER_INVALID, - CRX_VERSION_NUMBER_INVALID, - CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE, - CRX_ZERO_KEY_LENGTH, - CRX_ZERO_SIGNATURE_LENGTH, - CRX_PUBLIC_KEY_INVALID, - CRX_SIGNATURE_INVALID, - CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED, - CRX_SIGNATURE_VERIFICATION_FAILED, - CRX_HASH_VERIFICATION_FAILED, - }; - - // Validates that the .crx file at |crx_path| is properly signed. If - // |expected_hash| is non-empty, it should specify a hex-encoded SHA256 hash - // for the entire .crx file which will be checked as we read it, and any - // mismatch will cause a CRX_HASH_VERIFICATION_FAILED result. The - // |public_key| argument can be provided to receive a copy of the - // base64-encoded public key pem bytes extracted from the .crx. The - // |extension_id| argument can be provided to receive the extension id (which - // is computed as a hash of the public key provided in the .crx). The - // |header| argument can be provided to receive a copy of the crx header. - static ValidateError ValidateSignature(const base::FilePath& crx_path, - const std::string& expected_hash, - std::string* public_key, - std::string* extension_id, - CrxFile::Header* header); - - private: - Header header_; - - // Constructor is private. Clients should use static factory methods above. - explicit CrxFile(const Header& header); - - // Checks the |header| for validity and returns true if the values are valid. - // If false is returned, more detailed error code is returned in |error|. - static bool HeaderIsValid(const Header& header, Error* error); -}; - -} // namespace crx_file - -#endif // COMPONENTS_CRX_FILE_CRX_FILE_H_ diff --git a/chromium/components/crx_file/crx_verifier.cc b/chromium/components/crx_file/crx_verifier.cc new file mode 100644 index 00000000000..0a26b77d6f9 --- /dev/null +++ b/chromium/components/crx_file/crx_verifier.cc @@ -0,0 +1,308 @@ +// 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 <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" +#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; + +// 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, + 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]; +} + +// 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, + 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( + 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; + 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; + } + + if (!ReadHashAndVerifyArchive(file, 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; +} + +} // 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) { + 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; + + 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_local, &crx_id_local); + else if (version == 3) + 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) + 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; +} + +} // namespace crx_file diff --git a/chromium/components/crx_file/crx_verifier.h b/chromium/components/crx_file/crx_verifier.h new file mode 100644 index 00000000000..35cf1956a24 --- /dev/null +++ b/chromium/components/crx_file/crx_verifier.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef COMPONENTS_CRX_FILE_CRX_VERIFIER_H_ +#define COMPONENTS_CRX_FILE_CRX_VERIFIER_H_ + +#include <stdint.h> +#include <string> +#include <vector> + +namespace base { +class FilePath; +} // namespace base + +namespace crx_file { + +enum class VerifierFormat { + CRX2_OR_CRX3, // Accept Crx2 or Crx3. + CRX3, // Accept only Crx3. + CRX3_WITH_PUBLISHER_PROOF, // Accept only Crx3 with a publisher proof. +}; + +enum class VerifierResult { + OK_FULL, // The file verifies as a correct full CRX file. + OK_DELTA, // The file verifies as a correct differential CRX file. + ERROR_FILE_NOT_READABLE, // Cannot open the CRX file. + ERROR_HEADER_INVALID, // Failed to parse or understand CRX header. + ERROR_EXPECTED_HASH_INVALID, // Expected hash is not well-formed. + ERROR_FILE_HASH_FAILED, // The file's actual hash != the expected hash. + ERROR_SIGNATURE_INITIALIZATION_FAILED, // A signature or key is malformed. + ERROR_SIGNATURE_VERIFICATION_FAILED, // A signature doesn't match. + ERROR_REQUIRED_PROOF_MISSING, // RequireKeyProof was unsatisfied. +}; + +// Verify the file at |crx_path| as a valid Crx of |format|. The Crx must be +// well-formed, contain no invalid proofs, match the |required_file_hash| (if +// non-empty), and contain a proof with each of the |required_key_hashes|. +// If and only if this function returns OK_FULL or OK_DELTA, and only if +// |public_key| / |crx_id| are non-null, they will be updated to contain the +// public key (PEM format, without the header/footer) and crx id (encoded in +// base16 using the characters [a-p]). +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); + +} // namespace crx_file + +#endif // COMPONENTS_CRX_FILE_CRX_VERIFIER_H_ diff --git a/chromium/components/crx_file/crx_verifier_unittest.cc b/chromium/components/crx_file/crx_verifier_unittest.cc new file mode 100644 index 00000000000..d5bd5a88296 --- /dev/null +++ b/chromium/components/crx_file/crx_verifier_unittest.cc @@ -0,0 +1,196 @@ +// 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 "base/base_paths.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/strings/string_number_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +base::FilePath TestFile(const std::string& file) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + return path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("crx_file") + .AppendASCII(file); +} + +constexpr char kOjjHash[] = "ojjgnpkioondelmggbekfhllhdaimnho"; +constexpr char kOjjKey[] = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA230uN7vYDEhdDlb4/" + "+pg2pfL8p0FFzCF/O146NB3D5dPKuLbnNphn0OUzOrDzR/Z1XLVDlDyiA6xnb+qeRp7H8n7Wk/" + "/gvVDNArZyForlVqWdaHLhl4dyZoNJwPKsggf30p/" + "MxCbNfy2rzFujzn2nguOrJKzWvNt0BFqssrBpzOQl69blBezE2ZYGOnYW8mPgQV29ekIgOfJk2" + "GgXoJBQQRRsjoPmUY7GDuEKudEB/" + "CmWh3+" + "mCsHBHFWbqtGhSN4YCAw3DYQzwdTcIVaIA8f2Uo4AZ4INKkrEPRL8o9mZDYtO2YHIQg8pMSRMa" + "6AawBNYi9tZScnmgl5L1qE6z5oIwIDAQAB"; + +} // namespace + +namespace crx_file { + +using CrxVerifierTest = testing::Test; + +TEST_F(CrxVerifierTest, ValidFullCrx2) { + const std::vector<std::vector<uint8_t>> keys; + const std::vector<uint8_t> hash; + std::string public_key; + std::string crx_id; + + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(TestFile("valid.crx2"), VerifierFormat::CRX2_OR_CRX3, keys, + hash, &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); +} + +TEST_F(CrxVerifierTest, ValidFullCrx3) { + const std::vector<std::vector<uint8_t>> keys; + const std::vector<uint8_t> hash; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + + EXPECT_EQ(VerifierResult::OK_FULL, Verify(TestFile("valid_no_publisher.crx3"), + 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(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3, + keys, hash, &public_key, &crx_id)); + EXPECT_EQ(std::string(kOjjHash), crx_id); + EXPECT_EQ(std::string(kOjjKey), public_key); +} + +TEST_F(CrxVerifierTest, Crx3RejectsCrx2) { + const std::vector<std::vector<uint8_t>> keys; + const std::vector<uint8_t> hash; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + + EXPECT_EQ(VerifierResult::ERROR_HEADER_INVALID, + Verify(TestFile("valid.crx2"), VerifierFormat::CRX3, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} + +TEST_F(CrxVerifierTest, VerifiesFileHash) { + const std::vector<std::vector<uint8_t>> keys; + std::vector<uint8_t> hash; + EXPECT_TRUE(base::HexStringToBytes( + "d033c510f9e4ee081ccb60ea2bf530dc2e5cb0e71085b55503c8b13b74515fe4", + &hash)); + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + + EXPECT_EQ(VerifierResult::OK_FULL, Verify(TestFile("valid_no_publisher.crx3"), + 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(TestFile("valid_no_publisher.crx3"), 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(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3, + keys, hash, &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} + +TEST_F(CrxVerifierTest, ChecksRequiredKeyHashes) { + const std::vector<uint8_t> hash; + + std::vector<uint8_t> good_key; + EXPECT_TRUE(base::HexStringToBytes( + "e996dfa8eed34bc6614a57bb7308cd7e519bcc690841e1969f7cb173ef16800a", + &good_key)); + const std::vector<std::vector<uint8_t>> good_keys = {good_key}; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + EXPECT_EQ( + VerifierResult::OK_FULL, + Verify(TestFile("valid_no_publisher.crx3"), 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<uint8_t> bad_key; + EXPECT_TRUE(base::HexStringToBytes(std::string(64, '0'), &bad_key)); + const std::vector<std::vector<uint8_t>> bad_keys = {bad_key}; + public_key = "UNSET"; + crx_id = "UNSET"; + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(TestFile("valid_no_publisher.crx3"), VerifierFormat::CRX3, + bad_keys, hash, &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} + +TEST_F(CrxVerifierTest, ChecksPinnedKey) { + const std::vector<uint8_t> hash; + const std::vector<std::vector<uint8_t>> keys; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(TestFile("valid_publisher.crx3"), + 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"; + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(TestFile("valid_no_publisher.crx3"), + VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash, + &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} + +TEST_F(CrxVerifierTest, NullptrSafe) { + const std::vector<uint8_t> hash; + const std::vector<std::vector<uint8_t>> keys; + EXPECT_EQ(VerifierResult::OK_FULL, + Verify(TestFile("valid_publisher.crx3"), + VerifierFormat::CRX3_WITH_PUBLISHER_PROOF, keys, hash, + nullptr, nullptr)); +} + +TEST_F(CrxVerifierTest, RequiresDeveloperKey) { + const std::vector<uint8_t> hash; + const std::vector<std::vector<uint8_t>> keys; + std::string public_key = "UNSET"; + std::string crx_id = "UNSET"; + EXPECT_EQ(VerifierResult::ERROR_REQUIRED_PROOF_MISSING, + Verify(TestFile("unsigned.crx3"), VerifierFormat::CRX2_OR_CRX3, + keys, hash, &public_key, &crx_id)); + EXPECT_EQ("UNSET", crx_id); + EXPECT_EQ("UNSET", public_key); +} + +} // namespace crx_file |