]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTSOSAdapter.m
Security-59306.101.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 - (void)updateOctagonKeySetWithAccount:(id<CKKSSelfPeer>)currentSelfPeer error:(NSError**)error {
233
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-sos", "no octagon keys available skipping updating SOS record");
237 return;
238 }
239
240 __block CFDataRef signingFullKey = CFBridgingRetain(currentSelfPeer.signingKey.keyData);
241 __block CFDataRef encryptionFullKey = CFBridgingRetain(currentSelfPeer.encryptionKey.keyData);
242 __block CFDataRef signingPublicKey = CFBridgingRetain(currentSelfPeer.publicSigningKey.keyData);
243 __block CFDataRef encryptionPublicKey = CFBridgingRetain(currentSelfPeer.publicEncryptionKey.keyData);
244 __block SecKeyRef octagonSigningPubSecKey = CFRetainSafe(currentSelfPeer.publicSigningKey.secKey);
245 __block SecKeyRef octagonEncryptionPubSecKey = CFRetainSafe(currentSelfPeer.publicEncryptionKey.secKey);
246
247 SFAnalyticsActivityTracker *tracker = [[[CKKSAnalytics class] logger] startLogSystemMetricsForActivityNamed:OctagonSOSAdapterUpdateKeys];
248
249 /* WARNING! Please be very very careful passing keys to this routine. Note the slightly different variations of keys*/
250 SOSCCPerformUpdateOfAllOctagonKeys(signingFullKey, encryptionFullKey,
251 signingPublicKey, encryptionPublicKey,
252 octagonSigningPubSecKey, octagonEncryptionPubSecKey,
253 ^(CFErrorRef error) {
254 [tracker stopWithEvent: OctagonSOSAdapterUpdateKeys result:(__bridge NSError * _Nullable)(error)];
255 if(error){
256 secerror("octagon-sos: failed to update Octagon keys in SOS:%@", error);
257 }else {
258 secnotice("octagon-sos", "successfully updated Octagon keys in SOS!");
259 }
260 CFRelease(signingFullKey);
261 CFRelease(encryptionFullKey);
262 CFRelease(signingPublicKey);
263 CFRelease(encryptionPublicKey);
264 CFRelease(octagonSigningPubSecKey);
265 CFRelease(octagonEncryptionPubSecKey);
266 });
267 }
268
269 - (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener {
270 [self.peerChangeListeners registerListener:listener];
271 }
272
273 - (void)sendSelfPeerChangedUpdate {
274 [self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
275 [listener selfPeerChanged: self];
276 }];
277 }
278
279 - (void)sendTrustedPeerSetChangedUpdate {
280 [self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
281 [listener trustedPeerSetChanged: self];
282 }];
283 }
284
285 - (nonnull CKKSPeerProviderState *)currentState {
286 __block CKKSPeerProviderState* result = nil;
287
288 [SOSAccount performOnQuietAccountQueue: ^{
289 NSError* selfPeersError = nil;
290 CKKSSelves* currentSelfPeers = [self fetchSelfPeers:&selfPeersError];
291
292 NSError* trustedPeersError = nil;
293 NSSet<id<CKKSRemotePeerProtocol>>* currentTrustedPeers = [self fetchTrustedPeers:&trustedPeersError];
294
295 result = [[CKKSPeerProviderState alloc] initWithPeerProviderID:self.providerID
296 essential:self.essential
297 selfPeers:currentSelfPeers
298 selfPeersError:selfPeersError
299 trustedPeers:currentTrustedPeers
300 trustedPeersError:trustedPeersError];
301 }];
302
303 return result;
304 }
305 @end
306
307 @implementation OTSOSMissingAdapter
308 @synthesize sosEnabled;
309 @synthesize providerID = _providerID;
310 @synthesize essential = _essential;
311
312 - (instancetype)init {
313 if((self = [super init])) {
314 self.sosEnabled = false;
315 _providerID = @"[OTSOSMissingAdapter]";
316
317 // This adapter is never going to return anything, so you probably shouldn't ever consider it Must Succeed
318 _essential = NO;
319 }
320 return self;
321 }
322
323 - (SOSCCStatus)circleStatus:(NSError**)error
324 {
325 return kSOSCCNotInCircle;
326 }
327
328 - (id<CKKSSelfPeer> _Nullable)currentSOSSelf:(NSError**)error
329 {
330 if(error) {
331 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
332 code:errSecUnimplemented
333 description:@"SOS unsupported on this platform"];
334 }
335 return nil;
336 }
337
338 - (NSSet<id<CKKSRemotePeerProtocol>>* _Nullable)fetchTrustedPeers:(NSError**)error
339 {
340 if(error) {
341 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
342 code:errSecUnimplemented
343 description:@"SOS unsupported on this platform"];
344 }
345 return nil;
346 }
347
348 - (void)updateOctagonKeySetWithAccount:(nonnull id<CKKSSelfPeer>)currentSelfPeer error:(NSError *__autoreleasing _Nullable * _Nullable)error {
349 if(error) {
350 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
351 code:errSecUnimplemented
352 description:@"SOS unsupported on this platform"];
353 }
354 }
355
356 - (CKKSSelves * _Nullable)fetchSelfPeers:(NSError * _Nullable __autoreleasing * _Nullable)error
357 {
358 if(error) {
359 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
360 code:errSecUnimplemented
361 description:@"SOS unsupported on this platform"];
362 }
363 return nil;
364 }
365
366 - (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener
367 {
368 // no op
369 }
370
371 - (void)sendSelfPeerChangedUpdate
372 {
373 // no op
374 }
375
376 - (void)sendTrustedPeerSetChangedUpdate
377 {
378 // no op
379 }
380
381 - (nonnull CKKSPeerProviderState *)currentState {
382 NSError* unimplementedError = [NSError errorWithDomain:NSOSStatusErrorDomain
383 code:errSecUnimplemented
384 description:@"SOS unsupported on this platform"];
385 return [[CKKSPeerProviderState alloc] initWithPeerProviderID:self.providerID
386 essential:self.essential
387 selfPeers:nil
388 selfPeersError:unimplementedError
389 trustedPeers:nil
390 trustedPeersError:unimplementedError];
391 }
392
393 @end
394
395 @implementation OTSOSAdapterHelpers
396
397 + (NSArray<NSData*>*)peerPublicSigningKeySPKIs:(NSSet<id<CKKSPeer>>* _Nullable)peerSet
398 {
399 NSMutableArray<NSData*>* publicSigningSPKIs = [NSMutableArray array];
400
401 for(id<CKKSPeer> peer in peerSet) {
402 NSData* spki = [peer.publicSigningKey encodeSubjectPublicKeyInfo];
403 if(!spki) {
404 secerror("octagon-sos: Can't create SPKI for peer: %@", peer);
405 } else {
406 secerror("octagon-sos: Created SPKI for peer: %@", peer);
407 [publicSigningSPKIs addObject:spki];
408 }
409 }
410 return publicSigningSPKIs;
411 }
412
413 + (NSArray<NSData*>* _Nullable)peerPublicSigningKeySPKIsForCircle:(id<OTSOSAdapter>)sosAdapter error:(NSError**)error
414 {
415 NSError* peerError = nil;
416 SOSCCStatus sosStatus = [sosAdapter circleStatus:&peerError];
417
418 if(sosStatus != kSOSCCInCircle || peerError) {
419 secerror("octagon-sos: Not in circle; not preapproving keys: %@ (%@)", SOSAccountGetSOSCCStatusString(sosStatus), peerError);
420 if(error) {
421 *error = peerError;
422 }
423 return nil;
424 } else {
425 // We're in-circle: preapprove these peers
426 NSError* peerError = nil;
427
428 NSSet<id<CKKSRemotePeerProtocol>>* peerSet = [sosAdapter fetchTrustedPeers:&peerError];
429
430 if(!peerSet || peerError) {
431 secerror("octagon-sos: Can't fetch trusted peer SPKIs: %@", peerError);
432 if(error) {
433 *error = peerError;
434 }
435 return nil;
436 }
437
438 return [self peerPublicSigningKeySPKIs:peerSet];
439 }
440 }
441 @end
442 #endif // OCTAGON