Bug 1465251 - use PrioEncoder to encode Telemetry values for pilot project r=kmag

Use PrioEncoder to encode a few already-included histograms, so we can compare results on the Telemetry server side.

Differential Revision: https://phabricator.services.mozilla.com/D5088

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Robert Helmer 2018-09-09 00:23:52 +00:00
parent 4b2a583e7c
commit 7d4c15f217
11 changed files with 167 additions and 54 deletions

View File

@ -1759,9 +1759,19 @@ pref("app.shield.optoutstudies.enabled", false);
pref("intl.multilingual.enabled", false);
// Prio preferences
// Only enable by default on Nightly.
// On platforms that do not build libprio, do not set these prefs at all, which gives us a way to detect support.
// Curve25519 public keys for Prio servers
#ifdef MOZ_LIBPRIO
pref("prio.publicKeyA", "35AC1C7576C7C6EDD7FED6BCFC337B34D48CB4EE45C86BEEFB40BD8875707733");
pref("prio.publicKeyB", "26E6674E65425B823F1F1D5F96E3BB3EF9E406EC7FBA7DEF8B08A35DD135AF50");
#endif
// Whether or not Prio-encoded Telemetry will be sent along with the main ping.
#if defined(NIGHTLY_BUILD) && defined(MOZ_LIBPRIO)
pref("prio.enabled", true);
#endif
#ifdef NIGHTLY_BUILD
pref("browser.fastblock.enabled", true);

View File

