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
)()) {
82 return kc_transaction_type(dbt
, kSecDbExclusiveTransactionType
, error
, perform
);
85 bool kc_transaction_type(SecDbConnectionRef dbt
, SecDbTransactionType type
, CFErrorRef
*error
, bool(^perform
)()) {
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 CFStringAppendFormat(sql
, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c
->name
, desc
->name
, c
->name
, desc
->name
);
157 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
158 CFStringAppendFormat(sql
, 0, CFSTR("DROP TABLE %@;"), c
->name
);
161 static CFDataRef
SecPersistentRefCreateWithItem(SecDbItemRef item
, CFErrorRef
*error
) {
162 sqlite3_int64 row_id
= SecDbItemGetRowId(item
, error
);
164 return _SecItemCreatePersistentRef(SecDbItemGetClass(item
)->name
, row_id
, item
->attributes
);
168 bool SecItemDbCreateSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFArrayRef classIndexesForNewTables
, bool includeVersion
, CFErrorRef
*error
)
170 __block
bool ok
= true;
171 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
173 if (classIndexesForNewTables
) {
174 CFArrayForEach(classIndexesForNewTables
, ^(const void* index
) {
175 const SecDbClass
* class = schema
->classes
[(int)index
];
176 SecDbAppendCreateTableWithClass(sql
, class);
180 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
181 SecDbAppendCreateTableWithClass(sql
, *pclass
);
185 if (includeVersion
) {
186 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT INTO tversion(version,minor) VALUES(%d, %d);"),
187 schema
->majorVersion
, schema
->minorVersion
);
189 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
190 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
191 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
197 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
199 __block
bool ok
= true;
200 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
201 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
202 SecDbAppendDropTableWithClass(sql
, *pclass
);
204 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
205 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
206 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
212 CFTypeRef
SecDbItemCopyResult(SecDbItemRef item
, ReturnTypeMask return_type
, CFErrorRef
*error
) {
215 if (return_type
== 0) {
216 /* Caller isn't interested in any results at all. */
218 } else if (return_type
== kSecReturnDataMask
) {
219 a_result
= SecDbItemGetCachedValueWithName(item
, kSecValueData
);
221 CFRetainSafe(a_result
);
223 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
225 } else if (return_type
== kSecReturnPersistentRefMask
) {
226 a_result
= SecPersistentRefCreateWithItem(item
, error
);
228 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item
));
229 /* We need to return more than one value. */
230 if (return_type
& kSecReturnRefMask
) {
231 CFDictionarySetValue(dict
, kSecClass
, SecDbItemGetClass(item
)->name
);
233 CFOptionFlags mask
= (((return_type
& kSecReturnDataMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnDataFlag
: 0) |
234 ((return_type
& kSecReturnAttributesMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnAttrFlag
: 0));
235 SecDbForEachAttr(SecDbItemGetClass(item
), desc
) {
236 if ((desc
->flags
& mask
) != 0) {
237 CFTypeRef value
= SecDbItemGetValue(item
, desc
, error
);
238 if (value
&& !CFEqual(kCFNull
, value
)) {
239 CFDictionarySetValue(dict
, desc
->name
, value
);
240 } else if (value
== NULL
) {
246 CFDictionaryRemoveValue(dict
, kSecAttrUUID
);
248 if (return_type
& kSecReturnPersistentRefMask
) {
249 CFDataRef pref
= SecPersistentRefCreateWithItem(item
, error
);
250 CFDictionarySetValue(dict
, kSecValuePersistentRef
, pref
);
260 /* AUDIT[securityd](done):
261 attributes (ok) is a caller provided dictionary, only its cf type has
265 s3dl_query_add(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
, CFErrorRef
*error
)
267 if (query_match_count(q
) != 0)
268 return errSecItemMatchUnsupported
;
270 /* Add requires a class to be specified unless we are adding a ref. */
271 if (q
->q_use_item_list
)
272 return errSecUseItemListUnsupported
;
274 /* Actual work here. */
275 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, q
->q_class
, q
->q_item
, KEYBAG_DEVICE
, error
);
278 if (SecDbItemIsTombstone(item
))
279 SecDbItemSetValue(item
, &v7utomb
, q
->q_use_tomb
? q
->q_use_tomb
: kCFBooleanTrue
, NULL
);
283 ok
= SecDbItemSetValueWithName(item
, CFSTR("v_Data"), q
->q_data
, error
);
285 ok
= SecDbItemSetRowId(item
, q
->q_row_id
, error
);
287 ok
= SecDbItemSetValueWithName(item
, CFSTR("musr"), q
->q_musrView
, error
);
288 SecDbItemSetCredHandle(item
, q
->q_use_cred_handle
);
291 if(SecCKKSIsEnabled() && !SecCKKSTestDisableAutomaticUUID()) {
292 s3dl_item_make_new_uuid(item
, q
->q_uuid_from_primary_key
, error
);
294 if(q
->q_add_sync_callback
) {
295 CFTypeRef uuid
= SecDbItemGetValue(item
, &v10itemuuid
, error
);
297 CKKSRegisterSyncStatusCallback(uuid
, q
->q_add_sync_callback
);
299 secerror("Couldn't fetch UUID from item; can't call callback");
306 ok
= SecDbItemInsert(item
, dbt
, error
);
309 if (result
&& q
->q_return_type
) {
310 *result
= SecDbItemCopyResult(item
, q
->q_return_type
, error
);
313 if (!ok
&& error
&& *error
) {
314 if (CFEqual(CFErrorGetDomain(*error
), kSecDbErrorDomain
) && CFErrorGetCode(*error
) == SQLITE_CONSTRAINT
) {
315 CFReleaseNull(*error
);
316 SecError(errSecDuplicateItem
, error
, CFSTR("duplicate item %@"), item
);
317 } else if (CFEqual(CFErrorGetDomain(*error
), kSecErrorDomain
) && CFErrorGetCode(*error
) == errSecDecode
) { //handle situation when item have pdmn=akpu but passcode is not set
318 CFTypeRef value
= SecDbItemGetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbAccessAttr
, error
), error
);
319 if (value
&& CFEqual(value
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
)) {
320 CFReleaseNull(*error
);
321 SecError(errSecAuthFailed
, error
, CFSTR("authentication failed"));
328 if (SecDbItemIsSyncable(item
))
329 q
->q_sync_changed
= true;
332 secdebug("dbitem", "inserting item %@%s%@", item
, ok
? "" : "failed: ", ok
|| error
== NULL
? (CFErrorRef
)CFSTR("") : *error
);
339 bool s3dl_item_make_new_uuid(SecDbItemRef item
, bool uuid_from_primary_key
, CFErrorRef
* error
) {
344 // Set the item UUID.
345 CFUUIDRef uuid
= NULL
;
346 // Were we asked to make the UUID static?
347 if (uuid_from_primary_key
) {
348 // This UUID isn't used in any security mechanism, so we can
349 // just use the first bits of the SHA256 hash.
350 CFDataRef pkhash
= SecDbKeychainItemCopySHA256PrimaryKey(item
, error
);
351 if(CFDataGetLength(pkhash
) >= 16) {
353 CFRange range
= CFRangeMake(0, 16);
354 CFDataGetBytes(pkhash
, range
, uuidBytes
);
356 uuid
= CFUUIDCreateWithBytes(NULL
,
374 CFReleaseNull(pkhash
);
377 uuid
= CFUUIDCreate(NULL
);
379 SecDbItemSetValueWithName(item
, kSecAttrUUID
, uuid
, error
);
384 typedef void (*s3dl_handle_row
)(sqlite3_stmt
*stmt
, void *context
);
387 s3dl_copy_data_from_col(sqlite3_stmt
*stmt
, int col
, CFErrorRef
*error
) {
388 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt
, col
),
389 sqlite3_column_bytes(stmt
, col
),
394 s3dl_item_from_col(sqlite3_stmt
*stmt
, Query
*q
, int col
, CFArrayRef accessGroups
,
395 CFMutableDictionaryRef
*item
, SecAccessControlRef
*access_control
, keyclass_t
* keyclass
, CFErrorRef
*error
) {
396 CFDataRef edata
= NULL
;
398 require(edata
= s3dl_copy_data_from_col(stmt
, col
, error
), out
);
399 ok
= s3dl_item_from_data(edata
, q
, accessGroups
, item
, access_control
, keyclass
, error
);
402 CFReleaseSafe(edata
);
406 struct s3dl_query_ctx
{
408 CFArrayRef accessGroups
;
409 SecDbConnectionRef dbt
;
414 /* Return whatever the caller requested based on the value of q->q_return_type.
415 keys and values must be 3 larger than attr_count in size to accomadate the
416 optional data, class and persistent ref results. This is so we can use
417 the CFDictionaryCreate() api here rather than appending to a
418 mutable dictionary. */
419 static CF_RETURNS_RETAINED CFTypeRef
420 handle_result(Query
*q
,
421 CFMutableDictionaryRef item
,
426 data
= CFDictionaryGetValue(item
, kSecValueData
);
427 if (q
->q_return_type
== 0) {
428 /* Caller isn't interested in any results at all. */
430 } else if (q
->q_return_type
== kSecReturnDataMask
) {
435 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
437 } else if (q
->q_return_type
== kSecReturnPersistentRefMask
) {
438 a_result
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, item
);
440 /* We need to return more than one value. */
441 if (q
->q_return_type
& kSecReturnRefMask
) {
442 CFDictionarySetValue(item
, kSecClass
, q
->q_class
->name
);
443 } else if ((q
->q_return_type
& kSecReturnAttributesMask
)) {
444 if (!(q
->q_return_type
& kSecReturnDataMask
)) {
445 CFDictionaryRemoveValue(item
, kSecValueData
);
448 // Add any attributes which are supposed to be returned, are not present in the decrypted blob,
449 // and have a way to generate themselves.
450 SecDbItemRef itemRef
= NULL
;
451 SecDbForEachAttrWithMask(q
->q_class
, attr
, kSecDbReturnAttrFlag
) {
452 if(!CFDictionaryGetValue(item
, attr
->name
) && attr
->copyValue
) {
453 CFErrorRef cferror
= NULL
;
455 itemRef
= SecDbItemCreateWithAttributes(NULL
, q
->q_class
, item
, KEYBAG_DEVICE
, &cferror
);
457 if(!cferror
&& itemRef
) {
458 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
459 CFTypeRef attrValue
= attr
->copyValue(itemRef
, attr
, &cferror
);
460 if (!cferror
&& attrValue
) {
461 CFDictionarySetValue(item
, attr
->name
, attrValue
);
463 CFReleaseNull(attrValue
);
466 CFReleaseNull(cferror
);
469 CFReleaseNull(itemRef
);
471 CFDictionaryRemoveValue(item
, kSecAttrUUID
);
474 CFDictionaryRemoveAllValues(item
);
475 if ((q
->q_return_type
& kSecReturnDataMask
) && data
) {
476 CFDictionarySetValue(item
, kSecValueData
, data
);
480 if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
481 CFDataRef pref
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, item
);
482 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
493 static void s3dl_merge_into_dict(const void *key
, const void *value
, void *context
) {
494 CFDictionarySetValue(context
, key
, value
);
497 static bool checkTokenObjectID(CFDataRef token_object_id
, CFDataRef value_data
) {
498 bool equalOID
= false;
499 CFDictionaryRef itemValue
= SecTokenItemValueCopy(value_data
, NULL
);
500 require_quiet(itemValue
, out
);
501 CFDataRef oID
= CFDictionaryGetValue(itemValue
, kSecTokenValueObjectIDKey
);
502 equalOID
= CFEqualSafe(token_object_id
, oID
);
503 CFRelease(itemValue
);
508 static void s3dl_query_row(sqlite3_stmt
*stmt
, void *context
) {
509 struct s3dl_query_ctx
*c
= context
;
511 ReturnTypeMask saved_mask
= q
->q_return_type
;
513 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
514 CFMutableDictionaryRef item
= NULL
;
518 ok
= s3dl_item_from_col(stmt
, q
, 1, c
->accessGroups
, &item
, NULL
, NULL
, &q
->q_error
);
520 OSStatus status
= SecErrorGetOSStatus(q
->q_error
);
521 // errSecDecode means the item is corrupted, stash it for delete.
522 if (status
== errSecDecode
) {
523 secwarning("ignoring corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, rowid
, q
->q_error
);
525 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
526 CFMutableStringRef edatastring
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
528 CFStringAppendEncryptedData(edatastring
, edata
);
529 secnotice("item", "corrupted edata=%@", edatastring
);
531 CFReleaseSafe(edata
);
532 CFReleaseSafe(edatastring
);
534 CFReleaseNull(q
->q_error
);
535 } else if (status
== errSecAuthNeeded
) {
536 secwarning("Authentication is needed for %@,rowid=%" PRId64
" (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
537 } else if (status
== errSecInteractionNotAllowed
) {
538 static dispatch_once_t kclockedtoken
;
539 static sec_action_t kclockedaction
;
540 dispatch_once(&kclockedtoken
, ^{
541 kclockedaction
= sec_action_create("ratelimiterdisabledlogevent", 1);
542 sec_action_set_handler(kclockedaction
, ^{
543 secerror("decode item failed, keychain is locked (%d)", (int)errSecInteractionNotAllowed
);
546 sec_action_perform(kclockedaction
);
548 secerror("decode %@,rowid=%" PRId64
" failed (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
550 // q->q_error will be released appropriately by a call to query_error
557 if (CFDictionaryContainsKey(item
, kSecAttrTokenID
) && (q
->q_return_type
& kSecReturnDataMask
) == 0) {
558 // For token-based items, to get really meaningful set of attributes we must provide also data field, so augment mask
559 // and restart item decoding cycle.
560 q
->q_return_type
|= kSecReturnDataMask
;
565 if (q
->q_token_object_id
!= NULL
&& !checkTokenObjectID(q
->q_token_object_id
, CFDictionaryGetValue(item
, kSecValueData
)))
568 if (q
->q_class
== identity_class()) {
569 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
571 CFMutableDictionaryRef key
;
572 /* TODO : if there is a errSecDecode error here, we should cleanup */
573 if (!s3dl_item_from_col(stmt
, q
, 3, c
->accessGroups
, &key
, NULL
, NULL
, &q
->q_error
) || !key
)
576 CFDataRef certData
= CFDictionaryGetValue(item
, kSecValueData
);
578 CFDictionarySetValue(key
, kSecAttrIdentityCertificateData
, certData
);
579 CFDictionaryRemoveValue(item
, kSecValueData
);
582 CFDataRef certTokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
584 CFDictionarySetValue(key
, kSecAttrIdentityCertificateTokenID
, certTokenID
);
585 CFDictionaryRemoveValue(item
, kSecAttrTokenID
);
587 CFDictionaryApplyFunction(item
, s3dl_merge_into_dict
, key
);
592 if (!match_item(c
->dbt
, q
, c
->accessGroups
, item
))
595 CFTypeRef a_result
= handle_result(q
, item
, rowid
);
597 if (a_result
== kCFNull
) {
598 /* Caller wasn't interested in a result, but we still
599 count this row as found. */
600 CFRelease(a_result
); // Help shut up clang
601 } else if (q
->q_limit
== 1) {
602 c
->result
= a_result
;
604 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, a_result
);
611 q
->q_return_type
= saved_mask
;
616 SecDbAppendWhereROWID(CFMutableStringRef sql
,
617 CFStringRef col
, sqlite_int64 row_id
,
620 SecDbAppendWhereOrAnd(sql
, needWhere
);
621 CFStringAppendFormat(sql
, NULL
, CFSTR("%@=%lld"), col
, row_id
);
626 SecDbAppendWhereAttrs(CFMutableStringRef sql
, const Query
*q
, bool *needWhere
) {
627 CFIndex ix
, attr_count
= query_attr_count(q
);
628 for (ix
= 0; ix
< attr_count
; ++ix
) {
629 SecDbAppendWhereOrAndEquals(sql
, query_attr_at(q
, ix
).key
, needWhere
);
634 SecDbAppendWhereAccessGroups(CFMutableStringRef sql
,
636 CFArrayRef accessGroups
,
638 CFIndex ix
, ag_count
;
639 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
643 SecDbAppendWhereOrAnd(sql
, needWhere
);
644 CFStringAppend(sql
, col
);
645 CFStringAppend(sql
, CFSTR(" IN (?"));
646 for (ix
= 1; ix
< ag_count
; ++ix
) {
647 CFStringAppend(sql
, CFSTR(",?"));
649 CFStringAppend(sql
, CFSTR(")"));
653 isQueryOverAllMUSRViews(CFTypeRef musrView
)
655 return SecMUSRIsViewAllViews(musrView
);
659 isQueryOverSingleUserView(CFTypeRef musrView
)
661 return isNull(musrView
);
666 isQueryOverBothUserAndSystem(CFTypeRef musrView
, uid_t
*uid
)
668 return SecMUSRGetBothUserAndSystemUUID(musrView
, uid
);
673 SecDbAppendWhereMusr(CFMutableStringRef sql
,
677 SecDbAppendWhereOrAnd(sql
, needWhere
);
680 if (isQueryOverBothUserAndSystem(q
->q_musrView
, NULL
)) {
681 CFStringAppend(sql
, CFSTR("(musr = ? OR musr = ?)"));
684 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
685 /* query over all items, regardless of view */
686 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
687 CFStringAppend(sql
, CFSTR("musr = ?"));
689 CFStringAppend(sql
, CFSTR("musr = ?"));
693 static void SecDbAppendWhereClause(CFMutableStringRef sql
, const Query
*q
,
694 CFArrayRef accessGroups
) {
695 bool needWhere
= true;
696 SecDbAppendWhereROWID(sql
, CFSTR("ROWID"), q
->q_row_id
, &needWhere
);
697 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
698 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
699 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
702 static void SecDbAppendLimit(CFMutableStringRef sql
, CFIndex limit
) {
703 if (limit
!= kSecMatchUnlimited
)
704 CFStringAppendFormat(sql
, NULL
, CFSTR(" LIMIT %" PRIdCFIndex
), limit
);
707 static CFStringRef
s3dl_create_select_sql(Query
*q
, CFArrayRef accessGroups
) {
708 CFMutableStringRef sql
= CFStringCreateMutable(NULL
, 0);
709 if (q
->q_class
== identity_class()) {
710 CFStringAppendFormat(sql
, NULL
, CFSTR("SELECT crowid, %@"
712 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
713 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
714 " keys.*,cert.data AS %@"
716 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"),
717 kSecAttrIdentityCertificateData
, kSecAttrIdentityCertificateData
);
718 SecDbAppendWhereAccessGroups(sql
, CFSTR("cert.agrp"), accessGroups
, 0);
719 /* The next 3 SecDbAppendWhere calls are in the same order as in
720 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
721 as long as we do an extra sqlBindAccessGroups first. */
722 SecDbAppendWhereROWID(sql
, CFSTR("crowid"), q
->q_row_id
, 0);
723 CFStringAppend(sql
, CFSTR(")"));
724 bool needWhere
= true;
725 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
726 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
727 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
729 CFStringAppend(sql
, CFSTR("SELECT rowid, data FROM "));
730 CFStringAppend(sql
, q
->q_class
->name
);
731 SecDbAppendWhereClause(sql
, q
, accessGroups
);
733 //do not append limit for all queries which needs filtering
734 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
) {
735 SecDbAppendLimit(sql
, q
->q_limit
);
741 static bool sqlBindMusr(sqlite3_stmt
*stmt
, const Query
*q
, int *pParam
, CFErrorRef
*error
) {
747 if (isQueryOverBothUserAndSystem(q
->q_musrView
, &uid
)) {
748 /* network extensions are special and get to query both user and system views */
749 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
750 result
= SecDbBindObject(stmt
, param
++, systemUUID
, error
);
752 CFDataRef activeUser
= SecMUSRCreateActiveUserUUID(uid
);
753 result
= SecDbBindObject(stmt
, param
++, activeUser
, error
);
754 CFReleaseNull(activeUser
);
758 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
759 /* query over all items, regardless of view */
760 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
761 CFDataRef singleUUID
= SecMUSRGetSingleUserKeychainUUID();
762 result
= SecDbBindObject(stmt
, param
++, singleUUID
, error
);
764 result
= SecDbBindObject(stmt
, param
++, q
->q_musrView
, error
);
772 static bool sqlBindAccessGroups(sqlite3_stmt
*stmt
, CFArrayRef accessGroups
,
773 int *pParam
, CFErrorRef
*error
) {
776 CFIndex ix
, count
= accessGroups
? CFArrayGetCount(accessGroups
) : 0;
777 for (ix
= 0; ix
< count
; ++ix
) {
778 result
= SecDbBindObject(stmt
, param
++,
779 CFArrayGetValueAtIndex(accessGroups
, ix
),
788 static bool sqlBindWhereClause(sqlite3_stmt
*stmt
, const Query
*q
,
789 CFArrayRef accessGroups
, int *pParam
, CFErrorRef
*error
) {
792 CFIndex ix
, attr_count
= query_attr_count(q
);
793 for (ix
= 0; ix
< attr_count
; ++ix
) {
794 result
= SecDbBindObject(stmt
, param
++, query_attr_at(q
, ix
).value
, error
);
800 result
= sqlBindMusr(stmt
, q
, ¶m
, error
);
803 /* Bind the access group to the sql. */
805 result
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
812 bool SecDbItemQuery(SecDbQueryRef query
, CFArrayRef accessGroups
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
813 void (^handle_row
)(SecDbItemRef item
, bool *stop
)) {
814 __block
bool ok
= true;
815 /* Sanity check the query. */
817 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
819 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
820 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
822 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
825 CFStringRef sql
= s3dl_create_select_sql(query
, accessGroups
);
828 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
829 /* Bind the values being searched for to the SELECT statement. */
831 if (query
->q_class
== identity_class()) {
832 /* Bind the access groups to cert.agrp. */
833 ok
&= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
836 ok
&= sqlBindWhereClause(stmt
, query
, accessGroups
, ¶m
, error
);
838 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
) {
839 SecDbItemRef itemFromStatement
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, query
->q_class
, stmt
, query
->q_keybag
, error
, return_attr
);
840 if (itemFromStatement
) {
841 CFTransferRetained(itemFromStatement
->credHandle
, query
->q_use_cred_handle
);
842 if (match_item(dbconn
, query
, accessGroups
, itemFromStatement
->attributes
))
843 handle_row(itemFromStatement
, stop
);
844 CFReleaseNull(itemFromStatement
);
846 secerror("failed to create item from stmt: %@", error
? *error
: (CFErrorRef
)"no error");
848 CFReleaseNull(*error
);
863 s3dl_query(s3dl_handle_row handle_row
,
864 void *context
, CFErrorRef
*error
)
866 struct s3dl_query_ctx
*c
= context
;
867 SecDbConnectionRef dbt
= c
->dbt
;
869 CFArrayRef accessGroups
= c
->accessGroups
;
871 /* Sanity check the query. */
873 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
875 /* Actual work here. */
876 if (q
->q_limit
== 1) {
879 c
->result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
881 CFStringRef sql
= s3dl_create_select_sql(q
, accessGroups
);
882 bool ok
= SecDbWithSQL(dbt
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
884 /* Bind the values being searched for to the SELECT statement. */
886 if (q
->q_class
== identity_class()) {
887 /* Bind the access groups to cert.agrp. */
888 sql_ok
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
891 sql_ok
= sqlBindWhereClause(stmt
, q
, accessGroups
, ¶m
, error
);
893 SecDbForEach(dbt
, stmt
, error
, ^bool (int row_index
) {
894 handle_row(stmt
, context
);
896 bool needs_auth
= q
->q_error
&& CFErrorGetCode(q
->q_error
) == errSecAuthNeeded
;
897 if (q
->q_skip_acl_items
&& needs_auth
)
898 // Skip items needing authentication if we are told to do so.
899 CFReleaseNull(q
->q_error
);
901 bool stop
= q
->q_limit
!= kSecMatchUnlimited
&& c
->found
>= q
->q_limit
;
902 stop
= stop
|| (q
->q_error
&& !needs_auth
);
911 // First get the error from the query, since errSecDuplicateItem from an
912 // update query should superceed the errSecItemNotFound below.
913 if (!query_error(q
, error
))
915 if (ok
&& c
->found
== 0) {
916 ok
= SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
917 if (q
->q_spindump_on_failure
) {
918 __security_stackshotreport(CFSTR("ItemNotFound"), __sec_exception_code_LostInMist
);
926 s3dl_copy_matching(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
,
927 CFArrayRef accessGroups
, CFErrorRef
*error
)
929 struct s3dl_query_ctx ctx
= {
930 .q
= q
, .accessGroups
= accessGroups
, .dbt
= dbt
,
932 if (q
->q_row_id
&& query_attr_count(q
))
933 return SecError(errSecItemIllegalQuery
, error
,
934 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
935 if (q
->q_token_object_id
&& query_attr_count(q
) != 1)
936 return SecError(errSecItemIllegalQuery
, error
,
937 CFSTR("attributes to query illegal; both token persitent ref and other attributes can't be searched at the same time"));
939 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
940 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
941 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
942 bool ok
= s3dl_query(s3dl_query_row
, &ctx
, error
);
944 *result
= ctx
.result
;
946 CFReleaseSafe(ctx
.result
);
951 typedef void (^s3dl_item_digest_callback
)(CFDataRef persistantReference
, CFDataRef encryptedData
);
953 struct s3dl_digest_ctx
{
955 SecDbConnectionRef dbt
;
956 s3dl_item_digest_callback item_callback
;
959 static void s3dl_query_row_digest(sqlite3_stmt
*stmt
, void *context
) {
960 struct s3dl_query_ctx
*c
= context
;
963 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
964 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
965 CFDataRef persistant_reference
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, NULL
);
966 CFDataRef digest
= NULL
;
969 digest
= CFDataCopySHA256Digest(edata
, NULL
);
972 if (digest
&& persistant_reference
) {
973 CFDictionaryRef item
= CFDictionaryCreateForCFTypes(NULL
,
974 kSecValuePersistentRef
, persistant_reference
,
975 kSecValueData
, digest
,
978 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, item
);
982 secinfo("item", "rowid %lu in %@ failed to create pref/digest", (unsigned long)rowid
, q
->q_class
->name
);
984 CFReleaseNull(digest
);
985 CFReleaseNull(edata
);
986 CFReleaseNull(persistant_reference
);
991 s3dl_copy_digest(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef
*result
, CFArrayRef accessGroups
, CFErrorRef
*error
)
993 struct s3dl_query_ctx ctx
= {
994 .q
= q
, .dbt
= dbt
, .accessGroups
= accessGroups
,
996 // Force to always return an array
997 q
->q_limit
= kSecMatchUnlimited
;
998 // This interface only queries live data
999 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
1000 bool ok
= s3dl_query(s3dl_query_row_digest
, &ctx
, error
);
1002 *result
= (CFArrayRef
)ctx
.result
;
1004 CFReleaseSafe(ctx
.result
);
1009 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
1010 static void query_set_attribute_with_desc(const SecDbAttr
*desc
, const void *value
, Query
*q
) {
1011 if (CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
1013 for (ix
= 0; ix
< q
->q_attr_end
; ++ix
) {
1014 if (CFEqual(desc
->name
, q
->q_pairs
[ix
].key
)) {
1015 CFReleaseSafe(q
->q_pairs
[ix
].value
);
1017 for (; ix
< q
->q_attr_end
; ++ix
) {
1018 q
->q_pairs
[ix
] = q
->q_pairs
[ix
+ 1];
1020 CFDictionaryRemoveValue(q
->q_item
, desc
->name
);
1025 query_add_attribute_with_desc(desc
, value
, q
);
1028 /* Update modification_date if needed. */
1029 static void query_pre_update(Query
*q
) {
1030 SecDbForEachAttr(q
->q_class
, desc
) {
1031 if (desc
->kind
== kSecDbModificationDateAttr
) {
1032 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
1033 query_set_attribute_with_desc(desc
, now
, q
);
1039 /* Make sure all attributes that are marked as not_null have a value. If
1040 force_date is false, only set mdat and cdat if they aren't already set. */
1041 void query_pre_add(Query
*q
, bool force_date
) {
1042 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
1043 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInFlag
) {
1044 if (desc
->kind
== kSecDbCreationDateAttr
||
1045 desc
->kind
== kSecDbModificationDateAttr
) {
1047 query_set_attribute_with_desc(desc
, now
, q
);
1048 } else if (!CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
1049 query_add_attribute_with_desc(desc
, now
, q
);
1051 } else if ((desc
->flags
& kSecDbNotNullFlag
) &&
1052 !CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
1053 CFTypeRef value
= NULL
;
1054 if (desc
->flags
& kSecDbDefault0Flag
) {
1055 if (desc
->kind
== kSecDbDateAttr
)
1056 value
= CFDateCreate(kCFAllocatorDefault
, 0.0);
1059 value
= CFNumberCreate(0, kCFNumberSInt32Type
, &vzero
);
1061 } else if (desc
->flags
& kSecDbDefaultEmptyFlag
) {
1062 if (desc
->kind
== kSecDbDataAttr
|| desc
->kind
== kSecDbUUIDAttr
)
1063 value
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
1070 /* Safe to use query_add_attribute here since the attr wasn't
1072 query_add_attribute_with_desc(desc
, value
, q
);
1080 // Return a tri state value false->never make a tombstone, true->always make a
1081 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
1082 static CFBooleanRef
s3dl_should_make_tombstone(Query
*q
, bool item_is_syncable
, SecDbItemRef item
) {
1084 return q
->q_use_tomb
;
1085 else if (item_is_syncable
&& !SecDbItemIsTombstone(item
))
1088 return kCFBooleanFalse
;
1090 /* AUDIT[securityd](done):
1091 attributesToUpdate (ok) is a caller provided dictionary,
1092 only its cf types have been checked.
1095 s3dl_query_update(SecDbConnectionRef dbt
, Query
*q
,
1096 CFDictionaryRef attributesToUpdate
, CFArrayRef accessGroups
, CFErrorRef
*error
)
1098 /* Sanity check the query. */
1099 if (query_match_count(q
) != 0)
1100 return SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported in attributes to update"));
1102 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported in attributes to update"));
1103 if (q
->q_row_id
&& query_attr_count(q
))
1104 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
1105 if (q
->q_token_object_id
&& query_attr_count(q
) != 1)
1106 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both token persistent ref and other attributes can't be updated at the same time"));
1108 __block
bool result
= true;
1109 Query
*u
= query_create(q
->q_class
, NULL
, attributesToUpdate
, error
);
1110 if (u
== NULL
) return false;
1111 require_action_quiet(query_update_parse(u
, attributesToUpdate
, error
), errOut
, result
= false);
1112 query_pre_update(u
);
1113 result
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1114 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
1115 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
1116 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
1117 result
&= SecDbItemQuery(q
, accessGroups
, dbt
, error
, ^(SecDbItemRef item
, bool *stop
) {
1118 // We always need to know the error here.
1119 CFErrorRef localError
= NULL
;
1120 if (q
->q_token_object_id
) {
1121 const SecDbAttr
*valueDataAttr
= SecDbClassAttrWithKind(item
->class, kSecDbDataAttr
, NULL
);
1122 CFDataRef valueData
= SecDbItemGetValue(item
, valueDataAttr
, NULL
);
1123 if (q
->q_token_object_id
!= NULL
&& !checkTokenObjectID(q
->q_token_object_id
, valueData
))
1126 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
1127 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
1128 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
1129 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, u
->q_item
, &localError
);
1130 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
1131 CFReleaseSafe(storedSHA1
);
1132 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
1133 // We just ignore this, and treat as if item is not found.
1134 secwarning("deleting corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
1135 CFReleaseNull(localError
);
1136 if (!SecDbItemDelete(item
, dbt
, false, &localError
)) {
1137 secerror("failed to delete corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
1138 CFReleaseNull(localError
);
1140 CFReleaseNull(new_item
);
1143 if (new_item
!= NULL
&& u
->q_access_control
!= NULL
)
1144 SecDbItemSetAccessControl(new_item
, u
->q_access_control
, &localError
);
1145 result
= SecErrorPropagate(localError
, error
) && new_item
;
1147 bool item_is_sync
= SecDbItemIsSyncable(item
);
1148 result
= SecDbItemUpdate(item
, new_item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), q
->q_uuid_from_primary_key
, error
);
1150 q
->q_changed
= true;
1151 if (item_is_sync
|| SecDbItemIsSyncable(new_item
))
1152 q
->q_sync_changed
= true;
1154 CFRelease(new_item
);
1160 if (result
&& !q
->q_changed
)
1161 result
= SecError(errSecItemNotFound
, error
, CFSTR("No items updated"));
1163 if (!query_destroy(u
, error
))
1168 static bool SecDbItemNeedAuth(SecDbItemRef item
, CFErrorRef
*error
)
1170 CFErrorRef localError
= NULL
;
1171 if (!SecDbItemEnsureDecrypted(item
, true, &localError
) && localError
&& CFErrorGetCode(localError
) == errSecAuthNeeded
) {
1173 *error
= localError
;
1177 CFReleaseSafe(localError
);
1182 s3dl_query_delete(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFErrorRef
*error
)
1184 __block
bool ok
= true;
1185 __block
bool needAuth
= false;
1186 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
1187 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
1188 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
1189 ok
&= SecDbItemSelect(q
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
1191 },^bool(CFMutableStringRef sql
, bool *needWhere
) {
1192 SecDbAppendWhereClause(sql
, q
, accessGroups
);
1194 },^bool(sqlite3_stmt
* stmt
, int col
) {
1195 return sqlBindWhereClause(stmt
, q
, accessGroups
, &col
, error
);
1196 }, ^(SecDbItemRef item
, bool *stop
) {
1197 // Check if item for token persitence ref
1198 if (q
->q_token_object_id
) {
1199 const SecDbAttr
*valueDataAttr
= SecDbClassAttrWithKind(item
->class, kSecDbDataAttr
, NULL
);
1200 CFDataRef valueData
= SecDbItemGetValue(item
, valueDataAttr
, NULL
);
1201 if (q
->q_token_object_id
!= NULL
&& !checkTokenObjectID(q
->q_token_object_id
, valueData
))
1204 // Check if item need to be authenticated by LocalAuthentication
1205 item
->cryptoOp
= kAKSKeyOpDelete
;
1206 if (SecDbItemNeedAuth(item
, error
)) {
1210 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
1211 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
1212 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
1213 bool item_is_sync
= SecDbItemIsSyncable(item
);
1214 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
1215 CFReleaseSafe(storedSHA1
);
1216 ok
= SecDbItemDelete(item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
1218 q
->q_changed
= true;
1220 q
->q_sync_changed
= true;
1223 if (ok
&& !q
->q_changed
&& !needAuth
) {
1224 ok
= SecError(errSecItemNotFound
, error
, CFSTR("Delete failed to delete anything"));
1226 return ok
&& !needAuth
;
1230 matchAnyString(CFStringRef needle
, CFStringRef
*haystack
)
1233 if (CFEqual(needle
, *haystack
))
1240 /* Return true iff the item in question should not be backed up, nor restored,
1241 but when restoring a backup the original version of the item should be
1242 added back to the keychain again after the restore completes. */
1243 static bool SecItemIsSystemBound(CFDictionaryRef item
, const SecDbClass
*cls
, bool multiUser
) {
1244 CFNumberRef sysb
= CFDictionaryGetValue(item
, kSecAttrSysBound
);
1245 if (isNumber(sysb
)) {
1247 if (!CFNumberGetValue(sysb
, kCFNumberSInt32Type
, &num
))
1249 if (num
== kSecSecAttrSysBoundNot
) {
1251 } else if (num
== kSecSecAttrSysBoundPreserveDuringRestore
) {
1257 CFStringRef agrp
= CFDictionaryGetValue(item
, kSecAttrAccessGroup
);
1258 if (!isString(agrp
))
1261 if (CFEqualSafe(agrp
, kSOSInternalAccessGroup
)) {
1262 secdebug("backup", "found sysbound item: %@", item
);
1266 if (CFEqual(agrp
, CFSTR("lockdown-identities"))) {
1267 secdebug("backup", "found sys_bound item: %@", item
);
1271 if (CFEqual(agrp
, CFSTR("apple")) && cls
== genp_class()) {
1272 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1273 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1275 if (isString(service
) && isString(account
)) {
1276 static CFStringRef mcAccounts
[] = {
1282 if (CFEqual(service
, CFSTR("com.apple.managedconfiguration"))
1283 && matchAnyString(account
, mcAccounts
))
1285 secdebug("backup", "found sys_bound item: %@", item
);
1291 if (multiUser
&& CFEqual(agrp
, CFSTR("com.apple.apsd")) && cls
== genp_class()) {
1292 static CFStringRef pushServices
[] = {
1293 CFSTR("push.apple.com"),
1294 CFSTR("push.apple.com,PerAppToken.v0"),
1297 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1299 if (isString(service
) && matchAnyString(service
, pushServices
)) {
1300 secdebug("backup", "found sys_bound item: %@", item
);
1305 if (multiUser
&& CFEqual(agrp
, CFSTR("appleaccount")) && cls
== genp_class()) {
1306 static CFStringRef accountServices
[] = {
1307 CFSTR("com.apple.appleaccount.fmf.token"), /* temporary tokens while accout is being setup */
1308 CFSTR("com.apple.appleaccount.fmf.apptoken"),
1309 CFSTR("com.apple.appleaccount.fmip.siritoken"),
1310 CFSTR("com.apple.appleaccount.cloudkit.token"),
1313 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1315 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1316 secdebug("backup", "found exact sys_bound item: %@", item
);
1321 if (multiUser
&& CFEqual(agrp
, CFSTR("apple")) && cls
== genp_class()) {
1322 static CFStringRef accountServices
[] = {
1323 /* accounts, remove with rdar://37595482 */
1324 CFSTR("com.apple.account.AppleAccount.token"),
1325 CFSTR("com.apple.account.AppleAccount.password"),
1326 CFSTR("com.apple.account.AppleAccount.rpassword"),
1327 CFSTR("com.apple.account.idms.token"),
1328 CFSTR("com.apple.account.idms.heartbeat-token"),
1329 CFSTR("com.apple.account.idms.continuation-key"),
1330 CFSTR("com.apple.account.CloudKit.token"),
1331 CFSTR("com.apple.account.IdentityServices.password"), /* accountsd for ids */
1332 CFSTR("com.apple.account.IdentityServices.rpassword"),
1333 CFSTR("com.apple.account.IdentityServices.token"),
1335 CFSTR("BackupIDSAccountToken"),
1336 CFSTR("com.apple.ids"),
1341 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1343 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1344 secdebug("backup", "found exact sys_bound item: %@", item
);
1347 if (isString(service
) && CFEqual(service
, CFSTR("com.apple.facetime"))) {
1348 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1349 if (isString(account
) && CFEqual(account
, CFSTR("registrationV1"))) {
1350 secdebug("backup", "found exact sys_bound item: %@", item
);
1356 /* accounts, remove with rdar://37595482 */
1357 if (multiUser
&& CFEqual(agrp
, CFSTR("com.apple.ind")) && cls
== genp_class()) {
1358 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1359 if (isString(service
) && CFEqual(service
, CFSTR("com.apple.ind.registration"))) {
1360 secdebug("backup", "found exact sys_bound item: %@", item
);
1365 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== genp_class()) {
1366 static CFStringRef accountServices
[] = {
1370 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1372 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1373 secdebug("backup", "found exact sys_bound item: %@", item
);
1378 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== keys_class()) {
1379 static CFStringRef exactMatchingLabel
[] = {
1380 CFSTR("iMessage Encryption Key"),
1381 CFSTR("iMessage Signing Key"),
1383 CFStringRef label
= CFDictionaryGetValue(item
, kSecAttrLabel
);
1384 if (isString(label
)) {
1385 if (matchAnyString(label
, exactMatchingLabel
)) {
1386 secdebug("backup", "found exact sys_bound item: %@", item
);
1393 secdebug("backup", "found non sys_bound item: %@", item
);
1397 /* Delete all items from the current keychain. If this is not an in
1398 place upgrade we don't delete items in the 'lockdown-identities'
1399 access group, this ensures that an import or restore of a backup
1400 will never overwrite an existing activation record. */
1401 static bool SecServerDeleteAll(SecDbConnectionRef dbt
, CFErrorRef
*error
) {
1402 secwarning("SecServerDeleteAll");
1404 return kc_transaction(dbt
, error
, ^{
1406 bool ok
= (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1407 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1408 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1409 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1414 #if TARGET_OS_IPHONE
1416 static bool DeleteAllFromTableForMUSRView(SecDbConnectionRef dbt
,
1422 sqlite3_stmt
*stmt
= NULL
;
1423 CFStringRef sql2
= NULL
;
1427 sql2
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@ AND pdmn NOT IN ('aku','akpu','cku','dku')"), sql
);
1429 sql2
= CFRetain(sql
);
1431 require(sql2
, fail
);
1433 stmt
= SecDbCopyStmt(dbt
, sql2
, NULL
, error
);
1434 require(stmt
, fail
);
1436 ok
= SecDbBindObject(stmt
, 1, musr
, error
);
1439 ok
= SecDbStep(dbt
, stmt
, error
, ^(bool *stop
) { });
1444 ok
= SecDbFinalize(stmt
, error
);
1447 secwarning("DeleteAllFromTableForMUSRView failed for %@ for musr: %@: %@", sql2
, musr
, error
? *error
: NULL
);
1449 CFReleaseNull(sql2
);
1454 bool SecServerDeleteAllForUser(SecDbConnectionRef dbt
, CFDataRef musrView
, bool keepU
, CFErrorRef
*error
) {
1455 secwarning("SecServerDeleteAllForUser for user: %@ keepU %s", musrView
, keepU
? "yes" : "no");
1457 return kc_transaction(dbt
, error
, ^{
1460 ok
= (DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM genp WHERE musr = ?"), musrView
, keepU
, error
) &&
1461 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM inet WHERE musr = ?"), musrView
, keepU
, error
) &&
1462 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM cert WHERE musr = ?"), musrView
, keepU
, error
) &&
1463 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM keys WHERE musr = ?"), musrView
, keepU
, error
));
1471 struct s3dl_export_row_ctx
{
1472 struct s3dl_query_ctx qc
;
1473 keybag_handle_t dest_keybag
;
1474 enum SecItemFilter filter
;
1478 static void s3dl_export_row(sqlite3_stmt
*stmt
, void *context
) {
1479 struct s3dl_export_row_ctx
*c
= context
;
1481 SecAccessControlRef access_control
= NULL
;
1482 CFErrorRef localError
= NULL
;
1484 /* Skip akpu items when backing up, those are intentionally lost across restores. The same applies to SEP-based keys */
1485 bool skip_akpu_or_token
= c
->filter
== kSecBackupableItemFilter
;
1487 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
1488 CFMutableDictionaryRef allAttributes
= NULL
;
1489 CFMutableDictionaryRef metadataAttributes
= NULL
;
1490 CFMutableDictionaryRef secretStuff
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1491 keyclass_t keyclass
= 0;
1492 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->qc
.accessGroups
, &allAttributes
, &access_control
, &keyclass
, &localError
);
1495 metadataAttributes
= CFDictionaryCreateMutableCopy(NULL
, 0, allAttributes
);
1496 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbReturnDataFlag
) {
1497 CFTypeRef value
= CFDictionaryGetValue(metadataAttributes
, desc
->name
);
1499 CFDictionarySetValue(secretStuff
, desc
->name
, value
);
1500 CFDictionaryRemoveValue(metadataAttributes
, desc
->name
);
1505 bool is_akpu
= access_control
? CFEqualSafe(SecAccessControlGetProtection(access_control
),
1506 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
) : keyclass
== key_class_akpu
;
1507 bool is_token
= (ok
&& allAttributes
!= NULL
) ? CFDictionaryContainsKey(allAttributes
, kSecAttrTokenID
) : false;
1509 if (ok
&& allAttributes
&& !(skip_akpu_or_token
&& (is_akpu
|| is_token
))) {
1510 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1511 bool do_sys_bound
= c
->filter
== kSecSysBoundItemFilter
;
1512 if (c
->filter
== kSecNoItemFilter
||
1513 SecItemIsSystemBound(allAttributes
, q
->q_class
, c
->multiUser
) == do_sys_bound
) {
1514 /* Re-encode the item. */
1515 secdebug("item", "export rowid %llu item: %@", rowid
, allAttributes
);
1516 /* The code below could be moved into handle_row. */
1517 CFDataRef pref
= _SecItemCreatePersistentRef(q
->q_class
->name
, rowid
, allAttributes
);
1519 if (c
->dest_keybag
!= KEYBAG_NONE
) {
1520 CFMutableDictionaryRef auth_attribs
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1521 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInAuthenticatedDataFlag
) {
1522 CFTypeRef value
= CFDictionaryGetValue(metadataAttributes
, desc
->name
);
1524 CFDictionaryAddValue(auth_attribs
, desc
->name
, value
);
1525 CFDictionaryRemoveValue(metadataAttributes
, desc
->name
);
1529 /* Encode and encrypt the item to the specified keybag. */
1530 CFDataRef edata
= NULL
;
1531 bool encrypted
= ks_encrypt_data(c
->dest_keybag
, access_control
, q
->q_use_cred_handle
, secretStuff
, metadataAttributes
, auth_attribs
, &edata
, false, &q
->q_error
);
1532 CFDictionaryRemoveAllValues(allAttributes
);
1533 CFRelease(auth_attribs
);
1535 CFDictionarySetValue(allAttributes
, kSecValueData
, edata
);
1536 CFReleaseSafe(edata
);
1538 seccritical("ks_encrypt_data %@,rowid=%" PRId64
": failed: %@", q
->q_class
->name
, rowid
, q
->q_error
);
1539 CFReleaseNull(q
->q_error
);
1542 if (CFDictionaryGetCount(allAttributes
)) {
1543 CFDictionarySetValue(allAttributes
, kSecValuePersistentRef
, pref
);
1544 CFArrayAppendValue((CFMutableArrayRef
)c
->qc
.result
, allAttributes
);
1547 CFReleaseSafe(pref
);
1551 OSStatus status
= SecErrorGetOSStatus(localError
);
1553 if (status
== errSecInteractionNotAllowed
&& is_akpu
&& skip_akpu_or_token
) {
1554 // We expect akpu items to be inaccessible when the device is locked.
1555 CFReleaseNull(localError
);
1557 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1558 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1559 secinfo("item","Could not export item for rowid %llu: %@", rowid
, localError
);
1561 if(status
== errSecDecode
) {
1562 CFReleaseNull(localError
);
1564 CFReleaseSafe(q
->q_error
);
1565 q
->q_error
=localError
;
1569 CFReleaseNull(access_control
);
1570 CFReleaseNull(allAttributes
);
1571 CFReleaseNull(metadataAttributes
);
1572 CFReleaseNull(secretStuff
);
1576 SecCreateKeybagUUID(keybag_handle_t keybag
)
1578 #if !TARGET_HAS_KEYSTORE
1583 if (aks_get_bag_uuid(keybag
, uuid
) != KERN_SUCCESS
)
1585 uuid_unparse_lower(uuid
, uuidstr
);
1586 return CFStringCreateWithCString(NULL
, uuidstr
, kCFStringEncodingUTF8
);
1592 SecServerCopyKeychainPlist(SecDbConnectionRef dbt
,
1593 SecurityClient
*client
,
1594 keybag_handle_t src_keybag
,
1595 keybag_handle_t dest_keybag
,
1596 enum SecItemFilter filter
,
1597 CFErrorRef
*error
) {
1598 CFMutableDictionaryRef keychain
;
1599 keychain
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1600 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1602 bool inMultiUser
= false;
1603 CFStringRef keybaguuid
= NULL
;
1604 Query q
= { .q_keybag
= src_keybag
,
1609 if (error
&& !*error
)
1610 SecError(errSecAllocate
, error
, CFSTR("Can't create keychain dictionary"));
1615 kSecReturnDataMask
|
1616 kSecReturnAttributesMask
|
1617 kSecReturnPersistentRefMask
;
1618 q
.q_limit
= kSecMatchUnlimited
;
1619 q
.q_skip_acl_items
= true;
1622 #if TARGET_OS_IPHONE
1623 if (client
&& client
->inMultiUser
) {
1624 q
.q_musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1629 q
.q_musrView
= SecMUSRGetSingleUserKeychainUUID();
1630 CFRetain(q
.q_musrView
);
1632 keybaguuid
= SecCreateKeybagUUID(dest_keybag
);
1634 CFDictionarySetValue(keychain
, kSecBackupKeybagUUIDKey
, keybaguuid
);
1637 /* Get rid of this duplicate. */
1638 const SecDbClass
*SecDbClasses
[] = {
1645 for (class_ix
= 0; class_ix
< array_size(SecDbClasses
);
1647 q
.q_class
= SecDbClasses
[class_ix
];
1648 struct s3dl_export_row_ctx ctx
= {
1649 .qc
= { .q
= &q
, .dbt
= dbt
},
1650 .dest_keybag
= dest_keybag
, .filter
= filter
,
1651 .multiUser
= inMultiUser
,
1654 secnotice("item", "exporting class '%@'", q
.q_class
->name
);
1656 CFErrorRef localError
= NULL
;
1657 if (s3dl_query(s3dl_export_row
, &ctx
, &localError
)) {
1658 if (CFArrayGetCount(ctx
.qc
.result
)) {
1659 SecSignpostBackupCount(SecSignpostImpulseBackupClassCount
, q
.q_class
->name
, CFArrayGetCount(ctx
.qc
.result
), filter
);
1660 CFDictionaryAddValue(keychain
, q
.q_class
->name
, ctx
.qc
.result
);
1664 OSStatus status
= (OSStatus
)CFErrorGetCode(localError
);
1665 if (status
== errSecItemNotFound
) {
1666 CFRelease(localError
);
1668 secerror("Export failed: %@", localError
);
1670 CFReleaseSafe(*error
);
1671 *error
= localError
;
1673 CFRelease(localError
);
1675 CFReleaseNull(keychain
);
1676 CFReleaseNull(ctx
.qc
.result
);
1680 CFReleaseNull(ctx
.qc
.result
);
1684 CFReleaseNull(q
.q_musrView
);
1685 CFReleaseNull(keybaguuid
);
1690 struct SecServerImportClassState
{
1691 SecDbConnectionRef dbt
;
1693 keybag_handle_t src_keybag
;
1694 keybag_handle_t dest_keybag
;
1695 SecurityClient
*client
;
1696 enum SecItemFilter filter
;
1699 struct SecServerImportItemState
{
1700 const SecDbClass
*class;
1701 struct SecServerImportClassState
*s
;
1705 SecServerImportItem(const void *value
, void *context
)
1707 struct SecServerImportItemState
*state
= (struct SecServerImportItemState
*)context
;
1708 bool inMultiUser
= false;
1709 #if TARGET_OS_IPHONE
1710 if (state
->s
->client
->inMultiUser
)
1714 if (state
->s
->error
)
1717 if (!isDictionary(value
)) {
1718 SecError(errSecParam
, &state
->s
->error
, CFSTR("value %@ is not a dictionary"), value
);
1722 CFDictionaryRef dict
= (CFDictionaryRef
)value
;
1724 secdebug("item", "Import Item : %@", dict
);
1726 /* We use the kSecSysBoundItemFilter to indicate that we don't
1727 * preserve rowid's during import.
1729 if (state
->s
->filter
== kSecBackupableItemFilter
) {
1732 /* We don't filter non sys_bound items during import since we know we
1733 * will never have any in this case.
1735 if (SecItemIsSystemBound(dict
, state
->class, inMultiUser
))
1739 * Don't bother with u items when in edu mode since our current backup system
1740 * don't keep track of items that blongs to the device (u) but rather just
1741 * merge them into one blob.
1743 if (inMultiUser
&& (pdmu
= CFDictionaryGetValue(dict
, kSecAttrAccessible
))) {
1744 if (CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1745 CFEqual(pdmu
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
) ||
1746 CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1747 CFEqual(pdmu
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
))
1749 secdebug("item", "Skipping KU item : %@", dict
);
1754 /* Avoid importing token-based items. Although newer backups should not have them,
1755 * older (iOS9, iOS10.0) produced backups with token-based items.
1757 if (CFDictionaryContainsKey(dict
, kSecAttrTokenID
)) {
1758 secdebug("item", "Skipping token-based item : %@", dict
);
1765 /* This is sligthly confusing:
1766 - During upgrade all items are exported with KEYBAG_NONE.
1767 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1768 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1770 if (state
->s
->src_keybag
== KEYBAG_NONE
) {
1771 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, state
->class, dict
, state
->s
->dest_keybag
, &state
->s
->error
);
1773 item
= SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault
, state
->class, dict
, state
->s
->src_keybag
, state
->s
->dest_keybag
, &state
->s
->error
);
1781 CFDataRef musr
= NULL
;
1782 CFDataRef musrBackup
= CFDictionaryGetValue(dict
, kSecAttrMultiUser
);
1783 CFDataRef systemKeychainUUID
= SecMUSRGetSystemKeychainUUID();
1784 bool systemKeychain
= CFEqualSafe(musrBackup
, systemKeychainUUID
);
1786 #if TARGET_OS_IPHONE
1787 if (state
->s
->client
&& state
->s
->client
->inMultiUser
) {
1788 if (systemKeychain
) {
1789 secwarning("system keychain not allowed in multi user mode for item: %@", item
);
1791 musr
= SecMUSRCreateActiveUserUUID(state
->s
->client
->uid
);
1796 if (systemKeychain
) {
1797 musr
= SecMUSRCopySystemKeychainUUID();
1799 musr
= SecMUSRGetSingleUserKeychainUUID();
1804 CFReleaseNull(item
);
1806 SecDbItemSetValueWithName(item
, CFSTR("musr"), musr
, &state
->s
->error
);
1818 if(state
->s
->filter
!= kSecSysBoundItemFilter
) {
1819 SecDbItemExtractRowIdFromBackupDictionary(item
, dict
, &state
->s
->error
);
1821 SecDbItemInferSyncable(item
, &state
->s
->error
);
1822 insertStatus
= SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1823 if (!insertStatus
) {
1825 When running in EduMode, multiple users share the same
1826 keychain and unfortionaly the rowid is used a
1827 persistant reference and is part of the contraints (its
1828 UNIQUE), so lets clear the rowid and try to insert the
1831 This even happens for normal operation because of
1832 SysBound entries, so in case of a failure, lets try
1833 again to insert the record.
1835 SecDbItemClearRowId(item
, NULL
);
1836 SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1840 /* Reset error if we had one, since we just skip the current item
1841 and continue importing what we can. */
1842 if (state
->s
->error
) {
1843 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1844 item
, state
->class->name
, state
->s
->error
);
1845 CFReleaseNull(state
->s
->error
);
1848 CFReleaseSafe(item
);
1851 static void SecServerImportClass(const void *key
, const void *value
,
1853 struct SecServerImportClassState
*state
=
1854 (struct SecServerImportClassState
*)context
;
1857 if (!isString(key
)) {
1858 SecError(errSecParam
, &state
->error
, CFSTR("class name %@ is not a string"), key
);
1861 /* ignore the Keybag UUID */
1862 if (CFEqual(key
, kSecBackupKeybagUUIDKey
))
1864 const SecDbClass
*class = kc_class_with_name(key
);
1866 secwarning("Ignoring unknown key class '%@'", key
);
1869 if (class == identity_class()) {
1870 SecError(errSecParam
, &state
->error
, CFSTR("attempt to import an identity"));
1873 struct SecServerImportItemState item_state
= {
1874 .class = class, .s
= state
,
1876 if (isArray(value
)) {
1877 CFArrayRef items
= (CFArrayRef
)value
;
1878 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items
), key
, state
->filter
);
1879 SecSignpostBackupCount(SecSignpostImpulseRestoreClassCount
, class->name
, CFArrayGetCount(items
), state
->filter
);
1880 CFArrayApplyFunction(items
, CFRangeMake(0, CFArrayGetCount(items
)),
1881 SecServerImportItem
, &item_state
);
1882 } else if (isDictionary(value
)) {
1883 CFDictionaryRef item
= (CFDictionaryRef
)value
;
1884 secwarning("Import %ld items of class %@ (filter %d)", (long)1, key
, state
->filter
);
1885 SecSignpostBackupCount(SecSignpostImpulseRestoreClassCount
, class->name
, 1, state
->filter
);
1886 SecServerImportItem(item
, &item_state
);
1888 secwarning("Unknown value type for class %@ (filter %d)", key
, state
->filter
);
1892 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt
, SecurityClient
*client
,
1893 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
,
1894 CFDictionaryRef keychain
, enum SecItemFilter filter
,
1895 bool removeKeychainContent
, CFErrorRef
*error
) {
1896 CFStringRef keybaguuid
= NULL
;
1899 CFDictionaryRef sys_bound
= NULL
;
1900 if (filter
== kSecBackupableItemFilter
) {
1901 /* Grab a copy of all the items for which SecItemIsSystemBound()
1903 require(sys_bound
= SecServerCopyKeychainPlist(dbt
, client
, KEYBAG_DEVICE
,
1904 KEYBAG_NONE
, kSecSysBoundItemFilter
,
1909 * Validate the uuid of the source keybag matches what we have in the backup
1911 keybaguuid
= SecCreateKeybagUUID(src_keybag
);
1913 CFStringRef uuid
= CFDictionaryGetValue(keychain
, kSecBackupKeybagUUIDKey
);
1914 if (isString(uuid
)) {
1915 require_action(CFEqual(keybaguuid
, uuid
), errOut
,
1916 SecError(errSecDecode
, error
, CFSTR("Keybag UUID (%@) mismatch with backup (%@)"),
1923 * Shared iPad is very special, it always delete's the user keychain, and never merge content
1925 if (client
->inMultiUser
) {
1926 CFDataRef musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1927 require_action(musrView
, errOut
, ok
= false);
1928 require_action(ok
= SecServerDeleteAllForUser(dbt
, musrView
, true, error
), errOut
, CFReleaseNull(musrView
));
1929 CFReleaseNull(musrView
);
1934 * Delete everything in the keychain.
1935 * We don't want this if we're restoring backups because we probably already synced stuff over
1937 if (removeKeychainContent
) {
1938 require(ok
= SecServerDeleteAll(dbt
, error
), errOut
);
1940 // Custom hack to support bluetooth's workflow for 11.3. Should be removed in a future release.
1941 __block CFErrorRef btError
= NULL
;
1942 bool deletedBT
= kc_transaction(dbt
, &btError
, ^bool{
1943 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
);
1944 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
);
1945 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
);
1946 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
);
1950 secerror("Unable to delete nonsyncable items prior to keychain restore: %@", btError
);
1952 secnotice("restore", "Successfully deleted nonsyncable items");
1954 CFReleaseNull(btError
);
1958 struct SecServerImportClassState state
= {
1960 .src_keybag
= src_keybag
,
1961 .dest_keybag
= dest_keybag
,
1965 /* Import the provided items, preserving rowids. */
1966 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain
));
1967 CFDictionaryApplyFunction(keychain
, SecServerImportClass
, &state
);
1970 state
.src_keybag
= KEYBAG_NONE
;
1971 /* Import the items we preserved with random rowids. */
1972 state
.filter
= kSecSysBoundItemFilter
;
1973 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound
));
1974 CFDictionaryApplyFunction(sys_bound
, SecServerImportClass
, &state
);
1978 CFReleaseSafe(*error
);
1979 *error
= state
.error
;
1981 CFRelease(state
.error
);
1986 // If CKKS had spun up, it's very likely that we just deleted its data.
1987 // Tell it to perform a local resync.
1988 SecCKKSPerformLocalResync();
1991 CFReleaseSafe(sys_bound
);
1992 CFReleaseSafe(keybaguuid
);
1998 SecServerBackupGetKeybagUUID(CFDictionaryRef keychain
, CFErrorRef
*error
)
2000 CFStringRef uuid
= CFDictionaryGetValue(keychain
, kSecBackupKeybagUUIDKey
);
2001 if (!isString(uuid
)) {
2002 SecError(errSecDecode
, error
, CFSTR("Missing or invalid %@ in backup dictionary"), kSecBackupKeybagUUIDKey
);
2008 #pragma mark - key rolling support
2011 struct check_generation_ctx
{
2012 struct s3dl_query_ctx query_ctx
;
2013 uint32_t current_generation
;
2016 static void check_generation(sqlite3_stmt
*stmt
, void *context
) {
2017 struct check_generation_ctx
*c
= context
;
2018 CFDataRef blob
= NULL
;
2020 const uint8_t *cursor
= NULL
;
2022 keyclass_t keyclass
;
2023 uint32_t current_generation
= c
->current_generation
;
2025 require(blob
= s3dl_copy_data_from_col(stmt
, 1, &c
->query_ctx
.q
->q_error
), out
);
2026 blobLen
= CFDataGetLength(blob
);
2027 cursor
= CFDataGetBytePtr(blob
);
2029 /* Check for underflow, ensuring we have at least one full AES block left. */
2030 if (blobLen
< sizeof(version
) + sizeof(keyclass
)) {
2031 SecError(errSecDecode
, &c
->query_ctx
.q
->q_error
, CFSTR("check_generation: Check for underflow"));
2035 version
= *((uint32_t *)cursor
);
2036 cursor
+= sizeof(version
);
2038 (void) version
; // TODO: do something with the version number.
2040 keyclass
= *((keyclass_t
*)cursor
);
2042 // TODO: export get_key_gen macro
2043 if (((keyclass
& ~key_class_last
) == 0) != (current_generation
== 0)) {
2044 c
->query_ctx
.found
++;
2047 CFReleaseSafe(blob
);
2051 c
->query_ctx
.found
++;
2052 CFReleaseSafe(blob
);
2055 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt
, uint32_t current_generation
, CFErrorRef
*error
) {
2056 CFErrorRef localError
= NULL
;
2057 struct check_generation_ctx ctx
= { .query_ctx
= { .dbt
= dbt
}, .current_generation
= current_generation
};
2059 const SecDbClass
*classes
[] = {
2066 for (size_t class_ix
= 0; class_ix
< array_size(classes
); ++class_ix
) {
2067 Query
*q
= query_create(classes
[class_ix
], NULL
, NULL
, &localError
);
2071 ctx
.query_ctx
.q
= q
;
2072 q
->q_limit
= kSecMatchUnlimited
;
2074 bool ok
= s3dl_query(check_generation
, &ctx
, &localError
);
2075 query_destroy(q
, NULL
);
2076 CFReleaseNull(ctx
.query_ctx
.result
);
2078 if (!ok
&& localError
&& (CFErrorGetCode(localError
) == errSecItemNotFound
)) {
2079 CFReleaseNull(localError
);
2082 secerror("Class %@ not up to date", classes
[class_ix
]->name
);
2088 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt
, SecurityClient
*client
, CFErrorRef
*error
) {
2089 return SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
2090 __block
bool ok
= false;
2091 uint32_t keystore_generation_status
;
2093 /* can we migrate to new class keys right now? */
2094 if (!aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
) &&
2095 (keystore_generation_status
& generation_change_in_progress
)) {
2097 /* take a lock assertion */
2098 bool operated_while_unlocked
= SecAKSDoWhileUserBagLocked(error
, ^{
2099 CFErrorRef localError
= NULL
;
2100 CFDictionaryRef backup
= SecServerCopyKeychainPlist(dbt
, NULL
,
2101 KEYBAG_DEVICE
, KEYBAG_NONE
, kSecNoItemFilter
, &localError
);
2104 secerror("Ignoring export error: %@ during roll export", localError
);
2105 CFReleaseNull(localError
);
2107 // 'true' argument: we're replacing everything with newly wrapped entries so remove the old stuff
2108 ok
= SecServerImportKeychainInPlist(dbt
, client
, KEYBAG_NONE
,
2109 KEYBAG_DEVICE
, backup
, kSecNoItemFilter
, true, &localError
);
2111 secerror("Ignoring export error: %@ during roll export", localError
);
2112 CFReleaseNull(localError
);
2117 if (!operated_while_unlocked
)
2120 ok
= SecError(errSecBadReq
, error
, CFSTR("No key roll in progress."));