/ keychain / securityd / SFKeychainControlManager.m
SFKeychainControlManager.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 "SFKeychainControlManager.h"
 25  #import "SecCFError.h"
 26  #import "builtin_commands.h"
 27  #import "debugging.h"
 28  #import <Security/SecItem.h>
 29  #import <Security/SecItemPriv.h>
 30  #import <Foundation/NSXPCConnection_Private.h>
 31  #import <Security/SecXPCHelper.h>
 32  
 33  NSString* kSecEntitlementKeychainControl = @"com.apple.private.keychain.keychaincontrol";
 34  
 35  XPC_RETURNS_RETAINED xpc_endpoint_t SecServerCreateKeychainControlEndpoint(void)
 36  {
 37      return [[SFKeychainControlManager sharedManager] xpcControlEndpoint];
 38  }
 39  
 40  @implementation SFKeychainControlManager {
 41      NSXPCListener* _listener;
 42  }
 43  
 44  + (instancetype)sharedManager
 45  {
 46      static SFKeychainControlManager* manager = nil;
 47      static dispatch_once_t onceToken;
 48      dispatch_once(&onceToken, ^{
 49          manager = [[SFKeychainControlManager alloc] _init];
 50      });
 51  
 52      return manager;
 53  }
 54  
 55  - (instancetype)_init
 56  {
 57      if (self = [super init]) {
 58          _listener = [NSXPCListener anonymousListener];
 59          _listener.delegate = self;
 60          [_listener resume];
 61      }
 62  
 63      return self;
 64  }
 65  
 66  - (xpc_endpoint_t)xpcControlEndpoint
 67  {
 68      return [_listener.endpoint _endpoint];
 69  }
 70  
 71  - (BOOL)listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection
 72  {
 73      NSNumber* entitlementValue = [newConnection valueForEntitlement:kSecEntitlementKeychainControl];
 74      if (![entitlementValue isKindOfClass:[NSNumber class]] || !entitlementValue.boolValue) {
 75          secerror("SFKeychainControl: Client pid (%d) doesn't have entitlement: %@", newConnection.processIdentifier, kSecEntitlementKeychainControl);
 76          return NO;
 77      }
 78  
 79      NSSet<Class>* errorClasses = [SecXPCHelper safeErrorClasses];
 80  
 81      NSXPCInterface* interface = [NSXPCInterface interfaceWithProtocol:@protocol(SFKeychainControl)];
 82      [interface setClasses:errorClasses forSelector:@selector(rpcFindCorruptedItemsWithReply:) argumentIndex:1 ofReply:YES];
 83      [interface setClasses:errorClasses forSelector:@selector(rpcDeleteCorruptedItemsWithReply:) argumentIndex:1 ofReply:YES];
 84      newConnection.exportedInterface = interface;
 85      newConnection.exportedObject = self;
 86      [newConnection resume];
 87      return YES;
 88  }
 89  
 90  - (NSArray<NSDictionary*>*)findCorruptedItemsWithError:(NSError**)error
 91  {
 92      NSMutableArray<NSDictionary*>* corruptedItems = [[NSMutableArray alloc] init];
 93      NSMutableArray* underlyingErrors = [[NSMutableArray alloc] init];
 94  
 95      CFTypeRef genericPasswords = NULL;
 96      NSDictionary* genericPasswordsQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
 97                                               (id)kSecReturnPersistentRef : @(YES),
 98                                               (id)kSecUseDataProtectionKeychain : @(YES),
 99                                               (id)kSecMatchLimit : (id)kSecMatchLimitAll };
