/ OSX / sec / ipc / server_xpc.m
server_xpc.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  #import <Foundation/Foundation.h>
 25  
 26  #include <ipc/securityd_client.h>
 27  #include <ipc/server_security_helpers.h>
 28  #include <ipc/server_endpoint.h>
 29  #include <os/transaction_private.h>
 30  
 31  #if defined(TARGET_DARWINOS) && TARGET_DARWINOS
 32  #undef OCTAGON
 33  #undef SECUREOBJECTSYNC
 34  #undef SHAREDWEBCREDENTIALS
 35  #endif
 36  
 37  #if OCTAGON
 38  #import "keychain/categories/NSError+UsefulConstructors.h"
 39  #include <CloudKit/CloudKit_Private.h>
 40  // If your callbacks might pass back a CK error, you should use the XPCSanitizeError() spi on all branches at this layer.
 41  // Otherwise, XPC might crash on the other side if they haven't linked CloudKit.framework.
 42  #define XPCSanitizeError CKXPCSuitableError
 43  #else
 44  // This is a no-op: XPCSanitizeError(error) turns into (error)
 45  #define XPCSanitizeError
 46  #endif // OCTAGON
 47  
 48  #include <Security/SecEntitlements.h>
 49  #include <Security/SecItemPriv.h>
 50  #include "keychain/securityd/SecItemServer.h"
 51  #include "keychain/securityd/SecItemSchema.h"
 52  #include "keychain/securityd/SecItemDb.h"
 53  
 54  #include "keychain/ckks/CKKSViewManager.h"
 55  
 56  #import "keychain/securityd/SecDbBackupManager.h"
 57  
 58  @interface SecOSTransactionHolder : NSObject
 59  @property os_transaction_t transaction;
 60  - (instancetype)init:(os_transaction_t)transaction;
 61  @end
 62  
 63  @implementation SecOSTransactionHolder
 64  - (instancetype)init:(os_transaction_t)transaction {
 65      if((self = [super init])) {
 66          _transaction = transaction;
 67      }
 68      return self;
 69  }
 70  @end
 71  
 72  @implementation SecuritydXPCServer (SecuritydXPCProtocol)
 73  
 74  - (void) SecItemAddAndNotifyOnSync:(NSDictionary*) attributes
 75                        syncCallback:(id<SecuritydXPCCallbackProtocol>) callback
 76                            complete:(void (^) (NSDictionary* opDictResult, NSArray* opArrayResult, NSError* operror))xpcComplete
 77  {
 78      // The calling client might not handle CK types well. Sanitize!
 79      void (^complete)(NSDictionary*, NSArray*, NSError*) = ^(NSDictionary* opDictResult, NSArray* opArrayResult, NSError* operror){
 80          xpcComplete(opDictResult, opArrayResult, XPCSanitizeError(operror));
 81      };
 82  
 83      CFErrorRef cferror = NULL;
 84      if([self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementKeychainDeny]) {
 85          SecError(errSecNotAvailable, &cferror, CFSTR("SecItemAddAndNotifyOnSync: %@ has entitlement %@"), _client.task, kSecEntitlementKeychainDeny);
 86          //TODO: ensure cferror can transit xpc
 87          complete(NULL, NULL, (__bridge NSError*) cferror);
 88          CFReleaseNull(cferror);
 89          return;
 90      }
 91  
 92  #if OCTAGON
 93      // Wait a bit for CKKS initialization in case of daemon start, but don't bail if it isn't up
 94      [[CKKSViewManager manager].completedSecCKKSInitialize wait:10];
 95  #endif
 96  
 97      if(attributes[(id)kSecAttrDeriveSyncIDFromItemAttributes] ||
 98         attributes[(id)kSecAttrPCSPlaintextServiceIdentifier] ||
 99         attributes[(id)kSecAttrPCSPlaintextPublicKey] ||
100         attributes[(id)kSecAttrPCSPlaintextPublicIdentity]) {
101  
102          if(![self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementPrivateCKKSPlaintextFields]) {
103              SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemAddAndNotifyOnSync: %@ does not have entitlement %@, but is using SPI anyway"), _client.task, kSecEntitlementPrivateCKKSPlaintextFields);
104              complete(NULL, NULL, (__bridge NSError*) cferror);
105              CFReleaseNull(cferror);
106              return;
107          }
108      }
109  
110      if(attributes[(id)kSecDataInetExtraNotes] ||
111         attributes[(id)kSecDataInetExtraHistory] ||
112         attributes[(id)kSecDataInetExtraClientDefined0] ||
113         attributes[(id)kSecDataInetExtraClientDefined1] ||
114         attributes[(id)kSecDataInetExtraClientDefined2] ||
115         attributes[(id)kSecDataInetExtraClientDefined3]) {
116          if(![self clientHasBooleanEntitlement:(__bridge NSString*)kSecEntitlementPrivateInetExpansionFields]) {
117                SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemAddAndNotifyOnSync: %@ does not have entitlement %@"), _client.task, kSecEntitlementPrivateInetExpansionFields);
118                complete(NULL, NULL, (__bridge NSError*) cferror);
119                CFReleaseNull(cferror);
120                return;
121            }
122      }
123  
124      CFTypeRef cfresult = NULL;
125  
126      NSMutableDictionary* callbackQuery = [attributes mutableCopy];
127  
128      // We probably need to figure out how to call os_transaction_needs_more_time on this transaction, but as this callback passes through C code, it's quite difficult
129      SecOSTransactionHolder* callbackTransaction = [[SecOSTransactionHolder alloc] init:os_transaction_create("com.apple.securityd.SecItemAddAndNotifyOnSync-callback")];
130      callbackQuery[@"f_ckkscallback"] = ^void (bool didSync, CFErrorRef syncerror) {
131          [callback callCallback:didSync error:XPCSanitizeError((__bridge NSError*)syncerror)];
132          callbackTransaction.transaction = nil;
133      };
134  
135      _SecItemAdd((__bridge CFDictionaryRef) callbackQuery, &_client, &cfresult, &cferror);
136  
137      // SecItemAdd returns Some CF Object, but NSXPC is pretty adamant that everything be a specific NS type. Split it up here:
138      if(!cfresult) {
139          complete(NULL, NULL, (__bridge NSError *)(cferror));
140      } else if( CFGetTypeID(cfresult) == CFDictionaryGetTypeID()) {
141          complete((__bridge NSDictionary *)(cfresult), NULL, (__bridge NSError *)(cferror));
142      } else if( CFGetTypeID(cfresult) == CFArrayGetTypeID()) {
143          complete(NULL, (__bridge NSArray *)cfresult, (__bridge NSError *)(cferror));
144      } else {
145          // TODO: actually error here
146          complete(NULL, NULL, NULL);
147      }
148      CFReleaseNull(cfresult);
149      CFReleaseNull(cferror);
150  }
151  
152  - (void)secItemSetCurrentItemAcrossAllDevices:(NSData* _Nonnull)newItemPersistentRef
153                             newCurrentItemHash:(NSData* _Nonnull)newItemSHA1
154                                    accessGroup:(NSString* _Nonnull)accessGroup
155                                     identifier:(NSString* _Nonnull)identifier
156                                       viewHint:(NSString* _Nonnull)viewHint
157                        oldCurrentItemReference:(NSData* _Nullable)oldCurrentItemPersistentRef
158                             oldCurrentItemHash:(NSData* _Nullable)oldItemSHA1
159                                       complete:(void (^) (NSError* _Nullable operror))xpcComplete
160  {
161  #if OCTAGON
162      // The calling client might not handle CK types well. Sanitize!
163      void (^complete)(NSError*) = ^(NSError* error){
164          xpcComplete(XPCSanitizeError(error));
165      };
166  
167      __block CFErrorRef cferror = NULL;
168      if([self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementKeychainDeny]) {
169          SecError(errSecNotAvailable, &cferror, CFSTR("SecItemSetCurrentItemAcrossAllDevices: %@ has entitlement %@"), _client.task, kSecEntitlementKeychainDeny);
170          complete((__bridge NSError*) cferror);
171          CFReleaseNull(cferror);
172          return;
173      }
174  
175      if(![self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementPrivateCKKSWriteCurrentItemPointers]) {
176          SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemSetCurrentItemAcrossAllDevices: %@ does not have entitlement %@"), _client.task, kSecEntitlementPrivateCKKSWriteCurrentItemPointers);
177          complete((__bridge NSError*) cferror);
178          CFReleaseNull(cferror);
179          return;
180      }
181  
182      if (!accessGroupsAllows(self->_client.accessGroups, (__bridge CFStringRef)accessGroup, &_client)) {
183          SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemSetCurrentItemAcrossAllDevices: client is missing access-group %@: %@"), accessGroup, _client.task);
184          complete((__bridge NSError*)cferror);
185          CFReleaseNull(cferror);
186          return;
187      }
188  
189  #if OCTAGON
190      // Wait a bit for CKKS initialization in case of daemon start, and bail it doesn't come up
191      if([[CKKSViewManager manager].completedSecCKKSInitialize wait:10] != 0) {
192          secerror("SecItemSetCurrentItemAcrossAllDevices: CKKSViewManager not initialized?");
193          complete([NSError errorWithDomain:CKKSErrorDomain code:CKKSNotInitialized description:@"CKKS not yet initialized"]);
194          return;
195      }
196  #endif
197  
198      CKKSViewManager* manager = [CKKSViewManager manager];
199      if(!manager) {
200          secerror("SecItemSetCurrentItemAcrossAllDevices: no view manager?");
201          complete([NSError errorWithDomain:CKKSErrorDomain
202                                       code:CKKSNotInitialized
203                                description:@"No view manager, cannot forward request"]);
204          return;
205      }
206  
207      [manager setCurrentItemForAccessGroup:newItemPersistentRef
208                                       hash:newItemSHA1
209                                accessGroup:accessGroup
210                                 identifier:identifier
211                                   viewHint:viewHint
212                                  replacing:oldCurrentItemPersistentRef
213                                       hash:oldItemSHA1
214                                   complete:complete];
215      return;
216  #else // ! OCTAGON
217      xpcComplete([NSError errorWithDomain:@"securityd" code:errSecParam userInfo:@{NSLocalizedDescriptionKey: @"SecItemSetCurrentItemAcrossAllDevices not implemented on this platform"}]);
218  #endif // OCTAGON
219  }
220  
221  -(void)secItemFetchCurrentItemAcrossAllDevices:(NSString*)accessGroup
222                                      identifier:(NSString*)identifier
223                                        viewHint:(NSString*)viewHint
224                                 fetchCloudValue:(bool)fetchCloudValue
225                                        complete:(void (^) (NSData* persistentref, NSError* operror))xpcComplete
226  {
227  #if OCTAGON
228      // The calling client might not handle CK types well. Sanitize!
229      void (^complete)(NSData*, NSError*) = ^(NSData* persistentref, NSError* error){
230          xpcComplete(persistentref, XPCSanitizeError(error));
231      };
232  
233      CFErrorRef cferror = NULL;
234      if([self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementKeychainDeny]) {
235          SecError(errSecNotAvailable, &cferror, CFSTR("SecItemFetchCurrentItemAcrossAllDevices: %@ has entitlement %@"), _client.task, kSecEntitlementKeychainDeny);
236          complete(NULL, (__bridge NSError*) cferror);
237          CFReleaseNull(cferror);
238          return;
239      }
240  
241      if(![self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementPrivateCKKSReadCurrentItemPointers]) {
242          SecError(errSecNotAvailable, &cferror, CFSTR("SecItemFetchCurrentItemAcrossAllDevices: %@ does not have entitlement %@"), _client.task, kSecEntitlementPrivateCKKSReadCurrentItemPointers);
243          complete(NULL, (__bridge NSError*) cferror);
244          CFReleaseNull(cferror);
245          return;
246      }
247  
248      if (!accessGroupsAllows(self->_client.accessGroups, (__bridge CFStringRef)accessGroup, &_client)) {
249          SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemFetchCurrentItemAcrossAllDevices: client is missing access-group %@: %@"), accessGroup, _client.task);
250          complete(NULL, (__bridge NSError*)cferror);
251          CFReleaseNull(cferror);
252          return;
253      }
254  
255      // Wait a bit for CKKS initialization in case of daemon start, and bail it doesn't come up
256      if([[CKKSViewManager manager].completedSecCKKSInitialize wait:10] != 0) {
257          secerror("SecItemFetchCurrentItemAcrossAllDevices: CKKSViewManager not initialized?");
258          complete(NULL, [NSError errorWithDomain:CKKSErrorDomain code:CKKSNotInitialized description:@"CKKS not yet initialized"]);
259          return;
260      }
261  
262      [[CKKSViewManager manager] getCurrentItemForAccessGroup:accessGroup
263                                                   identifier:identifier
264                                                     viewHint:viewHint
265                                              fetchCloudValue:fetchCloudValue
266                                                     complete:^(NSString* uuid, NSError* error) {
267                                                         if(error || !uuid) {
268                                                             secnotice("ckkscurrent", "CKKS didn't find a current item for (%@,%@): %@ %@", accessGroup, identifier, uuid, error);
269                                                             complete(NULL, error);
270                                                             return;
271                                                         }
272  
273                                                         // Find the persistent ref and return it.
274                                                         secinfo("ckkscurrent", "CKKS believes current item UUID for (%@,%@) is %@. Looking up persistent ref...", accessGroup, identifier, uuid);
275                                                         [self findItemPersistentRefByUUID:uuid
276                                                                        extraLoggingString:[NSString stringWithFormat:@"%@,%@", accessGroup, identifier]
277                                                                                  complete:complete];
278                                                     }];
279  #else // ! OCTAGON
280      xpcComplete(NULL, [NSError errorWithDomain:@"securityd" code:errSecParam userInfo:@{NSLocalizedDescriptionKey: @"SecItemFetchCurrentItemAcrossAllDevices not implemented on this platform"}]);
281  #endif // OCTAGON
282  }
283  
284  -(void)findItemPersistentRefByUUID:(NSString*)uuid
285                  extraLoggingString:(NSString*)loggingStr
286                            complete:(void (^) (NSData* persistentref, NSError* operror))xpcComplete
287  {
288      // The calling client might not handle CK types well. Sanitize!
289      void (^complete)(NSData*, NSError*) = ^(NSData* persistentref, NSError* error){
290          xpcComplete(persistentref, XPCSanitizeError(error));
291      };
292  
293      CFErrorRef cferror = NULL;
294      CFTypeRef result = NULL;
295  
296      // Must query per-class, so:
297      const SecDbSchema *newSchema = current_schema();
298      for (const SecDbClass *const *class = newSchema->classes; *class != NULL; class++) {
299          if(!((*class)->itemclass)) {
300              //Don't try to search non-item 'classes'
301              continue;
302          }
303  
304          // Now that we're in an item class, reset any errSecItemNotFound errors from the last item class
305          CFReleaseNull(result);
306          CFReleaseNull(cferror);
307  
308          _SecItemCopyMatching((__bridge CFDictionaryRef) @{
309                                                            (__bridge NSString*) kSecClass: (__bridge NSString*) (*class)->name,
310                                                            (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny,
311                                                            (id)kSecMatchLimit : (id)kSecMatchLimitOne,
312                                                            (id)kSecAttrUUID: uuid,
313                                                            (id)kSecReturnPersistentRef: @YES,
314                                                            },
315                               &self->_client,
316                               &result,
317                               &cferror);
318  
319          if(cferror && CFErrorGetCode(cferror) != errSecItemNotFound) {
320              break;
321          }
322  
323          if(result) {
324              // Found the persistent ref! Quit searching.
325              break;
326          }
327      }
328  
329      if(result && !cferror) {
330          secinfo("ckkscurrent", "Found current item for (%@: %@)", loggingStr, uuid);
331      } else {
332          secerror("ckkscurrent: No current item for (%@,%@): %@ %@", loggingStr, uuid, result, cferror);
333      }
334  
335      complete((__bridge NSData*) result, (__bridge NSError*) cferror);
336      CFReleaseNull(result);
337      CFReleaseNull(cferror);
338  }
339  
340  - (void) secItemDigest:(NSString *)itemClass
341             accessGroup:(NSString *)accessGroup
342                complete:(void (^)(NSArray *digest, NSError* error))complete
343  {
344      CFArrayRef accessGroups = self->_client.accessGroups;
345      __block CFErrorRef cferror = NULL;
346      __block CFArrayRef result = NULL;
347  
348      if (itemClass == NULL || accessGroup == NULL) {
349          SecError(errSecParam, &cferror, CFSTR("parameter missing: %@"), _client.task);
350          complete(NULL, (__bridge NSError*) cferror);
351          CFReleaseNull(cferror);
352          return;
353      }
354  
355      if (![itemClass isEqualToString:@"inet"] && ![itemClass isEqualToString:@"genp"]) {
356          SecError(errSecParam, &cferror, CFSTR("class %@ is not supported: %@"), itemClass, _client.task);
357          complete(NULL, (__bridge NSError*) cferror);
358          CFReleaseNull(cferror);
359          return;
360      }
361  
362      if (!accessGroupsAllows(accessGroups, (__bridge CFStringRef)accessGroup, &_client)) {
363          SecError(errSecMissingEntitlement, &cferror, CFSTR("Client is missing access-group %@: %@"), accessGroup, _client.task);
364          complete(NULL, (__bridge NSError*) cferror);
365          CFReleaseNull(cferror);
366          return;
367      }
368  
369      if (CFArrayContainsValue(accessGroups, CFRangeMake(0, CFArrayGetCount(accessGroups)), CFSTR("*"))) {
370          /* Having the special accessGroup "*" allows access to all accessGroups. */
371          accessGroups = NULL;
372      }
373  
374      NSDictionary *attributes  = @{
375                                    (__bridge NSString *)kSecClass : itemClass,
376                                    (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
377                                    (__bridge NSString *)kSecAttrSynchronizable : (__bridge NSString *)kSecAttrSynchronizableAny,
378                                    };
379  
380      Query *q = query_create_with_limit((__bridge CFDictionaryRef)attributes, _client.musr, 0, &(_client), &cferror);
381      if (q == NULL) {
382          SecError(errSecParam, &cferror, CFSTR("failed to build query: %@"), _client.task);
383          complete(NULL, (__bridge NSError*) cferror);
384          CFReleaseNull(cferror);
385          return;
386      }
387  
388      bool ok = kc_with_dbt(false, &cferror, ^(SecDbConnectionRef dbt) {
389          return (bool)s3dl_copy_digest(dbt, q, &result, accessGroups, &cferror);
390      });
391  
392      (void)ok;
393  
394      complete((__bridge NSArray *)result, (__bridge NSError *)cferror);
395  
396      (void)query_destroy(q, &cferror);
397  
398      CFReleaseNull(result);
399      CFReleaseNull(cferror);
400  }
401  
402  
403  - (void) secKeychainDeleteMultiuser:(NSData *)uuid
404                             complete:(void(^)(bool status, NSError* error))complete
405  {
406      __block CFErrorRef cferror = NULL;
407  
408  #define SKDMUEntitlement @"com.apple.keychain.multiuser-admin"
409  
410      if([self clientHasBooleanEntitlement: SKDMUEntitlement]) {
411          SecError(errSecNotAvailable, &cferror, CFSTR("secKeychainDeleteMultiuser: %@ need entitlement %@"), _client.task, SKDMUEntitlement);
412          complete(false, (__bridge NSError *)cferror);
413          CFReleaseNull(cferror);
414          return;
415      }
416      if ([uuid length] != 16) {
417          SecError(errSecNotAvailable, &cferror, CFSTR("secKeychainDeleteMultiuser: %@ uuid have wrong length: %d"), _client.task, (int)[uuid length]);
418          complete(false, (__bridge NSError *)cferror);
419          CFReleaseNull(cferror);
420          return;
421  
422      }
423  
424  #if TARGET_OS_IPHONE
425      bool status = kc_with_dbt(true, &cferror, ^(SecDbConnectionRef dbt) {
426          return SecServerDeleteAllForUser(dbt, (__bridge CFDataRef)uuid, false, &cferror);
427      });
428  #else
429      bool status = false;
430  #endif
431  
432      complete(status, (__bridge NSError *)cferror);
433      CFReleaseNull(cferror);
434  }
435  
436  - (void)secItemVerifyBackupIntegrity:(BOOL)lightweight
437                            completion:(void (^)(NSDictionary<NSString*, NSString*>* results, NSError* error))completion
438  {
439      [[SecDbBackupManager manager] verifyBackupIntegrity:lightweight completion:completion];
440  }
441  
442  
443  - (void)secItemDeleteForAppClipApplicationIdentifier:(NSString*)identifier
444                                                completion:(void (^)(OSStatus))completion
445  {
446      if (![self clientHasBooleanEntitlement:(__bridge NSString*)kSecEntitlementPrivateAppClipDeletion]) {
447          completion(errSecMissingEntitlement);
448          return;
449      }
450  
451      completion(SecServerDeleteForAppClipApplicationIdentifier((__bridge CFStringRef)identifier));
452  }
453  
454  
455  - (void)secItemPersistKeychainWritesAtHighPerformanceCost:(void (^)(OSStatus status, NSError* error))completion
456  {
457      if (![self clientHasBooleanEntitlement:(__bridge NSString*)kSecEntitlementPrivatePerformanceImpactingAPI]) {
458          completion(errSecMissingEntitlement, [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecMissingEntitlement userInfo:nil]);
459          return;
460      }
461  
462      __block CFErrorRef cferror = NULL;
463      secnotice("item", "Performing keychain database checkpoint");
464  
465      bool status = kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) {
466          return SecDbCheckpoint(dbt, &cferror);
467      });
468  
469      if(!status) {
470          secerror("item: keychain database checkpoint failed: %@", cferror);
471      } else {
472          secnotice("item", "Keychain database checkpoint succeeded");
473      }
474  
475      completion(status ? errSecSuccess : errSecInternal, (__bridge NSError*)cferror);
476  
477      CFReleaseNull(cferror);
478  }
479  
480  @end