Commit 5918d5fb authored by waffles's avatar waffles Committed by Commit bot

Refactor CRX verification in preparation to support CRX₃ files.

Clarified that the crx_file.* refers to CRX₂, moved verification out of the CRX₂ code to allow callers to specify CRX₃-permissive policies.

Also fixed a potential crash in extension_creator.cc.

BUG=720092

Review-Url: https://codereview.chromium.org/2874503002
Cr-Commit-Position: refs/heads/master@{#473780}
parent 2cbad678
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "chrome/browser/extensions/extension_creator_filter.h" #include "chrome/browser/extensions/extension_creator_filter.h"
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "components/crx_file/crx_file.h" #include "components/crx_file/crx2_file.h"
#include "components/crx_file/id_util.h" #include "components/crx_file/id_util.h"
#include "crypto/rsa_private_key.h" #include "crypto/rsa_private_key.h"
#include "crypto/signature_creator.h" #include "crypto/signature_creator.h"
...@@ -252,24 +252,28 @@ bool ExtensionCreator::WriteCRX(const base::FilePath& zip_path, ...@@ -252,24 +252,28 @@ bool ExtensionCreator::WriteCRX(const base::FilePath& zip_path,
std::vector<uint8_t> public_key; std::vector<uint8_t> public_key;
CHECK(private_key->ExportPublicKey(&public_key)); CHECK(private_key->ExportPublicKey(&public_key));
crx_file::CrxFile::Error error; crx_file::Crx2File::Error error = crx_file::Crx2File::kMaxValue;
std::unique_ptr<crx_file::CrxFile> crx( std::unique_ptr<crx_file::Crx2File> crx(
crx_file::CrxFile::Create(public_key.size(), signature.size(), &error)); crx_file::Crx2File::Create(public_key.size(), signature.size(), &error));
if (!crx) { if (!crx) {
LOG(ERROR) << "cannot create CrxFileHeader: " << error; LOG(ERROR) << "cannot create Crx2FileHeader: " << error;
return false;
} }
const crx_file::CrxFile::Header header = crx->header(); const crx_file::Crx2File::Header header = crx->header();
if (fwrite(&header, sizeof(header), 1, crx_handle.get()) != 1) { if (fwrite(&header, sizeof(header), 1, crx_handle.get()) != 1) {
PLOG(ERROR) << "fwrite failed to write header"; PLOG(ERROR) << "fwrite failed to write header";
return false;
} }
if (fwrite(&public_key.front(), sizeof(uint8_t), public_key.size(), if (fwrite(&public_key.front(), sizeof(uint8_t), public_key.size(),
crx_handle.get()) != public_key.size()) { crx_handle.get()) != public_key.size()) {
PLOG(ERROR) << "fwrite failed to write public_key.front"; PLOG(ERROR) << "fwrite failed to write public_key.front";
return false;
} }
if (fwrite(&signature.front(), sizeof(uint8_t), signature.size(), if (fwrite(&signature.front(), sizeof(uint8_t), signature.size(),
crx_handle.get()) != signature.size()) { crx_handle.get()) != signature.size()) {
PLOG(ERROR) << "fwrite failed to write signature.front"; PLOG(ERROR) << "fwrite failed to write signature.front";
return false;
} }
size_t buffer_size = 1 << 16; size_t buffer_size = 1 << 16;
...@@ -281,6 +285,7 @@ bool ExtensionCreator::WriteCRX(const base::FilePath& zip_path, ...@@ -281,6 +285,7 @@ bool ExtensionCreator::WriteCRX(const base::FilePath& zip_path,
if (fwrite(buffer.get(), sizeof(char), bytes_read, crx_handle.get()) != if (fwrite(buffer.get(), sizeof(char), bytes_read, crx_handle.get()) !=
bytes_read) { bytes_read) {
PLOG(ERROR) << "fwrite failed to write buffer"; PLOG(ERROR) << "fwrite failed to write buffer";
return false;
} }
} }
......
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
static_library("crx_file") { static_library("crx_file") {
sources = [ sources = [
"crx_file.cc", "crx2_file.cc",
"crx_file.h", "crx2_file.h",
"crx_verifier.cc",
"crx_verifier.h",
"id_util.cc", "id_util.cc",
"id_util.h", "id_util.h",
] ]
......
// 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
// Copyright 2014 The Chromium Authors. All rights reserved. // Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef COMPONENTS_CRX_FILE_CRX_FILE_H_ #ifndef COMPONENTS_CRX_FILE_CRX2_FILE_H_
#define COMPONENTS_CRX_FILE_CRX_FILE_H_ #define COMPONENTS_CRX_FILE_CRX2_FILE_H_
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
...@@ -13,26 +13,23 @@ ...@@ -13,26 +13,23 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace base {
class FilePath;
}
namespace crx_file { 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 // CRX files have a header that includes a magic key, version number, and
// some signature sizing information. Use CrxFile object to validate whether // some signature sizing information. Use Crx2File object to validate whether
// the header is valid or not. // the header is valid or not.
class CrxFile { class Crx2File {
public: 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 // 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 // contents are purposely 32-bit aligned so that it can just be slurped into
// a struct without manual parsing. // a struct without manual parsing.
struct Header { struct Header {
char magic[kCrxFileHeaderMagicSize]; char magic[kCrx2FileHeaderMagicSize];
uint32_t version; uint32_t version;
uint32_t key_size; // The size of the public key, in bytes. uint32_t key_size; // The size of the public key, in bytes.
uint32_t signature_size; // The size of the signature, in bytes. uint32_t signature_size; // The size of the signature, in bytes.
...@@ -47,66 +44,27 @@ class CrxFile { ...@@ -47,66 +44,27 @@ class CrxFile {
kInvalidKeyTooSmall, kInvalidKeyTooSmall,
kInvalidSignatureTooLarge, kInvalidSignatureTooLarge,
kInvalidSignatureTooSmall, kInvalidSignatureTooSmall,
kMaxValue,
}; };
// 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. // Construct a new header for the given key and signature sizes.
// Returns a null scoped_ptr if erroneous values of |key_size| and/or // Returns null if erroneous values of |key_size| and/or
// |signature_size| are provided. |error| contains an error code with // |signature_size| are provided. |error| contains an error code with
// additional information. // additional information.
// Use this constructor and then .header() to obtain the Header // Use this constructor and then .header() to obtain the Header
// for writing out to a CRX file. // for writing out to a CRX file.
static std::unique_ptr<CrxFile> Create(const uint32_t key_size, static std::unique_ptr<Crx2File> Create(const uint32_t key_size,
const uint32_t signature_size, const uint32_t signature_size,
Error* error); Error* error);
// Returns the header structure for writing out to a CRX file. // Returns the header structure for writing out to a CRX file.
const Header& header() const { return header_; } 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: private:
Header header_; Header header_;
// Constructor is private. Clients should use static factory methods above. // Constructor is private. Clients should use static factory methods above.
explicit CrxFile(const Header& header); explicit Crx2File(const Header& header);
// Checks the |header| for validity and returns true if the values are valid. // 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|. // If false is returned, more detailed error code is returned in |error|.
...@@ -115,4 +73,4 @@ class CrxFile { ...@@ -115,4 +73,4 @@ class CrxFile {
} // namespace crx_file } // namespace crx_file
#endif // COMPONENTS_CRX_FILE_CRX_FILE_H_ #endif // COMPONENTS_CRX_FILE_CRX2_FILE_H_
// 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
// 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 <memory>
#include "base/base64.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "components/crx_file/crx2_file.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;
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];
}
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;
}
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;
crypto::SignatureVerifier verifier;
if (!verifier.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())
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);
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) {
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,
crx_id);
else if (version == 3)
result = VerifyCrx3(&file, file_hash.get(), required_key_hashes, public_key,
crx_id);
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;
}
return diff ? VerifierResult::OK_DELTA : VerifierResult::OK_FULL;
}
} // namespace crx_file
// 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_
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/base64.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
...@@ -21,17 +20,12 @@ ...@@ -21,17 +20,12 @@
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/values.h" #include "base/values.h"
#include "components/crx_file/crx_file.h" #include "components/crx_file/crx_verifier.h"
#include "components/update_client/component_patcher.h" #include "components/update_client/component_patcher.h"
#include "components/update_client/update_client.h" #include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h" #include "components/update_client/update_client_errors.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "third_party/zlib/google/zip.h" #include "third_party/zlib/google/zip.h"
using crypto::SecureHash;
using crx_file::CrxFile;
namespace update_client { namespace update_client {
// TODO(cpu): add a specific attribute check to a component json that the // TODO(cpu): add a specific attribute check to a component json that the
...@@ -89,33 +83,16 @@ bool ComponentUnpacker::Verify() { ...@@ -89,33 +83,16 @@ bool ComponentUnpacker::Verify() {
error_ = UnpackerError::kInvalidParams; error_ = UnpackerError::kInvalidParams;
return false; return false;
} }
// First, validate the CRX header and signature. As of today const std::vector<std::vector<uint8_t>> required_keys = {pk_hash_};
// this is SHA1 with RSA 1024. const crx_file::VerifierResult result =
std::string public_key_bytes; crx_file::Verify(path_, crx_file::VerifierFormat::CRX2_OR_CRX3,
std::string public_key_base64; required_keys, std::vector<uint8_t>(), nullptr, nullptr);
CrxFile::Header header; if (result != crx_file::VerifierResult::OK_FULL &&
CrxFile::ValidateError error = CrxFile::ValidateSignature( result != crx_file::VerifierResult::OK_DELTA) {
path_, std::string(), &public_key_base64, nullptr, &header);
if (error != CrxFile::ValidateError::NONE ||
!base::Base64Decode(public_key_base64, &public_key_bytes)) {
error_ = UnpackerError::kInvalidFile; error_ = UnpackerError::kInvalidFile;
return false; return false;
} }
is_delta_ = CrxFile::HeaderIsDelta(header); is_delta_ = result == crx_file::VerifierResult::OK_DELTA;
// File is valid and the digital signature matches. Now make sure
// the public key hash matches the expected hash. If they do we fully
// trust this CRX.
uint8_t hash[crypto::kSHA256Length] = {};
std::unique_ptr<SecureHash> sha256(SecureHash::Create(SecureHash::SHA256));
sha256->Update(public_key_bytes.data(), public_key_bytes.size());
sha256->Finish(hash, arraysize(hash));
if (!std::equal(pk_hash_.begin(), pk_hash_.end(), hash)) {
VLOG(1) << "Hash mismatch: " << path_.value();
error_ = UnpackerError::kInvalidId;
return false;
}
VLOG(1) << "Verification successful: " << path_.value(); VLOG(1) << "Verification successful: " << path_.value();
return true; return true;
} }
......
...@@ -161,7 +161,7 @@ TEST_F(ComponentUnpackerTest, UnpackFileHashMismatch) { ...@@ -161,7 +161,7 @@ TEST_F(ComponentUnpackerTest, UnpackFileHashMismatch) {
base::Unretained(this))); base::Unretained(this)));
RunThreads(); RunThreads();
EXPECT_EQ(UnpackerError::kInvalidId, result_.error); EXPECT_EQ(UnpackerError::kInvalidFile, result_.error);
EXPECT_EQ(0, result_.extended_error); EXPECT_EQ(0, result_.extended_error);
EXPECT_TRUE(result_.unpack_path.empty()); EXPECT_TRUE(result_.unpack_path.empty());
......
...@@ -51,7 +51,7 @@ enum class UnpackerError { ...@@ -51,7 +51,7 @@ enum class UnpackerError {
// kNoManifest = 5, // Deprecated. Never used. // kNoManifest = 5, // Deprecated. Never used.
kBadManifest = 6, kBadManifest = 6,
kBadExtension = 7, kBadExtension = 7,
kInvalidId = 8, // kInvalidId = 8, // Deprecated. Combined with kInvalidFile.
// kInstallerError = 9, // Deprecated. Don't use. // kInstallerError = 9, // Deprecated. Don't use.
kIoError = 10, kIoError = 10,
kDeltaVerificationFailure = 11, kDeltaVerificationFailure = 11,
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include <set> #include <set>
#include <tuple> #include <tuple>
#include <utility>
#include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
...@@ -17,10 +19,11 @@ ...@@ -17,10 +19,11 @@
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h" #include "base/threading/sequenced_worker_pool.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/crx_file/crx_file.h" #include "components/crx_file/crx_verifier.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "extensions/common/constants.h" #include "extensions/common/constants.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
...@@ -39,7 +42,6 @@ ...@@ -39,7 +42,6 @@
using base::ASCIIToUTF16; using base::ASCIIToUTF16;
using content::BrowserThread; using content::BrowserThread;
using crx_file::CrxFile;
// The following macro makes histograms that record the length of paths // The following macro makes histograms that record the length of paths
// in this file much easier to read. // in this file much easier to read.
...@@ -541,6 +543,10 @@ base::string16 SandboxedUnpacker::FailureReasonToString16( ...@@ -541,6 +543,10 @@ base::string16 SandboxedUnpacker::FailureReasonToString16(
return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED"); return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED");
case CRX_SIGNATURE_VERIFICATION_FAILED: case CRX_SIGNATURE_VERIFICATION_FAILED:
return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_FAILED"); return ASCIIToUTF16("CRX_SIGNATURE_VERIFICATION_FAILED");
case CRX_FILE_IS_DELTA_UPDATE:
return ASCIIToUTF16("CRX_FILE_IS_DELTA_UPDATE");
case CRX_EXPECTED_HASH_INVALID:
return ASCIIToUTF16("CRX_EXPECTED_HASH_INVALID");
case ERROR_SERIALIZING_MANIFEST_JSON: case ERROR_SERIALIZING_MANIFEST_JSON:
return ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON"); return ASCIIToUTF16("ERROR_SERIALIZING_MANIFEST_JSON");
...@@ -600,51 +606,47 @@ void SandboxedUnpacker::FailWithPackageError(FailureReason reason) { ...@@ -600,51 +606,47 @@ void SandboxedUnpacker::FailWithPackageError(FailureReason reason) {
bool SandboxedUnpacker::ValidateSignature(const base::FilePath& crx_path, bool SandboxedUnpacker::ValidateSignature(const base::FilePath& crx_path,
const std::string& expected_hash) { const std::string& expected_hash) {
CrxFile::ValidateError error = CrxFile::ValidateSignature( std::vector<uint8_t> hash;
crx_path, expected_hash, &public_key_, &extension_id_, nullptr); if (!expected_hash.empty()) {
if (!base::HexStringToBytes(expected_hash, &hash)) {
FailWithPackageError(CRX_EXPECTED_HASH_INVALID);
return false;
}
}
const crx_file::VerifierResult result = crx_file::Verify(
crx_path, crx_file::VerifierFormat::CRX2_OR_CRX3,
std::vector<std::vector<uint8_t>>(), hash, &public_key_, &extension_id_);
switch (error) { switch (result) {
case CrxFile::ValidateError::NONE: { case crx_file::VerifierResult::OK_FULL: {
if (!expected_hash.empty()) if (!expected_hash.empty())
UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", true); UMA_HISTOGRAM_BOOLEAN("Extensions.SandboxUnpackHashCheck", true);
return true; return true;
} }
case crx_file::VerifierResult::OK_DELTA:
case CrxFile::ValidateError::CRX_FILE_NOT_READABLE: FailWithPackageError(CRX_FILE_IS_DELTA_UPDATE);
break;
case crx_file::VerifierResult::ERROR_FILE_NOT_READABLE:
FailWithPackageError(CRX_FILE_NOT_READABLE); FailWithPackageError(CRX_FILE_NOT_READABLE);
break; break;
case CrxFile::ValidateError::CRX_HEADER_INVALID: case crx_file::VerifierResult::ERROR_HEADER_INVALID:
FailWithPackageError(CRX_HEADER_INVALID); FailWithPackageError(CRX_HEADER_INVALID);
break; break;
case CrxFile::ValidateError::CRX_MAGIC_NUMBER_INVALID: case crx_file::VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED:
FailWithPackageError(CRX_MAGIC_NUMBER_INVALID);
break;
case CrxFile::ValidateError::CRX_VERSION_NUMBER_INVALID:
FailWithPackageError(CRX_VERSION_NUMBER_INVALID);
break;
case CrxFile::ValidateError::CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE:
FailWithPackageError(CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE);
break;
case CrxFile::ValidateError::CRX_ZERO_KEY_LENGTH:
FailWithPackageError(CRX_ZERO_KEY_LENGTH);
break;
case CrxFile::ValidateError::CRX_ZERO_SIGNATURE_LENGTH:
FailWithPackageError(CRX_ZERO_SIGNATURE_LENGTH);
break;
case CrxFile::ValidateError::CRX_PUBLIC_KEY_INVALID:
FailWithPackageError(CRX_PUBLIC_KEY_INVALID);
break;
case CrxFile::ValidateError::CRX_SIGNATURE_INVALID:
FailWithPackageError(CRX_SIGNATURE_INVALID);
break;
case CrxFile::ValidateError::
CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED:
FailWithPackageError(CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED); FailWithPackageError(CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED);
break; break;
case CrxFile::ValidateError::CRX_SIGNATURE_VERIFICATION_FAILED: case crx_file::VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED:
FailWithPackageError(CRX_SIGNATURE_VERIFICATION_FAILED); FailWithPackageError(CRX_SIGNATURE_VERIFICATION_FAILED);
break; break;
case CrxFile::ValidateError::CRX_HASH_VERIFICATION_FAILED: case crx_file::VerifierResult::ERROR_EXPECTED_HASH_INVALID:
FailWithPackageError(CRX_EXPECTED_HASH_INVALID);
break;
case crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING:
// We should never get this result, as we do not call
// verifier.RequireKeyProof.
NOTREACHED();
break;
case crx_file::VerifierResult::ERROR_FILE_HASH_FAILED:
// We should never get this result unless we had specifically asked for // We should never get this result unless we had specifically asked for
// verification of the crx file's hash. // verification of the crx file's hash.
CHECK(!expected_hash.empty()); CHECK(!expected_hash.empty());
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_ #ifndef EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
#define EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_ #define EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
#include <memory>
#include <string> #include <string>
#include "base/files/file_path.h" #include "base/files/file_path.h"
...@@ -174,6 +175,10 @@ class SandboxedUnpacker : public base::RefCountedThreadSafe<SandboxedUnpacker> { ...@@ -174,6 +175,10 @@ class SandboxedUnpacker : public base::RefCountedThreadSafe<SandboxedUnpacker> {
UNZIP_FAILED, UNZIP_FAILED,
DIRECTORY_MOVE_FAILED, DIRECTORY_MOVE_FAILED,
// SandboxedUnpacker::ValidateSignature()
CRX_FILE_IS_DELTA_UPDATE,
CRX_EXPECTED_HASH_INVALID,
NUM_FAILURE_REASONS NUM_FAILURE_REASONS
}; };
......
...@@ -168,7 +168,7 @@ TEST_F(SandboxedUnpackerTest, FromDirWithCatalogsSuccess) { ...@@ -168,7 +168,7 @@ TEST_F(SandboxedUnpackerTest, FromDirWithCatalogsSuccess) {
TEST_F(SandboxedUnpackerTest, FailHashCheck) { TEST_F(SandboxedUnpackerTest, FailHashCheck) {
base::CommandLine::ForCurrentProcess()->AppendSwitch( base::CommandLine::ForCurrentProcess()->AppendSwitch(
extensions::switches::kEnableCrxHashCheck); extensions::switches::kEnableCrxHashCheck);
SetupUnpacker("good_l10n.crx", "badhash"); SetupUnpacker("good_l10n.crx", std::string(64, '0'));
// Check that there is an error message. // Check that there is an error message.
EXPECT_NE(base::string16(), GetInstallError()); EXPECT_NE(base::string16(), GetInstallError());
} }
......
...@@ -12828,6 +12828,8 @@ uploading your change for review. These are checked by presubmit scripts. ...@@ -12828,6 +12828,8 @@ uploading your change for review. These are checked by presubmit scripts.
<int value="31" label="ERROR_SERIALIZING_CATALOG"/> <int value="31" label="ERROR_SERIALIZING_CATALOG"/>
<int value="32" label="ERROR_SAVING_CATALOG"/> <int value="32" label="ERROR_SAVING_CATALOG"/>
<int value="33" label="CRX_HASH_VERIFICATION_FAILED"/> <int value="33" label="CRX_HASH_VERIFICATION_FAILED"/>
<int value="34" label="CRX_FILE_IS_DELTA_UPDATE"/>
<int value="35" label="CRX_EXPECTED_HASH_INVALID"/>
</enum> </enum>
<enum name="ExternalDeviceAction" type="int"> <enum name="ExternalDeviceAction" type="int">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment