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