2  * Copyright (c) 2017 Apple Inc. All Rights Reserved.
 
   4  * @APPLE_LICENSE_HEADER_START@
 
   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
 
  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.
 
  21  * @APPLE_LICENSE_HEADER_END@
 
  24 #import "SFKeychainControlManager.h"
 
  25 #import "SecCFError.h"
 
  26 #import "builtin_commands.h"
 
  28 #import <Security/SecItem.h>
 
  29 #import <Security/SecItemPriv.h>
 
  30 #import <Foundation/NSXPCConnection_Private.h>
 
  31 #import <Security/SecXPCHelper.h>
 
  33 NSString* kSecEntitlementKeychainControl = @"com.apple.private.keychain.keychaincontrol";
 
  35 XPC_RETURNS_RETAINED xpc_endpoint_t SecServerCreateKeychainControlEndpoint(void)
 
  37     return [[SFKeychainControlManager sharedManager] xpcControlEndpoint];
 
  40 @implementation SFKeychainControlManager {
 
  41     NSXPCListener* _listener;
 
  44 + (instancetype)sharedManager
 
  46     static SFKeychainControlManager* manager = nil;
 
  47     static dispatch_once_t onceToken;
 
  48     dispatch_once(&onceToken, ^{
 
  49         manager = [[SFKeychainControlManager alloc] _init];
 
  57     if (self = [super init]) {
 
  58         _listener = [NSXPCListener anonymousListener];
 
  59         _listener.delegate = self;
 
  66 - (xpc_endpoint_t)xpcControlEndpoint
 
  68     return [_listener.endpoint _endpoint];
 
  71 - (BOOL)listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)newConnection
 
  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);
 
  79     NSSet<Class>* errorClasses = [SecXPCHelper safeErrorClasses];
 
  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];
 
  90 - (NSArray<NSDictionary*>*)findCorruptedItemsWithError:(NSError**)error
 
  92     NSMutableArray<NSDictionary*>* corruptedItems = [[NSMutableArray alloc] init];
 
  93     NSMutableArray* underlyingErrors = [[NSMutableArray alloc] init];
 
  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)];
 
 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)];
 
 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) {
 
 132             [underlyingErrors addObject:CFBridgingRelease(keyError)];
 
 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)];
 
 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];
 
 167     scanArrayForCorruptedItem(genericPasswords, (id)kSecClassGenericPassword);
 
 168     scanArrayForCorruptedItem(internetPasswords, (id)kSecClassInternetPassword);
 
 169     scanArrayForCorruptedItem(keys, (id)kSecClassKey);
 
 170     scanArrayForCorruptedItem(certificates, (id)kSecClassCertificate);
 
 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) }];
 
 176     return corruptedItems;
 
 179 - (bool)deleteCorruptedItemsWithError:(NSError**)error
 
 181     NSError* findError = nil;
 
 182     NSArray* corruptedItems = [self findCorruptedItemsWithError:&findError];
 
 183     bool success = findError == nil;
 
 185     NSMutableArray* deleteErrors = [[NSMutableArray alloc] init];
 
 186     for (NSDictionary* corruptedItem in corruptedItems) {
 
 187         OSStatus status = SecItemDelete((__bridge CFDictionaryRef)corruptedItem);
 
 188         if (status != errSecSuccess) {
 
 190             CFErrorRef deleteError = NULL;
 
 191             SecError(status, &deleteError, CFSTR("failed to delete corrupted item"));
 
 192             [deleteErrors addObject:CFBridgingRelease(deleteError)];
 
 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]}];
 
 203 - (void)rpcFindCorruptedItemsWithReply:(void (^)(NSArray* corruptedItems, NSError* error))reply
 
 205     NSError* error = nil;
 
 206     NSArray* corruptedItems = [self findCorruptedItemsWithError:&error];
 
 207     reply(corruptedItems, error);
 
 210 - (void)rpcDeleteCorruptedItemsWithReply:(void (^)(bool success, NSError* error))reply
 
 212     NSError* error = nil;
 
 213     bool success = [self deleteCorruptedItemsWithError:&error];
 
 214     reply(success, error);