Bug 1810790 - Remove Origin Telemetry. r=chutten,timhuang

Differential Revision: https://phabricator.services.mozilla.com/D167779
This commit is contained in:
Simon Friedberger 2023-02-02 11:57:11 +00:00
parent cbfcf30602
commit 19d608932e
65 changed files with 4 additions and 9085 deletions

View File

@ -22,7 +22,6 @@ if not CONFIG["MOZ_SYSTEM_JPEG"]:
external_dirs += ["media/libjpeg"]
DIRS += [
"/third_party/prio",
"/third_party/msgpack",
"/third_party/sipcc",
]

View File

@ -1,20 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
[ChromeOnly, Exposed=Window]
namespace PrioEncoder {
[Throws, NewObject]
PrioEncodedData encode(ByteString batchID, PrioParams params);
};
dictionary PrioParams {
required sequence<boolean> booleans;
};
dictionary PrioEncodedData {
Uint8Array a;
Uint8Array b;
};

View File

@ -106,7 +106,3 @@ WEBIDL_FILES += [
"Glean.webidl",
"GleanPings.webidl",
]
WEBIDL_FILES += [
"PrioEncoder.webidl",
]

View File

@ -1443,7 +1443,6 @@ void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
if (mDocumentURI && (net::SchemeIsHTTP(mDocumentURI) ||
net::SchemeIsHTTPS(mDocumentURI))) {
GetContentBlockingLog()->ReportOrigins();
GetContentBlockingLog()->ReportEmailTrackingLog(DocumentPrincipal());
}
}

View File

@ -106,7 +106,6 @@ DIRS += [
"simpledb",
"reporting",
"localstorage",
"prio",
"l10n",
"origin-trials",
"webscheduling",

View File

@ -1,227 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/TextUtils.h"
#include "mozilla/dom/ToJSValue.h"
#include "mprio.h"
#include "PrioEncoder.h"
namespace mozilla::dom {
/* static */
StaticRefPtr<PrioEncoder> PrioEncoder::sSingleton;
/* static */
PublicKey PrioEncoder::sPublicKeyA = nullptr;
/* static */
PublicKey PrioEncoder::sPublicKeyB = nullptr;
// Production keys from bug 1552315 comment#3
/* static */
const char* kDefaultKeyA =
"E780C1A9C50E3FC5A9B39469FCC92D62D2527BAE6AF76BBDEF128883FA400846";
/* static */
const char* kDefaultKeyB =
"F992B575840AEC202289FBF99D6C04FB2A37B1DA1CDEB1DF8036E1340D46C561";
PrioEncoder::PrioEncoder() = default;
PrioEncoder::~PrioEncoder() {
if (sPublicKeyA) {
PublicKey_clear(sPublicKeyA);
sPublicKeyA = nullptr;
}
if (sPublicKeyB) {
PublicKey_clear(sPublicKeyB);
sPublicKeyB = nullptr;
}
Prio_clear();
}
/* 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;
}
nsCString aResult;
nsCString bResult;
nsresult rv = PrioEncoder::EncodeNative(aBatchID, aPrioParams.mBooleans,
aResult, bResult);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
nsTArray<uint8_t> arrayForServerA;
nsTArray<uint8_t> arrayForServerB;
if (!arrayForServerA.AppendElements(
reinterpret_cast<const uint8_t*>(aResult.BeginReading()),
aResult.Length(), fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
if (!arrayForServerB.AppendElements(
reinterpret_cast<const uint8_t*>(bResult.BeginReading()),
bResult.Length(), fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
JS::Rooted<JS::Value> valueA(aGlobal.Context());
if (!ToJSValue(aGlobal.Context(),
TypedArrayCreator<Uint8Array>(arrayForServerA), &valueA)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
aData.mA.Construct().Init(&valueA.toObject());
JS::Rooted<JS::Value> valueB(aGlobal.Context());
if (!ToJSValue(aGlobal.Context(),
TypedArrayCreator<Uint8Array>(arrayForServerB), &valueB)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
aData.mB.Construct().Init(&valueB.toObject());
}
/* static */
nsresult PrioEncoder::EncodeNative(const nsCString& aBatchID,
const nsTArray<bool>& aData,
nsCString& aResult, nsCString& bResult) {
SECStatus prio_rv = SECSuccess;
nsresult rv = PrioEncoder::LazyInitSingleton();
if (NS_FAILED(rv)) {
return rv;
}
if (aData.Length() > gNumBooleans) {
return NS_ERROR_INVALID_ARG;
}
PrioConfig prioConfig = PrioConfig_new(
aData.Length(), sPublicKeyA, sPublicKeyB,
reinterpret_cast<const unsigned char*>(aBatchID.BeginReading()),
aBatchID.Length());
if (!prioConfig) {
return NS_ERROR_FAILURE;
}
auto configGuard = MakeScopeExit([&] { PrioConfig_clear(prioConfig); });
unsigned char* forServerA = nullptr;
unsigned int lenA = 0;
unsigned char* forServerB = nullptr;
unsigned int lenB = 0;
prio_rv = PrioClient_encode(prioConfig, aData.Elements(), &forServerA, &lenA,
&forServerB, &lenB);
aResult.Adopt(reinterpret_cast<char*>(forServerA), lenA);
bResult.Adopt(reinterpret_cast<char*>(forServerB), lenB);
if (prio_rv != SECSuccess) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
bool PrioEncoder::IsValidHexPublicKey(mozilla::Span<const char> aStr) {
if (aStr.Length() != CURVE25519_KEY_LEN_HEX) {
return false;
}
for (auto c : aStr) {
if (!IsAsciiHexDigit(c)) {
return false;
}
}
return true;
}
/* static */
nsresult PrioEncoder::SetKeys(const char* aKeyA, const char* aKeyB) {
nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyA;
if (aKeyA == nullptr) {
prioKeyA = kDefaultKeyA;
} else {
prioKeyA = aKeyA;
}
nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyB;
if (aKeyB == nullptr) {
prioKeyB = kDefaultKeyB;
} else {
prioKeyB = aKeyB;
}
// Check that both public keys are of the right length
// and contain only hex digits 0-9a-fA-f
if (!PrioEncoder::IsValidHexPublicKey(prioKeyA) ||
!PrioEncoder::IsValidHexPublicKey(prioKeyB)) {
return NS_ERROR_UNEXPECTED;
}
SECStatus prio_rv = SECSuccess;
prio_rv = Prio_init();
if (prio_rv != SECSuccess) {
return NS_ERROR_UNEXPECTED;
}
prio_rv = PublicKey_import_hex(
&sPublicKeyA,
reinterpret_cast<const unsigned char*>(prioKeyA.BeginReading()),
CURVE25519_KEY_LEN_HEX);
if (prio_rv != SECSuccess) {
return NS_ERROR_UNEXPECTED;
}
prio_rv = PublicKey_import_hex(
&sPublicKeyB,
reinterpret_cast<const unsigned char*>(prioKeyB.BeginReading()),
CURVE25519_KEY_LEN_HEX);
if (prio_rv != SECSuccess) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
/* static */
nsresult PrioEncoder::LazyInitSingleton() {
if (!sSingleton) {
// Init to the default keys.
nsresult rv = PrioEncoder::SetKeys();
if (!NS_SUCCEEDED(rv)) {
return rv;
}
sSingleton = new PrioEncoder();
ClearOnShutdown(&sSingleton);
}
return NS_OK;
}
} // namespace mozilla::dom

View File

@ -1,58 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_PrioEncoder_h
#define mozilla_dom_PrioEncoder_h
#include "mozilla/dom/PrioEncoderBinding.h"
#include "mozilla/dom/RootedDictionary.h"
class nsIGlobalObject;
using SECKEYPublicKey = struct SECKEYPublicKeyStr;
using PublicKey = SECKEYPublicKey*;
namespace mozilla::dom {
class PrioEncoder {
public:
NS_INLINE_DECL_REFCOUNTING(PrioEncoder)
// C++ API
static nsresult EncodeNative(const nsCString& aBatchID,
const nsTArray<bool>& aData, nsCString& aResult,
nsCString& bResult);
// DOM API
static void Encode(GlobalObject& aGlobal, const nsCString& aBatchID,
const PrioParams& aPrioParams,
RootedDictionary<PrioEncodedData>& aData,
ErrorResult& aRv);
// maximum number of booleans that can be prio-encoded in a single batch.
const static uint32_t gNumBooleans = 2046;
// Set (or, by passing nullptrs, reset) the keys used to encode the payloads.
static nsresult SetKeys(const char* aKeyA = nullptr,
const char* aKeyB = nullptr);
private:
PrioEncoder();
~PrioEncoder();
static PublicKey sPublicKeyA;
static PublicKey sPublicKeyB;
static StaticRefPtr<PrioEncoder> sSingleton;
static bool IsValidHexPublicKey(mozilla::Span<const char>);
static nsresult LazyInitSingleton();
};
} // namespace mozilla::dom
#endif // mozilla_dom_PrioEncoder_h

View File

@ -1,22 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
LOCAL_INCLUDES += ["/third_party/msgpack/include"]
EXPORTS.mozilla.dom += [
"PrioEncoder.h",
]
UNIFIED_SOURCES += [
"PrioEncoder.cpp",
]
TEST_DIRS += ["test/gtest"]
FINAL_LIBRARY = "xul"

View File

@ -1,307 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "jsapi.h"
#include "PrioEncoder.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ErrorResult.h"
#include "mprio.h"
TEST(PrioEncoder, BadPublicKeys)
{
mozilla::ErrorResult rv;
rv = mozilla::dom::PrioEncoder::SetKeys("badA", "badB");
ASSERT_TRUE(rv.Failed());
rv = mozilla::ErrorResult();
}
TEST(PrioEncoder, BooleanLimitExceeded)
{
mozilla::dom::AutoJSAPI jsAPI;
ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope()));
JSContext* cx = jsAPI.cx();
mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope());
nsCString batchID = "abc123"_ns;
mozilla::dom::PrioParams prioParams;
FallibleTArray<bool> sequence;
const int ndata = mozilla::dom::PrioEncoder::gNumBooleans + 1;
const int seed = time(nullptr);
srand(seed);
for (int i = 0; i < ndata; i++) {
// Arbitrary data)
*(sequence.AppendElement(mozilla::fallible)) = rand() % 2;
}
ASSERT_TRUE(prioParams.mBooleans.Assign(sequence));
mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(
cx);
mozilla::ErrorResult rv;
mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams,
prioEncodedData, rv);
ASSERT_TRUE(rv.Failed());
// Reset error result so test runner does not fail.
rv = mozilla::ErrorResult();
}
TEST(PrioEncoder, VerifyFull)
{
SECStatus prioRv = SECSuccess;
PublicKey pkA = nullptr;
PublicKey pkB = nullptr;
PrivateKey skA = nullptr;
PrivateKey skB = nullptr;
PrioConfig cfg = nullptr;
PrioServer sA = nullptr;
PrioServer sB = nullptr;
PrioVerifier vA = nullptr;
PrioVerifier vB = nullptr;
PrioPacketVerify1 p1A = nullptr;
PrioPacketVerify1 p1B = nullptr;
PrioPacketVerify2 p2A = nullptr;
PrioPacketVerify2 p2B = nullptr;
PrioTotalShare tA = nullptr;
PrioTotalShare tB = nullptr;
unsigned char* forServerA = nullptr;
unsigned char* forServerB = nullptr;
const int seed = time(nullptr);
srand(seed);
// Number of different boolean data fields we collect.
const int ndata = 3;
unsigned char batchIDStr[32];
memset(batchIDStr, 0, sizeof batchIDStr);
snprintf((char*)batchIDStr, sizeof batchIDStr, "%d", rand());
bool dataItems[ndata];
unsigned long long output[ndata];
// The client's data submission is an arbitrary boolean vector.
for (int i = 0; i < ndata; i++) {
// Arbitrary data
dataItems[i] = rand() % 2;
}
// Initialize NSS random number generator.
prioRv = Prio_init();
ASSERT_TRUE(prioRv == SECSuccess);
// Generate keypairs for servers
prioRv = Keypair_new(&skA, &pkA);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = Keypair_new(&skB, &pkB);
ASSERT_TRUE(prioRv == SECSuccess);
// Export public keys to hex and print to stdout
const int keyLength = CURVE25519_KEY_LEN_HEX + 1;
unsigned char pkHexA[keyLength];
unsigned char pkHexB[keyLength];
prioRv = PublicKey_export_hex(pkA, pkHexA, keyLength);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PublicKey_export_hex(pkB, pkHexB, keyLength);
ASSERT_TRUE(prioRv == SECSuccess);
// Use the default configuration parameters.
cfg = PrioConfig_new(ndata, pkA, pkB, batchIDStr, strlen((char*)batchIDStr));
ASSERT_TRUE(cfg != nullptr);
PrioPRGSeed serverSecret;
prioRv = PrioPRGSeed_randomize(&serverSecret);
ASSERT_TRUE(prioRv == SECSuccess);
// Initialize two server objects. The role of the servers need not
// be symmetric. In a deployment, we envision that:
// * Server A is the main telemetry server that is always online.
// Clients send their encrypted data packets to Server A and
// Server A stores them.
// * Server B only comes online when the two servers want to compute
// the final aggregate statistics.
sA = PrioServer_new(cfg, PRIO_SERVER_A, skA, serverSecret);
ASSERT_TRUE(sA != nullptr);
sB = PrioServer_new(cfg, PRIO_SERVER_B, skB, serverSecret);
ASSERT_TRUE(sB != nullptr);
// Initialize empty verifier objects
vA = PrioVerifier_new(sA);
ASSERT_TRUE(vA != nullptr);
vB = PrioVerifier_new(sB);
ASSERT_TRUE(vB != nullptr);
// Initialize shares of final aggregate statistics
tA = PrioTotalShare_new();
ASSERT_TRUE(tA != nullptr);
tB = PrioTotalShare_new();
ASSERT_TRUE(tB != nullptr);
// Initialize shares of verification packets
p1A = PrioPacketVerify1_new();
ASSERT_TRUE(p1A != nullptr);
p1B = PrioPacketVerify1_new();
ASSERT_TRUE(p1B != nullptr);
p2A = PrioPacketVerify2_new();
ASSERT_TRUE(p2A != nullptr);
p2B = PrioPacketVerify2_new();
ASSERT_TRUE(p2B != nullptr);
// I. CLIENT DATA SUBMISSION.
//
// Read in the client data packets
unsigned int aLen = 0, bLen = 0;
mozilla::dom::AutoJSAPI jsAPI;
ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope()));
JSContext* cx = jsAPI.cx();
mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope());
nsCString batchID;
batchID = (char*)(batchIDStr);
mozilla::dom::PrioParams prioParams;
FallibleTArray<bool> sequence;
*(sequence.AppendElement(mozilla::fallible)) = dataItems[0];
*(sequence.AppendElement(mozilla::fallible)) = dataItems[1];
*(sequence.AppendElement(mozilla::fallible)) = dataItems[2];
ASSERT_TRUE(prioParams.mBooleans.Assign(sequence));
mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(
cx);
mozilla::ErrorResult rv;
rv =
mozilla::dom::PrioEncoder::SetKeys(reinterpret_cast<const char*>(pkHexA),
reinterpret_cast<const char*>(pkHexB));
ASSERT_FALSE(rv.Failed());
mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams,
prioEncodedData, rv);
ASSERT_FALSE(rv.Failed());
prioEncodedData.mA.Value().ComputeState();
prioEncodedData.mB.Value().ComputeState();
forServerA = prioEncodedData.mA.Value().Data();
forServerB = prioEncodedData.mB.Value().Data();
aLen = prioEncodedData.mA.Value().Length();
bLen = prioEncodedData.mB.Value().Length();
// II. VALIDATION PROTOCOL. (at servers)
//
// The servers now run a short 2-step protocol to check each
// client's packet:
// 1) Servers A and B broadcast one message (PrioPacketVerify1)
// to each other.
// 2) Servers A and B broadcast another message (PrioPacketVerify2)
// to each other.
// 3) Servers A and B can both determine whether the client's data
// submission is well-formed (in which case they add it to their
// running total of aggregate statistics) or ill-formed
// (in which case they ignore it).
// These messages must be sent over an authenticated channel, so
// that each server is assured that every received message came
// from its peer.
// Set up a Prio verifier object.
prioRv = PrioVerifier_set_data(vA, forServerA, aLen);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PrioVerifier_set_data(vB, forServerB, bLen);
ASSERT_TRUE(prioRv == SECSuccess);
// Both servers produce a packet1. Server A sends p1A to Server B
// and vice versa.
prioRv = PrioPacketVerify1_set_data(p1A, vA);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PrioPacketVerify1_set_data(p1B, vB);
ASSERT_TRUE(prioRv == SECSuccess);
// Both servers produce a packet2. Server A sends p2A to Server B
// and vice versa.
prioRv = PrioPacketVerify2_set_data(p2A, vA, p1A, p1B);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PrioPacketVerify2_set_data(p2B, vB, p1A, p1B);
ASSERT_TRUE(prioRv == SECSuccess);
// Using p2A and p2B, the servers can determine whether the request
// is valid. (In fact, only Server A needs to perform this
// check, since Server A can just tell Server B whether the check
// succeeded or failed.)
prioRv = PrioVerifier_isValid(vA, p2A, p2B);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PrioVerifier_isValid(vB, p2A, p2B);
ASSERT_TRUE(prioRv == SECSuccess);
// If we get here, the client packet is valid, so add it to the aggregate
// statistic counter for both servers.
prioRv = PrioServer_aggregate(sA, vA);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PrioServer_aggregate(sB, vB);
ASSERT_TRUE(prioRv == SECSuccess);
// The servers repeat the steps above for each client submission.
// III. PRODUCTION OF AGGREGATE STATISTICS.
//
// After collecting aggregates from MANY clients, the servers can compute
// their shares of the aggregate statistics.
//
// Server B can send tB to Server A.
prioRv = PrioTotalShare_set_data(tA, sA);
ASSERT_TRUE(prioRv == SECSuccess);
prioRv = PrioTotalShare_set_data(tB, sB);
ASSERT_TRUE(prioRv == SECSuccess);
// Once Server A has tA and tB, it can learn the aggregate statistics
// in the clear.
prioRv = PrioTotalShare_final(cfg, output, tA, tB);
ASSERT_TRUE(prioRv == SECSuccess);
for (int i = 0; i < ndata; i++) {
ASSERT_TRUE(output[i] == dataItems[i]);
}
PrioTotalShare_clear(tA);
PrioTotalShare_clear(tB);
PrioPacketVerify2_clear(p2A);
PrioPacketVerify2_clear(p2B);
PrioPacketVerify1_clear(p1A);
PrioPacketVerify1_clear(p1B);
PrioVerifier_clear(vA);
PrioVerifier_clear(vB);
PrioServer_clear(sA);
PrioServer_clear(sB);
PrioConfig_clear(cfg);
PublicKey_clear(pkA);
PublicKey_clear(pkB);
PrivateKey_clear(skA);
PrivateKey_clear(skB);
Prio_clear();
}

View File

@ -1,17 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
"TestPrioEncoder.cpp",
]
LOCAL_INCLUDES += [
"/dom/prio",
"/third_party/msgpack/include",
"/third_party/prio/include",
]
FINAL_LIBRARY = "xul-gtest"

View File

@ -46,9 +46,6 @@ with Files('webkit/**'):
with Files('xsimd/**'):
BUG_COMPONENT = ('Firefox Build System', 'General')
with Files('prio/**'):
BUG_COMPONENT = ('Firefox Build System', 'General')
with Files('msgpack/**'):
BUG_COMPONENT = ('Firefox Build System', 'General')

View File

@ -1,17 +0,0 @@
This directory contains the Prio source from the upstream repo:
https://github.com/mozilla/libprio
Current version: 1.6 [commit 52643cefe6662b4099e16a40a057cb60651ab001]
UPDATING:
Our in-tree copy of Prio does not depend on any generated files from the
upstream build system. Therefore, it should be sufficient to simply overwrite
the in-tree files one the updated ones from upstream to perform updates.
To simplify this, the in-tree copy can be updated by running
sh update.sh
from within the third_party/libprio directory.
If the collection of source files changes, manual updates to moz.build may be
needed as we don't use the upstream makefiles.

View File

@ -1,304 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __PRIO_H__
#define __PRIO_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <blapit.h>
#include <msgpack.h>
#include <pk11pub.h>
#include <seccomon.h>
#include <stdbool.h>
#include <stddef.h>
/* Seed for a pseudo-random generator (PRG). */
#define PRG_SEED_LENGTH AES_128_KEY_LENGTH
typedef unsigned char PrioPRGSeed[PRG_SEED_LENGTH];
/* Length of a raw curve25519 public key, in bytes. */
#define CURVE25519_KEY_LEN 32
/* Length of a hex-encoded curve25519 public key, in bytes. */
#define CURVE25519_KEY_LEN_HEX 64
/*
* Type for each of the two servers.
*/
typedef enum { PRIO_SERVER_A, PRIO_SERVER_B } PrioServerId;
/*
* Opaque types
*/
typedef struct prio_config* PrioConfig;
typedef const struct prio_config* const_PrioConfig;
typedef struct prio_server* PrioServer;
typedef const struct prio_server* const_PrioServer;
typedef struct prio_verifier* PrioVerifier;
typedef const struct prio_verifier* const_PrioVerifier;
typedef struct prio_packet_verify1* PrioPacketVerify1;
typedef const struct prio_packet_verify1* const_PrioPacketVerify1;
typedef struct prio_packet_verify2* PrioPacketVerify2;
typedef const struct prio_packet_verify2* const_PrioPacketVerify2;
typedef struct prio_total_share* PrioTotalShare;
typedef const struct prio_total_share* const_PrioTotalShare;
typedef SECKEYPublicKey* PublicKey;
typedef const SECKEYPublicKey* const_PublicKey;
typedef SECKEYPrivateKey* PrivateKey;
typedef const SECKEYPrivateKey* const_PrivateKey;
/*
* Initialize and clear random number generator state.
* You must call Prio_init() before using the library.
* To avoid memory leaks, call Prio_clear() afterwards.
*/
SECStatus Prio_init();
void Prio_clear();
/*
* PrioConfig holds the system parameters. The two relevant things determined
* by the config object are:
* (1) the number of data fields we are collecting, and
* (2) the modulus we use for modular arithmetic.
* The default configuration uses an 87-bit modulus.
*
* The value `nFields` must be in the range `0 < nFields <= max`, where `max`
* is the value returned by the function `PrioConfig_maxDataFields()` below.
*
* The `batch_id` field specifies which "batch" of aggregate statistics we are
* computing. For example, if the aggregate statistics are computed every 24
* hours, the `batch_id` might be set to an encoding of the date. The clients
* and servers must all use the same `batch_id` for each run of the protocol.
* Each set of aggregate statistics should use a different `batch_id`.
*
* `PrioConfig_new` does not keep a pointer to the `batch_id` string that the
* caller passes in, so you may free the `batch_id` string as soon as
* `PrioConfig_new` returns.
*/
PrioConfig PrioConfig_new(int nFields, PublicKey serverA, PublicKey serverB,
const unsigned char* batchId,
unsigned int batchIdLen);
void PrioConfig_clear(PrioConfig cfg);
int PrioConfig_numDataFields(const_PrioConfig cfg);
/*
* Return the maximum number of data fields that the implementation supports.
*/
int PrioConfig_maxDataFields(void);
/*
* Create a PrioConfig object with no encryption keys. This routine is
* useful for testing, but PrioClient_encode() will always fail when used with
* this config.
*/
PrioConfig PrioConfig_newTest(int nFields);
/*
* We use the PublicKey and PrivateKey objects for public-key encryption. Each
* Prio server has an associated public key, and the clients use these keys to
* encrypt messages to the servers.
*/
SECStatus Keypair_new(PrivateKey* pvtkey, PublicKey* pubkey);
/*
* Import a new curve25519 public/private key from the raw bytes given. When
* importing a private key, you must pass in the corresponding public key as
* well. The byte arrays given as input should be of length
* `CURVE25519_KEY_LEN`.
*
* These functions will allocate a new `PublicKey`/`PrivateKey` object, which
* the caller must free using `PublicKey_clear`/`PrivateKey_clear`.
*/
SECStatus PublicKey_import(PublicKey* pk, const unsigned char* data,
unsigned int dataLen);
SECStatus PrivateKey_import(PrivateKey* sk, const unsigned char* privData,
unsigned int privDataLen,
const unsigned char* pubData,
unsigned int pubDataLen);
/*
* Import a new curve25519 public/private key from a hex string that contains
* only the characters 0-9a-fA-F.
*
* The hex strings passed in must each be of length `CURVE25519_KEY_LEN_HEX`.
* These functions will allocate a new `PublicKey`/`PrivateKey` object, which
* the caller must free using `PublicKey_clear`/`PrivateKey_clear`.
*/
SECStatus PublicKey_import_hex(PublicKey* pk, const unsigned char* hexData,
unsigned int dataLen);
SECStatus PrivateKey_import_hex(PrivateKey* sk,
const unsigned char* privHexData,
unsigned int privDataLen,
const unsigned char* pubHexData,
unsigned int pubDataLen);
/*
* Export a curve25519 key as a raw byte-array.
*
* The output buffer `data` must have length exactly `CURVE25519_KEY_LEN`.
*/
SECStatus PublicKey_export(const_PublicKey pk, unsigned char* data,
unsigned int dataLen);
SECStatus PrivateKey_export(PrivateKey sk, unsigned char* data,
unsigned int dataLen);
/*
* Export a curve25519 key as a NULL-terminated hex string.
*
* The output buffer `data` must have length exactly `CURVE25519_KEY_LEN_HEX +
* 1`.
*/
SECStatus PublicKey_export_hex(const_PublicKey pk, unsigned char* data,
unsigned int dataLen);
SECStatus PrivateKey_export_hex(PrivateKey sk, unsigned char* data,
unsigned int dataLen);
void PublicKey_clear(PublicKey pubkey);
void PrivateKey_clear(PrivateKey pvtkey);
/*
* PrioPacketClient_encode
*
* Takes as input a pointer to an array (`data_in`) of boolean values
* whose length is equal to the number of data fields specified in
* the config. It then encodes the data for servers A and B into a
* string.
*
* NOTE: The caller must free() the strings `for_server_a` and
* `for_server_b` to avoid memory leaks.
*/
SECStatus PrioClient_encode(const_PrioConfig cfg, const bool* data_in,
unsigned char** forServerA, unsigned int* aLen,
unsigned char** forServerB, unsigned int* bLen);
/*
* Generate a new PRG seed using the NSS global randomness source.
* Use this routine to initialize the secret that the two Prio servers
* share.
*/
SECStatus PrioPRGSeed_randomize(PrioPRGSeed* seed);
/*
* The PrioServer object holds the state of the Prio servers.
* Pass in the _same_ secret PRGSeed when initializing the two servers.
* The PRGSeed must remain secret to the two servers.
*/
PrioServer PrioServer_new(const_PrioConfig cfg, PrioServerId serverIdx,
PrivateKey serverPriv,
const PrioPRGSeed serverSharedSecret);
void PrioServer_clear(PrioServer s);
/*
* After receiving a client packet, each of the servers generate
* a PrioVerifier object that they use to check whether the client's
* encoded packet is well formed.
*/
PrioVerifier PrioVerifier_new(PrioServer s);
void PrioVerifier_clear(PrioVerifier v);
/*
* Read in encrypted data from the client, decrypt it, and prepare to check the
* request for validity.
*/
SECStatus PrioVerifier_set_data(PrioVerifier v, unsigned char* data,
unsigned int dataLen);
/*
* Generate the first packet that servers need to exchange to verify the
* client's submission. This should be sent over a TLS connection between the
* servers.
*/
PrioPacketVerify1 PrioPacketVerify1_new(void);
void PrioPacketVerify1_clear(PrioPacketVerify1 p1);
SECStatus PrioPacketVerify1_set_data(PrioPacketVerify1 p1,
const_PrioVerifier v);
SECStatus PrioPacketVerify1_write(const_PrioPacketVerify1 p,
msgpack_packer* pk);
SECStatus PrioPacketVerify1_read(PrioPacketVerify1 p, msgpack_unpacker* upk,
const_PrioConfig cfg);
/*
* Generate the second packet that the servers need to exchange to verify the
* client's submission. The routine takes as input the PrioPacketVerify1
* packets from both server A and server B.
*
* This should be sent over a TLS connection between the servers.
*/
PrioPacketVerify2 PrioPacketVerify2_new(void);
void PrioPacketVerify2_clear(PrioPacketVerify2 p);
SECStatus PrioPacketVerify2_set_data(PrioPacketVerify2 p2, const_PrioVerifier v,
const_PrioPacketVerify1 p1A,
const_PrioPacketVerify1 p1B);
SECStatus PrioPacketVerify2_write(const_PrioPacketVerify2 p,
msgpack_packer* pk);
SECStatus PrioPacketVerify2_read(PrioPacketVerify2 p, msgpack_unpacker* upk,
const_PrioConfig cfg);
/*
* Use the PrioPacketVerify2s from both servers to check whether
* the client's submission is well formed.
*/
SECStatus PrioVerifier_isValid(const_PrioVerifier v, const_PrioPacketVerify2 pA,
const_PrioPacketVerify2 pB);
/*
* Each of the two servers calls this routine to aggregate the data
* submission from a client that is included in the PrioVerifier object.
*
* IMPORTANT: This routine does *not* check the validity of the client's
* data packet. The servers must execute the verification checks
* above before aggregating any client data.
*/
SECStatus PrioServer_aggregate(PrioServer s, PrioVerifier v);
/*
* After the servers have aggregated data packets from "enough" clients
* (this determines the anonymity set size), each server runs this routine
* to get a share of the aggregate statistics.
*/
PrioTotalShare PrioTotalShare_new(void);
void PrioTotalShare_clear(PrioTotalShare t);
SECStatus PrioTotalShare_set_data(PrioTotalShare t, const_PrioServer s);
SECStatus PrioTotalShare_write(const_PrioTotalShare t, msgpack_packer* pk);
SECStatus PrioTotalShare_read(PrioTotalShare t, msgpack_unpacker* upk,
const_PrioConfig cfg);
/*
* Read the output data into an array of unsigned longs. You should
* be sure that each data value can fit into a single `unsigned long`
* and that the pointer `output` points to a buffer large enough to
* store one long per data field.
*
* This function returns failure if some final data value is too
* long to fit in an `unsigned long`.
*/
SECStatus PrioTotalShare_final(const_PrioConfig cfg, unsigned long long* output,
const_PrioTotalShare tA,
const_PrioTotalShare tB);
#endif /* __PRIO_H__ */
#ifdef __cplusplus
}
#endif

