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