Bug 1299784 - Include a hashed version of the device ID with the sync ping r=bsmedberg,markh
authorThom Chiovoloni <tchiovoloni@mozilla.com>
Wed, 07 Sep 2016 16:49:21 -0400
changeset 313834 0123be9d2c87873e5d32bfd4ba517efdb4f38e6f
parent 313833 cec9f17302dbc776fa3a43c3a3c81e7fee873f0f
child 313835 58846175232c221d1d6eccfdd9e26c1d41044f98
push id30698
push usercbook@mozilla.com
push dateWed, 14 Sep 2016 10:07:43 +0000
treeherdermozilla-central@501e27643a52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, markh
bugs1299784
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1299784 - Include a hashed version of the device ID with the sync ping r=bsmedberg,markh MozReview-Commit-ID: 3sPSeBNrF8z
services/crypto/modules/utils.js
services/sync/modules/browserid_identity.js
services/sync/modules/telemetry.js
services/sync/modules/util.js
services/sync/tests/unit/sync_ping_schema.json
toolkit/components/telemetry/docs/data/sync-ping.rst
--- a/services/crypto/modules/utils.js
+++ b/services/crypto/modules/utils.js
@@ -101,16 +101,23 @@ this.CryptoUtils = {
   sha1: function sha1(message) {
     return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
   },
 
   sha1Base32: function sha1Base32(message) {
     return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
   },
 
+  sha256(message) {
+    let hasher = Cc["@mozilla.org/security/hash;1"]
+                 .createInstance(Ci.nsICryptoHash);
+    hasher.init(hasher.SHA256);
+    return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher));
+  },
+
   /**
    * Produce an HMAC key object from a key string.
    */
   makeHMACKey: function makeHMACKey(str) {
     return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
   },
 
   /**
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -116,16 +116,20 @@ this.BrowserIDManager.prototype = {
 
   hashedUID() {
     if (!this._token) {
       throw new Error("hashedUID: Don't have token");
     }
     return this._token.hashed_fxa_uid
   },
 
+  deviceID() {
+    return this._signedInUser && this._signedInUser.deviceId;
+  },
+
   initialize: function() {
     for (let topic of OBSERVER_TOPICS) {
       Services.obs.addObserver(this, topic, false);
     }
     // and a background fetch of account data just so we can set this.account,
     // so we have a username available before we've actually done a login.
     // XXX - this is actually a hack just for tests and really shouldn't be
     // necessary. Also, you'd think it would be safe to allow this.account to
--- a/services/sync/modules/telemetry.js
+++ b/services/sync/modules/telemetry.js
@@ -199,16 +199,17 @@ class TelemetryRecord {
 
   toJSON() {
     let result = {
       when: this.when,
       uid: this.uid,
       took: this.took,
       failureReason: this.failureReason,
       status: this.status,
+      deviceID: this.deviceID,
     };
     let engines = [];
     for (let engine of this.engines) {
       engines.push(engine.toJSON());
     }
     if (engines.length > 0) {
       result.engines = engines;
     }
@@ -223,18 +224,26 @@ class TelemetryRecord {
       this.onEngineStop(this.currentEngine.name);
     }
     if (error) {
       this.failureReason = transformError(error);
     }
 
     try {
       this.uid = Weave.Service.identity.hashedUID();
+      let deviceID = Weave.Service.identity.deviceID();
+      if (deviceID) {
+        // Combine the raw device id with the metrics uid to create a stable
+        // unique identifier that can't be mapped back to the user's FxA
+        // identity without knowing the metrics HMAC key.
+        this.deviceID = Utils.sha256(deviceID + this.uid);
+      }
     } catch (e) {
       this.uid = "0".repeat(32);
+      this.deviceID = undefined;
     }
 
     // Check for engine statuses. -- We do this now, and not in engine.finished
     // to make sure any statuses that get set "late" are recorded
     for (let engine of this.engines) {
       let status = Status.engines[engine.name];
       if (status && status !== constants.ENGINE_SUCCEEDED) {
         engine.status = status;
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -47,16 +47,17 @@ this.Utils = {
 
   // Aliases from CryptoUtils.
   generateRandomBytes: CryptoUtils.generateRandomBytes,
   computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
   digestUTF8: CryptoUtils.digestUTF8,
   digestBytes: CryptoUtils.digestBytes,
   sha1: CryptoUtils.sha1,
   sha1Base32: CryptoUtils.sha1Base32,
+  sha256: CryptoUtils.sha256,
   makeHMACKey: CryptoUtils.makeHMACKey,
   makeHMACHasher: CryptoUtils.makeHMACHasher,
   hkdfExpand: CryptoUtils.hkdfExpand,
   pbkdf2Generate: CryptoUtils.pbkdf2Generate,
   deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
   getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
 
   /**
--- a/services/sync/tests/unit/sync_ping_schema.json
+++ b/services/sync/tests/unit/sync_ping_schema.json
@@ -21,16 +21,20 @@
       "required": ["when", "uid", "took"],
       "properties": {
         "didLogin": { "type": "boolean" },
         "when": { "type": "integer" },
         "uid": {
           "type": "string",
           "pattern": "^[0-9a-f]{32}$"
         },
+        "deviceID": {
+          "type": "string",
+          "pattern": "^[0-9a-f]{64}$"
+        },
         "status": {
           "type": "object",
           "anyOf": [
             { "required": ["sync"] },
             { "required": ["service"] }
           ],
           "additionalProperties": false,
           "properties": {
--- a/toolkit/components/telemetry/docs/data/sync-ping.rst
+++ b/toolkit/components/telemetry/docs/data/sync-ping.rst
@@ -20,16 +20,17 @@ Structure:
         version: 1,
         discarded: <integer count> // Number of syncs discarded -- left out if zero.
         why: <string>, // Why did we submit the ping? Either "shutdown" or "schedule".
         // Array of recorded syncs. The ping is not submitted if this would be empty
         syncs: [{
           when: <integer milliseconds since epoch>,
           took: <integer duration in milliseconds>,
           uid: <string>, // Hashed FxA unique ID, or string of 32 zeros.
+          deviceID: <string>, // Hashed FxA Device ID, hex string of 64 characters, not included if the user is not logged in.
           didLogin: <bool>, // Optional, is this the first sync after login? Excluded if we don't know.
           why: <string>, // Optional, why the sync occured, excluded if we don't know.
 
           // Optional, excluded if there was no error.
           failureReason: {
             name: <string>, // "httperror", "networkerror", "shutdownerror", etc.
             code: <integer>, // Only present for "httperror" and "networkerror".
             error: <string>, // Only present for "othererror" and "unexpectederror".