Bug 1299784 - Include a hashed version of the device ID with the sync ping r?markh,bsmedberg
MozReview-Commit-ID: 3sPSeBNrF8z
--- 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".