Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/prio/test/gtest/TestPrioEncoder.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "gtest/gtest.h"
8
9
#include "jsapi.h"
10
#include "PrioEncoder.h"
11
12
#include "mozilla/Preferences.h"
13
#include "mozilla/dom/ScriptSettings.h"
14
15
TEST(PrioEncoder, BadPublicKeys)
16
0
{
17
0
  mozilla::dom::AutoJSAPI jsAPI;
18
0
  ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope()));
19
0
  JSContext* cx = jsAPI.cx();
20
0
21
0
  mozilla::Preferences::SetCString("prio.publicKeyA",
22
0
    nsCString(NS_LITERAL_CSTRING("badA")));
23
0
  mozilla::Preferences::SetCString("prio.publicKeyB",
24
0
    nsCString(NS_LITERAL_CSTRING("badB")));
25
0
26
0
  mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope());
27
0
28
0
  nsCString batchID = NS_LITERAL_CSTRING("abc123");
29
0
30
0
  mozilla::dom::PrioParams prioParams;
31
0
  prioParams.mBrowserIsUserDefault = true;
32
0
  prioParams.mNewTabPageEnabled = true;
33
0
  prioParams.mPdfViewerUsed = false;
34
0
35
0
  mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(cx);
36
0
  mozilla::ErrorResult rv;
37
0
38
0
  mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams, prioEncodedData, rv);
39
0
  ASSERT_TRUE(rv.Failed());
40
0
41
0
  // Call again to ensure that the singleton state is consistent.
42
0
  mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams, prioEncodedData, rv);
43
0
  ASSERT_TRUE(rv.Failed());
44
0
45
0
  // Reset error result so test runner does not fail.
46
0
  rv = mozilla::ErrorResult();
47
0
}
48
49
TEST(PrioEncoder, VerifyFull)
50
0
{
51
0
  SECStatus prioRv = SECSuccess;
52
0
53
0
  PublicKey pkA = nullptr;
54
0
  PublicKey pkB = nullptr;
55
0
  PrivateKey skA = nullptr;
56
0
  PrivateKey skB = nullptr;
57
0
58
0
  PrioConfig cfg = nullptr;
59
0
  PrioServer sA = nullptr;
60
0
  PrioServer sB = nullptr;
61
0
  PrioVerifier vA = nullptr;
62
0
  PrioVerifier vB = nullptr;
63
0
  PrioPacketVerify1 p1A = nullptr;
64
0
  PrioPacketVerify1 p1B = nullptr;
65
0
  PrioPacketVerify2 p2A = nullptr;
66
0
  PrioPacketVerify2 p2B = nullptr;
67
0
  PrioTotalShare tA = nullptr;
68
0
  PrioTotalShare tB = nullptr;
69
0
70
0
  unsigned char* forServerA = nullptr;
71
0
  unsigned char* forServerB = nullptr;
72
0
73
0
  const int seed = time(nullptr);
74
0
  srand(seed);
75
0
76
0
  // Number of different boolean data fields we collect.
77
0
  const int ndata = 3;
78
0
79
0
  unsigned char batchIDStr[32];
80
0
  memset(batchIDStr, 0, sizeof batchIDStr);
81
0
  snprintf((char*)batchIDStr, sizeof batchIDStr, "%d", rand());
82
0
83
0
  bool dataItems[ndata];
84
0
  unsigned long output[ndata];
85
0
86
0
  // The client's data submission is an arbitrary boolean vector.
87
0
  for (int i = 0; i < ndata; i++) {
88
0
    // Arbitrary data
89
0
    dataItems[i] = rand() % 2;
90
0
  }
91
0
92
0
  // Initialize NSS random number generator.
93
0
  prioRv = Prio_init();
94
0
  ASSERT_TRUE(prioRv == SECSuccess);
95
0
96
0
  // Generate keypairs for servers
97
0
  prioRv = Keypair_new(&skA, &pkA);
98
0
  ASSERT_TRUE(prioRv == SECSuccess);
99
0
100
0
  prioRv = Keypair_new(&skB, &pkB);
101
0
  ASSERT_TRUE(prioRv == SECSuccess);
102
0
103
0
  // Export public keys to hex and print to stdout
104
0
  unsigned char pkHexA[CURVE25519_KEY_LEN_HEX + 1];
105
0
  unsigned char pkHexB[CURVE25519_KEY_LEN_HEX + 1];
106
0
  prioRv = PublicKey_export_hex(pkA, pkHexA);
107
0
  ASSERT_TRUE(prioRv == SECSuccess);
108
0
109
0
  prioRv = PublicKey_export_hex(pkB, pkHexB);
110
0
  ASSERT_TRUE(prioRv == SECSuccess);
111
0
112
0
  // Use the default configuration parameters.
113
0
  cfg = PrioConfig_new(ndata, pkA, pkB, batchIDStr,
114
0
                       strlen((char*)batchIDStr));
115
0
  ASSERT_TRUE(cfg != nullptr);
116
0
117
0
  PrioPRGSeed serverSecret;
118
0
  prioRv = PrioPRGSeed_randomize(&serverSecret);
119
0
  ASSERT_TRUE(prioRv == SECSuccess);
120
0
121
0
  // Initialize two server objects. The role of the servers need not
122
0
  // be symmetric. In a deployment, we envision that:
123
0
  //   * Server A is the main telemetry server that is always online.
124
0
  //     Clients send their encrypted data packets to Server A and
125
0
  //     Server A stores them.
126
0
  //   * Server B only comes online when the two servers want to compute
127
0
  //     the final aggregate statistics.
128
0
  sA = PrioServer_new(cfg, PRIO_SERVER_A, skA, serverSecret);
129
0
  ASSERT_TRUE(sA != nullptr);
130
0
  sB = PrioServer_new(cfg, PRIO_SERVER_B, skB, serverSecret);
131
0
  ASSERT_TRUE(sB != nullptr);
132
0
133
0
  // Initialize empty verifier objects
134
0
  vA = PrioVerifier_new(sA);
135
0
  ASSERT_TRUE(vA != nullptr);
136
0
  vB = PrioVerifier_new(sB);
137
0
  ASSERT_TRUE(vB != nullptr);
138
0
139
0
  // Initialize shares of final aggregate statistics
140
0
  tA = PrioTotalShare_new();
141
0
  ASSERT_TRUE(tA != nullptr);
142
0
  tB = PrioTotalShare_new();
143
0
  ASSERT_TRUE(tB != nullptr);
144
0
145
0
  // Initialize shares of verification packets
146
0
  p1A = PrioPacketVerify1_new();
147
0
  ASSERT_TRUE(p1A != nullptr);
148
0
  p1B = PrioPacketVerify1_new();
149
0
  ASSERT_TRUE(p1B != nullptr);
150
0
  p2A = PrioPacketVerify2_new();
151
0
  ASSERT_TRUE(p2A != nullptr);
152
0
  p2B = PrioPacketVerify2_new();
153
0
  ASSERT_TRUE(p2B != nullptr);
154
0
155
0
  // I. CLIENT DATA SUBMISSION.
156
0
  //
157
0
  // Read in the client data packets
158
0
  unsigned int aLen = 0, bLen = 0;
159
0
160
0
  mozilla::dom::AutoJSAPI jsAPI;
161
0
  ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope()));