@ -45,6 +45,9 @@ for cdm in CONFIG['MOZ_EME_MODULES']:
if CONFIG['MOZ_GPSD']:
DEFINES['MOZ_GPSD'] = True
if CONFIG['MOZ_LIBPRIO']:
DEFINES['MOZ_LIBPRIO'] = True
# These files are specified in this moz.build to pick up DIST_SUBDIR as set in
# this directory, which is un-set in browser/app.
JS_PREFERENCE_PP_FILES += [

View File

@ -7,13 +7,13 @@
[ChromeOnly, Exposed=(Window,System)]
namespace PrioEncoder {
[Throws, NewObject]
Promise<PrioEncodedData> encode(ByteString batchID, PrioParams params);
PrioEncodedData encode(ByteString batchID, PrioParams params);
};
dictionary PrioParams {
required boolean startupCrashDetected;
required boolean safeModeUsage;
required boolean browserIsUserDefault;
required boolean newTabPageEnabled;
required boolean pdfViewerUsed;
};
dictionary PrioEncodedData {

View File

@ -5,10 +5,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/TextUtils.h"
#include "mozilla/dom/ToJSValue.h"
#include "PrioEncoder.h"
namespace mozilla {
@ -35,15 +38,17 @@ PrioEncoder::~PrioEncoder()
Prio_clear();
}
/* static */
already_AddRefed<Promise>
PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const PrioParams& aPrioParams, ErrorResult& aRv)
/* static */ void
PrioEncoder::Encode(GlobalObject& aGlobal,
const nsCString& aBatchID,
const PrioParams& aPrioParams,
RootedDictionary<PrioEncodedData>& aData,
ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
return;
}
SECStatus prio_rv = SECSuccess;
@ -55,18 +60,20 @@ PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const Prio
Prio_init();
nsresult rv;
nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyA;
nsresult rv = Preferences::GetCString("prio.publicKeyA", prioKeyA);
rv = Preferences::GetCString("prio.publicKeyA", prioKeyA);
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
return;
}
nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyB;
rv = Preferences::GetCString("prio.publicKeyB", prioKeyB);
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
return;
}
// Check that both public keys are of the right length
@ -74,35 +81,41 @@ PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const Prio
if (!PrioEncoder::IsValidHexPublicKey(prioKeyA)
|| !PrioEncoder::IsValidHexPublicKey(prioKeyB)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
return;
}
prio_rv = PublicKey_import_hex(&sPublicKeyA, reinterpret_cast<const unsigned char*>(prioKeyA.BeginReading()), CURVE25519_KEY_LEN_HEX);
prio_rv = PublicKey_import_hex(&sPublicKeyA,
reinterpret_cast<const unsigned char*>(prioKeyA.BeginReading()),
CURVE25519_KEY_LEN_HEX);
if (prio_rv != SECSuccess) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
return;
}
prio_rv = PublicKey_import_hex(&sPublicKeyB, reinterpret_cast<const unsigned char*>(prioKeyB.BeginReading()), CURVE25519_KEY_LEN_HEX);
prio_rv = PublicKey_import_hex(&sPublicKeyB,
reinterpret_cast<const unsigned char*>(prioKeyB.BeginReading()),
CURVE25519_KEY_LEN_HEX);
if (prio_rv != SECSuccess) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
return;
}
}
RefPtr<Promise> promise = Promise::Create(global, aRv);
bool dataItems[] = {
aPrioParams.mStartupCrashDetected,
aPrioParams.mSafeModeUsage,
aPrioParams.mBrowserIsUserDefault
aPrioParams.mBrowserIsUserDefault,
aPrioParams.mNewTabPageEnabled,
aPrioParams.mPdfViewerUsed,
};
PrioConfig prioConfig = PrioConfig_new(mozilla::ArrayLength(dataItems), sPublicKeyA, sPublicKeyB, reinterpret_cast<const unsigned char*>(aBatchID.BeginReading()), aBatchID.Length());
PrioConfig prioConfig = PrioConfig_new(mozilla::ArrayLength(dataItems),
sPublicKeyA,
sPublicKeyB,
reinterpret_cast<const unsigned char*>(aBatchID.BeginReading()),
aBatchID.Length());
if (!prioConfig) {
promise->MaybeReject(NS_ERROR_FAILURE);
return promise.forget();
aRv.Throw(NS_ERROR_FAILURE);
return;
}
auto configGuard = MakeScopeExit([&] {
@ -114,50 +127,54 @@ PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const Prio
unsigned char* forServerB = nullptr;
unsigned int lenB = 0;
prio_rv = PrioClient_encode(prioConfig, dataItems, &forServerA, &lenA, &forServerB, &lenB);
// Package the data into the dictionary
PrioEncodedData data;
prio_rv = PrioClient_encode(prioConfig,
dataItems,
&forServerA,
&lenA,
&forServerB,
&lenB);
nsTArray<uint8_t> arrayForServerA;
nsTArray<uint8_t> arrayForServerB;
if (!arrayForServerA.AppendElements(reinterpret_cast<uint8_t*>(forServerA), lenA, fallible)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
if (!arrayForServerA.AppendElements(reinterpret_cast<uint8_t*>(forServerA),
lenA,
fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
free(forServerA);
if (!arrayForServerB.AppendElements(reinterpret_cast<uint8_t*>(forServerB), lenB, fallible)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
if (!arrayForServerB.AppendElements(reinterpret_cast<uint8_t*>(forServerB),
lenB,
fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return ;
}
free(forServerB);
if (prio_rv != SECSuccess) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
JS::Rooted<JS::Value> valueA(aGlobal.Context());
if (!ToJSValue(aGlobal.Context(), TypedArrayCreator<Uint8Array>(arrayForServerA), &valueA)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
data.mA.Construct().Init(&valueA.toObject());
aData.mA.Construct().Init(&valueA.toObject());
JS::Rooted<JS::Value> valueB(aGlobal.Context());
if (!ToJSValue(aGlobal.Context(), TypedArrayCreator<Uint8Array>(arrayForServerB), &valueB)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
}
data.mB.Construct().Init(&valueB.toObject());
if (prio_rv != SECSuccess) {
promise->MaybeReject(NS_ERROR_FAILURE);
return promise.forget();
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
promise->MaybeResolve(data);
return promise.forget();
aData.mB.Construct().Init(&valueB.toObject());
}
bool

View File

@ -21,8 +21,12 @@ class PrioEncoder
public:
NS_INLINE_DECL_REFCOUNTING(PrioEncoder)
static already_AddRefed<Promise>
Encode(GlobalObject& aGlobal, const nsCString& aBatchID, const PrioParams& aPrioParams, ErrorResult& aRv);
static
void Encode(GlobalObject& aGlobal,
const nsCString& aBatchID,
const PrioParams& aPrioParams,
RootedDictionary<PrioEncodedData>& aData,
ErrorResult& aRv);
private:
PrioEncoder();

View File

@ -5952,3 +5952,8 @@ pref("dom.datatransfer.mozAtAPIs", false);
#else
pref("dom.datatransfer.mozAtAPIs", true);
#endif
// Whether or not Prio is supported on this platform.
#ifdef MOZ_LIBPRIO
pref("prio.enabled", false);
#endif

View File

@ -677,6 +677,24 @@ Structure:
...
],
Prio
----
This section contains experimental data encoded with a basic version of the Prio system for private aggregation.
See `the Prio paper <https://crypto.stanford.edu/prio/>`_ and `the libprio Github repo <https://github.com/mozilla/libprio>`_
for more information.
Prio splits data packets into two "shares", signed for different servers that will do the decryption+decoding and
aggregation. We call these "Server A" and "Server B", represented as `a` and `b` keys in `payload.prio`.
Structure:
.. code-block:: js
"prio": {
a: ... // Uint8Array containing data signed for Server A
b: ... // Uint8Array containing data signed for Server B
}
Version History
===============

View File

@ -87,6 +87,9 @@ const TOPIC_CYCLE_COLLECTOR_BEGIN = "cycle-collector-begin";
// How long to wait in millis for all the child memory reports to come in
const TOTAL_MEMORY_COLLECTOR_TIMEOUT = 200;
// Control whether Telemetry data should be encrypted with Prio.
const PRIO_ENABLED_PREF = "prio.enabled";
var gLastMemoryPoll = null;
var gWasDebuggerAttached = false;
@ -1168,6 +1171,11 @@ var Impl = {
payloadObj.info = info;
// Collect Prio-encoded measurements.
if (Services.prefs.getBoolPref(PRIO_ENABLED_PREF, false)) {
payloadObj.prio = protect(() => this._prioEncode());
}
// Add extended set measurements for chrome process.
if (Telemetry.canRecordExtended) {
payloadObj.slowSQL = protect(() => Telemetry.slowSQL);
@ -1947,4 +1955,44 @@ var Impl = {
this._newProfilePingSent = true;
return TelemetryStorage.saveSessionData(this._getSessionDataObject());
},
/**
* Encodes data for experimental Prio pilot project.
*
* @return {Object} An object containing Prio-encoded data.
*/
_prioEncode() {
// First, map the Telemetry histogram names to the params PrioEncoder.encode() expects.
const prioEncodedHistograms = {
"BROWSER_IS_USER_DEFAULT": "browserIsUserDefault",
"NEWTAB_PAGE_ENABLED": "newTabPageEnabled",
"PDF_VIEWER_USED": "pdfViewerUsed",
};
// Build list of Prio parameters, using the first value recorded in each histogram.
let prioParams = {};
for (const [histogramName, prioName] of Object.entries(prioEncodedHistograms)) {
try {
const histogram = Telemetry.getHistogramById(histogramName);
const firstCount = Boolean(histogram.snapshot().sum);
prioParams[prioName] = firstCount;
} catch (ex) {
this._log.error(ex);
}
}
// Prio encode the data and add to payload.
const batchID = Policy.now();
let prioEncodedData;
try {
prioEncodedData = PrioEncoder.encode(batchID, prioParams);
} catch (ex) {
this._log.error(ex);
}
return prioEncodedData;
},
};

View File

@ -403,6 +403,11 @@ function checkPayload(payload, reason, successfulPings) {
Assert.ok("processes" in payload, "The payload must have a processes section.");
Assert.ok("parent" in payload.processes, "There must be at least a parent process.");
if (Services.prefs.getBoolPref("prio.enabled", false)) {
Assert.ok("prio" in payload, "The payload must have a prio section.");
}
checkScalars(payload.processes);
}

View File

@ -1070,10 +1070,12 @@ set_define('MOZ_LAUNCHER_PROCESS', launcher)
# Prio
# ==============================================================
@depends(c_compiler)
def libprio(info):
@depends(c_compiler, target)
def libprio(info, target):
if info:
if info.type in ('msvc',):
# TODO - re-enable Windows when bug 1489691 is fixed.
# Note that we will probably never support MSVC however.
if info.type in ('msvc',) or target.os in ('WINNT',):
return None
return True

View File

@ -55,6 +55,7 @@ module.exports = {
// Specific to Firefox (Chrome code only).
"PlacesObservers": false,
"PlacesWeakCallbackWrapper": false,
"PrioEncoder": false,
// Specific to Firefox (Chrome code only).
"SharedArrayBuffer": false,
"SimpleGestureEvent": false,