100      OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordsQuery, &genericPasswords);
101      CFErrorRef genericPasswordError = NULL;
102      if (status != errSecItemNotFound) {
103          SecError(status, &genericPasswordError, CFSTR("generic password query failed"));
104          if (genericPasswordError) {
105              [underlyingErrors addObject:CFBridgingRelease(genericPasswordError)];
106          }
107      }
108  
109      CFTypeRef internetPasswords = NULL;
110      NSDictionary* internetPasswordsQuery = @{ (id)kSecClass : (id)kSecClassInternetPassword,
111                                                (id)kSecReturnPersistentRef : @(YES),
112                                                (id)kSecUseDataProtectionKeychain : @(YES),
113                                                (id)kSecMatchLimit : (id)kSecMatchLimitAll };
114      status = SecItemCopyMatching((__bridge CFDictionaryRef)internetPasswordsQuery, &internetPasswords);
115      CFErrorRef internetPasswordError = NULL;
116      if (status != errSecItemNotFound) {
117          SecError(status, &internetPasswordError, CFSTR("internet password query failed"));
118          if (internetPasswordError) {
119              [underlyingErrors addObject:CFBridgingRelease(internetPasswordError)];
120          }
121      }
122  
123      CFTypeRef keys = NULL;
124      NSDictionary* keysQuery = @{ (id)kSecClass : (id)kSecClassKey,
125                                   (id)kSecReturnPersistentRef : @(YES),
126                                   (id)kSecUseDataProtectionKeychain : @(YES),
127                                   (id)kSecMatchLimit : (id)kSecMatchLimitAll };
128      status = SecItemCopyMatching((__bridge CFDictionaryRef)keysQuery, &keys);
129      CFErrorRef keyError = NULL;
130      if (status != errSecItemNotFound) {
131          if (keyError) {
132              [underlyingErrors addObject:CFBridgingRelease(keyError)];
133          }
134      }
135  
136      CFTypeRef certificates = NULL;
137      NSDictionary* certificateQuery = @{ (id)kSecClass : (id)kSecClassCertificate,
138                                          (id)kSecReturnPersistentRef : @(YES),
139                                          (id)kSecUseDataProtectionKeychain : @(YES),
140                                          (id)kSecMatchLimit : (id)kSecMatchLimitAll };
141      status = SecItemCopyMatching((__bridge CFDictionaryRef)certificateQuery, &certificates);
142      CFErrorRef certificateError = NULL;
143      if (status != errSecItemNotFound) {
144          SecError(status, &certificateError, CFSTR("certificate query failed"));
145          if (certificateError) {
146              [underlyingErrors addObject:CFBridgingRelease(certificateError)];
147          }
148      }
149  
150      void (^scanArrayForCorruptedItem)(CFTypeRef, NSString*) = ^(CFTypeRef items, NSString* class) {
151          if ([(__bridge NSArray*)items isKindOfClass:[NSArray class]]) {
152              NSLog(@"scanning %d %@", (int)CFArrayGetCount(items), class);
153              for (NSData* persistentRef in (__bridge NSArray*)items) {
154                  NSDictionary* itemQuery = @{ (id)kSecClass : class,
155                                               (id)kSecValuePersistentRef : persistentRef,
156                                               (id)kSecReturnAttributes : @(YES),
157                                               (id)kSecUseDataProtectionKeychain : @(YES) };
158                  CFTypeRef itemAttributes = NULL;
159                  OSStatus copyStatus = SecItemCopyMatching((__bridge CFDictionaryRef)itemQuery, &itemAttributes);
160                  if (copyStatus != errSecSuccess && status != errSecInteractionNotAllowed) {
161                      [corruptedItems addObject:itemQuery];
162                  }
163              }
164          }
165      };
166  
167      scanArrayForCorruptedItem(genericPasswords, (id)kSecClassGenericPassword);
168      scanArrayForCorruptedItem(internetPasswords, (id)kSecClassInternetPassword);
169      scanArrayForCorruptedItem(keys, (id)kSecClassKey);
170      scanArrayForCorruptedItem(certificates, (id)kSecClassCertificate);
171  
172      if (underlyingErrors.count > 0 && error) {
173          *error = [NSError errorWithDomain:@"com.apple.security.keychainhealth" code:1 userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"encountered %d errors searching for corrupted items", (int)underlyingErrors.count], NSUnderlyingErrorKey : underlyingErrors.firstObject, @"searchingErrorCount" : @(underlyingErrors.count) }];
174      }
175  
176      return corruptedItems;
177  }
178  
179  - (bool)deleteCorruptedItemsWithError:(NSError**)error
180  {
181      NSError* findError = nil;
182      NSArray* corruptedItems = [self findCorruptedItemsWithError:&findError];
183      bool success = findError == nil;
184  
185      NSMutableArray* deleteErrors = [[NSMutableArray alloc] init];
186      for (NSDictionary* corruptedItem in corruptedItems) {
187          OSStatus status = SecItemDelete((__bridge CFDictionaryRef)corruptedItem);
188          if (status != errSecSuccess) {
189              success = false;
190              CFErrorRef deleteError = NULL;
191              SecError(status, &deleteError, CFSTR("failed to delete corrupted item"));
192              [deleteErrors addObject:CFBridgingRelease(deleteError)];
193          }
194      }
195  
196      if (error && (findError || deleteErrors.count > 0)) {
197          *error = [NSError errorWithDomain:@"com.apple.security.keychainhealth" code:2 userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"encountered %@ errors searching for corrupted items and %d errors attempting to delete corrupted items", findError.userInfo[@"searchingErrorCount"], (int)deleteErrors.count]}];
198      }
199  
200      return success;
201  }
202  
203  - (void)rpcFindCorruptedItemsWithReply:(void (^)(NSArray* corruptedItems, NSError* error))reply
204  {
205      NSError* error = nil;
206      NSArray* corruptedItems = [self findCorruptedItemsWithError:&error];
207      reply(corruptedItems, error);
208  }
209  
210  - (void)rpcDeleteCorruptedItemsWithReply:(void (^)(bool success, NSError* error))reply
211  {
212      NSError* error = nil;
213      bool success = [self deleteCorruptedItemsWithError:&error];
214      reply(success, error);
215  }
216  
217  @end