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