View File

@ -1,49 +0,0 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
LOCAL_INCLUDES += [
'/security/nss/lib/freebl/mpi',
'/third_party/msgpack/include',
'include',
]
EXPORTS += [
'include/mprio.h',
]
# We allow warnings for third-party code that can be updated from upstream.
AllowCompilerWarnings()
NoVisibilityFlags()
SOURCES += [
'/security/nss/lib/freebl/mpi/montmulf.c',
'/security/nss/lib/freebl/mpi/mp_gf2m.c',
'/security/nss/lib/freebl/mpi/mpcpucache.c',
'/security/nss/lib/freebl/mpi/mpi.c',
'/security/nss/lib/freebl/mpi/mplogic.c',
'/security/nss/lib/freebl/mpi/mpmontg.c',
'/security/nss/lib/freebl/mpi/mpprime.c',
]
SOURCES += [
'prio/client.c',
'prio/config.c',
'prio/encrypt.c',
'prio/mparray.c',
'prio/poly.c',
'prio/prg.c',
'prio/rand.c',
'prio/serial.c',
'prio/server.c',
'prio/share.c',
]
FINAL_LIBRARY = 'xul'
# Use PKCS11 v2 struct definitions for now, otherwise NSS requires
# CK_GCM_PARAMS.ulIvBits to be set. This workaround is only required
# until NSS 3.52 RTM and upstream correctly initializes the field.
DEFINES['NSS_PKCS11_2_0_COMPAT'] = True

View File

@ -1,26 +0,0 @@
import os
Import('env')
penv = env.Clone()
src = [
"client.c",
"config.c",
"encrypt.c",
"mparray.c",
"poly.c",
"rand.c",
"prg.c",
"server.c",
"serial.c",
"share.c",
]
libs = [
"msgpackc"
]
penv.Append(LIBS = libs)
penv.StaticLibrary("mprio", src)

View File

@ -1,362 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mpi.h>
#include <mprio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "client.h"
#include "config.h"
#include "encrypt.h"
#include "poly.h"
#include "rand.h"
#include "serial.h"
#include "share.h"
#include "util.h"
// Let the points of data_in be [x1, x2, x3, ... ].
// We construct the polynomial f such that
// (a) f(0) = random,
// (b) f(i) = x_i for all i >= 1,
// (c) degree(f)+1 is a power of two.
// We then evaluate f at the 2N-th roots of unity
// and we return these evaluations as `evals_out`
// and we return f(0) as `const_term`.
static SECStatus
data_polynomial_evals(const_PrioConfig cfg, const_MPArray data_in,
MPArray evals_out, mp_int* const_term)
{
SECStatus rv = SECSuccess;
const mp_int* mod = &cfg->modulus;
MPArray points_f = NULL;
MPArray poly_f = NULL;
// Number of multiplication gates in the Valid() circuit.
const int mul_gates = cfg->num_data_fields;
// Little n is the number of points on the polynomials.
// The constant term is randomized, so it's (mul_gates + 1).
const int n = mul_gates + 1;
// Big N is n rounded up to a power of two.
const int N = next_power_of_two(n);
P_CHECKA(points_f = MPArray_new(N));
P_CHECKA(poly_f = MPArray_new(N));
// Set constant term f(0) to random
P_CHECKC(rand_int(&points_f->data[0], mod));
MP_CHECKC(mp_copy(&points_f->data[0], const_term));
// Set other values of f(x)
for (int i = 1; i < n; i++) {
MP_CHECKC(mp_copy(&data_in->data[i - 1], &points_f->data[i]));
}
// Interpolate through the Nth roots of unity
P_CHECKC(poly_fft(poly_f, points_f, cfg, true));
// Evaluate at all 2N-th roots of unity.
// To do so, first resize the eval arrays and fill upper
// values with zeros.
P_CHECKC(MPArray_resize(poly_f, 2 * N));
P_CHECKC(MPArray_resize(evals_out, 2 * N));
// Evaluate at the 2N-th roots of unity
P_CHECKC(poly_fft(evals_out, poly_f, cfg, false));
cleanup:
MPArray_clear(points_f);
MPArray_clear(poly_f);
return rv;
}
static SECStatus
share_polynomials(const_PrioConfig cfg, const_MPArray data_in,
PrioPacketClient pA, PrioPacketClient pB, PRG prgB)
{
SECStatus rv = SECSuccess;
const mp_int* mod = &cfg->modulus;
const_MPArray points_f = data_in;
mp_int f0, g0;
MP_DIGITS(&f0) = NULL;
MP_DIGITS(&g0) = NULL;
MPArray points_g = NULL;
MPArray evals_f_2N = NULL;
MPArray evals_g_2N = NULL;
P_CHECKA(points_g = MPArray_dup(points_f));
P_CHECKA(evals_f_2N = MPArray_new(0));
P_CHECKA(evals_g_2N = MPArray_new(0));
MP_CHECKC(mp_init(&f0));
MP_CHECKC(mp_init(&g0));
for (int i = 0; i < points_f->len; i++) {
// For each input value x_i, we compute x_i * (x_i-1).
// f(i) = x_i
// g(i) = x_i - 1
MP_CHECKC(mp_sub_d(&points_g->data[i], 1, &points_g->data[i]));
MP_CHECKC(mp_mod(&points_g->data[i], mod, &points_g->data[i]));
}
P_CHECKC(data_polynomial_evals(cfg, points_f, evals_f_2N, &f0));
P_CHECKC(data_polynomial_evals(cfg, points_g, evals_g_2N, &g0));
// The values f(0) and g(0) are set to random values.
// We must send to each server a share of the points
// f(0), g(0), and h(0) = f(0)*g(0)
P_CHECKC(share_int(cfg, &f0, &pA->f0_share, &pB->f0_share));
P_CHECKC(share_int(cfg, &g0, &pA->g0_share, &pB->g0_share));
// Compute h(0) = f(0)*g(0).
MP_CHECKC(mp_mulmod(&f0, &g0, mod, &f0));
// Give one share of h(0) to each server.
P_CHECKC(share_int(cfg, &f0, &pA->h0_share, &pB->h0_share));
// const int lenN = (evals_f_2N->len/2);
// P_CHECKC (MPArray_resize (pA->shares.A.h_points, lenN));
// We need to send to the servers the evaluations of
// f(r) * g(r)
// for all 2N-th roots of unity r that are not also
// N-th roots of unity.
//
// For each such root r, compute h(r) = f(r)*g(r) and
// send a share of this value to each server.
int j = 0;
for (int i = 1; i < evals_f_2N->len; i += 2) {
MP_CHECKC(mp_mulmod(&evals_f_2N->data[i], &evals_g_2N->data[i], mod, &f0));
P_CHECKC(PRG_share_int(prgB, &pA->shares.A.h_points->data[j], &f0, cfg));
j++;
}
cleanup:
MPArray_clear(evals_f_2N);
MPArray_clear(evals_g_2N);
MPArray_clear(points_g);
mp_clear(&f0);
mp_clear(&g0);
return rv;
}
PrioPacketClient
PrioPacketClient_new(const_PrioConfig cfg, PrioServerId for_server)
{
SECStatus rv = SECSuccess;
const int data_len = cfg->num_data_fields;
PrioPacketClient p = NULL;
p = malloc(sizeof(*p));
if (!p)
return NULL;
p->for_server = for_server;
p->triple = NULL;
MP_DIGITS(&p->f0_share) = NULL;
MP_DIGITS(&p->g0_share) = NULL;
MP_DIGITS(&p->h0_share) = NULL;
switch (p->for_server) {
case PRIO_SERVER_A:
p->shares.A.data_shares = NULL;
p->shares.A.h_points = NULL;
break;
case PRIO_SERVER_B:
memset(p->shares.B.seed, 0, PRG_SEED_LENGTH);
break;
default:
// Should never get here
rv = SECFailure;
goto cleanup;
}
MP_CHECKC(mp_init(&p->f0_share));
MP_CHECKC(mp_init(&p->g0_share));
MP_CHECKC(mp_init(&p->h0_share));
P_CHECKA(p->triple = BeaverTriple_new());
if (p->for_server == PRIO_SERVER_A) {
const int num_h_points = PrioConfig_hPoints(cfg);
P_CHECKA(p->shares.A.data_shares = MPArray_new(data_len));
P_CHECKA(p->shares.A.h_points = MPArray_new(num_h_points));
}
cleanup:
if (rv != SECSuccess) {
PrioPacketClient_clear(p);
return NULL;
}
return p;
}
SECStatus
PrioPacketClient_set_data(const_PrioConfig cfg, const bool* data_in,
PrioPacketClient pA, PrioPacketClient pB)
{
MPArray client_data = NULL;
PRG prgB = NULL;
SECStatus rv = SECSuccess;
const int data_len = cfg->num_data_fields;
if (!data_in)
return SECFailure;
P_CHECKC(PrioPRGSeed_randomize(&pB->shares.B.seed));
P_CHECKA(prgB = PRG_new(pB->shares.B.seed));
P_CHECKC(BeaverTriple_set_rand(cfg, pA->triple, pB->triple));
P_CHECKA(client_data = MPArray_new_bool(data_len, data_in));
P_CHECKC(PRG_share_array(prgB, pA->shares.A.data_shares, client_data, cfg));
P_CHECKC(share_polynomials(cfg, client_data, pA, pB, prgB));
cleanup:
MPArray_clear(client_data);
PRG_clear(prgB);
return rv;
}
void
PrioPacketClient_clear(PrioPacketClient p)
{
if (p == NULL)
return;
if (p->for_server == PRIO_SERVER_A) {
MPArray_clear(p->shares.A.h_points);
MPArray_clear(p->shares.A.data_shares);
}
BeaverTriple_clear(p->triple);
mp_clear(&p->f0_share);
mp_clear(&p->g0_share);
mp_clear(&p->h0_share);
free(p);
}
bool
PrioPacketClient_areEqual(const_PrioPacketClient p1, const_PrioPacketClient p2)
{
if (!BeaverTriple_areEqual(p1->triple, p2->triple))
return false;
if (mp_cmp(&p1->f0_share, &p2->f0_share))
return false;
if (mp_cmp(&p1->g0_share, &p2->g0_share))
return false;
if (mp_cmp(&p1->h0_share, &p2->h0_share))
return false;
if (p1->for_server != p2->for_server)
return false;
switch (p1->for_server) {
case PRIO_SERVER_A:
if (!MPArray_areEqual(p1->shares.A.data_shares, p2->shares.A.data_shares))
return false;
if (!MPArray_areEqual(p1->shares.A.h_points, p2->shares.A.h_points))
return false;
break;
case PRIO_SERVER_B:
if (memcmp(p1->shares.B.seed, p2->shares.B.seed, PRG_SEED_LENGTH))
return false;
break;
default:
// Should never get here.
return false;
}
return true;
}
SECStatus
PrioClient_encode(const_PrioConfig cfg, const bool* data_in,
unsigned char** for_server_a, unsigned int* aLen,
unsigned char** for_server_b, unsigned int* bLen)
{
SECStatus rv = SECSuccess;
PrioPacketClient pA = NULL;
PrioPacketClient pB = NULL;
*for_server_a = NULL;
*for_server_b = NULL;
msgpack_sbuffer sbufA, sbufB;
msgpack_packer packerA, packerB;
msgpack_sbuffer_init(&sbufA);
msgpack_sbuffer_init(&sbufB);
msgpack_packer_init(&packerA, &sbufA, msgpack_sbuffer_write);
msgpack_packer_init(&packerB, &sbufB, msgpack_sbuffer_write);
P_CHECKA(pA = PrioPacketClient_new(cfg, PRIO_SERVER_A));
P_CHECKA(pB = PrioPacketClient_new(cfg, PRIO_SERVER_B));
P_CHECKC(PrioPacketClient_set_data(cfg, data_in, pA, pB));
P_CHECKC(serial_write_packet_client(&packerA, pA, cfg));
P_CHECKC(serial_write_packet_client(&packerB, pB, cfg));
P_CHECKC(PublicKey_encryptSize(sbufA.size, aLen));
P_CHECKC(PublicKey_encryptSize(sbufB.size, bLen));
P_CHECKA(*for_server_a = malloc(*aLen));
P_CHECKA(*for_server_b = malloc(*bLen));
unsigned int writtenA;
unsigned int writtenB;
P_CHECKC(PublicKey_encrypt(cfg->server_a_pub, *for_server_a, &writtenA, *aLen,
(unsigned char*)sbufA.data, sbufA.size));
P_CHECKC(PublicKey_encrypt(cfg->server_b_pub, *for_server_b, &writtenB, *bLen,
(unsigned char*)sbufB.data, sbufB.size));
P_CHECKCB(writtenA == *aLen);
P_CHECKCB(writtenB == *bLen);
cleanup:
if (rv != SECSuccess) {
if (*for_server_a)
free(*for_server_a);
if (*for_server_b)
free(*for_server_b);
*for_server_a = NULL;
*for_server_b = NULL;
}
PrioPacketClient_clear(pA);
PrioPacketClient_clear(pB);
msgpack_sbuffer_destroy(&sbufA);
msgpack_sbuffer_destroy(&sbufB);
return rv;
}
SECStatus
PrioPacketClient_decrypt(PrioPacketClient p, const_PrioConfig cfg,
PrivateKey server_priv, const unsigned char* data_in,
unsigned int data_len)
{
SECStatus rv = SECSuccess;
msgpack_unpacker upk;
if (!msgpack_unpacker_init(&upk, data_len)) {
return SECFailure;
}
// Decrypt the ciphertext into dec_buf
unsigned int bytes_decrypted;
P_CHECKC(PrivateKey_decrypt(server_priv,
(unsigned char*)msgpack_unpacker_buffer(&upk),
&bytes_decrypted, data_len, data_in, data_len));
msgpack_unpacker_buffer_consumed(&upk, bytes_decrypted);
P_CHECKC(serial_read_packet_client(&upk, p, cfg));
cleanup:
msgpack_unpacker_destroy(&upk);
return rv;
}

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __CLIENT_H__
#define __CLIENT_H__
#include "mparray.h"
#include "prg.h"
#include "share.h"
/*
* The PrioPacketClient object holds the encoded client data.
* The client sends one packet to server A and one packet to
* server B. The `for_server` parameter determines which server
* the packet is for.
*/
typedef struct prio_packet_client* PrioPacketClient;
typedef const struct prio_packet_client* const_PrioPacketClient;
struct server_a_data
{
// These values are only set for server A.
MPArray data_shares;
MPArray h_points;
};
struct server_b_data
{
// This value is only used for server B.
//
// We use a pseudo-random generator to compress the secret-shared data
// values. See Appendix I of the Prio paper (the paragraph starting
// "Optimization: PRG secret sharing.") for details on this.
PrioPRGSeed seed;
};
/*
* The data that a Prio client sends to each server.
*/
struct prio_packet_client
{
// TODO: Can also use a PRG to avoid need for sending Beaver triple shares.
// Since this optimization only saves ~30 bytes of communication, we haven't
// bothered implementing it yet.
BeaverTriple triple;
mp_int f0_share, g0_share, h0_share;
PrioServerId for_server;
union
{
struct server_a_data A;
struct server_b_data B;
} shares;
};
PrioPacketClient PrioPacketClient_new(const_PrioConfig cfg,
PrioServerId for_server);
void PrioPacketClient_clear(PrioPacketClient p);
SECStatus PrioPacketClient_set_data(const_PrioConfig cfg, const bool* data_in,
PrioPacketClient for_server_a,
PrioPacketClient for_server_b);
SECStatus PrioPacketClient_decrypt(PrioPacketClient p, const_PrioConfig cfg,
PrivateKey server_priv,
const unsigned char* data_in,
unsigned int data_len);
bool PrioPacketClient_areEqual(const_PrioPacketClient p1,
const_PrioPacketClient p2);
#endif /* __CLIENT_H__ */

View File

