blob: 2e165d23e42e4f9faab3a255b490ce81597fc72e [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/extensions/install_signer.h"
#include <stddef.h>
#include <stdint.h>
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/process.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/common/chrome_switches.h"
#include "crypto/random.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "crypto/signature_verifier.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "rlz/buildflags/buildflags.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_RLZ)
#include "rlz/lib/machine_id.h"
#endif
namespace {
using extensions::ExtensionIdSet;
const char kExpireDateKey[] = "expire_date";
const char kExpiryKey[] = "expiry";
const char kHashKey[] = "hash";
const char kIdsKey[] = "ids";
const char kInvalidIdsKey[] = "invalid_ids";
const char kProtocolVersionKey[] = "protocol_version";
const char kSaltKey[] = "salt";
const char kSignatureKey[] = "signature";
const char kSignatureFormatVersionKey[] = "signature_format_version";
const char kTimestampKey[] = "timestamp";
const char kContentTypeJSON[] = "application/json";
// This allows us to version the format of what we write into the prefs,
// allowing for forward migration, as well as detecting forwards/backwards
// incompatabilities, etc.
const int kSignatureFormatVersion = 2;
const size_t kSaltBytes = 32;
const char kBackendUrl[] =
"https://www.googleapis.com/chromewebstore/v1.1/items/verify";
const char kPublicKeyPEM[] = \
"-----BEGIN PUBLIC KEY-----" \
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj/u/XDdjlDyw7gHEtaaa" \
"sZ9GdG8WOKAyJzXd8HFrDtz2Jcuy7er7MtWvHgNDA0bwpznbI5YdZeV4UfCEsA4S" \
"rA5b3MnWTHwA1bgbiDM+L9rrqvcadcKuOlTeN48Q0ijmhHlNFbTzvT9W0zw/GKv8" \
"LgXAHggxtmHQ/Z9PP2QNF5O8rUHHSL4AJ6hNcEKSBVSmbbjeVm4gSXDuED5r0nwx" \
"vRtupDxGYp8IZpP5KlExqNu1nbkPc+igCTIB6XsqijagzxewUHCdovmkb2JNtskx" \
"/PMIEv+TvWIx2BzqGp71gSh/dV7SJ3rClvWd2xj8dtxG8FfAWDTIIi0qZXWn2Qhi" \
"zQIDAQAB" \
"-----END PUBLIC KEY-----";
GURL GetBackendUrl() {
return GURL(kBackendUrl);
}
// Hashes |salt| with the machine id, base64-encodes it and returns it in
// |result|.
bool HashWithMachineId(const std::string& salt, std::string* result) {
std::string machine_id;
#if BUILDFLAG(ENABLE_RLZ)
if (!rlz_lib::GetMachineId(&machine_id))
return false;
#else
machine_id = "unknown";
#endif
std::unique_ptr<crypto::SecureHash> hash(
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
hash->Update(machine_id.data(), machine_id.size());
hash->Update(salt.data(), salt.size());
std::string result_bytes(crypto::kSHA256Length, 0);
hash->Finish(base::data(result_bytes), result_bytes.size());
base::Base64Encode(result_bytes, result);
return true;
}
// Validates that |input| is a string of the form "YYYY-MM-DD".
bool ValidateExpireDateFormat(const std::string& input) {
if (input.length() != 10)
return false;
for (int i = 0; i < 10; i++) {
if (i == 4 || i == 7) {
if (input[i] != '-')
return false;
} else if (!base::IsAsciiDigit(input[i])) {
return false;
}
}
return true;
}
// Sets the value of |key| in |dictionary| to be a list with the contents of
// |ids|.
void SetExtensionIdSet(base::DictionaryValue* dictionary,
const char* key,
const ExtensionIdSet& ids) {
auto id_list = std::make_unique<base::ListValue>();
for (auto i = ids.begin(); i != ids.end(); ++i)
id_list->AppendString(*i);
dictionary->Set(key, std::move(id_list));
}
// Tries to fetch a list of strings from |dictionay| for |key|, and inserts
// them into |ids|. The return value indicates success/failure. Note: on
// failure, |ids| might contain partial results, for instance if some of the
// members of the list were not strings.
bool GetExtensionIdSet(const base::DictionaryValue& dictionary,
const char* key,
ExtensionIdSet* ids) {
const base::ListValue* id_list = NULL;
if (!dictionary.GetList(key, &id_list))
return false;
for (auto i = id_list->begin(); i != id_list->end(); ++i) {
std::string id;
if (!i->GetAsString(&id)) {
return false;
}
ids->insert(id);
}
return true;
}
} // namespace
namespace extensions {
InstallSignature::InstallSignature() {
}
InstallSignature::InstallSignature(const InstallSignature& other) = default;
InstallSignature::~InstallSignature() {
}
void InstallSignature::ToValue(base::DictionaryValue* value) const {
CHECK(value);
value->SetInteger(kSignatureFormatVersionKey, kSignatureFormatVersion);
SetExtensionIdSet(value, kIdsKey, ids);
SetExtensionIdSet(value, kInvalidIdsKey, invalid_ids);
value->SetString(kExpireDateKey, expire_date);
std::string salt_base64;
std::string signature_base64;
base::Base64Encode(salt, &salt_base64);
base::Base64Encode(signature, &signature_base64);
value->SetString(kSaltKey, salt_base64);
value->SetString(kSignatureKey, signature_base64);
value->SetString(kTimestampKey,
base::NumberToString(timestamp.ToInternalValue()));
}
// static
std::unique_ptr<InstallSignature> InstallSignature::FromValue(
const base::DictionaryValue& value) {
std::unique_ptr<InstallSignature> result(new InstallSignature);
// For now we don't want to support any backwards compability, but in the
// future if we do, we would want to put the migration code here.
int format_version = 0;
if (!value.GetInteger(kSignatureFormatVersionKey, &format_version) ||
format_version != kSignatureFormatVersion) {
result.reset();
return result;
}
std::string salt_base64;
std::string signature_base64;
if (!value.GetString(kExpireDateKey, &result->expire_date) ||
!value.GetString(kSaltKey, &salt_base64) ||
!value.GetString(kSignatureKey, &signature_base64) ||
!base::Base64Decode(salt_base64, &result->salt) ||
!base::Base64Decode(signature_base64, &result->signature)) {
result.reset();
return result;
}
// Note: earlier versions of the code did not write out a timestamp value
// so older entries will not necessarily have this.
if (value.HasKey(kTimestampKey)) {
std::string timestamp;
int64_t timestamp_value = 0;
if (!value.GetString(kTimestampKey, &timestamp) ||
!base::StringToInt64(timestamp, &timestamp_value)) {
result.reset();
return result;
}
result->timestamp = base::Time::FromInternalValue(timestamp_value);
}
if (!GetExtensionIdSet(value, kIdsKey, &result->ids) ||
!GetExtensionIdSet(value, kInvalidIdsKey, &result->invalid_ids)) {
result.reset();
return result;
}
return result;
}
InstallSigner::InstallSigner(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const ExtensionIdSet& ids)
: ids_(ids), url_loader_factory_(std::move(url_loader_factory)) {}
InstallSigner::~InstallSigner() {
}
// static
bool InstallSigner::VerifySignature(const InstallSignature& signature) {
if (signature.ids.empty())
return true;
std::string signed_data;
for (auto i = signature.ids.begin(); i != signature.ids.end(); ++i)
signed_data.append(*i);
std::string hash_base64;
if (!HashWithMachineId(signature.salt, &hash_base64))
return false;
signed_data.append(hash_base64);
signed_data.append(signature.expire_date);
std::string public_key;
if (!Extension::ParsePEMKeyBytes(kPublicKeyPEM, &public_key))
return false;
crypto::SignatureVerifier verifier;
if (!verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
base::as_bytes(base::make_span(signature.signature)),
base::as_bytes(base::make_span(public_key))))
return false;
verifier.VerifyUpdate(base::as_bytes(base::make_span(signed_data)));
return verifier.VerifyFinal();
}
// static
ExtensionIdSet InstallSigner::GetForcedNotFromWebstore() {
std::string value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
::switches::kExtensionsNotWebstore);
if (value.empty())
return ExtensionIdSet();
std::vector<std::string> ids = base::SplitString(
value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
return ExtensionIdSet(ids.begin(), ids.end());
}
namespace {
int g_request_count = 0;
base::LazyInstance<base::TimeTicks>::DestructorAtExit g_last_request_time =
LAZY_INSTANCE_INITIALIZER;
base::LazyInstance<base::ThreadChecker>::DestructorAtExit
g_single_thread_checker = LAZY_INSTANCE_INITIALIZER;
void LogRequestStartHistograms() {
// Make sure we only ever call this from one thread, so that we don't have to
// worry about race conditions setting g_last_request_time.
DCHECK(g_single_thread_checker.Get().CalledOnValidThread());
// Process::Current().CreationTime is only defined on some platforms.
#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
const base::Time process_creation_time =
base::Process::Current().CreationTime();
UMA_HISTOGRAM_COUNTS_1M(
"ExtensionInstallSigner.UptimeAtTimeOfRequest",
(base::Time::Now() - process_creation_time).InSeconds());
#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
base::TimeDelta delta;
base::TimeTicks now = base::TimeTicks::Now();
if (!g_last_request_time.Get().is_null())
delta = now - g_last_request_time.Get();
g_last_request_time.Get() = now;
UMA_HISTOGRAM_COUNTS_1M("ExtensionInstallSigner.SecondsSinceLastRequest",
delta.InSeconds());
g_request_count += 1;
UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.RequestCount",
g_request_count);
}
} // namespace
void InstallSigner::GetSignature(SignatureCallback callback) {
CHECK(!simple_loader_.get());
CHECK(callback_.is_null());
CHECK(salt_.empty());
callback_ = std::move(callback);
// If the set of ids is empty, just return an empty signature and skip the
// call to the server.
if (ids_.empty()) {
if (!callback_.is_null())
std::move(callback_).Run(std::make_unique<InstallSignature>());
return;
}
salt_ = std::string(kSaltBytes, 0);
crypto::RandBytes(base::data(salt_), salt_.size());
std::string hash_base64;
if (!HashWithMachineId(salt_, &hash_base64)) {
ReportErrorViaCallback();
return;
}
if (!url_loader_factory_) {
ReportErrorViaCallback();
return;
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("extension_install_signer", R"(
semantics {
sender: "Extension Install Signer"
description: "Fetches the signatures for installed extensions."
trigger:
"Chrome detects an extension that requires installation "
"verification."
data:
"The ids of the extensions that need to be verified, as well as a "
"non-revertable salted hash of the user's machine id provided by "
"RLZ library, which varies between different installs. This id is "
"only used to verify the validity of the response."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled, but it is only activated if "
"extensions are installed."
chrome_policy {
ExtensionInstallBlacklist {
policy_options {mode: MANDATORY}
ExtensionInstallBlacklist: {
entries: '*'
}
}
}
})");
// The request protocol is JSON of the form:
// {
// "protocol_version": "1",
// "hash": "<base64-encoded hash value here>",
// "ids": [ "<id1>", "id2" ]
// }
base::DictionaryValue dictionary;
dictionary.SetInteger(kProtocolVersionKey, 1);
dictionary.SetString(kHashKey, hash_base64);
std::unique_ptr<base::ListValue> id_list(new base::ListValue);
for (auto i = ids_.begin(); i != ids_.end(); ++i) {
id_list->AppendString(*i);
}
dictionary.Set(kIdsKey, std::move(id_list));
std::string json;
base::JSONWriter::Write(dictionary, &json);
if (json.empty()) {
ReportErrorViaCallback();
return;
}
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GetBackendUrl();
resource_request->method = "POST";
simple_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation);
simple_loader_->AttachStringForUpload(json, kContentTypeJSON);
LogRequestStartHistograms();
request_start_time_ = base::Time::Now();
VLOG(1) << "Sending request: " << json;
// TODO: Set a cap value to the expected content to be loaded, and use
// DownloadToString instead.
simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&InstallSigner::ParseFetchResponse,
base::Unretained(this)));
}
void InstallSigner::ReportErrorViaCallback() {
if (!callback_.is_null())
std::move(callback_).Run(nullptr);
}
void InstallSigner::ParseFetchResponse(
std::unique_ptr<std::string> response_body) {
UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.FetchSuccess", !!response_body);
UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.GetResponseSuccess",
!!response_body && !response_body->empty());
if (!response_body || response_body->empty()) {
ReportErrorViaCallback();
return;
}
VLOG(1) << "Got response: " << *response_body;
// The response is JSON of the form:
// {
// "protocol_version": "1",
// "signature": "<base64-encoded signature>",
// "expiry": "<date in YYYY-MM-DD form>",
// "invalid_ids": [ "<id3>", "<id4>" ]
// }
// where |invalid_ids| is a list of ids from the original request that
// could not be verified to be in the webstore.
base::DictionaryValue* dictionary = NULL;
std::unique_ptr<base::Value> parsed =
base::JSONReader::ReadDeprecated(*response_body);
bool json_success = parsed.get() && parsed->GetAsDictionary(&dictionary);
UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ParseJsonSuccess",
json_success);
if (!json_success) {
ReportErrorViaCallback();
return;
}
int protocol_version = 0;
std::string signature_base64;
std::string signature;
std::string expire_date;
dictionary->GetInteger(kProtocolVersionKey, &protocol_version);
dictionary->GetString(kSignatureKey, &signature_base64);
dictionary->GetString(kExpiryKey, &expire_date);
bool fields_success =
protocol_version == 1 && !signature_base64.empty() &&
ValidateExpireDateFormat(expire_date) &&
base::Base64Decode(signature_base64, &signature);
UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ParseFieldsSuccess",
fields_success);
if (!fields_success) {
ReportErrorViaCallback();
return;
}
ExtensionIdSet invalid_ids;
const base::ListValue* invalid_ids_list = NULL;
if (dictionary->GetList(kInvalidIdsKey, &invalid_ids_list)) {
for (size_t i = 0; i < invalid_ids_list->GetSize(); i++) {
std::string id;
if (!invalid_ids_list->GetString(i, &id)) {
ReportErrorViaCallback();
return;
}
invalid_ids.insert(id);
}
}
HandleSignatureResult(signature, expire_date, invalid_ids);
}
void InstallSigner::HandleSignatureResult(const std::string& signature,
const std::string& expire_date,
const ExtensionIdSet& invalid_ids) {
ExtensionIdSet valid_ids =
base::STLSetDifference<ExtensionIdSet>(ids_, invalid_ids);
std::unique_ptr<InstallSignature> result;
if (!signature.empty()) {
result.reset(new InstallSignature);
result->ids = valid_ids;
result->invalid_ids = invalid_ids;
result->salt = salt_;
result->signature = signature;
result->expire_date = expire_date;
result->timestamp = request_start_time_;
bool verified = VerifySignature(*result);
UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ResultWasValid", verified);
UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.InvalidCount",
invalid_ids.size());
if (!verified)
result.reset();
}
if (!callback_.is_null())
std::move(callback_).Run(std::move(result));
}
} // namespace extensions