fingerprint.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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// Generating a fingerprint consists of two major steps:
6//   (1) Gather all the necessary data.
7//   (2) Write it into a protocol buffer.
8//
9// Step (2) is as simple as it sounds -- it's really just a matter of copying
10// data.  Step (1) requires waiting on several asynchronous callbacks, which are
11// managed by the FingerprintDataLoader class.
12
13#include "components/autofill/content/browser/risk/fingerprint.h"
14
15#include "base/bind.h"
16#include "base/callback.h"
17#include "base/cpu.h"
18#include "base/logging.h"
19#include "base/scoped_observer.h"
20#include "base/strings/string_split.h"
21#include "base/strings/utf_string_conversions.h"
22#include "base/sys_info.h"
23#include "base/time/time.h"
24#include "base/values.h"
25#include "components/autofill/content/browser/risk/proto/fingerprint.pb.h"
26#include "content/public/browser/browser_thread.h"
27#include "content/public/browser/font_list_async.h"
28#include "content/public/browser/geolocation_provider.h"
29#include "content/public/browser/gpu_data_manager.h"
30#include "content/public/browser/gpu_data_manager_observer.h"
31#include "content/public/browser/plugin_service.h"
32#include "content/public/browser/render_widget_host.h"
33#include "content/public/browser/render_widget_host_view.h"
34#include "content/public/browser/web_contents.h"
35#include "content/public/browser/web_contents_view.h"
36#include "content/public/common/content_client.h"
37#include "content/public/common/geoposition.h"
38#include "content/public/common/webplugininfo.h"
39#include "gpu/config/gpu_info.h"
40#include "third_party/WebKit/public/platform/WebRect.h"
41#include "third_party/WebKit/public/platform/WebScreenInfo.h"
42#include "ui/gfx/rect.h"
43#include "ui/gfx/screen.h"
44
45using WebKit::WebScreenInfo;
46
47namespace autofill {
48namespace risk {
49
50namespace {
51
52const int32 kFingerprinterVersion = 1;
53
54// Returns the delta between the local timezone and UTC.
55base::TimeDelta GetTimezoneOffset() {
56  const base::Time utc = base::Time::Now();
57
58  base::Time::Exploded local;
59  utc.LocalExplode(&local);
60
61  return base::Time::FromUTCExploded(local) - utc;
62}
63
64// Returns the concatenation of the operating system name and version, e.g.
65// "Mac OS X 10.6.8".
66std::string GetOperatingSystemVersion() {
67  return base::SysInfo::OperatingSystemName() + " " +
68      base::SysInfo::OperatingSystemVersion();
69}
70
71Fingerprint::MachineCharacteristics::BrowserFeature
72    DialogTypeToBrowserFeature(DialogType dialog_type) {
73  switch (dialog_type) {
74    case DIALOG_TYPE_AUTOCHECKOUT:
75      return Fingerprint::MachineCharacteristics::FEATURE_AUTOCHECKOUT;
76
77    case DIALOG_TYPE_REQUEST_AUTOCOMPLETE:
78      return Fingerprint::MachineCharacteristics::FEATURE_REQUEST_AUTOCOMPLETE;
79  }
80
81  NOTREACHED();
82  return Fingerprint::MachineCharacteristics::FEATURE_UNKNOWN;
83}
84
85// Adds the list of |fonts| to the |machine|.
86void AddFontsToFingerprint(const base::ListValue& fonts,
87                           Fingerprint::MachineCharacteristics* machine) {
88  for (base::ListValue::const_iterator it = fonts.begin();
89       it != fonts.end(); ++it) {
90    // Each item in the list is a two-element list such that the first element
91    // is the font family and the second is the font name.
92    const base::ListValue* font_description = NULL;
93    bool success = (*it)->GetAsList(&font_description);
94    DCHECK(success);
95
96    std::string font_name;
97    success = font_description->GetString(1, &font_name);
98    DCHECK(success);
99
100    machine->add_font(font_name);
101  }
102}
103
104// Adds the list of |plugins| to the |machine|.
105void AddPluginsToFingerprint(const std::vector<content::WebPluginInfo>& plugins,
106                             Fingerprint::MachineCharacteristics* machine) {
107  for (std::vector<content::WebPluginInfo>::const_iterator it = plugins.begin();
108       it != plugins.end(); ++it) {
109    Fingerprint::MachineCharacteristics::Plugin* plugin =
110        machine->add_plugin();
111    plugin->set_name(UTF16ToUTF8(it->name));
112    plugin->set_description(UTF16ToUTF8(it->desc));
113    for (std::vector<content::WebPluginMimeType>::const_iterator mime_type =
114             it->mime_types.begin();
115         mime_type != it->mime_types.end(); ++mime_type) {
116      plugin->add_mime_type(mime_type->mime_type);
117    }
118    plugin->set_version(UTF16ToUTF8(it->version));
119  }
120}
121
122// Adds the list of HTTP accept languages to the |machine|.
123void AddAcceptLanguagesToFingerprint(
124    const std::string& accept_languages_str,
125    Fingerprint::MachineCharacteristics* machine) {
126  std::vector<std::string> accept_languages;
127  base::SplitString(accept_languages_str, ',', &accept_languages);
128  for (std::vector<std::string>::const_iterator it = accept_languages.begin();
129       it != accept_languages.end(); ++it) {
130    machine->add_requested_language(*it);
131  }
132}
133
134// This function writes
135//   (a) the number of screens,
136//   (b) the primary display's screen size,
137//   (c) the screen's color depth, and
138//   (d) the size of the screen unavailable to web page content,
139//       i.e. the Taskbar size on Windows
140// into the |machine|.
141void AddScreenInfoToFingerprint(const WebScreenInfo& screen_info,
142                                Fingerprint::MachineCharacteristics* machine) {
143  // TODO(scottmg): NativeScreen maybe wrong. http://crbug.com/133312
144  machine->set_screen_count(
145      gfx::Screen::GetNativeScreen()->GetNumDisplays());
146
147  const gfx::Size screen_size =
148      gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().GetSizeInPixel();
149  machine->mutable_screen_size()->set_width(screen_size.width());
150  machine->mutable_screen_size()->set_height(screen_size.height());
151
152  machine->set_screen_color_depth(screen_info.depth);
153
154  const gfx::Rect screen_rect(screen_info.rect);
155  const gfx::Rect available_rect(screen_info.availableRect);
156  const gfx::Rect unavailable_rect =
157      gfx::SubtractRects(screen_rect, available_rect);
158  machine->mutable_unavailable_screen_size()->set_width(
159      unavailable_rect.width());
160  machine->mutable_unavailable_screen_size()->set_height(
161      unavailable_rect.height());
162}
163
164// Writes info about the machine's CPU into the |machine|.
165void AddCpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) {
166  base::CPU cpu;
167  machine->mutable_cpu()->set_vendor_name(cpu.vendor_name());
168  machine->mutable_cpu()->set_brand(cpu.cpu_brand());
169}
170
171// Writes info about the machine's GPU into the |machine|.
172void AddGpuInfoToFingerprint(Fingerprint::MachineCharacteristics* machine) {
173  const gpu::GPUInfo& gpu_info =
174      content::GpuDataManager::GetInstance()->GetGPUInfo();
175  DCHECK(gpu_info.finalized);
176
177  Fingerprint::MachineCharacteristics::Graphics* graphics =
178      machine->mutable_graphics_card();
179  graphics->set_vendor_id(gpu_info.gpu.vendor_id);
180  graphics->set_device_id(gpu_info.gpu.device_id);
181  graphics->set_driver_version(gpu_info.driver_version);
182  graphics->set_driver_date(gpu_info.driver_date);
183
184  Fingerprint::MachineCharacteristics::Graphics::PerformanceStatistics*
185      gpu_performance = graphics->mutable_performance_statistics();
186  gpu_performance->set_graphics_score(gpu_info.performance_stats.graphics);
187  gpu_performance->set_gaming_score(gpu_info.performance_stats.gaming);
188  gpu_performance->set_overall_score(gpu_info.performance_stats.overall);
189}
190
191// Waits for all asynchronous data required for the fingerprint to be loaded,
192// then fills out the fingerprint.
193class FingerprintDataLoader : public content::GpuDataManagerObserver {
194 public:
195  FingerprintDataLoader(
196      uint64 obfuscated_gaia_id,
197      const gfx::Rect& window_bounds,
198      const gfx::Rect& content_bounds,
199      const WebScreenInfo& screen_info,
200      const std::string& version,
201      const std::string& charset,
202      const std::string& accept_languages,
203      const base::Time& install_time,
204      DialogType dialog_type,
205      const std::string& app_locale,
206      const base::Callback<void(scoped_ptr<Fingerprint>)>& callback);
207
208 private:
209  virtual ~FingerprintDataLoader() {}
210
211  // content::GpuDataManagerObserver:
212  virtual void OnGpuInfoUpdate() OVERRIDE;
213
214  // Callbacks for asynchronously loaded data.
215  void OnGotFonts(scoped_ptr<base::ListValue> fonts);
216  void OnGotPlugins(const std::vector<content::WebPluginInfo>& plugins);
217  void OnGotGeoposition(const content::Geoposition& geoposition);
218
219  // Methods that run on the IO thread to communicate with the
220  // GeolocationProvider.
221  void LoadGeoposition();
222  void OnGotGeopositionOnIOThread(const content::Geoposition& geoposition);
223
224  // If all of the asynchronous data has been loaded, calls |callback_| with
225  // the fingerprint data.
226  void MaybeFillFingerprint();
227
228  // Calls |callback_| with the fingerprint data.
229  void FillFingerprint();
230
231  // The GPU data provider.
232  // Weak reference because the GpuDataManager class is a singleton.
233  content::GpuDataManager* const gpu_data_manager_;
234
235  // Ensures that any observer registratiosn for the GPU data are cleaned up by
236  // the time this object is destroyed.
237  ScopedObserver<content::GpuDataManager, FingerprintDataLoader> gpu_observer_;
238
239  // The callback used as an "observer" of the GeolocationProvider.  Accessed
240  // only on the IO thread.
241  content::GeolocationProvider::LocationUpdateCallback geolocation_callback_;
242
243  // Data that will be passed on to the next loading phase.  See the comment for
244  // GetFingerprint() for a description of these variables.
245  const uint64 obfuscated_gaia_id_;
246  const gfx::Rect window_bounds_;
247  const gfx::Rect content_bounds_;
248  const WebScreenInfo screen_info_;
249  const std::string version_;
250  const std::string charset_;
251  const std::string accept_languages_;
252  const base::Time install_time_;
253  DialogType dialog_type_;
254
255  // Data that will be loaded asynchronously.
256  scoped_ptr<base::ListValue> fonts_;
257  std::vector<content::WebPluginInfo> plugins_;
258  bool waiting_on_plugins_;
259  content::Geoposition geoposition_;
260
261  // The current application locale.
262  std::string app_locale_;
263
264  // The callback that will be called once all the data is available.
265  base::Callback<void(scoped_ptr<Fingerprint>)> callback_;
266
267  DISALLOW_COPY_AND_ASSIGN(FingerprintDataLoader);
268};
269
270FingerprintDataLoader::FingerprintDataLoader(
271    uint64 obfuscated_gaia_id,
272    const gfx::Rect& window_bounds,
273    const gfx::Rect& content_bounds,
274    const WebScreenInfo& screen_info,
275    const std::string& version,
276    const std::string& charset,
277    const std::string& accept_languages,
278    const base::Time& install_time,
279    DialogType dialog_type,
280    const std::string& app_locale,
281    const base::Callback<void(scoped_ptr<Fingerprint>)>& callback)
282    : gpu_data_manager_(content::GpuDataManager::GetInstance()),
283      gpu_observer_(this),
284      obfuscated_gaia_id_(obfuscated_gaia_id),
285      window_bounds_(window_bounds),
286      content_bounds_(content_bounds),
287      screen_info_(screen_info),
288      version_(version),
289      charset_(charset),
290      accept_languages_(accept_languages),
291      install_time_(install_time),
292      dialog_type_(dialog_type),
293      waiting_on_plugins_(true),
294      callback_(callback) {
295  DCHECK(!install_time_.is_null());
296
297  // Load GPU data if needed.
298  if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) {
299    gpu_observer_.Add(gpu_data_manager_);
300    gpu_data_manager_->RequestCompleteGpuInfoIfNeeded();
301  }
302
303#if defined(ENABLE_PLUGINS)
304  // Load plugin data.
305  content::PluginService::GetInstance()->GetPlugins(
306      base::Bind(&FingerprintDataLoader::OnGotPlugins, base::Unretained(this)));
307#else
308  waiting_on_plugins_ = false;
309#endif
310
311  // Load font data.
312  content::GetFontListAsync(
313      base::Bind(&FingerprintDataLoader::OnGotFonts, base::Unretained(this)));
314
315  // Load geolocation data.
316  content::BrowserThread::PostTask(
317      content::BrowserThread::IO, FROM_HERE,
318      base::Bind(&FingerprintDataLoader::LoadGeoposition,
319                 base::Unretained(this)));
320}
321
322void FingerprintDataLoader::OnGpuInfoUpdate() {
323  if (!gpu_data_manager_->IsCompleteGpuInfoAvailable())
324    return;
325
326  gpu_observer_.Remove(gpu_data_manager_);
327  MaybeFillFingerprint();
328}
329
330void FingerprintDataLoader::OnGotFonts(scoped_ptr<base::ListValue> fonts) {
331  DCHECK(!fonts_);
332  fonts_.reset(fonts.release());
333  MaybeFillFingerprint();
334}
335
336void FingerprintDataLoader::OnGotPlugins(
337    const std::vector<content::WebPluginInfo>& plugins) {
338  DCHECK(waiting_on_plugins_);
339  waiting_on_plugins_ = false;
340  plugins_ = plugins;
341  MaybeFillFingerprint();
342}
343
344void FingerprintDataLoader::OnGotGeoposition(
345    const content::Geoposition& geoposition) {
346  DCHECK(!geoposition_.Validate());
347
348  geoposition_ = geoposition;
349  DCHECK(geoposition_.Validate() ||
350         geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE);
351
352  MaybeFillFingerprint();
353}
354
355void FingerprintDataLoader::LoadGeoposition() {
356  geolocation_callback_ =
357      base::Bind(&FingerprintDataLoader::OnGotGeopositionOnIOThread,
358                 base::Unretained(this));
359  content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback(
360      geolocation_callback_, false);
361}
362
363void FingerprintDataLoader::OnGotGeopositionOnIOThread(
364    const content::Geoposition& geoposition) {
365  content::BrowserThread::PostTask(
366      content::BrowserThread::UI, FROM_HERE,
367      base::Bind(&FingerprintDataLoader::OnGotGeoposition,
368                 base::Unretained(this), geoposition));
369
370  // Unregister as an observer, since this class instance might be destroyed
371  // after this callback.  Note: It's important to unregister *after* posting
372  // the task above.  Unregistering as an observer can have the side-effect of
373  // modifying the value of |geoposition|.
374  bool removed =
375      content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback(
376          geolocation_callback_);
377  DCHECK(removed);
378}
379
380void FingerprintDataLoader::MaybeFillFingerprint() {
381  // If all of the data has been loaded, fill the fingerprint and clean up.
382  if (gpu_data_manager_->IsCompleteGpuInfoAvailable() &&
383      fonts_ &&
384      !waiting_on_plugins_ &&
385      (geoposition_.Validate() ||
386       geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE)) {
387    FillFingerprint();
388    delete this;
389  }
390}
391
392void FingerprintDataLoader::FillFingerprint() {
393  scoped_ptr<Fingerprint> fingerprint(new Fingerprint);
394  Fingerprint::MachineCharacteristics* machine =
395      fingerprint->mutable_machine_characteristics();
396
397  machine->set_operating_system_build(GetOperatingSystemVersion());
398  // We use the delta between the install time and the Unix epoch, in hours.
399  machine->set_browser_install_time_hours(
400      (install_time_ - base::Time::UnixEpoch()).InHours());
401  machine->set_utc_offset_ms(GetTimezoneOffset().InMilliseconds());
402  machine->set_browser_language(app_locale_);
403  machine->set_charset(charset_);
404  machine->set_user_agent(content::GetUserAgent(GURL()));
405  machine->set_ram(base::SysInfo::AmountOfPhysicalMemory());
406  machine->set_browser_build(version_);
407  machine->set_browser_feature(DialogTypeToBrowserFeature(dialog_type_));
408  AddFontsToFingerprint(*fonts_, machine);
409  AddPluginsToFingerprint(plugins_, machine);
410  AddAcceptLanguagesToFingerprint(accept_languages_, machine);
411  AddScreenInfoToFingerprint(screen_info_, machine);
412  AddCpuInfoToFingerprint(machine);
413  AddGpuInfoToFingerprint(machine);
414
415  // TODO(isherman): Record the user_and_device_name_hash.
416  // TODO(isherman): Record the partition size of the hard drives?
417
418  Fingerprint::TransientState* transient_state =
419      fingerprint->mutable_transient_state();
420  Fingerprint::Dimension* inner_window_size =
421      transient_state->mutable_inner_window_size();
422  inner_window_size->set_width(content_bounds_.width());
423  inner_window_size->set_height(content_bounds_.height());
424  Fingerprint::Dimension* outer_window_size =
425      transient_state->mutable_outer_window_size();
426  outer_window_size->set_width(window_bounds_.width());
427  outer_window_size->set_height(window_bounds_.height());
428
429  // TODO(isherman): Record network performance data, which is theoretically
430  // available to JS.
431
432  // TODO(isherman): Record more user behavior data.
433  if (geoposition_.error_code == content::Geoposition::ERROR_CODE_NONE) {
434    Fingerprint::UserCharacteristics::Location* location =
435        fingerprint->mutable_user_characteristics()->mutable_location();
436    location->set_altitude(geoposition_.altitude);
437    location->set_latitude(geoposition_.latitude);
438    location->set_longitude(geoposition_.longitude);
439    location->set_accuracy(geoposition_.accuracy);
440    location->set_time_in_ms(
441        (geoposition_.timestamp - base::Time::UnixEpoch()).InMilliseconds());
442  }
443
444  Fingerprint::Metadata* metadata = fingerprint->mutable_metadata();
445  metadata->set_timestamp_ms(
446      (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds());
447  metadata->set_obfuscated_gaia_id(obfuscated_gaia_id_);
448  metadata->set_fingerprinter_version(kFingerprinterVersion);
449
450  callback_.Run(fingerprint.Pass());
451}
452
453}  // namespace
454
455namespace internal {
456
457void GetFingerprintInternal(
458    uint64 obfuscated_gaia_id,
459    const gfx::Rect& window_bounds,
460    const gfx::Rect& content_bounds,
461    const WebKit::WebScreenInfo& screen_info,
462    const std::string& version,
463    const std::string& charset,
464    const std::string& accept_languages,
465    const base::Time& install_time,
466    DialogType dialog_type,
467    const std::string& app_locale,
468    const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) {
469  // Begin loading all of the data that we need to load asynchronously.
470  // This class is responsible for freeing its own memory.
471  new FingerprintDataLoader(obfuscated_gaia_id, window_bounds, content_bounds,
472                            screen_info, version, charset, accept_languages,
473                            install_time, dialog_type, app_locale, callback);
474}
475
476}  // namespace internal
477
478void GetFingerprint(
479    uint64 obfuscated_gaia_id,
480    const gfx::Rect& window_bounds,
481    const content::WebContents& web_contents,
482    const std::string& version,
483    const std::string& charset,
484    const std::string& accept_languages,
485    const base::Time& install_time,
486    DialogType dialog_type,
487    const std::string& app_locale,
488    const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) {
489  gfx::Rect content_bounds;
490  web_contents.GetView()->GetContainerBounds(&content_bounds);
491
492  WebKit::WebScreenInfo screen_info;
493  content::RenderWidgetHostView* host_view =
494      web_contents.GetRenderWidgetHostView();
495  if (host_view)
496    host_view->GetRenderWidgetHost()->GetWebScreenInfo(&screen_info);
497
498  internal::GetFingerprintInternal(
499      obfuscated_gaia_id, window_bounds, content_bounds, screen_info, version,
500      charset, accept_languages, install_time, dialog_type, app_locale,
501      callback);
502}
503
504}  // namespace risk
505}  // namespace autofill
506