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