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