]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/SFKeychainControlManager.m
Security-59754.60.13.tar.gz
[apple/security.git] / keychain / securityd / 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