/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 | } |