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 "chrome/common/extensions/extension_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 kTimestampKey
[] = "timestamp";
53 const size_t kSaltBytes
= 32;
55 const char kBackendUrl
[] =
56 "https://www.googleapis.com/chromewebstore/v1.1/items/verify";
58 const char kPublicKeyPEM
[] = \
59 "-----BEGIN PUBLIC KEY-----" \
60 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj/u/XDdjlDyw7gHEtaaa" \
61 "sZ9GdG8WOKAyJzXd8HFrDtz2Jcuy7er7MtWvHgNDA0bwpznbI5YdZeV4UfCEsA4S" \
62 "rA5b3MnWTHwA1bgbiDM+L9rrqvcadcKuOlTeN48Q0ijmhHlNFbTzvT9W0zw/GKv8" \
63 "LgXAHggxtmHQ/Z9PP2QNF5O8rUHHSL4AJ6hNcEKSBVSmbbjeVm4gSXDuED5r0nwx" \
64 "vRtupDxGYp8IZpP5KlExqNu1nbkPc+igCTIB6XsqijagzxewUHCdovmkb2JNtskx" \
65 "/PMIEv+TvWIx2BzqGp71gSh/dV7SJ3rClvWd2xj8dtxG8FfAWDTIIi0qZXWn2Qhi" \
67 "-----END PUBLIC KEY-----";
69 GURL
GetBackendUrl() {
70 return GURL(kBackendUrl
);
73 // Hashes |salt| with the machine id, base64-encodes it and returns it in
75 bool HashWithMachineId(const std::string
& salt
, std::string
* result
) {
76 std::string machine_id
;
77 #if defined(ENABLE_RLZ)
78 if (!rlz_lib::GetMachineId(&machine_id
))
81 machine_id
= "unknown";
84 scoped_ptr
<crypto::SecureHash
> hash(
85 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
87 hash
->Update(machine_id
.data(), machine_id
.size());
88 hash
->Update(salt
.data(), salt
.size());
90 std::string
result_bytes(crypto::kSHA256Length
, 0);
91 hash
->Finish(string_as_array(&result_bytes
), result_bytes
.size());
93 base::Base64Encode(result_bytes
, result
);
97 // Validates that |input| is a string of the form "YYYY-MM-DD".
98 bool ValidateExpireDateFormat(const std::string
& input
) {
99 if (input
.length() != 10)
101 for (int i
= 0; i
< 10; i
++) {
102 if (i
== 4 || i
== 7) {
105 } else if (!IsAsciiDigit(input
[i
])) {
114 namespace extensions
{
116 InstallSignature::InstallSignature() {
118 InstallSignature::~InstallSignature() {
121 void InstallSignature::ToValue(base::DictionaryValue
* value
) const {
124 base::ListValue
* id_list
= new base::ListValue();
125 for (ExtensionIdSet::const_iterator i
= ids
.begin(); i
!= ids
.end();
127 id_list
->AppendString(*i
);
129 value
->Set(kIdsKey
, id_list
);
130 value
->SetString(kExpireDateKey
, expire_date
);
131 std::string salt_base64
;
132 std::string signature_base64
;
133 base::Base64Encode(salt
, &salt_base64
);
134 base::Base64Encode(signature
, &signature_base64
);
135 value
->SetString(kSaltKey
, salt_base64
);
136 value
->SetString(kSignatureKey
, signature_base64
);
137 value
->SetString(kTimestampKey
,
138 base::Int64ToString(timestamp
.ToInternalValue()));
142 scoped_ptr
<InstallSignature
> InstallSignature::FromValue(
143 const base::DictionaryValue
& value
) {
145 scoped_ptr
<InstallSignature
> result(new InstallSignature
);
147 std::string salt_base64
;
148 std::string signature_base64
;
149 if (!value
.GetString(kExpireDateKey
, &result
->expire_date
) ||
150 !value
.GetString(kSaltKey
, &salt_base64
) ||
151 !value
.GetString(kSignatureKey
, &signature_base64
) ||
152 !base::Base64Decode(salt_base64
, &result
->salt
) ||
153 !base::Base64Decode(signature_base64
, &result
->signature
)) {
155 return result
.Pass();
158 // Note: earlier versions of the code did not write out a timestamp value
159 // so older entries will not necessarily have this.
160 if (value
.HasKey(kTimestampKey
)) {
161 std::string timestamp
;
162 int64 timestamp_value
= 0;
163 if (!value
.GetString(kTimestampKey
, ×tamp
) ||
164 !base::StringToInt64(timestamp
, ×tamp_value
)) {
166 return result
.Pass();
168 result
->timestamp
= base::Time::FromInternalValue(timestamp_value
);
171 const base::ListValue
* ids
= NULL
;
172 if (!value
.GetList(kIdsKey
, &ids
)) {
174 return result
.Pass();
177 for (base::ListValue::const_iterator i
= ids
->begin(); i
!= ids
->end(); ++i
) {
179 if (!(*i
)->GetAsString(&id
)) {
181 return result
.Pass();
183 result
->ids
.insert(id
);
186 return result
.Pass();
190 InstallSigner::InstallSigner(net::URLRequestContextGetter
* context_getter
,
191 const ExtensionIdSet
& ids
)
192 : ids_(ids
), context_getter_(context_getter
) {
195 InstallSigner::~InstallSigner() {
199 bool InstallSigner::VerifySignature(const InstallSignature
& signature
) {
200 if (signature
.ids
.empty())
203 std::string signed_data
;
204 for (ExtensionIdSet::const_iterator i
= signature
.ids
.begin();
205 i
!= signature
.ids
.end(); ++i
)
206 signed_data
.append(*i
);
208 std::string hash_base64
;
209 if (!HashWithMachineId(signature
.salt
, &hash_base64
))
211 signed_data
.append(hash_base64
);
213 signed_data
.append(signature
.expire_date
);
215 std::string public_key
;
216 if (!Extension::ParsePEMKeyBytes(kPublicKeyPEM
, &public_key
))
219 crypto::SignatureVerifier verifier
;
220 if (!verifier
.VerifyInit(extension_misc::kSignatureAlgorithm
,
221 sizeof(extension_misc::kSignatureAlgorithm
),
222 reinterpret_cast<const uint8
*>(
223 signature
.signature
.data()),
224 signature
.signature
.size(),
225 reinterpret_cast<const uint8
*>(public_key
.data()),
229 verifier
.VerifyUpdate(reinterpret_cast<const uint8
*>(signed_data
.data()),
231 return verifier
.VerifyFinal();
235 class InstallSigner::FetcherDelegate
: public net::URLFetcherDelegate
{
237 explicit FetcherDelegate(const base::Closure
& callback
)
238 : callback_(callback
) {
241 virtual ~FetcherDelegate() {
244 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
{
249 base::Closure callback_
;
250 DISALLOW_COPY_AND_ASSIGN(FetcherDelegate
);
254 ExtensionIdSet
InstallSigner::GetForcedNotFromWebstore() {
255 std::string value
= CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
256 switches::kExtensionsNotWebstore
);
258 return ExtensionIdSet();
260 std::vector
<std::string
> ids
;
261 base::SplitString(value
, ',', &ids
);
262 return ExtensionIdSet(ids
.begin(), ids
.end());
267 static int g_request_count
= 0;
269 base::LazyInstance
<base::TimeTicks
> g_last_request_time
=
270 LAZY_INSTANCE_INITIALIZER
;
272 base::LazyInstance
<base::ThreadChecker
> g_single_thread_checker
=
273 LAZY_INSTANCE_INITIALIZER
;
275 void LogRequestStartHistograms() {
276 // Make sure we only ever call this from one thread, so that we don't have to
277 // worry about race conditions setting g_last_request_time.
278 DCHECK(g_single_thread_checker
.Get().CalledOnValidThread());
280 // CurrentProcessInfo::CreationTime is only defined on some platforms.
281 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
282 const base::Time process_creation_time
=
283 base::CurrentProcessInfo::CreationTime();
284 UMA_HISTOGRAM_COUNTS("ExtensionInstallSigner.UptimeAtTimeOfRequest",
285 (base::Time::Now() - process_creation_time
).InSeconds());
286 #endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
288 base::TimeDelta delta
;
289 base::TimeTicks now
= base::TimeTicks::Now();
290 if (!g_last_request_time
.Get().is_null())
291 delta
= now
- g_last_request_time
.Get();
292 g_last_request_time
.Get() = now
;
293 UMA_HISTOGRAM_COUNTS("ExtensionInstallSigner.SecondsSinceLastRequest",
296 g_request_count
+= 1;
297 UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.RequestCount",
303 void InstallSigner::GetSignature(const SignatureCallback
& callback
) {
304 CHECK(!url_fetcher_
.get());
305 CHECK(callback_
.is_null());
306 CHECK(salt_
.empty());
307 callback_
= callback
;
309 // If the set of ids is empty, just return an empty signature and skip the
310 // call to the server.
312 if (!callback_
.is_null())
313 callback_
.Run(scoped_ptr
<InstallSignature
>(new InstallSignature()));
317 salt_
= std::string(kSaltBytes
, 0);
318 DCHECK_EQ(kSaltBytes
, salt_
.size());
319 crypto::RandBytes(string_as_array(&salt_
), salt_
.size());
321 std::string hash_base64
;
322 if (!HashWithMachineId(salt_
, &hash_base64
)) {
323 ReportErrorViaCallback();
327 if (!context_getter_
) {
328 ReportErrorViaCallback();
332 base::Closure closure
= base::Bind(&InstallSigner::ParseFetchResponse
,
333 base::Unretained(this));
335 delegate_
.reset(new FetcherDelegate(closure
));
336 url_fetcher_
.reset(net::URLFetcher::Create(
337 GetBackendUrl(), net::URLFetcher::POST
, delegate_
.get()));
338 url_fetcher_
->SetRequestContext(context_getter_
);
340 // The request protocol is JSON of the form:
342 // "protocol_version": "1",
343 // "hash": "<base64-encoded hash value here>",
344 // "ids": [ "<id1>", "id2" ]
346 base::DictionaryValue dictionary
;
347 dictionary
.SetInteger(kProtocolVersionKey
, 1);
348 dictionary
.SetString(kHashKey
, hash_base64
);
349 scoped_ptr
<base::ListValue
> id_list(new base::ListValue
);
350 for (ExtensionIdSet::const_iterator i
= ids_
.begin(); i
!= ids_
.end(); ++i
) {
351 id_list
->AppendString(*i
);
353 dictionary
.Set(kIdsKey
, id_list
.release());
355 base::JSONWriter::Write(&dictionary
, &json
);
357 ReportErrorViaCallback();
360 url_fetcher_
->SetUploadData("application/json", json
);
361 LogRequestStartHistograms();
362 request_start_time_
= base::Time::Now();
363 url_fetcher_
->Start();
366 void InstallSigner::ReportErrorViaCallback() {
367 InstallSignature
* null_signature
= NULL
;
368 if (!callback_
.is_null())
369 callback_
.Run(scoped_ptr
<InstallSignature
>(null_signature
));
372 void InstallSigner::ParseFetchResponse() {
373 bool fetch_success
= url_fetcher_
->GetStatus().is_success();
374 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.FetchSuccess", fetch_success
);
376 std::string response
;
378 if (!url_fetcher_
->GetResponseAsString(&response
))
381 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.GetResponseSuccess",
383 if (!fetch_success
|| response
.empty()) {
384 ReportErrorViaCallback();
388 // The response is JSON of the form:
390 // "protocol_version": "1",
391 // "signature": "<base64-encoded signature>",
392 // "expiry": "<date in YYYY-MM-DD form>",
393 // "invalid_ids": [ "<id3>", "<id4>" ]
395 // where |invalid_ids| is a list of ids from the original request that
396 // could not be verified to be in the webstore.
398 base::DictionaryValue
* dictionary
= NULL
;
399 scoped_ptr
<base::Value
> parsed(base::JSONReader::Read(response
));
400 bool json_success
= parsed
.get() && parsed
->GetAsDictionary(&dictionary
);
401 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ParseJsonSuccess",
404 ReportErrorViaCallback();
408 int protocol_version
= 0;
409 std::string signature_base64
;
410 std::string signature
;
411 std::string expire_date
;
413 dictionary
->GetInteger(kProtocolVersionKey
, &protocol_version
);
414 dictionary
->GetString(kSignatureKey
, &signature_base64
);
415 dictionary
->GetString(kExpiryKey
, &expire_date
);
417 bool fields_success
=
418 protocol_version
== 1 && !signature_base64
.empty() &&
419 ValidateExpireDateFormat(expire_date
) &&
420 base::Base64Decode(signature_base64
, &signature
);
421 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ParseFieldsSuccess",
423 if (!fields_success
) {
424 ReportErrorViaCallback();
428 ExtensionIdSet invalid_ids
;
429 const base::ListValue
* invalid_ids_list
= NULL
;
430 if (dictionary
->GetList(kInvalidIdsKey
, &invalid_ids_list
)) {
431 for (size_t i
= 0; i
< invalid_ids_list
->GetSize(); i
++) {
433 if (!invalid_ids_list
->GetString(i
, &id
)) {
434 ReportErrorViaCallback();
437 invalid_ids
.insert(id
);
441 HandleSignatureResult(signature
, expire_date
, invalid_ids
);
444 void InstallSigner::HandleSignatureResult(const std::string
& signature
,
445 const std::string
& expire_date
,
446 const ExtensionIdSet
& invalid_ids
) {
447 ExtensionIdSet valid_ids
=
448 base::STLSetDifference
<ExtensionIdSet
>(ids_
, invalid_ids
);
450 scoped_ptr
<InstallSignature
> result
;
451 if (!signature
.empty()) {
452 result
.reset(new InstallSignature
);
453 result
->ids
= valid_ids
;
454 result
->salt
= salt_
;
455 result
->signature
= signature
;
456 result
->expire_date
= expire_date
;
457 result
->timestamp
= request_start_time_
;
458 bool verified
= VerifySignature(*result
);
459 UMA_HISTOGRAM_BOOLEAN("ExtensionInstallSigner.ResultWasValid", verified
);
460 UMA_HISTOGRAM_COUNTS_100("ExtensionInstallSigner.InvalidCount",
466 if (!callback_
.is_null())
467 callback_
.Run(result
.Pass());
471 } // namespace extensions