@ -1,113 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <mprio.h>
#include <stdlib.h>
#include "config.h"
#include "mparray.h"
#include "params.h"
#include "rand.h"
#include "util.h"
int
PrioConfig_maxDataFields(void)
{
const int n_roots = 1 << Generator2Order;
return (n_roots >> 1) - 1;
}
PrioConfig
PrioConfig_new(int n_fields, PublicKey server_a, PublicKey server_b,
const unsigned char* batch_id, unsigned int batch_id_len)
{
SECStatus rv = SECSuccess;
PrioConfig cfg = malloc(sizeof(*cfg));
if (!cfg)
return NULL;
cfg->batch_id = NULL;
cfg->batch_id_len = batch_id_len;
cfg->server_a_pub = server_a;
cfg->server_b_pub = server_b;
cfg->num_data_fields = n_fields;
cfg->n_roots = 1 << Generator2Order;
MP_DIGITS(&cfg->modulus) = NULL;
MP_DIGITS(&cfg->inv2) = NULL;
MP_DIGITS(&cfg->generator) = NULL;
P_CHECKCB(cfg->n_roots > 1);
P_CHECKCB(cfg->num_data_fields <= PrioConfig_maxDataFields());
P_CHECKA(cfg->batch_id = malloc(batch_id_len));
strncpy((char*)cfg->batch_id, (char*)batch_id, batch_id_len);
MP_CHECKC(mp_init(&cfg->modulus));
MP_CHECKC(mp_read_radix(&cfg->modulus, Modulus, 16));
MP_CHECKC(mp_init(&cfg->generator));
MP_CHECKC(mp_read_radix(&cfg->generator, Generator, 16));
// Compute 2^{-1} modulo M
MP_CHECKC(mp_init(&cfg->inv2));
mp_set(&cfg->inv2, 2);
MP_CHECKC(mp_invmod(&cfg->inv2, &cfg->modulus, &cfg->inv2));
cleanup:
if (rv != SECSuccess) {
PrioConfig_clear(cfg);
return NULL;
}
return cfg;
}
PrioConfig
PrioConfig_newTest(int nFields)
{
return PrioConfig_new(nFields, NULL, NULL, (unsigned char*)"testBatch", 9);
}
void
PrioConfig_clear(PrioConfig cfg)
{
if (!cfg)
return;
if (cfg->batch_id)
free(cfg->batch_id);
mp_clear(&cfg->modulus);
mp_clear(&cfg->inv2);
mp_clear(&cfg->generator);
free(cfg);
}
int
PrioConfig_numDataFields(const_PrioConfig cfg)
{
return cfg->num_data_fields;
}
SECStatus
Prio_init(void)
{
return rand_init();
}
void
Prio_clear(void)
{
rand_clear();
}
int
PrioConfig_hPoints(const_PrioConfig cfg)
{
const int mul_gates = cfg->num_data_fields + 1;
const int N = next_power_of_two(mul_gates);
return N;
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __CONFIG_H__
#define __CONFIG_H__
#include <mpi.h>
#include "mparray.h"
struct prio_config
{
int num_data_fields;
unsigned char* batch_id;
unsigned int batch_id_len;
PublicKey server_a_pub;
PublicKey server_b_pub;
mp_int modulus;
mp_int inv2;
int n_roots;
mp_int generator;
};
int PrioConfig_hPoints(const_PrioConfig cfg);
#endif /* __CONFIG_H__ */

View File

@ -1,23 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __DEBUG_H__
#define __DEBUG_H__
#include <stdio.h>
#ifdef DEBUG
#define PRIO_DEBUG(msg) \
do { \
fprintf(stderr, "Error: %s\n", msg); \
} while (false);
#else
#define PRIO_DEBUG(msg) ;
#endif
#endif /* __DEBUG_H__ */

View File

@ -1,566 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <keyhi.h>
#include <keythi.h>
#include <pk11pub.h>
#include <prerror.h>
#include "encrypt.h"
#include "prio/rand.h"
#include "prio/util.h"
// Use curve25519
#define CURVE_OID_TAG SEC_OID_CURVE25519
// Use 96-bit IV
#define GCM_IV_LEN_BYTES 12
// Use 128-bit auth tag
#define GCM_TAG_LEN_BYTES 16
#define PRIO_TAG "PrioPacket"
#define AAD_LEN (sizeof(PRIO_TAG) - 1 + CURVE25519_KEY_LEN + GCM_IV_LEN_BYTES)
// For an example of NSS curve25519 import/export code, see:
// https://searchfox.org/nss/rev/cfd5fcba7efbfe116e2c08848075240ec3a92718/gtests/pk11_gtest/pk11_curve25519_unittest.cc#66
// The all-zeros curve25519 public key, as DER-encoded SPKI blob.
static const uint8_t curve25519_spki_zeros[] = {
0x30, 0x39, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01,
0x03, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// The all-zeros curve25519 private key, as a PKCS#8 blob.
static const uint8_t curve25519_priv_zeros[] = {
0x30, 0x67, 0x02, 0x01, 0x00, 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce,
0x3d, 0x02, 0x01, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f,
0x01, 0x04, 0x4c, 0x30, 0x4a, 0x02, 0x01, 0x01, 0x04, 0x20,
/* Byte index 36: 32 bytes of curve25519 private key. */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* misc type fields */
0xa1, 0x23, 0x03, 0x21,
/* Byte index 73: 32 bytes of curve25519 public key. */
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Index into `curve25519_priv_zeros` at which the private key begins.
static const size_t curve25519_priv_sk_offset = 36;
// Index into `curve25519_priv_zeros` at which the public key begins.
static const size_t curve25519_priv_pk_offset = 73;
static SECStatus key_from_hex(
unsigned char key_out[CURVE25519_KEY_LEN],
const unsigned char hex_in[CURVE25519_KEY_LEN_HEX]);
// Note that we do not use isxdigit because it is locale-dependent
// See: https://github.com/mozilla/libprio/issues/20
static inline char
is_hex_digit(char c)
{
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') ||
('A' <= c && c <= 'F');
}
// Note that we do not use toupper because it is locale-dependent
// See: https://github.com/mozilla/libprio/issues/20
static inline char
to_upper(char c)
{
if (c >= 'a' && c <= 'z') {
return c - 0x20;
} else {
return c;
}
}
static inline uint8_t
hex_to_int(char h)
{
return (h > '9') ? to_upper(h) - 'A' + 10 : (h - '0');
}
static inline unsigned char
int_to_hex(uint8_t i)
{
return (i > 0x09) ? ((i - 10) + 'A') : i + '0';
}
static SECStatus
derive_dh_secret(PK11SymKey** shared_secret, PrivateKey priv, PublicKey pub)
{
if (priv == NULL)
return SECFailure;
if (pub == NULL)
return SECFailure;
if (shared_secret == NULL)
return SECFailure;
SECStatus rv = SECSuccess;
*shared_secret = NULL;
P_CHECKA(*shared_secret = PK11_PubDeriveWithKDF(
priv, pub, PR_FALSE, NULL, NULL, CKM_ECDH1_DERIVE, CKM_AES_GCM,
CKA_ENCRYPT | CKA_DECRYPT, 16, CKD_SHA256_KDF, NULL, NULL));
cleanup:
return rv;
}
SECStatus
PublicKey_import(PublicKey* pk, const unsigned char* data, unsigned int dataLen)
{
SECStatus rv = SECSuccess;
CERTSubjectPublicKeyInfo* pkinfo = NULL;
*pk = NULL;
unsigned char* key_bytes = NULL;
uint8_t* spki_data = NULL;
if (dataLen != CURVE25519_KEY_LEN)
return SECFailure;
P_CHECKA(key_bytes = calloc(dataLen, sizeof(unsigned char)));
memcpy(key_bytes, data, dataLen);
const int spki_len = sizeof(curve25519_spki_zeros);
P_CHECKA(spki_data = calloc(spki_len, sizeof(uint8_t)));
memcpy(spki_data, curve25519_spki_zeros, spki_len);
SECItem spki_item = { siBuffer, spki_data, spki_len };
// Import the all-zeros curve25519 public key.
P_CHECKA(pkinfo = SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item));
P_CHECKA(*pk = SECKEY_ExtractPublicKey(pkinfo));
// Overwrite the all-zeros public key with the 32-byte curve25519 public key
// given as input.
memcpy((*pk)->u.ec.publicValue.data, data, CURVE25519_KEY_LEN);
cleanup:
if (key_bytes)
free(key_bytes);
if (spki_data)
free(spki_data);
if (pkinfo)
SECKEY_DestroySubjectPublicKeyInfo(pkinfo);
if (rv != SECSuccess)
PublicKey_clear(*pk);
return rv;
}
SECStatus
PrivateKey_import(PrivateKey* sk, const unsigned char* sk_data,
unsigned int sk_data_len, const unsigned char* pk_data,
unsigned int pk_data_len)
{
if (sk_data_len != CURVE25519_KEY_LEN || !sk_data) {
return SECFailure;
}
if (pk_data_len != CURVE25519_KEY_LEN || !pk_data) {
return SECFailure;
}
SECStatus rv = SECSuccess;
PK11SlotInfo* slot = NULL;
uint8_t* zero_priv_data = NULL;
*sk = NULL;
const int zero_priv_len = sizeof(curve25519_priv_zeros);
P_CHECKA(slot = PK11_GetInternalSlot());
P_CHECKA(zero_priv_data = calloc(zero_priv_len, sizeof(uint8_t)));
SECItem zero_priv_item = { siBuffer, zero_priv_data, zero_priv_len };
// Copy the PKCS#8-encoded keypair into writable buffer.
memcpy(zero_priv_data, curve25519_priv_zeros, zero_priv_len);
// Copy private key into bytes beginning at index `curve25519_priv_sk_offset`.
memcpy(zero_priv_data + curve25519_priv_sk_offset, sk_data, sk_data_len);
// Copy private key into bytes beginning at index `curve25519_priv_pk_offset`.
memcpy(zero_priv_data + curve25519_priv_pk_offset, pk_data, pk_data_len);
P_CHECKC(PK11_ImportDERPrivateKeyInfoAndReturnKey(
slot, &zero_priv_item, NULL, NULL, PR_FALSE, PR_FALSE, KU_ALL, sk, NULL));
cleanup:
if (slot) {
PK11_FreeSlot(slot);
}
if (zero_priv_data) {
free(zero_priv_data);
}
if (rv != SECSuccess) {
PrivateKey_clear(*sk);
}
return rv;
}
SECStatus
PublicKey_import_hex(PublicKey* pk, const unsigned char* hexData,
unsigned int dataLen)
{
unsigned char raw_bytes[CURVE25519_KEY_LEN];
if (dataLen != CURVE25519_KEY_LEN_HEX || !hexData) {
return SECFailure;
}
if (key_from_hex(raw_bytes, hexData) != SECSuccess) {
return SECFailure;
}
return PublicKey_import(pk, raw_bytes, CURVE25519_KEY_LEN);
}
SECStatus
PrivateKey_import_hex(PrivateKey* sk, const unsigned char* privHexData,
unsigned int privDataLen, const unsigned char* pubHexData,
unsigned int pubDataLen)
{
SECStatus rv = SECSuccess;
unsigned char raw_priv[CURVE25519_KEY_LEN];
unsigned char raw_pub[CURVE25519_KEY_LEN];
if (privDataLen != CURVE25519_KEY_LEN_HEX ||
pubDataLen != CURVE25519_KEY_LEN_HEX) {
return SECFailure;
}
if (!privHexData || !pubHexData) {
return SECFailure;
}
P_CHECK(key_from_hex(raw_priv, privHexData));
P_CHECK(key_from_hex(raw_pub, pubHexData));
return PrivateKey_import(sk, raw_priv, CURVE25519_KEY_LEN, raw_pub,
CURVE25519_KEY_LEN);
}
SECStatus
PublicKey_export(const_PublicKey pk, unsigned char* data, unsigned int dataLen)
{
if (pk == NULL || dataLen != CURVE25519_KEY_LEN) {
return SECFailure;
}
const SECItem* key = &pk->u.ec.publicValue;
if (key->len != CURVE25519_KEY_LEN) {
return SECFailure;
}
memcpy(data, key->data, key->len);
return SECSuccess;
}
SECStatus
PrivateKey_export(PrivateKey sk, unsigned char* data, unsigned int dataLen)
{
if (sk == NULL || dataLen != CURVE25519_KEY_LEN) {
return SECFailure;
}
SECStatus rv = SECSuccess;
SECItem item = { siBuffer, NULL, 0 };
P_CHECKC(PK11_ReadRawAttribute(PK11_TypePrivKey, sk, CKA_VALUE, &item));
// If the leading bytes of the key are '\0', then this string can be
// shorter than `CURVE25519_KEY_LEN` bytes.
memset(data, 0, CURVE25519_KEY_LEN);
P_CHECKCB(item.len <= CURVE25519_KEY_LEN);
// Copy into the low-order bytes of the output.
const size_t leading_zeros = CURVE25519_KEY_LEN - item.len;
memcpy(data + leading_zeros, item.data, item.len);
cleanup:
if (item.data != NULL) {
SECITEM_ZfreeItem(&item, PR_FALSE);
}
return rv;
}
static void
key_to_hex(const unsigned char key_in[CURVE25519_KEY_LEN],
unsigned char hex_out[(2 * CURVE25519_KEY_LEN) + 1])
{
const unsigned char* p = key_in;
for (unsigned int i = 0; i < CURVE25519_KEY_LEN; i++) {
unsigned char bytel = p[0] & 0x0f;
unsigned char byteu = (p[0] & 0xf0) >> 4;
hex_out[2 * i] = int_to_hex(byteu);
hex_out[2 * i + 1] = int_to_hex(bytel);
p++;
}
hex_out[2 * CURVE25519_KEY_LEN] = '\0';
}
static SECStatus
key_from_hex(unsigned char key_out[CURVE25519_KEY_LEN],
const unsigned char hex_in[CURVE25519_KEY_LEN_HEX])
{
for (unsigned int i = 0; i < CURVE25519_KEY_LEN_HEX; i++) {
if (!is_hex_digit(hex_in[i]))
return SECFailure;
}
const unsigned char* p = hex_in;
for (unsigned int i = 0; i < CURVE25519_KEY_LEN; i++) {
uint8_t d0 = hex_to_int(p[0]);
uint8_t d1 = hex_to_int(p[1]);
key_out[i] = (d0 << 4) | d1;
p += 2;
}
return SECSuccess;
}
SECStatus
PublicKey_export_hex(const_PublicKey pk, unsigned char* data,
unsigned int dataLen)
{
if (dataLen != CURVE25519_KEY_LEN_HEX + 1) {
return SECFailure;
}
unsigned char raw_data[CURVE25519_KEY_LEN];
if (PublicKey_export(pk, raw_data, sizeof(raw_data)) != SECSuccess) {
return SECFailure;
}
key_to_hex(raw_data, data);
return SECSuccess;
}
SECStatus
PrivateKey_export_hex(PrivateKey sk, unsigned char* data, unsigned int dataLen)
{
if (dataLen != CURVE25519_KEY_LEN_HEX + 1) {
return SECFailure;
}
unsigned char raw_data[CURVE25519_KEY_LEN];
if (PrivateKey_export(sk, raw_data, sizeof(raw_data)) != SECSuccess) {
return SECFailure;
}
key_to_hex(raw_data, data);
return SECSuccess;
}
SECStatus
Keypair_new(PrivateKey* pvtkey, PublicKey* pubkey)
{
if (pvtkey == NULL)
return SECFailure;
if (pubkey == NULL)
return SECFailure;
SECStatus rv = SECSuccess;
SECOidData* oid_data = NULL;
*pubkey = NULL;
*pvtkey = NULL;
SECKEYECParams ecp;
ecp.data = NULL;
PK11SlotInfo* slot = NULL;
P_CHECKA(oid_data = SECOID_FindOIDByTag(CURVE_OID_TAG));
const int oid_struct_len = 2 + oid_data->oid.len;
P_CHECKA(ecp.data = malloc(oid_struct_len));
ecp.len = oid_struct_len;
ecp.type = siDEROID;
ecp.data[0] = SEC_ASN1_OBJECT_ID;
ecp.data[1] = oid_data->oid.len;
memcpy(&ecp.data[2], oid_data->oid.data, oid_data->oid.len);
P_CHECKA(slot = PK11_GetInternalSlot());
P_CHECKA(*pvtkey = PK11_GenerateKeyPair(slot, CKM_EC_KEY_PAIR_GEN, &ecp,
(SECKEYPublicKey**)pubkey, PR_FALSE,
PR_FALSE, NULL));
cleanup:
if (slot) {
PK11_FreeSlot(slot);
}
if (ecp.data) {
free(ecp.data);
}
if (rv != SECSuccess) {
PublicKey_clear(*pubkey);
PrivateKey_clear(*pvtkey);
}
return rv;
}
void
PublicKey_clear(PublicKey pubkey)
{
if (pubkey)
SECKEY_DestroyPublicKey(pubkey);
}
void
PrivateKey_clear(PrivateKey pvtkey)
{
if (pvtkey)
SECKEY_DestroyPrivateKey(pvtkey);
}
const SECItem*
PublicKey_toBytes(const_PublicKey pubkey)
{
return &pubkey->u.ec.publicValue;
}
SECStatus
PublicKey_encryptSize(unsigned int inputLen, unsigned int* outputLen)
{
if (outputLen == NULL || inputLen >= MAX_ENCRYPT_LEN)
return SECFailure;
// public key, IV, tag, and input
*outputLen =
CURVE25519_KEY_LEN + GCM_IV_LEN_BYTES + GCM_TAG_LEN_BYTES + inputLen;
return SECSuccess;
}
static void
set_gcm_params(SECItem* paramItem, CK_GCM_PARAMS* param, unsigned char* nonce,
const_PublicKey pubkey, unsigned char* aadBuf)
{
int offset = 0;
memcpy(aadBuf, PRIO_TAG, strlen(PRIO_TAG));
offset += strlen(PRIO_TAG);
memcpy(aadBuf + offset, PublicKey_toBytes(pubkey)->data, CURVE25519_KEY_LEN);
offset += CURVE25519_KEY_LEN;
memcpy(aadBuf + offset, nonce, GCM_IV_LEN_BYTES);
param->pIv = nonce;
param->ulIvLen = GCM_IV_LEN_BYTES;
param->pAAD = aadBuf;
param->ulAADLen = AAD_LEN;
param->ulTagBits = GCM_TAG_LEN_BYTES * 8;
paramItem->type = siBuffer;
paramItem->data = (void*)param;
paramItem->len = sizeof(*param);
}
SECStatus
PublicKey_encrypt(PublicKey pubkey, unsigned char* output,
unsigned int* outputLen, unsigned int maxOutputLen,
const unsigned char* input, unsigned int inputLen)
{
if (pubkey == NULL)
return SECFailure;
if (inputLen >= MAX_ENCRYPT_LEN)
return SECFailure;
unsigned int needLen;
if (PublicKey_encryptSize(inputLen, &needLen) != SECSuccess)
return SECFailure;
if (maxOutputLen < needLen)
return SECFailure;
SECStatus rv = SECSuccess;
PublicKey eph_pub = NULL;
PrivateKey eph_priv = NULL;
PK11SymKey* aes_key = NULL;
unsigned char nonce[GCM_IV_LEN_BYTES];
unsigned char aadBuf[AAD_LEN];
P_CHECKC(rand_bytes(nonce, GCM_IV_LEN_BYTES));
P_CHECKC(Keypair_new(&eph_priv, &eph_pub));
P_CHECKC(derive_dh_secret(&aes_key, eph_priv, pubkey));
CK_GCM_PARAMS param;
SECItem paramItem;
set_gcm_params(&paramItem, &param, nonce, eph_pub, aadBuf);
const SECItem* pk = PublicKey_toBytes(eph_pub);
P_CHECKCB(pk->len == CURVE25519_KEY_LEN);
memcpy(output, pk->data, pk->len);
memcpy(output + CURVE25519_KEY_LEN, param.pIv, param.ulIvLen);
const int offset = CURVE25519_KEY_LEN + param.ulIvLen;
P_CHECKC(PK11_Encrypt(aes_key, CKM_AES_GCM, &paramItem, output + offset,
outputLen, maxOutputLen - offset, input, inputLen));
*outputLen = *outputLen + CURVE25519_KEY_LEN + GCM_IV_LEN_BYTES;
cleanup:
PublicKey_clear(eph_pub);
PrivateKey_clear(eph_priv);
if (aes_key)
PK11_FreeSymKey(aes_key);
return rv;
}
SECStatus
PrivateKey_decrypt(PrivateKey privkey, unsigned char* output,
unsigned int* outputLen, unsigned int maxOutputLen,
const unsigned char* input, unsigned int inputLen)
{
PK11SymKey* aes_key = NULL;
PublicKey eph_pub = NULL;
unsigned char aad_buf[AAD_LEN];
if (privkey == NULL)
return SECFailure;
SECStatus rv = SECSuccess;
unsigned int headerLen;
if (PublicKey_encryptSize(0, &headerLen) != SECSuccess)
return SECFailure;
if (inputLen < headerLen)
return SECFailure;
const unsigned int msglen = inputLen - headerLen;
if (maxOutputLen < msglen || msglen >= MAX_ENCRYPT_LEN)
return SECFailure;
P_CHECKC(PublicKey_import(&eph_pub, input, CURVE25519_KEY_LEN));
unsigned char nonce[GCM_IV_LEN_BYTES];
memcpy(nonce, input + CURVE25519_KEY_LEN, GCM_IV_LEN_BYTES);
SECItem paramItem;
CK_GCM_PARAMS param;
set_gcm_params(&paramItem, &param, nonce, eph_pub, aad_buf);
P_CHECKC(derive_dh_secret(&aes_key, privkey, eph_pub));
const int offset = CURVE25519_KEY_LEN + GCM_IV_LEN_BYTES;
P_CHECKC(PK11_Decrypt(aes_key, CKM_AES_GCM, &paramItem, output, outputLen,
maxOutputLen, input + offset, inputLen - offset));
cleanup:
PublicKey_clear(eph_pub);
if (aes_key)
PK11_FreeSymKey(aes_key);
return rv;
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __ENCRYPT_H__
#define __ENCRYPT_H__
#include <limits.h>
#include <mprio.h>
/*******
* These functions attempt to implement CCA-secure public-key encryption using
* the NSS library. We use hashed-ElGamal encryption with Curve25519 as the
* underlying group and AES128-GCM as the bulk encryption mode of operation.
*
* I make no guarantees that I am using NSS correctly or that this encryption
* scheme is actually CCA secure. As far as I can tell, NSS does not provide
* any public-key hybrid encryption scheme out of the box, so I had to cook my
* own. If you want to be really safe, you should use the NaCl Box routines
* to implement these functions.
*/
/*
* Messages encrypted using this library must be smaller than MAX_ENCRYPT_LEN.
* Enforcing this length limit helps avoid integer overflow.
*/
#define MAX_ENCRYPT_LEN (INT_MAX >> 3)
/*
* Write the number of bytes needed to store a ciphertext that encrypts a
* plaintext message of length `inputLen` and authenticated data of length
* `adLen` into the variable pointed to by `outputLen`. If `inputLen`
* is too large (larger than `MAX_ENCRYPT_LEN`), this function returns
* an error.
*/
SECStatus PublicKey_encryptSize(unsigned int inputLen, unsigned int* outputLen);
/*
* Generate a new keypair for public-key encryption.
*/
SECStatus Keypair_new(PrivateKey* pvtkey, PublicKey* pubkey);
/*
* Encrypt an arbitrary bitstring to the specified public key. The buffer
* `output` should be large enough to store the ciphertext. Use the
* `PublicKey_encryptSize()` function above to figure out how large of a buffer
* you need.
*
* The value `inputLen` must be smaller than `MAX_ENCRYPT_LEN`.
*/
SECStatus PublicKey_encrypt(PublicKey pubkey, unsigned char* output,
unsigned int* outputLen, unsigned int maxOutputLen,
const unsigned char* input, unsigned int inputLen);
/*
* Decrypt an arbitrary bitstring using the specified private key. The output
* buffer should be at least 16 bytes larger than the plaintext you expect. If
* `outputLen` >= `inputLen`, you should be safe.
*/
SECStatus PrivateKey_decrypt(PrivateKey privkey, unsigned char* output,
unsigned int* outputLen, unsigned int maxOutputLen,
const unsigned char* input, unsigned int inputLen);
#endif /* __ENCRYPT_H__ */

View File

@ -1,198 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <mprio.h>
#include <stdlib.h>
#include "config.h"
#include "mparray.h"
#include "share.h"
#include "util.h"
MPArray
MPArray_new(int len)
{
SECStatus rv = SECSuccess;
MPArray arr = malloc(sizeof *arr);
if (!arr)
return NULL;
arr->data = NULL;
arr->len = len;
P_CHECKA(arr->data = calloc(len, sizeof(mp_int)));
// Initialize these to NULL so that we can figure
// out which allocations failed (if any)
for (int i = 0; i < len; i++) {
MP_DIGITS(&arr->data[i]) = NULL;
}
for (int i = 0; i < len; i++) {
MP_CHECKC(mp_init(&arr->data[i]));
}
cleanup:
if (rv != SECSuccess) {
MPArray_clear(arr);
return NULL;
}
return arr;
}
MPArray
MPArray_new_bool(int len, const bool* data_in)
{
MPArray arr = MPArray_new(len);
if (arr == NULL)
return NULL;
for (int i = 0; i < len; i++) {
mp_set(&arr->data[i], data_in[i]);
}
return arr;
}
SECStatus
MPArray_resize(MPArray arr, int newlen)
{
SECStatus rv = SECSuccess;
const int oldlen = arr->len;
if (oldlen == newlen)
return rv;
// TODO: Use realloc for this?
mp_int* newdata = calloc(newlen, sizeof(mp_int));
if (newdata == NULL)
return SECFailure;
for (int i = 0; i < newlen; i++) {
MP_DIGITS(&newdata[i]) = NULL;
}
// Initialize new array
for (int i = 0; i < newlen; i++) {
MP_CHECKC(mp_init(&newdata[i]));
}
// Copy old data into new array
for (int i = 0; i < newlen && i < oldlen; i++) {
MP_CHECKC(mp_copy(&arr->data[i], &newdata[i]));
}
// Free old data
for (int i = 0; i < oldlen; i++) {
mp_clear(&arr->data[i]);
}
free(arr->data);
arr->data = newdata;
arr->len = newlen;
cleanup:
if (rv != SECSuccess) {
for (int i = 0; i < newlen; i++) {
mp_clear(&newdata[i]);
}
free(newdata);
}
return rv;
}
MPArray
MPArray_dup(const_MPArray src)
{
MPArray dst = MPArray_new(src->len);
if (!dst)
return NULL;
SECStatus rv = MPArray_copy(dst, src);
if (rv == SECSuccess) {
return dst;
} else {
MPArray_clear(dst);
return NULL;
}
}
SECStatus
MPArray_copy(MPArray dst, const_MPArray src)
{
if (dst->len != src->len)
return SECFailure;
for (int i = 0; i < src->len; i++) {
if (mp_copy(&src->data[i], &dst->data[i]) != MP_OKAY) {
return SECFailure;
}
}
return SECSuccess;
}
SECStatus
MPArray_set_share(MPArray arrA, MPArray arrB, const_MPArray src,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
if (arrA->len != src->len || arrB->len != src->len)
return SECFailure;
const int len = src->len;
for (int i = 0; i < len; i++) {
P_CHECK(share_int(cfg, &src->data[i], &arrA->data[i], &arrB->data[i]));
}
return rv;
}
void
MPArray_clear(MPArray arr)
{
if (arr == NULL)
return;
if (arr->data != NULL) {
for (int i = 0; i < arr->len; i++) {
mp_clear(&arr->data[i]);
}
free(arr->data);
}
free(arr);
}
SECStatus
MPArray_addmod(MPArray dst, const_MPArray to_add, const mp_int* mod)
{
if (dst->len != to_add->len)
return SECFailure;
for (int i = 0; i < dst->len; i++) {
MP_CHECK(mp_addmod(&dst->data[i], &to_add->data[i], mod, &dst->data[i]));
}
return SECSuccess;
}
bool
MPArray_areEqual(const_MPArray arr1, const_MPArray arr2)
{
if (arr1->len != arr2->len)
return false;
for (int i = 0; i < arr1->len; i++) {
if (mp_cmp(&arr1->data[i], &arr2->data[i]))
return false;
}
return true;
}

View File

@ -1,70 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __MPARRAY_H__
#define __MPARRAY_H__
#include <mpi.h>
#include <mprio.h>
struct mparray
{
int len;
mp_int* data;
};
typedef struct mparray* MPArray;
typedef const struct mparray* const_MPArray;
/*
* Initialize an array of `mp_int`s of the given length.
*/
MPArray MPArray_new(int len);
void MPArray_clear(MPArray arr);
/*
* Copies secret sharing of data from src into arrays
* arrA and arrB. The lengths of the three input arrays
* must be identical.
*/
SECStatus MPArray_set_share(MPArray arrA, MPArray arrB, const_MPArray src,
const_PrioConfig cfg);
/*
* Initializes array with 0/1 values specified in boolean array `data_in`
*/
MPArray MPArray_new_bool(int len, const bool* data_in);
/*
* Expands or shrinks the MPArray to the desired size. If shrinking,
* will clear the values on the end of array.
*/
SECStatus MPArray_resize(MPArray arr, int newlen);
/*
* Initializes dst and creates a duplicate of the array in src.
*/
MPArray MPArray_dup(const_MPArray src);
/*
* Copies array from src to dst. Arrays must have the same length.
*/
SECStatus MPArray_copy(MPArray dst, const_MPArray src);
/* For each index i into the array, set:
* dst[i] = dst[i] + to_add[i] (modulo mod)
*/
SECStatus MPArray_addmod(MPArray dst, const_MPArray to_add, const mp_int* mod);
/*
* Return true iff the two arrays are equal in length
* and contents. This comparison is NOT constant time.
*/
bool MPArray_areEqual(const_MPArray arr1, const_MPArray arr2);
#endif /* __MPARRAY_H__ */

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __PARAMS_H__
#define __PARAMS_H__
// A prime modulus p.
static const char Modulus[] = "8000000000000000080001";
// A generator g of a subgroup of Z*_p.
static const char Generator[] = "2597c14f48d5b65ed8dcca";
// The generator g generates a subgroup of
// order 2^Generator2Order in Z*_p.
static const int Generator2Order = 19;
#endif /* __PARAMS_H__ */

View File

@ -1,211 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <mprio.h>
#include "config.h"
#include "poly.h"
#include "util.h"
/*
* A nice exposition of the recursive FFT/DFT algorithm we implement
* is in the book:
*
* "Modern Computer Algebra"
* by Von zur Gathen and Gerhard.
* Cambridge University Press, 2013.
*
* They present this algorithm as Algorithm 8.14.
*/
static SECStatus
fft_recurse(mp_int* out, const mp_int* mod, int n, const mp_int* roots,
const mp_int* ys, mp_int* tmp, mp_int* ySub, mp_int* rootsSub)
{
if (n == 1) {
MP_CHECK(mp_copy(&ys[0], &out[0]));
return SECSuccess;
}
// Recurse on the first half
for (int i = 0; i < n / 2; i++) {
MP_CHECK(mp_addmod(&ys[i], &ys[i + (n / 2)], mod, &ySub[i]));
MP_CHECK(mp_copy(&roots[2 * i], &rootsSub[i]));
}
MP_CHECK(fft_recurse(tmp, mod, n / 2, rootsSub, ySub, &tmp[n / 2],
&ySub[n / 2], &rootsSub[n / 2]));
for (int i = 0; i < n / 2; i++) {
MP_CHECK(mp_copy(&tmp[i], &out[2 * i]));
}
// Recurse on the second half
for (int i = 0; i < n / 2; i++) {
MP_CHECK(mp_submod(&ys[i], &ys[i + (n / 2)], mod, &ySub[i]));
MP_CHECK(mp_mulmod(&ySub[i], &roots[i], mod, &ySub[i]));
}
MP_CHECK(fft_recurse(tmp, mod, n / 2, rootsSub, ySub, &tmp[n / 2],
&ySub[n / 2], &rootsSub[n / 2]));
for (int i = 0; i < n / 2; i++) {
MP_CHECK(mp_copy(&tmp[i], &out[2 * i + 1]));
}
return SECSuccess;
}
static SECStatus
fft_interpolate_raw(mp_int* out, const mp_int* ys, int nPoints,
const_MPArray roots, const mp_int* mod, bool invert)
{
SECStatus rv = SECSuccess;
MPArray tmp = NULL;
MPArray ySub = NULL;
MPArray rootsSub = NULL;
P_CHECKA(tmp = MPArray_new(nPoints));
P_CHECKA(ySub = MPArray_new(nPoints));
P_CHECKA(rootsSub = MPArray_new(nPoints));
mp_int n_inverse;
MP_DIGITS(&n_inverse) = NULL;
MP_CHECKC(fft_recurse(out, mod, nPoints, roots->data, ys, tmp->data,
ySub->data, rootsSub->data));
if (invert) {
MP_CHECKC(mp_init(&n_inverse));
mp_set(&n_inverse, nPoints);
MP_CHECKC(mp_invmod(&n_inverse, mod, &n_inverse));
for (int i = 0; i < nPoints; i++) {
MP_CHECKC(mp_mulmod(&out[i], &n_inverse, mod, &out[i]));
}
}
cleanup:
MPArray_clear(tmp);
MPArray_clear(ySub);
MPArray_clear(rootsSub);
mp_clear(&n_inverse);
return rv;
}
/*
* The PrioConfig object has a list of N-th roots of unity for large N.
* This routine returns the n-th roots of unity for n < N, where n is
* a power of two. If the `invert` flag is set, it returns the inverses
* of the n-th roots of unity.
*/
SECStatus
poly_fft_get_roots(MPArray roots_out, int n_points, const_PrioConfig cfg,
bool invert)
{
if (n_points < 1) {
return SECFailure;
}
if (n_points != roots_out->len) {
return SECFailure;
}
if (n_points > cfg->n_roots) {
return SECFailure;
}
mp_set(&roots_out->data[0], 1);
if (n_points == 1) {
return SECSuccess;
}
const int step_size = cfg->n_roots / n_points;
mp_int* gen = &roots_out->data[1];
MP_CHECK(mp_copy(&cfg->generator, gen));
if (invert) {
MP_CHECK(mp_invmod(gen, &cfg->modulus, gen));
}
// Compute g' = g^step_size
// Now, g' generates a subgroup of order n_points.
MP_CHECK(mp_exptmod_d(gen, step_size, &cfg->modulus, gen));
for (int i = 2; i < n_points; i++) {
// Compute g^i for all i in {0,..., n-1}
MP_CHECK(mp_mulmod(gen, &roots_out->data[i - 1], &cfg->modulus,
&roots_out->data[i]));
}
return SECSuccess;
}
SECStatus
poly_fft(MPArray points_out, const_MPArray points_in, const_PrioConfig cfg,
bool invert)
{
SECStatus rv = SECSuccess;
const int n_points = points_in->len;
MPArray scaled_roots = NULL;
if (points_out->len != points_in->len)
return SECFailure;
if (n_points > cfg->n_roots)
return SECFailure;
if (cfg->n_roots % n_points != 0)
return SECFailure;
P_CHECKA(scaled_roots = MPArray_new(n_points));
P_CHECKC(poly_fft_get_roots(scaled_roots, n_points, cfg, invert));
P_CHECKC(fft_interpolate_raw(points_out->data, points_in->data, n_points,
scaled_roots, &cfg->modulus, invert));
cleanup:
MPArray_clear(scaled_roots);
return SECSuccess;
}
SECStatus
poly_eval(mp_int* value, const_MPArray coeffs, const mp_int* eval_at,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
const int n = coeffs->len;
// Use Horner's method to evaluate the polynomial at the point
// `eval_at`
MP_CHECK(mp_copy(&coeffs->data[n - 1], value));
for (int i = n - 2; i >= 0; i--) {
MP_CHECK(mp_mulmod(value, eval_at, &cfg->modulus, value));
MP_CHECK(mp_addmod(value, &coeffs->data[i], &cfg->modulus, value));
}
return rv;
}
SECStatus
poly_interp_evaluate(mp_int* value, const_MPArray poly_points,
const mp_int* eval_at, const_PrioConfig cfg)
{
SECStatus rv;
MPArray coeffs = NULL;
const int N = poly_points->len;
P_CHECKA(coeffs = MPArray_new(N));
// Interpolate polynomial through roots of unity
P_CHECKC(poly_fft(coeffs, poly_points, cfg, true))
P_CHECKC(poly_eval(value, coeffs, eval_at, cfg));
cleanup:
MPArray_clear(coeffs);
return rv;
}

View File

@ -1,56 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef _FFT__H
#define _FFT__H
#include <mpi.h>
#include <mprio.h>
#include <stdbool.h>
#include "mparray.h"
/*
* Compute the FFT or inverse FFT of the array in `points_in`.
* The length of the input and output arrays must be a multiple
* of two and must be no longer than the number of precomputed
* roots in the PrioConfig object passed in.
*/
SECStatus poly_fft(MPArray points_out, const_MPArray points_in,
const_PrioConfig cfg, bool invert);
/*
* Get an array
* (r^0, r^1, r^2, ... )
* where r is an n-th root of unity, for n a power of two
* less than cfg->n_roots.
*
* Do NOT mp_clear() the mp_ints stored in roots_out.
* These are owned by the PrioConfig object.
*/
SECStatus poly_fft_get_roots(MPArray roots_out, int n_points,
const_PrioConfig cfg, bool invert);
/*
* Evaluate the polynomial specified by the coefficients
* at the point `eval_at` and return the result as `value`.
*/
SECStatus poly_eval(mp_int* value, const_MPArray coeffs, const mp_int* eval_at,
const_PrioConfig cfg);
/*
* Interpolate the polynomial through the points
* (x_1, y_1), ..., (x_N, y_N),
* where x_i is an N-th root of unity and the y_i values are
* specified by `poly_points`. Evaluate the resulting polynomial
* at the point `eval_at`. Return the result as `value`.
*/
SECStatus poly_interp_evaluate(mp_int* value, const_MPArray poly_points,
const mp_int* eval_at, const_PrioConfig cfg);
#endif

View File

@ -1,188 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <blapit.h>
#include <mprio.h>
#include <pk11pub.h>
#include <string.h>
#include "prg.h"
#include "rand.h"
#include "share.h"
#include "util.h"
struct prg
{
PK11SlotInfo* slot;
PK11SymKey* key;
PK11Context* ctx;
};
SECStatus
PrioPRGSeed_randomize(PrioPRGSeed* key)
{
return rand_bytes((unsigned char*)key, PRG_SEED_LENGTH);
}
PRG
PRG_new(const PrioPRGSeed key_in)
{
PRG prg = malloc(sizeof(struct prg));
if (!prg)
return NULL;
prg->slot = NULL;
prg->key = NULL;
prg->ctx = NULL;
SECStatus rv = SECSuccess;
const CK_MECHANISM_TYPE cipher = CKM_AES_CTR;
P_CHECKA(prg->slot = PK11_GetInternalSlot());
// Create a mutable copy of the key.
PrioPRGSeed key_mut;
memcpy(key_mut, key_in, PRG_SEED_LENGTH);
SECItem keyItem = { siBuffer, key_mut, PRG_SEED_LENGTH };
// The IV can be all zeros since we only encrypt once with
// each AES key.
CK_AES_CTR_PARAMS param = { 128, {} };
SECItem paramItem = { siBuffer, (void*)&param, sizeof(CK_AES_CTR_PARAMS) };
P_CHECKA(prg->key = PK11_ImportSymKey(prg->slot, cipher, PK11_OriginUnwrap,
CKA_ENCRYPT, &keyItem, NULL));
P_CHECKA(prg->ctx = PK11_CreateContextBySymKey(cipher, CKA_ENCRYPT, prg->key,
&paramItem));
cleanup:
if (rv != SECSuccess) {
PRG_clear(prg);
prg = NULL;
}
return prg;
}
void
PRG_clear(PRG prg)
{
if (!prg)
return;
if (prg->key)
PK11_FreeSymKey(prg->key);
if (prg->slot)
PK11_FreeSlot(prg->slot);
if (prg->ctx)
PK11_DestroyContext(prg->ctx, PR_TRUE);
free(prg);
}
static SECStatus
PRG_get_bytes_internal(void* prg_vp, unsigned char* bytes, size_t len)
{
SECStatus rv = SECSuccess;
PRG prg = (PRG)prg_vp;
unsigned char* in = NULL;
P_CHECKA(in = calloc(len, sizeof(unsigned char)));
int outlen;
P_CHECKC(PK11_CipherOp(prg->ctx, bytes, &outlen, len, in, len));
P_CHECKCB((size_t)outlen == len);
cleanup:
if (in)
free(in);
return rv;
}
SECStatus
PRG_get_bytes(PRG prg, unsigned char* bytes, size_t len)
{
return PRG_get_bytes_internal((void*)prg, bytes, len);
}
SECStatus
PRG_get_int(PRG prg, mp_int* out, const mp_int* max)
{
return rand_int_rng(out, max, &PRG_get_bytes_internal, (void*)prg);
}
SECStatus
PRG_get_int_range(PRG prg, mp_int* out, const mp_int* lower, const mp_int* max)
{
SECStatus rv;
mp_int width;
MP_DIGITS(&width) = NULL;
MP_CHECKC(mp_init(&width));
// Compute
// width = max - lower
MP_CHECKC(mp_sub(max, lower, &width));
// Get an integer x in the range [0, width)
P_CHECKC(PRG_get_int(prg, out, &width));
// Set
// out = lower + x
// which is in the range [lower, width+lower),
// which is [lower, max).
MP_CHECKC(mp_add(lower, out, out));
cleanup:
mp_clear(&width);
return rv;
}
SECStatus
PRG_get_array(PRG prg, MPArray dst, const mp_int* mod)
{
SECStatus rv;
for (int i = 0; i < dst->len; i++) {
P_CHECK(PRG_get_int(prg, &dst->data[i], mod));
}
return SECSuccess;
}
SECStatus
PRG_share_int(PRG prgB, mp_int* shareA, const mp_int* src, const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
mp_int tmp;
MP_DIGITS(&tmp) = NULL;
MP_CHECKC(mp_init(&tmp));
P_CHECKC(PRG_get_int(prgB, &tmp, &cfg->modulus));
MP_CHECKC(mp_submod(src, &tmp, &cfg->modulus, shareA));
cleanup:
mp_clear(&tmp);
return rv;
}
SECStatus
PRG_share_array(PRG prgB, MPArray arrA, const_MPArray src, const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
if (arrA->len != src->len)
return SECFailure;
const int len = src->len;
for (int i = 0; i < len; i++) {
P_CHECK(PRG_share_int(prgB, &arrA->data[i], &src->data[i], cfg));
}
return rv;
}

View File

@ -1,67 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __PRG_H__
#define __PRG_H__
#include <blapit.h>
#include <mpi.h>
#include <stdlib.h>
#include "config.h"
typedef struct prg* PRG;
typedef const struct prg* const_PRG;
/*
* Initialize or destroy a pseudo-random generator.
*/
PRG PRG_new(const PrioPRGSeed key);
void PRG_clear(PRG prg);
/*
* Produce the next bytes of output from the PRG.
*/
SECStatus PRG_get_bytes(PRG prg, unsigned char* bytes, size_t len);
/*
* Use the PRG output to sample a big integer x in the range
* 0 <= x < max.
*/
SECStatus PRG_get_int(PRG prg, mp_int* out, const mp_int* max);
/*
* Use the PRG output to sample a big integer x in the range
* lower <= x < max.
*/
SECStatus PRG_get_int_range(PRG prg, mp_int* out, const mp_int* lower,
const mp_int* max);
/*
* Use secret sharing to split the int src into two shares.
* Use PRG to generate the value `shareB`.
* The mp_ints must be initialized.
*/
SECStatus PRG_share_int(PRG prg, mp_int* shareA, const mp_int* src,
const_PrioConfig cfg);
/*
* Set each item in the array to a pseudorandom value in the range
* [0, mod), where the values are generated using the PRG.
*/
SECStatus PRG_get_array(PRG prg, MPArray arr, const mp_int* mod);
/*
* Secret shares the array in `src` into `arrA` using randomness
* provided by `prgB`. The arrays `src` and `arrA` must be the same
* length.
*/
SECStatus PRG_share_array(PRG prgB, MPArray arrA, const_MPArray src,
const_PrioConfig cfg);
#endif /* __PRG_H__ */

View File

@ -1,137 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <limits.h>
#include <mprio.h>
#include <nss.h>
#include <pk11pub.h>
#include <prinit.h>
#include "debug.h"
#include "rand.h"
#include "util.h"
#define CHUNK_SIZE 8192
static NSSInitContext* prioGlobalContext = NULL;
SECStatus
rand_init(void)
{
if (prioGlobalContext)
return SECSuccess;
prioGlobalContext =
NSS_InitContext("", "", "", "", NULL,
NSS_INIT_READONLY | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB |
NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT);
return (prioGlobalContext != NULL) ? SECSuccess : SECFailure;
}
static SECStatus
rand_bytes_internal(void* user_data, unsigned char* out, size_t n_bytes)
{
// No pointer should ever be passed in.
if (user_data != NULL)
return SECFailure;
if (!NSS_IsInitialized()) {
PRIO_DEBUG("NSS not initialized. Call rand_init() first.");
return SECFailure;
}
SECStatus rv = SECFailure;
int to_go = n_bytes;
unsigned char* cp = out;
while (to_go) {
int to_gen = MIN(CHUNK_SIZE, to_go);
if ((rv = PK11_GenerateRandom(cp, to_gen)) != SECSuccess) {
PRIO_DEBUG("Error calling PK11_GenerateRandom");
return SECFailure;
}
cp += CHUNK_SIZE;
to_go -= to_gen;
}
return rv;
}
SECStatus
rand_bytes(unsigned char* out, size_t n_bytes)
{
return rand_bytes_internal(NULL, out, n_bytes);
}
SECStatus
rand_int(mp_int* out, const mp_int* max)
{
return rand_int_rng(out, max, &rand_bytes_internal, NULL);
}
SECStatus
rand_int_rng(mp_int* out, const mp_int* max, RandBytesFunc rng_func,
void* user_data)
{
SECStatus rv = SECSuccess;
unsigned char* max_bytes = NULL;
unsigned char* buf = NULL;
// Ensure max value is > 0
if (mp_cmp_z(max) == 0)
return SECFailure;
// Compute max-1, which tells us the largest
// value we will ever need to generate.
MP_CHECKC(mp_sub_d(max, 1, out));
const int nbytes = mp_unsigned_octet_size(out);
// Figure out how many MSBs we need to get in the
// most-significant byte.
P_CHECKA(max_bytes = calloc(nbytes, sizeof(unsigned char)));
MP_CHECKC(mp_to_fixlen_octets(out, max_bytes, nbytes));
const unsigned char mask = msb_mask(max_bytes[0]);
// Buffer to store the pseudo-random bytes
P_CHECKA(buf = calloc(nbytes, sizeof(unsigned char)));
do {
// Use rejection sampling to find a value strictly less than max.
P_CHECKC(rng_func(user_data, buf, nbytes));
// Mask off high-order bits that we will never need.
P_CHECKC(rng_func(user_data, &buf[0], 1));
if (mask)
buf[0] &= mask;
MP_CHECKC(mp_read_unsigned_octets(out, buf, nbytes));
} while (mp_cmp(out, max) != -1);
cleanup:
if (max_bytes)
free(max_bytes);
if (buf)
free(buf);
return rv;
}
void
rand_clear(void)
{
if (prioGlobalContext) {
NSS_ShutdownContext(prioGlobalContext);
#ifdef DO_PR_CLEANUP
PR_Cleanup();
#endif
}
prioGlobalContext = NULL;
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __RAND_H__
#define __RAND_H__
#include <mpi.h>
#include <seccomon.h>
#include <stdlib.h>
/*
* Typedef for function pointer. A function pointer of type RandBytesFunc
* points to a function that fills the buffer `out` of with `len` random bytes.
*/
typedef SECStatus (*RandBytesFunc)(void* user_data, unsigned char* out,
size_t len);
/*
* Initialize or cleanup the global random number generator
* state that NSS uses.
*/
SECStatus rand_init(void);
void rand_clear(void);
/*
* Generate the specified number of random bytes using the
* NSS random number generator.
*/
SECStatus rand_bytes(unsigned char* out, size_t n_bytes);
/*
* Generate a random number x such that
* 0 <= x < max
* using the NSS random number generator.
*/
SECStatus rand_int(mp_int* out, const mp_int* max);
/*
* Generate a random number x such that
* 0 <= x < max
* using the specified randomness generator.
*
* The pointer user_data is passed to RandBytesFung `rng` as a first
* argument.
*/
SECStatus rand_int_rng(mp_int* out, const mp_int* max, RandBytesFunc rng,
void* user_data);
#endif /* __RAND_H__ */

View File

@ -1,458 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <mprio.h>
#include <msgpack.h>
#include "client.h"
#include "serial.h"
#include "server.h"
#include "share.h"
#include "util.h"
#define MSGPACK_OK 0
static SECStatus
serial_write_mp_int(msgpack_packer* pk, const mp_int* n)
{
SECStatus rv = SECSuccess;
unsigned int n_size = mp_unsigned_octet_size(n);
unsigned char* data = NULL;
P_CHECKA(data = calloc(n_size, sizeof(unsigned char)));
MP_CHECKC(mp_to_fixlen_octets(n, data, n_size));
P_CHECKC(msgpack_pack_str(pk, n_size));
P_CHECKC(msgpack_pack_str_body(pk, data, n_size));
cleanup:
if (data)
free(data);
return rv;
}
static SECStatus
object_to_mp_int(msgpack_object* obj, mp_int* n, const mp_int* max)
{
SECStatus rv = SECSuccess;
P_CHECKCB(obj != NULL);
P_CHECKCB(obj->type == MSGPACK_OBJECT_STR);
P_CHECKCB(n != NULL);
msgpack_object_str s = obj->via.str;
P_CHECKCB(s.ptr != NULL);
MP_CHECKC(mp_read_unsigned_octets(n, (unsigned char*)s.ptr, s.size));
P_CHECKCB(mp_cmp_z(n) >= 0);
P_CHECKCB(mp_cmp(n, max) < 0);
cleanup:
return rv;
}
static SECStatus
serial_read_mp_int(msgpack_unpacker* upk, mp_int* n, const mp_int* max)
{
SECStatus rv = SECSuccess;
msgpack_unpacked res;
msgpack_unpacked_init(&res);
P_CHECKCB(upk != NULL);
P_CHECKCB(n != NULL);
P_CHECKCB(max != NULL);
UP_CHECKC(msgpack_unpacker_next(upk, &res));
msgpack_object obj = res.data;
P_CHECKC(object_to_mp_int(&obj, n, max));
cleanup:
msgpack_unpacked_destroy(&res);
return rv;
}
static SECStatus
serial_read_int(msgpack_unpacker* upk, int* n)
{
SECStatus rv = SECSuccess;
msgpack_unpacked res;
msgpack_unpacked_init(&res);
P_CHECKCB(upk != NULL);
P_CHECKCB(n != NULL);
UP_CHECKC(msgpack_unpacker_next(upk, &res));
msgpack_object obj = res.data;
P_CHECKCB(obj.type == MSGPACK_OBJECT_POSITIVE_INTEGER);
*n = obj.via.i64;
cleanup:
msgpack_unpacked_destroy(&res);
return rv;
}
static SECStatus
serial_write_mp_array(msgpack_packer* pk, const_MPArray arr)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(arr != NULL);
P_CHECKC(msgpack_pack_array(pk, arr->len));
for (int i = 0; i < arr->len; i++) {
P_CHECKC(serial_write_mp_int(pk, &arr->data[i]));
}
cleanup:
return rv;
}
static SECStatus
serial_read_mp_array(msgpack_unpacker* upk, MPArray arr, size_t len,
const mp_int* max)
{
SECStatus rv = SECSuccess;
msgpack_unpacked res;
msgpack_unpacked_init(&res);
P_CHECKCB(upk != NULL);
P_CHECKCB(arr != NULL);
P_CHECKCB(max != NULL);
UP_CHECKC(msgpack_unpacker_next(upk, &res));
msgpack_object obj = res.data;
P_CHECKCB(obj.type == MSGPACK_OBJECT_ARRAY);
msgpack_object_array objarr = obj.via.array;
P_CHECKCB(objarr.size == len);
P_CHECKC(MPArray_resize(arr, len));
for (unsigned int i = 0; i < len; i++) {
P_CHECKC(object_to_mp_int(&objarr.ptr[i], &arr->data[i], max));
}
cleanup:
msgpack_unpacked_destroy(&res);
return rv;
}
static SECStatus
serial_write_beaver_triple(msgpack_packer* pk, const_BeaverTriple t)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(t != NULL);
P_CHECKC(serial_write_mp_int(pk, &t->a));
P_CHECKC(serial_write_mp_int(pk, &t->b));
P_CHECKC(serial_write_mp_int(pk, &t->c));
cleanup:
return rv;
}
static SECStatus
serial_read_beaver_triple(msgpack_unpacker* pk, BeaverTriple t,
const mp_int* max)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(t != NULL);
P_CHECKCB(max != NULL);
P_CHECKC(serial_read_mp_int(pk, &t->a, max));
P_CHECKC(serial_read_mp_int(pk, &t->b, max));
P_CHECKC(serial_read_mp_int(pk, &t->c, max));
cleanup:
return rv;
}
static SECStatus
serial_write_server_a_data(msgpack_packer* pk, const struct server_a_data* A)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(A != NULL);
P_CHECKC(serial_write_mp_array(pk, A->data_shares));
P_CHECKC(serial_write_mp_array(pk, A->h_points));
cleanup:
return rv;
}
static SECStatus
serial_read_server_a_data(msgpack_unpacker* upk, struct server_a_data* A,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
P_CHECKCB(upk != NULL);
P_CHECKCB(A != NULL);
P_CHECKC(serial_read_mp_array(upk, A->data_shares, cfg->num_data_fields,
&cfg->modulus));
P_CHECKC(serial_read_mp_array(upk, A->h_points, PrioConfig_hPoints(cfg),
&cfg->modulus));
cleanup:
return rv;
}
static SECStatus
serial_write_prg_seed(msgpack_packer* pk, const PrioPRGSeed* seed)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(seed != NULL);
P_CHECKC(msgpack_pack_str(pk, PRG_SEED_LENGTH));
P_CHECKC(msgpack_pack_str_body(pk, seed, PRG_SEED_LENGTH));
cleanup:
return rv;
}
static SECStatus
serial_read_prg_seed(msgpack_unpacker* upk, PrioPRGSeed* seed)
{
SECStatus rv = SECSuccess;
msgpack_unpacked res;
msgpack_unpacked_init(&res);
P_CHECKCB(upk != NULL);
P_CHECKCB(seed != NULL);
UP_CHECKC(msgpack_unpacker_next(upk, &res));
msgpack_object obj = res.data;
P_CHECKCB(obj.type == MSGPACK_OBJECT_STR);
msgpack_object_str s = obj.via.str;
P_CHECKCB(s.size == PRG_SEED_LENGTH);
memcpy(seed, s.ptr, PRG_SEED_LENGTH);
cleanup:
msgpack_unpacked_destroy(&res);
return rv;
}
static SECStatus
serial_write_server_b_data(msgpack_packer* pk, const struct server_b_data* B)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(B != NULL);
rv = serial_write_prg_seed(pk, &B->seed);
cleanup:
return rv;
}
static SECStatus
serial_read_server_b_data(msgpack_unpacker* upk, struct server_b_data* B)
{
SECStatus rv = SECSuccess;
P_CHECKCB(upk != NULL);
P_CHECKCB(B != NULL);
rv = serial_read_prg_seed(upk, &B->seed);
cleanup:
return rv;
}
SECStatus
serial_write_packet_client(msgpack_packer* pk, const_PrioPacketClient p,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(p != NULL);
P_CHECKC(msgpack_pack_str(pk, cfg->batch_id_len));
P_CHECKC(msgpack_pack_str_body(pk, cfg->batch_id, cfg->batch_id_len));
P_CHECKC(serial_write_beaver_triple(pk, p->triple));
P_CHECKC(serial_write_mp_int(pk, &p->f0_share));
P_CHECKC(serial_write_mp_int(pk, &p->g0_share));
P_CHECKC(serial_write_mp_int(pk, &p->h0_share));
P_CHECKC(msgpack_pack_int(pk, p->for_server));
switch (p->for_server) {
case PRIO_SERVER_A:
P_CHECKC(serial_write_server_a_data(pk, &p->shares.A));
break;
case PRIO_SERVER_B:
P_CHECKC(serial_write_server_b_data(pk, &p->shares.B));
break;
default:
return SECFailure;
}
cleanup:
return rv;
}
SECStatus
serial_read_server_id(msgpack_unpacker* upk, PrioServerId* s)
{
SECStatus rv = SECSuccess;
P_CHECKCB(upk != NULL);
P_CHECKCB(s != NULL);
int serv;
P_CHECKC(serial_read_int(upk, &serv));
P_CHECKCB(serv == PRIO_SERVER_A || serv == PRIO_SERVER_B);
*s = serv;
cleanup:
return rv;
}
SECStatus
serial_read_packet_client(msgpack_unpacker* upk, PrioPacketClient p,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
msgpack_unpacked res;
msgpack_unpacked_init(&res);
P_CHECKCB(upk != NULL);
P_CHECKCB(p != NULL);
UP_CHECKC(msgpack_unpacker_next(upk, &res));
msgpack_object obj = res.data;
P_CHECKCB(obj.type == MSGPACK_OBJECT_STR);
msgpack_object_str s = obj.via.str;
P_CHECKCB(s.size == cfg->batch_id_len);
P_CHECKCB(!memcmp(s.ptr, (char*)cfg->batch_id, cfg->batch_id_len));
P_CHECKC(serial_read_beaver_triple(upk, p->triple, &cfg->modulus));
P_CHECKC(serial_read_mp_int(upk, &p->f0_share, &cfg->modulus));
P_CHECKC(serial_read_mp_int(upk, &p->g0_share, &cfg->modulus));
P_CHECKC(serial_read_mp_int(upk, &p->h0_share, &cfg->modulus));
PrioServerId remote_id;
P_CHECKC(serial_read_server_id(upk, &remote_id));
P_CHECKCB(remote_id == p->for_server);
switch (p->for_server) {
case PRIO_SERVER_A:
P_CHECKC(serial_read_server_a_data(upk, &p->shares.A, cfg));
break;
case PRIO_SERVER_B:
P_CHECKC(serial_read_server_b_data(upk, &p->shares.B));
break;
default:
rv = SECFailure;
goto cleanup;
}
cleanup:
msgpack_unpacked_destroy(&res);
return rv;
}
SECStatus
PrioPacketVerify1_write(const_PrioPacketVerify1 p, msgpack_packer* pk)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(p != NULL);
P_CHECKC(serial_write_mp_int(pk, &p->share_d));
P_CHECKC(serial_write_mp_int(pk, &p->share_e));
cleanup:
return rv;
}
SECStatus
PrioPacketVerify1_read(PrioPacketVerify1 p, msgpack_unpacker* upk,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
P_CHECKCB(upk != NULL);
P_CHECKCB(p != NULL);
P_CHECKC(serial_read_mp_int(upk, &p->share_d, &cfg->modulus));
P_CHECKC(serial_read_mp_int(upk, &p->share_e, &cfg->modulus));
cleanup:
return rv;
}
SECStatus
PrioPacketVerify2_write(const_PrioPacketVerify2 p, msgpack_packer* pk)
{
SECStatus rv = SECSuccess;
P_CHECKCB(pk != NULL);
P_CHECKCB(p != NULL);
P_CHECKC(serial_write_mp_int(pk, &p->share_out));
cleanup:
return rv;
}
SECStatus
PrioPacketVerify2_read(PrioPacketVerify2 p, msgpack_unpacker* upk,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
P_CHECKCB(upk != NULL);
P_CHECKCB(p != NULL);
P_CHECKC(serial_read_mp_int(upk, &p->share_out, &cfg->modulus));
cleanup:
return rv;
}
SECStatus
PrioTotalShare_write(const_PrioTotalShare t, msgpack_packer* pk)
{
SECStatus rv = SECSuccess;
P_CHECKCB(t != NULL);
P_CHECKCB(pk != NULL);
P_CHECKC(msgpack_pack_int(pk, t->idx));
P_CHECKC(serial_write_mp_array(pk, t->data_shares));
cleanup:
return rv;
}
SECStatus
PrioTotalShare_read(PrioTotalShare t, msgpack_unpacker* upk,
const_PrioConfig cfg)
{
SECStatus rv = SECSuccess;
P_CHECKCB(t != NULL);
P_CHECKCB(upk != NULL);
P_CHECKC(serial_read_server_id(upk, &t->idx));
P_CHECKC(serial_read_mp_array(upk, t->data_shares, cfg->num_data_fields,
&cfg->modulus));
cleanup:
return rv;
}

