]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemDb.c
b8a6cc4ab6fe3525cbbbdac6b3e58a713c60a102
[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. */
1235 bool skip_akpu = c->filter == kSecBackupableItemFilter;
1236
1237 sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
1238 CFMutableDictionaryRef item;
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
1244 if (ok && item && !(skip_akpu && is_akpu)) {
1245 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1246 bool do_sys_bound = c->filter == kSecSysBoundItemFilter;
1247 if (c->filter == kSecNoItemFilter ||
1248 SecItemIsSystemBound(item, q->q_class, c->multiUser) == do_sys_bound) {
1249 /* Re-encode the item. */
1250 secdebug("item", "export rowid %llu item: %@", rowid, item);
1251 /* The code below could be moved into handle_row. */
1252 CFDataRef pref = _SecItemMakePersistentRef(q->q_class->name, rowid);
1253 if (pref) {
1254 if (c->dest_keybag != KEYBAG_NONE) {
1255 CFMutableDictionaryRef auth_attribs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1256 SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInAuthenticatedDataFlag) {
1257 CFTypeRef value = CFDictionaryGetValue(item, desc->name);
1258 if(value) {
1259 CFDictionaryAddValue(auth_attribs, desc->name, value);
1260 CFDictionaryRemoveValue(item, desc->name);
1261 }
1262 }
1263
1264 /* Encode and encrypt the item to the specified keybag. */
1265 CFDataRef edata = NULL;
1266 bool encrypted = ks_encrypt_data(c->dest_keybag, access_control, q->q_use_cred_handle, item, auth_attribs, &edata, false, &q->q_error);
1267 CFDictionaryRemoveAllValues(item);
1268 CFRelease(auth_attribs);
1269 if (encrypted) {
1270 CFDictionarySetValue(item, kSecValueData, edata);
1271 CFReleaseSafe(edata);
1272 } else {
1273 seccritical("ks_encrypt_data %@,rowid=%" PRId64 ": failed: %@", q->q_class->name, rowid, q->q_error);
1274 CFReleaseNull(q->q_error);
1275 }
1276 }
1277 if (CFDictionaryGetCount(item)) {
1278 CFDictionarySetValue(item, kSecValuePersistentRef, pref);
1279 CFArrayAppendValue((CFMutableArrayRef)c->qc.result, item);
1280 c->qc.found++;
1281 }
1282 CFReleaseSafe(pref);
1283 }
1284 }
1285 CFRelease(item);
1286 } else {
1287 OSStatus status = SecErrorGetOSStatus(localError);
1288
1289 if (status == errSecInteractionNotAllowed && is_akpu && skip_akpu) {
1290 // We expect akpu items to be inaccessible when the device is locked.
1291 CFReleaseNull(localError);
1292 } else {
1293 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1294 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1295 secinfo("item","Could not export item for rowid %llu: %@", rowid, localError);
1296
1297 if(status == errSecDecode) {
1298 CFReleaseNull(localError);
1299 } else {
1300 CFReleaseSafe(q->q_error);
1301 q->q_error=localError;
1302 }
1303 }
1304 }
1305 CFReleaseSafe(access_control);
1306 }
1307
1308 static CFStringRef
1309 SecCreateKeybagUUID(keybag_handle_t keybag)
1310 {
1311 #if !TARGET_HAS_KEYSTORE
1312 return NULL;
1313 #else
1314 char uuidstr[37];
1315 uuid_t uuid;
1316 if (aks_get_bag_uuid(keybag, uuid) != KERN_SUCCESS)
1317 return NULL;
1318 uuid_unparse_lower(uuid, uuidstr);
1319 return CFStringCreateWithCString(NULL, uuidstr, kCFStringEncodingUTF8);
1320 #endif
1321 }
1322
1323
1324 CFDictionaryRef
1325 SecServerCopyKeychainPlist(SecDbConnectionRef dbt,
1326 SecurityClient *client,
1327 keybag_handle_t src_keybag,
1328 keybag_handle_t dest_keybag,
1329 enum SecItemFilter filter,
1330 CFErrorRef *error) {
1331 CFMutableDictionaryRef keychain;
1332 keychain = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1333 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1334 unsigned class_ix;
1335 bool inMultiUser = false;
1336 CFStringRef keybaguuid = NULL;
1337 Query q = { .q_keybag = src_keybag,
1338 .q_musrView = NULL
1339 };
1340
1341 if (!keychain) {
1342 if (error && !*error)
1343 SecError(errSecAllocate, error, CFSTR("Can't create keychain dictionary"));
1344 goto errOut;
1345 }
1346
1347 q.q_return_type =
1348 kSecReturnDataMask |
1349 kSecReturnAttributesMask |
1350 kSecReturnPersistentRefMask;
1351 q.q_limit = kSecMatchUnlimited;
1352 q.q_skip_acl_items = true;
1353
1354
1355 #if TARGET_OS_IPHONE
1356 if (client && client->inMultiUser) {
1357 q.q_musrView = SecMUSRCreateActiveUserUUID(client->uid);
1358 inMultiUser = true;
1359 } else
1360 #endif
1361 {
1362 q.q_musrView = SecMUSRGetSingleUserKeychainUUID();
1363 CFRetain(q.q_musrView);
1364
1365 keybaguuid = SecCreateKeybagUUID(dest_keybag);
1366 if (keybaguuid)
1367 CFDictionarySetValue(keychain, kSecBackupKeybagUUIDKey, keybaguuid);
1368 }
1369
1370 /* Get rid of this duplicate. */
1371 const SecDbClass *SecDbClasses[] = {
1372 &genp_class,
1373 &inet_class,
1374 &cert_class,
1375 &keys_class
1376 };
1377
1378 for (class_ix = 0; class_ix < array_size(SecDbClasses);
1379 ++class_ix) {
1380 q.q_class = SecDbClasses[class_ix];
1381 struct s3dl_export_row_ctx ctx = {
1382 .qc = { .q = &q, .dbt = dbt },
1383 .dest_keybag = dest_keybag, .filter = filter,
1384 .multiUser = inMultiUser,
1385 };
1386
1387 secnotice("item", "exporting class '%@'", q.q_class->name);
1388
1389 CFErrorRef localError = NULL;
1390 if (s3dl_query(s3dl_export_row, &ctx, &localError)) {
1391 if (CFArrayGetCount(ctx.qc.result))
1392 CFDictionaryAddValue(keychain, q.q_class->name, ctx.qc.result);
1393
1394 } else {
1395 OSStatus status = (OSStatus)CFErrorGetCode(localError);
1396 if (status == errSecItemNotFound) {
1397 CFRelease(localError);
1398 } else {
1399 secerror("Export failed: %@", localError);
1400 if (error) {
1401 CFReleaseSafe(*error);
1402 *error = localError;
1403 } else {
1404 CFRelease(localError);
1405 }
1406 CFReleaseNull(keychain);
1407 CFReleaseNull(ctx.qc.result);
1408 break;
1409 }
1410 }
1411 CFReleaseNull(ctx.qc.result);
1412 }
1413
1414 errOut:
1415 CFReleaseNull(q.q_musrView);
1416 CFReleaseNull(keybaguuid);
1417
1418 return keychain;
1419 }
1420
1421 struct SecServerImportClassState {
1422 SecDbConnectionRef dbt;
1423 CFErrorRef error;
1424 keybag_handle_t src_keybag;
1425 keybag_handle_t dest_keybag;
1426 SecurityClient *client;
1427 enum SecItemFilter filter;
1428 };
1429
1430 struct SecServerImportItemState {
1431 const SecDbClass *class;
1432 struct SecServerImportClassState *s;
1433 };
1434
1435 static void
1436 SecServerImportItem(const void *value, void *context)
1437 {
1438 struct SecServerImportItemState *state = (struct SecServerImportItemState *)context;
1439 bool inMultiUser = false;
1440 #if TARGET_OS_IPHONE
1441 if (state->s->client->inMultiUser)
1442 inMultiUser = true;
1443 #endif
1444
1445 if (state->s->error)
1446 return;
1447
1448 if (!isDictionary(value)) {
1449 SecError(errSecParam, &state->s->error, CFSTR("value %@ is not a dictionary"), value);
1450 return;
1451 }
1452
1453 CFDictionaryRef dict = (CFDictionaryRef)value;
1454
1455 secdebug("item", "Import Item : %@", dict);
1456
1457 /* We use the kSecSysBoundItemFilter to indicate that we don't
1458 * preserve rowid's during import.
1459 */
1460 if (state->s->filter == kSecBackupableItemFilter) {
1461 CFTypeRef pdmu;
1462
1463 /* We don't filter non sys_bound items during import since we know we
1464 * will never have any in this case.
1465 */
1466 if (SecItemIsSystemBound(dict, state->class, inMultiUser))
1467 return;
1468
1469 /*
1470 * Don't bother with u items when in edu mode since our current backup system
1471 * don't keep track of items that blongs to the device (u) but rather just
1472 * merge them into one blob.
1473 */
1474 if (inMultiUser && (pdmu = CFDictionaryGetValue(dict, kSecAttrAccessible))) {
1475 if (CFEqual(pdmu, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) ||
1476 CFEqual(pdmu, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) ||
1477 CFEqual(pdmu, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) ||
1478 CFEqual(pdmu, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly))
1479 {
1480 secdebug("item", "Skipping KU item : %@", dict);
1481 return;
1482 }
1483 }
1484 }
1485
1486 SecDbItemRef item;
1487
1488 /* This is sligthly confusing:
1489 - During upgrade all items are exported with KEYBAG_NONE.
1490 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1491 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1492 */
1493 if (state->s->src_keybag == KEYBAG_NONE) {
1494 item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, state->class, dict, state->s->dest_keybag, &state->s->error);
1495 } else {
1496 item = SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault, state->class, dict, state->s->src_keybag, state->s->dest_keybag, &state->s->error);
1497 }
1498
1499 /*
1500 *
1501 */
1502
1503 if (item) {
1504 CFDataRef musr = NULL;
1505 CFDataRef musrBackup = CFDictionaryGetValue(dict, kSecAttrMultiUser);
1506 CFDataRef systemKeychainUUID = SecMUSRGetSystemKeychainUUID();
1507 bool systemKeychain = CFEqualSafe(musrBackup, systemKeychainUUID);
1508
1509 #if TARGET_OS_IPHONE
1510 if (state->s->client && state->s->client->inMultiUser) {
1511 if (systemKeychain) {
1512 secwarning("system keychain not allowed in multi user mode for item: %@", item);
1513 } else {
1514 musr = SecMUSRCreateActiveUserUUID(state->s->client->uid);
1515 }
1516 } else
1517 #endif
1518 {
1519 if (systemKeychain) {
1520 musr = SecMUSRCopySystemKeychainUUID();
1521 } else {
1522 musr = SecMUSRGetSingleUserKeychainUUID();
1523 CFRetainSafe(musr);
1524 }
1525 }
1526 if (musr == NULL) {
1527 CFReleaseNull(item);
1528 } else {
1529 SecDbItemSetValueWithName(item, CFSTR("musr"), musr, &state->s->error);
1530 CFRelease(musr);
1531 }
1532 }
1533
1534 /*
1535 *
1536 */
1537
1538 if (item) {
1539 bool insertStatus;
1540
1541 if(state->s->filter != kSecSysBoundItemFilter) {
1542 SecDbItemExtractRowIdFromBackupDictionary(item, dict, &state->s->error);
1543 }
1544 SecDbItemInferSyncable(item, &state->s->error);
1545 insertStatus = SecDbItemInsert(item, state->s->dbt, &state->s->error);
1546 if (!insertStatus) {
1547 /*
1548 When running in EduMode, multiple users share the same
1549 keychain and unfortionaly the rowid is used a
1550 persistant reference and is part of the contraints (its
1551 UNIQUE), so lets clear the rowid and try to insert the
1552 entry again.
1553
1554 This even happens for normal operation because of
1555 SysBound entries, so in case of a failure, lets try
1556 again to insert the record.
1557 */
1558 SecDbItemClearRowId(item, NULL);
1559 SecDbItemInsert(item, state->s->dbt, &state->s->error);
1560 }
1561 }
1562
1563 /* Reset error if we had one, since we just skip the current item
1564 and continue importing what we can. */
1565 if (state->s->error) {
1566 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1567 item, state->class->name, state->s->error);
1568 CFReleaseNull(state->s->error);
1569 }
1570
1571 CFReleaseSafe(item);
1572 }
1573
1574 static void SecServerImportClass(const void *key, const void *value,
1575 void *context) {
1576 struct SecServerImportClassState *state =
1577 (struct SecServerImportClassState *)context;
1578 if (state->error)
1579 return;
1580 if (!isString(key)) {
1581 SecError(errSecParam, &state->error, CFSTR("class name %@ is not a string"), key);
1582 return;
1583 }
1584 /* ignore the Keybag UUID */
1585 if (CFEqual(key, kSecBackupKeybagUUIDKey))
1586 return;
1587 const SecDbClass *class = kc_class_with_name(key);
1588 if (!class) {
1589 secwarning("Ignoring unknown key class '%@'", key);
1590 return;
1591 }
1592 if (class == &identity_class) {
1593 SecError(errSecParam, &state->error, CFSTR("attempt to import an identity"));
1594 return;
1595 }
1596 struct SecServerImportItemState item_state = {
1597 .class = class, .s = state,
1598 };
1599 if (isArray(value)) {
1600 CFArrayRef items = (CFArrayRef)value;
1601 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items), key, state->filter);
1602 CFArrayApplyFunction(items, CFRangeMake(0, CFArrayGetCount(items)),
1603 SecServerImportItem, &item_state);
1604 } else if (isDictionary(value)) {
1605 CFDictionaryRef item = (CFDictionaryRef)value;
1606 secwarning("Import %ld items of class %@ (filter %d)", (long)CFDictionaryGetCount(item), key, state->filter);
1607 SecServerImportItem(item, &item_state);
1608 } else {
1609 secwarning("Unknown value type for class %@ (filter %d)", key, state->filter);
1610 }
1611 }
1612
1613 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt, SecurityClient *client,
1614 keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
1615 CFDictionaryRef keychain, enum SecItemFilter filter, CFErrorRef *error) {
1616 CFStringRef keybaguuid = NULL;
1617 bool ok = true;
1618
1619 CFDictionaryRef sys_bound = NULL;
1620 if (filter == kSecBackupableItemFilter) {
1621 /* Grab a copy of all the items for which SecItemIsSystemBound()
1622 returns true. */
1623 require(sys_bound = SecServerCopyKeychainPlist(dbt, client, KEYBAG_DEVICE,
1624 KEYBAG_NONE, kSecSysBoundItemFilter,
1625 error), errOut);
1626 }
1627
1628 /*
1629 * Validate the uuid of the source keybag matches what we have in the backup
1630 */
1631 keybaguuid = SecCreateKeybagUUID(src_keybag);
1632 if (keybaguuid) {
1633 CFStringRef uuid = CFDictionaryGetValue(keychain, kSecBackupKeybagUUIDKey);
1634 if (isString(uuid)) {
1635 require_action(CFEqual(keybaguuid, uuid), errOut,
1636 SecError(errSecDecode, error, CFSTR("Keybag UUID (%@) mismatch with backup (%@)"),
1637 keybaguuid, uuid));
1638 }
1639 }
1640
1641 /* Delete everything in the keychain. */
1642 #if TARGET_OS_IPHONE
1643 if (client->inMultiUser) {
1644 CFDataRef musrView = SecMUSRCreateActiveUserUUID(client->uid);
1645 require_action(musrView, errOut, ok = false);
1646 require_action(ok = SecServerDeleteAllForUser(dbt, musrView, true, error), errOut, CFReleaseNull(musrView));
1647 CFReleaseNull(musrView);
1648 } else
1649 #endif
1650 {
1651 require(ok = SecServerDeleteAll(dbt, error), errOut);
1652 }
1653
1654 struct SecServerImportClassState state = {
1655 .dbt = dbt,
1656 .src_keybag = src_keybag,
1657 .dest_keybag = dest_keybag,
1658 .client = client,
1659 .filter = filter,
1660 };
1661 /* Import the provided items, preserving rowids. */
1662 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain));
1663 CFDictionaryApplyFunction(keychain, SecServerImportClass, &state);
1664
1665 if (sys_bound) {
1666 state.src_keybag = KEYBAG_NONE;
1667 /* Import the items we preserved with random rowids. */
1668 state.filter = kSecSysBoundItemFilter;
1669 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound));
1670 CFDictionaryApplyFunction(sys_bound, SecServerImportClass, &state);
1671 }
1672 if (state.error) {
1673 if (error) {
1674 CFReleaseSafe(*error);
1675 *error = state.error;
1676 } else {
1677 CFRelease(state.error);
1678 }
1679 ok = false;
1680 }
1681
1682 errOut:
1683 CFReleaseSafe(sys_bound);
1684 CFReleaseSafe(keybaguuid);
1685
1686 return ok;
1687 }
1688
1689 CFStringRef
1690 SecServerBackupGetKeybagUUID(CFDictionaryRef keychain)
1691 {
1692 CFStringRef uuid = CFDictionaryGetValue(keychain, kSecBackupKeybagUUIDKey);
1693 if (!isString(uuid))
1694 return NULL;
1695 return uuid;
1696 }
1697
1698 #pragma mark - key rolling support
1699 #if USE_KEYSTORE
1700
1701 struct check_generation_ctx {
1702 struct s3dl_query_ctx query_ctx;
1703 uint32_t current_generation;
1704 };
1705
1706 static void check_generation(sqlite3_stmt *stmt, void *context) {
1707 struct check_generation_ctx *c = context;
1708 CFDataRef blob = NULL;
1709 size_t blobLen = 0;
1710 const uint8_t *cursor = NULL;
1711 uint32_t version;
1712 keyclass_t keyclass;
1713 uint32_t current_generation = c->current_generation;
1714
1715 require(blob = s3dl_copy_data_from_col(stmt, 1, &c->query_ctx.q->q_error), out);
1716 blobLen = CFDataGetLength(blob);
1717 cursor = CFDataGetBytePtr(blob);
1718
1719 /* Check for underflow, ensuring we have at least one full AES block left. */
1720 if (blobLen < sizeof(version) + sizeof(keyclass)) {
1721 SecError(errSecDecode, &c->query_ctx.q->q_error, CFSTR("check_generation: Check for underflow"));
1722 goto out;
1723 }
1724
1725 version = *((uint32_t *)cursor);
1726 cursor += sizeof(version);
1727
1728 (void) version; // TODO: do something with the version number.
1729
1730 keyclass = *((keyclass_t *)cursor);
1731
1732 // TODO: export get_key_gen macro
1733 if (((keyclass & ~key_class_last) == 0) != (current_generation == 0)) {
1734 c->query_ctx.found++;
1735 }
1736
1737 CFReleaseSafe(blob);
1738 return;
1739
1740 out:
1741 c->query_ctx.found++;
1742 CFReleaseSafe(blob);
1743 }
1744
1745 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt, uint32_t current_generation, CFErrorRef *error) {
1746 CFErrorRef localError = NULL;
1747 struct check_generation_ctx ctx = { .query_ctx = { .dbt = dbt }, .current_generation = current_generation };
1748
1749 const SecDbClass *classes[] = {
1750 &genp_class,
1751 &inet_class,
1752 &keys_class,
1753 &cert_class,
1754 };
1755
1756 for (size_t class_ix = 0; class_ix < array_size(classes); ++class_ix) {
1757 Query *q = query_create(classes[class_ix], NULL, NULL, &localError);
1758 if (!q)
1759 return false;
1760
1761 ctx.query_ctx.q = q;
1762 q->q_limit = kSecMatchUnlimited;
1763
1764 bool ok = s3dl_query(check_generation, &ctx, &localError);
1765 query_destroy(q, NULL);
1766 CFReleaseNull(ctx.query_ctx.result);
1767
1768 if (!ok && localError && (CFErrorGetCode(localError) == errSecItemNotFound)) {
1769 CFReleaseNull(localError);
1770 continue;
1771 }
1772 secerror("Class %@ not up to date", classes[class_ix]->name);
1773 return false;
1774 }
1775 return true;
1776 }
1777
1778 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt, SecurityClient *client, CFErrorRef *error) {
1779 return SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
1780 __block bool ok = false;
1781 uint32_t keystore_generation_status;
1782
1783 /* can we migrate to new class keys right now? */
1784 if (!aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status) &&
1785 (keystore_generation_status & generation_change_in_progress)) {
1786
1787 /* take a lock assertion */
1788 bool operated_while_unlocked = SecAKSDoWhileUserBagLocked(error, ^{
1789 CFErrorRef localError = NULL;
1790 CFDictionaryRef backup = SecServerCopyKeychainPlist(dbt, NULL,
1791 KEYBAG_DEVICE, KEYBAG_NONE, kSecNoItemFilter, &localError);
1792 if (backup) {
1793 if (localError) {
1794 secerror("Ignoring export error: %@ during roll export", localError);
1795 CFReleaseNull(localError);
1796 }
1797 ok = SecServerImportKeychainInPlist(dbt, client, KEYBAG_NONE,
1798 KEYBAG_DEVICE, backup, kSecNoItemFilter, &localError);
1799 if (localError) {
1800 secerror("Ignoring export error: %@ during roll export", localError);
1801 CFReleaseNull(localError);
1802 }
1803 CFRelease(backup);
1804 }
1805 });
1806 if (!operated_while_unlocked)
1807 ok = false;
1808 } else {
1809 ok = SecError(errSecBadReq, error, CFSTR("No key roll in progress."));
1810 }
1811
1812 *commit = ok;
1813 });
1814 }
1815 #endif