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