162
0
  JSContext* cx = jsAPI.cx();
163
0
164
0
  mozilla::Preferences::SetCString("prio.publicKeyA",
165
0
    nsCString(reinterpret_cast<const char*>(pkHexA)));
166
0
  mozilla::Preferences::SetCString("prio.publicKeyB",
167
0
    nsCString(reinterpret_cast<const char*>(pkHexB)));
168
0
169
0
  mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope());
170
0
171
0
  nsCString batchID;
172
0
  batchID = (char*)(batchIDStr);
173
0
174
0
  mozilla::dom::PrioParams prioParams;
175
0
  prioParams.mBrowserIsUserDefault = dataItems[0];
176
0
  prioParams.mNewTabPageEnabled = dataItems[1];
177
0
  prioParams.mPdfViewerUsed = dataItems[2];
178
0
179
0
  mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(cx);
180
0
  mozilla::ErrorResult rv;
181
0
182
0
  mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams, prioEncodedData, rv);
183
0
  ASSERT_FALSE(rv.Failed());
184
0
185
0
  prioEncodedData.mA.Value().ComputeLengthAndData();
186
0
  prioEncodedData.mB.Value().ComputeLengthAndData();
187
0
188
0
  forServerA = prioEncodedData.mA.Value().Data();
189
0
  forServerB = prioEncodedData.mB.Value().Data();
190
0
  aLen = prioEncodedData.mA.Value().Length();
191
0
  bLen = prioEncodedData.mB.Value().Length();
192
0
193
0
  // II. VALIDATION PROTOCOL. (at servers)
194
0
  //
195
0
  // The servers now run a short 2-step protocol to check each
196
0
  // client's packet:
197
0
  //    1) Servers A and B broadcast one message (PrioPacketVerify1)
198
0
  //       to each other.
199
0
  //    2) Servers A and B broadcast another message (PrioPacketVerify2)
200
0
  //       to each other.
