]>
Commit | Line | Data |
---|---|---|
ecaf5866 A |
1 | /* |
2 | * Copyright (c) 2016 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 | ||
25 | #import "SecEntitlements.h" | |
26 | #import <Foundation/NSXPCConnection.h> | |
27 | #import <Foundation/NSXPCConnection_Private.h> | |
28 | ||
29 | ||
30 | #if OCTAGON | |
31 | #import "keychain/ot/OTControlProtocol.h" | |
32 | #import "keychain/ot/OTControl.h" | |
33 | #import "keychain/ot/OTContext.h" | |
34 | #import "keychain/ot/OTManager.h" | |
35 | #import "keychain/ot/OTDefines.h" | |
36 | #import "keychain/ot/OTRamping.h" | |
37 | #import "keychain/ot/SFPublicKey+SPKI.h" | |
38 | #import "keychain/ot/OT.h" | |
39 | #import "keychain/ot/OTConstants.h" | |
40 | ||
41 | #import "keychain/ckks/CloudKitCategories.h" | |
42 | #import "keychain/ckks/CKKSAnalytics.h" | |
43 | #import "keychain/ckks/CKKSNearFutureScheduler.h" | |
44 | #import "keychain/ckks/CKKS.h" | |
45 | #import "keychain/ckks/CKKSViewManager.h" | |
46 | #import "keychain/ckks/CKKSLockStateTracker.h" | |
47 | ||
48 | #import <CloudKit/CloudKit.h> | |
49 | #import <CloudKit/CloudKit_Private.h> | |
50 | ||
51 | #import <SecurityFoundation/SFKey.h> | |
52 | #import <SecurityFoundation/SFKey_Private.h> | |
53 | ||
54 | #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR | |
55 | #import <Accounts/Accounts.h> | |
56 | #import <Accounts/ACAccountStore_Private.h> | |
57 | #import <Accounts/ACAccountType_Private.h> | |
58 | #import <Accounts/ACAccountStore.h> | |
59 | #import <AppleAccount/ACAccountStore+AppleAccount.h> | |
60 | #import <AppleAccount/ACAccount+AppleAccount.h> | |
61 | #else | |
62 | #import <Accounts/Accounts.h> | |
63 | #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h> | |
64 | #import <AOSAccounts/MobileMePrefsCore.h> | |
65 | #import <AOSAccounts/ACAccountStore+iCloudAccount.h> | |
66 | #import <AOSAccounts/ACAccount+iCloudAccount.h> | |
67 | #import <AOSAccounts/iCloudAccount.h> | |
68 | #endif | |
69 | ||
70 | #import <Security/SecureObjectSync/SOSAccountTransaction.h> | |
71 | #pragma clang diagnostic push | |
72 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" | |
73 | #import <Security/SecureObjectSync/SOSAccount.h> | |
74 | #pragma clang diagnostic pop | |
75 | ||
76 | static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll"; | |
77 | static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore"; | |
78 | static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu"; | |
79 | static NSString* const kOTRampZoneName = @"metadata_zone"; | |
80 | #define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC) | |
81 | ||
82 | @interface OTManager () <NSXPCListenerDelegate, OTContextIdentityProvider> | |
83 | @property NSXPCListener *listener; | |
84 | @property (nonatomic, strong) OTContext* context; | |
85 | @property (nonatomic, strong) OTLocalStore *localStore; | |
86 | @property (nonatomic, strong) OTRamp *enrollRamp; | |
87 | @property (nonatomic, strong) OTRamp *restoreRamp; | |
88 | @property (nonatomic, strong) OTRamp *cfuRamp; | |
89 | @property (nonatomic, strong) CKKSNearFutureScheduler *cfuScheduler; | |
90 | @property (nonatomic, strong) NSDate *lastPostedCoreFollowUp; | |
91 | @end | |
92 | ||
93 | @implementation OTManager | |
94 | ||
95 | -(instancetype)init | |
96 | { | |
97 | OTLocalStore* localStore = nil; | |
98 | OTContext* context = nil; | |
99 | ||
100 | NSString* dsid = [self askAccountsForDSID]; | |
101 | if(dsid){ | |
102 | localStore = [[OTLocalStore alloc]initWithContextID:OTDefaultContext dsid:dsid path:nil error:nil]; | |
103 | context = [[OTContext alloc]initWithContextID:OTDefaultContext dsid:dsid localStore:self.localStore cloudStore:nil identityProvider:self error:nil]; | |
104 | } | |
105 | //initialize our scheduler | |
106 | CKKSNearFutureScheduler *cfuScheduler = [[CKKSNearFutureScheduler alloc] initWithName:@"scheduling-cfu" initialDelay:NUM_NSECS_IN_24_HRS continuingDelay:NUM_NSECS_IN_24_HRS keepProcessAlive:true dependencyDescriptionCode:CKKSResultDescriptionNone block:^{ | |
107 | secnotice("octagon", "running scheduled cfu block"); | |
108 | NSError* error = nil; | |
109 | [self scheduledCloudKitRampCheck:&error]; | |
110 | }]; | |
111 | ||
112 | //initialize our ramp objects | |
113 | [self initRamps]; | |
114 | ||
115 | return [self initWithContext:context | |
116 | localStore:localStore | |
117 | enroll:self.enrollRamp | |
118 | restore:self.restoreRamp | |
119 | cfu:self.cfuRamp | |
120 | cfuScheduler:cfuScheduler]; | |
121 | } | |
122 | ||
123 | -(instancetype) initWithContext:(OTContext*)context | |
124 | localStore:(OTLocalStore*)localStore | |
125 | enroll:(OTRamp*)enroll | |
126 | restore:(OTRamp*)restore | |
127 | cfu:(OTRamp*)cfu | |
128 | cfuScheduler:(CKKSNearFutureScheduler*)cfuScheduler | |
129 | { | |
130 | self = [super init]; | |
131 | if(self){ | |
132 | self.context = context; | |
133 | self.localStore = localStore; | |
134 | self.cfuRamp = cfu; | |
135 | self.enrollRamp = enroll; | |
136 | self.restoreRamp = restore; | |
137 | self.cfuScheduler = cfuScheduler; | |
138 | ||
139 | secnotice("octagon", "otmanager init"); | |
140 | } | |
141 | return self; | |
142 | } | |
143 | ||
144 | -(NSString*) askAccountsForDSID | |
145 | { | |
146 | NSString *dsid = nil; | |
147 | ACAccountStore *accountStore = [[ACAccountStore alloc] init]; | |
148 | ||
149 | #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR | |
150 | ACAccount *account = [accountStore aa_primaryAppleAccount]; | |
151 | dsid = [account aa_personID]; | |
152 | #else | |
153 | ACAccount *primaryiCloudAccount = nil; | |
154 | if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){ | |
155 | primaryiCloudAccount = [accountStore icaPrimaryAppleAccount]; | |
156 | } | |
157 | dsid = [primaryiCloudAccount icaPersonID]; | |
158 | #endif | |
159 | return dsid; | |
160 | } | |
161 | ||
162 | + (instancetype _Nullable)manager { | |
163 | static OTManager* manager = nil; | |
164 | ||
165 | if(!SecOTIsEnabled()) { | |
166 | secerror("octagon: Attempt to fetch a manager while Octagon is disabled"); | |
167 | return nil; | |
168 | } | |
169 | ||
170 | static dispatch_once_t onceToken; | |
171 | dispatch_once(&onceToken, ^{ | |
172 | manager = [[OTManager alloc]init]; | |
173 | }); | |
174 | ||
175 | return manager; | |
176 | } | |
177 | ||
178 | ||
179 | -(BOOL) initRamps | |
180 | { | |
181 | BOOL initResult = NO; | |
182 | ||
183 | CKContainer* container = [CKKSViewManager manager].container; | |
184 | CKDatabase* database = [container privateCloudDatabase]; | |
185 | CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName]; | |
186 | ||
187 | CKKSCKAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker; | |
188 | CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker; | |
189 | CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker; | |
190 | ||
191 | self.cfuRamp = [[OTRamp alloc]initWithRecordName:kOTRampForCFURecordName | |
192 | featureName:@"cfu" | |
193 | container:container | |
194 | database:database | |
195 | zoneID:zoneID | |
196 | accountTracker:accountTracker | |
197 | lockStateTracker:lockStateTracker | |
198 | reachabilityTracker:reachabilityTracker | |
199 | fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]]; | |
200 | ||
201 | self.enrollRamp = [[OTRamp alloc]initWithRecordName:kOTRampForEnrollmentRecordName | |
202 | featureName:@"enroll" | |
203 | container:container | |
204 | database:database | |
205 | zoneID:zoneID | |
206 | accountTracker:accountTracker | |
207 | lockStateTracker:lockStateTracker | |
208 | reachabilityTracker:reachabilityTracker | |
209 | fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]]; | |
210 | ||
211 | ||
212 | self.restoreRamp = [[OTRamp alloc]initWithRecordName:kOTRampForRestoreRecordName | |
213 | featureName:@"restore" | |
214 | container:container | |
215 | database:database | |
216 | zoneID:zoneID | |
217 | accountTracker:accountTracker | |
218 | lockStateTracker:lockStateTracker | |
219 | reachabilityTracker:reachabilityTracker | |
220 | fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]]; | |
221 | ||
222 | if(self.cfuRamp && self.enrollRamp && self.restoreRamp){ | |
223 | initResult = YES; | |
224 | } | |
225 | return initResult; | |
226 | } | |
227 | ||
228 | -(BOOL) initializeManagerPropertiesForContext:(NSString*)dsid error:(NSError**)error | |
229 | { | |
230 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
231 | NSError *localError = nil; | |
232 | BOOL initialized = YES; | |
233 | ||
234 | if(dsid == nil){ | |
235 | dsid = [self askAccountsForDSID]; | |
236 | } | |
237 | ||
238 | //create local store | |
239 | self.localStore = [[OTLocalStore alloc] initWithContextID:OTDefaultContext dsid:dsid path:nil error:&localError]; | |
240 | if(!self.localStore){ | |
241 | secerror("octagon: could not create localStore: %@", localError); | |
242 | [logger logUnrecoverableError:localError forEvent:OctagonEventSignIn withAttributes:@{ | |
243 | OctagonEventAttributeFailureReason : @"creating local store", | |
244 | }]; | |
245 | initialized = NO; | |
246 | } | |
247 | ||
248 | //create context | |
249 | self.context = [[OTContext alloc]initWithContextID:OTDefaultContext dsid:dsid localStore:self.localStore cloudStore:nil identityProvider:self error:&localError]; | |
250 | if(!self.context){ | |
251 | secerror("octagon: could not create context: %@", localError); | |
252 | [logger logUnrecoverableError:localError forEvent:OctagonEventSignIn withAttributes:@{ | |
253 | OctagonEventAttributeFailureReason : @"creating context", | |
254 | }]; | |
255 | self.localStore = nil; | |
256 | initialized = NO; | |
257 | } | |
258 | ||
259 | //just in case, init the ramp objects | |
260 | [self initRamps]; | |
261 | ||
262 | if(localError && error){ | |
263 | *error = localError; | |
264 | } | |
265 | return initialized; | |
266 | } | |
267 | ||
268 | /* | |
269 | * SPI routines | |
270 | */ | |
271 | ||
272 | - (void)signIn:(NSString*)dsid reply:(void (^)(BOOL result, NSError * _Nullable signedInError))reply | |
273 | { | |
274 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
275 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonSignIn withAction:nil]; | |
276 | [tracker start]; | |
277 | ||
278 | NSError *error = nil; | |
279 | if(![self initializeManagerPropertiesForContext:dsid error:&error]){ | |
280 | [tracker cancel]; | |
281 | reply(NO, error); | |
282 | return; | |
283 | } | |
284 | ||
285 | [tracker stop]; | |
286 | [logger logSuccessForEventNamed:OctagonEventSignIn]; | |
287 | ||
288 | secnotice("octagon","created context and local store on manager for:%@", dsid); | |
289 | ||
290 | reply(YES, error); | |
291 | } | |
292 | ||
293 | - (void)signOut:(void (^)(BOOL result, NSError * _Nullable signedOutError))reply | |
294 | { | |
295 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
296 | ||
297 | NSError* error = nil; | |
298 | NSError *bottledPeerError = nil; | |
299 | NSError *localContextError = nil; | |
300 | ||
301 | secnotice("octagon", "signing out of octagon trust: dsid: %@ contextID: %@", | |
302 | self.context.dsid, | |
303 | self.context.contextID); | |
304 | ||
305 | NSString* contextAndDSID = [NSString stringWithFormat:@"%@-%@", self.context.contextID, self.context.dsid]; | |
306 | ||
307 | //remove all locally stored context | |
308 | BOOL result1 = [self.localStore deleteLocalContext:contextAndDSID error:&localContextError]; | |
309 | if(!result1){ | |
310 | secerror("octagon: could not delete local context: %@: %@", self.context.contextID, localContextError); | |
311 | [logger logUnrecoverableError:localContextError forEvent:OctagonEventSignOut withAttributes:@{ | |
312 | OctagonEventAttributeFailureReason : @"deleting local context", | |
313 | }]; | |
314 | error = localContextError; | |
315 | } | |
316 | ||
317 | BOOL result2 = [self.localStore deleteBottledPeersForContextAndDSID:contextAndDSID error:&bottledPeerError]; | |
318 | if(!result2){ | |
319 | secerror("octagon: could not delete bottle peer records: %@: %@", self.context.contextID, bottledPeerError); | |
320 | [logger logUnrecoverableError:bottledPeerError forEvent:OctagonEventSignOut withAttributes:@{ | |
321 | OctagonEventAttributeFailureReason : @"deleting local bottled peers", | |
322 | }]; | |
323 | error = bottledPeerError; | |
324 | } | |
325 | ||
326 | //free context & local store | |
327 | self.context = nil; | |
328 | self.localStore = nil; | |
329 | ||
330 | BOOL result = (result1 && result2); | |
331 | if (result) { | |
332 | [logger logSuccessForEventNamed:OctagonEventSignOut]; | |
333 | } | |
334 | ||
335 | reply(result, error); | |
336 | } | |
337 | - (void)preflightBottledPeer:(NSString*)contextID | |
338 | dsid:(NSString*)dsid | |
339 | reply:(void (^)(NSData* _Nullable entropy, | |
340 | NSString* _Nullable bottleID, | |
341 | NSData* _Nullable signingPublicKey, | |
342 | NSError* _Nullable error))reply | |
343 | { | |
344 | secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid); | |
345 | NSError* error = nil; | |
346 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
347 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonPreflightBottle withAction:nil]; | |
348 | ||
349 | [tracker start]; | |
350 | ||
351 | if(!self.context || !self.localStore){ | |
352 | if(![self initializeManagerPropertiesForContext:dsid error:&error]){ | |
353 | secerror("octagon: could not init manager obejcts: %@", error); | |
354 | reply(nil,nil,nil,error); | |
355 | [tracker cancel]; | |
356 | return; | |
357 | } | |
358 | } | |
359 | ||
360 | NSInteger retryDelayInSeconds = 0; | |
361 | BOOL isFeatureOn = [self.enrollRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error]; | |
362 | ||
363 | //got an error from ramp check, we should log it | |
364 | if(error){ | |
365 | [logger logRecoverableError:error | |
366 | forEvent:OctagonEventRamp | |
367 | zoneName:kOTRampZoneName | |
368 | withAttributes:@{ | |
369 | OctagonEventAttributeFailureReason : @"ramp check for preflight bottle" | |
370 | }]; | |
371 | } | |
372 | ||
373 | if(!isFeatureOn){ //cloud kit has not asked us to come back and the feature is off for this device | |
374 | secnotice("octagon", "bottled peers is not on"); | |
375 | if(!error){ | |
376 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}]; | |
377 | } | |
378 | reply(nil, nil, nil, error); | |
379 | return; | |
380 | } | |
381 | ||
382 | NSData* entropy = [self.context makeMeSomeEntropy:OTMasterSecretLength]; | |
383 | if(!entropy){ | |
384 | secerror("octagon: entropy creation failed: %@", error); | |
385 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEntropyCreationFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to create entropy"}]; | |
386 | [logger logUnrecoverableError:error forEvent:OctagonEventPreflightBottle withAttributes:@{ | |
387 | OctagonEventAttributeFailureReason : @"preflight bottle, entropy failure"} | |
388 | ]; | |
389 | [tracker stop]; | |
390 | reply(nil, nil, nil, error); | |
391 | return; | |
392 | } | |
393 | ||
394 | OTPreflightInfo* result = [self.context preflightBottledPeer:contextID entropy:entropy error:&error]; | |
395 | if(!result || error){ | |
396 | secerror("octagon: preflight failed: %@", error); | |
397 | [logger logUnrecoverableError:error forEvent:OctagonEventPreflightBottle withAttributes:@{ OctagonEventAttributeFailureReason : @"preflight bottle"}]; | |
398 | reply(nil, nil, nil, error); | |
399 | [tracker stop]; | |
400 | return; | |
401 | } | |
402 | ||
403 | [tracker stop]; | |
404 | [logger logSuccessForEventNamed:OctagonEventPreflightBottle]; | |
405 | ||
406 | secnotice("octagon", "preflightBottledPeer completed, created: %@", result.bottleID); | |
407 | ||
408 | reply(entropy, result.bottleID, result.escrowedSigningSPKI, error); | |
409 | } | |
410 | ||
411 | - (void)launchBottledPeer:(NSString*)contextID | |
412 | bottleID:(NSString*)bottleID | |
413 | reply:(void (^ _Nullable)(NSError* _Nullable error))reply | |
414 | { | |
415 | secnotice("octagon", "launchBottledPeer"); | |
416 | NSError* error = nil; | |
417 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
418 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonLaunchBottle withAction:nil]; | |
419 | ||
420 | [tracker start]; | |
421 | ||
422 | if(!self.context || !self.localStore){ | |
423 | if(![self initializeManagerPropertiesForContext:nil error:&error]){ | |
424 | [tracker cancel]; | |
425 | reply(error); | |
426 | return; | |
427 | } | |
428 | } | |
429 | ||
430 | NSInteger retryDelayInSeconds = 0; | |
431 | BOOL isFeatureOn = [self.enrollRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error]; | |
432 | ||
433 | //got an error from ramp check, we should log it | |
434 | if(error){ | |
435 | [logger logRecoverableError:error | |
436 | forEvent:OctagonEventRamp | |
437 | zoneName:kOTRampZoneName | |
438 | withAttributes:@{ | |
439 | OctagonEventAttributeFailureReason : @"ramp state check for launch bottle" | |
440 | }]; | |
441 | } | |
442 | ||
443 | if(!isFeatureOn){ | |
444 | secnotice("octagon", "bottled peers is not on"); | |
445 | if(!error){ | |
446 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}]; | |
447 | } | |
448 | reply(error); | |
449 | return; | |
450 | } | |
451 | ||
452 | OTBottledPeerRecord* bprecord = [self.localStore readLocalBottledPeerRecordWithRecordID:bottleID error:&error]; | |
453 | if(!bprecord || error){ | |
454 | secerror("octagon: could not retrieve record for: %@, error: %@", bottleID, error); | |
455 | [logger logUnrecoverableError:error forEvent:OctagonEventLaunchBottle withAttributes:@{ | |
456 | OctagonEventAttributeFailureReason : @"reading bottle from local store" | |
457 | }]; | |
458 | [tracker stop]; | |
459 | reply(error); | |
460 | return; | |
461 | } | |
462 | BOOL result = [self.context.cloudStore uploadBottledPeerRecord:bprecord escrowRecordID:bprecord.escrowRecordID error:&error]; | |
463 | if(!result || error){ | |
464 | secerror("octagon: could not upload record for bottleID %@, error: %@", bottleID, error); | |
465 | [logger logUnrecoverableError:error forEvent:OctagonEventLaunchBottle withAttributes:@{ | |
466 | OctagonEventAttributeFailureReason : @"upload bottle to cloud kit" | |
467 | }]; | |
468 | [tracker stop]; | |
469 | reply(error); | |
470 | return; | |
471 | } | |
472 | ||
473 | [tracker stop]; | |
474 | [logger logSuccessForEventNamed:OctagonEventLaunchBottle]; | |
475 | ||
476 | secnotice("octagon", "successfully launched: %@", bprecord.recordName); | |
477 | ||
478 | reply(error); | |
479 | } | |
480 | ||
481 | - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply | |
482 | { | |
483 | //check if configuration zone allows restore | |
484 | NSError* error = nil; | |
485 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
486 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonRestore withAction:nil]; | |
487 | ||
488 | [tracker start]; | |
489 | ||
490 | if(!self.context || !self.localStore){ | |
491 | if(![self initializeManagerPropertiesForContext:dsid error:&error]){ | |
492 | secerror("octagon: could not init manager obejcts: %@", error); | |
493 | reply(nil,nil,error); | |
494 | [tracker cancel]; | |
495 | return; | |
496 | } | |
497 | } | |
498 | ||
499 | NSInteger retryDelayInSeconds = 0; | |
500 | BOOL isFeatureOn = [self.restoreRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error]; | |
501 | ||
502 | //got an error from ramp check, we should log it | |
503 | if(error){ | |
504 | [logger logRecoverableError:error | |
505 | forEvent:OctagonEventRamp | |
506 | zoneName:kOTRampZoneName | |
507 | withAttributes:@{ | |
508 | OctagonEventAttributeFailureReason : @"checking ramp state for restore" | |
509 | }]; | |
510 | } | |
511 | ||
512 | if(!isFeatureOn){ | |
513 | secnotice("octagon", "bottled peers is not on"); | |
514 | if(!error){ | |
515 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}]; | |
516 | } | |
517 | [tracker stop]; | |
518 | reply(nil, nil, error); | |
519 | return; | |
520 | } | |
521 | ||
522 | if(!escrowRecordID || [escrowRecordID length] == 0){ | |
523 | secerror("octagon: missing escrowRecordID"); | |
524 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEmptyEscrowRecordID userInfo:@{NSLocalizedDescriptionKey: @"Escrow Record ID is empty or missing"}]; | |
525 | ||
526 | [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{ | |
527 | OctagonEventAttributeFailureReason : @"escrow record id missing", | |
528 | }]; | |
529 | ||
530 | [tracker stop]; | |
531 | reply(nil, nil, error); | |
532 | return; | |
533 | } | |
534 | if(!dsid || [dsid length] == 0){ | |
535 | secerror("octagon: missing dsid"); | |
536 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEmptyDSID userInfo:@{NSLocalizedDescriptionKey: @"DSID is empty or missing"}]; | |
537 | ||
538 | [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{ | |
539 | OctagonEventAttributeFailureReason : @"dsid missing", | |
540 | }]; | |
541 | [tracker stop]; | |
542 | reply(nil, nil, error); | |
543 | return; | |
544 | } | |
545 | if(!secret || [secret length] == 0){ | |
546 | secerror("octagon: missing secret"); | |
547 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEmptySecret userInfo:@{NSLocalizedDescriptionKey: @"Secret is empty or missing"}]; | |
548 | ||
549 | ||
550 | [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{ | |
551 | OctagonEventAttributeFailureReason : @"secret missing", | |
552 | }]; | |
553 | ||
554 | [tracker stop]; | |
555 | reply(nil, nil, error); | |
556 | return; | |
557 | } | |
558 | ||
559 | OTBottledPeerSigned *bps = [_context restoreFromEscrowRecordID:escrowRecordID secret:secret error:&error]; | |
560 | if(!bps || error != nil){ | |
561 | secerror("octagon: failed to restore bottled peer: %@", error); | |
562 | ||
563 | [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{ | |
564 | OctagonEventAttributeFailureReason : @"restore failed", | |
565 | }]; | |
566 | [tracker stop]; | |
567 | reply(nil, nil, error); | |
568 | return; | |
569 | } | |
570 | ||
571 | NSData *encryptionKeyData = bps.bp.peerEncryptionKey.publicKey.keyData; // FIXME | |
572 | if(!encryptionKeyData){ | |
573 | secerror("octagon: restored octagon encryption key is nil: %@", error); | |
574 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorRestoredPeerEncryptionKeyFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve restored Octagon Peer Encryption Key"}]; | |
575 | ||
576 | [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{ | |
577 | OctagonEventAttributeFailureReason : @"restored octagon encryption key" | |
578 | }]; | |
579 | [tracker stop]; | |
580 | reply(nil,nil,error); | |
581 | return; | |
582 | } | |
583 | ||
584 | NSData *signingKeyData = bps.bp.peerSigningKey.publicKey.keyData; // FIXME | |
585 | if(!signingKeyData){ | |
586 | secerror("octagon: restored octagon signing key is nil: %@", error); | |
587 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorRestoredPeerSigningKeyFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve restored Octagon Peer Signing Key"}]; | |
588 | ||
589 | [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{ | |
590 | OctagonEventAttributeFailureReason : @"restored octagon signing key" | |
591 | }]; | |
592 | [tracker stop]; | |
593 | reply(nil,nil,error); | |
594 | return; | |
595 | } | |
596 | [tracker stop]; | |
597 | ||
598 | [logger logSuccessForEventNamed:OctagonEventRestoreBottle]; | |
599 | ||
600 | secnotice("octagon", "restored bottled peer: %@", escrowRecordID); | |
601 | ||
602 | reply(signingKeyData, encryptionKeyData, error); | |
603 | } | |
604 | ||
605 | - (void)scrubBottledPeer:(NSString*)contextID | |
606 | bottleID:(NSString*)bottleID | |
607 | reply:(void (^ _Nullable)(NSError* _Nullable error))reply | |
608 | { | |
609 | NSError* error = nil; | |
610 | ||
611 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
612 | SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityScrubBottle withAction:nil]; | |
613 | ||
614 | if(!self.context || !self.localStore){ | |
615 | if(![self initializeManagerPropertiesForContext:nil error:&error]){ | |
616 | [tracker cancel]; | |
617 | reply(error); | |
618 | return; | |
619 | } | |
620 | } | |
621 | [tracker start]; | |
622 | ||
623 | NSInteger retryDelayInSeconds = 0; | |
624 | BOOL isFeatureOn = [self.enrollRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error]; | |
625 | ||
626 | //got an error from ramp check, we should log it | |
627 | if(error){ | |
628 | [logger logRecoverableError:error | |
629 | forEvent:OctagonEventRamp | |
630 | zoneName:kOTRampZoneName | |
631 | withAttributes:@{ | |
632 | OctagonEventAttributeFailureReason : @"ramp check for scrubbing bottled peer" | |
633 | }]; | |
634 | } | |
635 | ||
636 | if(!isFeatureOn){ | |
637 | secnotice("octagon", "bottled peers is not on"); | |
638 | if(!error){ | |
639 | error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}]; | |
640 | } | |
641 | [tracker stop]; | |
642 | reply(error); | |
643 | return; | |
644 | } | |
645 | ||
646 | BOOL result = [self.context scrubBottledPeer:contextID bottleID:bottleID error:&error]; | |
647 | if(!result || error){ | |
648 | secerror("octagon: could not scrub record for bottleID %@, error: %@", bottleID, error); | |
649 | [logger logUnrecoverableError:error forEvent:OctagonEventScrubBottle withAttributes:@{ | |
650 | OctagonEventAttributeFailureReason : @"could not scrub bottle", | |
651 | }]; | |
652 | [tracker stop]; | |
653 | reply(error); | |
654 | return; | |
655 | } | |
656 | [logger logSuccessForEventNamed:OctagonEventScrubBottle]; | |
657 | ||
658 | secnotice("octagon", "scrubbed bottled peer: %@", bottleID); | |
659 | ||
660 | reply(error); | |
661 | } | |
662 | ||
663 | /* | |
664 | * OTCTL tool routines | |
665 | */ | |
666 | ||
667 | -(void) reset:(void (^)(BOOL result, NSError *))reply | |
668 | { | |
669 | NSError* error = nil; | |
670 | ||
671 | if(self.context.lockStateTracker.isLocked){ | |
672 | secnotice("octagon","device is locked! can't check ramp state"); | |
673 | error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain | |
674 | code:errSecInteractionNotAllowed | |
675 | userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}]; | |
676 | ||
677 | reply(NO,error); | |
678 | return; | |
679 | } | |
680 | if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){ | |
681 | secnotice("octagon","not signed in! can't check ramp state"); | |
682 | error = [NSError errorWithDomain:octagonErrorDomain | |
683 | code:OTErrorNotSignedIn | |
684 | userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}]; | |
685 | reply(NO,error); | |
686 | return; | |
687 | ||
688 | } | |
689 | if(!self.context.reachabilityTracker.currentReachability){ | |
690 | secnotice("octagon","no network! can't check ramp state"); | |
691 | error = [NSError errorWithDomain:octagonErrorDomain | |
692 | code:OTErrorNoNetwork | |
693 | userInfo:@{NSLocalizedDescriptionKey: @"no network"}]; | |
694 | reply(NO,error); | |
695 | return; | |
696 | } | |
697 | ||
698 | NSError* bottledPeerError = nil; | |
699 | ||
700 | BOOL result = [_context.cloudStore performReset:&bottledPeerError]; | |
701 | if(!result || bottledPeerError != nil){ | |
702 | secerror("octagon: resetting octagon trust zone failed: %@", bottledPeerError); | |
703 | } | |
704 | ||
705 | NSString* contextAndDSID = [NSString stringWithFormat:@"%@-%@", self.context.contextID, self.context.dsid]; | |
706 | ||
707 | result = [self.localStore deleteBottledPeersForContextAndDSID:contextAndDSID error:&bottledPeerError]; | |
708 | if(!result){ | |
709 | secerror("octagon: could not delete bottle peer records: %@: %@", self.context.contextID, bottledPeerError); | |
710 | } | |
711 | ||
712 | reply(result, bottledPeerError); | |
713 | } | |
714 | ||
715 | - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError *))reply | |
716 | { | |
717 | NSError* error = nil; | |
718 | ||
719 | if(self.context.lockStateTracker.isLocked){ | |
720 | secnotice("octagon","device is locked! can't check ramp state"); | |
721 | error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain | |
722 | code:errSecInteractionNotAllowed | |
723 | userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}]; | |
724 | ||
725 | reply(nil,error); | |
726 | return; | |
727 | } | |
728 | if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){ | |
729 | secnotice("octagon","not signed in! can't check ramp state"); | |
730 | error = [NSError errorWithDomain:octagonErrorDomain | |
731 | code:OTErrorNotSignedIn | |
732 | userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}]; | |
733 | reply(nil,error); | |
734 | return; | |
735 | } | |
736 | if(!self.context.reachabilityTracker.currentReachability){ | |
737 | secnotice("octagon","no network! can't check ramp state"); | |
738 | error = [NSError errorWithDomain:octagonErrorDomain | |
739 | code:OTErrorNoNetwork | |
740 | userInfo:@{NSLocalizedDescriptionKey: @"no network"}]; | |
741 | reply(nil,error); | |
742 | return; | |
743 | } | |
744 | ||
745 | NSArray* list = [_context.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error]; | |
746 | if(!list || error !=nil){ | |
747 | secerror("octagon: there are not eligible bottle peer records: %@", error); | |
748 | reply(nil,error); | |
749 | return; | |
750 | } | |
751 | reply(list, error); | |
752 | } | |
753 | ||
754 | - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply | |
755 | { | |
756 | __block NSData *encryptionKey = NULL; | |
757 | __block NSError* localError = nil; | |
758 | ||
759 | SOSCCPerformWithOctagonEncryptionPublicKey(^(SecKeyRef octagonPrivKey, CFErrorRef error) { | |
760 | CFDataRef key; | |
761 | SecKeyCopyPublicBytes(octagonPrivKey, &key); | |
762 | encryptionKey = CFBridgingRelease(key); | |
763 | if(error){ | |
764 | localError = (__bridge NSError*)error; | |
765 | } | |
766 | }); | |
767 | if(!encryptionKey || localError != nil){ | |
768 | reply(nil, localError); | |
769 | secerror("octagon: retrieving the octagon encryption public key failed: %@", localError); | |
770 | return; | |
771 | } | |
772 | reply(encryptionKey, localError); | |
773 | } | |
774 | ||
775 | -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply | |
776 | { | |
777 | __block NSData *signingKey = NULL; | |
778 | __block NSError* localError = nil; | |
779 | ||
780 | SOSCCPerformWithOctagonSigningPublicKey(^(SecKeyRef octagonPrivKey, CFErrorRef error) { | |
781 | CFDataRef key; | |
782 | SecKeyCopyPublicBytes(octagonPrivKey, &key); | |
783 | signingKey = CFBridgingRelease(key); | |
784 | if(error){ | |
785 | localError = (__bridge NSError*)error; | |
786 | } | |
787 | }); | |
788 | if(!signingKey || localError != nil){ | |
789 | reply(nil, localError); | |
790 | secerror("octagon: retrieving the octagon signing public key failed: %@", localError); | |
791 | return; | |
792 | } | |
793 | reply(signingKey, localError); | |
794 | } | |
795 | ||
796 | /* | |
797 | * OT Helpers | |
798 | */ | |
799 | ||
800 | -(BOOL)scheduledCloudKitRampCheck:(NSError**)error | |
801 | { | |
802 | secnotice("octagon", "scheduling a CloudKit ramping check"); | |
803 | NSInteger retryAfterInSeconds = 0; | |
804 | NSError* localError = nil; | |
805 | BOOL cancelScheduler = YES; | |
806 | ||
807 | CKKSAnalytics* logger = [CKKSAnalytics logger]; | |
808 | ||
809 | if(self.cfuRamp){ | |
810 | BOOL canCFU = [self.cfuRamp checkRampState:&retryAfterInSeconds qos:NSQualityOfServiceUserInitiated error:&localError]; | |
811 | ||
812 | if(localError){ | |
813 | secerror("octagon: checking ramp state for CFU error'd: %@", localError); | |
814 | [logger logUnrecoverableError:localError forEvent:OctagonEventRamp withAttributes:@{ | |
815 | OctagonEventAttributeFailureReason : @"ramp check failed", | |
816 | }]; | |
817 | } | |
818 | ||
819 | if(canCFU){ | |
820 | secnotice("octagon", "CFU is enabled, checking if this device has a bottle"); | |
821 | OctagonBottleCheckState bottleStatus = [self.context doesThisDeviceHaveABottle:&localError]; | |
822 | ||
823 | if(bottleStatus == NOBOTTLE){ | |
824 | //time to post a follow up! | |
825 | secnotice("octagon", "device does not have a bottle, posting a follow up"); | |
826 | if(!SecCKKSTestsEnabled()){ | |
827 | [self.context postFollowUp]; | |
828 | } | |
829 | NSInteger timeDiff = -1; | |
830 | ||
831 | NSDate *currentDate = [NSDate date]; | |
832 | if(self.lastPostedCoreFollowUp){ | |
833 | timeDiff = [currentDate timeIntervalSinceDate:self.lastPostedCoreFollowUp]; | |
834 | } | |
835 | ||
836 | //log how long we last posted a followup, if any | |
837 | [logger logRecoverableError:localError | |
838 | forEvent:OctagonEventCoreFollowUp | |
839 | zoneName:kOTRampZoneName | |
840 | withAttributes:@{ | |
841 | OctagonEventAttributeFailureReason : @"No bottle for peer", | |
842 | OctagonEventAttributeTimeSinceLastPostedFollowUp: [NSNumber numberWithInteger:timeDiff], | |
843 | }]; | |
844 | ||
845 | self.lastPostedCoreFollowUp = currentDate; | |
846 | //if the followup failed or succeeded, we should continue the scheduler until we have a bottle. | |
847 | cancelScheduler = NO; | |
848 | }else if(bottleStatus == BOTTLE){ | |
849 | secnotice("octagon", "device has a bottle"); | |
850 | [logger logSuccessForEventNamed:OctagonEventBottleCheck]; | |
851 | } | |
852 | ||
853 | if(localError){ | |
854 | [logger logRecoverableError:localError | |
855 | forEvent:OctagonEventBottleCheck | |
856 | zoneName:kOTRampZoneName | |
857 | withAttributes:@{ | |
858 | OctagonEventAttributeFailureReason : @"bottle check", | |
859 | }]; | |
860 | } | |
861 | } | |
862 | } | |
863 | if(cancelScheduler == NO){ | |
864 | secnotice("octagon", "requesting bottle check again"); | |
865 | [self.cfuScheduler trigger]; | |
866 | } | |
867 | ||
868 | if(error && localError){ | |
869 | *error = localError; | |
870 | } | |
871 | return cancelScheduler; | |
872 | } | |
873 | ||
874 | -(void)scheduleCFUForFuture | |
875 | { | |
876 | secnotice("octagon", "scheduling a query to cloudkit to see if this device can post a core follow up"); | |
877 | ||
878 | [self.cfuScheduler trigger]; | |
879 | } | |
880 | ||
881 | - (nullable OTIdentity *) currentIdentity:(NSError**)error | |
882 | { | |
883 | return [OTIdentity currentIdentityFromSOS:error]; | |
884 | } | |
885 | ||
886 | @end | |
887 | ||
888 | #endif |