View File

@ -1,21 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __SERIAL_H__
#define __SERIAL_H__
#include <mprio.h>
SECStatus serial_write_packet_client(msgpack_packer* pk,
const_PrioPacketClient p,
const_PrioConfig cfg);
SECStatus serial_read_packet_client(msgpack_unpacker* upk, PrioPacketClient p,
const_PrioConfig cfg);
#endif /* __SERIAL_H__ */

View File

@ -1,509 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <mpi.h>
#include <mprio.h>
#include <stdio.h>
#include <stdlib.h>
#include "client.h"
#include "mparray.h"
#include "poly.h"
#include "prg.h"
#include "server.h"
#include "util.h"
/* In `PrioTotalShare_final`, we need to be able to store
* an `mp_digit` in an `unsigned long long`.
*/
#if (MP_DIGIT_MAX > ULLONG_MAX)
#error "Unsigned long long is not long enough to hold an MP digit"
#endif
PrioServer
PrioServer_new(const_PrioConfig cfg, PrioServerId server_idx,
PrivateKey server_priv, const PrioPRGSeed seed)
{
SECStatus rv = SECSuccess;
PrioServer s = malloc(sizeof(*s));
if (!s)
return NULL;
s->cfg = cfg;
s->idx = server_idx;
s->priv_key = server_priv;
s->data_shares = NULL;
s->prg = NULL;
P_CHECKA(s->data_shares = MPArray_new(s->cfg->num_data_fields));
P_CHECKA(s->prg = PRG_new(seed));
cleanup:
if (rv != SECSuccess) {
PrioServer_clear(s);
return NULL;
}
return s;
}
void
PrioServer_clear(PrioServer s)
{
if (!s)
return;
PRG_clear(s->prg);
MPArray_clear(s->data_shares);
free(s);
}
SECStatus
PrioServer_aggregate(PrioServer s, PrioVerifier v)
{
MPArray arr = NULL;
switch (s->idx) {
case PRIO_SERVER_A:
arr = v->clientp->shares.A.data_shares;
break;
case PRIO_SERVER_B:
arr = v->data_sharesB;
break;
default:
// Should never get here
return SECFailure;
}
return MPArray_addmod(s->data_shares, arr, &s->cfg->modulus);
}
PrioTotalShare
PrioTotalShare_new(void)
{
PrioTotalShare t = malloc(sizeof(*t));
if (!t)
return NULL;
t->data_shares = MPArray_new(0);
if (!t->data_shares) {
free(t);
return NULL;
}
return t;
}
void
PrioTotalShare_clear(PrioTotalShare t)
{
if (!t)
return;
MPArray_clear(t->data_shares);
free(t);
}
SECStatus
PrioTotalShare_set_data(PrioTotalShare t, const_PrioServer s)
{
t->idx = s->idx;
SECStatus rv = SECSuccess;
P_CHECK(MPArray_resize(t->data_shares, s->data_shares->len));
P_CHECK(MPArray_copy(t->data_shares, s->data_shares));
return rv;
}
SECStatus
PrioTotalShare_final(const_PrioConfig cfg, unsigned long long* output,
const_PrioTotalShare tA, const_PrioTotalShare tB)
{
if (tA->data_shares->len != cfg->num_data_fields)
return SECFailure;
if (tA->data_shares->len != tB->data_shares->len)
return SECFailure;
if (tA->idx != PRIO_SERVER_A || tB->idx != PRIO_SERVER_B)
return SECFailure;
SECStatus rv = SECSuccess;
mp_int tmp;
MP_DIGITS(&tmp) = NULL;
MP_CHECKC(mp_init(&tmp));
for (int i = 0; i < cfg->num_data_fields; i++) {
MP_CHECKC(mp_addmod(&tA->data_shares->data[i], &tB->data_shares->data[i],
&cfg->modulus, &tmp));
if (MP_USED(&tmp) > 1) {
P_CHECKCB(false);
}
output[i] = MP_DIGIT(&tmp, 0);
}
cleanup:
mp_clear(&tmp);
return rv;
}
inline static mp_int*
get_data_share(const_PrioVerifier v, int i)
{
switch (v->s->idx) {
case PRIO_SERVER_A:
return &v->clientp->shares.A.data_shares->data[i];
case PRIO_SERVER_B:
return &v->data_sharesB->data[i];
}
// Should never get here
return NULL;
}
inline static mp_int*
get_h_share(const_PrioVerifier v, int i)
{
switch (v->s->idx) {
case PRIO_SERVER_A:
return &v->clientp->shares.A.h_points->data[i];
case PRIO_SERVER_B:
return &v->h_pointsB->data[i];
}
// Should never get here
return NULL;
}
/*
* Build shares of the polynomials f, g, and h used in the Prio verification
* routine and evalute these polynomials at a random point determined
* by the shared secret. Store the evaluations in the verifier object.
*/
static SECStatus
compute_shares(PrioVerifier v, const_PrioPacketClient p)
{
SECStatus rv;
const int n = v->s->cfg->num_data_fields + 1;
const int N = next_power_of_two(n);
mp_int eval_at;
mp_int lower;
MP_DIGITS(&eval_at) = NULL;
MP_DIGITS(&lower) = NULL;
MPArray points_f = NULL;
MPArray points_g = NULL;
MPArray points_h = NULL;
MP_CHECKC(mp_init(&eval_at));
MP_CHECKC(mp_init(&lower));
P_CHECKA(points_f = MPArray_new(N));
P_CHECKA(points_g = MPArray_new(N));
P_CHECKA(points_h = MPArray_new(2 * N));
// Use PRG to generate random point. Per Appendix D.2 of full version of
// Prio paper, this value must be in the range
// [n+1, modulus).
mp_set(&lower, n + 1);
P_CHECKC(PRG_get_int_range(v->s->prg, &eval_at, &lower, &v->s->cfg->modulus));
// Reduce value into the field we're using. This
// doesn't yield exactly a uniformly random point,
// but for values this large, it will be close
// enough.
MP_CHECKC(mp_mod(&eval_at, &v->s->cfg->modulus, &eval_at));
// Client sends us the values of f(0) and g(0)
MP_CHECKC(mp_copy(&p->f0_share, &points_f->data[0]));
MP_CHECKC(mp_copy(&p->g0_share, &points_g->data[0]));
MP_CHECKC(mp_copy(&p->h0_share, &points_h->data[0]));
for (int i = 1; i < n; i++) {
// [f](i) = i-th data share
const mp_int* data_i_minus_1 = get_data_share(v, i - 1);
MP_CHECKC(mp_copy(data_i_minus_1, &points_f->data[i]));
// [g](i) = i-th data share minus 1
// Only need to shift the share for 0-th server
MP_CHECKC(mp_copy(&points_f->data[i], &points_g->data[i]));
if (!v->s->idx) {
MP_CHECKC(mp_sub_d(&points_g->data[i], 1, &points_g->data[i]));
MP_CHECKC(
mp_mod(&points_g->data[i], &v->s->cfg->modulus, &points_g->data[i]));
}
}
int j = 0;
for (int i = 1; i < 2 * N; i += 2) {
const mp_int* h_point_j = get_h_share(v, j++);
MP_CHECKC(mp_copy(h_point_j, &points_h->data[i]));
}
P_CHECKC(poly_interp_evaluate(&v->share_fR, points_f, &eval_at, v->s->cfg));
P_CHECKC(poly_interp_evaluate(&v->share_gR, points_g, &eval_at, v->s->cfg));
P_CHECKC(poly_interp_evaluate(&v->share_hR, points_h, &eval_at, v->s->cfg));
cleanup:
MPArray_clear(points_f);
MPArray_clear(points_g);
MPArray_clear(points_h);
mp_clear(&eval_at);
mp_clear(&lower);
return rv;
}
PrioVerifier
PrioVerifier_new(PrioServer s)
{
SECStatus rv = SECSuccess;
PrioVerifier v = malloc(sizeof *v);
if (!v)
return NULL;
v->s = s;
v->clientp = NULL;
v->data_sharesB = NULL;
v->h_pointsB = NULL;
MP_DIGITS(&v->share_fR) = NULL;
MP_DIGITS(&v->share_gR) = NULL;
MP_DIGITS(&v->share_hR) = NULL;
MP_CHECKC(mp_init(&v->share_fR));
MP_CHECKC(mp_init(&v->share_gR));
MP_CHECKC(mp_init(&v->share_hR));
P_CHECKA(v->clientp = PrioPacketClient_new(s->cfg, s->idx));
const int N = next_power_of_two(s->cfg->num_data_fields + 1);
if (v->s->idx == PRIO_SERVER_B) {
P_CHECKA(v->data_sharesB = MPArray_new(v->s->cfg->num_data_fields));
P_CHECKA(v->h_pointsB = MPArray_new(N));
}
cleanup:
if (rv != SECSuccess) {
PrioVerifier_clear(v);
return NULL;
}
return v;
}
SECStatus
PrioVerifier_set_data(PrioVerifier v, unsigned char* data,
unsigned int data_len)
{
SECStatus rv = SECSuccess;
PRG prgB = NULL;
P_CHECKC(PrioPacketClient_decrypt(v->clientp, v->s->cfg, v->s->priv_key, data,
data_len));
PrioPacketClient p = v->clientp;
if (p->for_server != v->s->idx)
return SECFailure;
const int N = next_power_of_two(v->s->cfg->num_data_fields + 1);
if (v->s->idx == PRIO_SERVER_A) {
// Check that packet has the correct number of data fields
if (p->shares.A.data_shares->len != v->s->cfg->num_data_fields)
return SECFailure;
if (p->shares.A.h_points->len != N)
return SECFailure;
}
if (v->s->idx == PRIO_SERVER_B) {
P_CHECKA(prgB = PRG_new(v->clientp->shares.B.seed));
P_CHECKC(PRG_get_array(prgB, v->data_sharesB, &v->s->cfg->modulus));
P_CHECKC(PRG_get_array(prgB, v->h_pointsB, &v->s->cfg->modulus));
}
// TODO: This can be done much faster by using the combined
// interpolate-and-evaluate optimization described in the
// Prio paper.
//
// Compute share of f(r), g(r), h(r)
P_CHECKC(compute_shares(v, p));
cleanup:
PRG_clear(prgB);
return rv;
}
void
PrioVerifier_clear(PrioVerifier v)
{
if (v == NULL)
return;
PrioPacketClient_clear(v->clientp);
MPArray_clear(v->data_sharesB);
MPArray_clear(v->h_pointsB);
mp_clear(&v->share_fR);
mp_clear(&v->share_gR);
mp_clear(&v->share_hR);
free(v);
}
PrioPacketVerify1
PrioPacketVerify1_new(void)
{
SECStatus rv = SECSuccess;
PrioPacketVerify1 p = malloc(sizeof *p);
if (!p)
return NULL;
MP_DIGITS(&p->share_d) = NULL;
MP_DIGITS(&p->share_e) = NULL;
MP_CHECKC(mp_init(&p->share_d));
MP_CHECKC(mp_init(&p->share_e));
cleanup:
if (rv != SECSuccess) {
PrioPacketVerify1_clear(p);
return NULL;
}
return p;
}
void
PrioPacketVerify1_clear(PrioPacketVerify1 p)
{
if (!p)
return;
mp_clear(&p->share_d);
mp_clear(&p->share_e);
free(p);
}
SECStatus
PrioPacketVerify1_set_data(PrioPacketVerify1 p1, const_PrioVerifier v)
{
// See the Prio paper for details on how this works.
// Appendix C descrives the MPC protocol used here.
SECStatus rv = SECSuccess;
// Compute corrections.
// [d] = [f(r)] - [a]
MP_CHECK(mp_sub(&v->share_fR, &v->clientp->triple->a, &p1->share_d));
MP_CHECK(mp_mod(&p1->share_d, &v->s->cfg->modulus, &p1->share_d));
// [e] = [g(r)] - [b]
MP_CHECK(mp_sub(&v->share_gR, &v->clientp->triple->b, &p1->share_e));
MP_CHECK(mp_mod(&p1->share_e, &v->s->cfg->modulus, &p1->share_e));
return rv;
}
PrioPacketVerify2
PrioPacketVerify2_new(void)
{
SECStatus rv = SECSuccess;
PrioPacketVerify2 p = malloc(sizeof *p);
if (!p)
return NULL;
MP_DIGITS(&p->share_out) = NULL;
MP_CHECKC(mp_init(&p->share_out));
cleanup:
if (rv != SECSuccess) {
PrioPacketVerify2_clear(p);
return NULL;
}
return p;
}
void
PrioPacketVerify2_clear(PrioPacketVerify2 p)
{
if (!p)
return;
mp_clear(&p->share_out);
free(p);
}
SECStatus
PrioPacketVerify2_set_data(PrioPacketVerify2 p2, const_PrioVerifier v,
const_PrioPacketVerify1 p1A,
const_PrioPacketVerify1 p1B)
{
SECStatus rv = SECSuccess;
mp_int d, e, tmp;
MP_DIGITS(&d) = NULL;
MP_DIGITS(&e) = NULL;
MP_DIGITS(&tmp) = NULL;
MP_CHECKC(mp_init(&d));
MP_CHECKC(mp_init(&e));
MP_CHECKC(mp_init(&tmp));
const mp_int* mod = &v->s->cfg->modulus;
// Compute share of f(r)*g(r)
// [f(r)*g(r)] = [d*e/2] + d[b] + e[a] + [c]
// Compute d
MP_CHECKC(mp_addmod(&p1A->share_d, &p1B->share_d, mod, &d));
// Compute e
MP_CHECKC(mp_addmod(&p1A->share_e, &p1B->share_e, mod, &e));
// Compute d*e
MP_CHECKC(mp_mulmod(&d, &e, mod, &p2->share_out));
// out = d*e/2
MP_CHECKC(mp_mulmod(&p2->share_out, &v->s->cfg->inv2, mod, &p2->share_out));
// Compute d[b]
MP_CHECKC(mp_mulmod(&d, &v->clientp->triple->b, mod, &tmp));
// out = d*e/2 + d[b]
MP_CHECKC(mp_addmod(&p2->share_out, &tmp, mod, &p2->share_out));
// Compute e[a]
MP_CHECKC(mp_mulmod(&e, &v->clientp->triple->a, mod, &tmp));
// out = d*e/2 + d[b] + e[a]
MP_CHECKC(mp_addmod(&p2->share_out, &tmp, mod, &p2->share_out));
// out = d*e/2 + d[b] + e[a] + [c]
MP_CHECKC(
mp_addmod(&p2->share_out, &v->clientp->triple->c, mod, &p2->share_out));
// We want to compute f(r)*g(r) - h(r),
// so subtract off [h(r)]:
// out = d*e/2 + d[b] + e[a] + [c] - [h(r)]
MP_CHECKC(mp_sub(&p2->share_out, &v->share_hR, &p2->share_out));
MP_CHECKC(mp_mod(&p2->share_out, mod, &p2->share_out));
cleanup:
mp_clear(&d);
mp_clear(&e);
mp_clear(&tmp);
return rv;
}
int
PrioVerifier_isValid(const_PrioVerifier v, const_PrioPacketVerify2 pA,
const_PrioPacketVerify2 pB)
{
SECStatus rv = SECSuccess;
mp_int res;
MP_DIGITS(&res) = NULL;
MP_CHECKC(mp_init(&res));
// Add up the shares of the output wire value and
// ensure that the sum is equal to zero, which indicates
// that
// f(r) * g(r) == h(r).
MP_CHECKC(
mp_addmod(&pA->share_out, &pB->share_out, &v->s->cfg->modulus, &res));
rv = (mp_cmp_d(&res, 0) == 0) ? SECSuccess : SECFailure;
cleanup:
mp_clear(&res);
return rv;
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __SERVER_H__
#define __SERVER_H__
#include "mparray.h"
#include "prg.h"
#include "share.h"
struct prio_total_share
{
PrioServerId idx;
MPArray data_shares;
};
struct prio_server
{
const_PrioConfig cfg;
PrioServerId idx;
// Sever's private decryption key
PrivateKey priv_key;
// The accumulated data values from the clients.
MPArray data_shares;
// PRG used to generate randomness for checking the client
// data packets. Both servers initialize this PRG with the
// same shared seed.
PRG prg;
};
struct prio_verifier
{
PrioServer s;
PrioPacketClient clientp;
MPArray data_sharesB;
MPArray h_pointsB;
mp_int share_fR;
mp_int share_gR;
mp_int share_hR;
mp_int share_out;
};
struct prio_packet_verify1
{
mp_int share_d;
mp_int share_e;
};
struct prio_packet_verify2
{
mp_int share_out;
};
#endif /* __SERVER_H__ */

View File

@ -1,106 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <mprio.h>
#include "rand.h"
#include "share.h"
#include "util.h"
SECStatus
share_int(const struct prio_config* cfg, const mp_int* src, mp_int* shareA,
mp_int* shareB)
{
SECStatus rv;
P_CHECK(rand_int(shareA, &cfg->modulus));
MP_CHECK(mp_submod(src, shareA, &cfg->modulus, shareB));
return rv;
}
BeaverTriple
BeaverTriple_new(void)
{
BeaverTriple triple = malloc(sizeof *triple);
if (!triple)
return NULL;
MP_DIGITS(&triple->a) = NULL;
MP_DIGITS(&triple->b) = NULL;
MP_DIGITS(&triple->c) = NULL;
SECStatus rv = SECSuccess;
MP_CHECKC(mp_init(&triple->a));
MP_CHECKC(mp_init(&triple->b));
MP_CHECKC(mp_init(&triple->c));
cleanup:
if (rv != SECSuccess) {
BeaverTriple_clear(triple);
return NULL;
}
return triple;
}
void
BeaverTriple_clear(BeaverTriple triple)
{
if (!triple)
return;
mp_clear(&triple->a);
mp_clear(&triple->b);
mp_clear(&triple->c);
free(triple);
}
SECStatus
BeaverTriple_set_rand(const struct prio_config* cfg,
struct beaver_triple* triple_1,
struct beaver_triple* triple_2)
{
SECStatus rv = SECSuccess;
// TODO: Can shorten this code using share_int()
// We need that
// (a1 + a2)(b1 + b2) = c1 + c2 (mod p)
P_CHECK(rand_int(&triple_1->a, &cfg->modulus));
P_CHECK(rand_int(&triple_1->b, &cfg->modulus));
P_CHECK(rand_int(&triple_2->a, &cfg->modulus));
P_CHECK(rand_int(&triple_2->b, &cfg->modulus));
// We are trying to be a little clever here to avoid the use of temp
// variables.
// c1 = a1 + a2
MP_CHECK(mp_addmod(&triple_1->a, &triple_2->a, &cfg->modulus, &triple_1->c));
// c2 = b1 + b2
MP_CHECK(mp_addmod(&triple_1->b, &triple_2->b, &cfg->modulus, &triple_2->c));
// c1 = c1 * c2 = (a1 + a2) (b1 + b2)
MP_CHECK(mp_mulmod(&triple_1->c, &triple_2->c, &cfg->modulus, &triple_1->c));
// Set c2 to random blinding value
MP_CHECK(rand_int(&triple_2->c, &cfg->modulus));
// c1 = c1 - c2
MP_CHECK(mp_submod(&triple_1->c, &triple_2->c, &cfg->modulus, &triple_1->c));
// Now we should have random tuples satisfying:
// (a1 + a2) (b1 + b2) = c1 + c2
return rv;
}
bool
BeaverTriple_areEqual(const_BeaverTriple t1, const_BeaverTriple t2)
{
return (mp_cmp(&t1->a, &t2->a) == 0 && mp_cmp(&t1->b, &t2->b) == 0 &&
mp_cmp(&t1->c, &t2->c) == 0);
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __SHARE_H__
#define __SHARE_H__
#include <mpi.h>
#include "config.h"
struct beaver_triple
{
mp_int a;
mp_int b;
mp_int c;
};
typedef struct beaver_triple* BeaverTriple;
typedef const struct beaver_triple* const_BeaverTriple;
/*
* Use secret sharing to split the int src into two shares.
* The mp_ints must be initialized.
*/
SECStatus share_int(const_PrioConfig cfg, const mp_int* src, mp_int* shareA,
mp_int* shareB);
/*
* Prio uses Beaver triples to implement one step of the
* client data validation routine. A Beaver triple is just
* a sharing of random values a, b, c such that
* a * b = c
*/
BeaverTriple BeaverTriple_new(void);
void BeaverTriple_clear(BeaverTriple t);
SECStatus BeaverTriple_set_rand(const_PrioConfig cfg, BeaverTriple triple_a,
BeaverTriple triple_b);
bool BeaverTriple_areEqual(const_BeaverTriple t1, const_BeaverTriple t2);
#endif /* __SHARE_H__ */

View File

@ -1,112 +0,0 @@
/*
* Copyright (c) 2018, Henry Corrigan-Gibbs
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef __UTIL_H__
#define __UTIL_H__
#include <mpi.h>
#include <mprio.h>
// Minimum of two values
#define MIN(a, b) ((a) < (b) ? (a) : (b))
// Check a Prio error code and return failure if the call fails.
#define P_CHECK(s) \
do { \
if ((rv = (s)) != SECSuccess) \
return rv; \
} while (0);
// Check an allocation that should not return NULL. If the allocation returns
// NULL, set the return value and jump to the cleanup label to free memory.
#define P_CHECKA(s) \
do { \
if ((s) == NULL) { \
rv = SECFailure; \
goto cleanup; \
} \
} while (0);
// Check a Prio library call that should return SECSuccess. If it doesn't,
// jump to the cleanup label.
#define P_CHECKC(s) \
do { \
if ((rv = (s)) != SECSuccess) { \
goto cleanup; \
} \
} while (0);
// Check a boolean that should be true. If it not,
// jump to the cleanup label.
#define P_CHECKCB(s) \
do { \
if (!(s)) { \
rv = SECFailure; \
goto cleanup; \
} \
} while (0);
// Check an MPI library call and return failure if it fails.
#define MP_CHECK(s) \
do { \
if ((s) != MP_OKAY) \
return SECFailure; \
} while (0);
// Check a msgpack object unpacked correctly. If
// not, jump to the cleanup label.
#define UP_CHECKC(s) \
do { \
int r = (s); \
if (r != MSGPACK_UNPACK_SUCCESS && r != MSGPACK_UNPACK_EXTRA_BYTES) { \
rv = SECFailure; \
goto cleanup; \
} \
} while (0);
// Check an MPI library call. If it fails, set the return code and jump
// to the cleanup label.
#define MP_CHECKC(s) \
do { \
if ((s) != MP_OKAY) { \
rv = SECFailure; \
goto cleanup; \
} \
} while (0);
static inline int
next_power_of_two(int val)
{
int i = val;
int out = 0;
for (; i > 0; i >>= 1) {
out++;
}
int pow = 1 << out;
return (pow > 1 && pow / 2 == val) ? val : pow;
}
/*
* Return a mask that masks out all of the zero bits
*/
static inline unsigned char
msb_mask(unsigned char val)
{
unsigned char mask;
for (mask = 0x00; (val & mask) != val; mask = (mask << 1) + 1)
;
return mask;
}
/*
* Specify that a parameter should be unused.
*/
#define UNUSED(x) (void)(x)
#endif /* __UTIL_H__ */

View File

@ -1,30 +0,0 @@
#!/bin/sh
# Script to update the mozilla in-tree copy of the libprio library.
# Run this within the /third_party/libprio directory of the source tree.
MY_TEMP_DIR=`mktemp -d -t libprio_update.XXXXXX` || exit 1
COMMIT="52643cefe6662b4099e16a40a057cb60651ab001"
git clone -n https://github.com/mozilla/libprio ${MY_TEMP_DIR}/libprio
git -C ${MY_TEMP_DIR}/libprio checkout ${COMMIT}
FILES="include prio"
VERSION=$(git -C ${MY_TEMP_DIR}/libprio describe --tags)
perl -p -i -e "s/Current version: \S+ \[commit [0-9a-f]{40}\]/Current version: ${VERSION} [commit ${COMMIT}]/" README-mozilla
for f in $FILES; do
rm -rf $f
mv ${MY_TEMP_DIR}/libprio/$f $f
done
rm -rf ${MY_TEMP_DIR}
hg revert -r . moz.build
hg addremove .
echo "###"
echo "### Updated libprio to $COMMIT."
echo "### Remember to verify and commit the changes to source control!"
echo "###"

View File

@ -26,18 +26,6 @@
namespace mozilla {
typedef Telemetry::OriginMetricID OriginMetricID;
// sync with TelemetryOriginData.inc
const nsLiteralCString ContentBlockingLog::kDummyOriginHash = "PAGELOAD"_ns;
// randomly choose 1% users included in the content blocking measurement
// based on their client id.
static constexpr double kRatioReportUser = 0.01;
// randomly choose 0.14% documents when the page is unload.
static constexpr double kRatioReportDocument = 0.0014;
namespace {
StaticAutoPtr<nsCString> gEmailWebAppDomainsPref;
@ -54,72 +42,6 @@ void EmailWebAppDomainPrefChangeCallback(const char* aPrefName, void*) {
} // namespace
static bool IsReportingPerUserEnabled() {
MOZ_ASSERT(NS_IsMainThread());
static Maybe<bool> sIsReportingEnabled;
if (sIsReportingEnabled.isSome()) {
return sIsReportingEnabled.value();
}
nsAutoCString cachedClientId;
if (NS_FAILED(Preferences::GetCString("toolkit.telemetry.cachedClientID",
cachedClientId))) {
return false;
}
nsID clientId;
if (!clientId.Parse(cachedClientId.get())) {
return false;
}
/**
* UUID might not be uniform-distributed (although some part of it could be).
* In order to generate more random result, usually we use a hash function,
* but here we hope it's fast and doesn't have to be cryptographic-safe.
* |XorShift128PlusRNG| looks like a good alternative because it takes a
* 128-bit data as its seed and always generate identical sequence if the
* initial seed is the same.
*/
static_assert(sizeof(nsID) == 16, "nsID is 128-bit");
uint64_t* init = reinterpret_cast<uint64_t*>(&clientId);
non_crypto::XorShift128PlusRNG rng(init[0], init[1]);
sIsReportingEnabled.emplace(rng.nextDouble() <= kRatioReportUser);
return sIsReportingEnabled.value();
}
static bool IsReportingPerDocumentEnabled() {
constexpr double boundary =
kRatioReportDocument * double(std::numeric_limits<uint64_t>::max());
Maybe<uint64_t> randomNum = RandomUint64();
return randomNum.isSome() && randomNum.value() <= boundary;
}
static bool IsReportingEnabled() {
if (StaticPrefs::telemetry_origin_telemetry_test_mode_enabled()) {
return true;
} else if (!StaticPrefs::
privacy_trackingprotection_origin_telemetry_enabled()) {
return false;
}
return IsReportingPerUserEnabled() && IsReportingPerDocumentEnabled();
}
static void ReportOriginSingleHash(OriginMetricID aId,
const nsACString& aOrigin) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
LOG(("ReportOriginSingleHash metric=%s",
Telemetry::MetricIDToString[static_cast<uint32_t>(aId)]));
LOG(("ReportOriginSingleHash origin=%s", PromiseFlatCString(aOrigin).get()));
Telemetry::RecordOrigin(aId, aOrigin);
}
Maybe<uint32_t> ContentBlockingLog::RecordLogParent(
const nsACString& aOrigin, uint32_t aType, bool aBlocked,
const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
@ -240,82 +162,6 @@ void ContentBlockingLog::ReportLog(nsIPrincipal* aFirstPartyPrincipal) {
trackingDBService->RecordContentBlockingLog(Stringify());
}
void ContentBlockingLog::ReportOrigins() {
if (!IsReportingEnabled()) {
return;
}
LOG(("ContentBlockingLog::ReportOrigins [this=%p]", this));
const bool testMode =
StaticPrefs::telemetry_origin_telemetry_test_mode_enabled();
OriginMetricID metricId =
testMode ? OriginMetricID::ContentBlocking_Blocked_TestOnly
: OriginMetricID::ContentBlocking_Blocked;
ReportOriginSingleHash(metricId, kDummyOriginHash);
nsTArray<HashNumber> lookupTable;
for (const auto& originEntry : mLog) {
if (!originEntry.mData) {
continue;
}
for (const auto& logEntry : Reversed(originEntry.mData->mLogs)) {
if ((logEntry.mType !=
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER &&
logEntry.mType !=
nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
logEntry.mTrackingFullHashes.IsEmpty()) {
continue;
}
const bool isBlocked = logEntry.mBlocked;
Maybe<StorageAccessPermissionGrantedReason> reason = logEntry.mReason;
metricId = testMode ? OriginMetricID::ContentBlocking_Blocked_TestOnly
: OriginMetricID::ContentBlocking_Blocked;
if (!isBlocked) {
MOZ_ASSERT(reason.isSome());
switch (reason.value()) {
case StorageAccessPermissionGrantedReason::eStorageAccessAPI:
metricId =
testMode
? OriginMetricID::
ContentBlocking_StorageAccessAPIExempt_TestOnly
: OriginMetricID::ContentBlocking_StorageAccessAPIExempt;
break;
case StorageAccessPermissionGrantedReason::
eOpenerAfterUserInteraction:
metricId =
testMode
? OriginMetricID::
ContentBlocking_OpenerAfterUserInteractionExempt_TestOnly
: OriginMetricID::
ContentBlocking_OpenerAfterUserInteractionExempt;
break;
case StorageAccessPermissionGrantedReason::eOpener:
metricId =
testMode ? OriginMetricID::ContentBlocking_OpenerExempt_TestOnly
: OriginMetricID::ContentBlocking_OpenerExempt;
break;
default:
MOZ_ASSERT_UNREACHABLE(
"Unknown StorageAccessPermissionGrantedReason");
}
}
for (const auto& hash : logEntry.mTrackingFullHashes) {
HashNumber key = AddToHash(HashString(hash.get(), hash.Length()),
static_cast<uint32_t>(metricId));
if (lookupTable.Contains(key)) {
continue;
}
lookupTable.AppendElement(key);
ReportOriginSingleHash(metricId, hash);
}
break;
}
}
}
void ContentBlockingLog::ReportEmailTrackingLog(
nsIPrincipal* aFirstPartyPrincipal) {
MOZ_ASSERT(XRE_IsParentProcess());

View File

@ -95,7 +95,6 @@ class ContentBlockingLog final {
RecordLogInternal(aOrigin, aType, aBlocked, aReason, aTrackingFullHashes);
}
void ReportOrigins();
void ReportLog(nsIPrincipal* aFirstPartyPrincipal);
void ReportEmailTrackingLog(nsIPrincipal* aFirstPartyPrincipal);

View File

@ -1,48 +0,0 @@
/* This file might later be auto-generated */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_TelemetryOriginEnums_h
#define mozilla_TelemetryOriginEnums_h
#include <stdint.h>
namespace mozilla {
namespace Telemetry {
enum class OriginMetricID : uint32_t {
// category: content.blocking
ContentBlocking_Blocked = 0,
ContentBlocking_Blocked_TestOnly = 1,
ContentBlocking_StorageAccessAPIExempt = 2,
ContentBlocking_StorageAccessAPIExempt_TestOnly = 3,
ContentBlocking_OpenerAfterUserInteractionExempt = 4,
ContentBlocking_OpenerAfterUserInteractionExempt_TestOnly = 5,
ContentBlocking_OpenerExempt = 6,
ContentBlocking_OpenerExempt_TestOnly = 7,
// category: telemetry.test
TelemetryTest_Test1 = 8,
TelemetryTest_Test2 = 9,
// meta
Count = 10,
};
static const char* const MetricIDToString[10] = {
"content.blocking_blocked",
"content.blocking_blocked_TESTONLY",
"content.blocking_storage_access_api_exempt",
"content.blocking_storage_access_api_exempt_TESTONLY",
"content.blocking_opener_after_user_interaction_exempt",
"content.blocking_opener_after_user_interaction_exempt_TESTONLY",
"content.blocking_opener_exempt",
"content.blocking_opener_exempt_TESTONLY",
"telemetry.test_test1",
"telemetry.test_test2",
};
} // namespace Telemetry
} // namespace mozilla
#endif // mozilla_TelemetryOriginEnums_h

View File

@ -58,7 +58,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
UpdatePing: "resource://gre/modules/UpdatePing.sys.mjs",
TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs",
TelemetryEventPing: "resource://gre/modules/EventPing.sys.mjs",
TelemetryPrioPing: "resource://gre/modules/PrioPing.sys.mjs",
UninstallPing: "resource://gre/modules/UninstallPing.sys.mjs",
});
@ -870,7 +869,6 @@ var Impl = {
}
lazy.TelemetryEventPing.startup();
lazy.TelemetryPrioPing.startup();
if (uploadEnabled) {
await this.saveUninstallPing().catch(e =>
@ -920,7 +918,6 @@ var Impl = {
lazy.UpdatePing.shutdown();
lazy.TelemetryEventPing.shutdown();
await lazy.TelemetryPrioPing.shutdown();
// Shutdown the sync ping if it is initialized - this is likely, but not
// guaranteed, to submit a "shutdown" sync ping.

View File

@ -9,9 +9,6 @@ import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
// Other pings
import { TelemetryPrioPing } from "resource://gre/modules/PrioPing.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetters(lazy, {
@ -360,7 +357,7 @@ export var TelemetryScheduler = {
this._log.trace("_schedulerTickLogic - Periodic ping due.");
this._lastPeriodicPingTime = now;
// Send other pings.
TelemetryPrioPing.periodicPing();
// ...currently no other pings exist
}
if (shouldSendDaily) {

View File

@ -75,10 +75,6 @@ export var TelemetryUtils = {
EventPingMinimumFrequency: "toolkit.telemetry.eventping.minimumFrequency",
EventPingMaximumFrequency: "toolkit.telemetry.eventping.maximumFrequency",
// Prio Ping Preferences
PrioPingEnabled: "toolkit.telemetry.prioping.enabled",
PrioPingDataLimit: "toolkit.telemetry.prioping.dataLimit",
// Data reporting Preferences
AcceptedPolicyDate: "datareporting.policy.dataSubmissionPolicyNotifiedTime",
AcceptedPolicyVersion:

View File

@ -89,7 +89,6 @@
#include "TelemetryCommon.h"
#include "TelemetryEvent.h"
#include "TelemetryHistogram.h"
#include "TelemetryOrigin.h"
#include "TelemetryScalar.h"
#include "TelemetryUserInteraction.h"
@ -248,10 +247,6 @@ TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf),
"Memory used by Telemetry Event data");
COLLECT_REPORT("explicit/telemetry/origin/data",
TelemetryOrigin::SizeOfIncludingThis(aMallocSizeOf),
"Memory used by Telemetry Origin data");
#undef COLLECT_REPORT
return NS_OK;
@ -1150,7 +1145,7 @@ already_AddRefed<nsITelemetry> TelemetryImpl::CreateTelemetryInstance() {
// Only record events from the parent process.
TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(),
XRE_IsParentProcess());
TelemetryOrigin::InitializeGlobalState();
// Currently, only UserInteractions from the parent process are recorded.
TelemetryUserInteraction::InitializeGlobalState(useTelemetry, useTelemetry);
@ -1189,7 +1184,7 @@ void TelemetryImpl::ShutdownTelemetry() {
TelemetryHistogram::DeInitializeGlobalState();
TelemetryScalar::DeInitializeGlobalState();
TelemetryEvent::DeInitializeGlobalState();
TelemetryOrigin::DeInitializeGlobalState();
TelemetryUserInteraction::DeInitializeGlobalState();
TelemetryIPCAccumulator::DeInitializeGlobalState();
}
@ -1641,48 +1636,6 @@ TelemetryImpl::SetEventRecordingEnabled(const nsACString& aCategory,
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::GetOriginSnapshot(bool aClear, JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
return TelemetryOrigin::GetOriginSnapshot(aClear, aCx, aResult);
}
NS_IMETHODIMP
TelemetryImpl::GetEncodedOriginSnapshot(bool aClear, JSContext* aCx,
Promise** aResult) {
if (!XRE_IsParentProcess()) {
return NS_ERROR_FAILURE;
}
NS_ENSURE_ARG_POINTER(aResult);
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
ErrorResult erv;
RefPtr<Promise> promise = Promise::Create(global, erv);
if (NS_WARN_IF(erv.Failed())) {
return erv.StealNSResult();
}
// TODO: Put this all on a Worker Thread
JS::Rooted<JS::Value> snapshot(aCx);
nsresult rv;
rv = TelemetryOrigin::GetEncodedOriginSnapshot(aClear, aCx, &snapshot);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
promise->MaybeResolve(snapshot);
promise.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::ClearOrigins() {
TelemetryOrigin::ClearOrigins();
return NS_OK;
}
NS_IMETHODIMP
TelemetryImpl::FlushBatchedChildTelemetry() {
TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
@ -2065,11 +2018,6 @@ void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled) {
TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
}
void RecordOrigin(mozilla::Telemetry::OriginMetricID aId,
const nsACString& aOrigin) {
TelemetryOrigin::RecordOrigin(aId, aOrigin);
}
void ShutdownTelemetry() { TelemetryImpl::ShutdownTelemetry(); }
} // namespace mozilla::Telemetry

View File

@ -9,7 +9,6 @@
#include "mozilla/Maybe.h"
#include "mozilla/TelemetryEventEnums.h"
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TelemetryOriginEnums.h"
#include "mozilla/TelemetryScalarEnums.h"
#include "mozilla/TimeStamp.h"
#include "nsString.h"
@ -572,18 +571,6 @@ void RecordEvent(mozilla::Telemetry::EventID aId,
*/
void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled);
/**
* YOU PROBABLY SHOULDN'T USE THIS.
* THIS IS AN EXPERIMENTAL API NOT YET READY FOR GENERAL USE.
*
* Records that the metric is true for the stated origin.
*
* @param aId the metric.
* @param aOrigin the origin on which to record the metric as true.
*/
void RecordOrigin(mozilla::Telemetry::OriginMetricID aId,
const nsACString& aOrigin);
} // namespace Telemetry
} // namespace mozilla

View File

@ -1,618 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TelemetryOrigin.h"
#include "nsIObserverService.h"
#include "nsPrintfCString.h"
#include "nsTArray.h"
#include "TelemetryCommon.h"
#include "TelemetryOriginEnums.h"
#include "js/Array.h" // JS::NewArrayObject
#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
#include "mozilla/Atomics.h"
#include "mozilla/Base64.h"
#include "mozilla/dom/PrioEncoder.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/Tuple.h"
#include "mozilla/UniquePtr.h"
#include <cmath>
#include <type_traits>
using mozilla::Get;
using mozilla::MakeTuple;
using mozilla::MakeUnique;
using mozilla::MallocSizeOf;
using mozilla::StaticMutex;
using mozilla::StaticMutexAutoLock;
using mozilla::Tuple;
using mozilla::UniquePtr;
using mozilla::dom::PrioEncoder;
using mozilla::Telemetry::OriginMetricID;
using mozilla::Telemetry::Common::ToJSString;
/***********************************************************************
*
* Firefox Origin Telemetry
* Docs:
* https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/origin.html
*
* Origin Telemetry stores pairs of information (metric, origin) which boils
* down to "$metric happened on $origin".
*
* Prio can only encode up-to-2046-length bit vectors. The process of
* transforming these pairs of information into bit vectors is called "App
* Encoding". The bit vectors are then "Prio Encoded" into binary goop. The
* binary goop is then "Base64 Encoded" into strings.
*
*/
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// PRIVATE TYPES
namespace {
class OriginMetricIDHashKey : public PLDHashEntryHdr {
public:
typedef const OriginMetricID& KeyType;
typedef const OriginMetricID* KeyTypePointer;
explicit OriginMetricIDHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
OriginMetricIDHashKey(OriginMetricIDHashKey&& aOther)
: PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {}
~OriginMetricIDHashKey() = default;
KeyType GetKey() const { return mValue; }
bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey) {
return static_cast<std::underlying_type<OriginMetricID>::type>(*aKey);
}
enum { ALLOW_MEMMOVE = true };
private:
const OriginMetricID mValue;
};
} // anonymous namespace
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// PRIVATE STATE, SHARED BY ALL THREADS
//
// Access for all of this state (except gInitDone) must be guarded by
// gTelemetryOriginMutex.
namespace {
// This is a StaticMutex rather than a plain Mutex (1) so that
// it gets initialised in a thread-safe manner the first time
// it is used, and (2) because it is never de-initialised, and
// a normal Mutex would show up as a leak in BloatView. StaticMutex
// also has the "OffTheBooks" property, so it won't show as a leak
// in BloatView.
// Another reason to use a StaticMutex instead of a plain Mutex is
// that, due to the nature of Telemetry, we cannot rely on having a
// mutex initialized in InitializeGlobalState. Unfortunately, we
// cannot make sure that no other function is called before this point.
static StaticMutex gTelemetryOriginMutex MOZ_UNANNOTATED;
typedef nsTArray<Tuple<const char*, const char*>> OriginHashesList;
UniquePtr<OriginHashesList> gOriginHashesList;
typedef nsTHashMap<nsCStringHashKey, size_t> OriginToIndexMap;
UniquePtr<OriginToIndexMap> gOriginToIndexMap;
typedef nsTHashMap<nsCStringHashKey, size_t> HashToIndexMap;
UniquePtr<HashToIndexMap> gHashToIndexMap;
typedef nsTHashMap<nsCStringHashKey, uint32_t> OriginBag;
typedef nsTHashMap<OriginMetricIDHashKey, OriginBag> IdToOriginBag;
UniquePtr<IdToOriginBag> gMetricToOriginBag;
mozilla::Atomic<bool, mozilla::Relaxed> gInitDone(false);
// Useful for app-encoded data
typedef nsTArray<std::pair<OriginMetricID, nsTArray<nsTArray<bool>>>>
IdBoolsPairArray;
// Prio has a maximum supported number of bools it can encode at a time.
// This means a single metric may result in several encoded payloads if the
// number of origins exceeds the number of bools.
// Each encoded payload corresponds to an element in the `prioData` array in the
// "prio" ping.
// This number is the number of encoded payloads needed per metric, and is
// equivalent to "how many bitvectors do we need to encode this whole list of
// origins?"
static uint32_t gPrioDatasPerMetric;
// The number of "meta-origins": in-band metadata about origin telemetry.
// Currently 1: the "unknown origin recorded" meta-origin.
static uint32_t kNumMetaOrigins = 1;
constexpr auto kUnknownOrigin = "__UNKNOWN__"_ns;
} // namespace
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// PRIVATE: thread-safe helpers
namespace {
const char* GetNameForMetricID(OriginMetricID aId) {
MOZ_ASSERT(aId < OriginMetricID::Count);
return mozilla::Telemetry::MetricIDToString[static_cast<uint32_t>(aId)];
}
// Calculates the number of `prioData` elements we'd need if we were asked for
// an encoded snapshot right now.
uint32_t PrioDataCount(const StaticMutexAutoLock& lock) {
uint32_t count = 0;
for (const auto& origins : gMetricToOriginBag->Values()) {
uint32_t maxOriginCount = 0;
for (const auto& data : origins.Values()) {
maxOriginCount = std::max(maxOriginCount, data);
}
count += gPrioDatasPerMetric * maxOriginCount;
}
return count;
}
// Takes the storage and turns it into bool arrays for Prio to encode, turning
// { metric1: [origin1, origin2, ...], ...}
// into
// [(metric1, [[shard1], [shard2], ...]), ...]
// Note: if an origin is present multiple times for a given metric, we must
// generate multiple (id, boolvectors) pairs so that they are all reported.
// Meaning
// { metric1: [origin1, origin2, origin2] }
// must turn into (with a pretend gNumBooleans of 1)
// [(metric1, [[1], [1]]), (metric1, [[0], [1]])]
nsresult AppEncodeTo(const StaticMutexAutoLock& lock,
IdBoolsPairArray& aResult) {
for (const auto& bagEntry : *gMetricToOriginBag) {
OriginMetricID id = bagEntry.GetKey();
const OriginBag& bag = bagEntry.GetData();
uint32_t generation = 1;
uint32_t maxGeneration = 1;
do {
// Fill in the result bool vectors with `false`s.
nsTArray<nsTArray<bool>> metricData(gPrioDatasPerMetric);
metricData.SetLength(gPrioDatasPerMetric);
for (size_t i = 0; i < metricData.Length() - 1; ++i) {
metricData[i].SetLength(PrioEncoder::gNumBooleans);
for (auto& metricDatum : metricData[i]) {
metricDatum = false;
}
}
auto& lastArray = metricData[metricData.Length() - 1];
lastArray.SetLength((gOriginHashesList->Length() + kNumMetaOrigins) %
PrioEncoder::gNumBooleans);
for (auto& metricDatum : lastArray) {
metricDatum = false;
}
for (const auto& originEntry : bag) {
uint32_t originCount = originEntry.GetData();
if (originCount >= generation) {
maxGeneration = std::max(maxGeneration, originCount);
const nsACString& origin = originEntry.GetKey();
size_t index;
if (!gOriginToIndexMap->Get(origin, &index)) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(index < (gOriginHashesList->Length() + kNumMetaOrigins));
size_t shardIndex = index / PrioEncoder::gNumBooleans;
MOZ_ASSERT(shardIndex < metricData.Length());
MOZ_ASSERT(index % PrioEncoder::gNumBooleans <
metricData[shardIndex].Length());
metricData[shardIndex][index % PrioEncoder::gNumBooleans] = true;
}
}
aResult.EmplaceBack(id, std::move(metricData));
} while (generation++ < maxGeneration);
}
return NS_OK;
}
} // anonymous namespace
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryOrigin::
void TelemetryOrigin::InitializeGlobalState() {
if (!XRE_IsParentProcess()) {
return;
}
StaticMutexAutoLock locker(gTelemetryOriginMutex);
MOZ_ASSERT(!gInitDone,
"TelemetryOrigin::InitializeGlobalState "
"may only be called once");
// The contents and order of the arrays that follow matter.
// Both ensure a consistent app-encoding.
static const char sOriginStrings[] = {
#define ORIGIN(origin, hash) origin "\0"
#include "TelemetryOriginData.inc"
#undef ORIGIN
};
static const char sHashStrings[] = {
#define ORIGIN(origin, hash) hash "\0"
#include "TelemetryOriginData.inc"
#undef ORIGIN
};
struct OriginHashLengths {
uint8_t originLength;
uint8_t hashLength;
};
static const OriginHashLengths sOriginHashLengths[] = {
#define ORIGIN(origin, hash) {sizeof(origin), sizeof(hash)},
#include "TelemetryOriginData.inc"
#undef ORIGIN
};
static const size_t kNumOrigins = MOZ_ARRAY_LENGTH(sOriginHashLengths);
gOriginHashesList = MakeUnique<OriginHashesList>(kNumOrigins);
gPrioDatasPerMetric =
ceil(static_cast<double>(kNumOrigins + kNumMetaOrigins) /
PrioEncoder::gNumBooleans);
gOriginToIndexMap =
MakeUnique<OriginToIndexMap>(kNumOrigins + kNumMetaOrigins);
gHashToIndexMap = MakeUnique<HashToIndexMap>(kNumOrigins);
size_t originOffset = 0;
size_t hashOffset = 0;
for (size_t i = 0; i < kNumOrigins; ++i) {
const char* origin = &sOriginStrings[originOffset];
const char* hash = &sHashStrings[hashOffset];
MOZ_ASSERT(!kUnknownOrigin.Equals(origin),
"Unknown origin literal is reserved in Origin Telemetry");
gOriginHashesList->AppendElement(MakeTuple(origin, hash));
const size_t originLength = sOriginHashLengths[i].originLength;
const size_t hashLength = sOriginHashLengths[i].hashLength;
originOffset += originLength;
hashOffset += hashLength;
// -1 to leave off the null terminators.
gOriginToIndexMap->InsertOrUpdate(
nsDependentCString(origin, originLength - 1), i);
gHashToIndexMap->InsertOrUpdate(nsDependentCString(hash, hashLength - 1),
i);
}
// Add the meta-origin for tracking recordings to untracked origins.
gOriginToIndexMap->InsertOrUpdate(kUnknownOrigin,
gOriginHashesList->Length());
gMetricToOriginBag = MakeUnique<IdToOriginBag>();
// This map shouldn't change at runtime, so make debug builds complain
// if it tries.
gOriginToIndexMap->MarkImmutable();
gHashToIndexMap->MarkImmutable();
gInitDone = true;
}
void TelemetryOrigin::DeInitializeGlobalState() {
if (!XRE_IsParentProcess()) {
return;
}
StaticMutexAutoLock locker(gTelemetryOriginMutex);
MOZ_ASSERT(gInitDone);
if (!gInitDone) {
return;
}
gOriginHashesList = nullptr;
gOriginToIndexMap = nullptr;
gHashToIndexMap = nullptr;
gMetricToOriginBag = nullptr;
gInitDone = false;
}
nsresult TelemetryOrigin::RecordOrigin(OriginMetricID aId,
const nsACString& aOrigin) {
if (!XRE_IsParentProcess()) {
return NS_ERROR_FAILURE;
}
uint32_t prioDataCount;
{
StaticMutexAutoLock locker(gTelemetryOriginMutex);
// Common Telemetry error-handling practices for recording functions:
// only illegal calls return errors whereas merely incorrect ones are mutely
// ignored.
if (!gInitDone) {
return NS_OK;
}
size_t index;
nsCString origin(aOrigin);
if (gHashToIndexMap->Get(aOrigin, &index)) {
MOZ_ASSERT(aOrigin.Equals(Get<1>((*gOriginHashesList)[index])));
origin = Get<0>((*gOriginHashesList)[index]);
}
if (!gOriginToIndexMap->Contains(origin)) {
// Only record one unknown origin per metric per snapshot.
// (otherwise we may get swamped and blow our data budget.)
if (gMetricToOriginBag->Contains(aId) &&
gMetricToOriginBag->LookupOrInsert(aId).Contains(kUnknownOrigin)) {
return NS_OK;
}
origin = kUnknownOrigin;
}
auto& originBag = gMetricToOriginBag->LookupOrInsert(aId);
originBag.LookupOrInsert(origin)++;
prioDataCount = PrioDataCount(locker);
}
static uint32_t sPrioPingLimit =
mozilla::Preferences::GetUint("toolkit.telemetry.prioping.dataLimit", 10);
if (prioDataCount >= sPrioPingLimit) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
// Ensure we don't notify while holding the lock in case of synchronous
// dispatch. May deadlock ourselves if we then trigger a snapshot.
os->NotifyObservers(nullptr, "origin-telemetry-storage-limit-reached",
nullptr);
}
}
return NS_OK;
}
nsresult TelemetryOrigin::GetOriginSnapshot(
bool aClear, JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
if (NS_WARN_IF(!XRE_IsParentProcess())) {
return NS_ERROR_FAILURE;
}
if (!gInitDone) {
return NS_OK;
}
// Step 1: Grab the lock, copy into stack-local storage, optionally clear.
IdToOriginBag copy;
{
StaticMutexAutoLock locker(gTelemetryOriginMutex);
if (aClear) {
// I'd really prefer to clear after we're sure the snapshot didn't go
// awry, but we can't hold a lock preventing recording while using JS
// APIs. And replaying any interleaving recording sounds like too much
// squeeze for not enough juice.
gMetricToOriginBag->SwapElements(copy);
} else {
for (const auto& entry : *gMetricToOriginBag) {
copy.InsertOrUpdate(entry.GetKey(), entry.GetData().Clone());
}
}
}
// Step 2: Without the lock, generate JS datastructure for snapshotting
JS::Rooted<JSObject*> rootObj(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!rootObj)) {
return NS_ERROR_FAILURE;
}
aResult.setObject(*rootObj);
for (const auto& entry : copy) {
JS::Rooted<JSObject*> originsObj(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!originsObj)) {
return NS_ERROR_FAILURE;
}
if (!JS_DefineProperty(aCx, rootObj, GetNameForMetricID(entry.GetKey()),
originsObj, JSPROP_ENUMERATE)) {
NS_WARNING("Failed to define property in origin snapshot.");
return NS_ERROR_FAILURE;
}
for (const auto& originEntry : entry.GetData()) {
if (!JS_DefineProperty(aCx, originsObj,
nsPromiseFlatCString(originEntry.GetKey()).get(),
originEntry.GetData(), JSPROP_ENUMERATE)) {
NS_WARNING("Failed to define origin and count in snapshot.");
return NS_ERROR_FAILURE;
}
}
}
return NS_OK;
}
nsresult TelemetryOrigin::GetEncodedOriginSnapshot(
bool aClear, JSContext* aCx, JS::MutableHandle<JS::Value> aSnapshot) {
if (!XRE_IsParentProcess()) {
return NS_ERROR_FAILURE;
}
if (!gInitDone) {
return NS_OK;
}
// Step 1: Take the lock and app-encode. Optionally clear.
nsresult rv;
IdBoolsPairArray appEncodedMetricData;
{
StaticMutexAutoLock lock(gTelemetryOriginMutex);
rv = AppEncodeTo(lock, appEncodedMetricData);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aClear) {
// I'd really prefer to clear after we're sure the snapshot didn't go
// awry, but we can't hold a lock preventing recording while using JS
// APIs. And replaying any interleaving recording sounds like too much
// squeeze for not enough juice.
gMetricToOriginBag->Clear();
}
}
// Step 2: Don't need the lock to prio-encode and base64-encode
nsTArray<std::pair<nsCString, std::pair<nsCString, nsCString>>> prioData;
for (auto& metricData : appEncodedMetricData) {
auto& boolVectors = metricData.second;
for (uint32_t i = 0; i < boolVectors.Length(); ++i) {
// "encoding" is of the form `metricName-X` where X is the shard index.
nsCString encodingName =
nsPrintfCString("%s-%u", GetNameForMetricID(metricData.first), i);
nsCString aResult;
nsCString bResult;
rv = PrioEncoder::EncodeNative(encodingName, boolVectors[i], aResult,
bResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString aBase64;
rv = mozilla::Base64Encode(aResult, aBase64);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString bBase64;
rv = mozilla::Base64Encode(bResult, bBase64);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
prioData.EmplaceBack(std::move(encodingName),
std::pair(std::move(aBase64), std::move(bBase64)));
}
}
// Step 3: Still don't need the lock to translate to JS
// The resulting data structure is:
// [{
// encoding: <encoding name>,
// prio: {
// a: <base64 string>,
// b: <base64 string>,
// },
// }, ...]
JS::Rooted<JSObject*> prioDataArray(
aCx, JS::NewArrayObject(aCx, prioData.Length()));
if (NS_WARN_IF(!prioDataArray)) {
return NS_ERROR_FAILURE;
}
uint32_t i = 0;
for (auto& prioDatum : prioData) {
JS::Rooted<JSObject*> prioDatumObj(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!prioDatumObj)) {
return NS_ERROR_FAILURE;
}
JSString* encoding = ToJSString(aCx, prioDatum.first);
JS::Rooted<JSString*> rootedEncoding(aCx, encoding);
if (NS_WARN_IF(!JS_DefineProperty(aCx, prioDatumObj, "encoding",
rootedEncoding, JSPROP_ENUMERATE))) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JSObject*> prioObj(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!prioObj)) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!JS_DefineProperty(aCx, prioDatumObj, "prio", prioObj,
JSPROP_ENUMERATE))) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JSString*> aRootStr(aCx,
ToJSString(aCx, prioDatum.second.first));
if (NS_WARN_IF(!JS_DefineProperty(aCx, prioObj, "a", aRootStr,
JSPROP_ENUMERATE))) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JSString*> bRootStr(aCx,
ToJSString(aCx, prioDatum.second.second));
if (NS_WARN_IF(!JS_DefineProperty(aCx, prioObj, "b", bRootStr,
JSPROP_ENUMERATE))) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!JS_DefineElement(aCx, prioDataArray, i++, prioDatumObj,
JSPROP_ENUMERATE))) {
return NS_ERROR_FAILURE;
}
}
aSnapshot.setObject(*prioDataArray);
return NS_OK;
}
/**
* Resets all the stored events. This is intended to be only used in tests.
*/
void TelemetryOrigin::ClearOrigins() {
StaticMutexAutoLock lock(gTelemetryOriginMutex);
if (!gInitDone) {
return;
}
gMetricToOriginBag->Clear();
}
size_t TelemetryOrigin::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
StaticMutexAutoLock locker(gTelemetryOriginMutex);
size_t n = 0;
if (!gInitDone) {
return 0;
}
n += gMetricToOriginBag->ShallowSizeOfIncludingThis(aMallocSizeOf);
for (const auto& origins : gMetricToOriginBag->Values()) {
// The string hashkey and count should both be contained by the hashtable.
n += origins.ShallowSizeOfExcludingThis(aMallocSizeOf);
}
// The string hashkey and ID should both be contained within the hashtable.
n += gOriginToIndexMap->ShallowSizeOfIncludingThis(aMallocSizeOf);
return n;
}
size_t TelemetryOrigin::SizeOfPrioDatasPerMetric() {
return gPrioDatasPerMetric;
}

