blob: a144b991b308051c1de6fedce010e7914150864f [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 "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.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/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/common/chrome_switches.h"
#include "crypto/random.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"
#include "url/gurl.h"
#if defined(ENABLE_RLZ)
#include "rlz/lib/machine_id.h"
#endif
namespace {
using extensions::ExtensionIdSet;
const char kExpireDateKey[] = "expire_date";
const char kIdsKey[] = "ids";
const char kSaltKey[] = "salt";
const char kSignatureKey[] = "signature";
const size_t kSaltBytes = 32;
// Returns a date 12 weeks from now in YYYY-MM-DD format.
std::string GetExpiryString() {
base::Time later = base::Time::Now() + base::TimeDelta::FromDays(7*12);
base::Time::Exploded exploded;
later.LocalExplode(&exploded);
return base::StringPrintf("%d-%02d-%02d",
exploded.year,
exploded.month,
exploded.day_of_month);
}
void FakeSignData(const ExtensionIdSet& ids,
const std::string& hash_base64,
const std::string& expire_date,
std::string* signature,
ExtensionIdSet* invalid_ids) {
DCHECK(invalid_ids && invalid_ids->empty());
DCHECK(IsStringASCII(hash_base64));
ExtensionIdSet invalid =
extensions::InstallSigner::GetForcedNotFromWebstore();
std::string data_to_sign;
for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); ++i) {
if (ContainsKey(invalid, (*i)))
invalid_ids->insert(*i);
else
data_to_sign.append(*i);
}
data_to_sign.append(hash_base64);
data_to_sign.append(expire_date);
*signature = std::string("FakeSignature:") +
crypto::SHA256HashString(data_to_sign);
}
void FakeSignDataWithCallback(
const ExtensionIdSet& ids,
const std::string& hash_base64,
const base::Callback<void(const std::string&,
const std::string&,
const ExtensionIdSet&)>& callback) {
std::string signature;
std::string expire_date = GetExpiryString();
ExtensionIdSet invalid_ids;
FakeSignData(ids, hash_base64, expire_date, &signature, &invalid_ids);
callback.Run(signature, expire_date, invalid_ids);
}
bool FakeCheckSignature(const extensions::ExtensionIdSet& ids,
const std::string& hash_base64,
const std::string& signature,
const std::string& expire_date) {
std::string computed_signature;
extensions::ExtensionIdSet invalid_ids;
FakeSignData(ids, hash_base64, expire_date, &computed_signature,
&invalid_ids);
return invalid_ids.empty() && computed_signature == signature;
}
// 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 defined(ENABLE_RLZ)
if (!rlz_lib::GetMachineId(&machine_id))
return false;
#endif
scoped_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(string_as_array(&result_bytes), result_bytes.size());
return base::Base64Encode(result_bytes, result);
}
} // namespace
namespace extensions {
InstallSignature::InstallSignature() {
}
InstallSignature::~InstallSignature() {
}
void InstallSignature::ToValue(base::DictionaryValue* value) const {
CHECK(value);
DCHECK(!signature.empty());
DCHECK(!salt.empty());
base::ListValue* id_list = new base::ListValue();
for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end();
++i)
id_list->AppendString(*i);
value->Set(kIdsKey, id_list);
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);
}
// static
scoped_ptr<InstallSignature> InstallSignature::FromValue(
const base::DictionaryValue& value) {
scoped_ptr<InstallSignature> result(new InstallSignature);
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.Pass();
}
const base::ListValue* ids = NULL;
if (!value.GetList(kIdsKey, &ids)) {
result.reset();
return result.Pass();
}
for (ListValue::const_iterator i = ids->begin(); i != ids->end(); ++i) {
std::string id;
if (!(*i)->GetAsString(&id)) {
result.reset();
return result.Pass();
}
result->ids.insert(id);
}
return result.Pass();
}
InstallSigner::InstallSigner(net::URLRequestContextGetter* context_getter,
const ExtensionIdSet& ids)
: ids_(ids), context_getter_(context_getter) {
}
InstallSigner::~InstallSigner() {
}
// static
bool InstallSigner::VerifySignature(const InstallSignature& signature) {
if (signature.ids.empty())
return true;
std::string hash_base64;
if (!HashWithMachineId(signature.salt, &hash_base64))
return false;
return FakeCheckSignature(signature.ids, hash_base64, signature.signature,
signature.expire_date);
}
class InstallSigner::FetcherDelegate : public net::URLFetcherDelegate {
public:
explicit FetcherDelegate(const base::Closure& callback)
: callback_(callback) {
}
virtual ~FetcherDelegate() {
}
virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
callback_.Run();
}
private:
base::Closure callback_;
DISALLOW_COPY_AND_ASSIGN(FetcherDelegate);
};
// static
ExtensionIdSet InstallSigner::GetForcedNotFromWebstore() {
std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kExtensionsNotWebstore);
if (value.empty())
return ExtensionIdSet();
std::vector<std::string> ids;
base::SplitString(value, ',', &ids);
return ExtensionIdSet(ids.begin(), ids.end());
}
void InstallSigner::GetSignature(const SignatureCallback& callback) {
CHECK(!url_fetcher_.get());
CHECK(callback_.is_null());
CHECK(salt_.empty());
callback_ = callback;
// If the set of ids is empty, just return an empty signature and skip the
// call to the server.
if (ids_.empty()) {
callback_.Run(scoped_ptr<InstallSignature>(new InstallSignature()));
return;
}
salt_ = std::string(kSaltBytes, 0);
DCHECK_EQ(kSaltBytes, salt_.size());
crypto::RandBytes(string_as_array(&salt_), salt_.size());
std::string hash_base64;
if (!HashWithMachineId(salt_, &hash_base64)) {
if (!callback_.is_null())
callback_.Run(scoped_ptr<InstallSignature>());
return;
}
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&FakeSignDataWithCallback,
ids_,
hash_base64,
base::Bind(&InstallSigner::HandleSignatureResult,
base::Unretained(this))));
}
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);
scoped_ptr<InstallSignature> result;
if (!signature.empty()) {
result.reset(new InstallSignature);
result->ids = valid_ids;
result->salt = salt_;
result->signature = signature;
result->expire_date = expire_date;
DCHECK(VerifySignature(*result));
}
if (!callback_.is_null())
callback_.Run(result.Pass());
}
} // namespace extensions