author | Robert Helmer <rhelmer@mozilla.com> |
Mon, 17 Sep 2018 17:38:36 +0000 | |
changeset 436781 | ed612eec41a44867a1330aa893040bacb7ac5b74 |
parent 436780 | 02c9085d46861ba4cf6bf19af8428fc94616335c |
child 436857 | 87a95e1b7ec691bef7b938e722fe1b01cce68664 |
child 436858 | d6196584952864d2f3354d1ff495e094cd43c9a0 |
push id | 34659 |
push user | [email protected] |
push date | Mon, 17 Sep 2018 21:55:13 +0000 |
treeherder | mozilla-central@ed612eec41a4 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bholley, hsivonen |
bugs | 1485620 |
milestone | 64.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/prio/PrioEncoder.h +++ b/dom/prio/PrioEncoder.h @@ -3,16 +3,17 @@ /* 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" #include "mprio.h" class nsIGlobalObject; namespace mozilla { namespace dom {
--- a/dom/prio/moz.build +++ b/dom/prio/moz.build @@ -14,9 +14,11 @@ LOCAL_INCLUDES += [ EXPORTS.mozilla.dom += [ 'PrioEncoder.h', ] UNIFIED_SOURCES += [ 'PrioEncoder.cpp', ] +TEST_DIRS += ['test/gtest'] + FINAL_LIBRARY = 'xul'
new file mode 100644 --- /dev/null +++ b/dom/prio/test/gtest/TestPrioEncoder.cpp @@ -0,0 +1,256 @@ +/* -*- 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/Preferences.h" +#include "mozilla/dom/ScriptSettings.h" + +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 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 + unsigned char pkHexA[CURVE25519_KEY_LEN_HEX + 1]; + unsigned char pkHexB[CURVE25519_KEY_LEN_HEX + 1]; + prioRv = PublicKey_export_hex(pkA, pkHexA); + ASSERT_TRUE(prioRv == SECSuccess); + + prioRv = PublicKey_export_hex(pkB, pkHexB); + 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::Preferences::SetCString("prio.publicKeyA", + nsCString(reinterpret_cast<const char*>(pkHexA))); + mozilla::Preferences::SetCString("prio.publicKeyB", + nsCString(reinterpret_cast<const char*>(pkHexB))); + + mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope()); + + nsCString batchID; + batchID = (char*)(batchIDStr); + + mozilla::dom::PrioParams prioParams; + prioParams.mBrowserIsUserDefault = dataItems[0]; + prioParams.mNewTabPageEnabled = dataItems[1]; + prioParams.mPdfViewerUsed = dataItems[2]; + + mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(cx); + mozilla::ErrorResult rv; + + mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams, prioEncodedData, rv); + ASSERT_FALSE(rv.Failed()); + + prioEncodedData.mA.Value().ComputeLengthAndData(); + prioEncodedData.mB.Value().ComputeLengthAndData(); + + 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(); +}
new file mode 100644 --- /dev/null +++ b/dom/prio/test/gtest/moz.build @@ -0,0 +1,17 @@ +# -*- 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'