SFKeychainServer.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 "SFKeychainServer.h" 25 #import <TargetConditionals.h> 26 27 #if !TARGET_OS_BRIDGE 28 #if __OBJC2__ 29 30 #import "SecCDKeychain.h" 31 #import "SecFileLocations.h" 32 #import "debugging.h" 33 #import "CloudKitCategories.h" 34 #import "SecAKSWrappers.h" 35 #include "securityd_client.h" 36 #import "server_entitlement_helpers.h" 37 #import "SecTask.h" 38 #import "keychain/categories/NSError+UsefulConstructors.h" 39 #import "SecEntitlements.h" 40 #import <Security/SecXPCHelper.h> 41 #import <SecurityFoundation/SFKeychain.h> 42 #import <SecurityFoundation/SFCredential_Private.h> 43 #import <SecurityFoundation/SFCredentialStore_Private.h> 44 #import <Foundation/NSKeyedArchiver_Private.h> 45 #import <Foundation/NSXPCConnection_Private.h> 46 47 static NSString* const SFKeychainItemAttributeLocalizedLabel = @"label"; 48 static NSString* const SFKeychainItemAttributeLocalizedDescription = @"description"; 49 50 static NSString* const SFCredentialAttributeUsername = @"username"; 51 static NSString* const SFCredentialAttributePrimaryServiceIdentifier = @"primaryServiceID"; 52 static NSString* const SFCredentialAttributeSupplementaryServiceIdentifiers = @"supplementaryServiceIDs"; 53 static NSString* const SFCredentialAttributeCreationDate = @"creationDate"; 54 static NSString* const SFCredentialAttributeModificationDate = @"modificationDate"; 55 static NSString* const SFCredentialAttributeCustom = @"customAttributes"; 56 static NSString* const SFCredentialSecretPassword = @"password"; 57 58 @interface SFCredential (securityd_only) 59 60 - (instancetype)_initWithUsername:(NSString*)username primaryServiceIdentifier:(SFServiceIdentifier*)primaryServiceIdentifier supplementaryServiceIdentifiers:(nullable NSArray<SFServiceIdentifier*>*)supplementaryServiceIdentifiers; 61 62 @end 63 64 @interface SFKeychainServerConnection () 65 66 - (instancetype)initWithKeychain:(SecCDKeychain*)keychain xpcConnection:(NSXPCConnection*)connection; 67 68 @end 69 70 @implementation SecCDKeychainItemTypeCredential 71 72 + (instancetype)itemType 73 { 74 static SecCDKeychainItemTypeCredential* itemType = nil; 75 static dispatch_once_t onceToken; 76 dispatch_once(&onceToken, ^{ 77 itemType = [[self alloc] _initWithName:@"Credential" version:1 primaryKeys:@[SFCredentialAttributeUsername, SFCredentialAttributePrimaryServiceIdentifier] syncableKeys:nil]; 78 }); 79 80 return itemType; 81 } 82 83 @end 84 85 @implementation SFKeychainServer { 86 SecCDKeychain* _keychain; 87 } 88 89 - (instancetype)initWithStorageURL:(NSURL*)persistentStoreURL modelURL:(NSURL*)managedObjectURL encryptDatabase:(bool)encryptDatabase 90 { 91 if (self = [super init]) { 92 _keychain = [[SecCDKeychain alloc] initWithStorageURL:persistentStoreURL modelURL:managedObjectURL encryptDatabase:encryptDatabase]; 93 } 94 95 return self; 96 } 97 98 - (BOOL)listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection 99 { 100 NSNumber* keychainDenyEntitlement = [newConnection valueForEntitlement:(__bridge NSString*)kSecEntitlementKeychainDeny]; 101 if ([keychainDenyEntitlement isKindOfClass:[NSNumber class]] && keychainDenyEntitlement.boolValue == YES) { 102 secerror("SFKeychainServer: connection denied due to entitlement %@", kSecEntitlementKeychainDeny); 103 return NO; 104 } 105 106 // wait a bit for shared function from SecurityFoundation to get to SDK, then addopt that 107 NSXPCInterface* interface = [NSXPCInterface interfaceWithProtocol:@protocol(SFKeychainServerProtocol)]; 108 109 NSSet<Class> *errorClasses = [SecXPCHelper safeErrorClasses]; 110 111 [interface setClasses:errorClasses forSelector:@selector(rpcAddCredential:withAccessPolicy:reply:) argumentIndex:1 ofReply:YES]; 112 [interface setClasses:errorClasses forSelector:@selector(rpcFetchPasswordCredentialForPersistentIdentifier:reply:) argumentIndex:2 ofReply:YES]; 113 [interface setClasses:errorClasses forSelector:@selector(rpcLookupCredentialsForServiceIdentifiers:reply:) argumentIndex:1 ofReply:YES]; 114 [interface setClasses:errorClasses forSelector:@selector(rpcRemoveCredentialWithPersistentIdentifier:reply:) argumentIndex:1 ofReply:YES]; 115 [interface setClasses:errorClasses forSelector:@selector(rpcReplaceOldCredential:withNewCredential:reply:) argumentIndex:1 ofReply:YES]; 116 [interface setClasses:errorClasses forSelector:@selector(rpcReplaceCredential:withNewCredential:reply:) argumentIndex:1 ofReply:YES]; 117 118 [interface setClasses:[NSSet setWithObjects:[NSArray class], [SFServiceIdentifier class], nil] forSelector:@selector(rpcLookupCredentialsForServiceIdentifiers:reply:) argumentIndex:0 ofReply:NO]; 119 [interface setClasses:[NSSet setWithObjects:[NSArray class], [SFPasswordCredential class], nil] forSelector:@selector(rpcLookupCredentialsForServiceIdentifiers:reply:) argumentIndex:0 ofReply:YES]; 120 newConnection.exportedInterface = interface; 121 newConnection.exportedObject = [[SFKeychainServerConnection alloc] initWithKeychain:_keychain xpcConnection:newConnection]; 122 [newConnection resume]; 123 return YES; 124 } 125 126 - (SecCDKeychain*)_keychain 127 { 128 return _keychain; 129 } 130 131 @end 132 133 @implementation SFKeychainServerConnection { 134 SecCDKeychain* _keychain; 135 NSArray* _clientAccessGroups; 136 } 137 138 @synthesize clientAccessGroups = _clientAccessGroups; 139 140 - (instancetype)initWithKeychain:(SecCDKeychain*)keychain xpcConnection:(NSXPCConnection*)connection 141 { 142 if (self = [super init]) { 143 _keychain = keychain; 144 145 SecTaskRef task = SecTaskCreateWithAuditToken(NULL, connection.auditToken); 146 if (task) { 147 _clientAccessGroups = (__bridge_transfer NSArray*)SecTaskCopyAccessGroups(task); 148 } 149 CFReleaseNull(task); 150 } 151 152 return self; 153 } 154 155 - (keyclass_t)keyclassForAccessPolicy:(SFAccessPolicy*)accessPolicy 156 { 157 if (accessPolicy.accessibility.mode == SFAccessibleAfterFirstUnlock) { 158 if (accessPolicy.sharingPolicy == SFSharingPolicyThisDeviceOnly) { 159 return key_class_cku; 160 } 161 else { 162 return key_class_ck; 163 } 164 } 165 else { 166 if (accessPolicy.sharingPolicy == SFSharingPolicyThisDeviceOnly) { 167 return key_class_aku; 168 } 169 else { 170 return key_class_ak; 171 } 172 } 173 } 174 175 - (void)rpcAddCredential:(SFCredential*)credential withAccessPolicy:(SFAccessPolicy*)accessPolicy reply:(void (^)(NSString* persistentIdentifier, NSError* error))reply 176 { 177 if (![credential isKindOfClass:[SFPasswordCredential class]]) { 178 reply(nil, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInvalidParameter userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"attempt to add credential to SFCredentialStore that is not a password credential: %@", credential]}]); 179 return; 180 } 181 182 NSString* accessGroup = accessPolicy.accessGroup; 183 if (!accessGroup) { 184 NSError* error = nil; 185 accessGroup = self.clientAccessGroups.firstObject; 186 if (!accessGroup) { 187 error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorMissingAccessGroup userInfo:@{NSLocalizedDescriptionKey : @"no keychain access group found; ensure that your process has the keychain-access-groups entitlement"}]; 188 reply(nil, error); 189 return; 190 } 191 } 192 193 SFPasswordCredential* passwordCredential = (SFPasswordCredential*)credential; 194 195 NSError* error = nil; 196 NSData* primaryServiceIdentifierData = [NSKeyedArchiver archivedDataWithRootObject:passwordCredential.primaryServiceIdentifier requiringSecureCoding:YES error:&error]; 197 if (!primaryServiceIdentifierData) { 198 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ 199 reply(nil, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorSaveFailed userInfo:@{ NSLocalizedDescriptionKey : @"failed to serialize primary service identifier", NSUnderlyingErrorKey : error }]); 200 }); 201 return; 202 } 203 204 NSMutableArray* serializedSupplementaryServiceIdentifiers = [[NSMutableArray alloc] initWithCapacity:passwordCredential.supplementaryServiceIdentifiers.count]; 205 for (SFServiceIdentifier* serviceIdentifier in passwordCredential.supplementaryServiceIdentifiers) { 206 NSData* serviceIdentifierData = [NSKeyedArchiver archivedDataWithRootObject:serviceIdentifier requiringSecureCoding:YES error:&error]; 207 if (serviceIdentifierData) { 208 [serializedSupplementaryServiceIdentifiers addObject:serviceIdentifierData]; 209 } 210 else { 211 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ 212 reply(nil, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorSaveFailed userInfo:@{ NSLocalizedDescriptionKey : @"failed to serialize supplementary service identifier", NSUnderlyingErrorKey : error }]); 213 }); 214 return; 215 } 216 } 217 218 NSDictionary* attributes = @{ SFCredentialAttributeUsername : passwordCredential.username, 219 SFCredentialAttributePrimaryServiceIdentifier : primaryServiceIdentifierData, 220 SFCredentialAttributeSupplementaryServiceIdentifiers : serializedSupplementaryServiceIdentifiers, 221 SFCredentialAttributeCreationDate : [NSDate date], 222 SFCredentialAttributeModificationDate : [NSDate date], 223 SFKeychainItemAttributeLocalizedLabel : passwordCredential.localizedLabel, 224 SFKeychainItemAttributeLocalizedDescription : passwordCredential.localizedDescription, 225 SFCredentialAttributeCustom : passwordCredential.customAttributes ?: [NSDictionary dictionary] }; 226 227 NSDictionary* secrets = @{ SFCredentialSecretPassword : passwordCredential.password }; 228 NSUUID* persistentID = [NSUUID UUID]; 229 230 // lookup attributes: 231 // 1. primaryServiceIdentifier (always) 232 // 2. username (always) 233 // 3. label (if present) 234 // 4. description (if present) 235 // 5. each of the service identifiers by type, e.g. "domain" 236 // 6. any custom attributes that fit the requirements (key is string, and value is plist type) 237 238 SecCDKeychainLookupTuple* primaryServiceIdentifierLookup = [SecCDKeychainLookupTuple lookupTupleWithKey:SFCredentialAttributePrimaryServiceIdentifier value:primaryServiceIdentifierData]; 239 SecCDKeychainLookupTuple* usernameLookup = [SecCDKeychainLookupTuple lookupTupleWithKey:SFCredentialAttributeUsername value:passwordCredential.username]; 240 SecCDKeychainLookupTuple* labelLookup = [SecCDKeychainLookupTuple lookupTupleWithKey:SFKeychainItemAttributeLocalizedLabel value:passwordCredential.localizedLabel]; 241 SecCDKeychainLookupTuple* descriptionLookup = [SecCDKeychainLookupTuple lookupTupleWithKey:SFKeychainItemAttributeLocalizedDescription value:passwordCredential.localizedDescription]; 242 NSMutableArray* lookupAttributes = [[NSMutableArray alloc] initWithObjects:primaryServiceIdentifierLookup, usernameLookup, nil]; 243 if (labelLookup) { 244 [lookupAttributes addObject:labelLookup]; 245 } 246 if (descriptionLookup) { 247 [lookupAttributes addObject:descriptionLookup]; 248 } 249 250 SFServiceIdentifier* primaryServiceIdentifier = credential.primaryServiceIdentifier; 251 [lookupAttributes addObject:[SecCDKeychainLookupTuple lookupTupleWithKey:primaryServiceIdentifier.lookupKey value:primaryServiceIdentifier.serviceID]]; 252 for (SFServiceIdentifier* serviceIdentifier in credential.supplementaryServiceIdentifiers) { 253 [lookupAttributes addObject:[SecCDKeychainLookupTuple lookupTupleWithKey:serviceIdentifier.lookupKey value:serviceIdentifier.serviceID]]; 254 } 255 256 [passwordCredential.customAttributes enumerateKeysAndObjectsUsingBlock:^(NSString* customKey, id value, BOOL* stop) { 257 if ([customKey isKindOfClass:[NSString class]]) { 258 SecCDKeychainLookupTuple* lookupTuple = [SecCDKeychainLookupTuple lookupTupleWithKey:customKey value:value]; 259 if (lookupTuple) { 260 [lookupAttributes addObject:lookupTuple]; 261 } 262 else { 263 // TODO: an error here? 264 } 265 } 266 }]; 267 268 SecCDKeychainAccessControlEntity* owner = [SecCDKeychainAccessControlEntity accessControlEntityWithType:SecCDKeychainAccessControlEntityTypeAccessGroup stringRepresentation:accessGroup]; 269 keyclass_t keyclass = [self keyclassForAccessPolicy:accessPolicy]; 270 SecCDKeychainItem* item = [[SecCDKeychainItem alloc] initItemType:[SecCDKeychainItemTypeCredential itemType] withPersistentID:persistentID attributes:attributes lookupAttributes:lookupAttributes secrets:secrets owner:owner keyclass:keyclass]; 271 [_keychain insertItems:@[item] withConnection:self completionHandler:^(bool success, NSError* insertError) { 272 if (success && !insertError) { 273 reply(persistentID.UUIDString, nil); 274 } 275 else { 276 reply(nil, insertError); 277 } 278 }]; 279 } 280 281 - (void)rpcFetchPasswordCredentialForPersistentIdentifier:(NSString*)persistentIdentifier reply:(void (^)(SFPasswordCredential* credential, NSString* password, NSError* error))reply 282 { 283 // TODO: negative testing 284 NSUUID* persistentID = [[NSUUID alloc] initWithUUIDString:persistentIdentifier]; 285 if (!persistentID) { 286 secerror("SFKeychainServer: attempt to fetch credential with invalid persistent identifier; %@", persistentIdentifier); 287 reply(nil, nil, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInvalidPersistentIdentifier userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"invalid persistent identifier: %@", persistentIdentifier]}]); 288 return; 289 } 290 291 [_keychain fetchItemForPersistentID:persistentID withConnection:self completionHandler:^(SecCDKeychainItem* item, NSError* error) { 292 NSError* localError = error; 293 SFPasswordCredential* credential = nil; 294 if (item && !error) { 295 credential = [self passwordCredentialForItem:item error:&localError]; 296 } 297 298 if (credential) { 299 reply(credential, credential.password, nil); 300 } 301 else { 302 reply(nil, nil, localError); 303 } 304 }]; 305 } 306 307 - (void)rpcLookupCredentialsForServiceIdentifiers:(nullable NSArray<SFServiceIdentifier*>*)serviceIdentifiers reply:(void (^)(NSArray<SFCredential*>* _Nullable results, NSError* _Nullable error))reply 308 { 309 __block NSMutableDictionary* resultsDict = [[NSMutableDictionary alloc] init]; 310 __block NSError* resultError = nil; 311 312 void (^processFetchedItems)(NSArray*) = ^(NSArray* fetchedItems) { 313 for (SecCDKeychainItemMetadata* item in fetchedItems) { 314 if ([item.itemType isKindOfClass:[SecCDKeychainItemTypeCredential class]]) { 315 SFPasswordCredential* credential = [self passwordCredentialForItemMetadata:item error:&resultError]; 316 if (credential) { 317 resultsDict[item.persistentID] = credential; 318 } 319 else { 320 resultsDict = nil; // got an error 321 } 322 } 323 } 324 }; 325 326 if (!serviceIdentifiers) { 327 // TODO: lookup everything 328 } 329 else { 330 for (SFServiceIdentifier* serviceIdentifier in serviceIdentifiers) { 331 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 332 // TODO: this is lamé; make fetchItemsWithValue take an array and get rid of the semaphore crap 333 [_keychain fetchItemsWithValue:serviceIdentifier.serviceID forLookupKey:serviceIdentifier.lookupKey ofType:SecCDKeychainLookupValueTypeString withConnection:self completionHandler:^(NSArray<SecCDKeychainItemMetadata*>* items, NSError* error) { 334 if (items && !error) { 335 processFetchedItems(items); 336 } 337 else { 338 resultsDict = nil; 339 resultError = error; 340 } 341 342 dispatch_semaphore_signal(semaphore); 343 }]; 344 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 345 } 346 } 347 348 reply(resultsDict.allValues, resultError); 349 } 350 351 - (void)rpcRemoveCredentialWithPersistentIdentifier:(NSString*)persistentIdentifier reply:(void (^)(BOOL success, NSError* _Nullable error))reply 352 { 353 NSUUID* persistentID = [[NSUUID alloc] initWithUUIDString:persistentIdentifier]; 354 if (!persistentID) { 355 secerror("SFKeychainServer: attempt to remove credential with invalid persistent identifier; %@", persistentIdentifier); 356 reply(false, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInvalidPersistentIdentifier userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"invalid persistent identifier: %@", persistentIdentifier]}]); 357 return; 358 } 359 360 [_keychain deleteItemWithPersistentID:persistentID withConnection:self completionHandler:^(bool success, NSError* error) { 361 reply(success, error); 362 }]; 363 } 364 365 - (void)rpcReplaceOldCredential:(SFCredential*)oldCredential withNewCredential:(SFCredential*)newCredential reply:(void (^)(NSString* newPersistentIdentifier, NSError* _Nullable error))reply 366 { 367 // TODO: implement 368 reply(nil, nil); 369 } 370 371 - (SFPasswordCredential*)passwordCredentialForItem:(SecCDKeychainItem*)item error:(NSError**)error 372 { 373 SFPasswordCredential* credential = [self passwordCredentialForItemMetadata:item.metadata error:error]; 374 if (credential) { 375 credential.password = item.secrets[SFCredentialSecretPassword]; 376 if (!credential.password) { 377 if (error) { 378 *error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorSecureDecodeFailed userInfo:@{NSLocalizedDescriptionKey : @"failed to get password for SFCredential"}]; 379 } 380 return nil; 381 } 382 } 383 384 return credential; 385 } 386 387 - (SFPasswordCredential*)passwordCredentialForItemMetadata:(SecCDKeychainItemMetadata*)metadata error:(NSError**)error 388 { 389 NSDictionary* attributes = metadata.attributes; 390 NSString* username = attributes[SFCredentialAttributeUsername]; 391 392 NSError* localError = nil; 393 SFServiceIdentifier* primaryServiceIdentifier = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFServiceIdentifier class] fromData:attributes[SFCredentialAttributePrimaryServiceIdentifier] error:&localError]; 394 395 NSArray* serializedSupplementaryServiceIdentifiers = attributes[SFCredentialAttributeSupplementaryServiceIdentifiers]; 396 NSMutableArray* supplementaryServiceIdentifiers = [[NSMutableArray alloc] initWithCapacity:serializedSupplementaryServiceIdentifiers.count]; 397 for (NSData* serializedServiceIdentifier in serializedSupplementaryServiceIdentifiers) { 398 if ([serializedServiceIdentifier isKindOfClass:[NSData class]]) { 399 SFServiceIdentifier* serviceIdentifier = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFServiceIdentifier class] fromData:serializedServiceIdentifier error:&localError]; 400 if (serviceIdentifier) { 401 [supplementaryServiceIdentifiers addObject:serviceIdentifier]; 402 } 403 else { 404 supplementaryServiceIdentifiers = nil; 405 break; 406 } 407 } 408 else { 409 supplementaryServiceIdentifiers = nil; 410 localError = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorSecureDecodeFailed userInfo:@{NSLocalizedDescriptionKey : @"malformed supplementary service identifiers array in SecCDKeychainItem"}]; 411 break; 412 } 413 } 414 415 if (username && primaryServiceIdentifier && supplementaryServiceIdentifiers) { 416 SFPasswordCredential* credential = [[SFPasswordCredential alloc] _initWithUsername:username primaryServiceIdentifier:primaryServiceIdentifier supplementaryServiceIdentifiers:supplementaryServiceIdentifiers]; 417 credential.creationDate = attributes[SFCredentialAttributeCreationDate]; 418 credential.modificationDate = attributes[SFCredentialAttributeModificationDate]; 419 credential.localizedLabel = attributes[SFKeychainItemAttributeLocalizedLabel]; 420 credential.localizedDescription = attributes[SFKeychainItemAttributeLocalizedDescription]; 421 credential.persistentIdentifier = metadata.persistentID.UUIDString; 422 credential.customAttributes = attributes[SFCredentialAttributeCustom]; 423 return credential; 424 } 425 else { 426 if (error) { 427 *error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorSecureDecodeFailed userInfo:@{ NSLocalizedDescriptionKey : @"failed to deserialize SFCredential", NSUnderlyingErrorKey : localError }]; 428 } 429 return nil; 430 } 431 } 432 433 @end 434 435 #endif // ___OBJC2__ 436 437 void SFKeychainServerInitialize(void) 438 { 439 static dispatch_once_t once; 440 static SFKeychainServer* server; 441 static NSXPCListener* listener; 442 443 dispatch_once(&once, ^{ 444 @autoreleasepool { 445 NSURL* persistentStoreURL = (__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)@"CDKeychain"); 446 NSBundle* resourcesBundle = [NSBundle bundleWithPath:@"/System/Library/Keychain/KeychainResources.bundle"]; 447 NSURL* managedObjectModelURL = [resourcesBundle URLForResource:@"KeychainModel" withExtension:@"momd"]; 448 server = [[SFKeychainServer alloc] initWithStorageURL:persistentStoreURL modelURL:managedObjectModelURL encryptDatabase:true]; 449 listener = [[NSXPCListener alloc] initWithMachServiceName:@(kSFKeychainServerServiceName)]; 450 listener.delegate = server; 451 [listener resume]; 452 } 453 }); 454 } 455 456 #else // !TARGET_OS_BRIDGE 457 458 void SFKeychainServerInitialize(void) {} 459 460 #endif 461