1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/install_signer.h" 6 7#include "base/base64.h" 8#include "base/bind.h" 9#include "base/command_line.h" 10#include "base/json/json_reader.h" 11#include "base/json/json_writer.h" 12#include "base/lazy_instance.h" 13#include "base/message_loop/message_loop.h" 14#include "base/metrics/histogram.h" 15#include "base/process/process_info.h" 16#include "base/stl_util.h" 17#include "base/strings/string_number_conversions.h" 18#include "base/strings/string_split.h" 19#include "base/strings/string_util.h" 20#include "base/strings/stringprintf.h" 21#include "base/time/time.h" 22#include "base/values.h" 23#include "chrome/common/chrome_switches.h" 24#include "crypto/random.h" 25#include "crypto/secure_hash.h" 26#include "crypto/sha2.h" 27#include "crypto/signature_verifier.h" 28#include "extensions/common/constants.h" 29#include "net/url_request/url_fetcher.h" 30#include "net/url_request/url_fetcher_delegate.h" 31#include "net/url_request/url_request_context_getter.h" 32#include "net/url_request/url_request_status.h" 33#include "url/gurl.h" 34 35#if defined(ENABLE_RLZ) 36#include "rlz/lib/machine_id.h" 37#endif 38 39namespace { 40 41using extensions::ExtensionIdSet; 42 43const char kExpireDateKey[] = "expire_date"; 44const char kExpiryKey[] = "expiry"; 45const char kHashKey[] = "hash"; 46const char kIdsKey[] = "ids"; 47const char kInvalidIdsKey[] = "invalid_ids"; 48const char kProtocolVersionKey[] = "protocol_version"; 49const char kSaltKey[] = "salt"; 50const char kSignatureKey[] = "signature"; 51const char kSignatureFormatVersionKey[] = "signature_format_version"; 52const char kTimestampKey[] = "timestamp"; 53 54// This allows us to version the format of what we write into the prefs, 55// allowing for forward migration, as well as detecting forwards/backwards 56// incompatabilities, etc. 57const int kSignatureFormatVersion = 2; 58 59const size_t kSaltBytes = 32; 60 61const char kBackendUrl[] = 62 "https://www.googleapis.com/chromewebstore/v1.1/items/verify"; 63 64const char kPublicKeyPEM[] = \ 65 "-----BEGIN PUBLIC KEY-----" \ 66 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj/u/XDdjlDyw7gHEtaaa" \ 67 "sZ9GdG8WOKAyJzXd8HFrDtz2Jcuy7er7MtWvHgNDA0bwpznbI5YdZeV4UfCEsA4S" \ 68 "rA5b3MnWTHwA1bgbiDM+L9rrqvcadcKuOlTeN48Q0ijmhHlNFbTzvT9W0zw/GKv8" \ 69 "LgXAHggxtmHQ/Z9PP2QNF5O8rUHHSL4AJ6hNcEKSBVSmbbjeVm4gSXDuED5r0nwx" \ 70 "vRtupDxGYp8IZpP5KlExqNu1nbkPc+igCTIB6XsqijagzxewUHCdovmkb2JNtskx" \ 71 "/PMIEv+TvWIx2BzqGp71gSh/dV7SJ3rClvWd2xj8dtxG8FfAWDTIIi0qZXWn2Qhi" \ 72 "zQIDAQAB" \ 73 "-----END PUBLIC KEY-----"; 74 75GURL GetBackendUrl() { 76 return GURL(kBackendUrl); 77} 78 79// Hashes |salt| with the machine id, base64-encodes it and returns it in 80// |result|. 81bool HashWithMachineId(const std::string& salt, std::string* result) { 82 std::string machine_id; 83#if defined(ENABLE_RLZ) 84 if (!rlz_lib::GetMachineId(&machine_id)) 85 return false; 86#else 87 machine_id = "unknown"; 88#endif 89 90 scoped_ptr<crypto::SecureHash> hash( 91 crypto::SecureHash::Create(crypto::SecureHash::SHA256)); 92 93 hash->Update(machine_id.data(), machine_id.size()); 94 hash->Update(salt.data(), salt.size()); 95 96 std::string result_bytes(crypto::kSHA256Length, 0); 97 hash->Finish(string_as_array(&result_bytes), result_bytes.size()); 98 99 base::Base64Encode(result_bytes, result); 100 return true; 101} 102 103// Validates that |input| is a string of the form "YYYY-MM-DD". 104bool ValidateExpireDateFormat(const std::string& input) { 105 if (input.length() != 10) 106 return false; 107 for (int i = 0; i < 10; i++) { 108 if (i == 4 || i == 7) { 109 if (input[i] != '-') 110 return false; 111 } else if (!IsAsciiDigit(input[i])) { 112 return false; 113 } 114 } 115 return true; 116} 117 118// Sets the value of |key| in |dictionary| to be a list with the contents of 119// |ids|. 120void SetExtensionIdSet(base::DictionaryValue* dictionary, 121 const char* key, 122 const ExtensionIdSet& ids) { 123 base::ListValue* id_list = new base::ListValue(); 124 for (ExtensionIdSet::const_iterator i = ids.begin(); i != ids.end(); ++i) 125 id_list->AppendString(*i); 126 dictionary->Set(key, id_list); 127} 128 129// Tries to fetch a list of strings from |dictionay| for |key|, and inserts 130// them into |ids|. The return value indicates success/failure. Note: on 131// failure, |ids| might contain partial results, for instance if some of the 132// members of the list were not strings. 133bool GetExtensionIdSet(const base::DictionaryValue& dictionary, 134 const char* key, 135 ExtensionIdSet* ids) { 136 const base::ListValue* id_list = NULL; 137 if (!dictionary.GetList(key, &id_list)) 138 return false; 139 for (base::ListValue::const_iterator i = id_list->begin(); 140 i != id_list->end(); 141 ++i) { 142 std::string id; 143 if (!(*i)->GetAsString(&id)) { 144 return false; 145 } 146 ids->insert(id); 147 } 148 return true; 149} 150 151} // namespace 152 153namespace extensions { 154 155InstallSignature::InstallSignature() { 156} 157InstallSignature::~InstallSignature() { 158} 159 160void InstallSignature::ToValue(base::DictionaryValue* value) const { 161 CHECK(value); 162 163 value->SetInteger(kSignatureFormatVersionKey, kSignatureFormatVersion); 164 SetExtensionIdSet(value, kIdsKey, ids); 165 SetExtensionIdSet(value, kInvalidIdsKey, invalid_ids); 166 value->SetString(kExpireDateKey, expire_date); 167 std::string salt_base64; 168 std::string signature_base64; 169 base::Base64Encode(salt, &salt_base64); 170 base::Base64Encode(signature, &signature_base64); 171 value->SetString(kSaltKey, salt_base64); 172 value->SetString(kSignatureKey, signature_base64); 173 value->SetString(kTimestampKey, 174 base::Int64ToString(timestamp.ToInternalValue())); 175} 176 177// static 178scoped_ptr<InstallSignature> InstallSignature::FromValue( 179 const base::DictionaryValue& value) { 180 181 scoped_ptr<InstallSignature> result(new InstallSignature); 182 183 // For now we don't want to support any backwards compability, but in the 184 // future if we do, we would want to put the migration code here. 185 int format_version = 0; 186 if (!value.GetInteger(kSignatureFormatVersionKey, &format_version) || 187 format_version != kSignatureFormatVersion) { 188 result.reset(); 189 return result.Pass(); 190 } 191 192 std::string salt_base64; 193 std::string signature_base64; 194 if (!value.GetString(kExpireDateKey, &result->expire_date) || 195 !value.GetString(kSaltKey, &salt_base64) || 196 !value.GetString(kSignatureKey, &signature_base64) || 197 !base::Base64Decode(salt_base64, &result->salt) || 198 !base::Base64Decode(signature_base64, &result->signature)) { 199 result.reset(); 200 return result.Pass(); 201 } 202 203 // Note: earlier versions of the code did not write out a timestamp value 204 // so older entries will not necessarily have this. 205 if (value.HasKey(kTimestampKey)) { 206 std::string timestamp; 207 int64 timestamp_value = 0; 208 if (!value.GetString(kTimestampKey, ×tamp) || 209 !base::StringToInt64(timestamp, ×tamp_value)) { 210 result.reset(); 211 return result.Pass(); 212 } 213 result->timestamp = base::Time::FromInternalValue(timestamp_value); 214 } 215 216 if (!GetExtensionIdSet(value, kIdsKey, &result->ids) || 217 !GetExtensionIdSet(value, kInvalidIdsKey, &result->invalid_ids)) { 218 result.reset(); 219 return result.Pass(); 220 } 221 222 return result.Pass(); 223} 224 225 226InstallSigner::InstallSigner(net::URLRequestContextGetter* context_getter, 227 const ExtensionIdSet& ids) 228 : ids_(ids), context_getter_(context_getter) { 229} 230 231InstallSigner::~InstallSigner() { 232} 233 234// static 235bool InstallSigner::VerifySignature(const InstallSignature& signature) { 236 if (signature.ids.empty()) 237 return true; 238 239 std::string signed_data; 240 for (ExtensionIdSet::const_iterator i = signature.ids.begin(); 241 i != signature.ids.end(); ++i) 242 signed_data.append(*i); 243 244 std::string hash_base64; 245 if (!HashWithMachineId(signature.salt, &hash_base64)) 246 return false; 247 signed_data.append(hash_base64); 248 249 signed_data.append(signature.expire_date); 250 251 std::string public_key; 252 if (!Extension::ParsePEMKeyBytes(kPublicKeyPEM, &public_key)) 253 return false; 254 255 crypto::SignatureVerifier verifier; 256 if (!verifier.VerifyInit(extension_misc::kSignatureAlgorithm, 257 sizeof(extension_misc::kSignatureAlgorithm), 258 reinterpret_cast<const uint8*>( 259 signature.signature.data()), 260 signature.signature.size(), 261 reinterpret_cast<const uint8*>(public_key.data()), 262 public_key.size())) 263 return false; 264 265 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(signed_data.data()), 266 signed_data.size()); 267 return verifier.VerifyFinal(); 268} 269 270 271class InstallSigner::FetcherDelegate : public net::URLFetcherDelegate { 272 public: 273 explicit FetcherDelegate(const base::Closure& callback) 274 : callback_(callback) { 275 } 276 277 virtual ~FetcherDelegate() { 278 } 279 280 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE { 281 callback_.Run(); 282 } 283 284 private: 285 base::Closure callback_; 286 DISALLOW_COPY_AND_ASSIGN(FetcherDelegate); 287}; 288 289// static 290ExtensionIdSet InstallSigner::GetForcedNotFromWebstore() { 291 std::string value = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 292 switches::kExtensionsNotWebstore); 293 if (value.empty()) 294 return ExtensionIdSet(); 295 296 std::vector<std::string> ids; 297 base::SplitString(value, ',', &ids); 298 return ExtensionIdSet(ids.begin(), ids.end()); 299} 300 301namespace { 302 303static int g_request_count = 0; 304 305base::LazyInstance<base::TimeTicks> g_last_request_time = 306 LAZY_INSTANCE_INITIALIZER; 307 308base::LazyInstance<base::ThreadChecker> g_single_thread_checker = 309 LAZY_INSTANCE_INITIALIZER; 310 311void LogRequestStartHistograms() { 312 // Make sure we only ever call this from one thread, so that we don't have to 313 // worry about race conditions setting g_last_request_time. 314 DCHECK(g_single_thread_checker.Get().CalledOnValidThread()); 315 316 // CurrentProcessInfo::CreationTime is only defined on some platforms. 317#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) 318 const base::Time process_creation_time = 319 base::CurrentProcessInfo::CreationTime(); 320 UMA_HISTOGRAM_COUNTS("ExtensionInstallSigner.UptimeAtTimeOfRequest", 321 (base::Time::Now() - process_creation_time).InSeconds()); 322#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) 323 324 base::TimeDelta delta; 325 base::TimeTicks now = base::TimeTicks::Now(); 326 if (!g_last_request_time.Get().is_null()) 327 delta = now - g_last_request_time.Get(); 328 g_last_request_time.Get() = now; 329 UMA_HISTOGRAM_COUNTS("ExtensionInstallSigner.SecondsSinceLastRequest", 330 delta.InSeconds()); 331 332 g_request_count += 1; 333 UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.RequestCount", 334 g_request_count); 335} 336 337} // namespace 338 339void InstallSigner::GetSignature(const SignatureCallback& callback) { 340 CHECK(!url_fetcher_.get()); 341 CHECK(callback_.is_null()); 342 CHECK(salt_.empty()); 343 callback_ = callback; 344 345 // If the set of ids is empty, just return an empty signature and skip the 346 // call to the server. 347 if (ids_.empty()) { 348 if (!callback_.is_null()) 349 callback_.Run(scoped_ptr<InstallSignature>(new InstallSignature())); 350 return; 351 } 352 353 salt_ = std::string(kSaltBytes, 0); 354 DCHECK_EQ(kSaltBytes, salt_.size()); 355 crypto::RandBytes(string_as_array(&salt_), salt_.size()); 356 357 std::string hash_base64; 358 if (!HashWithMachineId(salt_, &hash_base64)) { 359 ReportErrorViaCallback(); 360 return; 361 } 362 363 if (!context_getter_) { 364 ReportErrorViaCallback(); 365 return; 366 } 367 368 base::Closure closure = base::Bind(&InstallSigner::ParseFetchResponse, 369 base::Unretained(this)); 370 371 delegate_.reset(new FetcherDelegate(closure)); 372 url_fetcher_.reset(net::URLFetcher::Create( 373 GetBackendUrl(), net::URLFetcher::POST, delegate_.get())); 374 url_fetcher_->SetRequestContext(context_getter_); 375 376 // The request protocol is JSON of the form: 377 // { 378 // "protocol_version": "1", 379 // "hash": "<base64-encoded hash value here>", 380 // "ids": [ "<id1>", "id2" ] 381 // } 382 base::DictionaryValue dictionary; 383 dictionary.SetInteger(kProtocolVersionKey, 1); 384 dictionary.SetString(kHashKey, hash_base64); 385 scoped_ptr<base::ListValue> id_list(new base::ListValue); 386 for (ExtensionIdSet::const_iterator i = ids_.begin(); i != ids_.end(); ++i) { 387 id_list->AppendString(*i); 388 } 389 dictionary.Set(kIdsKey, id_list.release()); 390 std::string json; 391 base::JSONWriter::Write(&dictionary, &json); 392 if (json.empty()) { 393 ReportErrorViaCallback(); 394 return; 395 } 396 url_fetcher_->SetUploadData("application/json", json); 397 LogRequestStartHistograms(); 398 request_start_time_ = base::Time::Now(); 399 VLOG(1) << "Sending request: " << json; 400 url_fetcher_->Start(); 401} 402 403void InstallSigner::ReportErrorViaCallback() { 404 InstallSignature* null_signature = NULL; 405 if (!callback_.is_null()) 406 callback_.Run(scoped_ptr<InstallSignature>(null_signature)); 407} 408 409void InstallSigner::ParseFetchResponse() { 410 bool fetch_success = url_fetcher_->GetStatus().is_success(); 411 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.FetchSuccess", fetch_success); 412 413 std::string response; 414 if (fetch_success) { 415 if (!url_fetcher_->GetResponseAsString(&response)) 416 response.clear(); 417 } 418 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.GetResponseSuccess", 419 !response.empty()); 420 if (!fetch_success || response.empty()) { 421 ReportErrorViaCallback(); 422 return; 423 } 424 VLOG(1) << "Got response: " << response; 425 426 // The response is JSON of the form: 427 // { 428 // "protocol_version": "1", 429 // "signature": "<base64-encoded signature>", 430 // "expiry": "<date in YYYY-MM-DD form>", 431 // "invalid_ids": [ "<id3>", "<id4>" ] 432 // } 433 // where |invalid_ids| is a list of ids from the original request that 434 // could not be verified to be in the webstore. 435 436 base::DictionaryValue* dictionary = NULL; 437 scoped_ptr<base::Value> parsed(base::JSONReader::Read(response)); 438 bool json_success = parsed.get() && parsed->GetAsDictionary(&dictionary); 439 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ParseJsonSuccess", 440 json_success); 441 if (!json_success) { 442 ReportErrorViaCallback(); 443 return; 444 } 445 446 int protocol_version = 0; 447 std::string signature_base64; 448 std::string signature; 449 std::string expire_date; 450 451 dictionary->GetInteger(kProtocolVersionKey, &protocol_version); 452 dictionary->GetString(kSignatureKey, &signature_base64); 453 dictionary->GetString(kExpiryKey, &expire_date); 454 455 bool fields_success = 456 protocol_version == 1 && !signature_base64.empty() && 457 ValidateExpireDateFormat(expire_date) && 458 base::Base64Decode(signature_base64, &signature); 459 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ParseFieldsSuccess", 460 fields_success); 461 if (!fields_success) { 462 ReportErrorViaCallback(); 463 return; 464 } 465 466 ExtensionIdSet invalid_ids; 467 const base::ListValue* invalid_ids_list = NULL; 468 if (dictionary->GetList(kInvalidIdsKey, &invalid_ids_list)) { 469 for (size_t i = 0; i < invalid_ids_list->GetSize(); i++) { 470 std::string id; 471 if (!invalid_ids_list->GetString(i, &id)) { 472 ReportErrorViaCallback(); 473 return; 474 } 475 invalid_ids.insert(id); 476 } 477 } 478 479 HandleSignatureResult(signature, expire_date, invalid_ids); 480} 481 482void InstallSigner::HandleSignatureResult(const std::string& signature, 483 const std::string& expire_date, 484 const ExtensionIdSet& invalid_ids) { 485 ExtensionIdSet valid_ids = 486 base::STLSetDifference<ExtensionIdSet>(ids_, invalid_ids); 487 488 scoped_ptr<InstallSignature> result; 489 if (!signature.empty()) { 490 result.reset(new InstallSignature); 491 result->ids = valid_ids; 492 result->invalid_ids = invalid_ids; 493 result->salt = salt_; 494 result->signature = signature; 495 result->expire_date = expire_date; 496 result->timestamp = request_start_time_; 497 bool verified = VerifySignature(*result); 498 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ResultWasValid", verified); 499 UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.InvalidCount", 500 invalid_ids.size()); 501 if (!verified) 502 result.reset(); 503 } 504 505 if (!callback_.is_null()) 506 callback_.Run(result.Pass()); 507} 508 509 510} // namespace extensions 511