2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 * SecItemDb.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
30 #include <securityd/SecItemDb.h>
31 #include <utilities/SecAKSWrappers.h>
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/SecSignpost.h>
40 #include <Security/SecItemPriv.h>
41 #include <Security/SecItemInternal.h>
42 #include <securityd/SOSCloudCircleServer.h>
43 #include <utilities/array_size.h>
44 #include <utilities/SecIOFormat.h>
45 #include <utilities/SecCFCCWrappers.h>
46 #include <SecAccessControlPriv.h>
47 #include <uuid/uuid.h>
48 #include "sec_action.h"
50 #include "keychain/ckks/CKKS.h"
52 #define kSecBackupKeybagUUIDKey CFSTR("keybag-uuid")
54 const SecDbAttr
*SecDbAttrWithKey(const SecDbClass
*c
,
57 /* Special case: identites can have all attributes of either cert
59 if (c
== identity_class()) {
60 const SecDbAttr
*desc
;
61 if (!(desc
= SecDbAttrWithKey(cert_class(), key
, 0)))
62 desc
= SecDbAttrWithKey(keys_class(), key
, error
);
67 SecDbForEachAttr(c
, a
) {
68 if (CFEqual(a
->name
, key
))
71 if (CFEqual(kSecAttrNoLegacy
, key
)) {
72 return NULL
; /* results in no ops for this attribute */
76 SecError(errSecNoSuchAttr
, error
, CFSTR("attribute %@ not found in class %@"), key
, c
->name
);
81 bool kc_transaction(SecDbConnectionRef dbt
, CFErrorRef
*error
, bool(^perform
)(void)) {
82 return kc_transaction_type(dbt
, kSecDbExclusiveTransactionType
, error
, perform
);
85 bool kc_transaction_type(SecDbConnectionRef dbt
, SecDbTransactionType type
, CFErrorRef
*error
, bool(^perform
)(void)) {
86 __block
bool ok
= true;
87 return ok
&& SecDbTransaction(dbt
, type
, error
, ^(bool *commit
) {
88 ok
= *commit
= perform();
92 static CFStringRef
SecDbGetKindSQL(SecDbAttrKind kind
) {
98 case kSecDbPrimaryKeyAttr
:
99 case kSecDbEncryptedDataAttr
:
100 return CFSTR("BLOB");
101 case kSecDbAccessAttr
:
102 case kSecDbStringAttr
:
103 return CFSTR("TEXT");
104 case kSecDbNumberAttr
:
107 return CFSTR("INTEGER");
109 case kSecDbCreationDateAttr
:
110 case kSecDbModificationDateAttr
:
111 return CFSTR("REAL");
112 case kSecDbRowIdAttr
:
113 return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
114 case kSecDbAccessControlAttr
:
115 case kSecDbUTombAttr
:
116 /* This attribute does not exist in the DB. */
121 static void SecDbAppendUnique(CFMutableStringRef sql
, CFStringRef value
, bool *haveUnique
) {
124 CFStringAppend(sql
, CFSTR("UNIQUE("));
126 SecDbAppendElement(sql
, value
, haveUnique
);
129 static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
130 CFStringAppendFormat(sql
, 0, CFSTR("CREATE TABLE %@("), c
->name
);
131 SecDbForEachAttrWithMask(c
,desc
,kSecDbInFlag
) {
132 CFStringAppendFormat(sql
, 0, CFSTR("%@ %@"), desc
->name
, SecDbGetKindSQL(desc
->kind
));
133 if (desc
->flags
& kSecDbNotNullFlag
)
134 CFStringAppend(sql
, CFSTR(" NOT NULL"));
135 if (desc
->flags
& kSecDbDefault0Flag
)
136 CFStringAppend(sql
, CFSTR(" DEFAULT 0"));
137 if (desc
->flags
& kSecDbDefaultEmptyFlag
)
138 CFStringAppend(sql
, CFSTR(" DEFAULT ''"));
139 CFStringAppend(sql
, CFSTR(","));
142 bool haveUnique
= false;
143 SecDbForEachAttrWithMask(c
,desc
,kSecDbPrimaryKeyFlag
| kSecDbInFlag
) {
144 SecDbAppendUnique(sql
, desc
->name
, &haveUnique
);
147 CFStringAppend(sql
, CFSTR(")"));
149 CFStringAppend(sql
, CFSTR(");"));
152 SecDbForEachAttrWithMask(c
,desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
153 if (desc
->kind
== kSecDbSyncAttr
) {
154 CFStringAppendFormat(sql
, 0, CFSTR("CREATE INDEX %@%@0 ON %@(%@) WHERE %@=0;"), c
->name
, desc
->name
, c
->name
, desc
->name
, desc
->name
);
156 CFStringAppendFormat(sql
, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c
->name
, desc
->name
, c
->name
, desc
->name
);
161 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
162 CFStringAppendFormat(sql
, 0, CFSTR("DROP TABLE %@;"), c
->name
);
165 static CFDataRef
SecPersistentRefCreateWithItem(SecDbItemRef item
, CFErrorRef
*error
) {
166 sqlite3_int64 row_id
= SecDbItemGetRowId(item
, error
);
168 return _SecItemCreatePersistentRef(SecDbItemGetClass(item
)->name
, row_id
, item
->attributes
);
172 bool SecItemDbCreateSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFArrayRef classIndexesForNewTables
, bool includeVersion
, CFErrorRef
*error
)
174 __block
bool ok
= true;
175 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
177 if (classIndexesForNewTables
) {
178 CFArrayForEach(classIndexesForNewTables
, ^(const void* index
) {
179 const SecDbClass
* class = schema
->classes
[(int)index
];
180 SecDbAppendCreateTableWithClass(sql
, class);
184 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
185 SecDbAppendCreateTableWithClass(sql
, *pclass
);
189 if (includeVersion
) {
190 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT INTO tversion(version,minor) VALUES(%d, %d);"),
191 schema
->majorVersion
, schema
->minorVersion
);
193 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
194 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
195 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
201 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
203 __block
bool ok
= true;
204 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
205 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
206 SecDbAppendDropTableWithClass(sql
, *pclass
);
208 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
209 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
210 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
216 CFTypeRef
SecDbItemCopyResult(SecDbItemRef item
, ReturnTypeMask return_type
, CFErrorRef
*error
) {
219 if (return_type
== 0) {
220 /* Caller isn't interested in any results at all. */
222 } else if (return_type
== kSecReturnDataMask
) {
223 a_result
= SecDbItemGetCachedValueWithName(item
, kSecValueData
);
225 CFRetainSafe(a_result
);
227 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
229 } else if (return_type
== kSecReturnPersistentRefMask
) {
230 a_result
= SecPersistentRefCreateWithItem(item
, error
);
232 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item
));
233 /* We need to return more than one value. */
234 if (return_type
& kSecReturnRefMask
) {
235 CFDictionarySetValue(dict
, kSecClass
, SecDbItemGetClass(item
)->name
);
237 CFOptionFlags mask
= (((return_type
& kSecReturnDataMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnDataFlag
: 0) |
238 ((return_type
& kSecReturnAttributesMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnAttrFlag
: 0));
239 SecDbForEachAttr(SecDbItemGetClass(item
), desc
) {
240 if ((desc
->flags
& mask
) != 0) {
241 CFTypeRef value
= SecDbItemGetValue(item
, desc
, error
);
242 if (value
&& !CFEqual(kCFNull
, value
)) {
243 CFDictionarySetValue(dict
, desc
->name
, value
);
244 } else if (value
== NULL
) {
250 CFDictionaryRemoveValue(dict
, kSecAttrUUID
);
252 if (return_type
& kSecReturnPersistentRefMask
) {
253 CFDataRef pref
= SecPersistentRefCreateWithItem(item
, error
);
254 CFDictionarySetValue(dict
, kSecValuePersistentRef
, pref
);
264 /* AUDIT[securityd](done):
265 attributes (ok) is a caller provided dictionary, only its cf type has
269 s3dl_query_add(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
, CFErrorRef
*error
)
271 if (query_match_count(q
) != 0)
272 return errSecItemMatchUnsupported
;
274 /* Add requires a class to be specified unless we are adding a ref. */
275 if (q
->q_use_item_list
)
276 return errSecUseItemListUnsupported
;
278 /* Actual work here. */
279 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, q
->q_class
, q
->q_item
, KEYBAG_DEVICE
, error
);
282 if (SecDbItemIsTombstone(item
))
283 SecDbItemSetValue(item
, &v7utomb
, q
->q_use_tomb
? q
->q_use_tomb
: kCFBooleanTrue
, NULL
);
287 ok
= SecDbItemSetValueWithName(item
, CFSTR("v_Data"), q
->q_data
, error
);
289 ok
= SecDbItemSetRowId(item
, q
->q_row_id
, error
);
291 ok
= SecDbItemSetValueWithName(item
, CFSTR("musr"), q
->q_musrView
, error
);
292 SecDbItemSetCredHandle(item
, q
->q_use_cred_handle
);
295 if(SecCKKSIsEnabled() && !SecCKKSTestDisableAutomaticUUID()) {
296 s3dl_item_make_new_uuid(item
, q
->q_uuid_from_primary_key
, error
);
298 if(q
->q_add_sync_callback
) {
299 CFTypeRef uuid
= SecDbItemGetValue(item
, &v10itemuuid
, error
);
301 CKKSRegisterSyncStatusCallback(uuid
, q
->q_add_sync_callback
);
303 secerror("Couldn't fetch UUID from item; can't call callback");
310 ok
= SecDbItemInsert(item
, dbt
, error
);
313 if (result
&& q
->q_return_type
) {
314 *result
= SecDbItemCopyResult(item
, q
->q_return_type
, error
);
317 if (!ok
&& error
&& *error
) {
318 if (CFEqual(CFErrorGetDomain(*error
), kSecDbErrorDomain
) && CFErrorGetCode(*error
) == SQLITE_CONSTRAINT
) {
319 CFReleaseNull(*error
);
320 SecError(errSecDuplicateItem
, error
, CFSTR("duplicate item %@"), item
);
321 } else if (CFEqual(CFErrorGetDomain(*error
), kSecErrorDomain
) && CFErrorGetCode(*error
) == errSecDecode
) { //handle situation when item have pdmn=akpu but passcode is not set
322 CFTypeRef value
= SecDbItemGetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbAccessAttr
, error
), error
);
323 if (value
&& CFEqual(value
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
)) {
324 CFReleaseNull(*error
);
325 SecError(errSecAuthFailed
, error
, CFSTR("authentication failed"));
332 if (SecDbItemIsSyncable(item
))
333 q
->q_sync_changed
= true;
336 secdebug("dbitem", "inserting item %@%s%@", item
, ok
? "" : "failed: ", ok
|| error
== NULL
? (CFErrorRef
)CFSTR("") : *error
);
343 bool s3dl_item_make_new_uuid(SecDbItemRef item
, bool uuid_from_primary_key
, CFErrorRef
* error
) {
348 // Set the item UUID.
349 CFUUIDRef uuid
= NULL
;
350 // Were we asked to make the UUID static?
351 if (uuid_from_primary_key
) {
352 // This UUID isn't used in any security mechanism, so we can
353 // just use the first bits of the SHA256 hash.
354 CFDataRef pkhash
= SecDbKeychainItemCopySHA256PrimaryKey(item
, error
);
355 if(CFDataGetLength(pkhash
) >= 16) {
357 CFRange range
= CFRangeMake(0, 16);
358 CFDataGetBytes(pkhash
, range
, uuidBytes
);
360 uuid
= CFUUIDCreateWithBytes(NULL
,
378 CFReleaseNull(pkhash
);
381 uuid
= CFUUIDCreate(NULL
);
383 SecDbItemSetValueWithName(item
, kSecAttrUUID
, uuid
, error
);
388 typedef void (*s3dl_handle_row
)(sqlite3_stmt
*stmt
, void *context
);
391 s3dl_copy_data_from_col(sqlite3_stmt
*stmt
, int col
, CFErrorRef
*error
) {
392 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt
, col
),
393 sqlite3_column_bytes(stmt
, col
),
398 s3dl_item_from_col(sqlite3_stmt
*stmt
, Query
*q
, int col
, CFArrayRef accessGroups
,
399 CFMutableDictionaryRef
*item
, SecAccessControlRef
*access_control
, keyclass_t
* keyclass
, CFErrorRef
*error
) {
400 CFDataRef edata
= NULL
;
402 require(edata
= s3dl_copy_data_from_col(stmt
, col
, error
), out
);
403 ok
= s3dl_item_from_data(edata
, q
, accessGroups
, item
, access_control
, keyclass
, error
);
406 CFReleaseSafe(edata
);
410 struct s3dl_query_ctx
{
412 CFArrayRef accessGroups
;
413 SecDbConnectionRef dbt
;
418 /* Return whatever the caller requested based on the value of q->q_return_type.
419 keys and values must be 3 larger than attr_count in size to accomadate the
420 optional data, class and persistent ref results. This is so we can use
421 the CFDictionaryCreate() api here rather than appending to a
422 mutable dictionary. */
423 static CF_RETURNS_RETAINED CFTypeRef
424 handle_result(Query
*q
,
425 CFMutableDictionaryRef item
,
430 data
= CFDictionaryGetValue(item
, kSecValueData
);
431 if (q
->q_return_type
== 0) {
432 /* Caller isn't interested in any results at all. */
434 } else if (q
->q_return_type
== kSecReturnDataMask
) {
439 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
441 } else if (q
->q_return_type
== kSecReturnPersistentRefMask
) {
442 a_result
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, item
);
444 /* We need to return more than one value. */
445 if (q
->q_return_type
& kSecReturnRefMask
) {
446 CFDictionarySetValue(item
, kSecClass
, q
->q_class
->name
);
447 } else if ((q
->q_return_type
& kSecReturnAttributesMask
)) {
448 if (!(q
->q_return_type
& kSecReturnDataMask
)) {
449 CFDictionaryRemoveValue(item
, kSecValueData
);
452 // Add any attributes which are supposed to be returned, are not present in the decrypted blob,
453 // and have a way to generate themselves.
454 SecDbItemRef itemRef
= NULL
;
455 SecDbForEachAttrWithMask(q
->q_class
, attr
, kSecDbReturnAttrFlag
) {
456 if(!CFDictionaryGetValue(item
, attr
->name
) && attr
->copyValue
) {
457 CFErrorRef cferror
= NULL
;
459 itemRef
= SecDbItemCreateWithAttributes(NULL
, q
->q_class
, item
, KEYBAG_DEVICE
, &cferror
);
461 if(!cferror
&& itemRef
) {
462 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
463 CFTypeRef attrValue
= attr
->copyValue(itemRef
, attr
, &cferror
);
464 if (!cferror
&& attrValue
) {
465 CFDictionarySetValue(item
, attr
->name
, attrValue
);
467 CFReleaseNull(attrValue
);
470 CFReleaseNull(cferror
);
473 CFReleaseNull(itemRef
);
475 CFDictionaryRemoveValue(item
, kSecAttrUUID
);
478 CFDictionaryRemoveAllValues(item
);
479 if ((q
->q_return_type
& kSecReturnDataMask
) && data
) {
480 CFDictionarySetValue(item
, kSecValueData
, data
);
484 if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
485 CFDataRef pref
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, item
);
486 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
497 static void s3dl_merge_into_dict(const void *key
, const void *value
, void *context
) {
498 CFDictionarySetValue(context
, key
, value
);
501 static bool checkTokenObjectID(CFDataRef token_object_id
, CFDataRef value_data
) {
502 bool equalOID
= false;
503 CFDictionaryRef itemValue
= SecTokenItemValueCopy(value_data
, NULL
);
504 require_quiet(itemValue
, out
);
505 CFDataRef oID
= CFDictionaryGetValue(itemValue
, kSecTokenValueObjectIDKey
);
506 equalOID
= CFEqualSafe(token_object_id
, oID
);
507 CFRelease(itemValue
);
512 static void s3dl_query_row(sqlite3_stmt
*stmt
, void *context
) {
513 struct s3dl_query_ctx
*c
= context
;
515 ReturnTypeMask saved_mask
= q
->q_return_type
;
517 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
518 CFMutableDictionaryRef item
= NULL
;
522 ok
= s3dl_item_from_col(stmt
, q
, 1, c
->accessGroups
, &item
, NULL
, NULL
, &q
->q_error
);
524 OSStatus status
= SecErrorGetOSStatus(q
->q_error
);
525 // errSecDecode means the item is corrupted, stash it for delete.
526 if (status
== errSecDecode
) {
527 secwarning("ignoring corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, rowid
, q
->q_error
);
529 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
530 CFMutableStringRef edatastring
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
532 CFStringAppendEncryptedData(edatastring
, edata
);
533 secnotice("item", "corrupted edata=%@", edatastring
);
535 CFReleaseSafe(edata
);
536 CFReleaseSafe(edatastring
);
538 CFReleaseNull(q
->q_error
);
539 } else if (status
== errSecAuthNeeded
) {
540 secwarning("Authentication is needed for %@,rowid=%" PRId64
" (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
541 } else if (status
== errSecInteractionNotAllowed
) {
542 static dispatch_once_t kclockedtoken
;
543 static sec_action_t kclockedaction
;
544 dispatch_once(&kclockedtoken
, ^{
545 kclockedaction
= sec_action_create("ratelimiterdisabledlogevent", 1);
546 sec_action_set_handler(kclockedaction
, ^{
547 secerror("decode item failed, keychain is locked (%d)", (int)errSecInteractionNotAllowed
);
550 sec_action_perform(kclockedaction
);
552 secerror("decode %@,rowid=%" PRId64
" failed (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
554 // q->q_error will be released appropriately by a call to query_error
561 if (CFDictionaryContainsKey(item
, kSecAttrTokenID
) && (q
->q_return_type
& kSecReturnDataMask
) == 0) {
562 // For token-based items, to get really meaningful set of attributes we must provide also data field, so augment mask
563 // and restart item decoding cycle.
564 q
->q_return_type
|= kSecReturnDataMask
;
569 if (q
->q_token_object_id
!= NULL
&& !checkTokenObjectID(q
->q_token_object_id
, CFDictionaryGetValue(item
, kSecValueData
)))
572 if (q
->q_class
== identity_class()) {
573 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
575 CFMutableDictionaryRef key
;
576 /* TODO : if there is a errSecDecode error here, we should cleanup */
577 if (!s3dl_item_from_col(stmt
, q
, 3, c
->accessGroups
, &key
, NULL
, NULL
, &q
->q_error
) || !key
)
580 CFDataRef certData
= CFDictionaryGetValue(item
, kSecValueData
);
582 CFDictionarySetValue(key
, kSecAttrIdentityCertificateData
, certData
);
583 CFDictionaryRemoveValue(item
, kSecValueData
);
586 CFDataRef certTokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
588 CFDictionarySetValue(key
, kSecAttrIdentityCertificateTokenID
, certTokenID
);
589 CFDictionaryRemoveValue(item
, kSecAttrTokenID
);
591 CFDictionaryApplyFunction(item
, s3dl_merge_into_dict
, key
);
596 if (!match_item(c
->dbt
, q
, c
->accessGroups
, item
))
599 CFTypeRef a_result
= handle_result(q
, item
, rowid
);
601 if (a_result
== kCFNull
) {
602 /* Caller wasn't interested in a result, but we still
603 count this row as found. */
604 CFRelease(a_result
); // Help shut up clang
605 } else if (q
->q_limit
== 1) {
606 c
->result
= a_result
;
608 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, a_result
);
615 q
->q_return_type
= saved_mask
;
620 SecDbAppendWhereROWID(CFMutableStringRef sql
,
621 CFStringRef col
, sqlite_int64 row_id
,
624 SecDbAppendWhereOrAnd(sql
, needWhere
);
625 CFStringAppendFormat(sql
, NULL
, CFSTR("%@=%lld"), col
, row_id
);
630 SecDbAppendWhereAttrs(CFMutableStringRef sql
, const Query
*q
, bool *needWhere
) {
631 CFIndex ix
, attr_count
= query_attr_count(q
);
632 for (ix
= 0; ix
< attr_count
; ++ix
) {
633 SecDbAppendWhereOrAndEquals(sql
, query_attr_at(q
, ix
).key
, needWhere
);
638 SecDbAppendWhereAccessGroups(CFMutableStringRef sql
,
640 CFArrayRef accessGroups
,
642 CFIndex ix
, ag_count
;
643 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
647 SecDbAppendWhereOrAnd(sql
, needWhere
);
648 CFStringAppend(sql
, col
);
649 CFStringAppend(sql
, CFSTR(" IN (?"));
650 for (ix
= 1; ix
< ag_count
; ++ix
) {
651 CFStringAppend(sql
, CFSTR(",?"));
653 CFStringAppend(sql
, CFSTR(")"));
657 isQueryOverAllMUSRViews(CFTypeRef musrView
)
659 return SecMUSRIsViewAllViews(musrView
);
663 isQueryOverSingleUserView(CFTypeRef musrView
)
665 return isNull(musrView
);
670 isQueryOverBothUserAndSystem(CFTypeRef musrView
, uid_t
*uid
)
672 return SecMUSRGetBothUserAndSystemUUID(musrView
, uid
);
677 SecDbAppendWhereMusr(CFMutableStringRef sql
,
681 SecDbAppendWhereOrAnd(sql
, needWhere
);
684 if (isQueryOverBothUserAndSystem(q
->q_musrView
, NULL
)) {
685 CFStringAppend(sql
, CFSTR("(musr = ? OR musr = ?)"));
688 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
689 /* query over all items, regardless of view */
690 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
691 CFStringAppend(sql
, CFSTR("musr = ?"));
693 CFStringAppend(sql
, CFSTR("musr = ?"));
697 static void SecDbAppendWhereClause(CFMutableStringRef sql
, const Query
*q
,
698 CFArrayRef accessGroups
) {
699 bool needWhere
= true;
700 SecDbAppendWhereROWID(sql
, CFSTR("ROWID"), q
->q_row_id
, &needWhere
);
701 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
702 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
703 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
706 static void SecDbAppendLimit(CFMutableStringRef sql
, CFIndex limit
) {
707 if (limit
!= kSecMatchUnlimited
)
708 CFStringAppendFormat(sql
, NULL
, CFSTR(" LIMIT %" PRIdCFIndex
), limit
);
711 static CFStringRef
s3dl_create_select_sql(Query
*q
, CFArrayRef accessGroups
) {
712 CFMutableStringRef sql
= CFStringCreateMutable(NULL
, 0);
713 if (q
->q_class
== identity_class()) {
714 CFStringAppendFormat(sql
, NULL
, CFSTR("SELECT crowid, %@"
716 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
717 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
718 " keys.*,cert.data AS %@"
720 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"),
721 kSecAttrIdentityCertificateData
, kSecAttrIdentityCertificateData
);
722 SecDbAppendWhereAccessGroups(sql
, CFSTR("cert.agrp"), accessGroups
, 0);
723 /* The next 3 SecDbAppendWhere calls are in the same order as in
724 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
725 as long as we do an extra sqlBindAccessGroups first. */
726 SecDbAppendWhereROWID(sql
, CFSTR("crowid"), q
->q_row_id
, 0);
727 CFStringAppend(sql
, CFSTR(")"));
728 bool needWhere
= true;
729 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
730 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
731 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
733 CFStringAppend(sql
, CFSTR("SELECT rowid, data FROM "));
734 CFStringAppend(sql
, q
->q_class
->name
);
735 SecDbAppendWhereClause(sql
, q
, accessGroups
);
737 //do not append limit for all queries which needs filtering
738 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
) {
739 SecDbAppendLimit(sql
, q
->q_limit
);
745 static bool sqlBindMusr(sqlite3_stmt
*stmt
, const Query
*q
, int *pParam
, CFErrorRef
*error
) {
751 if (isQueryOverBothUserAndSystem(q
->q_musrView
, &uid
)) {
752 /* network extensions are special and get to query both user and system views */
753 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
754 result
= SecDbBindObject(stmt
, param
++, systemUUID
, error
);
756 CFDataRef activeUser
= SecMUSRCreateActiveUserUUID(uid
);
757 result
= SecDbBindObject(stmt
, param
++, activeUser
, error
);
758 CFReleaseNull(activeUser
);
762 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
763 /* query over all items, regardless of view */
764 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
765 CFDataRef singleUUID
= SecMUSRGetSingleUserKeychainUUID();
766 result
= SecDbBindObject(stmt
, param
++, singleUUID
, error
);
768 result
= SecDbBindObject(stmt
, param
++, q
->q_musrView
, error
);
776 static bool sqlBindAccessGroups(sqlite3_stmt
*stmt
, CFArrayRef accessGroups
,
777 int *pParam
, CFErrorRef
*error
) {
780 CFIndex ix
, count
= accessGroups
? CFArrayGetCount(accessGroups
) : 0;
781 for (ix
= 0; ix
< count
; ++ix
) {
782 result
= SecDbBindObject(stmt
, param
++,
783 CFArrayGetValueAtIndex(accessGroups
, ix
),
792 static bool sqlBindWhereClause(sqlite3_stmt
*stmt
, const Query
*q
,
793 CFArrayRef accessGroups
, int *pParam
, CFErrorRef
*error
) {
796 CFIndex ix
, attr_count
= query_attr_count(q
);
797 for (ix
= 0; ix
< attr_count
; ++ix
) {
798 result
= SecDbBindObject(stmt
, param
++, query_attr_at(q
, ix
).value
, error
);
804 result
= sqlBindMusr(stmt
, q
, ¶m
, error
);
807 /* Bind the access group to the sql. */
809 result
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
816 bool SecDbItemQuery(SecDbQueryRef query
, CFArrayRef accessGroups
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
817 void (^handle_row
)(SecDbItemRef item
, bool *stop
)) {
818 __block
bool ok
= true;
819 /* Sanity check the query. */
821 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
823 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
824 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
826 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
829 CFStringRef sql
= s3dl_create_select_sql(query
, accessGroups
);
832 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
833 /* Bind the values being searched for to the SELECT statement. */
835 if (query
->q_class
== identity_class()) {
836 /* Bind the access groups to cert.agrp. */
837 ok
&= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
840 ok
&= sqlBindWhereClause(stmt
, query
, accessGroups
, ¶m
, error
);
842 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
) {
843 SecDbItemRef itemFromStatement
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, query
->q_class
, stmt
, query
->q_keybag
, error
, return_attr
);
844 if (itemFromStatement
) {
845 CFTransferRetained(itemFromStatement
->credHandle
, query
->q_use_cred_handle
);
846 if (match_item(dbconn
, query
, accessGroups
, itemFromStatement
->attributes
))
847 handle_row(itemFromStatement
, stop
);
848 CFReleaseNull(itemFromStatement
);
850 secerror("failed to create item from stmt: %@", error
? *error
: (CFErrorRef
)"no error");
852 CFReleaseNull(*error
);
867 s3dl_query(s3dl_handle_row handle_row
,
868 void *context
, CFErrorRef
*error
)
870 struct s3dl_query_ctx
*c
= context
;
871 SecDbConnectionRef dbt
= c
->dbt
;
873 CFArrayRef accessGroups
= c
->accessGroups
;
875 /* Sanity check the query. */
877 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
879 /* Actual work here. */
880 if (q
->q_limit
== 1) {
883 c
->result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
885 CFStringRef sql
= s3dl_create_select_sql(q
, accessGroups
);
886 bool ok
= SecDbWithSQL(dbt
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
888 /* Bind the values being searched for to the SELECT statement. */
890 if (q
->q_class
== identity_class()) {
891 /* Bind the access groups to cert.agrp. */
892 sql_ok
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
895 sql_ok
= sqlBindWhereClause(stmt
, q
, accessGroups
, ¶m
, error
);
897 SecDbForEach(dbt
, stmt
, error
, ^bool (int row_index
) {
898 handle_row(stmt
, context
);
900 bool needs_auth
= q
->q_error
&& CFErrorGetCode(q
->q_error
) == errSecAuthNeeded
;
901 if (q
->q_skip_acl_items
&& needs_auth
)
902 // Skip items needing authentication if we are told to do so.
903 CFReleaseNull(q
->q_error
);
905 bool stop
= q
->q_limit
!= kSecMatchUnlimited
&& c
->found
>= q
->q_limit
;
906 stop
= stop
|| (q
->q_error
&& !needs_auth
);
915 // First get the error from the query, since errSecDuplicateItem from an
916 // update query should superceed the errSecItemNotFound below.
917 if (!query_error(q
, error
))
919 if (ok
&& c
->found
== 0) {
920 ok
= SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
921 if (q
->q_spindump_on_failure
) {
922 __security_stackshotreport(CFSTR("ItemNotFound"), __sec_exception_code_LostInMist
);
930 s3dl_copy_matching(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
,
931 CFArrayRef accessGroups
, CFErrorRef
*error
)
933 struct s3dl_query_ctx ctx
= {
934 .q
= q
, .accessGroups
= accessGroups
, .dbt
= dbt
,
936 if (q
->q_row_id
&& query_attr_count(q
))
937 return SecError(errSecItemIllegalQuery
, error
,
938 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
939 if (q
->q_token_object_id
&& query_attr_count(q
) != 1)
940 return SecError(errSecItemIllegalQuery
, error
,
941 CFSTR("attributes to query illegal; both token persitent ref and other attributes can't be searched at the same time"));
943 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
944 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
945 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
946 bool ok
= s3dl_query(s3dl_query_row
, &ctx
, error
);
948 *result
= ctx
.result
;
950 CFReleaseSafe(ctx
.result
);
955 typedef void (^s3dl_item_digest_callback
)(CFDataRef persistantReference
, CFDataRef encryptedData
);
957 struct s3dl_digest_ctx
{
959 SecDbConnectionRef dbt
;
960 s3dl_item_digest_callback item_callback
;
963 static void s3dl_query_row_digest(sqlite3_stmt
*stmt
, void *context
) {
964 struct s3dl_query_ctx
*c
= context
;
967 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
968 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
969 CFDataRef persistant_reference
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, NULL
);
970 CFDataRef digest
= NULL
;
973 digest
= CFDataCopySHA256Digest(edata
, NULL
);
976 if (digest
&& persistant_reference
) {
977 CFDictionaryRef item
= CFDictionaryCreateForCFTypes(NULL
,
978 kSecValuePersistentRef
, persistant_reference
,
979 kSecValueData
, digest
,
982 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, item
);
986 secinfo("item", "rowid %lu in %@ failed to create pref/digest", (unsigned long)rowid
, q
->q_class
->name
);
988 CFReleaseNull(digest
);
989 CFReleaseNull(edata
);
990 CFReleaseNull(persistant_reference
);
995 s3dl_copy_digest(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef
*result
, CFArrayRef accessGroups
, CFErrorRef
*error
)
997 struct s3dl_query_ctx ctx
= {
998 .q
= q
, .dbt
= dbt
, .accessGroups
= accessGroups
,
1000 // Force to always return an array
1001 q
->q_limit
= kSecMatchUnlimited
;
1002 // This interface only queries live data
1003 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
1004 bool ok
= s3dl_query(s3dl_query_row_digest
, &ctx
, error
);
1006 *result
= (CFArrayRef
)ctx
.result
;
1008 CFReleaseSafe(ctx
.result
);
1013 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
1014 static void query_set_attribute_with_desc(const SecDbAttr
*desc
, const void *value
, Query
*q
) {
1015 if (CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
1017 for (ix
= 0; ix
< q
->q_attr_end
; ++ix
) {
1018 if (CFEqual(desc
->name
, q
->q_pairs
[ix
].key
)) {
1019 CFReleaseSafe(q
->q_pairs
[ix
].value
);
1021 for (; ix
< q
->q_attr_end
; ++ix
) {
1022 q
->q_pairs
[ix
] = q
->q_pairs
[ix
+ 1];
1024 CFDictionaryRemoveValue(q
->q_item
, desc
->name
);
1029 query_add_attribute_with_desc(desc
, value
, q
);
1032 /* Update modification_date if needed. */
1033 static void query_pre_update(Query
*q
) {
1034 SecDbForEachAttr(q
->q_class
, desc
) {
1035 if (desc
->kind
== kSecDbModificationDateAttr
) {
1036 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
1037 query_set_attribute_with_desc(desc
, now
, q
);
1043 /* Make sure all attributes that are marked as not_null have a value. If
1044 force_date is false, only set mdat and cdat if they aren't already set. */
1045 void query_pre_add(Query
*q
, bool force_date
) {
1046 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
1047 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInFlag
) {
1048 if (desc
->kind
== kSecDbCreationDateAttr
||
1049 desc
->kind
== kSecDbModificationDateAttr
) {
1051 query_set_attribute_with_desc(desc
, now
, q
);
1052 } else if (!CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
1053 query_add_attribute_with_desc(desc
, now
, q
);
1055 } else if ((desc
->flags
& kSecDbNotNullFlag
) &&
1056 !CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
1057 CFTypeRef value
= NULL
;
1058 if (desc
->flags
& kSecDbDefault0Flag
) {
1059 if (desc
->kind
== kSecDbDateAttr
)
1060 value
= CFDateCreate(kCFAllocatorDefault
, 0.0);
1063 value
= CFNumberCreate(0, kCFNumberSInt32Type
, &vzero
);
1065 } else if (desc
->flags
& kSecDbDefaultEmptyFlag
) {
1066 if (desc
->kind
== kSecDbDataAttr
|| desc
->kind
== kSecDbUUIDAttr
)
1067 value
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
1074 /* Safe to use query_add_attribute here since the attr wasn't
1076 query_add_attribute_with_desc(desc
, value
, q
);
1084 // Return a tri state value false->never make a tombstone, true->always make a
1085 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
1086 static CFBooleanRef
s3dl_should_make_tombstone(Query
*q
, bool item_is_syncable
, SecDbItemRef item
) {
1088 return q
->q_use_tomb
;
1089 else if (item_is_syncable
&& !SecDbItemIsTombstone(item
))
1092 return kCFBooleanFalse
;
1094 /* AUDIT[securityd](done):
1095 attributesToUpdate (ok) is a caller provided dictionary,
1096 only its cf types have been checked.
1099 s3dl_query_update(SecDbConnectionRef dbt
, Query
*q
,
1100 CFDictionaryRef attributesToUpdate
, CFArrayRef accessGroups
, CFErrorRef
*error
)
1102 /* Sanity check the query. */
1103 if (query_match_count(q
) != 0)
1104 return SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported in attributes to update"));
1106 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported in attributes to update"));
1107 if (q
->q_row_id
&& query_attr_count(q
))
1108 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
1109 if (q
->q_token_object_id
&& query_attr_count(q
) != 1)
1110 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both token persistent ref and other attributes can't be updated at the same time"));
1112 __block
bool result
= true;
1113 Query
*u
= query_create(q
->q_class
, NULL
, attributesToUpdate
, error
);
1114 if (u
== NULL
) return false;
1115 require_action_quiet(query_update_parse(u
, attributesToUpdate
, error
), errOut
, result
= false);
1116 query_pre_update(u
);
1117 result
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1118 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
1119 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
1120 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
1121 result
&= SecDbItemQuery(q
, accessGroups
, dbt
, error
, ^(SecDbItemRef item
, bool *stop
) {
1122 // We always need to know the error here.
1123 CFErrorRef localError
= NULL
;
1124 if (q
->q_token_object_id
) {
1125 const SecDbAttr
*valueDataAttr
= SecDbClassAttrWithKind(item
->class, kSecDbDataAttr
, NULL
);
1126 CFDataRef valueData
= SecDbItemGetValue(item
, valueDataAttr
, NULL
);
1127 if (q
->q_token_object_id
!= NULL
&& !checkTokenObjectID(q
->q_token_object_id
, valueData
))
1130 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
1131 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
1132 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
1133 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, u
->q_item
, &localError
);
1134 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
1135 CFReleaseSafe(storedSHA1
);
1136 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
1137 // We just ignore this, and treat as if item is not found.
1138 secwarning("deleting corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
1139 CFReleaseNull(localError
);
1140 if (!SecDbItemDelete(item
, dbt
, false, &localError
)) {
1141 secerror("failed to delete corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
1142 CFReleaseNull(localError
);
1144 CFReleaseNull(new_item
);
1147 if (new_item
!= NULL
&& u
->q_access_control
!= NULL
)
1148 SecDbItemSetAccessControl(new_item
, u
->q_access_control
, &localError
);
1149 result
= SecErrorPropagate(localError
, error
) && new_item
;
1151 bool item_is_sync
= SecDbItemIsSyncable(item
);
1152 result
= SecDbItemUpdate(item
, new_item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), q
->q_uuid_from_primary_key
, error
);
1154 q
->q_changed
= true;
1155 if (item_is_sync
|| SecDbItemIsSyncable(new_item
))
1156 q
->q_sync_changed
= true;
1158 CFRelease(new_item
);
1164 if (result
&& !q
->q_changed
)
1165 result
= SecError(errSecItemNotFound
, error
, CFSTR("No items updated"));
1167 if (!query_destroy(u
, error
))
1172 static bool SecDbItemNeedAuth(SecDbItemRef item
, CFErrorRef
*error
)
1174 CFErrorRef localError
= NULL
;
1175 if (!SecDbItemEnsureDecrypted(item
, true, &localError
) && localError
&& CFErrorGetCode(localError
) == errSecAuthNeeded
) {
1177 *error
= localError
;
1181 CFReleaseSafe(localError
);
1186 s3dl_query_delete(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFErrorRef
*error
)
1188 __block
bool ok
= true;
1189 __block
bool needAuth
= false;
1190 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
1191 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
1192 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
1193 ok
&= SecDbItemSelect(q
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
1195 },^bool(CFMutableStringRef sql
, bool *needWhere
) {
1196 SecDbAppendWhereClause(sql
, q
, accessGroups
);
1198 },^bool(sqlite3_stmt
* stmt
, int col
) {
1199 return sqlBindWhereClause(stmt
, q
, accessGroups
, &col
, error
);
1200 }, ^(SecDbItemRef item
, bool *stop
) {
1201 // Check if item for token persitence ref
1202 if (q
->q_token_object_id
) {
1203 const SecDbAttr
*valueDataAttr
= SecDbClassAttrWithKind(item
->class, kSecDbDataAttr
, NULL
);
1204 CFDataRef valueData
= SecDbItemGetValue(item
, valueDataAttr
, NULL
);
1205 if (q
->q_token_object_id
!= NULL
&& !checkTokenObjectID(q
->q_token_object_id
, valueData
))
1208 // Check if item need to be authenticated by LocalAuthentication
1209 item
->cryptoOp
= kAKSKeyOpDelete
;
1210 if (SecDbItemNeedAuth(item
, error
)) {
1214 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
1215 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
1216 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
1217 bool item_is_sync
= SecDbItemIsSyncable(item
);
1218 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
1219 CFReleaseSafe(storedSHA1
);
1220 ok
= SecDbItemDelete(item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
1222 q
->q_changed
= true;
1224 q
->q_sync_changed
= true;
1227 if (ok
&& !q
->q_changed
&& !needAuth
) {
1228 ok
= SecError(errSecItemNotFound
, error
, CFSTR("Delete failed to delete anything"));
1230 return ok
&& !needAuth
;
1234 matchAnyString(CFStringRef needle
, CFStringRef
*haystack
)
1237 if (CFEqual(needle
, *haystack
))
1244 /* Return true iff the item in question should not be backed up, nor restored,
1245 but when restoring a backup the original version of the item should be
1246 added back to the keychain again after the restore completes. */
1247 static bool SecItemIsSystemBound(CFDictionaryRef item
, const SecDbClass
*cls
, bool multiUser
) {
1248 CFNumberRef sysb
= CFDictionaryGetValue(item
, kSecAttrSysBound
);
1249 if (isNumber(sysb
)) {
1251 if (!CFNumberGetValue(sysb
, kCFNumberSInt32Type
, &num
))
1253 if (num
== kSecSecAttrSysBoundNot
) {
1255 } else if (num
== kSecSecAttrSysBoundPreserveDuringRestore
) {
1261 CFStringRef agrp
= CFDictionaryGetValue(item
, kSecAttrAccessGroup
);
1262 if (!isString(agrp
))
1265 if (CFEqualSafe(agrp
, kSOSInternalAccessGroup
)) {
1266 secdebug("backup", "found sysbound item: %@", item
);
1270 if (CFEqual(agrp
, CFSTR("lockdown-identities"))) {
1271 secdebug("backup", "found sys_bound item: %@", item
);
1275 if (CFEqual(agrp
, CFSTR("apple")) && cls
== genp_class()) {
1276 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1277 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1279 if (isString(service
) && isString(account
)) {
1280 static CFStringRef mcAccounts
[] = {
1286 if (CFEqual(service
, CFSTR("com.apple.managedconfiguration"))
1287 && matchAnyString(account
, mcAccounts
))
1289 secdebug("backup", "found sys_bound item: %@", item
);
1295 if (multiUser
&& CFEqual(agrp
, CFSTR("com.apple.apsd")) && cls
== genp_class()) {
1296 static CFStringRef pushServices
[] = {
1297 CFSTR("push.apple.com"),
1298 CFSTR("push.apple.com,PerAppToken.v0"),
1301 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1303 if (isString(service
) && matchAnyString(service
, pushServices
)) {
1304 secdebug("backup", "found sys_bound item: %@", item
);
1309 if (multiUser
&& CFEqual(agrp
, CFSTR("appleaccount")) && cls
== genp_class()) {
1310 static CFStringRef accountServices
[] = {
1311 CFSTR("com.apple.appleaccount.fmf.token"), /* temporary tokens while accout is being setup */
1312 CFSTR("com.apple.appleaccount.fmf.apptoken"),
1313 CFSTR("com.apple.appleaccount.fmip.siritoken"),
1314 CFSTR("com.apple.appleaccount.cloudkit.token"),
1317 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1319 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1320 secdebug("backup", "found exact sys_bound item: %@", item
);
1325 if (multiUser
&& CFEqual(agrp
, CFSTR("apple")) && cls
== genp_class()) {
1326 static CFStringRef accountServices
[] = {
1327 /* accounts, remove with rdar://37595482 */
1328 CFSTR("com.apple.account.AppleAccount.token"),
1329 CFSTR("com.apple.account.AppleAccount.password"),
1330 CFSTR("com.apple.account.AppleAccount.rpassword"),
1331 CFSTR("com.apple.account.idms.token"),
1332 CFSTR("com.apple.account.idms.heartbeat-token"),
1333 CFSTR("com.apple.account.idms.continuation-key"),
1334 CFSTR("com.apple.account.CloudKit.token"),
1335 CFSTR("com.apple.account.IdentityServices.password"), /* accountsd for ids */
1336 CFSTR("com.apple.account.IdentityServices.rpassword"),
1337 CFSTR("com.apple.account.IdentityServices.token"),
1339 CFSTR("BackupIDSAccountToken"),
1340 CFSTR("com.apple.ids"),
1345 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1347 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1348 secdebug("backup", "found exact sys_bound item: %@", item
);
1351 if (isString(service
) && CFEqual(service
, CFSTR("com.apple.facetime"))) {
1352 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1353 if (isString(account
) && CFEqual(account
, CFSTR("registrationV1"))) {
1354 secdebug("backup", "found exact sys_bound item: %@", item
);
1360 /* accounts, remove with rdar://37595482 */
1361 if (multiUser
&& CFEqual(agrp
, CFSTR("com.apple.ind")) && cls
== genp_class()) {
1362 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1363 if (isString(service
) && CFEqual(service
, CFSTR("com.apple.ind.registration"))) {
1364 secdebug("backup", "found exact sys_bound item: %@", item
);
1369 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== genp_class()) {
1370 static CFStringRef accountServices
[] = {
1374 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1376 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1377 secdebug("backup", "found exact sys_bound item: %@", item
);
1382 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== keys_class()) {
1383 static CFStringRef exactMatchingLabel
[] = {
1384 CFSTR("iMessage Encryption Key"),
1385 CFSTR("iMessage Signing Key"),
1387 CFStringRef label
= CFDictionaryGetValue(item
, kSecAttrLabel
);
1388 if (isString(label
)) {
1389 if (matchAnyString(label
, exactMatchingLabel
)) {
1390 secdebug("backup", "found exact sys_bound item: %@", item
);
1397 secdebug("backup", "found non sys_bound item: %@", item
);
1401 /* Delete all items from the current keychain. If this is not an in
1402 place upgrade we don't delete items in the 'lockdown-identities'
1403 access group, this ensures that an import or restore of a backup
1404 will never overwrite an existing activation record. */
1405 static bool SecServerDeleteAll(SecDbConnectionRef dbt
, CFErrorRef
*error
) {
1406 secwarning("SecServerDeleteAll");
1408 return kc_transaction(dbt
, error
, ^{
1410 bool ok
= (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1411 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1412 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1413 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1418 #if TARGET_OS_IPHONE
1420 static bool DeleteAllFromTableForMUSRView(SecDbConnectionRef dbt
,
1426 sqlite3_stmt
*stmt
= NULL
;
1427 CFStringRef sql2
= NULL
;
1431 sql2
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@ AND pdmn NOT IN ('aku','akpu','cku','dku')"), sql
);
1433 sql2
= CFRetain(sql
);
1435 require(sql2
, fail
);
1437 stmt
= SecDbCopyStmt(dbt
, sql2
, NULL
, error
);
1438 require(stmt
, fail
);
1440 ok
= SecDbBindObject(stmt
, 1, musr
, error
);
1443 ok
= SecDbStep(dbt
, stmt
, error
, ^(bool *stop
) { });
1448 ok
= SecDbFinalize(stmt
, error
);
1451 secwarning("DeleteAllFromTableForMUSRView failed for %@ for musr: %@: %@", sql2
, musr
, error
? *error
: NULL
);
1453 CFReleaseNull(sql2
);
1458 bool SecServerDeleteAllForUser(SecDbConnectionRef dbt
, CFDataRef musrView
, bool keepU
, CFErrorRef
*error
) {
1459 secwarning("SecServerDeleteAllForUser for user: %@ keepU %s", musrView
, keepU
? "yes" : "no");
1461 return kc_transaction(dbt
, error
, ^{
1464 ok
= (DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM genp WHERE musr = ?"), musrView
, keepU
, error
) &&
1465 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM inet WHERE musr = ?"), musrView
, keepU
, error
) &&
1466 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM cert WHERE musr = ?"), musrView
, keepU
, error
) &&
1467 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM keys WHERE musr = ?"), musrView
, keepU
, error
));
1475 struct s3dl_export_row_ctx
{
1476 struct s3dl_query_ctx qc
;
1477 keybag_handle_t dest_keybag
;
1478 enum SecItemFilter filter
;
1482 static void s3dl_export_row(sqlite3_stmt
*stmt
, void *context
) {
1483 struct s3dl_export_row_ctx
*c
= context
;
1485 SecAccessControlRef access_control
= NULL
;
1486 CFErrorRef localError
= NULL
;
1488 /* Skip akpu items when backing up, those are intentionally lost across restores. The same applies to SEP-based keys */
1489 bool skip_akpu_or_token
= c
->filter
== kSecBackupableItemFilter
;
1491 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
1492 CFMutableDictionaryRef allAttributes
= NULL
;
1493 CFMutableDictionaryRef metadataAttributes
= NULL
;
1494 CFMutableDictionaryRef secretStuff
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1495 keyclass_t keyclass
= 0;
1496 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->qc
.accessGroups
, &allAttributes
, &access_control
, &keyclass
, &localError
);
1499 metadataAttributes
= CFDictionaryCreateMutableCopy(NULL
, 0, allAttributes
);
1500 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbReturnDataFlag
) {
1501 CFTypeRef value
= CFDictionaryGetValue(metadataAttributes
, desc
->name
);
1503 CFDictionarySetValue(secretStuff
, desc
->name
, value
);
1504 CFDictionaryRemoveValue(metadataAttributes
, desc
->name
);
1509 bool is_akpu
= access_control
? CFEqualSafe(SecAccessControlGetProtection(access_control
),
1510 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
) : keyclass
== key_class_akpu
;
1511 bool is_token
= (ok
&& allAttributes
!= NULL
) ? CFDictionaryContainsKey(allAttributes
, kSecAttrTokenID
) : false;
1513 if (ok
&& allAttributes
&& !(skip_akpu_or_token
&& (is_akpu
|| is_token
))) {
1514 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1515 bool do_sys_bound
= c
->filter
== kSecSysBoundItemFilter
;
1516 if (c
->filter
== kSecNoItemFilter
||
1517 SecItemIsSystemBound(allAttributes
, q
->q_class
, c
->multiUser
) == do_sys_bound
) {
1518 /* Re-encode the item. */
1519 secdebug("item", "export rowid %llu item: %@", rowid
, allAttributes
);
1520 /* The code below could be moved into handle_row. */
1521 CFDataRef pref
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, allAttributes
);
1523 if (c
->dest_keybag
!= KEYBAG_NONE
) {
1524 CFMutableDictionaryRef auth_attribs
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1525 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInAuthenticatedDataFlag
) {
1526 CFTypeRef value
= CFDictionaryGetValue(metadataAttributes
, desc
->name
);
1528 CFDictionaryAddValue(auth_attribs
, desc
->name
, value
);
1529 CFDictionaryRemoveValue(metadataAttributes
, desc
->name
);
1533 /* Encode and encrypt the item to the specified keybag. */
1534 CFDataRef edata
= NULL
;
1535 bool encrypted
= ks_encrypt_data(c
->dest_keybag
, access_control
, q
->q_use_cred_handle
, secretStuff
, metadataAttributes
, auth_attribs
, &edata
, false, &q
->q_error
);
1536 CFDictionaryRemoveAllValues(allAttributes
);
1537 CFRelease(auth_attribs
);
1539 CFDictionarySetValue(allAttributes
, kSecValueData
, edata
);
1540 CFReleaseSafe(edata
);
1542 seccritical("ks_encrypt_data %@,rowid=%" PRId64
": failed: %@", q
->q_class
->name
, rowid
, q
->q_error
);
1543 CFReleaseNull(q
->q_error
);
1546 if (CFDictionaryGetCount(allAttributes
)) {
1547 CFDictionarySetValue(allAttributes
, kSecValuePersistentRef
, pref
);
1548 CFArrayAppendValue((CFMutableArrayRef
)c
->qc
.result
, allAttributes
);
1551 CFReleaseSafe(pref
);
1555 OSStatus status
= SecErrorGetOSStatus(localError
);
1557 if (status
== errSecInteractionNotAllowed
&& is_akpu
&& skip_akpu_or_token
) {
1558 // We expect akpu items to be inaccessible when the device is locked.
1559 CFReleaseNull(localError
);
1561 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1562 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1563 secinfo("item","Could not export item for rowid %llu: %@", rowid
, localError
);
1565 if(status
== errSecDecode
) {
1566 CFReleaseNull(localError
);
1568 CFReleaseSafe(q
->q_error
);
1569 q
->q_error
=localError
;
1573 CFReleaseNull(access_control
);
1574 CFReleaseNull(allAttributes
);
1575 CFReleaseNull(metadataAttributes
);
1576 CFReleaseNull(secretStuff
);
1580 SecCreateKeybagUUID(keybag_handle_t keybag
)
1582 #if !TARGET_HAS_KEYSTORE
1587 if (aks_get_bag_uuid(keybag
, uuid
) != KERN_SUCCESS
)
1589 uuid_unparse_lower(uuid
, uuidstr
);
1590 return CFStringCreateWithCString(NULL
, uuidstr
, kCFStringEncodingUTF8
);
1596 SecServerCopyKeychainPlist(SecDbConnectionRef dbt
,
1597 SecurityClient
*client
,
1598 keybag_handle_t src_keybag
,
1599 keybag_handle_t dest_keybag
,
1600 enum SecItemFilter filter
,
1601 CFErrorRef
*error
) {
1602 CFMutableDictionaryRef keychain
;
1603 keychain
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1604 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1606 bool inMultiUser
= false;
1607 CFStringRef keybaguuid
= NULL
;
1608 Query q
= { .q_keybag
= src_keybag
,
1613 if (error
&& !*error
)
1614 SecError(errSecAllocate
, error
, CFSTR("Can't create keychain dictionary"));
1619 kSecReturnDataMask
|
1620 kSecReturnAttributesMask
|
1621 kSecReturnPersistentRefMask
;
1622 q
.q_limit
= kSecMatchUnlimited
;
1623 q
.q_skip_acl_items
= true;
1626 #if TARGET_OS_IPHONE
1627 if (client
&& client
->inMultiUser
) {
1628 q
.q_musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1633 q
.q_musrView
= SecMUSRGetSingleUserKeychainUUID();
1634 CFRetain(q
.q_musrView
);
1636 keybaguuid
= SecCreateKeybagUUID(dest_keybag
);
1638 CFDictionarySetValue(keychain
, kSecBackupKeybagUUIDKey
, keybaguuid
);
1641 /* Get rid of this duplicate. */
1642 const SecDbClass
*SecDbClasses
[] = {
1649 for (class_ix
= 0; class_ix
< array_size(SecDbClasses
);
1651 q
.q_class
= SecDbClasses
[class_ix
];
1652 struct s3dl_export_row_ctx ctx
= {
1653 .qc
= { .q
= &q
, .dbt
= dbt
},
1654 .dest_keybag
= dest_keybag
, .filter
= filter
,
1655 .multiUser
= inMultiUser
,
1658 secnotice("item", "exporting class '%@'", q
.q_class
->name
);
1660 CFErrorRef localError
= NULL
;
1661 if (s3dl_query(s3dl_export_row
, &ctx
, &localError
)) {
1662 if (CFArrayGetCount(ctx
.qc
.result
)) {
1663 SecSignpostBackupCount(SecSignpostImpulseBackupClassCount
, q
.q_class
->name
, CFArrayGetCount(ctx
.qc
.result
), filter
);
1664 CFDictionaryAddValue(keychain
, q
.q_class
->name
, ctx
.qc
.result
);
1668 OSStatus status
= (OSStatus
)CFErrorGetCode(localError
);
1669 if (status
== errSecItemNotFound
) {
1670 CFRelease(localError
);
1672 secerror("Export failed: %@", localError
);
1674 CFReleaseSafe(*error
);
1675 *error
= localError
;
1677 CFRelease(localError
);
1679 CFReleaseNull(keychain
);
1680 CFReleaseNull(ctx
.qc
.result
);
1684 CFReleaseNull(ctx
.qc
.result
);
1688 CFReleaseNull(q
.q_musrView
);
1689 CFReleaseNull(keybaguuid
);
1694 struct SecServerImportClassState
{
1695 SecDbConnectionRef dbt
;
1697 keybag_handle_t src_keybag
;
1698 keybag_handle_t dest_keybag
;
1699 SecurityClient
*client
;
1700 enum SecItemFilter filter
;
1703 struct SecServerImportItemState
{
1704 const SecDbClass
*class;
1705 struct SecServerImportClassState
*s
;
1709 SecServerImportItem(const void *value
, void *context
)
1711 struct SecServerImportItemState
*state
= (struct SecServerImportItemState
*)context
;
1712 bool inMultiUser
= false;
1713 #if TARGET_OS_IPHONE
1714 if (state
->s
->client
->inMultiUser
)
1718 if (state
->s
->error
)
1721 if (!isDictionary(value
)) {
1722 SecError(errSecParam
, &state
->s
->error
, CFSTR("value %@ is not a dictionary"), value
);
1726 CFDictionaryRef dict
= (CFDictionaryRef
)value
;
1728 secdebug("item", "Import Item : %@", dict
);
1730 /* We use the kSecSysBoundItemFilter to indicate that we don't
1731 * preserve rowid's during import.
1733 if (state
->s
->filter
== kSecBackupableItemFilter
) {
1736 /* We don't filter non sys_bound items during import since we know we
1737 * will never have any in this case.
1739 if (SecItemIsSystemBound(dict
, state
->class, inMultiUser
))
1743 * Don't bother with u items when in edu mode since our current backup system
1744 * don't keep track of items that blongs to the device (u) but rather just
1745 * merge them into one blob.
1747 if (inMultiUser
&& (pdmu
= CFDictionaryGetValue(dict
, kSecAttrAccessible
))) {
1748 if (CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1749 CFEqual(pdmu
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
) ||
1750 CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1751 CFEqual(pdmu
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
))
1753 secdebug("item", "Skipping KU item : %@", dict
);
1758 /* Avoid importing token-based items. Although newer backups should not have them,
1759 * older (iOS9, iOS10.0) produced backups with token-based items.
1761 if (CFDictionaryContainsKey(dict
, kSecAttrTokenID
)) {
1762 secdebug("item", "Skipping token-based item : %@", dict
);
1769 /* This is sligthly confusing:
1770 - During upgrade all items are exported with KEYBAG_NONE.
1771 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1772 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1774 if (state
->s
->src_keybag
== KEYBAG_NONE
) {
1775 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, state
->class, dict
, state
->s
->dest_keybag
, &state
->s
->error
);
1777 item
= SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault
, state
->class, dict
, state
->s
->src_keybag
, state
->s
->dest_keybag
, &state
->s
->error
);
1785 CFDataRef musr
= NULL
;
1786 CFDataRef musrBackup
= CFDictionaryGetValue(dict
, kSecAttrMultiUser
);
1787 CFDataRef systemKeychainUUID
= SecMUSRGetSystemKeychainUUID();
1788 bool systemKeychain
= CFEqualSafe(musrBackup
, systemKeychainUUID
);
1790 #if TARGET_OS_IPHONE
1791 if (state
->s
->client
&& state
->s
->client
->inMultiUser
) {
1792 if (systemKeychain
) {
1793 secwarning("system keychain not allowed in multi user mode for item: %@", item
);
1795 musr
= SecMUSRCreateActiveUserUUID(state
->s
->client
->uid
);
1800 if (systemKeychain
) {
1801 musr
= SecMUSRCopySystemKeychainUUID();
1803 musr
= SecMUSRGetSingleUserKeychainUUID();
1808 CFReleaseNull(item
);
1810 SecDbItemSetValueWithName(item
, CFSTR("musr"), musr
, &state
->s
->error
);
1822 if(state
->s
->filter
!= kSecSysBoundItemFilter
) {
1823 SecDbItemExtractRowIdFromBackupDictionary(item
, dict
, &state
->s
->error
);
1825 SecDbItemInferSyncable(item
, &state
->s
->error
);
1826 insertStatus
= SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1827 if (!insertStatus
) {
1829 When running in EduMode, multiple users share the same
1830 keychain and unfortionaly the rowid is used a
1831 persistant reference and is part of the contraints (its
1832 UNIQUE), so lets clear the rowid and try to insert the
1835 This even happens for normal operation because of
1836 SysBound entries, so in case of a failure, lets try
1837 again to insert the record.
1839 SecDbItemClearRowId(item
, NULL
);
1840 SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1844 /* Reset error if we had one, since we just skip the current item
1845 and continue importing what we can. */
1846 if (state
->s
->error
) {
1847 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1848 item
, state
->class->name
, state
->s
->error
);
1849 CFReleaseNull(state
->s
->error
);
1852 CFReleaseSafe(item
);
1855 static void SecServerImportClass(const void *key
, const void *value
,
1857 struct SecServerImportClassState
*state
=
1858 (struct SecServerImportClassState
*)context
;
1861 if (!isString(key
)) {
1862 SecError(errSecParam
, &state
->error
, CFSTR("class name %@ is not a string"), key
);
1865 /* ignore the Keybag UUID */
1866 if (CFEqual(key
, kSecBackupKeybagUUIDKey
))
1868 const SecDbClass
*class = kc_class_with_name(key
);
1870 secwarning("Ignoring unknown key class '%@'", key
);
1873 if (class == identity_class()) {
1874 SecError(errSecParam
, &state
->error
, CFSTR("attempt to import an identity"));
1877 struct SecServerImportItemState item_state
= {
1878 .class = class, .s
= state
,
1880 if (isArray(value
)) {
1881 CFArrayRef items
= (CFArrayRef
)value
;
1882 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items
), key
, state
->filter
);
1883 SecSignpostBackupCount(SecSignpostImpulseRestoreClassCount
, class->name
, CFArrayGetCount(items
), state
->filter
);
1884 CFArrayApplyFunction(items
, CFRangeMake(0, CFArrayGetCount(items
)),
1885 SecServerImportItem
, &item_state
);
1886 } else if (isDictionary(value
)) {
1887 CFDictionaryRef item
= (CFDictionaryRef
)value
;
1888 secwarning("Import %ld items of class %@ (filter %d)", (long)1, key
, state
->filter
);
1889 SecSignpostBackupCount(SecSignpostImpulseRestoreClassCount
, class->name
, 1, state
->filter
);
1890 SecServerImportItem(item
, &item_state
);
1892 secwarning("Unknown value type for class %@ (filter %d)", key
, state
->filter
);
1896 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt
, SecurityClient
*client
,
1897 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
,
1898 CFDictionaryRef keychain
, enum SecItemFilter filter
,
1899 bool removeKeychainContent
, CFErrorRef
*error
) {
1900 CFStringRef keybaguuid
= NULL
;
1903 CFDictionaryRef sys_bound
= NULL
;
1904 if (filter
== kSecBackupableItemFilter
) {
1905 /* Grab a copy of all the items for which SecItemIsSystemBound()
1907 require(sys_bound
= SecServerCopyKeychainPlist(dbt
, client
, KEYBAG_DEVICE
,
1908 KEYBAG_NONE
, kSecSysBoundItemFilter
,
1913 * Validate the uuid of the source keybag matches what we have in the backup
1915 keybaguuid
= SecCreateKeybagUUID(src_keybag
);
1917 CFStringRef uuid
= CFDictionaryGetValue(keychain
, kSecBackupKeybagUUIDKey
);
1918 if (isString(uuid
)) {
1919 require_action(CFEqual(keybaguuid
, uuid
), errOut
,
1920 SecError(errSecDecode
, error
, CFSTR("Keybag UUID (%@) mismatch with backup (%@)"),
1927 * Shared iPad is very special, it always delete's the user keychain, and never merge content
1929 if (client
->inMultiUser
) {
1930 CFDataRef musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1931 require_action(musrView
, errOut
, ok
= false);
1932 require_action(ok
= SecServerDeleteAllForUser(dbt
, musrView
, true, error
), errOut
, CFReleaseNull(musrView
));
1933 CFReleaseNull(musrView
);
1938 * Delete everything in the keychain.
1939 * We don't want this if we're restoring backups because we probably already synced stuff over
1941 if (removeKeychainContent
) {
1942 require(ok
= SecServerDeleteAll(dbt
, error
), errOut
);
1944 // Custom hack to support bluetooth's workflow for 11.3. Should be removed in a future release.
1945 __block CFErrorRef btError
= NULL
;
1946 bool deletedBT
= kc_transaction(dbt
, &btError
, ^bool{
1947 bool ok
= SecDbExec(dbt
, CFSTR("DELETE FROM genp WHERE sync = 0 AND NOT agrp IN ('com.apple.security.sos', 'com.apple.security.sos-usercredential', 'com.apple.security.ckks');"), &btError
);
1948 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM inet WHERE sync = 0 AND NOT agrp IN ('com.apple.security.sos', 'com.apple.security.sos-usercredential', 'com.apple.security.ckks');"), &btError
);
1949 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM cert WHERE sync = 0 AND NOT agrp IN ('com.apple.security.sos', 'com.apple.security.sos-usercredential', 'com.apple.security.ckks');"), &btError
);
1950 ok
&= SecDbExec(dbt
, CFSTR("DELETE FROM keys WHERE sync = 0 AND NOT agrp IN ('com.apple.security.sos', 'com.apple.security.sos-usercredential', 'com.apple.security.ckks');"), &btError
);
1954 secerror("Unable to delete nonsyncable items prior to keychain restore: %@", btError
);
1956 secnotice("restore", "Successfully deleted nonsyncable items");
1958 CFReleaseNull(btError
);
1962 struct SecServerImportClassState state
= {
1964 .src_keybag
= src_keybag
,
1965 .dest_keybag
= dest_keybag
,
1969 /* Import the provided items, preserving rowids. */
1970 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain
));
1971 CFDictionaryApplyFunction(keychain
, SecServerImportClass
, &state
);
1974 state
.src_keybag
= KEYBAG_NONE
;
1975 /* Import the items we preserved with random rowids. */
1976 state
.filter
= kSecSysBoundItemFilter
;
1977 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound
));
1978 CFDictionaryApplyFunction(sys_bound
, SecServerImportClass
, &state
);
1982 CFReleaseSafe(*error
);
1983 *error
= state
.error
;
1985 CFRelease(state
.error
);
1990 // If CKKS had spun up, it's very likely that we just deleted its data.
1991 // Tell it to perform a local resync.
1992 SecCKKSPerformLocalResync();
1995 CFReleaseSafe(sys_bound
);
1996 CFReleaseSafe(keybaguuid
);
2002 SecServerBackupGetKeybagUUID(CFDictionaryRef keychain
, CFErrorRef
*error
)
2004 CFStringRef uuid
= CFDictionaryGetValue(keychain
, kSecBackupKeybagUUIDKey
);
2005 if (!isString(uuid
)) {
2006 SecError(errSecDecode
, error
, CFSTR("Missing or invalid %@ in backup dictionary"), kSecBackupKeybagUUIDKey
);
2012 #pragma mark - key rolling support
2015 struct check_generation_ctx
{
2016 struct s3dl_query_ctx query_ctx
;
2017 uint32_t current_generation
;
2020 static void check_generation(sqlite3_stmt
*stmt
, void *context
) {
2021 struct check_generation_ctx
*c
= context
;
2022 CFDataRef blob
= NULL
;
2024 const uint8_t *cursor
= NULL
;
2026 keyclass_t keyclass
;
2027 uint32_t current_generation
= c
->current_generation
;
2029 require(blob
= s3dl_copy_data_from_col(stmt
, 1, &c
->query_ctx
.q
->q_error
), out
);
2030 blobLen
= CFDataGetLength(blob
);
2031 cursor
= CFDataGetBytePtr(blob
);
2033 /* Check for underflow, ensuring we have at least one full AES block left. */
2034 if (blobLen
< sizeof(version
) + sizeof(keyclass
)) {
2035 SecError(errSecDecode
, &c
->query_ctx
.q
->q_error
, CFSTR("check_generation: Check for underflow"));
2039 version
= *((uint32_t *)cursor
);
2040 cursor
+= sizeof(version
);
2042 (void) version
; // TODO: do something with the version number.
2044 keyclass
= *((keyclass_t
*)cursor
);
2046 // TODO: export get_key_gen macro
2047 if (((keyclass
& ~key_class_last
) == 0) != (current_generation
== 0)) {
2048 c
->query_ctx
.found
++;
2051 CFReleaseSafe(blob
);
2055 c
->query_ctx
.found
++;
2056 CFReleaseSafe(blob
);
2059 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt
, uint32_t current_generation
, CFErrorRef
*error
) {
2060 CFErrorRef localError
= NULL
;
2061 struct check_generation_ctx ctx
= { .query_ctx
= { .dbt
= dbt
}, .current_generation
= current_generation
};
2063 const SecDbClass
*classes
[] = {
2070 for (size_t class_ix
= 0; class_ix
< array_size(classes
); ++class_ix
) {
2071 Query
*q
= query_create(classes
[class_ix
], NULL
, NULL
, &localError
);
2075 ctx
.query_ctx
.q
= q
;
2076 q
->q_limit
= kSecMatchUnlimited
;
2078 bool ok
= s3dl_query(check_generation
, &ctx
, &localError
);
2079 query_destroy(q
, NULL
);
2080 CFReleaseNull(ctx
.query_ctx
.result
);
2082 if (!ok
&& localError
&& (CFErrorGetCode(localError
) == errSecItemNotFound
)) {
2083 CFReleaseNull(localError
);
2086 secerror("Class %@ not up to date", classes
[class_ix
]->name
);
2092 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt
, SecurityClient
*client
, CFErrorRef
*error
) {
2093 return SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
2094 __block
bool ok
= false;
2095 uint32_t keystore_generation_status
;
2097 /* can we migrate to new class keys right now? */
2098 if (!aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
) &&
2099 (keystore_generation_status
& generation_change_in_progress
)) {
2101 /* take a lock assertion */
2102 bool operated_while_unlocked
= SecAKSDoWhileUserBagLocked(error
, ^{
2103 CFErrorRef localError
= NULL
;
2104 CFDictionaryRef backup
= SecServerCopyKeychainPlist(dbt
, NULL
,
2105 KEYBAG_DEVICE
, KEYBAG_NONE
, kSecNoItemFilter
, &localError
);
2108 secerror("Ignoring export error: %@ during roll export", localError
);
2109 CFReleaseNull(localError
);
2111 // 'true' argument: we're replacing everything with newly wrapped entries so remove the old stuff
2112 ok
= SecServerImportKeychainInPlist(dbt
, client
, KEYBAG_NONE
,
2113 KEYBAG_DEVICE
, backup
, kSecNoItemFilter
, true, &localError
);
2115 secerror("Ignoring export error: %@ during roll export", localError
);
2116 CFReleaseNull(localError
);
2121 if (!operated_while_unlocked
)
2124 ok
= SecError(errSecBadReq
, error
, CFSTR("No key roll in progress."));