View File

@ -1,46 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef TelemetryOrigin_h__
#define TelemetryOrigin_h__
#include "TelemetryOriginEnums.h"
#include "mozilla/MemoryReporting.h"
#include "js/TypeDecls.h"
#include "nsError.h"
#include "nsStringFwd.h"
// This module is internal to Telemetry. It encapsulates Telemetry's
// origin recording and storage logic. It should only be used by
// Telemetry.cpp. These functions should not be used anywhere else.
// For the public interface to Telemetry functionality, see Telemetry.h.
namespace TelemetryOrigin {
void InitializeGlobalState();
void DeInitializeGlobalState();
// C++ Recording Endpoint
nsresult RecordOrigin(mozilla::Telemetry::OriginMetricID aId,
const nsACString& aOrigin);
// JS API Endpoints.
nsresult GetOriginSnapshot(bool aClear, JSContext* aCx,
JS::MutableHandle<JS::Value> aResult);
nsresult GetEncodedOriginSnapshot(bool aClear, JSContext* aCx,
JS::MutableHandle<JS::Value> aSnapshot);
// Only to be used for testing.
void ClearOrigins();
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
// Only to be used for testing.
size_t SizeOfPrioDatasPerMetric();
} // namespace TelemetryOrigin
#endif // TelemetryOrigin_h__

