1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
|
// Copyright 2017 The Chromium Authors
// 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 <algorithm>
#include <climits>
#include <cstring>
#include <iterator>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include "base/base64.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/numerics/byte_conversions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "components/crx_file/crx3.pb.h"
#include "components/crx_file/crx_file.h"
#include "components/crx_file/id_util.h"
#include "crypto/hash.h"
#include "crypto/secure_util.h"
#include "crypto/signature_verifier.h"
namespace crx_file {
namespace {
using KeyHash = std::array<uint8_t, crypto::hash::kSha256Size>;
// The SHA256 hash of the DER SPKI "ecdsa_2017_public" Crx3 key.
constexpr KeyHash 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};
// The SHA256 hash of the DER SPKI "ecdsa_2017_public" Crx3 test key.
constexpr KeyHash kPublisherTestKeyHash = {
0x6c, 0x46, 0x41, 0x3b, 0x00, 0xd0, 0xfa, 0x0e, 0x72, 0xc8, 0xd2,
0x5f, 0x64, 0xf3, 0xa6, 0x17, 0x03, 0x0d, 0xde, 0x21, 0x61, 0xbe,
0xb7, 0x95, 0x91, 0x95, 0x83, 0x68, 0x12, 0xe9, 0x78, 0x1e};
constexpr auto kEocd = std::to_array<uint8_t>({'P', 'K', 0x05, 0x06});
constexpr auto kEocd64 = std::to_array<uint8_t>({'P', 'K', 0x06, 0x07});
using VerifierCollection =
std::vector<std::unique_ptr<crypto::SignatureVerifier>>;
using RepeatedProof = google::protobuf::RepeatedPtrField<AsymmetricKeyProof>;
std::optional<size_t> ReadAndHashBuffer(base::span<uint8_t> buffer,
base::File* file,
crypto::hash::Hasher& hash) {
auto read = file->ReadAtCurrentPos(buffer);
if (read.value_or(0) > 0) {
hash.Update(buffer.first(*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::hash::Hasher& hash) {
std::array<uint8_t, 4> buffer;
if (ReadAndHashBuffer(buffer, file, hash).value_or(4) != buffer.size()) {
return UINT32_MAX;
}
return base::I32FromLittleEndian(buffer);
}
// Read to the end of the file, updating the hash and all verifiers.
bool ReadHashAndVerifyArchive(base::File* file,
crypto::hash::Hasher& hash,
const VerifierCollection& verifiers) {
std::array<uint8_t, 1 << 12> buffer;
std::optional<size_t> len;
while ((len = ReadAndHashBuffer(buffer, file, hash)).value_or(0) > 0) {
auto to_verify = base::span<const uint8_t>(buffer).first(*len);
for (auto& verifier : verifiers) {
verifier->VerifyUpdate(to_verify);
}
}
for (auto& verifier : verifiers) {
if (!verifier->VerifyFinal()) {
return false;
}
}
// A final read with a length of 0 signals the end of the input file. A read
// with no length at all signals a read error and should be treated as a
// failure.
return len.has_value() && len.value() == 0;
}
// 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::hash::Hasher& hash,
const std::vector<std::vector<uint8_t>>& required_key_hashes,
std::string* public_key,
std::string* crx_id,
std::vector<uint8_t>* compressed_verified_contents,
bool require_publisher_key,
bool accept_publisher_test_key) {
// Parse [header-size] and [header].
int header_size =
base::saturated_cast<int>(ReadAndHashLittleEndianUInt32(file, hash));
if (header_size == INT_MAX) {
return VerifierResult::ERROR_HEADER_INVALID;
}
std::vector<uint8_t> header_bytes(header_size);
if (ReadAndHashBuffer(header_bytes, file, hash) != header_size) {
return VerifierResult::ERROR_HEADER_INVALID;
}
// If the header contains a ZIP EOCD or EOCD64 token, unzipping may not work
// correctly.
if (std::ranges::search(header_bytes, kEocd) ||
std::ranges::search(header_bytes, kEocd64)) {
return VerifierResult::ERROR_HEADER_INVALID;
}
CrxFileHeader header;
if (!header.ParseFromArray(header_bytes.data(), header_size)) {
return VerifierResult::ERROR_HEADER_INVALID;
}
// Parse [verified_contents].
if (header.has_verified_contents() && compressed_verified_contents) {
const std::string& header_verified_contents(header.verified_contents());
compressed_verified_contents->assign(header_verified_contents.begin(),
header_verified_contents.end());
}
// 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));
// Create a little-endian representation of [signed-header-size].
const auto header_size_octets =
base::I32ToLittleEndian(signed_header_data_str.size());
// Create a set of all required key hashes.
std::set<KeyHash> required_key_set;
for (const auto& key_hash : required_key_hashes) {
KeyHash hash_copy;
base::span<uint8_t>(hash_copy).copy_from(key_hash);
required_key_set.insert(hash_copy);
}
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)};
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;
}
auto key_hash = crypto::hash::Sha256(key);
required_key_set.erase(key_hash);
found_publisher_key =
found_publisher_key || key_hash == kPublisherKeyHash ||
(accept_publisher_test_key && key_hash == kPublisherTestKeyHash);
auto v = std::make_unique<crypto::SignatureVerifier>();
if (!v->VerifyInit(proof_type.second, base::as_byte_span(sig),
base::as_byte_span(key))) {
return VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED;
}
v->VerifyUpdate(base::as_byte_span(kSignatureContext));
v->VerifyUpdate(header_size_octets);
v->VerifyUpdate(base::as_byte_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 (!ReadHashAndVerifyArchive(file, hash, verifiers)) {
return VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED;
}
*public_key = base::Base64Encode(public_key_bytes);
*crx_id = declared_crx_id;
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::vector<uint8_t>* compressed_verified_contents) {
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;
}
crypto::hash::Hasher file_hash(crypto::hash::HashKind::kSha256);
// Magic number.
bool diff = false;
std::array<uint8_t, std::size(kCrxFileHeaderMagic)> buffer;
if (!file.ReadAtCurrentPosAndCheck(buffer)) {
return VerifierResult::ERROR_HEADER_INVALID;
}
if (std::ranges::equal(buffer, kCrxDiffFileHeaderMagic)) {
diff = true;
} else if (!std::ranges::equal(buffer, kCrxFileHeaderMagic)) {
return VerifierResult::ERROR_HEADER_INVALID;
}
file_hash.Update(buffer);
// Version number.
const uint32_t version = ReadAndHashLittleEndianUInt32(&file, file_hash);
VerifierResult result;
if (version == 3) {
bool require_publisher_key =
format == VerifierFormat::CRX3_WITH_PUBLISHER_PROOF ||
format == VerifierFormat::CRX3_WITH_TEST_PUBLISHER_PROOF;
result = VerifyCrx3(
&file, file_hash, required_key_hashes, &public_key_local, &crx_id_local,
compressed_verified_contents, 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.
std::array<uint8_t, crypto::hash::kSha256Size> final_hash;
file_hash.Finish(final_hash);
if (!required_file_hash.empty()) {
if (required_file_hash.size() != crypto::hash::kSha256Size) {
return VerifierResult::ERROR_EXPECTED_HASH_INVALID;
}
if (!crypto::SecureMemEqual(final_hash, required_file_hash)) {
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
|