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.
5 #include "chrome/browser/extensions/install_signer.h"
7 #include "base/base64.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 "components/crx_file/constants.h"
25 #include "crypto/random.h"
26 #include "crypto/secure_hash.h"
27 #include "crypto/sha2.h"
28 #include "crypto/signature_verifier.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"
35 #if defined(ENABLE_RLZ)
36 #include "rlz/lib/machine_id.h"
41 using extensions::ExtensionIdSet
;
43 const char kExpireDateKey
[] = "expire_date";
44 const char kExpiryKey
[] = "expiry";
45 const char kHashKey
[] = "hash";
46 const char kIdsKey
[] = "ids";
47 const char kInvalidIdsKey
[] = "invalid_ids";
48 const char kProtocolVersionKey
[] = "protocol_version";
49 const char kSaltKey
[] = "salt";
50 const char kSignatureKey
[] = "signature";
51 const char kSignatureFormatVersionKey
[] = "signature_format_version";
52 const char kTimestampKey
[] = "timestamp";
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.
57 const int kSignatureFormatVersion
= 2;
59 const size_t kSaltBytes
= 32;
61 const char kBackendUrl
[] =
62 "https://www.googleapis.com/chromewebstore/v1.1/items/verify";
64 const 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" \
73 "-----END PUBLIC KEY-----";
75 GURL
GetBackendUrl() {
76 return GURL(kBackendUrl
);
79 // Hashes |salt| with the machine id, base64-encodes it and returns it in
81 bool 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
))
87 machine_id
= "unknown";
90 scoped_ptr
<crypto::SecureHash
> hash(
91 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
93 hash
->Update(machine_id
.data(), machine_id
.size());
94 hash
->Update(salt
.data(), salt
.size());
96 std::string
result_bytes(crypto::kSHA256Length
, 0);
97 hash
->Finish(string_as_array(&result_bytes
), result_bytes
.size());
99 base::Base64Encode(result_bytes
, result
);
103 // Validates that |input| is a string of the form "YYYY-MM-DD".
104 bool ValidateExpireDateFormat(const std::string
& input
) {
105 if (input
.length() != 10)
107 for (int i
= 0; i
< 10; i
++) {
108 if (i
== 4 || i
== 7) {
111 } else if (!base::IsAsciiDigit(input
[i
])) {
118 // Sets the value of |key| in |dictionary| to be a list with the contents of
120 void SetExtensionIdSet(base::DictionaryValue
* dictionary
,
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
);
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.
133 bool GetExtensionIdSet(const base::DictionaryValue
& dictionary
,
135 ExtensionIdSet
* ids
) {
136 const base::ListValue
* id_list
= NULL
;
137 if (!dictionary
.GetList(key
, &id_list
))
139 for (base::ListValue::const_iterator i
= id_list
->begin();
143 if (!(*i
)->GetAsString(&id
)) {
153 namespace extensions
{
155 InstallSignature::InstallSignature() {
157 InstallSignature::~InstallSignature() {
160 void InstallSignature::ToValue(base::DictionaryValue
* value
) const {
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()));
178 scoped_ptr
<InstallSignature
> InstallSignature::FromValue(
179 const base::DictionaryValue
& value
) {
181 scoped_ptr
<InstallSignature
> result(new InstallSignature
);
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
) {
189 return result
.Pass();
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
)) {
200 return result
.Pass();
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
)) {
211 return result
.Pass();
213 result
->timestamp
= base::Time::FromInternalValue(timestamp_value
);
216 if (!GetExtensionIdSet(value
, kIdsKey
, &result
->ids
) ||
217 !GetExtensionIdSet(value
, kInvalidIdsKey
, &result
->invalid_ids
)) {
219 return result
.Pass();
222 return result
.Pass();
226 InstallSigner::InstallSigner(net::URLRequestContextGetter
* context_getter
,
227 const ExtensionIdSet
& ids
)
228 : ids_(ids
), context_getter_(context_getter
) {
231 InstallSigner::~InstallSigner() {
235 bool InstallSigner::VerifySignature(const InstallSignature
& signature
) {
236 if (signature
.ids
.empty())
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
);
244 std::string hash_base64
;
245 if (!HashWithMachineId(signature
.salt
, &hash_base64
))
247 signed_data
.append(hash_base64
);
249 signed_data
.append(signature
.expire_date
);
251 std::string public_key
;
252 if (!Extension::ParsePEMKeyBytes(kPublicKeyPEM
, &public_key
))
255 crypto::SignatureVerifier verifier
;
256 if (!verifier
.VerifyInit(crx_file::kSignatureAlgorithm
,
257 sizeof(crx_file::kSignatureAlgorithm
),
258 reinterpret_cast<const uint8
*>(
259 signature
.signature
.data()),
260 signature
.signature
.size(),
261 reinterpret_cast<const uint8
*>(public_key
.data()),
265 verifier
.VerifyUpdate(reinterpret_cast<const uint8
*>(signed_data
.data()),
267 return verifier
.VerifyFinal();
271 class InstallSigner::FetcherDelegate
: public net::URLFetcherDelegate
{
273 explicit FetcherDelegate(const base::Closure
& callback
)
274 : callback_(callback
) {
277 ~FetcherDelegate() override
{}
279 void OnURLFetchComplete(const net::URLFetcher
* source
) override
{
284 base::Closure callback_
;
285 DISALLOW_COPY_AND_ASSIGN(FetcherDelegate
);
289 ExtensionIdSet
InstallSigner::GetForcedNotFromWebstore() {
291 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
292 switches::kExtensionsNotWebstore
);
294 return ExtensionIdSet();
296 std::vector
<std::string
> ids
= base::SplitString(
297 value
, ",", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
298 return ExtensionIdSet(ids
.begin(), ids
.end());
303 static int g_request_count
= 0;
305 base::LazyInstance
<base::TimeTicks
> g_last_request_time
=
306 LAZY_INSTANCE_INITIALIZER
;
308 base::LazyInstance
<base::ThreadChecker
> g_single_thread_checker
=
309 LAZY_INSTANCE_INITIALIZER
;
311 void 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());
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)
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",
332 g_request_count
+= 1;
333 UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.RequestCount",
339 void InstallSigner::GetSignature(const SignatureCallback
& callback
) {
340 CHECK(!url_fetcher_
.get());
341 CHECK(callback_
.is_null());
342 CHECK(salt_
.empty());
343 callback_
= callback
;
345 // If the set of ids is empty, just return an empty signature and skip the
346 // call to the server.
348 if (!callback_
.is_null())
349 callback_
.Run(scoped_ptr
<InstallSignature
>(new InstallSignature()));
353 salt_
= std::string(kSaltBytes
, 0);
354 DCHECK_EQ(kSaltBytes
, salt_
.size());
355 crypto::RandBytes(string_as_array(&salt_
), salt_
.size());
357 std::string hash_base64
;
358 if (!HashWithMachineId(salt_
, &hash_base64
)) {
359 ReportErrorViaCallback();
363 if (!context_getter_
) {
364 ReportErrorViaCallback();
368 base::Closure closure
= base::Bind(&InstallSigner::ParseFetchResponse
,
369 base::Unretained(this));
371 delegate_
.reset(new FetcherDelegate(closure
));
372 url_fetcher_
= net::URLFetcher::Create(GetBackendUrl(), net::URLFetcher::POST
,
374 url_fetcher_
->SetRequestContext(context_getter_
);
376 // The request protocol is JSON of the form:
378 // "protocol_version": "1",
379 // "hash": "<base64-encoded hash value here>",
380 // "ids": [ "<id1>", "id2" ]
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
);
389 dictionary
.Set(kIdsKey
, id_list
.release());
391 base::JSONWriter::Write(dictionary
, &json
);
393 ReportErrorViaCallback();
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();
403 void InstallSigner::ReportErrorViaCallback() {
404 InstallSignature
* null_signature
= NULL
;
405 if (!callback_
.is_null())
406 callback_
.Run(scoped_ptr
<InstallSignature
>(null_signature
));
409 void InstallSigner::ParseFetchResponse() {
410 bool fetch_success
= url_fetcher_
->GetStatus().is_success();
411 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.FetchSuccess", fetch_success
);
413 std::string response
;
415 if (!url_fetcher_
->GetResponseAsString(&response
))
418 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.GetResponseSuccess",
420 if (!fetch_success
|| response
.empty()) {
421 ReportErrorViaCallback();
424 VLOG(1) << "Got response: " << response
;
426 // The response is JSON of the form:
428 // "protocol_version": "1",
429 // "signature": "<base64-encoded signature>",
430 // "expiry": "<date in YYYY-MM-DD form>",
431 // "invalid_ids": [ "<id3>", "<id4>" ]
433 // where |invalid_ids| is a list of ids from the original request that
434 // could not be verified to be in the webstore.
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",
442 ReportErrorViaCallback();
446 int protocol_version
= 0;
447 std::string signature_base64
;
448 std::string signature
;
449 std::string expire_date
;
451 dictionary
->GetInteger(kProtocolVersionKey
, &protocol_version
);
452 dictionary
->GetString(kSignatureKey
, &signature_base64
);
453 dictionary
->GetString(kExpiryKey
, &expire_date
);
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",
461 if (!fields_success
) {
462 ReportErrorViaCallback();
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
++) {
471 if (!invalid_ids_list
->GetString(i
, &id
)) {
472 ReportErrorViaCallback();
475 invalid_ids
.insert(id
);
479 HandleSignatureResult(signature
, expire_date
, invalid_ids
);
482 void 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
);
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",
505 if (!callback_
.is_null())
506 callback_
.Run(result
.Pass());
510 } // namespace extensions