File diff suppressed because it is too large Load Diff

View File

@ -671,32 +671,4 @@ interface nsITelemetry : nsISupports
*/
[implicit_jscontext]
Promise gatherMemory();
/**
* Serializes the per-origin data in plain text, optionally clearing
* the storage. Only to be used by about:telemetry.
*
* The returned structure looks like:
* { metric: {origin1: count1, origin2: count2, ...}, ...}
*
* @param aClear Whether to clear the storage. Default false.
* @return a snapshot of the per-origin data.
*/
[implicit_jscontext]
jsval getOriginSnapshot([optional] in boolean aClear);
/**
* Encodes the per-origin information then serializes it.
* Returns a Promise.
*
* @param aClear Whether to clear the storage. Default false.
* @return Promise that resolves to the serialized encoded data.
*/
[implicit_jscontext]
Promise getEncodedOriginSnapshot([optional] in boolean aClear);
/**
* Clears Firefox Origin Telemetry. Only to be used in tests.
*/
void clearOrigins();
};

View File

@ -1,166 +0,0 @@
.. _origintelemetry:
================
Origin Telemetry
================
*Origin Telemetry* is an experimental Firefox Telemetry mechanism that allows us to privately report origin-specific information in aggregate.
In short, it allows us to get exact counts of how *many* Firefox clients do certain things on specific origins without us being able to know *which* clients were doing which things on which origins.
As an example, Content Blocking would like to know which trackers Firefox blocked most frequently.
Origin Telemetry allows us to count how many times a given tracker is blocked without being able to find out which clients were visiting pages that had those trackers on them.
.. important::
This mechanism is experimental and is a prototype.
Please do not try to use this without explicit permission from the Firefox Telemetry Team, as it's really only been designed to work for Content Blocking right now.
Adding or removing Origins or Metrics is not supported in artifact builds and build faster workflows. A non-artifact Firefox build is necessary to change these lists.
This mechanism is enabled on Firefox Nightly only at present.
.. important::
Every new or changed data collection in Firefox needs a `data collection review <https://wiki.mozilla.org/Firefox/Data_Collection>`__ from a Data Steward.
Privacy
=======
To achieve the necessary goal of getting accurate counts without being able to learn which clients contributed to the counts we use a mechanism called `Prio (pdf) <https://www.usenix.org/system/files/conference/nsdi17/nsdi17-corrigan-gibbs.pdf>`_.
Prio uses cryptographic techniques to encrypt information and a proof that the information is correct, only sending the encrypted information on to be aggregated.
Only after aggregation do we learn the information we want (aggregated counts), and at no point do we learn the information we don't want (which clients contributed to the counts).
.. _origin.usage:
Using Origin Telemetry
======================
To record that something happened on a given origin, three things must happen:
1. The origin must be one of the fixed, known list of origins. ("Where" something happened)
2. The metric must be one of the fixed, known list of metrics. ("What" happened)
3. A call must be made to the Origin Telemetry API. (To let Origin Telemetry know "that" happened "there")
At present the lists of origins and metrics are hardcoded in C++.
Please consult the Firefox Telemetry Team before changing these lists.
Origins can be arbitrary byte sequences of any length.
Do not add duplicate origins to the list.
If an attempt is made to record to an unknown origin, a meta-origin ``__UNKNOWN__`` captures that it happened.
Unlike other origins where multiple recordings are considered additive ``__UNKNOWN__`` only accumulates a single value.
This is to avoid inflating the ping size in case the caller submits a lot of unknown origins for a given unit (e.g. pageload).
Metrics should be of the form ``categoryname.metric_name``.
Both ``categoryname`` and ``metric_name`` should not exceed 40 bytes (UTF-8 encoded) in length and should only contain alphanumeric character and infix underscores.
.. _origin.API:
API
===
Origin Telemetry supplies APIs for recording information into and snapshotting information out of storage.
Recording
---------
``Telemetry::RecordOrigin(aOriginMetricID, aOrigin);``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This C++ API records that a metric was true for a given origin.
For instance, maybe the user visited a page in which content from ``example.net`` was blocked.
That call might look like ``Telemetry::RecordOrigin(OriginMetricID::ContentBlocking_Blocked, "example.net"_ns)``.
Snapshotting
------------
``let snapshot = await Telemetry.getEncodedOriginSnapshot(aClear);``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This JS API provides a snapshot of the prio-encoded payload and is intended to only be used to assemble the :doc:`"prio" ping's <../data/prio-ping>` payload.
It returns a Promise which resolves to an object of the form:
.. code-block:: js
{
a: <base64-encoded, prio-encoded data>,
b: <base64-encoded, prio-encoded data>,
}
``let snapshot = Telemetry.getOriginSnapshot(aClear);``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This JS API provides a snapshot of the unencrypted storage of unsent Origin Telemetry, optionally clearing that storage.
It returns a structure of the form:
.. code-block:: js
{
"categoryname.metric_name": {
"origin1": count1,
"origin2": count2,
...
},
...
}
.. important::
This API is only intended to be used by ``about:telemetry`` and tests.
.. _origin.example:
Example
=======
Firefox Content Blocking blocks web content from certain origins present on a list.
Users can exempt certain origins from being blocked.
To improve Content Blocking's effectiveness we need to know these two "what's" of information about that list of "wheres".
This means we need two metrics ``contentblocking.blocked`` and ``contentblocking.exempt`` (the "what's"), and a list of origins (the "wheres").
Say "example.net" was blocked and "example.com" was exempted from blocking.
Content Blocking calls ``Telemetry::RecordOrigin(OriginMetricID::ContentBlocking_Blocked, "example.net"_ns))`` and ``Telemetry::RecordOrigin(OriginMetricID::ContentBlocking_Exempt, "example.com"_ns)``.
At this time a call to ``Telemetry.getOriginSnapshot()`` would return:
.. code-block:: js
{
"contentblocking.blocked": {"example.net": 1},
"contentblocking.exempt": {"example.com": 1},
}
Later, Origin Telemetry will get the encoded snapshot (clearing the storage) and assemble it with other information into a :doc:`"prio" ping <../data/prio-ping>` which will then be submitted.
.. _origin.encoding:
Encoding
========
.. note::
This section is provided to help you understand the client implementation's architecture.
If how we arranged our code doesn't matter to you, feel free to ignore.
There are three levels of encoding in Origin Telemetry: App Encoding, Prio Encoding, and Base64 Encoding.
*App Encoding* is the process by which we turn the Metrics and Origins into data structures that Prio can encrypt for us.
Prio, at time of writing, only supports counting up to 2046 "true/false" values at a time.
Thus, from the example, we need to turn "example.net was blocked" into "the boolean at index 11 of chunk 2 is true".
This encoding can be done any way we like so long as we don't change it without informing the aggregation servers (by sending it a new :ref:`encoding name <prio-ping.encoding>`).
This encoding provides no privacy benefit and is just a matter of transforming the data into a format Prio can process.
*Prio Encoding* is the process by which those ordered true/false values that result from App Encoding are turned into an encrypted series of bytes.
You can `read the paper (pdf) <https://www.usenix.org/system/files/conference/nsdi17/nsdi17-corrigan-gibbs.pdf>`_ to learn more about that.
This encoding, together with the overall system architecture, is what provides the privacy quality to Origin Telemetry.
*Base64 Encoding* is how we turn those encrypted bytes into a string of characters we can send over the network.
You can learn more about Base64 encoding `on wikipedia <https://wikipedia.org/wiki/Base64>`_.
This encoding provides no privacy benefit and is just used to make Data Engineers' lives a little easier.
Version History
===============
- Firefox 68: Initial Origin Telemetry support (Nightly Only) (`bug 1536565 <https://bugzilla.mozilla.org/show_bug.cgi?id=1536565>`_).