201
0
  //    3) Servers A and B can both determine whether the client's data
202
0
  //       submission is well-formed (in which case they add it to their
203
0
  //       running total of aggregate statistics) or ill-formed
204
0
  //       (in which case they ignore it).
205
0
  // These messages must be sent over an authenticated channel, so
206
0
  // that each server is assured that every received message came
207
0
  // from its peer.
208
0
209
0
  // Set up a Prio verifier object.
210
0
  prioRv = PrioVerifier_set_data(vA, forServerA, aLen);
211
0
  ASSERT_TRUE(prioRv == SECSuccess);
212
0
  prioRv = PrioVerifier_set_data(vB, forServerB, bLen);
213
0
  ASSERT_TRUE(prioRv == SECSuccess);
214
0
215
0
  // Both servers produce a packet1. Server A sends p1A to Server B
216
0
  // and vice versa.
217
0
  prioRv = PrioPacketVerify1_set_data(p1A, vA);
218
0
  ASSERT_TRUE(prioRv == SECSuccess);
219
0
  prioRv = PrioPacketVerify1_set_data(p1B, vB);
220
0
  ASSERT_TRUE(prioRv == SECSuccess);
221
0
222
0
  // Both servers produce a packet2. Server A sends p2A to Server B
223
0
  // and vice versa.
224
0
  prioRv = PrioPacketVerify2_set_data(p2A, vA, p1A, p1B);
225
0
  ASSERT_TRUE(prioRv == SECSuccess);
226
0
  prioRv = PrioPacketVerify2_set_data(p2B, vB, p1A, p1B);
227
0
  ASSERT_TRUE(prioRv == SECSuccess);
228
0
229
0
  // Using p2A and p2B, the servers can determine whether the request
230
0
  // is valid. (In fact, only Server A needs to perform this
231
0
  // check, since Server A can just tell Server B whether the check
232
0
  // succeeded or failed.)
233
0
  prioRv = PrioVerifier_isValid(vA, p2A, p2B);
234
0
  ASSERT_TRUE(prioRv == SECSuccess);
235
0
  prioRv = PrioVerifier_isValid(vB, p2A, p2B);
236
0
  ASSERT_TRUE(prioRv == SECSuccess);
237
0
238
0
  // If we get here, the client packet is valid, so add it to the aggregate
239
0
  // statistic counter for both servers.
240
0
  prioRv = PrioServer_aggregate(sA, vA);
241
0
  ASSERT_TRUE(prioRv == SECSuccess);
242
0
  prioRv = PrioServer_aggregate(sB, vB);
243
0
  ASSERT_TRUE(prioRv == SECSuccess);
244
0
245
0
  // The servers repeat the steps above for each client submission.
246
0
247
0
  // III. PRODUCTION OF AGGREGATE STATISTICS.
248
0
  //
249
0
  // After collecting aggregates from MANY clients, the servers can compute
250
0
  // their shares of the aggregate statistics.
251
0
  //
252
0
  // Server B can send tB to Server A.
253
0
  prioRv = PrioTotalShare_set_data(tA, sA);
254
0
  ASSERT_TRUE(prioRv == SECSuccess);
255
0
  prioRv = PrioTotalShare_set_data(tB, sB);
256
0
  ASSERT_TRUE(prioRv == SECSuccess);
257
0
258
0
  // Once Server A has tA and tB, it can learn the aggregate statistics
259
0
  // in the clear.
260
0
  prioRv = PrioTotalShare_final(cfg, output, tA, tB);
261
0
  ASSERT_TRUE(prioRv == SECSuccess);
262
0
263
0
  for (int i = 0; i < ndata; i++) {
264
0
    ASSERT_TRUE(output[i] == dataItems[i]);
265
0
  }
266
0
267
0
  PrioTotalShare_clear(tA);
268
0
  PrioTotalShare_clear(tB);
269
0
270
0
  PrioPacketVerify2_clear(p2A);
271
0
  PrioPacketVerify2_clear(p2B);
272
0
273
0
  PrioPacketVerify1_clear(p1A);
274
0
  PrioPacketVerify1_clear(p1B);
275
0
276
0
  PrioVerifier_clear(vA);
277
0
  PrioVerifier_clear(vB);
278
0
279
0
  PrioServer_clear(sA);
280
0
  PrioServer_clear(sB);
281
0
  PrioConfig_clear(cfg);
282
0
283
0
  PublicKey_clear(pkA);
284
0
  PublicKey_clear(pkB);
285
0
286
0
  PrivateKey_clear(skA);
287
0
  PrivateKey_clear(skB);
288
0
289
0
  Prio_clear();
290
0
}