]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTSOSAdapter.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ot / OTSOSAdapter.m
1 #if OCTAGON
2 #import <dispatch/dispatch.h>
3 #import <notify.h>
4
5 #import "keychain/ot/OTSOSAdapter.h"
6
7 #import "keychain/SecureObjectSync/SOSCloudCircleInternal.h"
8 #include "keychain/SecureObjectSync/SOSViews.h"
9
10 #import "keychain/securityd/SOSCloudCircleServer.h"
11 #import "OSX/utilities/SecCFWrappers.h"
12
13 #import "keychain/categories/NSError+UsefulConstructors.h"
14
15 #import "keychain/ckks/CKKS.h"
16 #import "keychain/ckks/CKKSLockStateTracker.h"
17 #import "keychain/ckks/CKKSListenerCollection.h"
18 #import "keychain/SecureObjectSync/SOSAccount.h"
19 #import "keychain/SecureObjectSync/SOSAccountPriv.h"
20
21 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
22 #import "keychain/ot/OTDefines.h"
23 #import "keychain/ot/OTConstants.h"
24 #import "keychain/ckks/CKKSAnalytics.h"
25
26 @interface OTSOSActualAdapter ()
27 @property CKKSListenerCollection* peerChangeListeners;
28 @end
29
30 @implementation OTSOSActualAdapter
31 @synthesize sosEnabled;
32 @synthesize essential = _essential;
33 @synthesize providerID = _providerID;
34
35 + (NSSet<NSString*>*)sosCKKSViewList
36 {
37 static NSSet<NSString*>* list = nil;
38 static dispatch_once_t onceToken;
39 dispatch_once(&onceToken, ^{
40 list = CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
41 });
42 return list;
43 }
44
45 - (instancetype)initAsEssential:(BOOL)essential {
46 if((self = [super init])) {
47 self.sosEnabled = true;
48
49 _essential = essential;
50
51 _providerID = @"[OTSOSActualAdapter]";
52 _peerChangeListeners = [[CKKSListenerCollection alloc] initWithName:@"ckks-sos"];
53
54 __typeof(self) weakSelf = self;
55 // If this is a live server, register with notify
56 if(!SecCKKSTestsEnabled()) {
57 int token = 0;
58 notify_register_dispatch(kSOSCCCircleOctagonKeysChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
59 // Since SOS doesn't change the self peer, we can reliably just send "trusted peers changed"; it'll be mostly right
60 secnotice("octagon-sos", "Received a notification that the SOS Octagon peer set changed");
61 [weakSelf sendTrustedPeerSetChangedUpdate];
62 });
63 }
64 }
65 return self;
66 }
67
68 - (NSString*)description
69 {
70 return [NSString stringWithFormat:@"<OTSOSActualAdapter e:%d>", self.essential];
71 }
72
73 - (SOSCCStatus)circleStatus:(NSError**)error
74 {
75 CFErrorRef cferror = nil;
76 SOSCCStatus status = SOSCCThisDeviceIsInCircle(&cferror);
77 if(error && cferror) {
78 *error = CFBridgingRelease(cferror);
79 } else {
80 CFReleaseNull(cferror);
81 }
82 return status;
83 }
84
85 - (id<CKKSSelfPeer> _Nullable)currentSOSSelf:(NSError**)error
86 {
87 __block SFECKeyPair* signingPrivateKey = nil;
88 __block SFECKeyPair* encryptionPrivateKey = nil;
89
90 __block NSError* localerror = nil;
91
92 CFErrorRef cferror = nil;
93
94 SOSCCStatus circleStatus = [self circleStatus:&localerror];
95 if(circleStatus != kSOSCCInCircle) {
96 if(!localerror) {
97 localerror = [NSError errorWithDomain:CKKSErrorDomain
98 code:CKKSNoPeersAvailable
99 description:@"Not in SOS circle, but no error returned"];
100 }
101 secerror("octagon-sos: Not in circle : %@ %@", SOSAccountGetSOSCCStatusString(circleStatus), localerror);
102 if(error) {
103 *error = localerror;
104 }
105 return nil;
106 }
107
108 SOSPeerInfoRef egoPeerInfo = SOSCCCopyMyPeerInfo(&cferror);
109 NSString* egoPeerID = egoPeerInfo ? (NSString*)CFBridgingRelease(CFRetainSafe(SOSPeerInfoGetPeerID(egoPeerInfo))) : nil;
110 CFReleaseNull(egoPeerInfo);
111
112 if(!egoPeerID || cferror) {
113 localerror = CFBridgingRelease(cferror);
114 if(!localerror) {
115 localerror = [NSError errorWithDomain:CKKSErrorDomain
116 code:CKKSNoPeersAvailable
117 description:@"No SOS peer info available, but no error returned"];
118 }
119
120 secerror("octagon-sos: Error fetching self peer : %@", cferror);
121 if(error) {
122 *error = localerror;
123 }
124 return nil;
125 }
126
127 SOSCCPerformWithAllOctagonKeys(^(SecKeyRef octagonEncryptionKey, SecKeyRef octagonSigningKey, CFErrorRef cferror) {
128 if(cferror) {
129 localerror = (__bridge NSError*)cferror;
130 return;
131 }
132 if (!cferror && octagonEncryptionKey && octagonSigningKey) {
133 signingPrivateKey = [[SFECKeyPair alloc] initWithSecKey:octagonSigningKey];
134 encryptionPrivateKey = [[SFECKeyPair alloc] initWithSecKey:octagonEncryptionKey];
135 } else {
136 localerror = [NSError errorWithDomain:CKKSErrorDomain
137 code:CKKSNoPeersAvailable
138 description:@"Not all SOS peer keys available, but no error returned"];
139 }
140 });
141
142 if(localerror) {
143 if(![[CKKSLockStateTracker globalTracker] isLockedError:localerror]) {
144 secerror("octagon-sos: Error fetching self encryption keys: %@", localerror);
145 }
146 if(error) {
147 *error = localerror;
148 }
149 return nil;
150 }
151
152 CKKSSOSSelfPeer* selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:egoPeerID
153 encryptionKey:encryptionPrivateKey
154 signingKey:signingPrivateKey
155 viewList:[OTSOSActualAdapter sosCKKSViewList]];
156 return selfPeer;
157 }
158
159 - (CKKSSelves * _Nullable)fetchSelfPeers:(NSError *__autoreleasing _Nullable * _Nullable)error {
160 id<CKKSSelfPeer> peer = [self currentSOSSelf:error];
161 if(!peer) {
162 return nil;
163 }
164
165 return [[CKKSSelves alloc] initWithCurrent:peer allSelves:nil];
166 }
167
168 - (NSSet<id<CKKSRemotePeerProtocol>>* _Nullable)fetchTrustedPeers:(NSError**)error
169 {
170 __block NSMutableSet<id<CKKSRemotePeerProtocol>>* peerSet = [NSMutableSet set];
171
172 __block NSError* localError = nil;
173
174 SOSCCPerformWithTrustedPeers(^(CFSetRef sosPeerInfoRefs, CFErrorRef cfTrustedPeersError) {
175 if(cfTrustedPeersError) {
176 secerror("octagon-sos: Error fetching trusted peers: %@", cfTrustedPeersError);
177 if(localError) {
178 localError = (__bridge NSError*)cfTrustedPeersError;
179 }
180 }
181
182 CFSetForEach(sosPeerInfoRefs, ^(const void* voidPeer) {
183 CFErrorRef cfPeerError = NULL;
184 SOSPeerInfoRef sosPeerInfoRef = (SOSPeerInfoRef)voidPeer;
185
186 if(!sosPeerInfoRef) {
187 return;
188 }
189
190 CFStringRef cfpeerID = SOSPeerInfoGetPeerID(sosPeerInfoRef);
191 SecKeyRef cfOctagonSigningKey = NULL, cfOctagonEncryptionKey = NULL;
192
193 cfOctagonSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(sosPeerInfoRef, &cfPeerError);
194 if (cfOctagonSigningKey) {
195 cfOctagonEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(sosPeerInfoRef, &cfPeerError);
196 }
197
198 if(cfOctagonSigningKey == NULL || cfOctagonEncryptionKey == NULL) {
199 // Don't log non-debug for -50; it almost always just means this peer didn't have octagon keys
200 if(cfPeerError == NULL
201 || !(CFEqualSafe(CFErrorGetDomain(cfPeerError), kCFErrorDomainOSStatus) && (CFErrorGetCode(cfPeerError) == errSecParam)))
202 {
203 secerror("octagon-sos: error fetching octagon keys for peer: %@ %@", sosPeerInfoRef, cfPeerError);
204 } else {
205 secinfo("octagon-sos", "Peer(%@) doesn't have Octagon keys, but this is expected: %@", cfpeerID, cfPeerError);
206 }
207 }
208
209 // Add all peers to the trust set: old-style SOS peers will just have null keys
210 SFECPublicKey* signingPublicKey = cfOctagonSigningKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonSigningKey] : nil;
211 SFECPublicKey* encryptionPublicKey = cfOctagonEncryptionKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonEncryptionKey] : nil;
212
213 CKKSSOSPeer* peer = [[CKKSSOSPeer alloc] initWithSOSPeerID:(__bridge NSString*)cfpeerID
214 encryptionPublicKey:encryptionPublicKey
215 signingPublicKey:signingPublicKey
216 viewList:[OTSOSActualAdapter sosCKKSViewList]];
217 [peerSet addObject:peer];
218
219 CFReleaseNull(cfOctagonSigningKey);
220 CFReleaseNull(cfOctagonEncryptionKey);
221 CFReleaseNull(cfPeerError);
222 });
223 });
224
225 if(error && localError) {
226 *error = localError;
227 }
228
229 return peerSet;
230 }
231
232
233 - (BOOL)preloadOctagonKeySetOnAccount:(id<CKKSSelfPeer>)currentSelfPeer error:(NSError**)error {
234 // in case we don't have the keys, don't try to update them
235 if (currentSelfPeer.publicSigningKey.secKey == NULL || currentSelfPeer.publicEncryptionKey.secKey == NULL) {
236 secnotice("octagon-preload-keys", "no octagon keys available skipping updating SOS record");
237 return YES;
238 }
239
240 __block CFDataRef signingFullKey = CFBridgingRetain(currentSelfPeer.signingKey.keyData);
241 __block CFDataRef encryptionFullKey = CFBridgingRetain(currentSelfPeer.encryptionKey.keyData);
242 __block SecKeyRef octagonSigningPubSecKey = CFRetainSafe(currentSelfPeer.publicSigningKey.secKey);
243 __block SecKeyRef octagonEncryptionPubSecKey = CFRetainSafe(currentSelfPeer.publicEncryptionKey.secKey);
244 __block SecKeyRef signingFullKeyRef = CFRetainSafe(currentSelfPeer.signingKey.secKey);
245 __block SecKeyRef encryptionFullKeyRef = CFRetainSafe(currentSelfPeer.encryptionKey.secKey);
246
247 SFAnalyticsActivityTracker *tracker = [[[CKKSAnalytics class] logger] startLogSystemMetricsForActivityNamed:OctagonSOSAdapterUpdateKeys];
248
249 __block BOOL ret;
250 __block NSError *localError = nil;
251
252 /* WARNING! Please be very very careful passing keys to this routine. Note the slightly different variations of keys*/
253 SOSCCPerformPreloadOfAllOctagonKeys(signingFullKey, encryptionFullKey,
254 signingFullKeyRef, encryptionFullKeyRef,
255 octagonSigningPubSecKey, octagonEncryptionPubSecKey,
256 ^(CFErrorRef cferror) {
257 [tracker stopWithEvent: OctagonSOSAdapterUpdateKeys result:(__bridge NSError * _Nullable)(cferror)];
258 if(cferror) {
259 secerror("octagon-preload-keys: failed to preload Octagon keys in SOS:%@", cferror);
260 ret = NO;
261 localError = (__bridge NSError *)cferror;
262 } else {
263 ret = YES;
264 secnotice("octagon-preload-keys", "successfully preloaded Octagon keys in SOS!");
265 }
266 CFRelease(signingFullKey);
267 CFRelease(encryptionFullKey);
268 CFRelease(octagonSigningPubSecKey);
269 CFRelease(octagonEncryptionPubSecKey);
270 CFRelease(signingFullKeyRef);
271 CFRelease(encryptionFullKeyRef);
272 });
273 if (error) {
274 *error = localError;
275 }
276 return ret;
277
278 }
279
280 - (BOOL)updateOctagonKeySetWithAccount:(id<CKKSSelfPeer>)currentSelfPeer error:(NSError**)error {
281
282 // in case we don't have the keys, don't try to update them
283 if (currentSelfPeer.publicSigningKey.secKey == NULL || currentSelfPeer.publicEncryptionKey.secKey == NULL) {
284 secnotice("octagon-sos", "no octagon keys available skipping updating SOS record");
285 return YES;
286 }
287
288 __block CFDataRef signingFullKey = CFBridgingRetain(currentSelfPeer.signingKey.keyData);
289 __block CFDataRef encryptionFullKey = CFBridgingRetain(currentSelfPeer.encryptionKey.keyData);
290 __block CFDataRef signingPublicKey = CFBridgingRetain(currentSelfPeer.publicSigningKey.keyData);
291 __block CFDataRef encryptionPublicKey = CFBridgingRetain(currentSelfPeer.publicEncryptionKey.keyData);
292 __block SecKeyRef octagonSigningPubSecKey = CFRetainSafe(currentSelfPeer.publicSigningKey.secKey);
293 __block SecKeyRef octagonEncryptionPubSecKey = CFRetainSafe(currentSelfPeer.publicEncryptionKey.secKey);
294
295 SFAnalyticsActivityTracker *tracker = [[[CKKSAnalytics class] logger] startLogSystemMetricsForActivityNamed:OctagonSOSAdapterUpdateKeys];
296
297 __block BOOL ret;
298 __block NSError *localError = nil;
299
300 /* WARNING! Please be very very careful passing keys to this routine. Note the slightly different variations of keys*/
301 SOSCCPerformUpdateOfAllOctagonKeys(signingFullKey, encryptionFullKey,
302 signingPublicKey, encryptionPublicKey,
303 octagonSigningPubSecKey, octagonEncryptionPubSecKey,
304 ^(CFErrorRef cferror) {
305 [tracker stopWithEvent: OctagonSOSAdapterUpdateKeys result:(__bridge NSError * _Nullable)(cferror)];
306 if(cferror) {
307 secerror("octagon-sos: failed to update Octagon keys in SOS:%@", cferror);
308 ret = NO;
309 localError = (__bridge NSError *)cferror;
310 } else {
311 ret = YES;
312 secnotice("octagon-sos", "successfully updated Octagon keys in SOS!");
313 }
314 CFRelease(signingFullKey);
315 CFRelease(encryptionFullKey);
316 CFRelease(signingPublicKey);
317 CFRelease(encryptionPublicKey);
318 CFRelease(octagonSigningPubSecKey);
319 CFRelease(octagonEncryptionPubSecKey);
320 });
321 if (error) {
322 *error = localError;
323 }
324 return ret;
325 }
326
327 - (BOOL)updateCKKS4AllStatus:(BOOL)status error:(NSError**)error
328 {
329 CFErrorRef cferror = nil;
330 bool result = SOSCCSetCKKS4AllStatus(status, &cferror);
331
332 NSError* localerror = CFBridgingRelease(cferror);
333
334 if(!result || localerror) {
335 secerror("octagon-sos: failed to update CKKS4All status in SOS: %@", localerror);
336 } else {
337 secnotice("octagon-sos", "successfully updated CKKS4All status in SOS to '%@'", status ? @"supported" : @"not supported");
338 }
339
340 if(error && localerror) {
341 *error = localerror;
342 }
343 return result;
344 }
345
346 - (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener {
347 [self.peerChangeListeners registerListener:listener];
348 }
349
350 - (void)sendSelfPeerChangedUpdate {
351 [self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
352 [listener selfPeerChanged: self];
353 }];
354 }
355
356 - (void)sendTrustedPeerSetChangedUpdate {
357 [self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
358 [listener trustedPeerSetChanged: self];
359 }];
360 }
361
362 - (nonnull CKKSPeerProviderState *)currentState {
363 __block CKKSPeerProviderState* result = nil;
364
365 [SOSAccount performOnQuietAccountQueue: ^{
366 NSError* selfPeersError = nil;
367 CKKSSelves* currentSelfPeers = [self fetchSelfPeers:&selfPeersError];
368
369 NSError* trustedPeersError = nil;
370 NSSet<id<CKKSRemotePeerProtocol>>* currentTrustedPeers = [self fetchTrustedPeers:&trustedPeersError];
371
372 result = [[CKKSPeerProviderState alloc] initWithPeerProviderID:self.providerID
373 essential:self.essential
374 selfPeers:currentSelfPeers
375 selfPeersError:selfPeersError
376 trustedPeers:currentTrustedPeers
377 trustedPeersError:trustedPeersError];
378 }];
379
380 return result;
381 }
382
383 - (BOOL)safariViewSyncingEnabled:(NSError**)error
384 {
385 CFErrorRef viewCFError = NULL;
386 SOSViewResultCode result = SOSCCView(kSOSViewAutofillPasswords, kSOSCCViewQuery, &viewCFError);
387
388 if(viewCFError) {
389 if(error) {
390 *error = CFBridgingRelease(viewCFError);
391 } else {
392 CFReleaseNull(viewCFError);
393 }
394 return NO;
395 }
396
397 return result == kSOSCCViewMember;
398 }
399 @end
400
401 @implementation OTSOSMissingAdapter
402 @synthesize sosEnabled;
403 @synthesize providerID = _providerID;
404 @synthesize essential = _essential;
405
406 - (instancetype)init {
407 if((self = [super init])) {
408 self.sosEnabled = false;
409 _providerID = @"[OTSOSMissingAdapter]";
410
411 // This adapter is never going to return anything, so you probably shouldn't ever consider it Must Succeed
412 _essential = NO;
413 }
414 return self;
415 }
416
417 - (SOSCCStatus)circleStatus:(NSError**)error
418 {
419 return kSOSCCNotInCircle;
420 }
421
422 - (id<CKKSSelfPeer> _Nullable)currentSOSSelf:(NSError**)error
423 {
424 if(error) {
425 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
426 code:errSecUnimplemented
427 description:@"SOS unsupported on this platform"];
428 }
429 return nil;
430 }
431
432 - (NSSet<id<CKKSRemotePeerProtocol>>* _Nullable)fetchTrustedPeers:(NSError**)error
433 {
434 if(error) {
435 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
436 code:errSecUnimplemented
437 description:@"SOS unsupported on this platform"];
438 }
439 return nil;
440 }
441
442 - (BOOL)updateOctagonKeySetWithAccount:(nonnull id<CKKSSelfPeer>)currentSelfPeer error:(NSError *__autoreleasing _Nullable * _Nullable)error {
443 if(error) {
444 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
445 code:errSecUnimplemented
446 description:@"SOS unsupported on this platform"];
447 }
448 return NO;
449 }
450
451 - (BOOL)updateCKKS4AllStatus:(BOOL)status error:(NSError**)error
452 {
453 if(error) {
454 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
455 code:errSecUnimplemented
456 description:@"SOS unsupported on this platform"];
457 }
458 return NO;
459 }
460
461 - (CKKSSelves * _Nullable)fetchSelfPeers:(NSError * _Nullable __autoreleasing * _Nullable)error
462 {
463 if(error) {
464 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
465 code:errSecUnimplemented
466 description:@"SOS unsupported on this platform"];
467 }
468 return nil;
469 }
470
471 - (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener
472 {
473 // no op
474 }
475
476 - (void)sendSelfPeerChangedUpdate
477 {
478 // no op
479 }
480
481 - (void)sendTrustedPeerSetChangedUpdate
482 {
483 // no op
484 }
485
486 - (nonnull CKKSPeerProviderState *)currentState {
487 NSError* unimplementedError = [NSError errorWithDomain:NSOSStatusErrorDomain
488 code:errSecUnimplemented
489 description:@"SOS unsupported on this platform"];
490 return [[CKKSPeerProviderState alloc] initWithPeerProviderID:self.providerID
491 essential:self.essential
492 selfPeers:nil
493 selfPeersError:unimplementedError
494 trustedPeers:nil
495 trustedPeersError:unimplementedError];
496 }
497
498 - (BOOL)safariViewSyncingEnabled:(NSError**)error
499 {
500 return NO;
501 }
502
503 - (BOOL)preloadOctagonKeySetOnAccount:(nonnull id<CKKSSelfPeer>)currentSelfPeer error:(NSError *__autoreleasing _Nullable * _Nullable)error {
504 if(error) {
505 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
506 code:errSecUnimplemented
507 description:@"SOS unsupported on this platform"];
508 }
509 return NO;
510 }
511
512 @end
513
514 @implementation OTSOSAdapterHelpers
515
516 + (NSArray<NSData*>*)peerPublicSigningKeySPKIs:(NSSet<id<CKKSPeer>>* _Nullable)peerSet
517 {
518 NSMutableArray<NSData*>* publicSigningSPKIs = [NSMutableArray array];
519
520 for(id<CKKSPeer> peer in peerSet) {
521 NSData* spki = [peer.publicSigningKey encodeSubjectPublicKeyInfo];
522 if(!spki) {
523 secerror("octagon-sos: Can't create SPKI for peer: %@", peer);
524 } else {
525 secerror("octagon-sos: Created SPKI for peer: %@", peer);
526 [publicSigningSPKIs addObject:spki];
527 }
528 }
529 return publicSigningSPKIs;
530 }
531
532 + (NSArray<NSData*>* _Nullable)peerPublicSigningKeySPKIsForCircle:(id<OTSOSAdapter>)sosAdapter error:(NSError**)error
533 {
534 NSError* peerError = nil;
535 SOSCCStatus sosStatus = [sosAdapter circleStatus:&peerError];
536
537 if(sosStatus != kSOSCCInCircle || peerError) {
538 secerror("octagon-sos: Not in circle; not preapproving keys: %@ (%@)", SOSAccountGetSOSCCStatusString(sosStatus), peerError);
539 if(error) {
540 *error = peerError;
541 }
542 return nil;
543 } else {
544 // We're in-circle: preapprove these peers
545 NSError* peerError = nil;
546
547 NSSet<id<CKKSRemotePeerProtocol>>* peerSet = [sosAdapter fetchTrustedPeers:&peerError];
548
549 if(!peerSet || peerError) {
550 secerror("octagon-sos: Can't fetch trusted peer SPKIs: %@", peerError);
551 if(error) {
552 *error = peerError;
553 }
554 return nil;
555 }
556
557 return [self peerPublicSigningKeySPKIs:peerSet];
558 }
559 }
560 @end
561 #endif // OCTAGON