/ keychain / securityd / SFKeychainServer.m
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