]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemDb.c
Security-59306.11.20.tar.gz
[apple/security.git] / OSX / sec / securityd / SecItemDb.c
1 /*
2 * Copyright (c) 2006-2014 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 /*
25 * SecItemDb.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
27 passwords.)
28 */
29
30 #if TARGET_DARWINOS
31 #undef OCTAGON
32 #undef SECUREOBJECTSYNC
33 #undef SHAREDWEBCREDENTIALS
34 #endif
35
36 #include <securityd/SecItemDb.h>
37 #include <utilities/SecAKSWrappers.h>
38
39 #include <securityd/SecDbKeychainItem.h>
40 #include <securityd/SecItemSchema.h>
41 #include <securityd/SecItemServer.h>
42 #include <Security/SecAccessControlPriv.h>
43 #include <Security/SecBasePriv.h>
44 #include <Security/SecItem.h>
45 #include <Security/SecSignpost.h>
46 #include <Security/SecItemPriv.h>
47 #include <Security/SecItemInternal.h>
48 #include <securityd/SOSCloudCircleServer.h>
49 #include <utilities/array_size.h>
50 #include <utilities/SecIOFormat.h>
51 #include <utilities/SecCFCCWrappers.h>
52 #include <uuid/uuid.h>
53 #include "sec_action.h"
54
55 #include "keychain/ckks/CKKS.h"
56
57 #define kSecBackupKeybagUUIDKey CFSTR("keybag-uuid")
58
59 const SecDbAttr *SecDbAttrWithKey(const SecDbClass *c,
60 CFTypeRef key,
61 CFErrorRef *error) {
62 /* Special case: identites can have all attributes of either cert
63 or keys. */
64 if (c == identity_class()) {
65 const SecDbAttr *desc;
66 if (!(desc = SecDbAttrWithKey(cert_class(), key, 0)))
67 desc = SecDbAttrWithKey(keys_class(), key, error);
68 return desc;
69 }
70
71 if (isString(key)) {
72 SecDbForEachAttr(c, a) {
73 if (CFEqual(a->name, key))
74 return a;
75 }
76 if (CFEqual(kSecUseDataProtectionKeychain, key)) {
77 return NULL; /* results in no ops for this attribute */
78 }
79 }
80
81 SecError(errSecNoSuchAttr, error, CFSTR("attribute %@ not found in class %@"), key, c->name);
82
83 return NULL;
84 }
85
86 bool kc_transaction(SecDbConnectionRef dbt, CFErrorRef *error, bool(^perform)(void)) {
87 return kc_transaction_type(dbt, kSecDbExclusiveTransactionType, error, perform);
88 }
89
90 bool kc_transaction_type(SecDbConnectionRef dbt, SecDbTransactionType type, CFErrorRef *error, bool(^perform)(void)) {
91 __block bool ok = true;
92 return ok && SecDbTransaction(dbt, type, error, ^(bool *commit) {
93 ok = *commit = perform();
94 });
95 }
96
97 static CFStringRef SecDbGetKindSQL(SecDbAttrKind kind) {
98 switch (kind) {
99 case kSecDbBlobAttr:
100 case kSecDbDataAttr:
101 case kSecDbUUIDAttr:
102 case kSecDbSHA1Attr:
103 case kSecDbPrimaryKeyAttr:
104 case kSecDbEncryptedDataAttr:
105 return CFSTR("BLOB");
106 case kSecDbAccessAttr:
107 case kSecDbStringAttr:
108 return CFSTR("TEXT");
109 case kSecDbNumberAttr:
110 case kSecDbSyncAttr:
111 case kSecDbTombAttr:
112 return CFSTR("INTEGER");
113 case kSecDbDateAttr:
114 case kSecDbCreationDateAttr:
115 case kSecDbModificationDateAttr:
116 return CFSTR("REAL");
117 case kSecDbRowIdAttr:
118 return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
119 case kSecDbAccessControlAttr:
120 case kSecDbUTombAttr:
121 /* This attribute does not exist in the DB. */
122 return NULL;
123 }
124 }
125
126 static void SecDbAppendUnique(CFMutableStringRef sql, CFStringRef value, bool *haveUnique) {
127 assert(haveUnique);
128 if (!*haveUnique)
129 CFStringAppend(sql, CFSTR("UNIQUE("));
130
131 SecDbAppendElement(sql, value, haveUnique);
132 }
133
134 static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql, const SecDbClass *c) {
135 CFStringAppendFormat(sql, 0, CFSTR("CREATE TABLE %@("), c->name);
136 SecDbForEachAttrWithMask(c,desc,kSecDbInFlag) {
137 CFStringAppendFormat(sql, 0, CFSTR("%@ %@"), desc->name, SecDbGetKindSQL(desc->kind));
138 if (desc->flags & kSecDbNotNullFlag)
139 CFStringAppend(sql, CFSTR(" NOT NULL"));
140 if (desc->flags & kSecDbDefault0Flag)
141 CFStringAppend(sql, CFSTR(" DEFAULT 0"));
142 if (desc->flags & kSecDbDefaultEmptyFlag)
143 CFStringAppend(sql, CFSTR(" DEFAULT ''"));
144 CFStringAppend(sql, CFSTR(","));
145 }
146
147 bool haveUnique = false;
148 SecDbForEachAttrWithMask(c,desc,kSecDbPrimaryKeyFlag | kSecDbInFlag) {
149 SecDbAppendUnique(sql, desc->name, &haveUnique);
150 }
151 if (haveUnique)
152 CFStringAppend(sql, CFSTR(")"));
153
154 CFStringAppend(sql, CFSTR(");"));
155
156 // Create indices
157 SecDbForEachAttrWithMask(c,desc, kSecDbIndexFlag | kSecDbInFlag) {
158 if (desc->kind == kSecDbSyncAttr) {
159 CFStringAppendFormat(sql, 0, CFSTR("CREATE INDEX %@%@0 ON %@(%@) WHERE %@=0;"), c->name, desc->name, c->name, desc->name, desc->name);
160 } else {
161 CFStringAppendFormat(sql, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c->name, desc->name, c->name, desc->name);
162 }
163 }
164 }
165
166 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql, const SecDbClass *c) {
167 CFStringAppendFormat(sql, 0, CFSTR("DROP TABLE %@;"), c->name);
168 }
169
170 static CFDataRef SecPersistentRefCreateWithItem(SecDbItemRef item, CFErrorRef *error) {
171 sqlite3_int64 row_id = SecDbItemGetRowId(item, error);
172 if (row_id)
173 return _SecItemCreatePersistentRef(SecDbItemGetClass(item)->name, row_id, item->attributes);
174 return NULL;
175 }
176
177 bool SecItemDbCreateSchema(SecDbConnectionRef dbt, const SecDbSchema *schema, CFArrayRef classIndexesForNewTables, bool includeVersion, CFErrorRef *error)
178 {
179 __block bool ok = true;
180 CFMutableStringRef sql = CFStringCreateMutable(kCFAllocatorDefault, 0);
181
182 if (classIndexesForNewTables) {
183 CFArrayForEach(classIndexesForNewTables, ^(const void* index) {
184 const SecDbClass* class = schema->classes[(int)index];
185 SecDbAppendCreateTableWithClass(sql, class);
186 });
187 }
188 else {
189 for (const SecDbClass * const *pclass = schema->classes; *pclass; ++pclass) {
190 SecDbAppendCreateTableWithClass(sql, *pclass);
191 }
192 }
193
194 if (includeVersion) {
195 CFStringAppendFormat(sql, NULL, CFSTR("INSERT INTO tversion(version,minor) VALUES(%d, %d);"),
196 schema->majorVersion, schema->minorVersion);
197 }
198 CFStringPerformWithCString(sql, ^(const char *sql_string) {
199 ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), sql_string, NULL, NULL, NULL),
200 SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), sql_string);
201 });
202 CFReleaseSafe(sql);
203 return ok;
204 }
205
206 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt, const SecDbSchema *schema, CFErrorRef *error)
207 {
208 __block bool ok = true;
209 CFMutableStringRef sql = CFStringCreateMutable(kCFAllocatorDefault, 0);
210 for (const SecDbClass * const *pclass = schema->classes; *pclass; ++pclass) {
211 SecDbAppendDropTableWithClass(sql, *pclass);
212 }
213 CFStringPerformWithCString(sql, ^(const char *sql_string) {
214 ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), sql_string, NULL, NULL, NULL),
215 SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), sql_string);
216 });
217 CFReleaseSafe(sql);
218 return ok;
219 }
220
221 CFTypeRef SecDbItemCopyResult(SecDbItemRef item, ReturnTypeMask return_type, CFErrorRef *error) {
222 CFTypeRef a_result;
223
224 if (return_type == 0) {
225 /* Caller isn't interested in any results at all. */
226 a_result = kCFNull;
227 } else if (return_type == kSecReturnDataMask) {
228 a_result = SecDbItemGetCachedValueWithName(item, kSecValueData);
229 if (a_result) {
230 CFRetainSafe(a_result);
231 } else {
232 a_result = CFDataCreate(kCFAllocatorDefault, NULL, 0);
233 }
234 } else if (return_type == kSecReturnPersistentRefMask) {
235 a_result = SecPersistentRefCreateWithItem(item, error);
236 } else {
237 CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item));
238 /* We need to return more than one value. */
239 if (return_type & kSecReturnRefMask) {
240 CFDictionarySetValue(dict, kSecClass, SecDbItemGetClass(item)->name);
241 }
242 CFOptionFlags mask = (((return_type & kSecReturnDataMask || return_type & kSecReturnRefMask) ? kSecDbReturnDataFlag : 0) |
243 ((return_type & kSecReturnAttributesMask || return_type & kSecReturnRefMask) ? kSecDbReturnAttrFlag : 0));
244 SecDbForEachAttr(SecDbItemGetClass(item), desc) {
245 if ((desc->flags & mask) != 0) {
246 CFTypeRef value = SecDbItemGetValue(item, desc, error);
247 if (value && !CFEqual(kCFNull, value)) {
248 CFDictionarySetValue(dict, desc->name, value);
249 } else if (value == NULL) {
250 CFReleaseNull(dict);
251 break;
252 }
253 }
254 }
255 CFDictionaryRemoveValue(dict, kSecAttrUUID);
256
257 if (return_type & kSecReturnPersistentRefMask) {
258 CFDataRef pref = SecPersistentRefCreateWithItem(item, error);
259 CFDictionarySetValue(dict, kSecValuePersistentRef, pref);
260 CFReleaseSafe(pref);
261 }
262
263 a_result = dict;
264 }
265
266 return a_result;
267 }
268
269 /* AUDIT[securityd](done):
270 attributes (ok) is a caller provided dictionary, only its cf type has
271 been checked.
272 */
273 bool
274 s3dl_query_add(SecDbConnectionRef dbt, Query *q, CFTypeRef *result, CFErrorRef *error)
275 {
276 if (query_match_count(q) != 0)
277 return errSecItemMatchUnsupported;
278
279 /* Add requires a class to be specified unless we are adding a ref. */
280 if (q->q_use_item_list)
281 return errSecUseItemListUnsupported;
282
283 /* Actual work here. */
284 SecDbItemRef item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, q->q_class, q->q_item, KEYBAG_DEVICE, error);
285 if (!item)
286 return false;
287 if (SecDbItemIsTombstone(item))
288 SecDbItemSetValue(item, &v7utomb, q->q_use_tomb ? q->q_use_tomb : kCFBooleanTrue, NULL);
289
290 bool ok = true;
291 if (q->q_data)
292 ok = SecDbItemSetValueWithName(item, CFSTR("v_Data"), q->q_data, error);
293 if (q->q_row_id)
294 ok = SecDbItemSetRowId(item, q->q_row_id, error);
295 if (q->q_musrView)
296 ok = SecDbItemSetValueWithName(item, CFSTR("musr"), q->q_musrView, error);
297 SecDbItemSetCredHandle(item, q->q_use_cred_handle);
298
299 #if OCTAGON
300 if(SecCKKSIsEnabled() && !SecCKKSTestDisableAutomaticUUID()) {
301 s3dl_item_make_new_uuid(item, q->q_uuid_from_primary_key, error);
302
303 if(q->q_add_sync_callback) {
304 CFTypeRef uuid = SecDbItemGetValue(item, &v10itemuuid, error);
305 if(uuid) {
306 CKKSRegisterSyncStatusCallback(uuid, q->q_add_sync_callback);
307 } else {
308 secerror("Couldn't fetch UUID from item; can't call callback");
309 }
310 }
311 }
312 #endif
313
314 if (ok)
315 ok = SecDbItemInsert(item, dbt, error);
316
317 if (ok) {
318 if (result && q->q_return_type) {
319 *result = SecDbItemCopyResult(item, q->q_return_type, error);
320 }
321 }
322 if (!ok && error && *error) {
323 if (CFEqual(CFErrorGetDomain(*error), kSecDbErrorDomain) && CFErrorGetCode(*error) == SQLITE_CONSTRAINT) {
324 CFReleaseNull(*error);
325 SecError(errSecDuplicateItem, error, CFSTR("duplicate item %@"), item);
326 } else if (CFEqual(CFErrorGetDomain(*error), kSecErrorDomain) && CFErrorGetCode(*error) == errSecDecode) { //handle situation when item have pdmn=akpu but passcode is not set
327 CFTypeRef value = SecDbItemGetValue(item, SecDbClassAttrWithKind(item->class, kSecDbAccessAttr, error), error);
328 if (value && CFEqual(value, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)) {
329 CFReleaseNull(*error);
330 SecError(errSecAuthFailed, error, CFSTR("authentication failed"));
331 }
332 }
333 }
334
335 if (ok) {
336 q->q_changed = true;
337 if (SecDbItemIsSyncable(item))
338 q->q_sync_changed = true;
339 }
340
341 secdebug("dbitem", "inserting item %@%s%@", item, ok ? "" : "failed: ", ok || error == NULL ? (CFErrorRef)CFSTR("") : *error);
342
343 CFRelease(item);
344
345 return ok;
346 }
347
348 bool s3dl_item_make_new_uuid(SecDbItemRef item, bool uuid_from_primary_key, CFErrorRef* error) {
349 if(!item) {
350 return false;
351 }
352
353 // Set the item UUID.
354 CFUUIDRef uuid = NULL;
355 // Were we asked to make the UUID static?
356 if (uuid_from_primary_key) {
357 // This UUID isn't used in any security mechanism, so we can
358 // just use the first bits of the SHA256 hash.
359 CFDataRef pkhash = SecDbKeychainItemCopySHA256PrimaryKey(item, error);
360 if(CFDataGetLength(pkhash) >= 16) {
361 UInt8 uuidBytes[16];
362 CFRange range = CFRangeMake(0, 16);
363 CFDataGetBytes(pkhash, range, uuidBytes);
364
365 uuid = CFUUIDCreateWithBytes(NULL,
366 uuidBytes[ 0],
367 uuidBytes[ 1],
368 uuidBytes[ 2],
369 uuidBytes[ 3],
370 uuidBytes[ 4],
371 uuidBytes[ 5],
372 uuidBytes[ 6],
373 uuidBytes[ 7],
374 uuidBytes[ 8],
375 uuidBytes[ 9],
376 uuidBytes[10],
377 uuidBytes[11],
378 uuidBytes[12],
379 uuidBytes[13],
380 uuidBytes[14],
381 uuidBytes[15]);
382 }
383 CFReleaseNull(pkhash);
384 }
385 if(uuid == NULL) {
386 uuid = CFUUIDCreate(NULL);
387 }
388 SecDbItemSetValueWithName(item, kSecAttrUUID, uuid, error);
389 CFReleaseNull(uuid);
390 return true;
391 }
392
393 typedef void (*s3dl_handle_row)(sqlite3_stmt *stmt, void *context);
394
395 static CFDataRef
396 s3dl_copy_data_from_col(sqlite3_stmt *stmt, int col, CFErrorRef *error) {
397 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt, col),
398 sqlite3_column_bytes(stmt, col),
399 kCFAllocatorNull);
400 }
401
402 static bool
403 s3dl_item_from_col(sqlite3_stmt *stmt, Query *q, int col, CFArrayRef accessGroups,
404 CFMutableDictionaryRef *item, SecAccessControlRef *access_control, keyclass_t* keyclass, CFErrorRef *error) {
405 CFDataRef edata = NULL;
406 bool ok = false;
407 require(edata = s3dl_copy_data_from_col(stmt, col, error), out);
408 ok = s3dl_item_from_data(edata, q, accessGroups, item, access_control, keyclass, error);
409
410 out:
411 CFReleaseSafe(edata);
412 return ok;
413 }
414
415 struct s3dl_query_ctx {
416 Query *q;
417 CFArrayRef accessGroups;
418 SecDbConnectionRef dbt;
419 CFTypeRef result;
420 int found;
421 };
422
423 /* Return whatever the caller requested based on the value of q->q_return_type.
424 keys and values must be 3 larger than attr_count in size to accomadate the
425 optional data, class and persistent ref results. This is so we can use
426 the CFDictionaryCreate() api here rather than appending to a
427 mutable dictionary. */
428 static CF_RETURNS_RETAINED CFTypeRef
429 handle_result(Query *q,
430 CFMutableDictionaryRef item,
431 sqlite_int64 rowid)
432 {
433 CFTypeRef a_result;
434 CFDataRef data;
435 data = CFDictionaryGetValue(item, kSecValueData);
436 CFDataRef pref = NULL;
437 if (q->q_return_type & kSecReturnPersistentRefMask) {
438 pref = _SecItemCreatePersistentRef(q->q_class->name, rowid, item);
439 }
440 if (q->q_return_type == 0) {
441 /* Caller isn't interested in any results at all. */
442 a_result = kCFNull;
443 } else if (q->q_return_type == kSecReturnDataMask) {
444 if (data) {
445 a_result = data;
446 CFRetain(a_result);
447 } else {
448 a_result = CFDataCreate(kCFAllocatorDefault, NULL, 0);
449 }
450 } else if (q->q_return_type == kSecReturnPersistentRefMask) {
451 a_result = _SecItemCreatePersistentRef(q->q_class->name, rowid, item);
452 } else {
453 /* We need to return more than one value. */
454 if (q->q_return_type & kSecReturnRefMask) {
455 CFDictionarySetValue(item, kSecClass, q->q_class->name);
456 } else if ((q->q_return_type & kSecReturnAttributesMask)) {
457 if (!(q->q_return_type & kSecReturnDataMask)) {
458 CFDictionaryRemoveValue(item, kSecValueData);
459 }
460
461 // Add any attributes which are supposed to be returned, are not present in the decrypted blob,
462 // and have a way to generate themselves.
463 SecDbItemRef itemRef = NULL;
464 SecDbForEachAttrWithMask(q->q_class, attr, kSecDbReturnAttrFlag) {
465 if(!CFDictionaryGetValue(item, attr->name) && attr->copyValue) {
466 CFErrorRef cferror = NULL;
467 if(!itemRef) {
468 itemRef = SecDbItemCreateWithAttributes(NULL, q->q_class, item, KEYBAG_DEVICE, &cferror);
469 }
470 if(!cferror && itemRef) {
471 if (attr->kind != kSecDbSHA1Attr || (q->q_return_type & kSecReturnDataMask)) { // we'll skip returning the sha1 attribute unless the client has also asked us to return data, because without data our sha1 could be invalid
472 CFTypeRef attrValue = attr->copyValue(itemRef, attr, &cferror);
473 if (!cferror && attrValue) {
474 CFDictionarySetValue(item, attr->name, attrValue);
475 }
476 CFReleaseNull(attrValue);
477 }
478 }
479 CFReleaseNull(cferror);
480 }
481 }
482 CFReleaseNull(itemRef);
483
484 CFDictionaryRemoveValue(item, kSecAttrUUID);
485 } else {
486 CFRetainSafe(data);
487 CFDictionaryRemoveAllValues(item);
488 if ((q->q_return_type & kSecReturnDataMask) && data) {
489 CFDictionarySetValue(item, kSecValueData, data);
490 }
491 CFReleaseSafe(data);
492 }
493 if (q->q_return_type & kSecReturnPersistentRefMask && pref != NULL) {
494 CFDictionarySetValue(item, kSecValuePersistentRef, pref);
495 }
496
497 a_result = item;
498 CFRetain(item);
499 }
500 CFReleaseSafe(pref);
501 return a_result;
502 }
503
504 static void s3dl_merge_into_dict(const void *key, const void *value, void *context) {
505 CFDictionarySetValue(context, key, value);
506 }
507
508 static bool checkTokenObjectID(CFDataRef token_object_id, CFDataRef value_data) {
509 bool equalOID = false;
510 require_quiet(value_data, out);
511 CFDictionaryRef itemValue = SecTokenItemValueCopy(value_data, NULL);
512 require_quiet(itemValue, out);
513 CFDataRef oID = CFDictionaryGetValue(itemValue, kSecTokenValueObjectIDKey);
514 equalOID = CFEqualSafe(token_object_id, oID);
515 CFRelease(itemValue);
516 out:
517 return equalOID;
518 }
519
520 static void s3dl_query_row(sqlite3_stmt *stmt, void *context) {
521 struct s3dl_query_ctx *c = context;
522 Query *q = c->q;
523 ReturnTypeMask saved_mask = q->q_return_type;
524
525 sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
526 CFMutableDictionaryRef item = NULL;
527 bool ok;
528
529 decode:
530 ok = s3dl_item_from_col(stmt, q, 1, c->accessGroups, &item, NULL, NULL, &q->q_error);
531 if (!ok) {
532 OSStatus status = SecErrorGetOSStatus(q->q_error);
533 // errSecDecode means the item is corrupted, stash it for delete.
534 if (status == errSecDecode) {
535 secwarning("ignoring corrupt %@,rowid=%" PRId64 " %@", q->q_class->name, rowid, q->q_error);
536 {
537 CFStringRef tablename = CFStringCreateCopy(kCFAllocatorDefault, q->q_class->name);
538 // Can't get rid of this item on the read path. Let's come back from elsewhere.
539 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
540 __block CFErrorRef localErr = NULL;
541 __block bool ok = true;
542 ok &= kc_with_dbt(true, &localErr, ^bool(SecDbConnectionRef dbt) {
543 CFStringRef sql = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("DELETE FROM %@ WHERE rowid=%lli"), tablename, rowid);
544 ok &= SecDbPrepare(dbt, sql, &localErr, ^(sqlite3_stmt *stmt) {
545 ok &= SecDbStep(dbt, stmt, &localErr, NULL);
546 });
547
548 if (!ok || localErr) {
549 secerror("Failed to delete corrupt item, %@ row %lli: %@", tablename, rowid, localErr);
550 } else {
551 secnotice("item", "Deleted corrupt rowid %lli from table %@", rowid, tablename);
552 }
553 CFReleaseNull(localErr);
554 CFReleaseNull(sql);
555 CFReleaseSafe(tablename);
556 return ok;
557 });
558 });
559
560 CFDataRef edata = s3dl_copy_data_from_col(stmt, 1, NULL);
561 CFMutableStringRef edatastring = CFStringCreateMutable(kCFAllocatorDefault, 0);
562 if(edatastring) {
563 CFStringAppendEncryptedData(edatastring, edata);
564 secnotice("item", "corrupted edata=%@", edatastring);
565 }
566 CFReleaseSafe(edata);
567 CFReleaseSafe(edatastring);
568 }
569 CFReleaseNull(q->q_error); // This item was never here, keep going
570 } else if (status == errSecAuthNeeded) {
571 secwarning("Authentication is needed for %@,rowid=%" PRId64 " (%" PRIdOSStatus "): %@", q->q_class->name, rowid, status, q->q_error);
572 } else if (status == errSecInteractionNotAllowed) {
573 static dispatch_once_t kclockedtoken;
574 static sec_action_t kclockedaction;
575 dispatch_once(&kclockedtoken, ^{
576 kclockedaction = sec_action_create("ratelimiterdisabledlogevent", 1);
577 sec_action_set_handler(kclockedaction, ^{
578 secerror("decode item failed, keychain is locked (%d)", (int)errSecInteractionNotAllowed);
579 });
580 });
581 sec_action_perform(kclockedaction);
582 } else if (status == errSecMissingEntitlement) {
583 // That's fine, let's pretend the item never existed for this query.
584 // We may find other, better items for the caller!
585 CFReleaseNull(q->q_error);
586 } else {
587 secerror("decode %@,rowid=%" PRId64 " failed (%" PRIdOSStatus "): %@", q->q_class->name, rowid, status, q->q_error);
588 }
589 // q->q_error will be released appropriately by a call to query_error
590 return;
591 }
592
593 if (!item)
594 goto out;
595
596 if (CFDictionaryContainsKey(item, kSecAttrTokenID) && (q->q_return_type & kSecReturnDataMask) == 0) {
597 // For token-based items, to get really meaningful set of attributes we must provide also data field, so augment mask
598 // and restart item decoding cycle.
599 q->q_return_type |= kSecReturnDataMask;
600 CFReleaseNull(item);
601 goto decode;
602 }
603
604 if (q->q_token_object_id != NULL && !checkTokenObjectID(q->q_token_object_id, CFDictionaryGetValue(item, kSecValueData)))
605 goto out;
606
607 if (q->q_class == identity_class()) {
608 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
609
610 CFMutableDictionaryRef key;
611 /* TODO : if there is a errSecDecode error here, we should cleanup */
612 if (!s3dl_item_from_col(stmt, q, 3, c->accessGroups, &key, NULL, NULL, &q->q_error) || !key)
613 goto out;
614
615 CFDataRef certData = CFDictionaryGetValue(item, kSecValueData);
616 if (certData) {
617 CFDictionarySetValue(key, kSecAttrIdentityCertificateData, certData);
618 CFDictionaryRemoveValue(item, kSecValueData);
619 }
620
621 CFDataRef certTokenID = CFDictionaryGetValue(item, kSecAttrTokenID);
622 if (certTokenID) {
623 CFDictionarySetValue(key, kSecAttrIdentityCertificateTokenID, certTokenID);
624 CFDictionaryRemoveValue(item, kSecAttrTokenID);
625 }
626 CFDictionaryApplyFunction(item, s3dl_merge_into_dict, key);
627 CFRelease(item);
628 item = key;
629 }
630
631 if (!match_item(c->dbt, q, c->accessGroups, item))
632 goto out;
633
634 CFTypeRef a_result = handle_result(q, item, rowid);
635 if (a_result) {
636 if (a_result == kCFNull) {
637 /* Caller wasn't interested in a result, but we still
638 count this row as found. */
639 CFRelease(a_result); // Help shut up clang
640 } else if (q->q_limit == 1) {
641 c->result = a_result;
642 } else {
643 CFArrayAppendValue((CFMutableArrayRef)c->result, a_result);
644 CFRelease(a_result);
645 }
646 c->found++;
647 }
648
649 out:
650 q->q_return_type = saved_mask;
651 CFReleaseSafe(item);
652 }
653
654 static void
655 SecDbAppendWhereROWID(CFMutableStringRef sql,
656 CFStringRef col, sqlite_int64 row_id,
657 bool *needWhere) {
658 if (row_id > 0) {
659 SecDbAppendWhereOrAnd(sql, needWhere);
660 CFStringAppendFormat(sql, NULL, CFSTR("%@=%lld"), col, row_id);
661 }
662 }
663
664 static void
665 SecDbAppendWhereAttrs(CFMutableStringRef sql, const Query *q, bool *needWhere) {
666 CFIndex ix, attr_count = query_attr_count(q);
667 for (ix = 0; ix < attr_count; ++ix) {
668 SecDbAppendWhereOrAndEquals(sql, query_attr_at(q, ix).key, needWhere);
669 }
670 }
671
672 static void
673 SecDbAppendWhereAccessGroups(CFMutableStringRef sql,
674 CFStringRef col,
675 CFArrayRef accessGroups,
676 bool *needWhere) {
677 CFIndex ix, ag_count;
678 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
679 return;
680 }
681
682 SecDbAppendWhereOrAnd(sql, needWhere);
683 CFStringAppend(sql, col);
684 CFStringAppend(sql, CFSTR(" IN (?"));
685 for (ix = 1; ix < ag_count; ++ix) {
686 CFStringAppend(sql, CFSTR(",?"));
687 }
688 CFStringAppend(sql, CFSTR(")"));
689 }
690
691 static bool
692 isQueryOverAllMUSRViews(CFTypeRef musrView)
693 {
694 return SecMUSRIsViewAllViews(musrView);
695 }
696
697 static bool
698 isQueryOverSingleUserView(CFTypeRef musrView)
699 {
700 return isNull(musrView);
701 }
702
703 #if TARGET_OS_IPHONE
704 static bool
705 isQueryOverBothUserAndSystem(CFTypeRef musrView, uid_t *uid)
706 {
707 return SecMUSRGetBothUserAndSystemUUID(musrView, uid);
708 }
709 #endif
710
711 static void
712 SecDbAppendWhereMusr(CFMutableStringRef sql,
713 const Query *q,
714 bool *needWhere)
715 {
716 SecDbAppendWhereOrAnd(sql, needWhere);
717
718 #if TARGET_OS_IPHONE
719 if (isQueryOverBothUserAndSystem(q->q_musrView, NULL)) {
720 CFStringAppend(sql, CFSTR("(musr = ? OR musr = ?)"));
721 } else
722 #endif
723 if (isQueryOverAllMUSRViews(q->q_musrView)) {
724 /* query over all items, regardless of view */
725 } else if (isQueryOverSingleUserView(q->q_musrView)) {
726 CFStringAppend(sql, CFSTR("musr = ?"));
727 } else {
728 CFStringAppend(sql, CFSTR("musr = ?"));
729 }
730 }
731
732 static void SecDbAppendWhereClause(CFMutableStringRef sql, const Query *q,
733 CFArrayRef accessGroups) {
734 bool needWhere = true;
735 SecDbAppendWhereROWID(sql, CFSTR("ROWID"), q->q_row_id, &needWhere);
736 SecDbAppendWhereAttrs(sql, q, &needWhere);
737 SecDbAppendWhereMusr(sql, q, &needWhere);
738 SecDbAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
739 }
740
741 static void SecDbAppendLimit(CFMutableStringRef sql, CFIndex limit) {
742 if (limit != kSecMatchUnlimited)
743 CFStringAppendFormat(sql, NULL, CFSTR(" LIMIT %" PRIdCFIndex), limit);
744 }
745
746 static CFStringRef s3dl_create_select_sql(Query *q, CFArrayRef accessGroups) {
747 CFMutableStringRef sql = CFStringCreateMutable(NULL, 0);
748 if (q->q_class == identity_class()) {
749 CFStringAppendFormat(sql, NULL, CFSTR("SELECT crowid, %@"
750 ", rowid,data FROM "
751 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
752 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
753 " keys.*,cert.data AS %@"
754 " FROM keys, cert"
755 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"),
756 kSecAttrIdentityCertificateData, kSecAttrIdentityCertificateData);
757 SecDbAppendWhereAccessGroups(sql, CFSTR("cert.agrp"), accessGroups, 0);
758 /* The next 3 SecDbAppendWhere calls are in the same order as in
759 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
760 as long as we do an extra sqlBindAccessGroups first. */
761 SecDbAppendWhereROWID(sql, CFSTR("crowid"), q->q_row_id, 0);
762 CFStringAppend(sql, CFSTR(")"));
763 bool needWhere = true;
764 SecDbAppendWhereAttrs(sql, q, &needWhere);
765 SecDbAppendWhereMusr(sql, q, &needWhere);
766 SecDbAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
767 } else {
768 CFStringAppend(sql, CFSTR("SELECT rowid, data FROM "));
769 CFStringAppend(sql, q->q_class->name);
770 SecDbAppendWhereClause(sql, q, accessGroups);
771 }
772 //do not append limit for all queries which needs filtering
773 if (q->q_match_issuer == NULL && q->q_match_policy == NULL && q->q_match_valid_on_date == NULL && q->q_match_trusted_only == NULL && q->q_token_object_id == NULL) {
774 SecDbAppendLimit(sql, q->q_limit);
775 }
776
777 return sql;
778 }
779
780 static bool sqlBindMusr(sqlite3_stmt *stmt, const Query *q, int *pParam, CFErrorRef *error) {
781 int param = *pParam;
782 bool result = true;
783 #if TARGET_OS_IPHONE
784 uid_t uid;
785
786 if (isQueryOverBothUserAndSystem(q->q_musrView, &uid)) {
787 /* network extensions are special and get to query both user and system views */
788 CFDataRef systemUUID = SecMUSRGetSystemKeychainUUID();
789 result = SecDbBindObject(stmt, param++, systemUUID, error);
790 if (result) {
791 CFDataRef activeUser = SecMUSRCreateActiveUserUUID(uid);
792 result = SecDbBindObject(stmt, param++, activeUser, error);
793 CFReleaseNull(activeUser);
794 }
795 } else
796 #endif
797 if (isQueryOverAllMUSRViews(q->q_musrView)) {
798 /* query over all items, regardless of view */
799 } else if (isQueryOverSingleUserView(q->q_musrView)) {
800 CFDataRef singleUUID = SecMUSRGetSingleUserKeychainUUID();
801 result = SecDbBindObject(stmt, param++, singleUUID, error);
802 } else {
803 result = SecDbBindObject(stmt, param++, q->q_musrView, error);
804 }
805
806 *pParam = param;
807 return result;
808 }
809
810
811 static bool sqlBindAccessGroups(sqlite3_stmt *stmt, CFArrayRef accessGroups,
812 int *pParam, CFErrorRef *error) {
813 bool result = true;
814 int param = *pParam;
815 CFIndex ix, count = accessGroups ? CFArrayGetCount(accessGroups) : 0;
816 for (ix = 0; ix < count; ++ix) {
817 result = SecDbBindObject(stmt, param++,
818 CFArrayGetValueAtIndex(accessGroups, ix),
819 error);
820 if (!result)
821 break;
822 }
823 *pParam = param;
824 return result;
825 }
826
827 static bool sqlBindWhereClause(sqlite3_stmt *stmt, const Query *q,
828 CFArrayRef accessGroups, int *pParam, CFErrorRef *error) {
829 bool result = true;
830 int param = *pParam;
831 CFIndex ix, attr_count = query_attr_count(q);
832 for (ix = 0; ix < attr_count; ++ix) {
833 result = SecDbBindObject(stmt, param++, query_attr_at(q, ix).value, error);
834 if (!result)
835 break;
836 }
837
838 if (result) {
839 result = sqlBindMusr(stmt, q, &param, error);
840 }
841
842 /* Bind the access group to the sql. */
843 if (result) {
844 result = sqlBindAccessGroups(stmt, accessGroups, &param, error);
845 }
846
847 *pParam = param;
848 return result;
849 }
850
851 bool SecDbItemQuery(SecDbQueryRef query, CFArrayRef accessGroups, SecDbConnectionRef dbconn, CFErrorRef *error,
852 void (^handle_row)(SecDbItemRef item, bool *stop)) {
853 __block bool ok = true;
854 /* Sanity check the query. */
855 if (query->q_ref)
856 return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by queries"));
857
858 bool (^return_attr)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
859 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
860 // "rowid, data"
861 return attr->kind == kSecDbRowIdAttr || attr->kind == kSecDbEncryptedDataAttr;
862 };
863
864 CFStringRef sql = s3dl_create_select_sql(query, accessGroups);
865 ok = sql;
866 if (sql) {
867 ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) {
868 /* Bind the values being searched for to the SELECT statement. */
869 int param = 1;
870 if (query->q_class == identity_class()) {
871 /* Bind the access groups to cert.agrp. */
872 ok &= sqlBindAccessGroups(stmt, accessGroups, &param, error);
873 }
874 if (ok)
875 ok &= sqlBindWhereClause(stmt, query, accessGroups, &param, error);
876 if (ok) {
877 SecDbStep(dbconn, stmt, error, ^(bool *stop) {
878 SecDbItemRef itemFromStatement = SecDbItemCreateWithStatement(kCFAllocatorDefault, query->q_class, stmt, query->q_keybag, error, return_attr);
879 if (itemFromStatement) {
880 CFTransferRetained(itemFromStatement->credHandle, query->q_use_cred_handle);
881 if (match_item(dbconn, query, accessGroups, itemFromStatement->attributes))
882 handle_row(itemFromStatement, stop);
883 CFReleaseNull(itemFromStatement);
884 } else {
885 secerror("failed to create item from stmt: %@", error ? *error : (CFErrorRef)"no error");
886 if (error) {
887 CFReleaseNull(*error);
888 }
889 //*stop = true;
890 //ok = false;
891 }
892 });
893 }
894 });
895 CFRelease(sql);
896 }
897
898 return ok;
899 }
900
901 static bool
902 s3dl_query(s3dl_handle_row handle_row,
903 void *context, CFErrorRef *error)
904 {
905 struct s3dl_query_ctx *c = context;
906 SecDbConnectionRef dbt = c->dbt;
907 Query *q = c->q;
908 CFArrayRef accessGroups = c->accessGroups;
909
910 /* Sanity check the query. */
911 if (q->q_ref)
912 return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by queries"));
913
914 /* Actual work here. */
915 if (q->q_limit == 1) {
916 c->result = NULL;
917 } else {
918 c->result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
919 }
920 CFStringRef sql = s3dl_create_select_sql(q, accessGroups);
921 bool ok = SecDbWithSQL(dbt, sql, error, ^(sqlite3_stmt *stmt) {
922 bool sql_ok = true;
923 /* Bind the values being searched for to the SELECT statement. */
924 int param = 1;
925 if (q->q_class == identity_class()) {
926 /* Bind the access groups to cert.agrp. */
927 sql_ok = sqlBindAccessGroups(stmt, accessGroups, &param, error);
928 }
929 if (sql_ok)
930 sql_ok = sqlBindWhereClause(stmt, q, accessGroups, &param, error);
931 if (sql_ok) {
932 SecDbForEach(dbt, stmt, error, ^bool (int row_index) {
933 handle_row(stmt, context);
934
935 bool needs_auth = q->q_error && CFErrorGetCode(q->q_error) == errSecAuthNeeded;
936 if (q->q_skip_acl_items && needs_auth)
937 // Skip items needing authentication if we are told to do so.
938 CFReleaseNull(q->q_error);
939
940 bool stop = q->q_limit != kSecMatchUnlimited && c->found >= q->q_limit;
941 stop = stop || (q->q_error && !needs_auth);
942 return !stop;
943 });
944 }
945 return sql_ok;
946 });
947
948 CFRelease(sql);
949
950 // First get the error from the query, since errSecDuplicateItem from an
951 // update query should superceed the errSecItemNotFound below.
952 if (!query_error(q, error))
953 ok = false;
954 if (ok && c->found == 0) {
955 ok = SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
956 if (q->q_spindump_on_failure) {
957 __security_stackshotreport(CFSTR("ItemNotFound"), __sec_exception_code_LostInMist);
958 }
959 }
960
961 return ok;
962 }
963
964 bool
965 s3dl_copy_matching(SecDbConnectionRef dbt, Query *q, CFTypeRef *result,
966 CFArrayRef accessGroups, CFErrorRef *error)
967 {
968 struct s3dl_query_ctx ctx = {
969 .q = q, .accessGroups = accessGroups, .dbt = dbt,
970 };
971 if (q->q_row_id && query_attr_count(q))
972 return SecError(errSecItemIllegalQuery, error,
973 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
974 if (q->q_token_object_id && query_attr_count(q) != 1)
975 return SecError(errSecItemIllegalQuery, error,
976 CFSTR("attributes to query illegal; both token persitent ref and other attributes can't be searched at the same time"));
977
978 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
979 if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
980 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
981 bool ok = s3dl_query(s3dl_query_row, &ctx, error);
982 if (ok && result)
983 *result = ctx.result;
984 else
985 CFReleaseSafe(ctx.result);
986
987 return ok;
988 }
989
990 typedef void (^s3dl_item_digest_callback)(CFDataRef persistantReference, CFDataRef encryptedData);
991
992 struct s3dl_digest_ctx {
993 Query *q;
994 SecDbConnectionRef dbt;
995 s3dl_item_digest_callback item_callback;
996 };
997
998 static void s3dl_query_row_digest(sqlite3_stmt *stmt, void *context) {
999 struct s3dl_query_ctx *c = context;
1000 Query *q = c->q;
1001
1002 sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
1003 CFDataRef edata = s3dl_copy_data_from_col(stmt, 1, NULL);
1004 CFDataRef persistant_reference = _SecItemCreatePersistentRef(q->q_class->name, rowid, NULL);
1005 CFDataRef digest = NULL;
1006
1007 if (edata) {
1008 digest = CFDataCopySHA256Digest(edata, NULL);
1009 }
1010
1011 if (digest && persistant_reference) {
1012 CFDictionaryRef item = CFDictionaryCreateForCFTypes(NULL,
1013 kSecValuePersistentRef, persistant_reference,
1014 kSecValueData, digest,
1015 NULL);
1016 if (item)
1017 CFArrayAppendValue((CFMutableArrayRef)c->result, item);
1018 CFReleaseNull(item);
1019 c->found++;
1020 } else {
1021 secinfo("item", "rowid %lu in %@ failed to create pref/digest", (unsigned long)rowid, q->q_class->name);
1022 }
1023 CFReleaseNull(digest);
1024 CFReleaseNull(edata);
1025 CFReleaseNull(persistant_reference);
1026 }
1027
1028
1029 bool
1030 s3dl_copy_digest(SecDbConnectionRef dbt, Query *q, CFArrayRef *result, CFArrayRef accessGroups, CFErrorRef *error)
1031 {
1032 struct s3dl_query_ctx ctx = {
1033 .q = q, .dbt = dbt, .accessGroups = accessGroups,
1034 };
1035 // Force to always return an array
1036 q->q_limit = kSecMatchUnlimited;
1037 // This interface only queries live data
1038 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
1039 bool ok = s3dl_query(s3dl_query_row_digest, &ctx, error);
1040 if (ok && result)
1041 *result = (CFArrayRef)ctx.result;
1042 else
1043 CFReleaseSafe(ctx.result);
1044
1045 return ok;
1046 }
1047
1048 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
1049 static void query_set_attribute_with_desc(const SecDbAttr *desc, const void *value, Query *q) {
1050 if (CFDictionaryContainsKey(q->q_item, desc->name)) {
1051 CFIndex ix;
1052 for (ix = 0; ix < q->q_attr_end; ++ix) {
1053 if (CFEqual(desc->name, q->q_pairs[ix].key)) {
1054 CFReleaseSafe(q->q_pairs[ix].value);
1055 --q->q_attr_end;
1056 for (; ix < q->q_attr_end; ++ix) {
1057 q->q_pairs[ix] = q->q_pairs[ix + 1];
1058 }
1059 CFDictionaryRemoveValue(q->q_item, desc->name);
1060 break;
1061 }
1062 }
1063 }
1064 query_add_attribute_with_desc(desc, value, q);
1065 }
1066
1067 /* Update modification_date if needed. */
1068 static void query_pre_update(Query *q) {
1069 SecDbForEachAttr(q->q_class, desc) {
1070 if (desc->kind == kSecDbModificationDateAttr) {
1071 CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
1072 query_set_attribute_with_desc(desc, now, q);
1073 CFReleaseSafe(now);
1074 }
1075 }
1076 }
1077
1078 /* Make sure all attributes that are marked as not_null have a value. If
1079 force_date is false, only set mdat and cdat if they aren't already set. */
1080 void query_pre_add(Query *q, bool force_date) {
1081 CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
1082 SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInFlag) {
1083 if (desc->kind == kSecDbCreationDateAttr ||
1084 desc->kind == kSecDbModificationDateAttr) {
1085 if (force_date) {
1086 query_set_attribute_with_desc(desc, now, q);
1087 } else if (!CFDictionaryContainsKey(q->q_item, desc->name)) {
1088 query_add_attribute_with_desc(desc, now, q);
1089 }
1090 } else if ((desc->flags & kSecDbNotNullFlag) &&
1091 !CFDictionaryContainsKey(q->q_item, desc->name)) {
1092 CFTypeRef value = NULL;
1093 if (desc->flags & kSecDbDefault0Flag) {
1094 if (desc->kind == kSecDbDateAttr)
1095 value = CFDateCreate(kCFAllocatorDefault, 0.0);
1096 else {
1097 SInt32 vzero = 0;
1098 value = CFNumberCreate(0, kCFNumberSInt32Type, &vzero);
1099 }
1100 } else if (desc->flags & kSecDbDefaultEmptyFlag) {
1101 if (desc->kind == kSecDbDataAttr || desc->kind == kSecDbUUIDAttr)
1102 value = CFDataCreate(kCFAllocatorDefault, NULL, 0);
1103 else {
1104 value = CFSTR("");
1105 CFRetain(value);
1106 }
1107 }
1108 if (value) {
1109 /* Safe to use query_add_attribute here since the attr wasn't
1110 set yet. */
1111 query_add_attribute_with_desc(desc, value, q);
1112 CFRelease(value);
1113 }
1114 }
1115 }
1116 CFReleaseSafe(now);
1117 }
1118
1119 // Return a tri state value false->never make a tombstone, true->always make a
1120 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
1121 static CFBooleanRef s3dl_should_make_tombstone(Query *q, bool item_is_syncable, SecDbItemRef item) {
1122 if (q->q_use_tomb)
1123 return q->q_use_tomb;
1124 else if (item_is_syncable && !SecDbItemIsTombstone(item))
1125 return NULL;
1126 else
1127 return kCFBooleanFalse;
1128 }
1129 /* AUDIT[securityd](done):
1130 attributesToUpdate (ok) is a caller provided dictionary,
1131 only its cf types have been checked.
1132 */
1133 bool
1134 s3dl_query_update(SecDbConnectionRef dbt, Query *q,
1135 CFDictionaryRef attributesToUpdate, CFArrayRef accessGroups, CFErrorRef *error)
1136 {
1137 /* Sanity check the query. */
1138 if (query_match_count(q) != 0)
1139 return SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported in attributes to update"));
1140 if (q->q_ref)
1141 return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported in attributes to update"));
1142 if (q->q_row_id && query_attr_count(q))
1143 return SecError(errSecItemIllegalQuery, error, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
1144 if (q->q_token_object_id && query_attr_count(q) != 1)
1145 return SecError(errSecItemIllegalQuery, error, CFSTR("attributes to update illegal; both token persistent ref and other attributes can't be updated at the same time"));
1146
1147 __block bool result = true;
1148 Query *u = query_create(q->q_class, NULL, attributesToUpdate, error);
1149 if (u == NULL) return false;
1150 require_action_quiet(query_update_parse(u, attributesToUpdate, error), errOut, result = false);
1151 query_pre_update(u);
1152 result &= SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
1153 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
1154 if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
1155 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
1156 result &= SecDbItemQuery(q, accessGroups, dbt, error, ^(SecDbItemRef item, bool *stop) {
1157 // We always need to know the error here.
1158 CFErrorRef localError = NULL;
1159 if (q->q_token_object_id) {
1160 const SecDbAttr *valueDataAttr = SecDbClassAttrWithKind(item->class, kSecDbDataAttr, NULL);
1161 CFDataRef valueData = SecDbItemGetValue(item, valueDataAttr, NULL);
1162 if (q->q_token_object_id != NULL && !checkTokenObjectID(q->q_token_object_id, valueData))
1163 return;
1164 }
1165 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
1166 const SecDbAttr *sha1attr = SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, NULL);
1167 CFDataRef storedSHA1 = CFRetainSafe(SecDbItemGetValue(item, sha1attr, NULL));
1168 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, u->q_item, &localError);
1169 SecDbItemSetValue(item, sha1attr, storedSHA1, NULL);
1170 CFReleaseSafe(storedSHA1);
1171 if (SecErrorGetOSStatus(localError) == errSecDecode) {
1172 // We just ignore this, and treat as if item is not found.
1173 secwarning("deleting corrupt %@,rowid=%" PRId64 " %@", q->q_class->name, SecDbItemGetRowId(item, NULL), localError);
1174 CFReleaseNull(localError);
1175 if (!SecDbItemDelete(item, dbt, false, &localError)) {
1176 secerror("failed to delete corrupt %@,rowid=%" PRId64 " %@", q->q_class->name, SecDbItemGetRowId(item, NULL), localError);
1177 CFReleaseNull(localError);
1178 }
1179 CFReleaseNull(new_item);
1180 return;
1181 }
1182 if (new_item != NULL && u->q_access_control != NULL)
1183 SecDbItemSetAccessControl(new_item, u->q_access_control, &localError);
1184 result = SecErrorPropagate(localError, error) && new_item;
1185 if (new_item) {
1186 bool item_is_sync = SecDbItemIsSyncable(item);
1187 result = SecDbItemUpdate(item, new_item, dbt, s3dl_should_make_tombstone(q, item_is_sync, item), q->q_uuid_from_primary_key, error);
1188 if (result) {
1189 q->q_changed = true;
1190 if (item_is_sync || SecDbItemIsSyncable(new_item))
1191 q->q_sync_changed = true;
1192 }
1193 CFRelease(new_item);
1194 }
1195 });
1196 if (!result)
1197 *commit = false;
1198 });
1199 if (result && !q->q_changed)
1200 result = SecError(errSecItemNotFound, error, CFSTR("No items updated"));
1201 errOut:
1202 if (!query_destroy(u, error))
1203 result = false;
1204 return result;
1205 }
1206
1207 static bool SecDbItemNeedAuth(SecDbItemRef item, CFErrorRef *error)
1208 {
1209 CFErrorRef localError = NULL;
1210 if (!SecDbItemEnsureDecrypted(item, true, &localError) && localError && CFErrorGetCode(localError) == errSecAuthNeeded) {
1211 if (error)
1212 *error = localError;
1213 return true;
1214 }
1215
1216 CFReleaseSafe(localError);
1217 return false;
1218 }
1219
1220 bool
1221 s3dl_query_delete(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFErrorRef *error)
1222 {
1223 __block bool ok = true;
1224 __block bool needAuth = false;
1225 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
1226 if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
1227 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
1228 ok &= SecDbItemSelect(q, dbt, error, NULL, ^bool(const SecDbAttr *attr) {
1229 return false;
1230 },^bool(CFMutableStringRef sql, bool *needWhere) {
1231 SecDbAppendWhereClause(sql, q, accessGroups);
1232 return true;
1233 },^bool(sqlite3_stmt * stmt, int col) {
1234 return sqlBindWhereClause(stmt, q, accessGroups, &col, error);
1235 }, ^(SecDbItemRef item, bool *stop) {
1236 // Check if item for token persitence ref
1237 if (q->q_token_object_id) {
1238 const SecDbAttr *valueDataAttr = SecDbClassAttrWithKind(item->class, kSecDbDataAttr, NULL);
1239 CFDataRef valueData = SecDbItemGetValue(item, valueDataAttr, NULL);
1240 if (q->q_token_object_id != NULL && !checkTokenObjectID(q->q_token_object_id, valueData))
1241 return;
1242 }
1243 // Check if item need to be authenticated by LocalAuthentication
1244 item->cryptoOp = kAKSKeyOpDelete;
1245 if (SecDbItemNeedAuth(item, error)) {
1246 needAuth = true;
1247 return;
1248 }
1249 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
1250 const SecDbAttr *sha1attr = SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, NULL);
1251 CFDataRef storedSHA1 = CFRetainSafe(SecDbItemGetValue(item, sha1attr, NULL));
1252 bool item_is_sync = SecDbItemIsSyncable(item);
1253 SecDbItemSetValue(item, sha1attr, storedSHA1, NULL);
1254 CFReleaseSafe(storedSHA1);
1255 ok = SecDbItemDelete(item, dbt, s3dl_should_make_tombstone(q, item_is_sync, item), error);
1256 if (ok) {
1257 q->q_changed = true;
1258 if (item_is_sync)
1259 q->q_sync_changed = true;
1260 }
1261 });
1262 if (ok && !q->q_changed && !needAuth) {
1263 ok = SecError(errSecItemNotFound, error, CFSTR("Delete failed to delete anything"));
1264 }
1265 return ok && !needAuth;
1266 }
1267
1268 static bool
1269 matchAnyString(CFStringRef needle, CFStringRef *haystack)
1270 {
1271 while (*haystack) {
1272 if (CFEqual(needle, *haystack))
1273 return true;
1274 haystack++;
1275 }
1276 return false;
1277 }
1278
1279 /* Return true iff the item in question should not be backed up, nor restored,
1280 but when restoring a backup the original version of the item should be
1281 added back to the keychain again after the restore completes. */
1282 bool SecItemIsSystemBound(CFDictionaryRef item, const SecDbClass *cls, bool multiUser) {
1283 CFNumberRef sysb = CFDictionaryGetValue(item, kSecAttrSysBound);
1284 if (isNumber(sysb)) {
1285 int32_t num = 0;
1286 if (!CFNumberGetValue(sysb, kCFNumberSInt32Type, &num))
1287 return false;
1288 if (num == kSecSecAttrSysBoundNot) {
1289 return false;
1290 } else if (num == kSecSecAttrSysBoundPreserveDuringRestore) {
1291 return true;
1292 }
1293 return true;
1294 }
1295
1296 CFStringRef agrp = CFDictionaryGetValue(item, kSecAttrAccessGroup);
1297 if (!isString(agrp))
1298 return false;
1299
1300 if (CFEqualSafe(agrp, kSOSInternalAccessGroup)) {
1301 secdebug("backup", "found sysbound item: %@", item);
1302 return true;
1303 }
1304
1305 if (CFEqual(agrp, CFSTR("lockdown-identities"))) {
1306 secdebug("backup", "found sys_bound item: %@", item);
1307 return true;
1308 }
1309
1310 if (CFEqual(agrp, CFSTR("apple")) && cls == genp_class()) {
1311 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1312 CFStringRef account = CFDictionaryGetValue(item, kSecAttrAccount);
1313
1314 if (isString(service) && isString(account)) {
1315 static CFStringRef mcAccounts[] = {
1316 CFSTR("Public"),
1317 CFSTR("Private"),
1318 NULL,
1319 };
1320
1321 if (CFEqual(service, CFSTR("com.apple.managedconfiguration"))
1322 && matchAnyString(account, mcAccounts))
1323 {
1324 secdebug("backup", "found sys_bound item: %@", item);
1325 return true;
1326 }
1327 }
1328
1329 if (isString(service) && CFEqual(service, CFSTR("com.apple.account.CloudKit.token"))) {
1330 secdebug("backup", "found sys_bound item: %@", item);
1331 return true;
1332 }
1333
1334 if (isString(service) && CFEqual(service, CFSTR("com.apple.account.idms.continuation-key"))) {
1335 secdebug("backup", "found sys_bound item: %@", item);
1336 return true;
1337 }
1338 }
1339
1340 if (multiUser && CFEqual(agrp, CFSTR("com.apple.apsd")) && cls == genp_class()) {
1341 static CFStringRef pushServices[] = {
1342 CFSTR("push.apple.com"),
1343 CFSTR("push.apple.com,PerAppToken.v0"),
1344 NULL
1345 };
1346 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1347
1348 if (isString(service) && matchAnyString(service, pushServices)) {
1349 secdebug("backup", "found sys_bound item: %@", item);
1350 return true;
1351 }
1352 }
1353
1354 if (multiUser && CFEqual(agrp, CFSTR("appleaccount")) && cls == genp_class()) {
1355 static CFStringRef accountServices[] = {
1356 CFSTR("com.apple.appleaccount.fmf.token"), /* temporary tokens while accout is being setup */
1357 CFSTR("com.apple.appleaccount.fmf.apptoken"),
1358 CFSTR("com.apple.appleaccount.fmip.siritoken"),
1359 CFSTR("com.apple.appleaccount.cloudkit.token"),
1360 NULL
1361 };
1362 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1363
1364 if (isString(service) && matchAnyString(service, accountServices)) {
1365 secdebug("backup", "found exact sys_bound item: %@", item);
1366 return true;
1367 }
1368 }
1369
1370 if (multiUser && CFEqual(agrp, CFSTR("apple")) && cls == genp_class()) {
1371 static CFStringRef accountServices[] = {
1372 /* accounts, remove with rdar://37595482 */
1373 CFSTR("com.apple.account.AppleAccount.token"),
1374 CFSTR("com.apple.account.AppleAccount.password"),
1375 CFSTR("com.apple.account.AppleAccount.rpassword"),
1376 CFSTR("com.apple.account.idms.token"),
1377 CFSTR("com.apple.account.idms.heartbeat-token"),
1378 CFSTR("com.apple.account.idms.continuation-key"),
1379 CFSTR("com.apple.account.CloudKit.token"),
1380 CFSTR("com.apple.account.IdentityServices.password"), /* accountsd for ids */
1381 CFSTR("com.apple.account.IdentityServices.rpassword"),
1382 CFSTR("com.apple.account.IdentityServices.token"),
1383 /* IDS stuff */
1384 CFSTR("BackupIDSAccountToken"),
1385 CFSTR("com.apple.ids"),
1386 CFSTR("ids"),
1387 CFSTR("IDS"),
1388 NULL
1389 };
1390 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1391
1392 if (isString(service) && matchAnyString(service, accountServices)) {
1393 secdebug("backup", "found exact sys_bound item: %@", item);
1394 return true;
1395 }
1396 if (isString(service) && CFStringHasPrefix(service, CFSTR("com.apple.gs."))) {
1397 secdebug("backup", "found exact sys_bound item: %@", item);
1398 return true;
1399 }
1400 if (isString(service) && CFEqual(service, CFSTR("com.apple.facetime"))) {
1401 CFStringRef account = CFDictionaryGetValue(item, kSecAttrAccount);
1402 if (isString(account) && CFEqual(account, CFSTR("registrationV1"))) {
1403 secdebug("backup", "found exact sys_bound item: %@", item);
1404 return true;
1405 }
1406 }
1407 }
1408
1409 /* accounts, remove with rdar://37595482 */
1410 if (multiUser && CFEqual(agrp, CFSTR("com.apple.ind")) && cls == genp_class()) {
1411 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1412 if (isString(service) && CFEqual(service, CFSTR("com.apple.ind.registration"))) {
1413 secdebug("backup", "found exact sys_bound item: %@", item);
1414 return true;
1415 }
1416 }
1417
1418 if (multiUser && CFEqual(agrp, CFSTR("ichat")) && cls == genp_class()) {
1419 static CFStringRef accountServices[] = {
1420 CFSTR("ids"),
1421 NULL
1422 };
1423 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1424
1425 if (isString(service) && matchAnyString(service, accountServices)) {
1426 secdebug("backup", "found exact sys_bound item: %@", item);
1427 return true;
1428 }
1429 }
1430
1431 if (multiUser && CFEqual(agrp, CFSTR("ichat")) && cls == keys_class()) {
1432 static CFStringRef exactMatchingLabel[] = {
1433 CFSTR("iMessage Encryption Key"),
1434 CFSTR("iMessage Signing Key"),
1435 };
1436 CFStringRef label = CFDictionaryGetValue(item, kSecAttrLabel);
1437 if (isString(label)) {
1438 if (matchAnyString(label, exactMatchingLabel)) {
1439 secdebug("backup", "found exact sys_bound item: %@", item);
1440 return true;
1441 }
1442 }
1443 }
1444
1445 if (multiUser && CFEqual(agrp, CFSTR("com.apple.rapport")) && cls == genp_class()) {
1446 secdebug("backup", "found exact sys_bound item: %@", item);
1447 return true;
1448 }
1449
1450 secdebug("backup", "found non sys_bound item: %@", item);
1451 return false;
1452 }
1453
1454 /* Delete all items from the current keychain. If this is not an in
1455 place upgrade we don't delete items in the 'lockdown-identities'
1456 access group, this ensures that an import or restore of a backup
1457 will never overwrite an existing activation record. */
1458 static bool SecServerDeleteAll(SecDbConnectionRef dbt, CFErrorRef *error) {
1459 secwarning("SecServerDeleteAll");
1460
1461 return kc_transaction(dbt, error, ^{
1462
1463 bool ok = (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
1464 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
1465 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
1466 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
1467 return ok;
1468 });
1469 }
1470
1471 #if TARGET_OS_IPHONE
1472
1473 static bool DeleteAllFromTableForMUSRView(SecDbConnectionRef dbt,
1474 CFStringRef sql,
1475 CFDataRef musr,
1476 bool keepU,
1477 CFErrorRef *error)
1478 {
1479 sqlite3_stmt *stmt = NULL;
1480 CFStringRef sql2 = NULL;
1481 bool ok = false;
1482
1483 if (keepU) {
1484 sql2 = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ AND pdmn NOT IN ('aku','akpu','cku','dku')"), sql);
1485 } else {
1486 sql2 = CFRetain(sql);
1487 }
1488 require(sql2, fail);
1489
1490 stmt = SecDbCopyStmt(dbt, sql2, NULL, error);
1491 require(stmt, fail);
1492
1493 ok = SecDbBindObject(stmt, 1, musr, error);
1494 require(ok, fail);
1495
1496 ok = SecDbStep(dbt, stmt, error, ^(bool *stop) { });
1497 require(ok, fail);
1498
1499 fail:
1500 if (stmt) {
1501 ok = SecDbFinalize(stmt, error);
1502 }
1503 if (!ok)
1504 secwarning("DeleteAllFromTableForMUSRView failed for %@ for musr: %@: %@", sql2, musr, error ? *error : NULL);
1505
1506 CFReleaseNull(sql2);
1507
1508 return ok;
1509 }
1510
1511 bool SecServerDeleteAllForUser(SecDbConnectionRef dbt, CFDataRef musrView, bool keepU, CFErrorRef *error) {
1512 secwarning("SecServerDeleteAllForUser for user: %@ keepU %s", musrView, keepU ? "yes" : "no");
1513
1514 return kc_transaction(dbt, error, ^{
1515 bool ok;
1516
1517 ok = (DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM genp WHERE musr = ?"), musrView, keepU, error) &&
1518 DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM inet WHERE musr = ?"), musrView, keepU, error) &&
1519 DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM cert WHERE musr = ?"), musrView, keepU, error) &&
1520 DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM keys WHERE musr = ?"), musrView, keepU, error));
1521
1522 return ok;
1523 });
1524 }
1525 #endif
1526
1527
1528 struct s3dl_export_row_ctx {
1529 struct s3dl_query_ctx qc;
1530 keybag_handle_t dest_keybag;
1531 enum SecItemFilter filter;
1532 bool multiUser;
1533 };
1534
1535 static void s3dl_export_row(sqlite3_stmt *stmt, void *context) {
1536 struct s3dl_export_row_ctx *c = context;
1537 Query *q = c->qc.q;
1538 SecAccessControlRef access_control = NULL;
1539 CFErrorRef localError = NULL;
1540
1541 /* Skip akpu items when backing up, those are intentionally lost across restores. The same applies to SEP-based keys */
1542 bool skip_akpu_or_token = c->filter == kSecBackupableItemFilter;
1543
1544 sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
1545 CFMutableDictionaryRef allAttributes = NULL;
1546 CFMutableDictionaryRef metadataAttributes = NULL;
1547 CFMutableDictionaryRef secretStuff = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1548 keyclass_t keyclass = 0;
1549 bool ok = s3dl_item_from_col(stmt, q, 1, c->qc.accessGroups, &allAttributes, &access_control, &keyclass, &localError);
1550
1551 if (ok) {
1552 metadataAttributes = CFDictionaryCreateMutableCopy(NULL, 0, allAttributes);
1553 SecDbForEachAttrWithMask(q->q_class, desc, kSecDbReturnDataFlag) {
1554 CFTypeRef value = CFDictionaryGetValue(metadataAttributes, desc->name);
1555 if (value) {
1556 CFDictionarySetValue(secretStuff, desc->name, value);
1557 CFDictionaryRemoveValue(metadataAttributes, desc->name);
1558 }
1559 }
1560 }
1561
1562 bool is_akpu = access_control ? CFEqualSafe(SecAccessControlGetProtection(access_control), kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)
1563 // Mask generation, only look at class per se
1564 : (keyclass & key_class_last) == key_class_akpu;
1565 bool is_token = (ok && allAttributes != NULL) ? CFDictionaryContainsKey(allAttributes, kSecAttrTokenID) : false;
1566
1567 if (ok && allAttributes && !(skip_akpu_or_token && (is_akpu || is_token))) {
1568 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1569 bool do_sys_bound = c->filter == kSecSysBoundItemFilter;
1570 if (c->filter == kSecNoItemFilter ||
1571 SecItemIsSystemBound(allAttributes, q->q_class, c->multiUser) == do_sys_bound) {
1572 /* Re-encode the item. */
1573 secdebug("item", "export rowid %llu item: %@", rowid, allAttributes);
1574 /* The code below could be moved into handle_row. */
1575 CFDataRef pref = _SecItemCreatePersistentRef(q->q_class->name, rowid, allAttributes);
1576 if (pref) {
1577 if (c->dest_keybag != KEYBAG_NONE) {
1578 CFMutableDictionaryRef auth_attribs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1579 SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInAuthenticatedDataFlag) {
1580 CFTypeRef value = CFDictionaryGetValue(metadataAttributes, desc->name);
1581 if(value) {
1582 CFDictionaryAddValue(auth_attribs, desc->name, value);
1583 CFDictionaryRemoveValue(metadataAttributes, desc->name);
1584 }
1585 }
1586
1587 /* Encode and encrypt the item to the specified keybag. */
1588 CFDataRef edata = NULL;
1589 bool encrypted = ks_encrypt_data(c->dest_keybag, access_control, q->q_use_cred_handle, secretStuff, metadataAttributes, auth_attribs, &edata, false, &q->q_error);
1590 CFDictionaryRemoveAllValues(allAttributes);
1591 CFRelease(auth_attribs);
1592 if (encrypted) {
1593 CFDictionarySetValue(allAttributes, kSecValueData, edata);
1594 CFReleaseSafe(edata);
1595 } else {
1596 seccritical("ks_encrypt_data %@,rowid=%" PRId64 ": failed: %@", q->q_class->name, rowid, q->q_error);
1597 CFReleaseNull(q->q_error);
1598 }
1599 }
1600 if (CFDictionaryGetCount(allAttributes)) {
1601 CFDictionarySetValue(allAttributes, kSecValuePersistentRef, pref);
1602 CFArrayAppendValue((CFMutableArrayRef)c->qc.result, allAttributes);
1603 c->qc.found++;
1604 }
1605 CFReleaseSafe(pref);
1606 }
1607 }
1608 } else {
1609 OSStatus status = SecErrorGetOSStatus(localError);
1610
1611 if (status == errSecInteractionNotAllowed && is_akpu) {
1612 if (skip_akpu_or_token) {
1613 secdebug("item", "Skipping akpu item for backup");
1614 } else { // Probably failed to decrypt sysbound item. Should never be an akpu item in backup.
1615 secerror("Encountered akpu item we cannot export (filter %d), skipping. %@", c->filter, localError);
1616 // TODO: Find out who is doing this somehow and make them not do this
1617 }
1618 // We expect akpu items to be inaccessible when the device is locked.
1619 CFReleaseNull(localError);
1620 } else {
1621 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1622 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1623 secinfo("item","Could not export item for rowid %llu: %@", rowid, localError);
1624
1625 if (status == errSecDecode) {
1626 CFReleaseNull(localError);
1627 } else {
1628 CFReleaseSafe(q->q_error);
1629 q->q_error = localError;
1630 }
1631 }
1632 }
1633 CFReleaseNull(access_control);
1634 CFReleaseNull(allAttributes);
1635 CFReleaseNull(metadataAttributes);
1636 CFReleaseNull(secretStuff);
1637 }
1638
1639 static CFStringRef
1640 SecCreateKeybagUUID(keybag_handle_t keybag)
1641 {
1642 #if !TARGET_HAS_KEYSTORE
1643 return NULL;
1644 #else
1645 char uuidstr[37];
1646 uuid_t uuid;
1647 if (aks_get_bag_uuid(keybag, uuid) != KERN_SUCCESS)
1648 return NULL;
1649 uuid_unparse_lower(uuid, uuidstr);
1650 return CFStringCreateWithCString(NULL, uuidstr, kCFStringEncodingUTF8);
1651 #endif
1652 }
1653
1654
1655 CFDictionaryRef
1656 SecServerCopyKeychainPlist(SecDbConnectionRef dbt,
1657 SecurityClient *client,
1658 keybag_handle_t src_keybag,
1659 keybag_handle_t dest_keybag,
1660 enum SecItemFilter filter,
1661 CFErrorRef *error) {
1662 CFMutableDictionaryRef keychain;
1663 keychain = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1664 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1665 unsigned class_ix;
1666 bool inMultiUser = false;
1667 CFStringRef keybaguuid = NULL;
1668 Query q = { .q_keybag = src_keybag,
1669 .q_musrView = NULL
1670 };
1671
1672 if (!keychain) {
1673 if (error && !*error)
1674 SecError(errSecAllocate, error, CFSTR("Can't create keychain dictionary"));
1675 goto errOut;
1676 }
1677
1678 q.q_return_type =
1679 kSecReturnDataMask |
1680 kSecReturnAttributesMask |
1681 kSecReturnPersistentRefMask;
1682 q.q_limit = kSecMatchUnlimited;
1683 q.q_skip_acl_items = true;
1684
1685
1686 #if TARGET_OS_IPHONE
1687 if (client && client->inMultiUser) {
1688 q.q_musrView = SecMUSRCreateActiveUserUUID(client->uid);
1689 inMultiUser = true;
1690 } else
1691 #endif
1692 {
1693 q.q_musrView = SecMUSRGetSingleUserKeychainUUID();
1694 CFRetain(q.q_musrView);
1695
1696 keybaguuid = SecCreateKeybagUUID(dest_keybag);
1697 if (keybaguuid) {
1698 CFDictionarySetValue(keychain, kSecBackupKeybagUUIDKey, keybaguuid);
1699 }
1700 }
1701
1702 /* Get rid of this duplicate. */
1703 const SecDbClass *SecDbClasses[] = {
1704 genp_class(),
1705 inet_class(),
1706 cert_class(),
1707 keys_class()
1708 };
1709
1710 for (class_ix = 0; class_ix < array_size(SecDbClasses);
1711 ++class_ix) {
1712 q.q_class = SecDbClasses[class_ix];
1713 struct s3dl_export_row_ctx ctx = {
1714 .qc = { .q = &q, .dbt = dbt },
1715 .dest_keybag = dest_keybag, .filter = filter,
1716 .multiUser = inMultiUser,
1717 };
1718
1719 secnotice("item", "exporting %ssysbound class '%@'", filter != kSecSysBoundItemFilter ? "non-" : "", q.q_class->name);
1720
1721 CFErrorRef localError = NULL;
1722 if (s3dl_query(s3dl_export_row, &ctx, &localError)) {
1723 secnotice("item", "exporting class '%@' complete", q.q_class->name);
1724 if (CFArrayGetCount(ctx.qc.result)) {
1725 SecSignpostBackupCount(SecSignpostImpulseBackupClassCount, q.q_class->name, CFArrayGetCount(ctx.qc.result), filter);
1726 CFDictionaryAddValue(keychain, q.q_class->name, ctx.qc.result);
1727 }
1728
1729 } else {
1730 OSStatus status = (OSStatus)CFErrorGetCode(localError);
1731 if (status == errSecItemNotFound) {
1732 secnotice("item", "exporting class '%@' complete (no items)", q.q_class->name);
1733 CFRelease(localError);
1734 } else {
1735 secerror("exporting class '%@' failed: %@", q.q_class->name, localError);
1736 if (error) {
1737 CFReleaseSafe(*error);
1738 *error = localError;
1739 } else {
1740 CFRelease(localError);
1741 }
1742 CFReleaseNull(keychain);
1743 CFReleaseNull(ctx.qc.result);
1744 break;
1745 }
1746 }
1747 CFReleaseNull(ctx.qc.result);
1748 }
1749
1750 errOut:
1751 CFReleaseNull(q.q_musrView);
1752 CFReleaseNull(keybaguuid);
1753
1754 return keychain;
1755 }
1756
1757 struct SecServerImportClassState {
1758 SecDbConnectionRef dbt;
1759 CFErrorRef error;
1760 keybag_handle_t src_keybag;
1761 keybag_handle_t dest_keybag;
1762 SecurityClient *client;
1763 enum SecItemFilter filter;
1764 };
1765
1766 struct SecServerImportItemState {
1767 const SecDbClass *class;
1768 struct SecServerImportClassState *s;
1769 };
1770
1771 static void
1772 SecServerImportItem(const void *value, void *context)
1773 {
1774 struct SecServerImportItemState *state = (struct SecServerImportItemState *)context;
1775 bool inMultiUser = false;
1776 #if TARGET_OS_IPHONE
1777 if (state->s->client->inMultiUser)
1778 inMultiUser = true;
1779 #endif
1780
1781 if (state->s->error)
1782 return;
1783
1784 if (!isDictionary(value)) {
1785 SecError(errSecParam, &state->s->error, CFSTR("value %@ is not a dictionary"), value);
1786 return;
1787 }
1788
1789 CFDictionaryRef dict = (CFDictionaryRef)value;
1790
1791 secdebug("item", "Import Item : %@", dict);
1792
1793 SecDbItemRef item = NULL;
1794
1795 /* This is sligthly confusing:
1796 - During upgrade all items are exported with KEYBAG_NONE.
1797 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1798 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1799 */
1800 if (state->s->src_keybag == KEYBAG_NONE) {
1801 item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, state->class, dict, state->s->dest_keybag, &state->s->error);
1802 } else {
1803 item = SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault, state->class, dict, state->s->src_keybag, state->s->dest_keybag, &state->s->error);
1804 }
1805
1806 /* If item is NULL here, control flow ends up at the end where error is cleared. */
1807 if (item && !SecDbItemEnsureDecrypted(item, true, &state->s->error)) {
1808 secdebug("item", "Failed to import item because of decryption failure: %@", state->s->error);
1809 CFReleaseNull(item);
1810 /* No early return; as just above, go to the end where error is cleared. */
1811 }
1812
1813 /* We use the kSecSysBoundItemFilter to indicate that we don't
1814 * preserve rowid's during import.
1815 */
1816 if (item && item->attributes && state->s->filter == kSecBackupableItemFilter) {
1817 CFTypeRef pdmu;
1818
1819 /* We don't filter non sys_bound items during import since we know we
1820 * will never have any in this case.
1821 */
1822 if (SecItemIsSystemBound(item->attributes, state->class, inMultiUser)) {
1823 secdebug("item", "skipping backup of item: %@", dict);
1824 CFReleaseNull(item);
1825 return;
1826 }
1827
1828 /*
1829 * Don't bother with u items when in edu mode since our current backup system
1830 * don't keep track of items that blongs to the device (u) but rather just
1831 * merge them into one blob.
1832 */
1833 if (inMultiUser && (pdmu = CFDictionaryGetValue(item->attributes, kSecAttrAccessible))) {
1834 if (CFEqual(pdmu, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) ||
1835 CFEqual(pdmu, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) ||
1836 CFEqual(pdmu, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) ||
1837 CFEqual(pdmu, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly))
1838 {
1839 secdebug("item", "Skipping KU item : %@", dict);
1840 CFReleaseNull(item);
1841 return;
1842 }
1843 }
1844
1845 /* Avoid importing token-based items. Although newer backups should not have them,
1846 * older (iOS9, iOS10.0) produced backups with token-based items.
1847 */
1848 if (CFDictionaryContainsKey(item->attributes, kSecAttrTokenID)) {
1849 secdebug("item", "Skipping token-based item : %@", dict);
1850 CFReleaseNull(item);
1851 return;
1852 }
1853 }
1854
1855 /*
1856 *
1857 */
1858
1859 if (item && item->attributes) {
1860 CFDataRef musr = NULL;
1861 CFDataRef musrBackup = CFDictionaryGetValue(item->attributes, kSecAttrMultiUser);
1862 CFDataRef systemKeychainUUID = SecMUSRGetSystemKeychainUUID();
1863 bool systemKeychain = CFEqualSafe(musrBackup, systemKeychainUUID);
1864
1865 #if TARGET_OS_IPHONE
1866 if (state->s->client && state->s->client->inMultiUser) {
1867 if (systemKeychain) {
1868 secwarning("system keychain not allowed in multi user mode for item: %@", item);
1869 } else {
1870 musr = SecMUSRCreateActiveUserUUID(state->s->client->uid);
1871 }
1872 } else
1873 #endif
1874 {
1875 if (systemKeychain) {
1876 musr = SecMUSRCopySystemKeychainUUID();
1877 } else {
1878 musr = SecMUSRGetSingleUserKeychainUUID();
1879 CFRetainSafe(musr);
1880 }
1881 }
1882 if (musr == NULL) {
1883 CFReleaseNull(item);
1884 } else {
1885 SecDbItemSetValueWithName(item, CFSTR("musr"), musr, &state->s->error);
1886 CFRelease(musr);
1887 }
1888 }
1889
1890 /*
1891 *
1892 */
1893
1894 if (item) {
1895 bool insertStatus;
1896
1897 if(state->s->filter != kSecSysBoundItemFilter) {
1898 SecDbItemExtractRowIdFromBackupDictionary(item, dict, &state->s->error);
1899 }
1900 SecDbItemInferSyncable(item, &state->s->error);
1901 insertStatus = SecDbItemInsert(item, state->s->dbt, &state->s->error);
1902 if (!insertStatus) {
1903 /*
1904 When running in EduMode, multiple users share the same
1905 keychain and unfortionaly the rowid is used a
1906 persistant reference and is part of the contraints (its
1907 UNIQUE), so lets clear the rowid and try to insert the
1908 entry again.
1909
1910 This even happens for normal operation because of
1911 SysBound entries, so in case of a failure, lets try
1912 again to insert the record.
1913 */
1914 SecDbItemClearRowId(item, NULL);
1915 SecDbItemInsert(item, state->s->dbt, &state->s->error);
1916 }
1917 }
1918
1919 /* Reset error if we had one, since we just skip the current item
1920 and continue importing what we can. */
1921 if (state->s->error) {
1922 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1923 item, state->class->name, state->s->error);
1924 CFReleaseNull(state->s->error);
1925 }
1926
1927 CFReleaseSafe(item);
1928 }
1929
1930 static void SecServerImportClass(const void *key, const void *value,
1931 void *context) {
1932 struct SecServerImportClassState *state =
1933 (struct SecServerImportClassState *)context;
1934 if (state->error)
1935 return;
1936 if (!isString(key)) {
1937 SecError(errSecParam, &state->error, CFSTR("class name %@ is not a string"), key);
1938 return;
1939 }
1940 /* ignore the Keybag UUID */
1941 if (CFEqual(key, kSecBackupKeybagUUIDKey))
1942 return;
1943 const SecDbClass *class = kc_class_with_name(key);
1944 if (!class) {
1945 secwarning("Ignoring unknown key class '%@'", key);
1946 return;
1947 }
1948 if (class == identity_class()) {
1949 SecError(errSecParam, &state->error, CFSTR("attempt to import an identity"));
1950 return;
1951 }
1952 struct SecServerImportItemState item_state = {
1953 .class = class, .s = state,
1954 };
1955 if (isArray(value)) {
1956 CFArrayRef items = (CFArrayRef)value;
1957 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items), key, state->filter);
1958 SecSignpostBackupCount(SecSignpostImpulseRestoreClassCount, class->name, CFArrayGetCount(items), state->filter);
1959 CFArrayApplyFunction(items, CFRangeMake(0, CFArrayGetCount(items)),
1960 SecServerImportItem, &item_state);
1961 } else if (isDictionary(value)) {
1962 CFDictionaryRef item = (CFDictionaryRef)value;
1963 secwarning("Import %ld items of class %@ (filter %d)", (long)1, key, state->filter);
1964 SecSignpostBackupCount(SecSignpostImpulseRestoreClassCount, class->name, 1, state->filter);
1965 SecServerImportItem(item, &item_state);
1966 } else {
1967 secwarning("Unknown value type for class %@ (filter %d)", key, state->filter);
1968 }
1969 }
1970
1971 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt, SecurityClient *client,
1972 keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
1973 CFDictionaryRef keychain, enum SecItemFilter filter,
1974 bool removeKeychainContent, CFErrorRef *error) {
1975 CFStringRef keybaguuid = NULL;
1976 bool ok = true;
1977
1978 CFDictionaryRef sys_bound = NULL;
1979 if (filter == kSecBackupableItemFilter) {
1980 /* Grab a copy of all the items for which SecItemIsSystemBound()
1981 returns true. */
1982 require(sys_bound = SecServerCopyKeychainPlist(dbt, client, KEYBAG_DEVICE,
1983 KEYBAG_NONE, kSecSysBoundItemFilter,
1984 error), errOut);
1985 }
1986
1987 /*
1988 * Validate the uuid of the source keybag matches what we have in the backup
1989 */
1990 keybaguuid = SecCreateKeybagUUID(src_keybag);
1991 if (keybaguuid) {
1992 CFStringRef uuid = CFDictionaryGetValue(keychain, kSecBackupKeybagUUIDKey);
1993 if (isString(uuid)) {
1994 require_action(CFEqual(keybaguuid, uuid), errOut,
1995 SecError(errSecDecode, error, CFSTR("Keybag UUID (%@) mismatch with backup (%@)"),
1996 keybaguuid, uuid));
1997 }
1998 }
1999
2000 #if TARGET_OS_IOS
2001 /*
2002 * Shared iPad is very special, it always delete's the user keychain, and never merge content
2003 */
2004 if (client->inMultiUser) {
2005 CFDataRef musrView = SecMUSRCreateActiveUserUUID(client->uid);
2006 require_action(musrView, errOut, ok = false);
2007 require_action(ok = SecServerDeleteAllForUser(dbt, musrView, true, error), errOut, CFReleaseNull(musrView));
2008 CFReleaseNull(musrView);
2009 } else
2010 #endif
2011 {
2012 /*
2013 * Delete everything in the keychain.
2014 * We don't want this if we're restoring backups because we probably already synced stuff over
2015 */
2016 if (removeKeychainContent) {
2017 require(ok = SecServerDeleteAll(dbt, error), errOut);
2018 } else {
2019 // Custom hack to support bluetooth's workflow for 11.3. Should be removed in a future release.
2020 __block CFErrorRef btError = NULL;
2021 bool deletedBT = kc_transaction(dbt, &btError, ^bool{
2022
2023 #define EXCLUDE_AGRPS "'com.apple.security.sos', 'com.apple.security.sos-usercredential', 'com.apple.security.ckks', 'com.apple.security.egoIdentities', 'com.apple.security.octagon'"
2024
2025 bool tok = SecDbExec(dbt, CFSTR("DELETE FROM genp WHERE sync = 0 AND NOT agrp IN (" EXCLUDE_AGRPS ");"), &btError);
2026 tok &= SecDbExec(dbt, CFSTR("DELETE FROM inet WHERE sync = 0 AND NOT agrp IN (" EXCLUDE_AGRPS ");"), &btError);
2027 tok &= SecDbExec(dbt, CFSTR("DELETE FROM cert WHERE sync = 0 AND NOT agrp IN (" EXCLUDE_AGRPS ");"), &btError);
2028 tok &= SecDbExec(dbt, CFSTR("DELETE FROM keys WHERE sync = 0 AND NOT agrp IN (" EXCLUDE_AGRPS ");"), &btError);
2029
2030 #undef EXCLUDE_AGRPS
2031 return tok;
2032 });
2033 if (!deletedBT) {
2034 secerror("Unable to delete nonsyncable items prior to keychain restore: %@", btError);
2035 } else {
2036 secnotice("restore", "Successfully deleted nonsyncable items");
2037 }
2038 CFReleaseNull(btError);
2039 }
2040 }
2041
2042 struct SecServerImportClassState state = {
2043 .dbt = dbt,
2044 .src_keybag = src_keybag,
2045 .dest_keybag = dest_keybag,
2046 .client = client,
2047 .filter = filter,
2048 };
2049 /* Import the provided items, preserving rowids. */
2050 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain));
2051 CFDictionaryApplyFunction(keychain, SecServerImportClass, &state);
2052
2053 if (sys_bound) {
2054 state.src_keybag = KEYBAG_NONE;
2055 /* Import the items we preserved with random rowids. */
2056 state.filter = kSecSysBoundItemFilter;
2057 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound));
2058 CFDictionaryApplyFunction(sys_bound, SecServerImportClass, &state);
2059 }
2060 if (state.error) {
2061 if (error) {
2062 CFReleaseSafe(*error);
2063 *error = state.error;
2064 } else {
2065 CFRelease(state.error);
2066 }
2067 ok = false;
2068 }
2069
2070 // If CKKS had spun up, it's very likely that we just deleted its data.
2071 // Tell it to perform a local resync.
2072 #if OCTAGON
2073 SecCKKSPerformLocalResync();
2074 #endif
2075
2076 errOut:
2077 CFReleaseSafe(sys_bound);
2078 CFReleaseSafe(keybaguuid);
2079
2080 return ok;
2081 }
2082
2083 CFStringRef
2084 SecServerBackupGetKeybagUUID(CFDictionaryRef keychain, CFErrorRef *error)
2085 {
2086 CFStringRef uuid = CFDictionaryGetValue(keychain, kSecBackupKeybagUUIDKey);
2087 if (!isString(uuid)) {
2088 SecError(errSecDecode, error, CFSTR("Missing or invalid %@ in backup dictionary"), kSecBackupKeybagUUIDKey);
2089 return NULL;
2090 }
2091 return uuid;
2092 }
2093
2094 #pragma mark - key rolling support
2095 #if USE_KEYSTORE
2096
2097 struct check_generation_ctx {
2098 struct s3dl_query_ctx query_ctx;
2099 uint32_t current_generation;
2100 };
2101
2102 static void check_generation(sqlite3_stmt *stmt, void *context) {
2103 struct check_generation_ctx *c = context;
2104 CFDataRef blob = NULL;
2105 size_t blobLen = 0;
2106 const uint8_t *cursor = NULL;
2107 uint32_t version;
2108 keyclass_t keyclass;
2109 uint32_t current_generation = c->current_generation;
2110
2111 require(blob = s3dl_copy_data_from_col(stmt, 1, &c->query_ctx.q->q_error), out);
2112 blobLen = CFDataGetLength(blob);
2113 cursor = CFDataGetBytePtr(blob);
2114
2115 /* Check for underflow, ensuring we have at least one full AES block left. */
2116 if (blobLen < sizeof(version) + sizeof(keyclass)) {
2117 SecError(errSecDecode, &c->query_ctx.q->q_error, CFSTR("check_generation: Check for underflow"));
2118 goto out;
2119 }
2120
2121 version = *((uint32_t *)cursor);
2122 cursor += sizeof(version);
2123
2124 (void) version; // TODO: do something with the version number.
2125
2126 keyclass = *((keyclass_t *)cursor);
2127
2128 // TODO: export get_key_gen macro
2129 if (((keyclass & ~key_class_last) == 0) != (current_generation == 0)) {
2130 c->query_ctx.found++;
2131 }
2132
2133 CFReleaseSafe(blob);
2134 return;
2135
2136 out:
2137 c->query_ctx.found++;
2138 CFReleaseSafe(blob);
2139 }
2140
2141 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt, uint32_t current_generation, CFErrorRef *error) {
2142 CFErrorRef localError = NULL;
2143 struct check_generation_ctx ctx = { .query_ctx = { .dbt = dbt }, .current_generation = current_generation };
2144
2145 const SecDbClass *classes[] = {
2146 genp_class(),
2147 inet_class(),
2148 keys_class(),
2149 cert_class(),
2150 };
2151
2152 for (size_t class_ix = 0; class_ix < array_size(classes); ++class_ix) {
2153 Query *q = query_create(classes[class_ix], NULL, NULL, &localError);
2154 if (!q)
2155 return false;
2156
2157 ctx.query_ctx.q = q;
2158 q->q_limit = kSecMatchUnlimited;
2159
2160 bool ok = s3dl_query(check_generation, &ctx, &localError);
2161 query_destroy(q, NULL);
2162 CFReleaseNull(ctx.query_ctx.result);
2163
2164 if (!ok && localError && (CFErrorGetCode(localError) == errSecItemNotFound)) {
2165 CFReleaseNull(localError);
2166 continue;
2167 }
2168 secerror("Class %@ not up to date", classes[class_ix]->name);
2169 return false;
2170 }
2171 return true;
2172 }
2173
2174 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt, SecurityClient *client, CFErrorRef *error) {
2175 return SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
2176 __block bool ok = false;
2177 uint32_t keystore_generation_status;
2178
2179 /* can we migrate to new class keys right now? */
2180 if (!aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status) &&
2181 (keystore_generation_status & generation_change_in_progress)) {
2182
2183 /* take a lock assertion */
2184 bool operated_while_unlocked = SecAKSDoWithUserBagLockAssertion(error, ^{
2185 CFErrorRef localError = NULL;
2186 CFDictionaryRef backup = SecServerCopyKeychainPlist(dbt, NULL,
2187 KEYBAG_DEVICE, KEYBAG_NONE, kSecNoItemFilter, &localError);
2188 if (backup) {
2189 if (localError) {
2190 secerror("Ignoring export error: %@ during roll export", localError);
2191 CFReleaseNull(localError);
2192 }
2193 // 'true' argument: we're replacing everything with newly wrapped entries so remove the old stuff
2194 ok = SecServerImportKeychainInPlist(dbt, client, KEYBAG_NONE,
2195 KEYBAG_DEVICE, backup, kSecNoItemFilter, true, &localError);
2196 if (localError) {
2197 secerror("Ignoring export error: %@ during roll export", localError);
2198 CFReleaseNull(localError);
2199 }
2200 CFRelease(backup);
2201 }
2202 });
2203 if (!operated_while_unlocked)
2204 ok = false;
2205 } else {
2206 ok = SecError(errSecBadReq, error, CFSTR("No key roll in progress."));
2207 }
2208
2209 *commit = ok;
2210 });
2211 }
2212 #endif