View File

@ -1,79 +0,0 @@
"prio" ping
===========
This ping transmits :ref:`Origin Telemetry <origintelemetry>` data.
The client id is not submitted with this ping.
The :doc:`Telemetry Environment <../data/environment>` is not submitted in this ping.
.. code-block:: js
{
"type": "prio",
... common ping data
"payload": {
"reason": {periodic, max, shutdown}, // Why the ping was submitted
"prioData": [{
encoding: <encoding name>, // Name of App Encoding applied. e.g. "content-blocking-1"
prio: {
// opaque prio-specific payload. Like { a: <base64 string>, b: <base64 string> }
},
}, ... ],
}
}
Scheduling
----------
The ping is submitted at least once a day for sessions that last longer than 24h.
The ping is immediately sent if the ``prioData`` array has 10 elements.
The ping is submitted on shutdown.
The ping is enabled on pre-release channels (Nightly, Beta), except when disabled by :doc:`preference <../internals/preferences>`.
The 10-element limit is also controlled by :doc:`preference <../internals/preferences>`.
Field details
-------------
reason
~~~~~~
The ``reason`` field contains the information about why the "prio" ping was submitted:
* ``periodic``: The ping was submitted because some Origin Telemetry was recorded in the past day.
* ``max``: The ping was submitted because the 10-element limit was reached.
* ``shutdown``: The ping was submitted because Firefox is shutting down and some Origin Telemetry data have yet to be submitted.
prioData
~~~~~~~~
An array of ``encoding``/``prio`` pairs.
Multiple elements of the ``prioData`` array may have the same ``encoding``.
This is how we encode how many times something happened.
.. _prio-ping.encoding:
encoding
~~~~~~~~
The name of the encoding process used to encode the payload.
In short: This field identifies how :ref:`Origin Telemetry <origintelemetry>` encoded information of the form "tracker 'example.net' was blocked" into a boolean, split it into small enough lists for Prio to encode, encrypted it with a Prio batch ID, and did all the rest of the stuff it needed to do in order for it to be sent.
This name enables the servers to be able to make sense of the base64 strings we're sending it so we can get the aggregated information out on the other end.
See :ref:`Encoding <origin.encoding>` for more details.
prio
~~~~
The prio-encoded data.
For the prototype's encoding this is an object of the form:
.. code-block:: js
{
a: <base64 string>,
b: <base64 string>,
}
Version History
---------------
- Firefox 68: Initial Origin Telemetry support (Nightly only) (`bug 1536565 <https://bugzilla.mozilla.org/show_bug.cgi?id=1536565>`_).

View File

@ -13,7 +13,7 @@ Here is a quick overview of the most important code parts. They can be found in
* TelemetryStorage: Handles writing pings to disk for TelemetrySend
* TelemetrySession: Collects data for a browsing session, includes many of the most important probes (aka metrics)
* Policy: A layer of indirection added to provide testability. A common pattern in many files
* pings/: Contains definitions and handling for most ping types, like EventPing or PrioPing
* pings/: Contains definitions and handling for most ping types, like EventPing
More details on different topics can be found in these chapters:

View File

@ -163,16 +163,6 @@ Preferences
If a user has opted into the Pioneer program, this will contain their Pioneer ID.
``toolkit.telemetry.prioping.enabled``
Whether the :doc:`../data/prio-ping` is enabled.
Defaults to true. Change requires restart.
``toolkit.telemetry.prioping.dataLimit``
The number of encoded prio payloads which triggers an immediate :doc:`../data/prio-ping` with reason "max".
Default is 10 payloads.
Data-choices notification
-------------------------

View File

