/ keychain / ckks / CKKSControl.m
CKKSControl.m
  1  /*
  2   * Copyright (c) 2017 Apple Inc. All Rights Reserved.
  3   *
  4   * @APPLE_LICENSE_HEADER_START@
  5   *
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   *
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   *
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  #if __OBJC2__
 25  
 26  #import <Foundation/NSXPCConnection_Private.h>
 27  #import <xpc/xpc.h>
 28  
 29  #import <Security/SecItemPriv.h>
 30  
 31  #import "keychain/ckks/CKKSControl.h"
 32  #import "keychain/ckks/CKKSControlProtocol.h"
 33  #import "keychain/ckks/CKKSControlServer.h"
 34  #import "utilities/debugging.h"
 35  
 36  @interface CKKSControl ()
 37  @property (readwrite,assign) BOOL synchronous;
 38  @property xpc_endpoint_t endpoint;
 39  @property NSXPCConnection *connection;
 40  @end
 41  
 42  @implementation CKKSControl
 43  
 44  - (instancetype)initWithConnection:(NSXPCConnection*)connection {
 45      if(self = [super init]) {
 46          _connection = connection;
 47      }
 48      return self;
 49  }
 50  
 51  - (void)dealloc {
 52      [self.connection invalidate];
 53  }
 54  
 55  - (id<CKKSControlProtocol>)objectProxyWithErrorHandler:(void(^)(NSError * _Nonnull error))failureHandler
 56  {
 57      if (self.synchronous) {
 58          return [self.connection synchronousRemoteObjectProxyWithErrorHandler:failureHandler];
 59      } else {
 60          return [self.connection remoteObjectProxyWithErrorHandler:failureHandler];
 61      }
 62  }
 63  
 64  - (void)rpcStatus:(NSString*)viewName reply:(void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply {
 65      [[self objectProxyWithErrorHandler: ^(NSError* error) {
 66          reply(nil, error);
 67  
 68      }] rpcStatus:viewName reply:^(NSArray<NSDictionary*>* result, NSError* error){
 69          reply(result, error);
 70      }];
 71  }
 72  
 73  - (void)rpcFastStatus:(NSString*)viewName reply:(void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply {
 74      [[self objectProxyWithErrorHandler: ^(NSError* error) {
 75          reply(nil, error);
 76  
 77      }] rpcFastStatus:viewName reply:^(NSArray<NSDictionary*>* result, NSError* error){
 78          reply(result, error);
 79      }];
 80  }
 81  
 82  
 83  - (void)rpcResetLocal:(NSString*)viewName reply:(void(^)(NSError* error))reply {
 84      secnotice("ckkscontrol", "Requesting a local reset for view %@", viewName);
 85      [[self objectProxyWithErrorHandler:^(NSError* error) {
 86          reply(error);
 87      }] rpcResetLocal:viewName reply:^(NSError* error){
 88          if(error) {
 89              secnotice("ckkscontrol", "Local reset finished with error: %@", error);
 90          } else {
 91              secnotice("ckkscontrol", "Local reset finished successfully");
 92          }
 93          reply(error);
 94      }];
 95  }
 96  
 97  - (void)rpcResetCloudKit:(NSString*)viewName reason:(NSString *)reason reply:(void(^)(NSError* error))reply {
 98      secnotice("ckkscontrol", "Requesting a CloudKit reset for view %@", viewName);
 99      [[self objectProxyWithErrorHandler:^(NSError* error) {
100          reply(error);
101      }] rpcResetCloudKit:viewName reason:reason reply:^(NSError* error){
102          if(error) {
103              secnotice("ckkscontrol", "CloudKit reset finished with error: %@", error);
104          } else {
105              secnotice("ckkscontrol", "CloudKit reset finished successfully");
106          }
107          reply(error);
108      }];
109  }
110  
111  - (void)rpcResyncLocal:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply
112  {
113      secnotice("ckkscontrol", "Requesting a local resync for view %@", viewName);
114      [[self objectProxyWithErrorHandler:^(NSError* error) {
115          reply(error);
116      }] rpcResyncLocal:viewName reply:^(NSError* error){
117          if(error) {
118              secnotice("ckkscontrol", "Local resync finished with error: %@", error);
119          } else {
120              secnotice("ckkscontrol", "Local resync finished successfully");
121          }
122          reply(error);
123      }];
124  }
125  - (void)rpcResync:(NSString*)viewName reply:(void(^)(NSError* error))reply {
126      secnotice("ckkscontrol", "Requesting a resync for view %@", viewName);
127      [[self objectProxyWithErrorHandler:^(NSError* error) {
128          reply(error);
129      }] rpcResync:viewName reply:^(NSError* error){
130          if(error) {
131              secnotice("ckkscontrol", "Resync finished with error: %@", error);
132          } else {
133              secnotice("ckkscontrol", "Resync finished successfully");
134          }
135          reply(error);
136      }];
137  }
138  - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply:(void(^)(NSError* error))reply {
139      secnotice("ckkscontrol", "Requesting a fetch for view %@", viewName);
140      [[self objectProxyWithErrorHandler:^(NSError* error) {
141          reply(error);
142      }] rpcFetchAndProcessChanges:viewName reply:^(NSError* error){
143          if(error) {
144              secnotice("ckkscontrol", "Fetch(classA) finished with error: %@", error);
145          } else {
146              secnotice("ckkscontrol", "Fetch(classA) finished successfully");
147          }
148          reply(error);
149      }];
150  }
151  - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply:(void(^)(NSError* error))reply {
152      secnotice("ckkscontrol", "Requesting a fetch(classA) for view %@", viewName);
153      [[self objectProxyWithErrorHandler:^(NSError* error) {
154          reply(error);
155      }] rpcFetchAndProcessClassAChanges:viewName reply:^(NSError* error){
156          if(error) {
157              secnotice("ckkscontrol", "Fetch finished with error: %@", error);
158          } else {
159              secnotice("ckkscontrol", "Fetch finished successfully");
160          }
161          reply(error);
162      }];
163  }
164  - (void)rpcPushOutgoingChanges:(NSString*)viewName reply:(void(^)(NSError* error))reply {
165      secnotice("ckkscontrol", "Requesting a push for view %@", viewName);
166      [[self objectProxyWithErrorHandler:^(NSError* error) {
167          reply(error);
168      }] rpcPushOutgoingChanges:viewName reply:^(NSError* error){
169          if(error) {
170              secnotice("ckkscontrol", "Push finished with error: %@", error);
171          } else {
172              secnotice("ckkscontrol", "Push finished successfully");
173          }
174          reply(error);
175      }];
176  }
177  
178  - (void)rpcCKMetric:(NSString *)eventName attributes:(NSDictionary *)attributes reply:(void(^)(NSError* error))reply {
179      [[self objectProxyWithErrorHandler:^(NSError* error) {
180          reply(error);
181      }] rpcCKMetric:eventName attributes:attributes reply:^(NSError* error){
182          reply(error);
183      }];
184  }
185  
186  - (void)rpcPerformanceCounters:(void(^)(NSDictionary <NSString *,NSNumber *> *,NSError*))reply {
187      [[self objectProxyWithErrorHandler: ^(NSError* error) {
188          reply(nil, error);
189      }] performanceCounters:^(NSDictionary <NSString *, NSNumber *> *counters){
190          reply(counters, nil);
191      }];
192  }
193  
194  - (void)rpcGetCKDeviceIDWithReply:(void (^)(NSString *))reply {
195      [[self objectProxyWithErrorHandler:^(NSError * _Nonnull error) {
196          reply(nil);
197      }] rpcGetCKDeviceIDWithReply:^(NSString *ckdeviceID) {
198          reply(ckdeviceID);
199      }];
200  }
201  
202  - (void)rpcTLKMissing:(NSString*)viewName reply:(void(^)(bool missing))reply {
203      [self rpcFastStatus:viewName reply:^(NSArray<NSDictionary*>* results, NSError* blockError) {
204          bool missing = false;
205  
206          for(NSDictionary* result in results) {
207              NSString* name = result[@"view"];
208              NSString* keystate = result[@"keystate"];
209  
210              if([name isEqualToString:@"global"]) {
211                  // this is global status; no view implicated
212                  continue;
213              }
214  
215              if ([keystate isEqualToString:@"waitfortlk"] || [keystate isEqualToString:@"error"]) {
216                  missing = true;
217              }
218          }
219  
220          reply(missing);
221      }];
222  }
223  
224  - (void)rpcKnownBadState:(NSString* _Nullable)viewName reply:(void (^)(CKKSKnownBadState))reply {
225      [self rpcFastStatus:viewName reply:^(NSArray<NSDictionary*>* results, NSError* blockError) {
226          bool tlkMissing = false;
227          bool waitForUnlock = false;
228          bool waitForOctagon = false;
229          bool noAccount = false;
230  
231          CKKSKnownBadState response = CKKSKnownStatePossiblyGood;
232  
233          for(NSDictionary* result in results) {
234              NSString* name = result[@"view"];
235              NSString* keystate = result[@"keystate"];
236  
237              if([name isEqualToString:@"global"]) {
238                  // this is global status; no view implicated
239                  continue;
240              }
241  
242              if ([keystate isEqualToString:@"waitfortlk"] || [keystate isEqualToString:@"error"]) {
243                  tlkMissing = true;
244              }
245              if ([keystate isEqualToString:@"waitforunlock"]) {
246                  waitForUnlock = true;
247              }
248  
249              if([keystate isEqualToString:@"waitfortlkcreation"] ||
250                 [keystate isEqualToString:@"waitfortlkupload"] ||
251                 [keystate isEqualToString:@"waitfortrust"]) {
252                  waitForOctagon = true;
253              }
254  
255              if([keystate isEqualToString:@"loggedout"]) {
256                  noAccount = true;
257              }
258          }
259  
260          response = (noAccount ? CKKSKnownStateNoCloudKitAccount :
261                      (tlkMissing ? CKKSKnownStateTLKsMissing :
262                       (waitForUnlock ? CKKSKnownStateWaitForUnlock :
263                        (waitForOctagon ? CKKSKnownStateWaitForOctagon :
264                         CKKSKnownStatePossiblyGood))));
265  
266          reply(response);
267      }];
268  }
269  
270  + (CKKSControl*)controlObject:(NSError* __autoreleasing *)error {
271      return [CKKSControl CKKSControlObject:NO error:error];
272  }
273  
274  + (CKKSControl*)CKKSControlObject:(BOOL)synchronous error:(NSError* __autoreleasing *)error {
275  
276      NSXPCConnection* connection = [[NSXPCConnection alloc] initWithMachServiceName:@(kSecuritydCKKSServiceName) options:0];
277  
278      if (connection == nil) {
279          if(error) {
280              *error =  [NSError errorWithDomain:@"securityd" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Couldn't create connection (no reason given)"}];
281          }
282          return nil;
283      }
284  
285      NSXPCInterface *interface = CKKSSetupControlProtocol([NSXPCInterface interfaceWithProtocol:@protocol(CKKSControlProtocol)]);
286      connection.remoteObjectInterface = interface;
287      [connection resume];
288  
289      CKKSControl* c = [[CKKSControl alloc] initWithConnection:connection];
290      c.synchronous = synchronous;
291      return c;
292  }
293  
294  @end
295  
296  #endif // __OBJC2__