]> git.saurik.com Git - apple/security.git/blame - keychain/ot/OTManager.m
Security-58286.60.28.tar.gz
[apple/security.git] / keychain / ot / OTManager.m
CommitLineData
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
76static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
77static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
78static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
79static 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