]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemDb.c
Security-57337.60.2.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 #include <securityd/SecItemDb.h>
31
32 #include <securityd/SecDbKeychainItem.h>
33 #include <securityd/SecItemSchema.h>
34 #include <securityd/SecItemServer.h>
35 #include <Security/SecAccessControlPriv.h>
36 #include <Security/SecBasePriv.h>
37 #include <Security/SecItem.h>
38 #include <Security/SecItemPriv.h>
39 #include <Security/SecItemInternal.h>
40 #include <securityd/SOSCloudCircleServer.h>
41 #include <utilities/array_size.h>
42 #include <utilities/SecIOFormat.h>
43 #include <SecAccessControlPriv.h>
44
45 /* label when certificate data is joined with key data */
46 #define CERTIFICATE_DATA_COLUMN_LABEL "certdata"
47
48 const SecDbAttr *SecDbAttrWithKey(const SecDbClass *c,
49 CFTypeRef key,
50 CFErrorRef *error) {
51 /* Special case: identites can have all attributes of either cert
52 or keys. */
53 if (c == &identity_class) {
54 const SecDbAttr *desc;
55 if (!(desc = SecDbAttrWithKey(&cert_class, key, 0)))
56 desc = SecDbAttrWithKey(&keys_class, key, error);
57 return desc;
58 }
59
60 if (isString(key)) {
61 SecDbForEachAttr(c, a) {
62 if (CFEqual(a->name, key))
63 return a;
64 }
65 if (CFEqual(kSecAttrNoLegacy, key)) {
66 return NULL; /* results in no ops for this attribute */
67 }
68 }
69
70 SecError(errSecNoSuchAttr, error, CFSTR("attribute %@ not found in class %@"), key, c->name);
71
72 return NULL;
73 }
74
75 bool kc_transaction(SecDbConnectionRef dbt, CFErrorRef *error, bool(^perform)()) {
76 __block bool ok = true;
77 return ok && SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
78 ok = *commit = perform();
79 });
80 }
81
82 static CFStringRef SecDbGetKindSQL(SecDbAttrKind kind) {
83 switch (kind) {
84 case kSecDbBlobAttr:
85 case kSecDbDataAttr:
86 case kSecDbUUIDAttr:
87 case kSecDbSHA1Attr:
88 case kSecDbPrimaryKeyAttr:
89 case kSecDbEncryptedDataAttr:
90 return CFSTR("BLOB");
91 case kSecDbAccessAttr:
92 case kSecDbStringAttr:
93 return CFSTR("TEXT");
94 case kSecDbNumberAttr:
95 case kSecDbSyncAttr:
96 case kSecDbTombAttr:
97 return CFSTR("INTEGER");
98 case kSecDbDateAttr:
99 case kSecDbCreationDateAttr:
100 case kSecDbModificationDateAttr:
101 return CFSTR("REAL");
102 case kSecDbRowIdAttr:
103 return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
104 case kSecDbAccessControlAttr:
105 case kSecDbUTombAttr:
106 /* This attribute does not exist in the DB. */
107 return NULL;
108 }
109 }
110
111 static void SecDbAppendUnique(CFMutableStringRef sql, CFStringRef value, bool *haveUnique) {
112 assert(haveUnique);
113 if (!*haveUnique)
114 CFStringAppend(sql, CFSTR("UNIQUE("));
115
116 SecDbAppendElement(sql, value, haveUnique);
117 }
118
119 static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql, const SecDbClass *c) {
120 CFStringAppendFormat(sql, 0, CFSTR("CREATE TABLE %@("), c->name);
121 SecDbForEachAttrWithMask(c,desc,kSecDbInFlag) {
122 CFStringAppendFormat(sql, 0, CFSTR("%@ %@"), desc->name, SecDbGetKindSQL(desc->kind));
123 if (desc->flags & kSecDbNotNullFlag)
124 CFStringAppend(sql, CFSTR(" NOT NULL"));
125 if (desc->flags & kSecDbDefault0Flag)
126 CFStringAppend(sql, CFSTR(" DEFAULT 0"));
127 if (desc->flags & kSecDbDefaultEmptyFlag)
128 CFStringAppend(sql, CFSTR(" DEFAULT ''"));
129 CFStringAppend(sql, CFSTR(","));
130 }
131
132 bool haveUnique = false;
133 SecDbForEachAttrWithMask(c,desc,kSecDbPrimaryKeyFlag | kSecDbInFlag) {
134 SecDbAppendUnique(sql, desc->name, &haveUnique);
135 }
136 if (haveUnique)
137 CFStringAppend(sql, CFSTR(")"));
138
139 CFStringAppend(sql, CFSTR(");"));
140
141 // Create indicies
142 SecDbForEachAttrWithMask(c,desc, kSecDbIndexFlag | kSecDbInFlag) {
143 CFStringAppendFormat(sql, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c->name, desc->name, c->name, desc->name);
144 }
145 }
146
147 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql, const SecDbClass *c) {
148 CFStringAppendFormat(sql, 0, CFSTR("DROP TABLE %@;"), c->name);
149 }
150
151 static CFDataRef SecPersistentRefCreateWithItem(SecDbItemRef item, CFErrorRef *error) {
152 sqlite3_int64 row_id = SecDbItemGetRowId(item, error);
153 if (row_id)
154 return _SecItemMakePersistentRef(SecDbItemGetClass(item)->name, row_id);
155 return NULL;
156 }
157
158 bool SecItemDbCreateSchema(SecDbConnectionRef dbt, const SecDbSchema *schema, CFErrorRef *error)
159 {
160 __block bool ok = true;
161 CFMutableStringRef sql = CFStringCreateMutable(kCFAllocatorDefault, 0);
162 for (const SecDbClass * const *pclass = schema->classes; *pclass; ++pclass) {
163 SecDbAppendCreateTableWithClass(sql, *pclass);
164 }
165 // TODO: Use tversion_class to do this.
166 CFStringAppendFormat(sql, NULL, CFSTR("INSERT INTO tversion(version) VALUES(%d);"), schema->version);
167 CFStringPerformWithCString(sql, ^(const char *sql_string) {
168 ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), sql_string, NULL, NULL, NULL),
169 SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), sql_string);
170 });
171 CFReleaseSafe(sql);
172 return ok;
173 }
174
175 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt, const SecDbSchema *schema, CFErrorRef *error)
176 {
177 __block bool ok = true;
178 CFMutableStringRef sql = CFStringCreateMutable(kCFAllocatorDefault, 0);
179 for (const SecDbClass * const *pclass = schema->classes; *pclass; ++pclass) {
180 SecDbAppendDropTableWithClass(sql, *pclass);
181 }
182 CFStringPerformWithCString(sql, ^(const char *sql_string) {
183 ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), sql_string, NULL, NULL, NULL),
184 SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), sql_string);
185 });
186 CFReleaseSafe(sql);
187 return ok;
188 }
189
190 CFTypeRef SecDbItemCopyResult(SecDbItemRef item, ReturnTypeMask return_type, CFErrorRef *error) {
191 CFTypeRef a_result;
192
193 if (return_type == 0) {
194 /* Caller isn't interested in any results at all. */
195 a_result = kCFNull;
196 } else if (return_type == kSecReturnDataMask) {
197 a_result = SecDbItemGetCachedValueWithName(item, kSecValueData);
198 if (a_result) {
199 CFRetainSafe(a_result);
200 } else {
201 a_result = CFDataCreate(kCFAllocatorDefault, NULL, 0);
202 }
203 } else if (return_type == kSecReturnPersistentRefMask) {
204 a_result = SecPersistentRefCreateWithItem(item, error);
205 } else {
206 CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item));
207 /* We need to return more than one value. */
208 if (return_type & kSecReturnRefMask) {
209 CFDictionarySetValue(dict, kSecClass, SecDbItemGetClass(item)->name);
210 }
211 CFOptionFlags mask = (((return_type & kSecReturnDataMask || return_type & kSecReturnRefMask) ? kSecDbReturnDataFlag : 0) |
212 ((return_type & kSecReturnAttributesMask || return_type & kSecReturnRefMask) ? kSecDbReturnAttrFlag : 0));
213 SecDbForEachAttr(SecDbItemGetClass(item), desc) {
214 if ((desc->flags & mask) != 0) {
215 CFTypeRef value = SecDbItemGetValue(item, desc, error);
216 if (value && !CFEqual(kCFNull, value)) {
217 CFDictionarySetValue(dict, desc->name, value);
218 } else if (value == NULL) {
219 CFReleaseNull(dict);
220 break;
221 }
222 }
223 }
224 if (return_type & kSecReturnPersistentRefMask) {
225 CFDataRef pref = SecPersistentRefCreateWithItem(item, error);
226 CFDictionarySetValue(dict, kSecValuePersistentRef, pref);
227 CFReleaseSafe(pref);
228 }
229
230 a_result = dict;
231 }
232
233 return a_result;
234 }
235
236 /* AUDIT[securityd](done):
237 attributes (ok) is a caller provided dictionary, only its cf type has
238 been checked.
239 */
240 bool
241 s3dl_query_add(SecDbConnectionRef dbt, Query *q, CFTypeRef *result, CFErrorRef *error)
242 {
243 if (query_match_count(q) != 0)
244 return errSecItemMatchUnsupported;
245
246 /* Add requires a class to be specified unless we are adding a ref. */
247 if (q->q_use_item_list)
248 return errSecUseItemListUnsupported;
249
250 /* Actual work here. */
251 SecDbItemRef item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, q->q_class, q->q_item, KEYBAG_DEVICE, error);
252 if (!item)
253 return false;
254 if (SecDbItemIsTombstone(item))
255 SecDbItemSetValue(item, &v7utomb, q->q_use_tomb ? q->q_use_tomb : kCFBooleanTrue, NULL);
256
257 bool ok = true;
258 if (q->q_data)
259 ok = SecDbItemSetValueWithName(item, CFSTR("v_Data"), q->q_data, error);
260 if (q->q_row_id)
261 ok = SecDbItemSetRowId(item, q->q_row_id, error);
262 if (q->q_musrView)
263 ok = SecDbItemSetValueWithName(item, CFSTR("musr"), q->q_musrView, error);
264 SecDbItemSetCredHandle(item, q->q_use_cred_handle);
265
266 if (ok)
267 ok = SecDbItemInsert(item, dbt, error);
268
269 if (ok) {
270 if (result && q->q_return_type) {
271 *result = SecDbItemCopyResult(item, q->q_return_type, error);
272 }
273 }
274 if (!ok && error && *error) {
275 if (CFEqual(CFErrorGetDomain(*error), kSecDbErrorDomain) && CFErrorGetCode(*error) == SQLITE_CONSTRAINT) {
276 CFReleaseNull(*error);
277 SecError(errSecDuplicateItem, error, CFSTR("duplicate item %@"), item);
278 } else if (CFEqual(CFErrorGetDomain(*error), kSecErrorDomain) && CFErrorGetCode(*error) == errSecDecode) { //handle situation when item have pdmn=akpu but passcode is not set
279 CFTypeRef value = SecDbItemGetValue(item, SecDbClassAttrWithKind(item->class, kSecDbAccessAttr, error), error);
280 if (value && CFEqual(value, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)) {
281 CFReleaseNull(*error);
282 SecError(errSecAuthFailed, error, CFSTR("authentication failed"));
283 }
284 }
285 }
286
287 if (ok) {
288 q->q_changed = true;
289 if (SecDbItemIsSyncable(item))
290 q->q_sync_changed = true;
291 }
292
293 secdebug("dbitem", "inserting item %@%s%@", item, ok ? "" : "failed: ", ok || error == NULL ? (CFErrorRef)CFSTR("") : *error);
294
295 CFRelease(item);
296
297 return ok;
298 }
299
300 typedef void (*s3dl_handle_row)(sqlite3_stmt *stmt, void *context);
301
302 static CFDataRef
303 s3dl_copy_data_from_col(sqlite3_stmt *stmt, int col, CFErrorRef *error) {
304 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt, col),
305 sqlite3_column_bytes(stmt, col),
306 kCFAllocatorNull);
307 }
308
309 static bool
310 s3dl_item_from_col(sqlite3_stmt *stmt, Query *q, int col, CFArrayRef accessGroups,
311 CFMutableDictionaryRef *item, SecAccessControlRef *access_control, CFErrorRef *error) {
312 CFDataRef edata = NULL;
313 bool ok = false;
314 require(edata = s3dl_copy_data_from_col(stmt, col, error), out);
315 ok = s3dl_item_from_data(edata, q, accessGroups, item, access_control, error);
316
317 out:
318 CFReleaseSafe(edata);
319 return ok;
320 }
321
322 struct s3dl_query_ctx {
323 Query *q;
324 CFArrayRef accessGroups;
325 SecDbConnectionRef dbt;
326 CFTypeRef result;
327 int found;
328 };
329
330 /* Return whatever the caller requested based on the value of q->q_return_type.
331 keys and values must be 3 larger than attr_count in size to accomadate the
332 optional data, class and persistent ref results. This is so we can use
333 the CFDictionaryCreate() api here rather than appending to a
334 mutable dictionary. */
335 static CF_RETURNS_RETAINED CFTypeRef handle_result(Query *q, CFMutableDictionaryRef item,
336 sqlite_int64 rowid) {
337 CFTypeRef a_result;
338 CFDataRef data;
339 data = CFDictionaryGetValue(item, kSecValueData);
340 if (q->q_return_type == 0) {
341 /* Caller isn't interested in any results at all. */
342 a_result = kCFNull;
343 } else if (q->q_return_type == kSecReturnDataMask) {
344 if (data) {
345 a_result = data;
346 CFRetain(a_result);
347 } else {
348 a_result = CFDataCreate(kCFAllocatorDefault, NULL, 0);
349 }
350 } else if (q->q_return_type == kSecReturnPersistentRefMask) {
351 a_result = _SecItemMakePersistentRef(q->q_class->name, rowid);
352 } else {
353 /* We need to return more than one value. */
354 if (q->q_return_type & kSecReturnRefMask) {
355 CFDictionarySetValue(item, kSecClass, q->q_class->name);
356 } else if ((q->q_return_type & kSecReturnAttributesMask)) {
357 if (!(q->q_return_type & kSecReturnDataMask)) {
358 CFDictionaryRemoveValue(item, kSecValueData);
359 }
360 } else {
361 CFRetainSafe(data);
362 CFDictionaryRemoveAllValues(item);
363 if ((q->q_return_type & kSecReturnDataMask) && data) {
364 CFDictionarySetValue(item, kSecValueData, data);
365 }
366 CFReleaseSafe(data);
367 }
368 if (q->q_return_type & kSecReturnPersistentRefMask) {
369 CFDataRef pref = _SecItemMakePersistentRef(q->q_class->name, rowid);
370 CFDictionarySetValue(item, kSecValuePersistentRef, pref);
371 CFRelease(pref);
372 }
373
374 a_result = item;
375 CFRetain(item);
376 }
377
378 return a_result;
379 }
380
381 static void s3dl_merge_into_dict(const void *key, const void *value, void *context) {
382 CFDictionarySetValue(context, key, value);
383 }
384
385 static void s3dl_query_row(sqlite3_stmt *stmt, void *context) {
386 struct s3dl_query_ctx *c = context;
387 Query *q = c->q;
388
389 sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
390 CFMutableDictionaryRef item;
391 bool ok = s3dl_item_from_col(stmt, q, 1, c->accessGroups, &item, NULL, &q->q_error);
392 if (!ok) {
393 OSStatus status = SecErrorGetOSStatus(q->q_error);
394 // errSecDecode means the item is corrupted, stash it for delete.
395 if (status == errSecDecode) {
396 secwarning("ignoring corrupt %@,rowid=%" PRId64 " %@", q->q_class->name, rowid, q->q_error);
397 {
398 CFDataRef edata = s3dl_copy_data_from_col(stmt, 1, NULL);
399 CFMutableStringRef edatastring = CFStringCreateMutable(kCFAllocatorDefault, 0);
400 if(edatastring) {
401 CFStringAppendEncryptedData(edatastring, edata);
402 secnotice("item", "corrupted edata=%@", edatastring);
403 }
404 CFReleaseSafe(edata);
405 CFReleaseSafe(edatastring);
406 }
407 CFReleaseNull(q->q_error);
408 } else if (status == errSecAuthNeeded) {
409 secwarning("Authentication is needed for %@,rowid=%" PRId64 " (%" PRIdOSStatus "): %@", q->q_class->name, rowid, status, q->q_error);
410 } else {
411 secerror("decode %@,rowid=%" PRId64 " failed (%" PRIdOSStatus "): %@", q->q_class->name, rowid, status, q->q_error);
412 }
413 // q->q_error will be released appropriately by a call to query_error
414 return;
415 }
416
417 if (!item)
418 goto out;
419
420 if (q->q_class == &identity_class) {
421 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
422
423 CFMutableDictionaryRef key;
424 /* TODO : if there is a errSecDecode error here, we should cleanup */
425 if (!s3dl_item_from_col(stmt, q, 3, c->accessGroups, &key, NULL, &q->q_error) || !key)
426 goto out;
427
428 CFDataRef certData = CFDictionaryGetValue(item, kSecValueData);
429 if (certData) {
430 CFDictionarySetValue(key, CFSTR(CERTIFICATE_DATA_COLUMN_LABEL),
431 certData);
432 CFDictionaryRemoveValue(item, kSecValueData);
433 }
434 CFDictionaryApplyFunction(item, s3dl_merge_into_dict, key);
435 CFRelease(item);
436 item = key;
437 }
438
439 if (!match_item(c->dbt, q, c->accessGroups, item))
440 goto out;
441
442 CFTypeRef a_result = handle_result(q, item, rowid);
443 if (a_result) {
444 if (a_result == kCFNull) {
445 /* Caller wasn't interested in a result, but we still
446 count this row as found. */
447 CFRelease(a_result); // Help shut up clang
448 } else if (q->q_limit == 1) {
449 c->result = a_result;
450 } else {
451 CFArrayAppendValue((CFMutableArrayRef)c->result, a_result);
452 CFRelease(a_result);
453 }
454 c->found++;
455 }
456
457 out:
458 CFReleaseSafe(item);
459 }
460
461 static void
462 SecDbAppendWhereROWID(CFMutableStringRef sql,
463 CFStringRef col, sqlite_int64 row_id,
464 bool *needWhere) {
465 if (row_id > 0) {
466 SecDbAppendWhereOrAnd(sql, needWhere);
467 CFStringAppendFormat(sql, NULL, CFSTR("%@=%lld"), col, row_id);
468 }
469 }
470
471 static void
472 SecDbAppendWhereAttrs(CFMutableStringRef sql, const Query *q, bool *needWhere) {
473 CFIndex ix, attr_count = query_attr_count(q);
474 for (ix = 0; ix < attr_count; ++ix) {
475 SecDbAppendWhereOrAndEquals(sql, query_attr_at(q, ix).key, needWhere);
476 }
477 }
478
479 static void
480 SecDbAppendWhereAccessGroups(CFMutableStringRef sql,
481 CFStringRef col,
482 CFArrayRef accessGroups,
483 bool *needWhere) {
484 CFIndex ix, ag_count;
485 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
486 return;
487 }
488
489 SecDbAppendWhereOrAnd(sql, needWhere);
490 CFStringAppend(sql, col);
491 CFStringAppend(sql, CFSTR(" IN (?"));
492 for (ix = 1; ix < ag_count; ++ix) {
493 CFStringAppend(sql, CFSTR(",?"));
494 }
495 CFStringAppend(sql, CFSTR(")"));
496 }
497
498 static bool
499 isQueryOverAllMUSRViews(CFTypeRef musrView)
500 {
501 return SecMUSRIsViewAllViews(musrView);
502 }
503
504 static bool
505 isQueryOverSingleUserView(CFTypeRef musrView)
506 {
507 return isNull(musrView);
508 }
509
510 #if TARGET_OS_IPHONE
511 static bool
512 isQueryOverBothUserAndSystem(CFTypeRef musrView, uid_t *uid)
513 {
514 return SecMUSRGetBothUserAndSystemUUID(musrView, uid);
515 }
516 #endif
517
518 static void
519 SecDbAppendWhereMusr(CFMutableStringRef sql,
520 const Query *q,
521 bool *needWhere)
522 {
523 SecDbAppendWhereOrAnd(sql, needWhere);
524
525 #if TARGET_OS_IPHONE
526 if (isQueryOverBothUserAndSystem(q->q_musrView, NULL)) {
527 CFStringAppend(sql, CFSTR("(musr = ? OR musr = ?)"));
528 } else
529 #endif
530 if (isQueryOverAllMUSRViews(q->q_musrView)) {
531 /* query over all items, regardless of view */
532 } else if (isQueryOverSingleUserView(q->q_musrView)) {
533 CFStringAppend(sql, CFSTR("musr = ?"));
534 } else {
535 CFStringAppend(sql, CFSTR("musr = ?"));
536 }
537 }
538
539 static void SecDbAppendWhereClause(CFMutableStringRef sql, const Query *q,
540 CFArrayRef accessGroups) {
541 bool needWhere = true;
542 SecDbAppendWhereROWID(sql, CFSTR("ROWID"), q->q_row_id, &needWhere);
543 SecDbAppendWhereAttrs(sql, q, &needWhere);
544 SecDbAppendWhereMusr(sql, q, &needWhere);
545 SecDbAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
546 }
547
548 static void SecDbAppendLimit(CFMutableStringRef sql, CFIndex limit) {
549 if (limit != kSecMatchUnlimited)
550 CFStringAppendFormat(sql, NULL, CFSTR(" LIMIT %" PRIdCFIndex), limit);
551 }
552
553 static CFStringRef s3dl_select_sql(Query *q, CFArrayRef accessGroups) {
554 CFMutableStringRef sql = CFStringCreateMutable(NULL, 0);
555 if (q->q_class == &identity_class) {
556 CFStringAppendFormat(sql, NULL, CFSTR("SELECT crowid, "
557 CERTIFICATE_DATA_COLUMN_LABEL ", rowid,data FROM "
558 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
559 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
560 " keys.*,cert.data AS " CERTIFICATE_DATA_COLUMN_LABEL
561 " FROM keys, cert"
562 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"));
563 SecDbAppendWhereAccessGroups(sql, CFSTR("cert.agrp"), accessGroups, 0);
564 /* The next 3 SecDbAppendWhere calls are in the same order as in
565 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
566 as long as we do an extra sqlBindAccessGroups first. */
567 SecDbAppendWhereROWID(sql, CFSTR("crowid"), q->q_row_id, 0);
568 CFStringAppend(sql, CFSTR(")"));
569 bool needWhere = true;
570 SecDbAppendWhereAttrs(sql, q, &needWhere);
571 SecDbAppendWhereMusr(sql, q, &needWhere);
572 SecDbAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
573 } else {
574 CFStringAppend(sql, CFSTR("SELECT rowid, data FROM "));
575 CFStringAppend(sql, q->q_class->name);
576 SecDbAppendWhereClause(sql, q, accessGroups);
577 }
578 SecDbAppendLimit(sql, q->q_limit);
579
580 return sql;
581 }
582
583 static bool sqlBindMusr(sqlite3_stmt *stmt, const Query *q, int *pParam, CFErrorRef *error) {
584 int param = *pParam;
585 bool result = true;
586 #if TARGET_OS_IPHONE
587 uid_t uid;
588
589 if (isQueryOverBothUserAndSystem(q->q_musrView, &uid)) {
590 /* network extensions are special and get to query both user and system views */
591 CFDataRef systemUUID = SecMUSRGetSystemKeychainUUID();
592 result = SecDbBindObject(stmt, param++, systemUUID, error);
593 if (result) {
594 CFDataRef activeUser = SecMUSRCreateActiveUserUUID(uid);
595 result = SecDbBindObject(stmt, param++, activeUser, error);
596 CFReleaseNull(activeUser);
597 }
598 } else
599 #endif
600 if (isQueryOverAllMUSRViews(q->q_musrView)) {
601 /* query over all items, regardless of view */
602 } else if (isQueryOverSingleUserView(q->q_musrView)) {
603 CFDataRef singleUUID = SecMUSRGetSingleUserKeychainUUID();
604 result = SecDbBindObject(stmt, param++, singleUUID, error);
605 } else {
606 result = SecDbBindObject(stmt, param++, q->q_musrView, error);
607 }
608
609 *pParam = param;
610 return result;
611 }
612
613
614 static bool sqlBindAccessGroups(sqlite3_stmt *stmt, CFArrayRef accessGroups,
615 int *pParam, CFErrorRef *error) {
616 bool result = true;
617 int param = *pParam;
618 CFIndex ix, count = accessGroups ? CFArrayGetCount(accessGroups) : 0;
619 for (ix = 0; ix < count; ++ix) {
620 result = SecDbBindObject(stmt, param++,
621 CFArrayGetValueAtIndex(accessGroups, ix),
622 error);
623 if (!result)
624 break;
625 }
626 *pParam = param;
627 return result;
628 }
629
630 static bool sqlBindWhereClause(sqlite3_stmt *stmt, const Query *q,
631 CFArrayRef accessGroups, int *pParam, CFErrorRef *error) {
632 bool result = true;
633 int param = *pParam;
634 CFIndex ix, attr_count = query_attr_count(q);
635 for (ix = 0; ix < attr_count; ++ix) {
636 result = SecDbBindObject(stmt, param++, query_attr_at(q, ix).value, error);
637 if (!result)
638 break;
639 }
640
641 if (result) {
642 result = sqlBindMusr(stmt, q, &param, error);
643 }
644
645 /* Bind the access group to the sql. */
646 if (result) {
647 result = sqlBindAccessGroups(stmt, accessGroups, &param, error);
648 }
649
650 *pParam = param;
651 return result;
652 }
653
654 bool SecDbItemQuery(SecDbQueryRef query, CFArrayRef accessGroups, SecDbConnectionRef dbconn, CFErrorRef *error,
655 void (^handle_row)(SecDbItemRef item, bool *stop)) {
656 __block bool ok = true;
657 /* Sanity check the query. */
658 if (query->q_ref)
659 return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by queries"));
660
661 bool (^return_attr)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
662 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
663 // "rowid, data"
664 return attr->kind == kSecDbRowIdAttr || attr->kind == kSecDbEncryptedDataAttr;
665 };
666
667 CFStringRef sql = s3dl_select_sql(query, accessGroups);
668 ok = sql;
669 if (sql) {
670 ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) {
671 /* Bind the values being searched for to the SELECT statement. */
672 int param = 1;
673 if (query->q_class == &identity_class) {
674 /* Bind the access groups to cert.agrp. */
675 ok &= sqlBindAccessGroups(stmt, accessGroups, &param, error);
676 }
677 if (ok)
678 ok &= sqlBindWhereClause(stmt, query, accessGroups, &param, error);
679 if (ok) {
680 SecDbStep(dbconn, stmt, error, ^(bool *stop) {
681 SecDbItemRef itemFromStatement = SecDbItemCreateWithStatement(kCFAllocatorDefault, query->q_class, stmt, query->q_keybag, error, return_attr);
682 if (itemFromStatement) {
683 CFTransferRetained(itemFromStatement->credHandle, query->q_use_cred_handle);
684 if (match_item(dbconn, query, accessGroups, itemFromStatement->attributes))
685 handle_row(itemFromStatement, stop);
686 CFReleaseNull(itemFromStatement);
687 } else {
688 secerror("failed to create item from stmt: %@", error ? *error : (CFErrorRef)"no error");
689 if (error) {
690 CFReleaseNull(*error);
691 }
692 //*stop = true;
693 //ok = false;
694 }
695 });
696 }
697 });
698 CFRelease(sql);
699 }
700
701 return ok;
702 }
703
704 static bool
705 s3dl_query(s3dl_handle_row handle_row,
706 void *context, CFErrorRef *error)
707 {
708 struct s3dl_query_ctx *c = context;
709 SecDbConnectionRef dbt = c->dbt;
710 Query *q = c->q;
711 CFArrayRef accessGroups = c->accessGroups;
712
713 /* Sanity check the query. */
714 if (q->q_ref)
715 return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by queries"));
716
717 /* Actual work here. */
718 if (q->q_limit == 1) {
719 c->result = NULL;
720 } else {
721 c->result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
722 }
723 CFStringRef sql = s3dl_select_sql(q, accessGroups);
724 bool ok = SecDbWithSQL(dbt, sql, error, ^(sqlite3_stmt *stmt) {
725 bool sql_ok = true;
726 /* Bind the values being searched for to the SELECT statement. */
727 int param = 1;
728 if (q->q_class == &identity_class) {
729 /* Bind the access groups to cert.agrp. */
730 sql_ok = sqlBindAccessGroups(stmt, accessGroups, &param, error);
731 }
732 if (sql_ok)
733 sql_ok = sqlBindWhereClause(stmt, q, accessGroups, &param, error);
734 if (sql_ok) {
735 SecDbForEach(stmt, error, ^bool (int row_index) {
736 handle_row(stmt, context);
737
738 bool needs_auth = q->q_error && CFErrorGetCode(q->q_error) == errSecAuthNeeded;
739 if (q->q_skip_acl_items && needs_auth)
740 // Skip items needing authentication if we are told to do so.
741 CFReleaseNull(q->q_error);
742
743 bool stop = q->q_limit != kSecMatchUnlimited && c->found >= q->q_limit;
744 stop = stop || (q->q_error && !needs_auth);
745 return !stop;
746 });
747 }
748 return sql_ok;
749 });
750
751 CFRelease(sql);
752
753 // First get the error from the query, since errSecDuplicateItem from an
754 // update query should superceed the errSecItemNotFound below.
755 if (!query_error(q, error))
756 ok = false;
757 if (ok && c->found == 0)
758 ok = SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
759
760 return ok;
761 }
762
763 bool
764 s3dl_copy_matching(SecDbConnectionRef dbt, Query *q, CFTypeRef *result,
765 CFArrayRef accessGroups, CFErrorRef *error)
766 {
767 struct s3dl_query_ctx ctx = {
768 .q = q, .accessGroups = accessGroups, .dbt = dbt,
769 };
770 if (q->q_row_id && query_attr_count(q))
771 return SecError(errSecItemIllegalQuery, error,
772 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
773
774 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
775 if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
776 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
777 bool ok = s3dl_query(s3dl_query_row, &ctx, error);
778 if (ok && result)
779 *result = ctx.result;
780 else
781 CFReleaseSafe(ctx.result);
782
783 return ok;
784 }
785
786 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
787 static void query_set_attribute_with_desc(const SecDbAttr *desc, const void *value, Query *q) {
788 if (CFDictionaryContainsKey(q->q_item, desc->name)) {
789 CFIndex ix;
790 for (ix = 0; ix < q->q_attr_end; ++ix) {
791 if (CFEqual(desc->name, q->q_pairs[ix].key)) {
792 CFReleaseSafe(q->q_pairs[ix].value);
793 --q->q_attr_end;
794 for (; ix < q->q_attr_end; ++ix) {
795 q->q_pairs[ix] = q->q_pairs[ix + 1];
796 }
797 CFDictionaryRemoveValue(q->q_item, desc->name);
798 break;
799 }
800 }
801 }
802 query_add_attribute_with_desc(desc, value, q);
803 }
804
805 /* Update modification_date if needed. */
806 static void query_pre_update(Query *q) {
807 SecDbForEachAttr(q->q_class, desc) {
808 if (desc->kind == kSecDbModificationDateAttr) {
809 CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
810 query_set_attribute_with_desc(desc, now, q);
811 CFReleaseSafe(now);
812 }
813 }
814 }
815
816 /* Make sure all attributes that are marked as not_null have a value. If
817 force_date is false, only set mdat and cdat if they aren't already set. */
818 void query_pre_add(Query *q, bool force_date) {
819 CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
820 SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInFlag) {
821 if (desc->kind == kSecDbCreationDateAttr ||
822 desc->kind == kSecDbModificationDateAttr) {
823 if (force_date) {
824 query_set_attribute_with_desc(desc, now, q);
825 } else if (!CFDictionaryContainsKey(q->q_item, desc->name)) {
826 query_add_attribute_with_desc(desc, now, q);
827 }
828 } else if ((desc->flags & kSecDbNotNullFlag) &&
829 !CFDictionaryContainsKey(q->q_item, desc->name)) {
830 CFTypeRef value = NULL;
831 if (desc->flags & kSecDbDefault0Flag) {
832 if (desc->kind == kSecDbDateAttr)
833 value = CFDateCreate(kCFAllocatorDefault, 0.0);
834 else {
835 SInt32 vzero = 0;
836 value = CFNumberCreate(0, kCFNumberSInt32Type, &vzero);
837 }
838 } else if (desc->flags & kSecDbDefaultEmptyFlag) {
839 if (desc->kind == kSecDbDataAttr || desc->kind == kSecDbUUIDAttr)
840 value = CFDataCreate(kCFAllocatorDefault, NULL, 0);
841 else {
842 value = CFSTR("");
843 CFRetain(value);
844 }
845 }
846 if (value) {
847 /* Safe to use query_add_attribute here since the attr wasn't
848 set yet. */
849 query_add_attribute_with_desc(desc, value, q);
850 CFRelease(value);
851 }
852 }
853 }
854 CFReleaseSafe(now);
855 }
856
857 // Return a tri state value false->never make a tombstone, true->always make a
858 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
859 static CFBooleanRef s3dl_should_make_tombstone(Query *q, bool item_is_syncable, SecDbItemRef item) {
860 if (q->q_use_tomb)
861 return q->q_use_tomb;
862 else if (item_is_syncable && !SecDbItemIsTombstone(item))
863 return NULL;
864 else
865 return kCFBooleanFalse;
866 }
867 /* AUDIT[securityd](done):
868 attributesToUpdate (ok) is a caller provided dictionary,
869 only its cf types have been checked.
870 */
871 bool
872 s3dl_query_update(SecDbConnectionRef dbt, Query *q,
873 CFDictionaryRef attributesToUpdate, CFArrayRef accessGroups, CFErrorRef *error)
874 {
875 /* Sanity check the query. */
876 if (query_match_count(q) != 0)
877 return SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported in attributes to update"));
878 if (q->q_ref)
879 return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported in attributes to update"));
880 if (q->q_row_id && query_attr_count(q))
881 return SecError(errSecItemIllegalQuery, error, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
882
883 __block bool result = true;
884 Query *u = query_create(q->q_class, NULL, attributesToUpdate, error);
885 if (u == NULL) return false;
886 require_action_quiet(query_update_parse(u, attributesToUpdate, error), errOut, result = false);
887 query_pre_update(u);
888 result &= SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
889 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
890 if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
891 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
892 result &= SecDbItemQuery(q, accessGroups, dbt, error, ^(SecDbItemRef item, bool *stop) {
893 // We always need to know the error here.
894 CFErrorRef localError = NULL;
895 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
896 const SecDbAttr *sha1attr = SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, NULL);
897 CFDataRef storedSHA1 = CFRetainSafe(SecDbItemGetValue(item, sha1attr, NULL));
898 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, u->q_item, &localError);
899 SecDbItemSetValue(item, sha1attr, storedSHA1, NULL);
900 CFReleaseSafe(storedSHA1);
901 if (SecErrorGetOSStatus(localError) == errSecDecode) {
902 // We just ignore this, and treat as if item is not found.
903 secwarning("deleting corrupt %@,rowid=%" PRId64 " %@", q->q_class->name, SecDbItemGetRowId(item, NULL), localError);
904 CFReleaseNull(localError);
905 if (!SecDbItemDelete(item, dbt, false, &localError)) {
906 secerror("failed to delete corrupt %@,rowid=%" PRId64 " %@", q->q_class->name, SecDbItemGetRowId(item, NULL), localError);
907 CFReleaseNull(localError);
908 }
909 return;
910 }
911 if (new_item != NULL && u->q_access_control != NULL)
912 SecDbItemSetAccessControl(new_item, u->q_access_control, &localError);
913 result = SecErrorPropagate(localError, error) && new_item;
914 if (new_item) {
915 bool item_is_sync = SecDbItemIsSyncable(item);
916 result = SecDbItemUpdate(item, new_item, dbt, s3dl_should_make_tombstone(q, item_is_sync, item), error);
917 if (result) {
918 q->q_changed = true;
919 if (item_is_sync || SecDbItemIsSyncable(new_item))
920 q->q_sync_changed = true;
921 }
922 CFRelease(new_item);
923 }
924 });
925 if (!result)
926 *commit = false;
927 });
928 if (result && !q->q_changed)
929 result = SecError(errSecItemNotFound, error, CFSTR("No items updated"));
930 errOut:
931 if (!query_destroy(u, error))
932 result = false;
933 return result;
934 }
935
936 static bool SecDbItemNeedAuth(SecDbItemRef item, CFErrorRef *error)
937 {
938 CFErrorRef localError = NULL;
939 if (!SecDbItemEnsureDecrypted(item, &localError) && localError && CFErrorGetCode(localError) == errSecAuthNeeded) {
940 if (error)
941 *error = localError;
942 return true;
943 }
944
945 CFReleaseSafe(localError);
946 return false;
947 }
948
949 bool
950 s3dl_query_delete(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFErrorRef *error)
951 {
952 __block bool ok = true;
953 __block bool needAuth = false;
954 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
955 if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
956 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
957 ok &= SecDbItemSelect(q, dbt, error, NULL, ^bool(const SecDbAttr *attr) {
958 return false;
959 },^bool(CFMutableStringRef sql, bool *needWhere) {
960 SecDbAppendWhereClause(sql, q, accessGroups);
961 return true;
962 },^bool(sqlite3_stmt * stmt, int col) {
963 return sqlBindWhereClause(stmt, q, accessGroups, &col, error);
964 }, ^(SecDbItemRef item, bool *stop) {
965 // Check if item need to be authenticated by LocalAuthentication
966 item->cryptoOp = kAKSKeyOpDelete;
967 if (SecDbItemNeedAuth(item, error)) {
968 needAuth = true;
969 return;
970 }
971 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
972 const SecDbAttr *sha1attr = SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, NULL);
973 CFDataRef storedSHA1 = CFRetainSafe(SecDbItemGetValue(item, sha1attr, NULL));
974 bool item_is_sync = SecDbItemIsSyncable(item);
975 SecDbItemSetValue(item, sha1attr, storedSHA1, NULL);
976 CFReleaseSafe(storedSHA1);
977 ok = SecDbItemDelete(item, dbt, s3dl_should_make_tombstone(q, item_is_sync, item), error);
978 if (ok) {
979 q->q_changed = true;
980 if (item_is_sync)
981 q->q_sync_changed = true;
982 }
983 });
984 if (ok && !q->q_changed && !needAuth) {
985 ok = SecError(errSecItemNotFound, error, CFSTR("Delete failed to delete anything"));
986 }
987 return ok && !needAuth;
988 }
989
990 static bool
991 matchAnyString(CFStringRef needle, CFStringRef *haystack)
992 {
993 while (*haystack) {
994 if (CFEqual(needle, *haystack))
995 return true;
996 haystack++;
997 }
998 return false;
999 }
1000
1001 /* Return true iff the item in question should not be backed up, nor restored,
1002 but when restoring a backup the original version of the item should be
1003 added back to the keychain again after the restore completes. */
1004 static bool SecItemIsSystemBound(CFDictionaryRef item, const SecDbClass *cls, bool multiUser) {
1005 CFStringRef agrp = CFDictionaryGetValue(item, kSecAttrAccessGroup);
1006 if (!isString(agrp))
1007 return false;
1008
1009 if (CFEqualSafe(agrp, kSOSInternalAccessGroup)) {
1010 secdebug("backup", "found sysbound item: %@", item);
1011 return true;
1012 }
1013
1014 if (CFEqual(agrp, CFSTR("lockdown-identities"))) {
1015 secdebug("backup", "found sys_bound item: %@", item);
1016 return true;
1017 }
1018
1019 if (CFEqual(agrp, CFSTR("apple")) && cls == &genp_class) {
1020 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1021 CFStringRef account = CFDictionaryGetValue(item, kSecAttrAccount);
1022
1023 if (isString(service) && isString(account)) {
1024 static CFStringRef mcAccounts[] = {
1025 CFSTR("Public"),
1026 CFSTR("Private"),
1027 NULL,
1028 };
1029
1030 if (CFEqual(service, CFSTR("com.apple.managedconfiguration"))
1031 && matchAnyString(account, mcAccounts))
1032 {
1033 secdebug("backup", "found sys_bound item: %@", item);
1034 return true;
1035 }
1036 }
1037 }
1038
1039 if (multiUser && CFEqual(agrp, CFSTR("com.apple.apsd")) && cls == &genp_class) {
1040 static CFStringRef pushServices[] = {
1041 CFSTR("push.apple.com"),
1042 CFSTR("push.apple.com,PerAppToken.v0"),
1043 NULL
1044 };
1045 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1046
1047 if (isString(service) && matchAnyString(service, pushServices)) {
1048 secdebug("backup", "found sys_bound item: %@", item);
1049 return true;
1050 }
1051 }
1052
1053 if (multiUser && CFEqual(agrp, CFSTR("apple")) && cls == &genp_class) {
1054 static CFStringRef acountServices[] = {
1055 CFSTR("com.apple.account.AppleAccount.token"),
1056 CFSTR("com.apple.account.AppleAccount.password"),
1057 CFSTR("com.apple.account.AppleAccount.rpassword"),
1058 CFSTR("com.apple.account.idms.token"),
1059 CFSTR("com.apple.account.idms.heartbeat-token"),
1060 CFSTR("com.apple.account.idms.continuation-key"),
1061 CFSTR("com.apple.account.CloudKit.token"),
1062 CFSTR("com.apple.account.IdentityServices.password"), /* accountsd for ids */
1063 CFSTR("com.apple.account.IdentityServices.rpassword"),
1064 CFSTR("com.apple.account.IdentityServices.token"),
1065 CFSTR("BackupIDSAccountToken"),
1066 CFSTR("com.apple.ids"),
1067 CFSTR("ids"),
1068 CFSTR("IDS"),
1069 NULL
1070 };
1071 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1072
1073 if (isString(service) && matchAnyString(service, acountServices)) {
1074 secdebug("backup", "found exact sys_bound item: %@", item);
1075 return true;
1076 }
1077 if (isString(service) && CFEqual(service, CFSTR("com.apple.facetime"))) {
1078 CFStringRef account = CFDictionaryGetValue(item, kSecAttrAccount);
1079 if (isString(account) && CFEqual(account, CFSTR("registrationV1"))) {
1080 secdebug("backup", "found exact sys_bound item: %@", item);
1081 return true;
1082 }
1083 }
1084 }
1085
1086 if (multiUser && CFEqual(agrp, CFSTR("ichat")) && cls == &genp_class) {
1087 static CFStringRef acountServices[] = {
1088 CFSTR("ids"),
1089 NULL
1090 };
1091 CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
1092
1093 if (isString(service) && matchAnyString(service, acountServices)) {
1094 secdebug("backup", "found exact sys_bound item: %@", item);
1095 return true;
1096 }
1097 }
1098
1099 if (multiUser && CFEqual(agrp, CFSTR("ichat")) && cls == &keys_class) {
1100 static CFStringRef exactMatchingLabel[] = {
1101 CFSTR("iMessage Encryption Key"),
1102 CFSTR("iMessage Signing Key"),
1103 };
1104 CFStringRef label = CFDictionaryGetValue(item, kSecAttrLabel);
1105 if (isString(label)) {
1106 if (matchAnyString(label, exactMatchingLabel)) {
1107 secdebug("backup", "found exact sys_bound item: %@", item);
1108 return true;
1109 }
1110 }
1111 }
1112
1113
1114 secdebug("backup", "found non sys_bound item: %@", item);
1115 return false;
1116 }
1117
1118 /* Delete all items from the current keychain. If this is not an in
1119 place upgrade we don't delete items in the 'lockdown-identities'
1120 access group, this ensures that an import or restore of a backup
1121 will never overwrite an existing activation record. */
1122 static bool SecServerDeleteAll(SecDbConnectionRef dbt, CFErrorRef *error) {
1123 secwarning("SecServerDeleteAll");
1124
1125 return kc_transaction(dbt, error, ^{
1126
1127 bool ok = (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
1128 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
1129 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
1130 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
1131 return ok;
1132 });
1133 }
1134
1135 #if TARGET_OS_IPHONE
1136
1137 static bool DeleteAllFromTableForMUSRView(SecDbConnectionRef dbt,
1138 CFStringRef sql,
1139 CFDataRef musr,
1140 bool keepU,
1141 CFErrorRef *error)
1142 {
1143 sqlite3_stmt *stmt = NULL;
1144 CFStringRef sql2 = NULL;
1145 bool ok = false;
1146
1147 if (keepU) {
1148 sql2 = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ AND pdmn NOT IN ('aku','akpu','cku','dku')"), sql);
1149 } else {
1150 sql2 = CFRetain(sql);
1151 }
1152 require(sql2, fail);
1153
1154 stmt = SecDbCopyStmt(dbt, sql2, NULL, error);
1155 require(stmt, fail);
1156
1157 ok = SecDbBindObject(stmt, 1, musr, error);
1158 require(ok, fail);
1159
1160 ok = SecDbStep(dbt, stmt, error, ^(bool *stop) { });
1161 require(ok, fail);
1162
1163 fail:
1164 if (stmt) {
1165 ok = SecDbFinalize(stmt, error);
1166 }
1167 if (!ok)
1168 secwarning("DeleteAllFromTableForMUSRView failed for %@ for musr: %@: %@", sql2, musr, error ? *error : NULL);
1169
1170 CFReleaseNull(sql2);
1171
1172 return ok;
1173 }
1174
1175 bool SecServerDeleteAllForUser(SecDbConnectionRef dbt, CFDataRef musrView, bool keepU, CFErrorRef *error) {
1176 secwarning("SecServerDeleteAllForUser for user: %@ keepU %s", musrView, keepU ? "yes" : "no");
1177
1178 return kc_transaction(dbt, error, ^{
1179 bool ok;
1180
1181 ok = (DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM genp WHERE musr = ?"), musrView, keepU, error) &&
1182 DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM inet WHERE musr = ?"), musrView, keepU, error) &&
1183 DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM cert WHERE musr = ?"), musrView, keepU, error) &&
1184 DeleteAllFromTableForMUSRView(dbt, CFSTR("DELETE FROM keys WHERE musr = ?"), musrView, keepU, error));
1185
1186 return ok;
1187 });
1188 }
1189 #endif
1190
1191
1192 struct s3dl_export_row_ctx {
1193 struct s3dl_query_ctx qc;
1194 keybag_handle_t dest_keybag;
1195 enum SecItemFilter filter;
1196 bool multiUser;
1197 };
1198
1199 static void s3dl_export_row(sqlite3_stmt *stmt, void *context) {
1200 struct s3dl_export_row_ctx *c = context;
1201 Query *q = c->qc.q;
1202 SecAccessControlRef access_control = NULL;
1203 CFErrorRef localError = NULL;
1204
1205 /* Skip akpu items when backing up, those are intentionally lost across restores. */
1206 bool skip_akpu = c->filter == kSecBackupableItemFilter;
1207
1208 sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
1209 CFMutableDictionaryRef item;
1210 bool ok = s3dl_item_from_col(stmt, q, 1, c->qc.accessGroups, &item, &access_control, &localError);
1211
1212 bool is_akpu = access_control ? CFEqualSafe(SecAccessControlGetProtection(access_control),
1213 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) : false;
1214
1215 if (ok && item && !(skip_akpu && is_akpu)) {
1216 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1217 bool do_sys_bound = c->filter == kSecSysBoundItemFilter;
1218 if (c->filter == kSecNoItemFilter ||
1219 SecItemIsSystemBound(item, q->q_class, c->multiUser) == do_sys_bound) {
1220 /* Re-encode the item. */
1221 secdebug("item", "export rowid %llu item: %@", rowid, item);
1222 /* The code below could be moved into handle_row. */
1223 CFDataRef pref = _SecItemMakePersistentRef(q->q_class->name, rowid);
1224 if (pref) {
1225 if (c->dest_keybag != KEYBAG_NONE) {
1226 CFMutableDictionaryRef auth_attribs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1227 SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInAuthenticatedDataFlag) {
1228 CFTypeRef value = CFDictionaryGetValue(item, desc->name);
1229 if(value) {
1230 CFDictionaryAddValue(auth_attribs, desc->name, value);
1231 CFDictionaryRemoveValue(item, desc->name);
1232 }
1233 }
1234
1235 /* Encode and encrypt the item to the specified keybag. */
1236 CFDataRef edata = NULL;
1237 bool encrypted = ks_encrypt_data(c->dest_keybag, access_control, q->q_use_cred_handle, item, auth_attribs, &edata, &q->q_error);
1238 CFDictionaryRemoveAllValues(item);
1239 CFRelease(auth_attribs);
1240 if (encrypted) {
1241 CFDictionarySetValue(item, kSecValueData, edata);
1242 CFReleaseSafe(edata);
1243 } else {
1244 seccritical("ks_encrypt_data %@,rowid=%" PRId64 ": failed: %@", q->q_class->name, rowid, q->q_error);
1245 CFReleaseNull(q->q_error);
1246 }
1247 }
1248 if (CFDictionaryGetCount(item)) {
1249 CFDictionarySetValue(item, kSecValuePersistentRef, pref);
1250 CFArrayAppendValue((CFMutableArrayRef)c->qc.result, item);
1251 c->qc.found++;
1252 }
1253 CFReleaseSafe(pref);
1254 }
1255 }
1256 CFRelease(item);
1257 } else {
1258 OSStatus status = SecErrorGetOSStatus(localError);
1259
1260 if (status == errSecInteractionNotAllowed && is_akpu && skip_akpu) {
1261 // We expect akpu items to be inaccessible when the device is locked.
1262 CFReleaseNull(localError);
1263 } else {
1264 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1265 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1266 secinfo("item","Could not export item for rowid %llu: %@", rowid, localError);
1267
1268 if(status == errSecDecode) {
1269 CFReleaseNull(localError);
1270 } else {
1271 CFReleaseSafe(q->q_error);
1272 q->q_error=localError;
1273 }
1274 }
1275 }
1276 CFReleaseSafe(access_control);
1277 }
1278
1279 CFDictionaryRef
1280 SecServerCopyKeychainPlist(SecDbConnectionRef dbt,
1281 SecurityClient *client,
1282 keybag_handle_t src_keybag,
1283 keybag_handle_t dest_keybag,
1284 enum SecItemFilter filter,
1285 CFErrorRef *error) {
1286 CFMutableDictionaryRef keychain;
1287 keychain = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1288 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1289 if (!keychain) {
1290 if (error && !*error)
1291 SecError(errSecAllocate, error, CFSTR("Can't create keychain dictionary"));
1292 goto errOut;
1293 }
1294 unsigned class_ix;
1295 bool inMultiUser = false;
1296 Query q = { .q_keybag = src_keybag };
1297 q.q_return_type =
1298 kSecReturnDataMask |
1299 kSecReturnAttributesMask |
1300 kSecReturnPersistentRefMask;
1301 q.q_limit = kSecMatchUnlimited;
1302 q.q_skip_acl_items = true;
1303
1304 #if TARGET_OS_IPHONE
1305 if (client && client->inMultiUser) {
1306 q.q_musrView = SecMUSRCreateActiveUserUUID(client->uid);
1307 inMultiUser = true;
1308 } else
1309 #endif
1310 {
1311 q.q_musrView = SecMUSRGetSingleUserKeychainUUID();
1312 CFRetain(q.q_musrView);
1313 }
1314
1315 /* Get rid of this duplicate. */
1316 const SecDbClass *SecDbClasses[] = {
1317 &genp_class,
1318 &inet_class,
1319 &cert_class,
1320 &keys_class
1321 };
1322
1323 for (class_ix = 0; class_ix < array_size(SecDbClasses);
1324 ++class_ix) {
1325 q.q_class = SecDbClasses[class_ix];
1326 struct s3dl_export_row_ctx ctx = {
1327 .qc = { .q = &q, .dbt = dbt },
1328 .dest_keybag = dest_keybag, .filter = filter,
1329 .multiUser = inMultiUser,
1330 };
1331
1332 secnotice("item", "exporting class '%@'", q.q_class->name);
1333
1334 CFErrorRef localError = NULL;
1335 if (s3dl_query(s3dl_export_row, &ctx, &localError)) {
1336 if (CFArrayGetCount(ctx.qc.result))
1337 CFDictionaryAddValue(keychain, q.q_class->name, ctx.qc.result);
1338
1339 } else {
1340 OSStatus status = (OSStatus)CFErrorGetCode(localError);
1341 if (status == errSecItemNotFound) {
1342 CFRelease(localError);
1343 } else {
1344 secerror("Export failed: %@", localError);
1345 if (error) {
1346 CFReleaseSafe(*error);
1347 *error = localError;
1348 } else {
1349 CFRelease(localError);
1350 }
1351 CFReleaseNull(keychain);
1352 CFReleaseNull(ctx.qc.result);
1353 break;
1354 }
1355 }
1356 CFReleaseNull(ctx.qc.result);
1357 }
1358
1359 errOut:
1360 CFReleaseNull(q.q_musrView);
1361
1362 return keychain;
1363 }
1364
1365 struct SecServerImportClassState {
1366 SecDbConnectionRef dbt;
1367 CFErrorRef error;
1368 keybag_handle_t src_keybag;
1369 keybag_handle_t dest_keybag;
1370 SecurityClient *client;
1371 enum SecItemFilter filter;
1372 };
1373
1374 struct SecServerImportItemState {
1375 const SecDbClass *class;
1376 struct SecServerImportClassState *s;
1377 };
1378
1379 static void
1380 SecServerImportItem(const void *value, void *context)
1381 {
1382 struct SecServerImportItemState *state = (struct SecServerImportItemState *)context;
1383 bool inMultiUser = false;
1384 #if TARGET_OS_IPHONE
1385 if (state->s->client->inMultiUser)
1386 inMultiUser = true;
1387 #endif
1388
1389 if (state->s->error)
1390 return;
1391
1392 if (!isDictionary(value)) {
1393 SecError(errSecParam, &state->s->error, CFSTR("value %@ is not a dictionary"), value);
1394 return;
1395 }
1396
1397 CFDictionaryRef dict = (CFDictionaryRef)value;
1398
1399 secdebug("item", "Import Item : %@", dict);
1400
1401 /* We use the kSecSysBoundItemFilte to indicate that we don't
1402 * preserve rowid's during import.
1403 */
1404 if (state->s->filter == kSecBackupableItemFilter) {
1405 CFTypeRef pdmu;
1406
1407 /* We don't filter non sys_bound items during import since we know we
1408 * will never have any in this case.
1409 */
1410 if (SecItemIsSystemBound(dict, state->class, inMultiUser))
1411 return;
1412
1413 /*
1414 * Don't bother with u items when in edu mode since our current backup system
1415 * don't keep track of items that blongs to the device (u) but rather just
1416 * merge them into one blob.
1417 */
1418 if (inMultiUser && (pdmu = CFDictionaryGetValue(dict, kSecAttrAccessible))) {
1419 if (CFEqual(pdmu, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) ||
1420 CFEqual(pdmu, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) ||
1421 CFEqual(pdmu, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) ||
1422 CFEqual(pdmu, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly))
1423 {
1424 secdebug("item", "Skipping KU item : %@", dict);
1425 return;
1426 }
1427 }
1428 }
1429
1430 SecDbItemRef item;
1431
1432 /* This is sligthly confusing:
1433 - During upgrade all items are exported with KEYBAG_NONE.
1434 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1435 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1436 */
1437 if (state->s->src_keybag == KEYBAG_NONE) {
1438 item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, state->class, dict, state->s->dest_keybag, &state->s->error);
1439 } else {
1440 item = SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault, state->class, dict, state->s->src_keybag, state->s->dest_keybag, &state->s->error);
1441 }
1442
1443 /*
1444 *
1445 */
1446
1447 if (item) {
1448 CFDataRef musr = NULL;
1449 CFDataRef musrBackup = CFDictionaryGetValue(dict, kSecAttrMultiUser);
1450 CFDataRef systemKeychainUUID = SecMUSRGetSystemKeychainUUID();
1451 bool systemKeychain = CFEqualSafe(musrBackup, systemKeychainUUID);
1452
1453 #if TARGET_OS_IPHONE
1454 if (state->s->client && state->s->client->inMultiUser) {
1455 if (systemKeychain) {
1456 secwarning("system keychain not allowed in multi user mode for item: %@", item);
1457 } else {
1458 musr = SecMUSRCreateActiveUserUUID(state->s->client->uid);
1459 }
1460 } else
1461 #endif
1462 {
1463 if (systemKeychain) {
1464 musr = SecMUSRCopySystemKeychainUUID();
1465 } else {
1466 musr = SecMUSRGetSingleUserKeychainUUID();
1467 CFRetainSafe(musr);
1468 }
1469 }
1470 if (musr == NULL) {
1471 CFReleaseNull(item);
1472 } else {
1473 SecDbItemSetValueWithName(item, CFSTR("musr"), musr, &state->s->error);
1474 CFRelease(musr);
1475 }
1476 }
1477
1478 /*
1479 *
1480 */
1481
1482 if (item) {
1483 bool insertStatus;
1484
1485 if(state->s->filter != kSecSysBoundItemFilter) {
1486 SecDbItemExtractRowIdFromBackupDictionary(item, dict, &state->s->error);
1487 }
1488 SecDbItemInferSyncable(item, &state->s->error);
1489 insertStatus = SecDbItemInsert(item, state->s->dbt, &state->s->error);
1490 if (!insertStatus) {
1491 /*
1492 When running in EduMode, multiple users share the same
1493 keychain and unfortionaly the rowid is used a
1494 persistant reference and is part of the contraints (its
1495 UNIQUE), so lets clear the rowid and try to insert the
1496 entry again.
1497
1498 This even happens for normal operation because of
1499 SysBound entries, so in case of a failure, lets try
1500 again to insert the record.
1501 */
1502 SecDbItemClearRowId(item, NULL);
1503 SecDbItemInsert(item, state->s->dbt, &state->s->error);
1504 }
1505 }
1506
1507 /* Reset error if we had one, since we just skip the current item
1508 and continue importing what we can. */
1509 if (state->s->error) {
1510 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1511 item, state->class->name, state->s->error);
1512 CFReleaseNull(state->s->error);
1513 }
1514
1515 CFReleaseSafe(item);
1516 }
1517
1518 static void SecServerImportClass(const void *key, const void *value,
1519 void *context) {
1520 struct SecServerImportClassState *state =
1521 (struct SecServerImportClassState *)context;
1522 if (state->error)
1523 return;
1524 if (!isString(key)) {
1525 SecError(errSecParam, &state->error, CFSTR("class name %@ is not a string"), key);
1526 return;
1527 }
1528 const SecDbClass *class = kc_class_with_name(key);
1529 if (!class) {
1530 secwarning("Ignoring unknown key class '%@'", key);
1531 return;
1532 }
1533 if (class == &identity_class) {
1534 SecError(errSecParam, &state->error, CFSTR("attempt to import an identity"));
1535 return;
1536 }
1537 struct SecServerImportItemState item_state = {
1538 .class = class, .s = state,
1539 };
1540 if (isArray(value)) {
1541 CFArrayRef items = (CFArrayRef)value;
1542 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items), key, state->filter);
1543 CFArrayApplyFunction(items, CFRangeMake(0, CFArrayGetCount(items)),
1544 SecServerImportItem, &item_state);
1545 } else if (isDictionary(value)) {
1546 CFDictionaryRef item = (CFDictionaryRef)value;
1547 secwarning("Import %ld items of class %@ (filter %d)", (long)CFDictionaryGetCount(item), key, state->filter);
1548 SecServerImportItem(item, &item_state);
1549 } else {
1550 secwarning("Unknown value type for class %@ (filter %d)", key, state->filter);
1551 }
1552 }
1553
1554 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt, SecurityClient *client,
1555 keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
1556 CFDictionaryRef keychain, enum SecItemFilter filter, CFErrorRef *error) {
1557 bool ok = true;
1558
1559 CFDictionaryRef sys_bound = NULL;
1560 if (filter == kSecBackupableItemFilter) {
1561 /* Grab a copy of all the items for which SecItemIsSystemBound()
1562 returns true. */
1563 require(sys_bound = SecServerCopyKeychainPlist(dbt, client, KEYBAG_DEVICE,
1564 KEYBAG_NONE, kSecSysBoundItemFilter,
1565 error), errOut);
1566 }
1567
1568 /* Delete everything in the keychain. */
1569 #if TARGET_OS_IPHONE
1570 if (client->inMultiUser) {
1571 CFDataRef musrView = SecMUSRCreateActiveUserUUID(client->uid);
1572 require_action(musrView, errOut, ok = false);
1573 require_action(ok = SecServerDeleteAllForUser(dbt, musrView, true, error), errOut, CFReleaseNull(musrView));
1574 CFReleaseNull(musrView);
1575 } else
1576 #endif
1577 {
1578 require(ok = SecServerDeleteAll(dbt, error), errOut);
1579 }
1580
1581 struct SecServerImportClassState state = {
1582 .dbt = dbt,
1583 .src_keybag = src_keybag,
1584 .dest_keybag = dest_keybag,
1585 .client = client,
1586 .filter = filter,
1587 };
1588 /* Import the provided items, preserving rowids. */
1589 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain));
1590 CFDictionaryApplyFunction(keychain, SecServerImportClass, &state);
1591
1592 if (sys_bound) {
1593 state.src_keybag = KEYBAG_NONE;
1594 /* Import the items we preserved with random rowids. */
1595 state.filter = kSecSysBoundItemFilter;
1596 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound));
1597 CFDictionaryApplyFunction(sys_bound, SecServerImportClass, &state);
1598 }
1599 if (state.error) {
1600 if (error) {
1601 CFReleaseSafe(*error);
1602 *error = state.error;
1603 } else {
1604 CFRelease(state.error);
1605 }
1606 ok = false;
1607 }
1608
1609 errOut:
1610 CFReleaseSafe(sys_bound);
1611
1612 return ok;
1613 }
1614
1615 #pragma mark - key rolling support
1616 #if USE_KEYSTORE
1617
1618 struct check_generation_ctx {
1619 struct s3dl_query_ctx query_ctx;
1620 uint32_t current_generation;
1621 };
1622
1623 static void check_generation(sqlite3_stmt *stmt, void *context) {
1624 struct check_generation_ctx *c = context;
1625 CFDataRef blob = NULL;
1626 size_t blobLen = 0;
1627 const uint8_t *cursor = NULL;
1628 uint32_t version;
1629 keyclass_t keyclass;
1630 uint32_t current_generation = c->current_generation;
1631
1632 require(blob = s3dl_copy_data_from_col(stmt, 1, &c->query_ctx.q->q_error), out);
1633 blobLen = CFDataGetLength(blob);
1634 cursor = CFDataGetBytePtr(blob);
1635
1636 /* Check for underflow, ensuring we have at least one full AES block left. */
1637 if (blobLen < sizeof(version) + sizeof(keyclass)) {
1638 SecError(errSecDecode, &c->query_ctx.q->q_error, CFSTR("check_generation: Check for underflow"));
1639 goto out;
1640 }
1641
1642 version = *((uint32_t *)cursor);
1643 cursor += sizeof(version);
1644
1645 (void) version; // TODO: do something with the version number.
1646
1647 keyclass = *((keyclass_t *)cursor);
1648
1649 // TODO: export get_key_gen macro
1650 if (((keyclass & ~key_class_last) == 0) != (current_generation == 0)) {
1651 c->query_ctx.found++;
1652 }
1653
1654 CFReleaseSafe(blob);
1655 return;
1656
1657 out:
1658 c->query_ctx.found++;
1659 CFReleaseSafe(blob);
1660 }
1661
1662 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt, uint32_t current_generation, CFErrorRef *error) {
1663 CFErrorRef localError = NULL;
1664 struct check_generation_ctx ctx = { .query_ctx = { .dbt = dbt }, .current_generation = current_generation };
1665
1666 const SecDbClass *classes[] = {
1667 &genp_class,
1668 &inet_class,
1669 &keys_class,
1670 &cert_class,
1671 };
1672
1673 for (size_t class_ix = 0; class_ix < array_size(classes); ++class_ix) {
1674 Query *q = query_create(classes[class_ix], NULL, NULL, &localError);
1675 if (!q)
1676 return false;
1677
1678 ctx.query_ctx.q = q;
1679 q->q_limit = kSecMatchUnlimited;
1680
1681 bool ok = s3dl_query(check_generation, &ctx, &localError);
1682 query_destroy(q, NULL);
1683 CFReleaseNull(ctx.query_ctx.result);
1684
1685 if (!ok && localError && (CFErrorGetCode(localError) == errSecItemNotFound)) {
1686 CFReleaseNull(localError);
1687 continue;
1688 }
1689 secerror("Class %@ not up to date", classes[class_ix]->name);
1690 return false;
1691 }
1692 return true;
1693 }
1694
1695 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt, SecurityClient *client, CFErrorRef *error) {
1696 return SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
1697 __block bool ok = false;
1698 uint32_t keystore_generation_status;
1699
1700 /* can we migrate to new class keys right now? */
1701 if (!aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status) &&
1702 (keystore_generation_status & generation_change_in_progress)) {
1703
1704 /* take a lock assertion */
1705 bool operated_while_unlocked = SecAKSDoWhileUserBagLocked(error, ^{
1706 CFErrorRef localError = NULL;
1707 CFDictionaryRef backup = SecServerCopyKeychainPlist(dbt, NULL,
1708 KEYBAG_DEVICE, KEYBAG_NONE, kSecNoItemFilter, &localError);
1709 if (backup) {
1710 if (localError) {
1711 secerror("Ignoring export error: %@ during roll export", localError);
1712 CFReleaseNull(localError);
1713 }
1714 ok = SecServerImportKeychainInPlist(dbt, client, KEYBAG_NONE,
1715 KEYBAG_DEVICE, backup, kSecNoItemFilter, &localError);
1716 if (localError) {
1717 secerror("Ignoring export error: %@ during roll export", localError);
1718 CFReleaseNull(localError);
1719 }
1720 CFRelease(backup);
1721 }
1722 });
1723 if (!operated_while_unlocked)
1724 ok = false;
1725 } else {
1726 ok = SecError(errSecBadReq, error, CFSTR("No key roll in progress."));
1727 }
1728
1729 *commit = ok;
1730 });
1731 }
1732 #endif