]> git.saurik.com Git - apple/security.git/blob - keychain/trust/TrustedPeers/TPModel.m
Security-58286.41.2.tar.gz
[apple/security.git] / keychain / trust / TrustedPeers / TPModel.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 "TPModel.h"
25 #import "TPPeer.h"
26 #import "TPHash.h"
27 #import "TPCircle.h"
28 #import "TPVoucher.h"
29 #import "TPPeerPermanentInfo.h"
30 #import "TPPeerStableInfo.h"
31 #import "TPPeerDynamicInfo.h"
32 #import "TPPolicy.h"
33 #import "TPPolicyDocument.h"
34
35
36 @interface TPModel ()
37 @property (nonatomic, strong) NSMutableDictionary<NSString*, TPPeer*>* peersByID;
38 @property (nonatomic, strong) NSMutableDictionary<NSString*, TPCircle*>* circlesByID;
39 @property (nonatomic, strong) NSMutableDictionary<NSNumber*, TPPolicyDocument*>* policiesByVersion;
40 @property (nonatomic, strong) NSMutableSet<TPVoucher*>* vouchers;
41 @property (nonatomic, strong) id<TPDecrypter> decrypter;
42 @end
43
44 @implementation TPModel
45
46 - (instancetype)initWithDecrypter:(id<TPDecrypter>)decrypter
47 {
48 self = [super init];
49 if (self) {
50 _peersByID = [[NSMutableDictionary alloc] init];
51 _circlesByID = [[NSMutableDictionary alloc] init];
52 _policiesByVersion = [[NSMutableDictionary alloc] init];
53 _vouchers = [[NSMutableSet alloc] init];
54 _decrypter = decrypter;
55 }
56 return self;
57 }
58
59 - (TPCounter)latestEpochAmongPeerIDs:(NSSet<NSString*> *)peerIDs
60 {
61 TPCounter latestEpoch = 0;
62 for (NSString *peerID in peerIDs) {
63 TPPeer *peer = self.peersByID[peerID];
64 if (nil == peer) {
65 continue;
66 }
67 latestEpoch = MAX(latestEpoch, peer.permanentInfo.epoch);
68 }
69 return latestEpoch;
70 }
71
72 - (void)registerPolicyDocument:(TPPolicyDocument *)policyDoc
73 {
74 NSAssert(policyDoc, @"policyDoc must not be nil");
75 self.policiesByVersion[@(policyDoc.policyVersion)] = policyDoc;
76 }
77
78 - (void)registerPeerWithPermanentInfo:(TPPeerPermanentInfo *)permanentInfo
79 {
80 NSAssert(permanentInfo, @"permanentInfo must not be nil");
81 if (nil == [self.peersByID objectForKey:permanentInfo.peerID]) {
82 TPPeer *peer = [[TPPeer alloc] initWithPermanentInfo:permanentInfo];
83 [self.peersByID setObject:peer forKey:peer.peerID];
84 } else {
85 // Do nothing, to avoid overwriting the existing peer object which might have accumulated state.
86 }
87 }
88
89 - (void)deletePeerWithID:(NSString *)peerID
90 {
91 [self.peersByID removeObjectForKey:peerID];
92 }
93
94 - (BOOL)hasPeerWithID:(NSString *)peerID
95 {
96 return nil != self.peersByID[peerID];
97 }
98
99 - (nonnull TPPeer *)peerWithID:(NSString *)peerID
100 {
101 TPPeer *peer = [self.peersByID objectForKey:peerID];
102 NSAssert(nil != peer, @"peerID is not registered: %@", peerID);
103 return peer;
104 }
105
106 - (TPPeerStatus)statusOfPeerWithID:(NSString *)peerID
107 {
108 TPPeer *peer = [self peerWithID:peerID];
109 TPPeerStatus status = 0;
110 if (0 < [peer.circle.includedPeerIDs count]) {
111 status |= TPPeerStatusFullyReciprocated;
112 // This flag might get cleared again, below.
113 }
114 for (NSString *otherID in peer.circle.includedPeerIDs) {
115 if ([peerID isEqualToString:otherID]) {
116 continue;
117 }
118 TPPeer *otherPeer = self.peersByID[otherID];
119 if (nil == otherPeer) {
120 continue;
121 }
122 if ([otherPeer.circle.includedPeerIDs containsObject:peerID]) {
123 status |= TPPeerStatusPartiallyReciprocated;
124 } else {
125 status &= ~TPPeerStatusFullyReciprocated;
126 }
127 if ([otherPeer.circle.excludedPeerIDs containsObject:peerID]) {
128 status |= TPPeerStatusExcluded;
129 }
130 if (otherPeer.permanentInfo.epoch > peer.permanentInfo.epoch) {
131 status |= TPPeerStatusOutdatedEpoch;
132 }
133 if (otherPeer.permanentInfo.epoch > peer.permanentInfo.epoch + 1) {
134 status |= TPPeerStatusAncientEpoch;
135 }
136 }
137 if ([peer.circle.excludedPeerIDs containsObject:peerID]) {
138 status |= TPPeerStatusExcluded;
139 }
140 return status;
141 }
142
143
144 - (TPPeerPermanentInfo *)getPermanentInfoForPeerWithID:(NSString *)peerID
145 {
146 return [self peerWithID:peerID].permanentInfo;
147 }
148
149 - (TPPeerStableInfo *)getStableInfoForPeerWithID:(NSString *)peerID
150 {
151 return [self peerWithID:peerID].stableInfo;
152 }
153
154 - (NSData *)getWrappedPrivateKeysForPeerWithID:(NSString *)peerID
155 {
156 return [self peerWithID:peerID].wrappedPrivateKeys;
157 }
158
159 - (void)setWrappedPrivateKeys:(nullable NSData *)wrappedPrivateKeys
160 forPeerWithID:(NSString *)peerID
161 {
162 [self peerWithID:peerID].wrappedPrivateKeys = wrappedPrivateKeys;
163 }
164
165 - (TPPeerDynamicInfo *)getDynamicInfoForPeerWithID:(NSString *)peerID
166 {
167 return [self peerWithID:peerID].dynamicInfo;
168 }
169
170 - (TPCircle *)getCircleForPeerWithID:(NSString *)peerID
171 {
172 return [self peerWithID:peerID].circle;
173 }
174
175 - (void)registerCircle:(TPCircle *)circle
176 {
177 NSAssert(circle, @"circle must not be nil");
178 [self.circlesByID setObject:circle forKey:circle.circleID];
179
180 // A dynamicInfo might have been set on a peer before we had the circle identified by dynamicInfo.circleID.
181 // Check if this circle is referenced by any dynamicInfo.circleID.
182 [self.peersByID enumerateKeysAndObjectsUsingBlock:^(NSString *peerID, TPPeer *peer, BOOL *stop) {
183 if (nil == peer.circle && [peer.dynamicInfo.circleID isEqualToString:circle.circleID]) {
184 peer.circle = circle;
185 }
186 }];
187 }
188
189 - (void)deleteCircleWithID:(NSString *)circleID
190 {
191 [self.peersByID enumerateKeysAndObjectsUsingBlock:^(NSString *peerID, TPPeer *peer, BOOL *stop) {
192 NSAssert(![circleID isEqualToString:peer.dynamicInfo.circleID],
193 @"circle being deleted is in use by peer %@, circle %@", peerID, circleID);
194 }];
195 [self.circlesByID removeObjectForKey:circleID];
196 }
197
198 - (TPCircle *)circleWithID:(NSString *)circleID
199 {
200 return [self.circlesByID objectForKey:circleID];
201 }
202
203 - (TPResult)updateStableInfo:(TPPeerStableInfo *)stableInfo
204 forPeerWithID:(NSString *)peerID
205 {
206 TPPeer *peer = [self peerWithID:peerID];
207 return [peer updateStableInfo:stableInfo];
208 }
209
210 - (TPPeerStableInfo *)createStableInfoWithDictionary:(NSDictionary *)dict
211 policyVersion:(TPCounter)policyVersion
212 policyHash:(NSString *)policyHash
213 policySecrets:(nullable NSDictionary *)policySecrets
214 forPeerWithID:(NSString *)peerID
215 error:(NSError **)error
216 {
217 TPPeer *peer = [self peerWithID:peerID];
218 TPCounter clock = [self maxClock] + 1;
219 return [TPPeerStableInfo stableInfoWithDict:dict
220 clock:clock
221 policyVersion:policyVersion
222 policyHash:policyHash
223 policySecrets:policySecrets
224 trustSigningKey:peer.permanentInfo.trustSigningKey
225 error:error];
226 }
227
228 - (TPResult)updateDynamicInfo:(TPPeerDynamicInfo *)dynamicInfo
229 forPeerWithID:(NSString *)peerID
230 {
231 TPPeer *peer = [self peerWithID:peerID];
232 TPResult result = [peer updateDynamicInfo:dynamicInfo];
233 if (result != TPResultOk) {
234 return result;
235 }
236 TPCircle *circle = [self.circlesByID objectForKey:dynamicInfo.circleID];
237 if (nil != circle) {
238 peer.circle = circle;
239 } else {
240 // When the corresponding circleID is eventually registered,
241 // a call to registerCircle: will set peer.circle.
242 }
243 return result;
244 }
245
246 - (TPCounter)maxClock
247 {
248 __block TPCounter maxClock = 0;
249 [self.peersByID enumerateKeysAndObjectsUsingBlock:^(NSString *peerID, TPPeer *peer, BOOL *stop) {
250 if (nil != peer.stableInfo) {
251 maxClock = MAX(maxClock, peer.stableInfo.clock);
252 }
253 if (nil != peer.dynamicInfo) {
254 maxClock = MAX(maxClock, peer.dynamicInfo.clock);
255 }
256 }];
257 return maxClock;
258 }
259
260 - (TPCounter)maxRemovals
261 {
262 __block TPCounter maxRemovals = 0;
263 [self.peersByID enumerateKeysAndObjectsUsingBlock:^(NSString *peerID, TPPeer *peer, BOOL *stop) {
264 if (nil != peer.dynamicInfo) {
265 maxRemovals = MAX(maxRemovals, peer.dynamicInfo.removals);
266 }
267 }];
268 return maxRemovals;
269 }
270
271 - (TPPeerDynamicInfo *)createDynamicInfoForPeerWithID:(NSString *)peerID
272 circle:(TPCircle *)circle
273 clique:(NSString *)clique
274 newRemovals:(TPCounter)newRemovals
275 error:(NSError **)error
276 {
277 TPPeer *peer = self.peersByID[peerID];
278
279 TPCounter clock = [self maxClock] + 1;
280 TPCounter removals = [self maxRemovals] + newRemovals;
281
282 return [TPPeerDynamicInfo dynamicInfoWithCircleID:circle.circleID
283 clique:clique
284 removals:removals
285 clock:clock
286 trustSigningKey:peer.permanentInfo.trustSigningKey
287 error:error];
288 }
289
290 - (BOOL)canTrustCandidate:(TPPeerPermanentInfo *)candidate inEpoch:(TPCounter)epoch
291 {
292 return candidate.epoch + 1 >= epoch;
293 }
294
295 - (BOOL)canIntroduceCandidate:(TPPeerPermanentInfo *)candidate
296 withSponsor:(TPPeerPermanentInfo *)sponsor
297 toEpoch:(TPCounter)epoch
298 underPolicy:(id<TPPolicy>)policy
299 {
300 if (![self canTrustCandidate:candidate inEpoch:sponsor.epoch]) {
301 return NO;
302 }
303 if (![self canTrustCandidate:candidate inEpoch:epoch]) {
304 return NO;
305 }
306
307 NSString *sponsorCategory = [policy categoryForModel:sponsor.modelID];
308 NSString *candidateCategory = [policy categoryForModel:candidate.modelID];
309
310 return [policy trustedPeerInCategory:sponsorCategory canIntroduceCategory:candidateCategory];
311 }
312
313 - (nullable TPVoucher *)createVoucherForCandidate:(TPPeerPermanentInfo *)candidate
314 withSponsorID:(NSString *)sponsorID
315 error:(NSError **)error
316 {
317 TPPeer *sponsor = [self peerWithID:sponsorID];
318
319 NSSet<NSString*> *peerIDs = [sponsor.trustedPeerIDs setByAddingObject:candidate.peerID];
320 id<TPPolicy> policy = [self policyForPeerIDs:peerIDs error:error];
321 if (nil == policy) {
322 return nil;
323 }
324
325 if (![self canIntroduceCandidate:candidate
326 withSponsor:sponsor.permanentInfo
327 toEpoch:sponsor.permanentInfo.epoch
328 underPolicy:policy])
329 {
330 if (error) {
331 *error = nil;
332 }
333 return nil;
334 }
335
336 // clock is correctly zero if sponsor does not yet have dynamicInfo
337 TPCounter clock = sponsor.dynamicInfo.clock;
338 return [TPVoucher voucherWithBeneficiaryID:candidate.peerID
339 sponsorID:sponsorID
340 clock:clock
341 trustSigningKey:sponsor.permanentInfo.trustSigningKey
342 error:error];
343 }
344
345 - (TPResult)registerVoucher:(TPVoucher *)voucher
346 {
347 NSAssert(voucher, @"voucher must not be nil");
348 TPPeer *sponsor = [self peerWithID:voucher.sponsorID];
349 if (![sponsor.permanentInfo.trustSigningKey checkSignature:voucher.voucherInfoSig matchesData:voucher.voucherInfoPList]) {
350 return TPResultSignatureMismatch;
351 }
352 [self.vouchers addObject:voucher];
353 return TPResultOk;
354 }
355
356 - (NSSet<NSString*> *)calculateUnusedCircleIDs
357 {
358 NSMutableSet<NSString *>* circleIDs = [NSMutableSet setWithArray:[self.circlesByID allKeys]];
359
360 [self.peersByID enumerateKeysAndObjectsUsingBlock:^(NSString *peerID, TPPeer *peer, BOOL *stop) {
361 if (nil != peer.dynamicInfo) {
362 [circleIDs removeObject:peer.dynamicInfo.circleID];
363 }
364 }];
365 return circleIDs;
366 }
367
368 - (nullable NSError *)considerCandidateID:(NSString *)candidateID
369 withSponsor:(TPPeer *)sponsor
370 toExpandIncludedPeerIDs:(NSMutableSet<NSString *>*)includedPeerIDs
371 andExcludedPeerIDs:(NSMutableSet<NSString *>*)excludedPeerIDs
372 forEpoch:(TPCounter)epoch
373 {
374 if ([includedPeerIDs containsObject:candidateID]) {
375 // Already included, nothing to do.
376 return nil;
377 }
378 if ([excludedPeerIDs containsObject:candidateID]) {
379 // Denied.
380 return nil;
381 }
382
383 TPPeer *candidate = self.peersByID[candidateID];
384 if (nil == candidate) {
385 return nil;
386 }
387 NSMutableSet<NSString*> *peerIDs = [NSMutableSet setWithSet:includedPeerIDs];
388 [peerIDs minusSet:excludedPeerIDs];
389 [peerIDs addObject:candidateID];
390 NSError *error = nil;
391 id<TPPolicy> policy = [self policyForPeerIDs:peerIDs error:&error];
392 if (nil == policy) {
393 return error;
394 }
395
396 if ([self canIntroduceCandidate:candidate.permanentInfo
397 withSponsor:sponsor.permanentInfo
398 toEpoch:epoch
399 underPolicy:policy])
400 {
401 [includedPeerIDs addObject:candidateID];
402 [excludedPeerIDs unionSet:candidate.circle.excludedPeerIDs];
403
404 // The accepted candidate can now be a sponsor.
405 error = [self recursivelyExpandIncludedPeerIDs:includedPeerIDs
406 andExcludedPeerIDs:excludedPeerIDs
407 withPeersTrustedBySponsorID:candidateID
408 forEpoch:epoch];
409 if (nil != error) {
410 return error;
411 }
412 }
413 return nil;
414 }
415
416 - (nullable NSError *)considerVouchersSponsoredByPeer:(TPPeer *)sponsor
417 toReecursivelyExpandIncludedPeerIDs:(NSMutableSet<NSString *>*)includedPeerIDs
418 andExcludedPeerIDs:(NSMutableSet<NSString *>*)excludedPeerIDs
419 forEpoch:(TPCounter)epoch
420 {
421 for (TPVoucher *voucher in self.vouchers) {
422 if ([voucher.sponsorID isEqualToString:sponsor.peerID]
423 && voucher.clock == sponsor.dynamicInfo.clock)
424 {
425 NSError *error = [self considerCandidateID:voucher.beneficiaryID
426 withSponsor:sponsor
427 toExpandIncludedPeerIDs:includedPeerIDs
428 andExcludedPeerIDs:excludedPeerIDs
429 forEpoch:epoch];
430 if (nil != error) {
431 return error;
432 }
433 }
434 }
435 return nil;
436 }
437
438 - (nullable NSError *)recursivelyExpandIncludedPeerIDs:(NSMutableSet<NSString *>*)includedPeerIDs
439 andExcludedPeerIDs:(NSMutableSet<NSString *>*)excludedPeerIDs
440 withPeersTrustedBySponsorID:(NSString *)sponsorID
441 forEpoch:(TPCounter)epoch
442 {
443 TPPeer *sponsor = self.peersByID[sponsorID];
444 if (nil == sponsor) {
445 // It is possible that we might receive a voucher sponsored
446 // by a peer that has not yet been registered or has been deleted,
447 // or that a peer will have a circle that includes a peer that
448 // has not yet been registered or has been deleted.
449 return nil;
450 }
451 [excludedPeerIDs unionSet:sponsor.circle.excludedPeerIDs];
452 for (NSString *candidateID in sponsor.circle.includedPeerIDs) {
453 NSError *error = [self considerCandidateID:candidateID
454 withSponsor:sponsor
455 toExpandIncludedPeerIDs:includedPeerIDs
456 andExcludedPeerIDs:excludedPeerIDs
457 forEpoch:epoch];
458 if (nil != error) {
459 return error;
460 }
461 }
462 return [self considerVouchersSponsoredByPeer:sponsor
463 toReecursivelyExpandIncludedPeerIDs:includedPeerIDs
464 andExcludedPeerIDs:excludedPeerIDs
465 forEpoch:epoch];
466 }
467
468 - (TPPeerDynamicInfo *)calculateDynamicInfoForPeerWithID:(NSString *)peerID
469 addingPeerIDs:(NSArray<NSString*> *)addingPeerIDs
470 removingPeerIDs:(NSArray<NSString*> *)removingPeerIDs
471 createClique:(NSString* (^)())createClique
472 updatedCircle:(TPCircle **)updatedCircle
473 error:(NSError **)error
474 {
475 TPPeer *peer = [self peerWithID:peerID];
476 TPCounter epoch = peer.permanentInfo.epoch;
477
478 // If we have dynamicInfo then we must know the corresponding circle.
479 NSAssert(nil != peer.circle || nil == peer.dynamicInfo, @"dynamicInfo without corresponding circle");
480
481 // If I am excluded by myself then make no changes. I am no longer playing the game.
482 // This is useful in the case where I have replaced myself with a new peer.
483 if ([peer.circle.excludedPeerIDs containsObject:peerID]) {
484 if (updatedCircle) {
485 *updatedCircle = peer.circle;
486 }
487 return peer.dynamicInfo;
488 }
489
490 NSMutableSet<NSString *> *includedPeerIDs = [NSMutableSet setWithSet:peer.circle.includedPeerIDs];
491 NSMutableSet<NSString *> *excludedPeerIDs = [NSMutableSet setWithSet:peer.circle.excludedPeerIDs];
492
493 // I trust myself by default, though this might be overridden by excludedPeerIDs
494 [includedPeerIDs addObject:peerID];
495
496 // The user has explictly told us to trust addingPeerIDs.
497 // This implies that the peers included in the circles of addingPeerIDs should also be trusted,
498 // as long epoch tests pass. This is regardless of whether trust policy says a member of addingPeerIDs
499 // can *introduce* a peer in its circle, because it isn't introducing it, the user already trusts it.
500 [includedPeerIDs addObjectsFromArray:addingPeerIDs];
501 for (NSString *addingPeerID in addingPeerIDs) {
502 TPPeer *addingPeer = self.peersByID[addingPeerID];
503 for (NSString *candidateID in addingPeer.circle.includedPeerIDs) {
504 TPPeer *candidate = self.peersByID[candidateID];
505 if (candidate && [self canTrustCandidate:candidate.permanentInfo inEpoch:epoch]) {
506 [includedPeerIDs addObject:candidateID];
507 }
508 }
509 }
510
511 [excludedPeerIDs addObjectsFromArray:removingPeerIDs];
512 [includedPeerIDs minusSet:excludedPeerIDs];
513
514 // We iterate over a copy because the loop will mutate includedPeerIDs
515 NSSet<NSString *>* sponsorIDs = [includedPeerIDs copy];
516
517 for (NSString *sponsorID in sponsorIDs) {
518 NSError *err = [self recursivelyExpandIncludedPeerIDs:includedPeerIDs
519 andExcludedPeerIDs:excludedPeerIDs
520 withPeersTrustedBySponsorID:sponsorID
521 forEpoch:epoch];
522 if (nil != err) {
523 if (error) {
524 *error = err;
525 }
526 return nil;
527 }
528 }
529 NSError *err = [self considerVouchersSponsoredByPeer:peer
530 toReecursivelyExpandIncludedPeerIDs:includedPeerIDs
531 andExcludedPeerIDs:excludedPeerIDs
532 forEpoch:epoch];
533 if (nil != err) {
534 if (error) {
535 *error = err;
536 }
537 return nil;
538 }
539
540 [includedPeerIDs minusSet:excludedPeerIDs];
541
542 NSString *clique = [self bestCliqueAmongPeerIDs:includedPeerIDs];
543 if (nil == clique) {
544 clique = peer.dynamicInfo.clique;
545 }
546 if (nil == clique && nil != createClique) {
547 clique = createClique();
548 }
549 if (nil == clique) {
550 // Either nil == createClique or createClique returned nil.
551 // We would create a clique but caller has said not to.
552 // Not an error, it's just what they asked for.
553 if (error) {
554 *error = nil;
555 }
556 return nil;
557 }
558
559 TPCircle *newCircle;
560 if ([excludedPeerIDs containsObject:peerID]) {
561 // I have been kicked out, and anybody who trusts me should now exclude me.
562 newCircle = [TPCircle circleWithIncludedPeerIDs:addingPeerIDs excludedPeerIDs:@[peerID]];
563 } else {
564 // Drop items from excludedPeerIDs that predate epoch - 1
565 NSSet<NSString*> *filteredExcluded = [excludedPeerIDs objectsPassingTest:^BOOL(NSString *exPeerID, BOOL *stop) {
566 TPPeer *exPeer = self.peersByID[exPeerID];
567 if (nil == exPeer) {
568 return YES;
569 }
570 // If we could trust it then we have to keep it in the exclude list.
571 return [self canTrustCandidate:exPeer.permanentInfo inEpoch:epoch];
572 }];
573 newCircle = [TPCircle circleWithIncludedPeerIDs:[includedPeerIDs allObjects]
574 excludedPeerIDs:[filteredExcluded allObjects]];
575 }
576 if (updatedCircle) {
577 *updatedCircle = newCircle;
578 }
579 return [self createDynamicInfoForPeerWithID:peerID
580 circle:newCircle
581 clique:clique
582 newRemovals:[removingPeerIDs count]
583 error:error];
584 }
585
586 - (NSString *)bestCliqueAmongPeerIDs:(NSSet<NSString*>*)peerIDs
587 {
588 // The "best" clique is considered the one that is last in lexical ordering.
589 NSString *bestClique = nil;
590 for (NSString *peerID in peerIDs) {
591 NSString *clique = self.peersByID[peerID].dynamicInfo.clique;
592 if (clique) {
593 if (bestClique && NSOrderedAscending != [bestClique compare:clique]) {
594 continue;
595 }
596 bestClique = clique;
597 }
598 }
599 return bestClique;
600 }
601
602 - (TPCircle *)advancePeerWithID:(NSString *)peerID
603 addingPeerIDs:(NSArray<NSString*> *)addingPeerIDs
604 removingPeerIDs:(NSArray<NSString*> *)removingPeerIDs
605 createClique:(NSString* (^)())createClique
606 {
607 TPCircle *circle = nil;
608 TPPeerDynamicInfo *dyn;
609 dyn = [self calculateDynamicInfoForPeerWithID:peerID
610 addingPeerIDs:addingPeerIDs
611 removingPeerIDs:removingPeerIDs
612 createClique:createClique
613 updatedCircle:&circle
614 error:NULL];
615 if (dyn) {
616 [self registerCircle:circle];
617 [self updateDynamicInfo:dyn forPeerWithID:peerID];
618 return circle;
619 } else {
620 return nil;
621 }
622 }
623
624 NSString *TPErrorDomain = @"com.apple.security.trustedpeers";
625
626 enum {
627 TPErrorUnknownPolicyVersion = 1,
628 TPErrorPolicyHashMismatch = 2,
629 TPErrorMissingStableInfo = 3,
630 };
631
632
633 - (nullable id<TPPolicy>)policyForPeerIDs:(NSSet<NSString*> *)peerIDs
634 error:(NSError **)error
635 {
636 NSAssert(peerIDs.count > 0, @"policyForPeerIDs does not accept empty set");
637
638 TPPolicyDocument *newestPolicyDoc = nil;
639
640 // This will become the union of policySecrets across the members of peerIDs
641 NSMutableDictionary<NSString*,NSData*> *secrets = [NSMutableDictionary dictionary];
642
643 for (NSString *peerID in peerIDs) {
644 TPPeerStableInfo *stableInfo = [self peerWithID:peerID].stableInfo;
645 if (nil == stableInfo) {
646 // Allowing missing stableInfo here might be useful if we are writing a voucher
647 // for a peer for which we got permanentInfo over some channel that does not
648 // also convey stableInfo.
649 continue;
650 }
651 for (NSString *name in stableInfo.policySecrets) {
652 secrets[name] = stableInfo.policySecrets[name];
653 }
654 if (newestPolicyDoc && newestPolicyDoc.policyVersion > stableInfo.policyVersion) {
655 continue;
656 }
657 TPPolicyDocument *policyDoc = self.policiesByVersion[@(stableInfo.policyVersion)];
658 if (nil == policyDoc) {
659 if (error) {
660 *error = [NSError errorWithDomain:TPErrorDomain
661 code:TPErrorUnknownPolicyVersion
662 userInfo:@{
663 @"peerID": peerID,
664 @"policyVersion": @(stableInfo.policyVersion)
665 }];
666 }
667 return nil;
668 }
669 if (![policyDoc.policyHash isEqualToString:stableInfo.policyHash]) {
670 if (error) {
671 *error = [NSError errorWithDomain:TPErrorDomain
672 code:TPErrorPolicyHashMismatch
673 userInfo:@{
674 @"peerID": peerID,
675 @"policyVersion": @(stableInfo.policyVersion),
676 @"policyDocHash": policyDoc.policyHash,
677 @"peerExpectsHash": stableInfo.policyHash
678 }];
679 }
680 return nil;
681 }
682 newestPolicyDoc = policyDoc;
683 }
684 if (nil == newestPolicyDoc) {
685 // Can happen if no members of peerIDs have stableInfo
686 if (error) {
687 *error = [NSError errorWithDomain:TPErrorDomain
688 code:TPErrorMissingStableInfo
689 userInfo:nil];
690 }
691 return nil;
692 }
693 return [newestPolicyDoc policyWithSecrets:secrets decrypter:self.decrypter error:error];
694 }
695
696 - (NSSet<NSString*> *)getPeerIDsTrustedByPeerWithID:(NSString *)peerID
697 toAccessView:(NSString *)view
698 error:(NSError **)error
699 {
700 TPCircle *circle = [self peerWithID:peerID].circle;
701 NSMutableSet<NSString*> *peerIDs = [NSMutableSet set];
702
703 id<TPPolicy> policy = [self policyForPeerIDs:circle.includedPeerIDs error:error];
704
705 for (NSString *candidateID in circle.includedPeerIDs) {
706 TPPeer *candidate = self.peersByID[candidateID];
707 if (candidate != nil) {
708 NSString *category = [policy categoryForModel:candidate.permanentInfo.modelID];
709 if ([policy peerInCategory:category canAccessView:view]) {
710 [peerIDs addObject:candidateID];
711 }
712 }
713 }
714 return peerIDs;
715 }
716
717 - (NSDictionary<NSString*,NSNumber*> *)vectorClock
718 {
719 NSMutableDictionary<NSString*,NSNumber*> *dict = [NSMutableDictionary dictionary];
720
721 [self.peersByID enumerateKeysAndObjectsUsingBlock:^(NSString *peerID, TPPeer *peer, BOOL *stop) {
722 if (peer.stableInfo || peer.dynamicInfo) {
723 TPCounter clock = MAX(peer.stableInfo.clock, peer.dynamicInfo.clock);
724 dict[peerID] = @(clock);
725 }
726 }];
727 return dict;
728 }
729
730 @end