]> git.saurik.com Git - apple/security.git/blob - OSX/sec/ipc/server_xpc.m
Security-58286.41.2.tar.gz
[apple/security.git] / OSX / sec / ipc / server_xpc.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 <Foundation/Foundation.h>
25
26 #include <ipc/securityd_client.h>
27 #include <ipc/server_security_helpers.h>
28 #include <ipc/server_endpoint.h>
29
30 #if OCTAGON
31 #include <CloudKit/CloudKit_Private.h>
32 // If your callbacks might pass back a CK error, you should use the XPCSanitizeError() spi on all branches at this layer.
33 // Otherwise, XPC might crash on the other side if they haven't linked CloudKit.framework.
34 #define XPCSanitizeError CKXPCSuitableError
35 #else
36 // This is a no-op: XPCSanitizeError(error) turns into (error)
37 #define XPCSanitizeError
38 #endif // OCTAGON
39
40 #include <Security/SecEntitlements.h>
41 #include <Security/SecItemPriv.h>
42 #include <securityd/SecItemServer.h>
43 #include <securityd/SecItemSchema.h>
44 #include <securityd/SecItemDb.h>
45
46 #include "keychain/ckks/CKKSViewManager.h"
47
48 @implementation SecuritydXPCServer (SecuritydXPCProtocol)
49
50 - (void) SecItemAddAndNotifyOnSync:(NSDictionary*) attributes
51 syncCallback:(id<SecuritydXPCCallbackProtocol>) callback
52 complete:(void (^) (NSDictionary* opDictResult, NSArray* opArrayResult, NSError* operror))xpcComplete
53 {
54 // The calling client might not handle CK types well. Sanitize!
55 void (^complete)(NSDictionary*, NSArray*, NSError*) = ^(NSDictionary* opDictResult, NSArray* opArrayResult, NSError* operror){
56 xpcComplete(opDictResult, opArrayResult, XPCSanitizeError(operror));
57 };
58
59 CFErrorRef cferror = NULL;
60 if([self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementKeychainDeny]) {
61 SecError(errSecNotAvailable, &cferror, CFSTR("SecItemAddAndNotifyOnSync: %@ has entitlement %@"), _client.task, kSecEntitlementKeychainDeny);
62 //TODO: ensure cferror can transit xpc
63 complete(NULL, NULL, (__bridge NSError*) cferror);
64 CFReleaseNull(cferror);
65 return;
66 }
67
68 if(attributes[(id)kSecAttrDeriveSyncIDFromItemAttributes] ||
69 attributes[(id)kSecAttrPCSPlaintextServiceIdentifier] ||
70 attributes[(id)kSecAttrPCSPlaintextPublicKey] ||
71 attributes[(id)kSecAttrPCSPlaintextPublicIdentity]) {
72
73 if(![self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementPrivateCKKSPlaintextFields]) {
74 SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemAddAndNotifyOnSync: %@ does not have entitlement %@, but is using SPI anyway"), _client.task, kSecEntitlementPrivateCKKSPlaintextFields);
75 complete(NULL, NULL, (__bridge NSError*) cferror);
76 CFReleaseNull(cferror);
77 return;
78 }
79 }
80
81 CFTypeRef cfresult = NULL;
82
83 NSMutableDictionary* callbackQuery = [attributes mutableCopy];
84 callbackQuery[@"f_ckkscallback"] = ^void (bool didSync, CFErrorRef syncerror) {
85 [callback callCallback: didSync error: (__bridge NSError*)syncerror];
86 };
87
88 _SecItemAdd((__bridge CFDictionaryRef) callbackQuery, &_client, &cfresult, &cferror);
89
90 //TODO: ensure cferror can transit xpc
91
92 // SecItemAdd returns Some CF Object, but NSXPC is pretty adamant that everything be a specific NS type. Split it up here:
93 if(!cfresult) {
94 complete(NULL, NULL, (__bridge NSError *)(cferror));
95 } else if( CFGetTypeID(cfresult) == CFDictionaryGetTypeID()) {
96 complete((__bridge NSDictionary *)(cfresult), NULL, (__bridge NSError *)(cferror));
97 } else if( CFGetTypeID(cfresult) == CFArrayGetTypeID()) {
98 complete(NULL, (__bridge NSArray *)cfresult, (__bridge NSError *)(cferror));
99 } else {
100 // TODO: actually error here
101 complete(NULL, NULL, NULL);
102 }
103 CFReleaseNull(cfresult);
104 CFReleaseNull(cferror);
105 }
106
107 - (void)secItemSetCurrentItemAcrossAllDevices:(NSData* _Nonnull)newItemPersistentRef
108 newCurrentItemHash:(NSData* _Nonnull)newItemSHA1
109 accessGroup:(NSString* _Nonnull)accessGroup
110 identifier:(NSString* _Nonnull)identifier
111 viewHint:(NSString* _Nonnull)viewHint
112 oldCurrentItemReference:(NSData* _Nullable)oldCurrentItemPersistentRef
113 oldCurrentItemHash:(NSData* _Nullable)oldItemSHA1
114 complete:(void (^) (NSError* _Nullable operror))xpcComplete
115 {
116 #if OCTAGON
117 // The calling client might not handle CK types well. Sanitize!
118 void (^complete)(NSError*) = ^(NSError* error){
119 xpcComplete(XPCSanitizeError(error));
120 };
121
122 __block CFErrorRef cferror = NULL;
123 if([self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementKeychainDeny]) {
124 SecError(errSecNotAvailable, &cferror, CFSTR("SecItemSetCurrentItemAcrossAllDevices: %@ has entitlement %@"), _client.task, kSecEntitlementKeychainDeny);
125 complete((__bridge NSError*) cferror);
126 CFReleaseNull(cferror);
127 return;
128 }
129
130 if(![self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementPrivateCKKSWriteCurrentItemPointers]) {
131 SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemSetCurrentItemAcrossAllDevices: %@ does not have entitlement %@"), _client.task, kSecEntitlementPrivateCKKSWriteCurrentItemPointers);
132 complete((__bridge NSError*) cferror);
133 CFReleaseNull(cferror);
134 return;
135 }
136
137 if (!accessGroupsAllows(self->_client.accessGroups, (__bridge CFStringRef)accessGroup, &_client)) {
138 SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemSetCurrentItemAcrossAllDevices: client is missing access-group %@: %@"), accessGroup, _client.task);
139 complete((__bridge NSError*)cferror);
140 CFReleaseNull(cferror);
141 return;
142 }
143
144 __block SecDbItemRef newItem = NULL;
145 __block SecDbItemRef oldItem = NULL;
146
147 bool ok = kc_with_dbt(false, &cferror, ^bool (SecDbConnectionRef dbt) {
148 // Use a DB transaction to gain synchronization with all CKKS zones.
149 return kc_transaction_type(dbt, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, ^bool {
150 Query *q = query_create_with_limit( (__bridge CFDictionaryRef) @{
151 (__bridge NSString *)kSecValuePersistentRef : newItemPersistentRef,
152 (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
153 },
154 NULL,
155 1,
156 &cferror);
157 if(cferror) {
158 secerror("couldn't create query for new item pref: %@", cferror);
159 return false;
160 }
161
162 if(!SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
163 newItem = CFRetainSafe(item);
164 })) {
165 query_destroy(q, NULL);
166 secerror("couldn't run query for new item pref: %@", cferror);
167 return false;
168 }
169
170 if(!query_destroy(q, &cferror)) {
171 secerror("couldn't destroy query for new item pref: %@", cferror);
172 return false;
173 };
174
175 if(oldCurrentItemPersistentRef) {
176 q = query_create_with_limit( (__bridge CFDictionaryRef) @{
177 (__bridge NSString *)kSecValuePersistentRef : oldCurrentItemPersistentRef,
178 (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
179 },
180 NULL,
181 1,
182 &cferror);
183 if(cferror) {
184 secerror("couldn't create query: %@", cferror);
185 return false;
186 }
187
188 if(!SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
189 oldItem = CFRetainSafe(item);
190 })) {
191 query_destroy(q, NULL);
192 secerror("couldn't run query for old item pref: %@", cferror);
193 return false;
194 }
195
196 if(!query_destroy(q, &cferror)) {
197 secerror("couldn't destroy query for old item pref: %@", cferror);
198 return false;
199 };
200 }
201
202 CKKSViewManager* manager = [CKKSViewManager manager];
203 if(!manager) {
204 secerror("SecItemSetCurrentItemAcrossAllDevices: no view manager?");
205 cferror = (CFErrorRef) CFBridgingRetain([NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"No view manager, cannot forward request"}]);
206 return false;
207 }
208 [manager setCurrentItemForAccessGroup:newItem
209 hash:newItemSHA1
210 accessGroup:accessGroup
211 identifier:identifier
212 viewHint:viewHint
213 replacing:oldItem
214 hash:oldItemSHA1
215 complete:complete];
216 return true;
217 });
218 });
219
220 CFReleaseNull(newItem);
221 CFReleaseNull(oldItem);
222
223 if(!ok) {
224 secnotice("ckks", "SecItemSetCurrentItemAcrossAllDevices failed due to: %@", cferror);
225 complete((__bridge NSError*) cferror);
226 }
227 CFReleaseNull(cferror);
228 #else // ! OCTAGON
229 xpcComplete([NSError errorWithDomain:@"securityd" code:errSecParam userInfo:@{NSLocalizedDescriptionKey: @"SecItemSetCurrentItemAcrossAllDevices not implemented on this platform"}]);
230 #endif // OCTAGON
231 }
232
233 -(void)secItemFetchCurrentItemAcrossAllDevices:(NSString*)accessGroup
234 identifier:(NSString*)identifier
235 viewHint:(NSString*)viewHint
236 fetchCloudValue:(bool)fetchCloudValue
237 complete:(void (^) (NSData* persistentref, NSError* operror))xpcComplete
238 {
239 #if OCTAGON
240 // The calling client might not handle CK types well. Sanitize!
241 void (^complete)(NSData*, NSError*) = ^(NSData* persistentref, NSError* error){
242 xpcComplete(persistentref, XPCSanitizeError(error));
243 };
244
245 CFErrorRef cferror = NULL;
246 if([self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementKeychainDeny]) {
247 SecError(errSecNotAvailable, &cferror, CFSTR("SecItemFetchCurrentItemAcrossAllDevices: %@ has entitlement %@"), _client.task, kSecEntitlementKeychainDeny);
248 complete(NULL, (__bridge NSError*) cferror);
249 CFReleaseNull(cferror);
250 return;
251 }
252
253 if(![self clientHasBooleanEntitlement: (__bridge NSString*) kSecEntitlementPrivateCKKSReadCurrentItemPointers]) {
254 SecError(errSecNotAvailable, &cferror, CFSTR("SecItemFetchCurrentItemAcrossAllDevices: %@ does not have entitlement %@"), _client.task, kSecEntitlementPrivateCKKSReadCurrentItemPointers);
255 complete(NULL, (__bridge NSError*) cferror);
256 CFReleaseNull(cferror);
257 return;
258 }
259
260 if (!accessGroupsAllows(self->_client.accessGroups, (__bridge CFStringRef)accessGroup, &_client)) {
261 SecError(errSecMissingEntitlement, &cferror, CFSTR("SecItemFetchCurrentItemAcrossAllDevices: client is missing access-group %@: %@"), accessGroup, _client.task);
262 complete(NULL, (__bridge NSError*)cferror);
263 CFReleaseNull(cferror);
264 return;
265 }
266
267 [[CKKSViewManager manager] getCurrentItemForAccessGroup:accessGroup
268 identifier:identifier
269 viewHint:viewHint
270 fetchCloudValue:fetchCloudValue
271 complete:^(NSString* uuid, NSError* error) {
272 if(error || !uuid) {
273 secnotice("ckkscurrent", "CKKS didn't find a current item for (%@,%@): %@ %@", accessGroup, identifier, uuid, error);
274 complete(NULL, error);
275 return;
276 }
277
278 // Find the persisent ref and return it.
279 secnotice("ckkscurrent", "CKKS believes current item UUID for (%@,%@) is %@. Looking up persistent ref...", accessGroup, identifier, uuid);
280 [self findItemPersistentRefByUUID:uuid complete:complete];
281 }];
282 #else // ! OCTAGON
283 xpcComplete(NULL, [NSError errorWithDomain:@"securityd" code:errSecParam userInfo:@{NSLocalizedDescriptionKey: @"SecItemFetchCurrentItemAcrossAllDevices not implemented on this platform"}]);
284 #endif // OCTAGON
285 }
286
287 -(void)findItemPersistentRefByUUID:(NSString*)uuid
288 complete:(void (^) (NSData* persistentref, NSError* operror))xpcComplete
289 {
290 // The calling client might not handle CK types well. Sanitize!
291 void (^complete)(NSData*, NSError*) = ^(NSData* persistentref, NSError* error){
292 xpcComplete(persistentref, XPCSanitizeError(error));
293 };
294
295 CFErrorRef cferror = NULL;
296 CFTypeRef result = NULL;
297
298 // Must query per-class, so:
299 const SecDbSchema *newSchema = current_schema();
300 for (const SecDbClass *const *class = newSchema->classes; *class != NULL; class++) {
301 if(!((*class)->itemclass)) {
302 //Don't try to search non-item 'classes'
303 continue;
304 }
305
306 // Now that we're in an item class, reset any errSecItemNotFound errors from the last item class
307 CFReleaseNull(result);
308 CFReleaseNull(cferror);
309
310 _SecItemCopyMatching((__bridge CFDictionaryRef) @{
311 (__bridge NSString*) kSecClass: (__bridge NSString*) (*class)->name,
312 (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny,
313 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
314 (id)kSecAttrUUID: uuid,
315 (id)kSecReturnPersistentRef: @YES,
316 },
317 &self->_client,
318 &result,
319 &cferror);
320
321 if(cferror && CFErrorGetCode(cferror) != errSecItemNotFound) {
322 break;
323 }
324
325 if(result) {
326 // Found the persistent ref! Quit searching.
327 break;
328 }
329 }
330
331 if(result && !cferror) {
332 secnotice("ckkscurrent", "Found current item for (%@)", uuid);
333 } else {
334 secerror("ckkscurrent: No current item for (%@): %@ %@", uuid, result, cferror);
335 }
336
337 complete((__bridge NSData*) result, (__bridge NSError*) cferror);
338 CFReleaseNull(result);
339 CFReleaseNull(cferror);
340 }
341
342 - (void) secItemDigest:(NSString *)itemClass
343 accessGroup:(NSString *)accessGroup
344 complete:(void (^)(NSArray *digest, NSError* error))complete
345 {
346 CFArrayRef accessGroups = self->_client.accessGroups;
347 __block CFErrorRef cferror = NULL;
348 __block CFArrayRef result = NULL;
349
350 if (itemClass == NULL || accessGroup == NULL) {
351 SecError(errSecParam, &cferror, CFSTR("parameter missing: %@"), _client.task);
352 complete(NULL, (__bridge NSError*) cferror);
353 CFReleaseNull(cferror);
354 return;
355 }
356
357 if (![itemClass isEqualToString:@"inet"] && ![itemClass isEqualToString:@"genp"]) {
358 SecError(errSecParam, &cferror, CFSTR("class %@ is not supported: %@"), itemClass, _client.task);
359 complete(NULL, (__bridge NSError*) cferror);
360 CFReleaseNull(cferror);
361 return;
362 }
363
364 if (!accessGroupsAllows(accessGroups, (__bridge CFStringRef)accessGroup, &_client)) {
365 SecError(errSecMissingEntitlement, &cferror, CFSTR("Client is missing access-group %@: %@"), accessGroup, _client.task);
366 complete(NULL, (__bridge NSError*) cferror);
367 CFReleaseNull(cferror);
368 return;
369 }
370
371 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, CFArrayGetCount(accessGroups)), CFSTR("*"))) {
372 /* Having the special accessGroup "*" allows access to all accessGroups. */
373 accessGroups = NULL;
374 }
375
376 NSDictionary *attributes = @{
377 (__bridge NSString *)kSecClass : itemClass,
378 (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
379 (__bridge NSString *)kSecAttrSynchronizable : (__bridge NSString *)kSecAttrSynchronizableAny,
380 };
381
382 Query *q = query_create_with_limit((__bridge CFDictionaryRef)attributes, _client.musr, 0, &cferror);
383 if (q == NULL) {
384 SecError(errSecParam, &cferror, CFSTR("failed to build query: %@"), _client.task);
385 complete(NULL, (__bridge NSError*) cferror);
386 CFReleaseNull(cferror);
387 return;
388 }
389
390 bool ok = kc_with_dbt(false, &cferror, ^(SecDbConnectionRef dbt) {
391 return (bool)s3dl_copy_digest(dbt, q, &result, accessGroups, &cferror);
392 });
393
394 (void)ok;
395
396 complete((__bridge NSArray *)result, (__bridge NSError *)cferror);
397
398 (void)query_destroy(q, &cferror);
399
400 CFReleaseNull(result);
401 CFReleaseNull(cferror);
402 }
403
404
405 @end