]> git.saurik.com Git - apple/security.git/blob - keychain/trust/TrustedPeersTests/TPModelTests.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / trust / TrustedPeersTests / TPModelTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import <XCTest/XCTest.h>
25 #import <TrustedPeers/TrustedPeers.h>
26 #import "TPDummySigningKey.h"
27 #import "TPDummyDecrypter.h"
28 #import "TPDummyEncrypter.h"
29
30 @interface TPModelTests : XCTestCase
31
32 @property (nonatomic, strong) TPModel *model;
33 @property (nonatomic, strong) TPPolicyDocument *policyDocV1;
34 @property (nonatomic, strong) TPPolicyDocument *policyDocV2;
35 @property (nonatomic, strong) NSString *secretName;
36 @property (nonatomic, strong) NSData *secretKey;
37
38 @end
39
40 @implementation TPModelTests
41
42 - (TPModel *)makeModel
43 {
44 id<TPDecrypter> decrypter = [TPDummyDecrypter dummyDecrypter];
45 TPModel *model = [[TPModel alloc] initWithDecrypter:decrypter];
46 [model registerPolicyDocument:self.policyDocV1];
47 [model registerPolicyDocument:self.policyDocV2];
48 return model;
49 }
50
51 - (void)setUp
52 {
53 self.secretName = @"foo";
54 TPDummyEncrypter *encrypter = [TPDummyEncrypter dummyEncrypterWithKey:[@"sekritkey" dataUsingEncoding:NSUTF8StringEncoding]];
55 self.secretKey = encrypter.decryptionKey;
56 NSData *redaction = [TPPolicyDocument redactionWithEncrypter:encrypter
57 modelToCategory:@[ @{ @"prefix": @"iCycle", @"category": @"full" } ]
58 categoriesByView:nil
59 introducersByCategory:nil
60 error:NULL];
61
62 self.policyDocV1
63 = [TPPolicyDocument policyDocWithVersion:1
64 modelToCategory:@[
65 @{ @"prefix": @"iPhone", @"category": @"full" },
66 @{ @"prefix": @"iPad", @"category": @"full" },
67 @{ @"prefix": @"Mac", @"category": @"full" },
68 @{ @"prefix": @"iMac", @"category": @"full" },
69 @{ @"prefix": @"AppleTV", @"category": @"tv" },
70 @{ @"prefix": @"Watch", @"category": @"watch" },
71 ]
72 categoriesByView:@{
73 @"WiFi": @[ @"full", @"tv", @"watch" ],
74 @"SafariCreditCards": @[ @"full" ],
75 @"PCSEscrow": @[ @"full" ]
76 }
77 introducersByCategory:@{
78 @"full": @[ @"full" ],
79 @"tv": @[ @"full", @"tv" ],
80 @"watch": @[ @"full", @"watch" ]
81 }
82 redactions:@{
83 self.secretName: redaction
84 }
85 hashAlgo:kTPHashAlgoSHA256];
86
87 self.policyDocV2
88 = [TPPolicyDocument policyDocWithVersion:2
89 modelToCategory:@[
90 @{ @"prefix": @"iCycle", @"category": @"full" }, // new
91 @{ @"prefix": @"iPhone", @"category": @"full" },
92 @{ @"prefix": @"iPad", @"category": @"full" },
93 @{ @"prefix": @"Mac", @"category": @"full" },
94 @{ @"prefix": @"iMac", @"category": @"full" },
95 @{ @"prefix": @"AppleTV", @"category": @"tv" },
96 @{ @"prefix": @"Watch", @"category": @"watch" },
97 ]
98 categoriesByView:@{
99 @"WiFi": @[ @"full", @"tv", @"watch" ],
100 @"SafariCreditCards": @[ @"full" ],
101 @"PCSEscrow": @[ @"full" ]
102 }
103 introducersByCategory:@{
104 @"full": @[ @"full" ],
105 @"tv": @[ @"full", @"tv" ],
106 @"watch": @[ @"full", @"watch" ]
107 }
108 redactions:@{}
109 hashAlgo:kTPHashAlgoSHA256];
110
111 self.model = [self makeModel];
112 }
113
114 - (TPPeerPermanentInfo *)makePeerWithMachineID:(NSString *)machineID
115 {
116 return [self makePeerWithMachineID:machineID modelID:@"iPhone" epoch:1 key:machineID];
117 }
118
119 - (TPPeerPermanentInfo *)makePeerWithMachineID:(NSString *)machineID
120 modelID:(NSString *)modelID
121 epoch:(TPCounter)epoch
122 key:(NSString *)key
123 {
124 NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
125 id<TPSigningKey> trustSigningKey = [[TPDummySigningKey alloc] initWithPublicKeyData:keyData];
126 TPPeerPermanentInfo *permanentInfo
127 = [TPPeerPermanentInfo permanentInfoWithMachineID:machineID
128 modelID:modelID
129 epoch:epoch
130 trustSigningKey:trustSigningKey
131 peerIDHashAlgo:kTPHashAlgoSHA256
132 error:NULL];
133 [self.model registerPeerWithPermanentInfo:permanentInfo];
134
135 TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{}
136 policyVersion:self.policyDocV1.policyVersion
137 policyHash:self.policyDocV1.policyHash
138 policySecrets:nil
139 forPeerWithID:permanentInfo.peerID
140 error:NULL];
141 [self.model updateStableInfo:stableInfo forPeerWithID:permanentInfo.peerID];
142 return permanentInfo;
143 }
144
145 static BOOL circleEquals(TPCircle *circle, NSArray<NSString*> *includedPeerIDs, NSArray<NSString*> *excludedPeerIDs)
146 {
147 return [circle isEqualToCircle:[TPCircle circleWithIncludedPeerIDs:includedPeerIDs excludedPeerIDs:excludedPeerIDs]];
148 }
149
150 - (void)testModelBasics
151 {
152 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
153 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
154 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
155
156 TPCircle *circle;
157
158 // A trusts B, establishes clique
159 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
160 return @"clique1";
161 }];
162 XCTAssert(circleEquals(circle, @[A, B], @[]));
163
164 // B trusts A
165 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
166 XCTAssert(circleEquals(circle, @[A, B], @[]));
167
168 // A trusts C
169 circle = [self.model advancePeerWithID:A addingPeerIDs:@[C] removingPeerIDs:@[] createClique:nil];
170 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
171
172 // C trusts A
173 circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
174 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
175
176 // Updating B (B should now trust C)
177 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
178 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
179
180 // Updating B again (should be no change)
181 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
182 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
183
184 // A decides to exclude B
185 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[B] createClique:nil];
186 XCTAssert(circleEquals(circle, @[A, C], @[B]));
187
188 // Updating C (C should now exclude B)
189 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
190 XCTAssert(circleEquals(circle, @[A, C], @[B]));
191
192 // Updating B (B should now exclude itself and include nobody)
193 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
194 XCTAssert(circleEquals(circle, @[], @[B]));
195
196 // Updating B again (should be no change)
197 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
198 XCTAssert(circleEquals(circle, @[], @[B]));
199
200 // C decides to exclude itself
201 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:@[C] createClique:nil];
202 XCTAssert(circleEquals(circle, @[], @[C]));
203
204 // Updating C (should be no change)
205 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
206 XCTAssert(circleEquals(circle, @[], @[C]));
207
208 // Updating A (A should now exclude C)
209 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
210 XCTAssert(circleEquals(circle, @[A], @[B, C]));
211 }
212
213 - (void)testPeerReplacement
214 {
215 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
216 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
217 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
218
219 TPCircle *circle;
220
221 // A trusts B, establishes clique. A is in a drawer.
222 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
223 return @"clique1";
224 }];
225 XCTAssert(circleEquals(circle, @[A, B], @[]));
226
227 // B trusts A
228 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
229 XCTAssert(circleEquals(circle, @[A, B], @[]));
230
231 // B decides to replace itself with C.
232 circle = [self.model advancePeerWithID:B addingPeerIDs:@[C] removingPeerIDs:@[B] createClique:nil];
233 XCTAssert(circleEquals(circle, @[C], @[B]));
234
235 // B should be able to update itself without forgetting it trusts C.
236 circle = [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
237 XCTAssert(circleEquals(circle, @[C], @[B]));
238
239 // When A wakes up, it should trust C instead of B.
240 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
241 XCTAssert(circleEquals(circle, @[A, C], @[B]));
242 }
243
244 - (void)testVoucher
245 {
246 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
247 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
248 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
249
250 NSString *A = aaa.peerID;
251 NSString *B = bbb.peerID;
252 NSString *C = ccc.peerID;
253
254 TPCircle *circle;
255
256 // A establishes clique.
257 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
258 return @"clique1";
259 }];
260 XCTAssert(circleEquals(circle, @[A], @[]));
261
262 // B trusts A
263 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
264 XCTAssert(circleEquals(circle, @[A, B], @[]));
265
266 // C trusts A
267 circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
268 XCTAssert(circleEquals(circle, @[A, C], @[]));
269
270 // B gets a voucher from A
271 TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL];
272 XCTAssertNotNil(voucher);
273 XCTAssertEqual(TPResultOk, [self.model registerVoucher:voucher]);
274
275 // Updating C, it sees the voucher and now trusts B
276 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
277 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
278
279 // Updating A, it sees the voucher (sponsored by A itself) and now trusts B.
280 // (A updating its dynamicInfo also expires the voucher.)
281 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
282 XCTAssert(circleEquals(circle, @[A, B], @[]));
283 }
284
285 - (void)testExpiredVoucher
286 {
287 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
288 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
289 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
290 TPPeerPermanentInfo *ddd = [self makePeerWithMachineID:@"ddd"];
291
292 NSString *A = aaa.peerID;
293 NSString *B = bbb.peerID;
294 NSString *C = ccc.peerID;
295 NSString *D = ddd.peerID;
296
297 TPCircle *circle;
298
299 // A establishes clique.
300 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
301 return @"clique1";
302 }];
303 XCTAssert(circleEquals(circle, @[A], @[]));
304
305 // B trusts A
306 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
307 XCTAssert(circleEquals(circle, @[A, B], @[]));
308
309 // C trusts A
310 circle = [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
311 XCTAssert(circleEquals(circle, @[A, C], @[]));
312
313 // B gets a voucher from A (but doesn't register the voucher yet because A would notice it)
314 TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL];
315
316 // A advances its clock by deciding to trust D
317 circle = [self.model advancePeerWithID:A addingPeerIDs:@[D] removingPeerIDs:nil createClique:nil];
318 XCTAssert(circleEquals(circle, @[A, D], @[]));
319
320 // Register the voucher, which is now expired because A has advanced its clock
321 [self.model registerVoucher:voucher];
322
323 // Updating C, it ignores the expired voucher for B
324 circle = [self.model advancePeerWithID:C addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
325 XCTAssert(circleEquals(circle, @[A, C, D], @[]));
326 }
327
328 - (void)testVoucherWithBadSignature
329 {
330 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
331 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
332
333 NSString *A = aaa.peerID;
334 NSString *B = bbb.peerID;
335
336 TPCircle *circle;
337
338 // A establishes clique.
339 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
340 return @"clique1";
341 }];
342 XCTAssert(circleEquals(circle, @[A], @[]));
343
344 // B trusts A
345 circle = [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
346 XCTAssert(circleEquals(circle, @[A, B], @[]));
347
348 // B gets a voucher from A, but signed by B's key
349 TPVoucher *voucher = [TPVoucher voucherWithBeneficiaryID:B
350 sponsorID:A
351 clock:[self.model getDynamicInfoForPeerWithID:A].clock
352 trustSigningKey:bbb.trustSigningKey
353 error:NULL];
354 XCTAssertNotNil(voucher);
355 XCTAssertEqual(TPResultSignatureMismatch, [self.model registerVoucher:voucher]);
356 }
357
358 - (void)testVoucherPolicy
359 {
360 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa" modelID:@"watch" epoch:1 key:@"aaa"];
361 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
362
363 NSString *A = aaa.peerID;
364
365 // B is a phone trying to get a voucher from A which is a watch
366 TPVoucher *voucher = [self.model createVoucherForCandidate:bbb withSponsorID:A error:NULL];
367 XCTAssertNil(voucher);
368 }
369
370 - (void)testDynamicInfoReplay
371 {
372 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
373 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
374
375 TPCircle *circle;
376
377 // A establishes clique, trusts B.
378 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:nil createClique:^NSString *{
379 return @"clique1";
380 }];
381 XCTAssert(circleEquals(circle, @[A, B], @[]));
382
383 // Attacker snapshots A's dynamicInfo
384 TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A];
385
386 // A excludes B
387 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[B] createClique:nil];
388 XCTAssert(circleEquals(circle, @[A], @[B]));
389
390 // Attacker replays the old snapshot
391 XCTAssertEqual(TPResultClockViolation, [self.model updateDynamicInfo:dyn forPeerWithID:A]);
392
393 circle = [self.model getCircleForPeerWithID:A];
394 XCTAssert(circleEquals(circle, @[A], @[B]));
395 }
396
397 - (void)testPhoneApprovingWatch
398 {
399 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
400 NSString *watch = [self makePeerWithMachineID:@"watch" modelID:@"Watch1,1" epoch:1 key:@"watch"].peerID;
401
402 TPCircle *circle;
403
404 // phoneA establishes clique, trusts watch.
405 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[watch] removingPeerIDs:nil createClique:^NSString *{
406 return @"clique1";
407 }];
408 XCTAssert(circleEquals(circle, @[phoneA, watch], @[]));
409 }
410
411 - (void)testWatchApprovingPhone
412 {
413 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
414 NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID;
415 NSString *watch = [self makePeerWithMachineID:@"watch" modelID:@"Watch1,1" epoch:1 key:@"watch"].peerID;
416
417 TPCircle *circle;
418
419 // phoneA establishes clique, trusts watch.
420 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[watch] removingPeerIDs:nil createClique:^NSString *{
421 return @"clique1";
422 }];
423 XCTAssert(circleEquals(circle, @[phoneA, watch], @[]));
424
425 // watch trusts phoneA and phoneB
426 circle = [self.model advancePeerWithID:watch addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil];
427 XCTAssert(circleEquals(circle, @[phoneA, phoneB, watch], @[]));
428
429 // phoneA updates, and it should ignore phoneB, so no change.
430 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
431 XCTAssert(circleEquals(circle, @[phoneA, watch], @[]));
432 }
433
434 - (void)testNilCreateClique
435 {
436 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
437
438 TPCircle *circle;
439
440 // Try to establish dynamicInfo without providing createClique
441 circle = [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
442 XCTAssertNil(circle);
443 }
444
445 - (void)testCliqueConvergence
446 {
447 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
448 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
449
450 TPCircle *circle;
451
452 // A establishes clique1
453 circle = [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:^NSString *{
454 return @"clique1";
455 }];
456 XCTAssert(circleEquals(circle, @[A], @[]));
457 XCTAssert([[self.model getDynamicInfoForPeerWithID:A].clique isEqualToString:@"clique1"]);
458
459 // B establishes clique2
460 circle = [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:^NSString *{
461 return @"clique2";
462 }];
463 XCTAssert(circleEquals(circle, @[B], @[]));
464 XCTAssert([[self.model getDynamicInfoForPeerWithID:B].clique isEqualToString:@"clique2"]);
465
466 // A trusts B. A should now switch to clique2, which is later than clique1 in lexical order.
467 circle = [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:nil];
468 XCTAssert(circleEquals(circle, @[A, B], @[]));
469 XCTAssert([[self.model getDynamicInfoForPeerWithID:A].clique isEqualToString:@"clique2"]);
470 }
471
472 - (void)testRemovalCounts
473 {
474 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
475 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
476 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
477
478 // A establishes clique with B and C
479 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{
480 return @"clique1";
481 }];
482 XCTAssertEqual(0ULL, [self.model getDynamicInfoForPeerWithID:A].removals);
483
484 // B trusts A
485 [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
486 XCTAssertEqual(0ULL, [self.model getDynamicInfoForPeerWithID:B].removals);
487
488 // A removes C
489 [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:@[C] createClique:nil];
490 XCTAssertEqual(1ULL, [self.model getDynamicInfoForPeerWithID:A].removals);
491
492 // B updates, and now shows 1 removal
493 [self.model advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
494 XCTAssertEqual(1ULL, [self.model getDynamicInfoForPeerWithID:B].removals);
495 }
496
497 - (void)testCommunicatingModels
498 {
499 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
500 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
501 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
502
503 NSString *A = aaa.peerID;
504 NSString *B = bbb.peerID;
505 NSString *C = ccc.peerID;
506
507 // A lives on self.model, where it trusts B and C
508 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:nil createClique:^NSString *{
509 return @"clique1";
510 }];
511
512 // B lives on model2, where it trusts A
513 TPModel *model2 = [self makeModel];
514 [model2 registerPeerWithPermanentInfo:aaa];
515 [model2 registerPeerWithPermanentInfo:bbb];
516 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:A] forPeerWithID:A];
517 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:B] forPeerWithID:B];
518 [model2 advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:nil createClique:^NSString *{
519 return @"clique1";
520 }];
521
522 // A's circle and dynamicInfo are transmitted from model to model2
523 TPCircle *circle = [self.model getCircleForPeerWithID:A];
524 TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A];
525 [model2 updateDynamicInfo:dyn forPeerWithID:A];
526 [model2 registerCircle:circle];
527
528 // B updates in model2, but C is not yet registered so is ignored.
529 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
530 XCTAssert(circleEquals(circle, @[A, B], @[]));
531
532 // Now C registers in model2
533 [model2 registerPeerWithPermanentInfo:ccc];
534
535 // B updates in model2, and now it trusts C.
536 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
537 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
538 }
539
540 - (void)testCommunicatingModelsWithVouchers
541 {
542 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
543 TPPeerPermanentInfo *bbb = [self makePeerWithMachineID:@"bbb"];
544 TPPeerPermanentInfo *ccc = [self makePeerWithMachineID:@"ccc"];
545
546 NSString *A = aaa.peerID;
547 NSString *B = bbb.peerID;
548 NSString *C = ccc.peerID;
549
550 // A lives on self.model, where it trusts B
551 [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:nil createClique:^NSString *{
552 return @"clique1";
553 }];
554
555 // B lives on model2, where it trusts A
556 TPModel *model2 = [self makeModel];
557 [model2 registerPeerWithPermanentInfo:aaa];
558 [model2 registerPeerWithPermanentInfo:bbb];
559 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:A] forPeerWithID:A];
560 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:B] forPeerWithID:B];
561 [model2 advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:nil createClique:^NSString *{
562 return @"clique1";
563 }];
564
565 // A's circle and dynamicInfo are transmitted from model to model2
566 TPCircle *circle = [self.model getCircleForPeerWithID:A];
567 TPPeerDynamicInfo *dyn = [self.model getDynamicInfoForPeerWithID:A];
568 [model2 updateDynamicInfo:dyn forPeerWithID:A];
569 [model2 registerCircle:circle];
570
571 // A writes a voucher for C, and it is transmitted to model2
572 TPVoucher *voucher = [self.model createVoucherForCandidate:ccc withSponsorID:A error:NULL];
573 [model2 registerVoucher:voucher];
574
575 // B updates in model2, but C is not yet registered so is ignored.
576 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
577 XCTAssert(circleEquals(circle, @[A, B], @[]));
578
579 // Now C registers in model2
580 [model2 registerPeerWithPermanentInfo:ccc];
581 [model2 updateStableInfo:[self.model getStableInfoForPeerWithID:C] forPeerWithID:C];
582
583 // B updates in model2, and now it trusts C.
584 circle = [model2 advancePeerWithID:B addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
585 XCTAssert(circleEquals(circle, @[A, B, C], @[]));
586 }
587
588 - (void)testReregisterPeer
589 {
590 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
591
592 NSString *A = aaa.peerID;
593
594 [self.model advancePeerWithID:A addingPeerIDs:nil removingPeerIDs:nil createClique:^NSString *{
595 return @"clique1";
596 }];
597
598 // Registering the peer again should not overwrite its dynamicInfo or other state.
599 [self.model registerPeerWithPermanentInfo:aaa];
600 XCTAssertNotNil([self.model getDynamicInfoForPeerWithID:A]);
601 }
602
603 - (void)testPeerAccessors
604 {
605 TPPeerPermanentInfo *aaa = [self makePeerWithMachineID:@"aaa"];
606
607 NSString *A = aaa.peerID;
608
609 XCTAssert([self.model hasPeerWithID:A]);
610
611 TPPeerPermanentInfo *aaa2 = [self.model getPermanentInfoForPeerWithID:A];
612 XCTAssertEqualObjects(aaa, aaa2);
613
614 TPPeerStableInfo *info = [self.model createStableInfoWithDictionary:@{ @"hello": @"world" }
615 policyVersion:1
616 policyHash:@""
617 policySecrets:nil
618 forPeerWithID:A
619 error:NULL];
620 XCTAssertEqual(TPResultOk, [self.model updateStableInfo:info forPeerWithID:A]);
621
622 XCTAssertEqualObjects([self.model getStableInfoForPeerWithID:A], info);
623
624 [self.model deletePeerWithID:A];
625 XCTAssertFalse([self.model hasPeerWithID:A]);
626 }
627
628 - (void)testCircleAccessors
629 {
630 TPCircle *circle = [TPCircle circleWithIncludedPeerIDs:@[@"A, B"] excludedPeerIDs:nil];
631 XCTAssertNil([self.model circleWithID:circle.circleID]);
632 [self.model registerCircle:circle];
633 XCTAssertNotNil([self.model circleWithID:circle.circleID]);
634 [self.model deleteCircleWithID:circle.circleID];
635 XCTAssertNil([self.model circleWithID:circle.circleID]);
636 }
637
638 - (void)testLatestEpoch
639 {
640 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID;
641 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:1 key:@"aaa"].peerID;
642 NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"iPhone" epoch:2 key:@"aaa"].peerID;
643
644 TPCounter epoch = [self.model latestEpochAmongPeerIDs:[NSSet setWithArray:@[A, B, C]]];
645 XCTAssertEqual(epoch, 2ULL);
646 }
647
648 - (void)testPeerStatus
649 {
650 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID;
651 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:0 key:@"bbb"].peerID;
652 NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"iPhone" epoch:0 key:@"ccc"].peerID;
653 NSString *D = [self makePeerWithMachineID:@"ddd" modelID:@"iPhone" epoch:1 key:@"ddd"].peerID;
654 NSString *E = [self makePeerWithMachineID:@"eee" modelID:@"iPhone" epoch:2 key:@"eee"].peerID;
655
656 XCTAssertEqual([self.model statusOfPeerWithID:A], 0);
657
658 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{
659 return @"clique1";
660 }];
661 XCTAssertEqual([self.model statusOfPeerWithID:A], 0);
662
663 [self.model advancePeerWithID:B addingPeerIDs:@[A, C] removingPeerIDs:@[] createClique:nil];
664 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated);
665
666 [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
667 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated | TPPeerStatusFullyReciprocated);
668
669 [self.model advancePeerWithID:C addingPeerIDs:@[] removingPeerIDs:@[A] createClique:nil];
670 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusPartiallyReciprocated | TPPeerStatusExcluded);
671
672 [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
673 XCTAssertEqual([self.model statusOfPeerWithID:A], TPPeerStatusExcluded);
674
675 [self.model advancePeerWithID:B addingPeerIDs:@[D] removingPeerIDs:@[] createClique:nil];
676 XCTAssertEqual([self.model statusOfPeerWithID:B], TPPeerStatusPartiallyReciprocated | TPPeerStatusOutdatedEpoch);
677
678 [self.model advancePeerWithID:C addingPeerIDs:@[E] removingPeerIDs:@[] createClique:nil];
679 [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
680 XCTAssertEqual([self.model statusOfPeerWithID:B], TPPeerStatusPartiallyReciprocated | TPPeerStatusOutdatedEpoch | TPPeerStatusAncientEpoch);
681 }
682
683 - (void)testCalculateUnusedCircleIDs
684 {
685 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone" epoch:0 key:@"aaa"].peerID;
686 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone" epoch:0 key:@"bbb"].peerID;
687
688 [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
689 return @"clique1";
690 }];
691 [self.model advancePeerWithID:B addingPeerIDs:@[B] removingPeerIDs:@[] createClique:nil];
692
693 NSSet<NSString*>* unused;
694 unused = [self.model calculateUnusedCircleIDs];
695 XCTAssertEqualObjects(unused, [NSSet set]);
696
697 NSString *circleID = [self.model getCircleForPeerWithID:A].circleID;
698
699 [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[B] createClique:nil];
700
701 unused = [self.model calculateUnusedCircleIDs];
702 XCTAssertEqualObjects(unused, [NSSet setWithObject:circleID]);
703 }
704
705 - (void)testGetPeerIDsTrustedByPeerWithID
706 {
707 NSString *A = [self makePeerWithMachineID:@"aaa" modelID:@"iPhone7,1" epoch:0 key:@"aaa"].peerID;
708 NSString *B = [self makePeerWithMachineID:@"bbb" modelID:@"iPhone6,2" epoch:0 key:@"bbb"].peerID;
709 NSString *C = [self makePeerWithMachineID:@"ccc" modelID:@"Watch1,1" epoch:0 key:@"ccc"].peerID;
710 [self makePeerWithMachineID:@"ddd" modelID:@"iPhone7,1" epoch:0 key:@"ddd"];
711
712 [self.model advancePeerWithID:A addingPeerIDs:@[B, C] removingPeerIDs:@[] createClique:^NSString *{
713 return @"clique1";
714 }];
715
716 // Everyone can access WiFi. Only full peers can access SafariCreditCards
717
718 NSSet<NSString *>* peerIDs;
719 NSSet<NSString *>* expected;
720
721 peerIDs = [self.model getPeerIDsTrustedByPeerWithID:A toAccessView:@"WiFi" error:NULL];
722 expected = [NSSet setWithArray:@[A, B, C]];
723 XCTAssertEqualObjects(peerIDs, expected);
724
725 peerIDs = [self.model getPeerIDsTrustedByPeerWithID:A toAccessView:@"SafariCreditCards" error:NULL];
726 expected = [NSSet setWithArray:@[A, B]];
727 XCTAssertEqualObjects(peerIDs, expected);
728 }
729
730 - (void)testVectorClock
731 {
732 NSString *A = [self makePeerWithMachineID:@"aaa"].peerID;
733 NSString *B = [self makePeerWithMachineID:@"bbb"].peerID;
734 NSString *C = [self makePeerWithMachineID:@"ccc"].peerID;
735
736 [self.model advancePeerWithID:A addingPeerIDs:@[B] removingPeerIDs:@[] createClique:^NSString *{
737 return @"clique1";
738 }];
739 [self.model advancePeerWithID:B addingPeerIDs:@[A] removingPeerIDs:@[] createClique:nil];
740
741 NSDictionary *dict;
742 NSDictionary *expected;
743
744 dict = [self.model vectorClock];
745 expected = @{ A: @4, B: @5, C: @3 };
746 XCTAssertEqualObjects(dict, expected);
747
748 [self.model advancePeerWithID:C addingPeerIDs:@[A] removingPeerIDs:@[B] createClique:nil];
749 [self.model advancePeerWithID:A addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
750 [self.model advancePeerWithID:B addingPeerIDs:@[] removingPeerIDs:@[] createClique:nil];
751
752 dict = [self.model vectorClock];
753 expected = @{ A: @7, B: @8, C: @6 };
754 XCTAssertEqualObjects(dict, expected);
755 }
756
757 - (void)testICycleApprovingPhoneWithNewPolicy
758 {
759 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
760 NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID;
761 NSString *icycle = [self makePeerWithMachineID:@"icycle" modelID:@"iCycle1,1" epoch:1 key:@"icycle"].peerID;
762
763 TPCircle *circle;
764
765 // phoneA establishes clique, trusts icycle
766 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[icycle] removingPeerIDs:nil createClique:^NSString *{
767 return @"clique1";
768 }];
769 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
770
771 // icycle trusts phoneA and phoneB
772 circle = [self.model advancePeerWithID:icycle addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil];
773 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
774
775 // phoneA updates, and it doesn't know iCycles can approve phones, so it should ignore phoneB, so no change.
776 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
777 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
778
779 // icycle presents a new policy that says iCycles can approve phones
780 TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{}
781 policyVersion:self.policyDocV2.policyVersion
782 policyHash:self.policyDocV2.policyHash
783 policySecrets:nil
784 forPeerWithID:icycle
785 error:NULL];
786 [self.model updateStableInfo:stableInfo forPeerWithID:icycle];
787
788 // phoneA updates again, sees the new policy that says iCycles can approve phones, and now trusts phoneB
789 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
790 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
791 }
792
793 - (void)testICycleApprovingPhoneWithRedactedPolicy
794 {
795 NSString *phoneA = [self makePeerWithMachineID:@"phoneA" modelID:@"iPhone7,1" epoch:1 key:@"phoneA"].peerID;
796 NSString *phoneB = [self makePeerWithMachineID:@"phoneB" modelID:@"iPhone7,1" epoch:1 key:@"phoneB"].peerID;
797 NSString *icycle = [self makePeerWithMachineID:@"icycle" modelID:@"iCycle1,1" epoch:1 key:@"icycle"].peerID;
798
799 TPCircle *circle;
800
801 // phoneA establishes clique, trusts icycle
802 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:@[icycle] removingPeerIDs:nil createClique:^NSString *{
803 return @"clique1";
804 }];
805 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
806
807 // icycle trusts phoneA and phoneB
808 circle = [self.model advancePeerWithID:icycle addingPeerIDs:@[phoneA, phoneB] removingPeerIDs:nil createClique:nil];
809 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
810
811 // phoneA updates, and it doesn't know iCycles can approve phones, so it should ignore phoneB, so no change.
812 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
813 XCTAssert(circleEquals(circle, @[phoneA, icycle], @[]));
814
815 // icycle presents a new policy that says iCycles can approve phones
816 TPPeerStableInfo *stableInfo = [self.model createStableInfoWithDictionary:@{}
817 policyVersion:self.policyDocV1.policyVersion
818 policyHash:self.policyDocV1.policyHash
819 policySecrets:@{
820 self.secretName: self.secretKey
821 }
822 forPeerWithID:icycle
823 error:NULL];
824 [self.model updateStableInfo:stableInfo forPeerWithID:icycle];
825
826 // phoneA updates again, sees the new policy that says iCycles can approve phones, and now trusts phoneB
827 circle = [self.model advancePeerWithID:phoneA addingPeerIDs:nil removingPeerIDs:nil createClique:nil];
828 XCTAssert(circleEquals(circle, @[phoneA, phoneB, icycle], @[]));
829 }
830
831 @end