@ -61,7 +61,6 @@ EXPORTS.mozilla += [
"dap/DAPTelemetryBindings.h",
"other/CombinedStacks.h",
"other/ProcessedStack.h",
"TelemetryOriginEnums.h",
]
EXPORTS.mozilla.telemetry += [
@ -76,7 +75,6 @@ SOURCES += [
"core/TelemetryCommon.cpp",
"core/TelemetryEvent.cpp",
"core/TelemetryHistogram.cpp",
"core/TelemetryOrigin.cpp",
"core/TelemetryScalar.cpp",
"core/TelemetryUserInteraction.cpp",
"dap/DAPTelemetry.cpp",
@ -120,7 +118,6 @@ EXTRA_JS_MODULES += [
"pings/EventPing.sys.mjs",
"pings/HealthPing.sys.mjs",
"pings/ModulesPing.sys.mjs",
"pings/PrioPing.sys.mjs",
"pings/TelemetrySession.sys.mjs",
"pings/UntrustedModulesPing.sys.mjs",
"pings/UpdatePing.sys.mjs",

View File

@ -1,145 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* This module sends Origin Telemetry periodically:
* https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/prio-ping.html
*/
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Log: "resource://gre/modules/Log.sys.mjs",
TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
});
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
const Utils = TelemetryUtils;
const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryPrioPing";
// Triggered from native Origin Telemetry storage.
const PRIO_LIMIT_REACHED_TOPIC = "origin-telemetry-storage-limit-reached";
const PRIO_PING_VERSION = "1";
export var Policy = {
sendPing: (type, payload, options) =>
lazy.TelemetryController.submitExternalPing(type, payload, options),
getEncodedOriginSnapshot: async aClear =>
Services.telemetry.getEncodedOriginSnapshot(aClear),
};
export var TelemetryPrioPing = {
Reason: Object.freeze({
PERIODIC: "periodic", // Sent the ping containing Origin Telemetry from the past periodic interval (default 24h).
MAX: "max", // Sent the ping containing at least the maximum number (default 10) of prioData elements, earlier than the periodic interval.
SHUTDOWN: "shutdown", // Recorded data was sent on shutdown.
}),
PRIO_PING_TYPE: "prio",
_logger: null,
_testing: false,
_timeoutId: null,
startup() {
if (!this._testing && !Services.telemetry.canRecordPrereleaseData) {
this._log.trace("Extended collection disabled. Prio ping disabled.");
return;
}
if (
!this._testing &&
!Services.prefs.getBoolPref(Utils.Preferences.PrioPingEnabled, true)
) {
this._log.trace("Prio ping disabled by pref.");
return;
}
this._log.trace("Starting up.");
Services.obs.addObserver(this, PRIO_LIMIT_REACHED_TOPIC);
},
async shutdown() {
this._log.trace("Shutting down.");
// removeObserver may throw, which could interrupt shutdown.
try {
Services.obs.removeObserver(this, PRIO_LIMIT_REACHED_TOPIC);
} catch (ex) {}
await this._submitPing(this.Reason.SHUTDOWN);
},
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case PRIO_LIMIT_REACHED_TOPIC:
this._log.trace("prio limit reached");
this._submitPing(this.Reason.MAX);
break;
}
},
periodicPing() {
this._log.trace("periodic ping triggered");
this._submitPing(this.Reason.PERIODIC);
},
/**
* Submits an "prio" ping and restarts the timer for the next interval.
*
* @param {String} reason The reason we're sending the ping. One of TelemetryPrioPing.Reason.
*/
async _submitPing(reason) {
this._log.trace("_submitPing");
let snapshot = await Policy.getEncodedOriginSnapshot(true /* clear */);
if (!this._testing) {
snapshot = snapshot.filter(
({ encoding }) => !encoding.startsWith("telemetry.test")
);
}
if (snapshot.length === 0) {
// Don't send a ping if we haven't anything to send
this._log.trace("nothing to send");
return;
}
let payload = {
version: PRIO_PING_VERSION,
reason,
prioData: snapshot,
};
const options = {
addClientId: false,
addEnvironment: false,
usePingSender: reason === this.Reason.SHUTDOWN,
};
Policy.sendPing(this.PRIO_PING_TYPE, payload, options);
},
/**
* Test-only, restore to initial state.
*/
testReset() {
this._testing = true;
},
get _log() {
if (!this._logger) {
this._logger = lazy.Log.repository.getLoggerWithMessagePrefix(
LOGGER_NAME,
LOGGER_PREFIX + "::"
);
}
return this._logger;
},
};

View File

@ -5,7 +5,6 @@
#include "TelemetryTestHelpers.h"
#include "core/TelemetryCommon.h"
#include "core/TelemetryOrigin.h"
#include "gtest/gtest.h"
#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
#include "js/CallAndConstruct.h" // JS_CallFunctionName
@ -261,99 +260,6 @@ nsTArray<nsString> EventValuesToArray(JSContext* aCx,
return valueArray;
}
void GetOriginSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
bool aClear) {
nsCOMPtr<nsITelemetry> telemetry =
do_GetService("@mozilla.org/base/telemetry;1");
JS::Rooted<JS::Value> originSnapshot(aCx);
nsresult rv;
rv = telemetry->GetOriginSnapshot(aClear, aCx, &originSnapshot);
ASSERT_EQ(rv, NS_OK) << "Snapshotting origin data must not fail.";
ASSERT_TRUE(originSnapshot.isObject())
<< "The snapshot must be an object.";
aResult.set(originSnapshot);
}
/*
* Extracts the `a` and `b` strings from the prioData snapshot object
* of any length. Which looks like:
*
* [{
* encoding: encodingName,
* prio: {
* a: <string>,
* b: <string>,
* },
* }, ...]
*/
void GetEncodedOriginStrings(
JSContext* aCx, const nsCString& aEncoding,
nsTArray<Tuple<nsCString, nsCString>>& aPrioStrings) {
JS::Rooted<JS::Value> snapshot(aCx);
nsresult rv;
rv = TelemetryOrigin::GetEncodedOriginSnapshot(false /* clear */, aCx,
&snapshot);
ASSERT_NS_SUCCEEDED(rv);
ASSERT_FALSE(snapshot.isNullOrUndefined())
<< "Encoded snapshot must not be null/undefined.";
JS::Rooted<JSObject*> prioDataObj(aCx, &snapshot.toObject());
bool isArray = false;
ASSERT_TRUE(JS::IsArrayObject(aCx, prioDataObj, &isArray) && isArray)
<< "The metric's origins must be in an array.";
uint32_t length = 0;
ASSERT_TRUE(JS::GetArrayLength(aCx, prioDataObj, &length));
ASSERT_TRUE(length > 0)
<< "Length of returned array must greater than 0";
for (auto i = 0u; i < length; ++i) {
JS::Rooted<JS::Value> arrayItem(aCx);
ASSERT_TRUE(JS_GetElement(aCx, prioDataObj, i, &arrayItem));
ASSERT_TRUE(arrayItem.isObject());
ASSERT_FALSE(arrayItem.isNullOrUndefined());
JS::Rooted<JSObject*> arrayItemObj(aCx, &arrayItem.toObject());
JS::Rooted<JS::Value> encodingVal(aCx);
ASSERT_TRUE(JS_GetProperty(aCx, arrayItemObj, "encoding", &encodingVal));
ASSERT_TRUE(encodingVal.isString());
nsAutoJSString jsStr;
ASSERT_TRUE(jsStr.init(aCx, encodingVal));
nsPrintfCString encoding(aEncoding.get(),
i % TelemetryOrigin::SizeOfPrioDatasPerMetric());
ASSERT_TRUE(NS_ConvertUTF16toUTF8(jsStr) == encoding)
<< "Actual 'encoding' (" << NS_ConvertUTF16toUTF8(jsStr).get()
<< ") must match expected (" << encoding << ")";
JS::Rooted<JS::Value> prioVal(aCx);
ASSERT_TRUE(JS_GetProperty(aCx, arrayItemObj, "prio", &prioVal));
ASSERT_TRUE(prioVal.isObject());
ASSERT_FALSE(prioVal.isNullOrUndefined());
JS::Rooted<JSObject*> prioObj(aCx, &prioVal.toObject());
JS::Rooted<JS::Value> aVal(aCx);
nsAutoJSString aStr;
ASSERT_TRUE(JS_GetProperty(aCx, prioObj, "a", &aVal));
ASSERT_TRUE(aVal.isString());
ASSERT_TRUE(aStr.init(aCx, aVal));
JS::Rooted<JS::Value> bVal(aCx);
nsAutoJSString bStr;
ASSERT_TRUE(JS_GetProperty(aCx, prioObj, "b", &bVal));
ASSERT_TRUE(bVal.isString());
ASSERT_TRUE(bStr.init(aCx, bVal));
aPrioStrings.AppendElement(Tuple<nsCString, nsCString>(
NS_ConvertUTF16toUTF8(aStr), NS_ConvertUTF16toUTF8(bStr)));
}
}
void GetEventSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
ProcessID aProcessType) {
nsCOMPtr<nsITelemetry> telemetry =

View File

@ -63,14 +63,6 @@ void GetElement(JSContext* cx, uint32_t index, JS::Handle<JS::Value> valueIn,
void GetSnapshots(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
const char* name, JS::MutableHandle<JS::Value> valueOut,
bool is_keyed);
void GetOriginSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
bool aClear = false);
void GetEncodedOriginStrings(
JSContext* aCx, const nsCString& aEncoding,
nsTArray<mozilla::Tuple<nsCString, nsCString>>& aPrioStrings);
} // namespace TelemetryTestHelpers
#endif

View File

@ -1,291 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "core/TelemetryOrigin.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/ContentBlockingLog.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "nsIObserverService.h"
#include "TelemetryFixture.h"
#include "TelemetryTestHelpers.h"
#include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetProperty
using namespace mozilla;
using namespace TelemetryTestHelpers;
using mozilla::Telemetry::OriginMetricID;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::StrEq;
constexpr auto kTelemetryTest1Metric = "telemetry.test_test1"_ns;
constexpr auto kDoubleclickOrigin = "doubleclick.net"_ns;
constexpr auto kDoubleclickOriginHash =
"uXNT1PzjAVau8b402OMAIGDejKbiXfQX5iXvPASfO/s="_ns;
constexpr auto kFacebookOrigin = "fb.com"_ns;
constexpr auto kUnknownOrigin1 =
"this origin isn't known to Origin Telemetry"_ns;
constexpr auto kUnknownOrigin2 = "neither is this one"_ns;
// Properly prepare the prio prefs
// (Sourced from PrioEncoder.cpp from when it was being prototyped)
constexpr auto prioKeyA =
"35AC1C7576C7C6EDD7FED6BCFC337B34D48CB4EE45C86BEEFB40BD8875707733"_ns;
constexpr auto prioKeyB =
"26E6674E65425B823F1F1D5F96E3BB3EF9E406EC7FBA7DEF8B08A35DD135AF50"_ns;
// Test that we can properly record origin stuff using the C++ API.
TEST_F(TelemetryTestFixture, RecordOrigin) {
AutoJSContextWithGlobal cx(mCleanGlobal);
JSContext* aCx = cx.GetJSContext();
Unused << mTelemetry->ClearOrigins();
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
mozilla::ContentBlockingLog::kDummyOriginHash);
JS::Rooted<JS::Value> originSnapshot(aCx);
GetOriginSnapshot(aCx, &originSnapshot);
ASSERT_FALSE(originSnapshot.isNullOrUndefined())
<< "Origin snapshot must not be null/undefined.";
JS::Rooted<JS::Value> origins(aCx);
JS::Rooted<JSObject*> snapshotObj(aCx, &originSnapshot.toObject());
ASSERT_TRUE(
JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
<< "telemetry.test_test1 must be in the snapshot.";
JS::Rooted<JSObject*> originsObj(aCx, &origins.toObject());
JS::Rooted<JS::Value> count(aCx);
ASSERT_TRUE(JS_GetProperty(
aCx, originsObj, mozilla::ContentBlockingLog::kDummyOriginHash.get(),
&count));
ASSERT_TRUE(count.isInt32() && count.toInt32() == 1)
<< "Must have recorded the origin exactly once.";
// Now test that the snapshot didn't clear things out.
GetOriginSnapshot(aCx, &originSnapshot);
ASSERT_FALSE(originSnapshot.isNullOrUndefined());
JS::Rooted<JSObject*> unemptySnapshotObj(aCx, &originSnapshot.toObject());
JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
ASSERT_TRUE(JS_Enumerate(aCx, unemptySnapshotObj, &ids));
ASSERT_GE(ids.length(), (unsigned)0) << "Returned object must not be empty.";
}
TEST_F(TelemetryTestFixture, RecordOriginTwiceAndClear) {
AutoJSContextWithGlobal cx(mCleanGlobal);
JSContext* aCx = cx.GetJSContext();
Unused << mTelemetry->ClearOrigins();
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kDoubleclickOrigin);
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kDoubleclickOrigin);
JS::Rooted<JS::Value> originSnapshot(aCx);
GetOriginSnapshot(aCx, &originSnapshot, true /* aClear */);
ASSERT_FALSE(originSnapshot.isNullOrUndefined())
<< "Origin snapshot must not be null/undefined.";
JS::Rooted<JS::Value> origins(aCx);
JS::Rooted<JSObject*> snapshotObj(aCx, &originSnapshot.toObject());
ASSERT_TRUE(
JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
<< "telemetry.test_test1 must be in the snapshot.";
JS::Rooted<JSObject*> originsObj(aCx, &origins.toObject());
JS::Rooted<JS::Value> count(aCx);
ASSERT_TRUE(
JS_GetProperty(aCx, originsObj, kDoubleclickOrigin.get(), &count));
ASSERT_TRUE(count.isInt32() && count.toInt32() == 2)
<< "Must have recorded the origin exactly twice.";
// Now check that snapshotting with clear actually cleared it.
GetOriginSnapshot(aCx, &originSnapshot);
ASSERT_FALSE(originSnapshot.isNullOrUndefined());
JS::Rooted<JSObject*> emptySnapshotObj(aCx, &originSnapshot.toObject());
JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
ASSERT_TRUE(JS_Enumerate(aCx, emptySnapshotObj, &ids));
ASSERT_EQ(ids.length(), (unsigned)0) << "Returned object must be empty.";
}
TEST_F(TelemetryTestFixture, RecordOriginTwiceMixed) {
AutoJSContextWithGlobal cx(mCleanGlobal);
JSContext* aCx = cx.GetJSContext();
Unused << mTelemetry->ClearOrigins();
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kDoubleclickOrigin);
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kDoubleclickOriginHash);
Preferences::SetCString("prio.publicKeyA", prioKeyA);
Preferences::SetCString("prio.publicKeyB", prioKeyB);
nsTArray<Tuple<nsCString, nsCString>> encodedStrings;
GetEncodedOriginStrings(aCx, kTelemetryTest1Metric + "-%u"_ns,
encodedStrings);
ASSERT_EQ(2 * TelemetryOrigin::SizeOfPrioDatasPerMetric(),
encodedStrings.Length());
JS::Rooted<JS::Value> originSnapshot(aCx);
GetOriginSnapshot(aCx, &originSnapshot, true /* aClear */);
ASSERT_FALSE(originSnapshot.isNullOrUndefined())
<< "Origin snapshot must not be null/undefined.";
JS::Rooted<JS::Value> origins(aCx);
JS::Rooted<JSObject*> snapshotObj(aCx, &originSnapshot.toObject());
ASSERT_TRUE(
JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
<< "telemetry.test_test1 must be in the snapshot.";
JS::Rooted<JSObject*> originsObj(aCx, &origins.toObject());
JS::Rooted<JS::Value> count(aCx);
ASSERT_TRUE(
JS_GetProperty(aCx, originsObj, kDoubleclickOrigin.get(), &count));
ASSERT_TRUE(count.isInt32() && count.toInt32() == 2)
<< "Must have recorded the origin exactly twice.";
}
TEST_F(TelemetryTestFixture, RecordUnknownOrigin) {
AutoJSContextWithGlobal cx(mCleanGlobal);
JSContext* aCx = cx.GetJSContext();
Unused << mTelemetry->ClearOrigins();
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1, kUnknownOrigin1);
JS::Rooted<JS::Value> originSnapshot(aCx);
GetOriginSnapshot(aCx, &originSnapshot);
ASSERT_FALSE(originSnapshot.isNullOrUndefined())
<< "Origin snapshot must not be null/undefined.";
JS::Rooted<JS::Value> origins(aCx);
JS::Rooted<JSObject*> snapshotObj(aCx, &originSnapshot.toObject());
ASSERT_TRUE(
JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
<< "telemetry.test_test1 must be in the snapshot.";
JS::Rooted<JSObject*> originsObj(aCx, &origins.toObject());
JS::Rooted<JS::Value> count(aCx);
ASSERT_TRUE(JS_GetProperty(aCx, originsObj, "__UNKNOWN__", &count));
ASSERT_TRUE(count.isInt32() && count.toInt32() == 1)
<< "Must have recorded the unknown origin exactly once.";
// Record a second, different unknown origin and ensure only one is stored.
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1, kUnknownOrigin2);
GetOriginSnapshot(aCx, &originSnapshot);
ASSERT_FALSE(originSnapshot.isNullOrUndefined())
<< "Origin snapshot must not be null/undefined.";
JS::Rooted<JSObject*> snapshotObj2(aCx, &originSnapshot.toObject());
ASSERT_TRUE(
JS_GetProperty(aCx, snapshotObj2, kTelemetryTest1Metric.get(), &origins))
<< "telemetry.test_test1 must be in the snapshot.";
JS::Rooted<JSObject*> originsObj2(aCx, &origins.toObject());
JS::Rooted<JS::Value> count2(aCx);
ASSERT_TRUE(JS_GetProperty(aCx, originsObj2, "__UNKNOWN__", &count2));
ASSERT_TRUE(count2.isInt32() && count2.toInt32() == 1)
<< "Must have recorded the unknown origin exactly once.";
}
TEST_F(TelemetryTestFixture, EncodedSnapshot) {
AutoJSContextWithGlobal cx(mCleanGlobal);
JSContext* aCx = cx.GetJSContext();
Unused << mTelemetry->ClearOrigins();
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kDoubleclickOrigin);
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1, kUnknownOrigin1);
Preferences::SetCString("prio.publicKeyA", prioKeyA);
Preferences::SetCString("prio.publicKeyB", prioKeyB);
nsTArray<Tuple<nsCString, nsCString>> firstStrings;
GetEncodedOriginStrings(aCx, kTelemetryTest1Metric + "-%u"_ns, firstStrings);
// Now snapshot a second time and ensure the encoded payloads change.
nsTArray<Tuple<nsCString, nsCString>> secondStrings;
GetEncodedOriginStrings(aCx, kTelemetryTest1Metric + "-%u"_ns, secondStrings);
const auto sizeOfPrioDatasPerMetric =
TelemetryOrigin::SizeOfPrioDatasPerMetric();
ASSERT_EQ(sizeOfPrioDatasPerMetric, firstStrings.Length());
ASSERT_EQ(sizeOfPrioDatasPerMetric, secondStrings.Length());
for (size_t i = 0; i < sizeOfPrioDatasPerMetric; ++i) {
auto& aStr = Get<0>(firstStrings[i]);
auto& bStr = Get<1>(firstStrings[i]);
auto& secondAStr = Get<0>(secondStrings[i]);
auto& secondBStr = Get<1>(secondStrings[i]);
ASSERT_TRUE(aStr != secondAStr)
<< "aStr (" << aStr.get() << ") must not equal secondAStr ("
<< secondAStr.get() << ")";
ASSERT_TRUE(bStr != secondBStr)
<< "bStr (" << bStr.get() << ") must not equal secondBStr ("
<< secondBStr.get() << ")";
}
}
class MockObserver final : public nsIObserver {
public:
NS_DECL_ISUPPORTS
MOCK_METHOD1(Mobserve, void(const char* aTopic));
NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) override {
Mobserve(aTopic);
return NS_OK;
};
MockObserver() = default;
private:
~MockObserver() = default;
};
NS_IMPL_ISUPPORTS(MockObserver, nsIObserver);
TEST_F(TelemetryTestFixture, OriginTelemetryNotifiesTopic) {
Unused << mTelemetry->ClearOrigins();
const char* kTopic = "origin-telemetry-storage-limit-reached";
MockObserver* mo = new MockObserver();
nsCOMPtr<nsIObserver> nsMo(mo);
EXPECT_CALL(*mo, Mobserve(StrEq(kTopic))).Times(1);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
ASSERT_TRUE(os);
os->AddObserver(nsMo, kTopic, false);
const size_t size = ceil(10.0 / TelemetryOrigin::SizeOfPrioDatasPerMetric());
for (size_t i = 0; i < size; ++i) {
if (i < size - 1) {
// Let's ensure we only notify the once.
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kFacebookOrigin);
}
Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
kDoubleclickOrigin);
}
os->RemoveObserver(nsMo, kTopic);
}

View File

@ -21,7 +21,6 @@ if CONFIG["OS_TARGET"] != "Android":
"TestCounters.cpp",
"TestEvents.cpp",
"TestHistograms.cpp",
"TestOrigins.cpp",
"TestScalars.cpp",
]

View File

@ -1,138 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
ChromeUtils.defineESModuleGetters(this, {
TelemetryPrioPing: "resource://gre/modules/PrioPing.sys.mjs",
});
function checkPingStructure(type, payload, options) {
Assert.equal(
type,
TelemetryPrioPing.PRIO_PING_TYPE,
"Should be a prio ping."
);
// Check the payload for required fields.
Assert.ok("version" in payload, "Payload must have version.");
Assert.ok("reason" in payload, "Payload must have reason.");
Assert.ok(
Object.values(TelemetryPrioPing.Reason).some(
reason => payload.reason === reason
),
"Should be a known reason."
);
Assert.ok(
Array.isArray(payload.prioData),
"Payload prioData must be present and an array."
);
payload.prioData.forEach(prioData => {
Assert.ok("encoding" in prioData, "All prioData must have encodings.");
Assert.ok("prio" in prioData, "All prioData must have prio blocks.");
});
// Ensure we forbid client id and environment
Assert.equal(options.addClientId, false, "Must forbid client Id.");
Assert.equal(options.addEnvironment, false, "Must forbid Environment.");
}
function fakePolicy(set, clear, send, snapshot) {
let { Policy } = ChromeUtils.importESModule(
"resource://gre/modules/PrioPing.sys.mjs"
);
Policy.setTimeout = set;
Policy.clearTimeout = clear;
Policy.sendPing = send;
Policy.getEncodedOriginSnapshot = snapshot;
}
function pass() {
/* intentionally empty */
}
function fail() {
Assert.ok(false, "Not allowed");
}
function fakeSnapshot() {
return [
{
encoding: "telemetry.test-1-1",
prio: {},
},
{
encoding: "telemetry.test-1-1",
prio: {},
},
];
}
add_task(async function setup() {
// Trigger a proper telemetry init.
do_get_profile(true);
// Make sure we don't generate unexpected pings due to pref changes.
await setEmptyPrefWatchlist();
await TelemetryController.testSetup();
TelemetryPrioPing.testReset();
});
// Similarly to test_EventPing tests in this file often follow the form:
// 1: Fake out timeout, ping submission, and snapshotting
// 2: Trigger a "prio" ping to happen
// 3: Inside the fake ping submission, ensure the ping is correctly formed.
// In sinon this would be replaced with spies and .wasCalledWith().
add_task(async function test_limit_reached() {
// Ensure that on being notified of the limit we immediately trigger a ping
// with reason "max"
fakePolicy(
pass,
pass,
(type, payload, options) => {
checkPingStructure(type, payload, options);
Assert.equal(
payload.reason,
TelemetryPrioPing.Reason.MAX,
"Sent using max reason."
);
},
fakeSnapshot
);
Services.obs.notifyObservers(null, "origin-telemetry-storage-limit-reached");
});
add_task(async function test_periodic() {
fakePolicy(
pass,
pass,
(type, payload, options) => {
checkPingStructure(type, payload, options);
Assert.equal(
payload.reason,
TelemetryPrioPing.Reason.PERIODIC,
"Sent with periodic reason."
);
},
fakeSnapshot
);
// This is normally triggered by the scheduler once a day
TelemetryPrioPing.periodicPing();
});
add_task(async function test_shutdown() {
fakePolicy(
fail,
pass,
(type, payload, options) => {
checkPingStructure(type, payload, options);
Assert.equal(
payload.reason,
TelemetryPrioPing.Reason.SHUTDOWN,
"Sent with shutdown reason."
);
},
fakeSnapshot
);
await TelemetryPrioPing.shutdown();
});

View File

@ -119,7 +119,6 @@ run-if = nightly_build && (os == 'win' && !msix) # Disabled for MSIX due to http
[test_EventPing.js]
tags = coverage
[test_CoveragePing.js]
[test_PrioPing.js]
[test_bug1555798.js]
[test_UninstallPing.js]
run-if = os == "win"

View File

@ -414,10 +414,6 @@ var PingPicker = {
}
}
// augment "current ping payload" with origin telemetry
const originSnapshot = Telemetry.getOriginSnapshot(false /* clear */);
ping.payload.origins = originSnapshot;
displayPingData(ping, true);
},
@ -1959,34 +1955,6 @@ var Events = {
},
};
var Origins = {
render(aOrigins) {
let originSection = document.getElementById("origins");
removeAllChildNodes(originSection);
const headings = [
"about-telemetry-origin-origin",
"about-telemetry-origin-count",
];
let hasData = false;
for (let [metric, origins] of Object.entries(aOrigins || {})) {
if (!Object.entries(origins).length) {
continue;
}
hasData = true;
const metricHeader = document.createElement("caption");
metricHeader.appendChild(document.createTextNode(metric));
const table = GenericTable.render(Object.entries(origins), headings);
table.appendChild(metricHeader);
originSection.appendChild(table);
}
setHasData("origin-telemetry-section", hasData);
},
};
/**
* Helper function for showing either the toggle element or "No data collected" message for a section
*
@ -2644,9 +2612,6 @@ function displayRichPingData(ping, updatePayloadList) {
LateWritesSingleton.renderLateWrites(payload.lateWrites);
// Show origin telemetry.
Origins.render(payload.origins);
// Show simple measurements
SimpleMeasurements.render(payload);
}

View File

@ -1359,8 +1359,6 @@
"resource://gre/modules/Preferences.jsm": "toolkit/modules/Preferences.jsm",
"resource://gre/modules/PrincipalsCollector.jsm":
"toolkit/components/cleardata/PrincipalsCollector.jsm",
"resource://gre/modules/PrioPing.jsm":
"toolkit/components/telemetry/pings/PrioPing.jsm",
"resource://gre/modules/PrivateBrowsingUtils.jsm":
"toolkit/modules/PrivateBrowsingUtils.jsm",
"resource://gre/modules/ProcessSelector.jsm": "dom/base/ProcessSelector.jsm",