// 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/scoped_ptr.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 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"; scoped_ptr CrxFile::Parse(const CrxFile::Header& header, CrxFile::Error* error) { if (HeaderIsValid(header, error)) return scoped_ptr(new CrxFile(header)); return scoped_ptr(); } scoped_ptr 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 scoped_ptr(new CrxFile(header)); return scoped_ptr(); } 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")); scoped_ptr hash; if (!expected_hash.empty()) hash.reset(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; scoped_ptr 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 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 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(), static_cast(signature.size()), key.data(), static_cast(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, static_cast(len)); if (!verifier.VerifyFinal()) return ValidateError::CRX_SIGNATURE_VERIFICATION_FAILED; std::string public_key_bytes = std::string(reinterpret_cast(&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