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/SecItemPriv.h>
40 #include <Security/SecItemInternal.h>
41 #include <securityd/SOSCloudCircleServer.h>
42 #include <utilities/array_size.h>
43 #include <utilities/SecIOFormat.h>
44 #include <SecAccessControlPriv.h>
45 #include <uuid/uuid.h>
47 #define kSecBackupKeybagUUIDKey CFSTR("keybag-uuid")
49 const SecDbAttr
*SecDbAttrWithKey(const SecDbClass
*c
,
52 /* Special case: identites can have all attributes of either cert
54 if (c
== &identity_class
) {
55 const SecDbAttr
*desc
;
56 if (!(desc
= SecDbAttrWithKey(&cert_class
, key
, 0)))
57 desc
= SecDbAttrWithKey(&keys_class
, key
, error
);
62 SecDbForEachAttr(c
, a
) {
63 if (CFEqual(a
->name
, key
))
66 if (CFEqual(kSecAttrNoLegacy
, key
)) {
67 return NULL
; /* results in no ops for this attribute */
71 SecError(errSecNoSuchAttr
, error
, CFSTR("attribute %@ not found in class %@"), key
, c
->name
);
76 bool kc_transaction(SecDbConnectionRef dbt
, CFErrorRef
*error
, bool(^perform
)()) {
77 __block
bool ok
= true;
78 return ok
&& SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
79 ok
= *commit
= perform();
83 static CFStringRef
SecDbGetKindSQL(SecDbAttrKind kind
) {
89 case kSecDbPrimaryKeyAttr
:
90 case kSecDbEncryptedDataAttr
:
92 case kSecDbAccessAttr
:
93 case kSecDbStringAttr
:
95 case kSecDbNumberAttr
:
98 return CFSTR("INTEGER");
100 case kSecDbCreationDateAttr
:
101 case kSecDbModificationDateAttr
:
102 return CFSTR("REAL");
103 case kSecDbRowIdAttr
:
104 return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
105 case kSecDbAccessControlAttr
:
106 case kSecDbUTombAttr
:
107 /* This attribute does not exist in the DB. */
112 static void SecDbAppendUnique(CFMutableStringRef sql
, CFStringRef value
, bool *haveUnique
) {
115 CFStringAppend(sql
, CFSTR("UNIQUE("));
117 SecDbAppendElement(sql
, value
, haveUnique
);
120 static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
121 CFStringAppendFormat(sql
, 0, CFSTR("CREATE TABLE %@("), c
->name
);
122 SecDbForEachAttrWithMask(c
,desc
,kSecDbInFlag
) {
123 CFStringAppendFormat(sql
, 0, CFSTR("%@ %@"), desc
->name
, SecDbGetKindSQL(desc
->kind
));
124 if (desc
->flags
& kSecDbNotNullFlag
)
125 CFStringAppend(sql
, CFSTR(" NOT NULL"));
126 if (desc
->flags
& kSecDbDefault0Flag
)
127 CFStringAppend(sql
, CFSTR(" DEFAULT 0"));
128 if (desc
->flags
& kSecDbDefaultEmptyFlag
)
129 CFStringAppend(sql
, CFSTR(" DEFAULT ''"));
130 CFStringAppend(sql
, CFSTR(","));
133 bool haveUnique
= false;
134 SecDbForEachAttrWithMask(c
,desc
,kSecDbPrimaryKeyFlag
| kSecDbInFlag
) {
135 SecDbAppendUnique(sql
, desc
->name
, &haveUnique
);
138 CFStringAppend(sql
, CFSTR(")"));
140 CFStringAppend(sql
, CFSTR(");"));
143 SecDbForEachAttrWithMask(c
,desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
144 CFStringAppendFormat(sql
, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c
->name
, desc
->name
, c
->name
, desc
->name
);
148 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
149 CFStringAppendFormat(sql
, 0, CFSTR("DROP TABLE %@;"), c
->name
);
152 static CFDataRef
SecPersistentRefCreateWithItem(SecDbItemRef item
, CFErrorRef
*error
) {
153 sqlite3_int64 row_id
= SecDbItemGetRowId(item
, error
);
155 return _SecItemMakePersistentRef(SecDbItemGetClass(item
)->name
, row_id
);
159 bool SecItemDbCreateSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, bool includeVersion
, CFErrorRef
*error
)
161 __block
bool ok
= true;
162 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
163 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
164 SecDbAppendCreateTableWithClass(sql
, *pclass
);
166 if (includeVersion
) {
167 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT INTO tversion(version,minor) VALUES(%d, %d);"),
168 schema
->majorVersion
, schema
->minorVersion
);
170 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
171 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
172 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
178 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
180 __block
bool ok
= true;
181 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
182 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
183 SecDbAppendDropTableWithClass(sql
, *pclass
);
185 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
186 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
187 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
193 CFTypeRef
SecDbItemCopyResult(SecDbItemRef item
, ReturnTypeMask return_type
, CFErrorRef
*error
) {
196 if (return_type
== 0) {
197 /* Caller isn't interested in any results at all. */
199 } else if (return_type
== kSecReturnDataMask
) {
200 a_result
= SecDbItemGetCachedValueWithName(item
, kSecValueData
);
202 CFRetainSafe(a_result
);
204 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
206 } else if (return_type
== kSecReturnPersistentRefMask
) {
207 a_result
= SecPersistentRefCreateWithItem(item
, error
);
209 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item
));
210 /* We need to return more than one value. */
211 if (return_type
& kSecReturnRefMask
) {
212 CFDictionarySetValue(dict
, kSecClass
, SecDbItemGetClass(item
)->name
);
214 CFOptionFlags mask
= (((return_type
& kSecReturnDataMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnDataFlag
: 0) |
215 ((return_type
& kSecReturnAttributesMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnAttrFlag
: 0));
216 SecDbForEachAttr(SecDbItemGetClass(item
), desc
) {
217 if ((desc
->flags
& mask
) != 0) {
218 CFTypeRef value
= SecDbItemGetValue(item
, desc
, error
);
219 if (value
&& !CFEqual(kCFNull
, value
)) {
220 CFDictionarySetValue(dict
, desc
->name
, value
);
221 } else if (value
== NULL
) {
227 if (return_type
& kSecReturnPersistentRefMask
) {
228 CFDataRef pref
= SecPersistentRefCreateWithItem(item
, error
);
229 CFDictionarySetValue(dict
, kSecValuePersistentRef
, pref
);
239 /* AUDIT[securityd](done):
240 attributes (ok) is a caller provided dictionary, only its cf type has
244 s3dl_query_add(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
, CFErrorRef
*error
)
246 if (query_match_count(q
) != 0)
247 return errSecItemMatchUnsupported
;
249 /* Add requires a class to be specified unless we are adding a ref. */
250 if (q
->q_use_item_list
)
251 return errSecUseItemListUnsupported
;
253 /* Actual work here. */
254 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, q
->q_class
, q
->q_item
, KEYBAG_DEVICE
, error
);
257 if (SecDbItemIsTombstone(item
))
258 SecDbItemSetValue(item
, &v7utomb
, q
->q_use_tomb
? q
->q_use_tomb
: kCFBooleanTrue
, NULL
);
262 ok
= SecDbItemSetValueWithName(item
, CFSTR("v_Data"), q
->q_data
, error
);
264 ok
= SecDbItemSetRowId(item
, q
->q_row_id
, error
);
266 ok
= SecDbItemSetValueWithName(item
, CFSTR("musr"), q
->q_musrView
, error
);
267 SecDbItemSetCredHandle(item
, q
->q_use_cred_handle
);
270 ok
= SecDbItemInsert(item
, dbt
, error
);
273 if (result
&& q
->q_return_type
) {
274 *result
= SecDbItemCopyResult(item
, q
->q_return_type
, error
);
277 if (!ok
&& error
&& *error
) {
278 if (CFEqual(CFErrorGetDomain(*error
), kSecDbErrorDomain
) && CFErrorGetCode(*error
) == SQLITE_CONSTRAINT
) {
279 CFReleaseNull(*error
);
280 SecError(errSecDuplicateItem
, error
, CFSTR("duplicate item %@"), item
);
281 } else if (CFEqual(CFErrorGetDomain(*error
), kSecErrorDomain
) && CFErrorGetCode(*error
) == errSecDecode
) { //handle situation when item have pdmn=akpu but passcode is not set
282 CFTypeRef value
= SecDbItemGetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbAccessAttr
, error
), error
);
283 if (value
&& CFEqual(value
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
)) {
284 CFReleaseNull(*error
);
285 SecError(errSecAuthFailed
, error
, CFSTR("authentication failed"));
292 if (SecDbItemIsSyncable(item
))
293 q
->q_sync_changed
= true;
296 secdebug("dbitem", "inserting item %@%s%@", item
, ok
? "" : "failed: ", ok
|| error
== NULL
? (CFErrorRef
)CFSTR("") : *error
);
303 typedef void (*s3dl_handle_row
)(sqlite3_stmt
*stmt
, void *context
);
306 s3dl_copy_data_from_col(sqlite3_stmt
*stmt
, int col
, CFErrorRef
*error
) {
307 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt
, col
),
308 sqlite3_column_bytes(stmt
, col
),
313 s3dl_item_from_col(sqlite3_stmt
*stmt
, Query
*q
, int col
, CFArrayRef accessGroups
,
314 CFMutableDictionaryRef
*item
, SecAccessControlRef
*access_control
, CFErrorRef
*error
) {
315 CFDataRef edata
= NULL
;
317 require(edata
= s3dl_copy_data_from_col(stmt
, col
, error
), out
);
318 ok
= s3dl_item_from_data(edata
, q
, accessGroups
, item
, access_control
, error
);
321 CFReleaseSafe(edata
);
325 struct s3dl_query_ctx
{
327 CFArrayRef accessGroups
;
328 SecDbConnectionRef dbt
;
333 /* Return whatever the caller requested based on the value of q->q_return_type.
334 keys and values must be 3 larger than attr_count in size to accomadate the
335 optional data, class and persistent ref results. This is so we can use
336 the CFDictionaryCreate() api here rather than appending to a
337 mutable dictionary. */
338 static CF_RETURNS_RETAINED CFTypeRef
339 handle_result(Query
*q
,
340 CFMutableDictionaryRef item
,
345 data
= CFDictionaryGetValue(item
, kSecValueData
);
346 if (q
->q_return_type
== 0) {
347 /* Caller isn't interested in any results at all. */
349 } else if (q
->q_return_type
== kSecReturnDataMask
) {
354 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
356 } else if (q
->q_return_type
== kSecReturnPersistentRefMask
) {
357 a_result
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
359 /* We need to return more than one value. */
360 if (q
->q_return_type
& kSecReturnRefMask
) {
361 CFDictionarySetValue(item
, kSecClass
, q
->q_class
->name
);
362 } else if ((q
->q_return_type
& kSecReturnAttributesMask
)) {
363 if (!(q
->q_return_type
& kSecReturnDataMask
)) {
364 CFDictionaryRemoveValue(item
, kSecValueData
);
368 CFDictionaryRemoveAllValues(item
);
369 if ((q
->q_return_type
& kSecReturnDataMask
) && data
) {
370 CFDictionarySetValue(item
, kSecValueData
, data
);
374 if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
375 CFDataRef pref
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
376 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
387 static void s3dl_merge_into_dict(const void *key
, const void *value
, void *context
) {
388 CFDictionarySetValue(context
, key
, value
);
391 static void s3dl_query_row(sqlite3_stmt
*stmt
, void *context
) {
392 struct s3dl_query_ctx
*c
= context
;
395 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
396 CFMutableDictionaryRef item
= NULL
;
397 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->accessGroups
, &item
, NULL
, &q
->q_error
);
399 OSStatus status
= SecErrorGetOSStatus(q
->q_error
);
400 // errSecDecode means the item is corrupted, stash it for delete.
401 if (status
== errSecDecode
) {
402 secwarning("ignoring corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, rowid
, q
->q_error
);
404 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
405 CFMutableStringRef edatastring
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
407 CFStringAppendEncryptedData(edatastring
, edata
);
408 secnotice("item", "corrupted edata=%@", edatastring
);
410 CFReleaseSafe(edata
);
411 CFReleaseSafe(edatastring
);
413 CFReleaseNull(q
->q_error
);
414 } else if (status
== errSecAuthNeeded
) {
415 secwarning("Authentication is needed for %@,rowid=%" PRId64
" (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
417 secerror("decode %@,rowid=%" PRId64
" failed (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
419 // q->q_error will be released appropriately by a call to query_error
426 if (q
->q_class
== &identity_class
) {
427 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
429 CFMutableDictionaryRef key
;
430 /* TODO : if there is a errSecDecode error here, we should cleanup */
431 if (!s3dl_item_from_col(stmt
, q
, 3, c
->accessGroups
, &key
, NULL
, &q
->q_error
) || !key
)
434 CFDataRef certData
= CFDictionaryGetValue(item
, kSecValueData
);
436 CFDictionarySetValue(key
, kSecAttrIdentityCertificateData
, certData
);
437 CFDictionaryRemoveValue(item
, kSecValueData
);
440 CFDataRef certTokenID
= CFDictionaryGetValue(item
, kSecAttrTokenID
);
442 CFDictionarySetValue(key
, kSecAttrIdentityCertificateTokenID
, certTokenID
);
443 CFDictionaryRemoveValue(item
, kSecAttrTokenID
);
445 CFDictionaryApplyFunction(item
, s3dl_merge_into_dict
, key
);
450 if (!match_item(c
->dbt
, q
, c
->accessGroups
, item
))
453 CFTypeRef a_result
= handle_result(q
, item
, rowid
);
455 if (a_result
== kCFNull
) {
456 /* Caller wasn't interested in a result, but we still
457 count this row as found. */
458 CFRelease(a_result
); // Help shut up clang
459 } else if (q
->q_limit
== 1) {
460 c
->result
= a_result
;
462 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, a_result
);
473 SecDbAppendWhereROWID(CFMutableStringRef sql
,
474 CFStringRef col
, sqlite_int64 row_id
,
477 SecDbAppendWhereOrAnd(sql
, needWhere
);
478 CFStringAppendFormat(sql
, NULL
, CFSTR("%@=%lld"), col
, row_id
);
483 SecDbAppendWhereAttrs(CFMutableStringRef sql
, const Query
*q
, bool *needWhere
) {
484 CFIndex ix
, attr_count
= query_attr_count(q
);
485 for (ix
= 0; ix
< attr_count
; ++ix
) {
486 SecDbAppendWhereOrAndEquals(sql
, query_attr_at(q
, ix
).key
, needWhere
);
491 SecDbAppendWhereAccessGroups(CFMutableStringRef sql
,
493 CFArrayRef accessGroups
,
495 CFIndex ix
, ag_count
;
496 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
500 SecDbAppendWhereOrAnd(sql
, needWhere
);
501 CFStringAppend(sql
, col
);
502 CFStringAppend(sql
, CFSTR(" IN (?"));
503 for (ix
= 1; ix
< ag_count
; ++ix
) {
504 CFStringAppend(sql
, CFSTR(",?"));
506 CFStringAppend(sql
, CFSTR(")"));
510 isQueryOverAllMUSRViews(CFTypeRef musrView
)
512 return SecMUSRIsViewAllViews(musrView
);
516 isQueryOverSingleUserView(CFTypeRef musrView
)
518 return isNull(musrView
);
523 isQueryOverBothUserAndSystem(CFTypeRef musrView
, uid_t
*uid
)
525 return SecMUSRGetBothUserAndSystemUUID(musrView
, uid
);
530 SecDbAppendWhereMusr(CFMutableStringRef sql
,
534 SecDbAppendWhereOrAnd(sql
, needWhere
);
537 if (isQueryOverBothUserAndSystem(q
->q_musrView
, NULL
)) {
538 CFStringAppend(sql
, CFSTR("(musr = ? OR musr = ?)"));
541 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
542 /* query over all items, regardless of view */
543 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
544 CFStringAppend(sql
, CFSTR("musr = ?"));
546 CFStringAppend(sql
, CFSTR("musr = ?"));
550 static void SecDbAppendWhereClause(CFMutableStringRef sql
, const Query
*q
,
551 CFArrayRef accessGroups
) {
552 bool needWhere
= true;
553 SecDbAppendWhereROWID(sql
, CFSTR("ROWID"), q
->q_row_id
, &needWhere
);
554 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
555 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
556 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
559 static void SecDbAppendLimit(CFMutableStringRef sql
, CFIndex limit
) {
560 if (limit
!= kSecMatchUnlimited
)
561 CFStringAppendFormat(sql
, NULL
, CFSTR(" LIMIT %" PRIdCFIndex
), limit
);
564 static CF_RETURNS_RETAINED CFStringRef
s3dl_select_sql(Query
*q
, CFArrayRef accessGroups
) {
565 CFMutableStringRef sql
= CFStringCreateMutable(NULL
, 0);
566 if (q
->q_class
== &identity_class
) {
567 CFStringAppendFormat(sql
, NULL
, CFSTR("SELECT crowid, %@"
569 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
570 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
571 " keys.*,cert.data AS %@"
573 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"),
574 kSecAttrIdentityCertificateData
, kSecAttrIdentityCertificateData
);
575 SecDbAppendWhereAccessGroups(sql
, CFSTR("cert.agrp"), accessGroups
, 0);
576 /* The next 3 SecDbAppendWhere calls are in the same order as in
577 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
578 as long as we do an extra sqlBindAccessGroups first. */
579 SecDbAppendWhereROWID(sql
, CFSTR("crowid"), q
->q_row_id
, 0);
580 CFStringAppend(sql
, CFSTR(")"));
581 bool needWhere
= true;
582 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
583 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
584 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
586 CFStringAppend(sql
, CFSTR("SELECT rowid, data FROM "));
587 CFStringAppend(sql
, q
->q_class
->name
);
588 SecDbAppendWhereClause(sql
, q
, accessGroups
);
590 //do not append limit for all queries which needs filtering
591 if (q
->q_match_issuer
== NULL
&& q
->q_match_policy
== NULL
&& q
->q_match_valid_on_date
== NULL
&& q
->q_match_trusted_only
== NULL
) {
592 SecDbAppendLimit(sql
, q
->q_limit
);
598 static bool sqlBindMusr(sqlite3_stmt
*stmt
, const Query
*q
, int *pParam
, CFErrorRef
*error
) {
604 if (isQueryOverBothUserAndSystem(q
->q_musrView
, &uid
)) {
605 /* network extensions are special and get to query both user and system views */
606 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
607 result
= SecDbBindObject(stmt
, param
++, systemUUID
, error
);
609 CFDataRef activeUser
= SecMUSRCreateActiveUserUUID(uid
);
610 result
= SecDbBindObject(stmt
, param
++, activeUser
, error
);
611 CFReleaseNull(activeUser
);
615 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
616 /* query over all items, regardless of view */
617 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
618 CFDataRef singleUUID
= SecMUSRGetSingleUserKeychainUUID();
619 result
= SecDbBindObject(stmt
, param
++, singleUUID
, error
);
621 result
= SecDbBindObject(stmt
, param
++, q
->q_musrView
, error
);
629 static bool sqlBindAccessGroups(sqlite3_stmt
*stmt
, CFArrayRef accessGroups
,
630 int *pParam
, CFErrorRef
*error
) {
633 CFIndex ix
, count
= accessGroups
? CFArrayGetCount(accessGroups
) : 0;
634 for (ix
= 0; ix
< count
; ++ix
) {
635 result
= SecDbBindObject(stmt
, param
++,
636 CFArrayGetValueAtIndex(accessGroups
, ix
),
645 static bool sqlBindWhereClause(sqlite3_stmt
*stmt
, const Query
*q
,
646 CFArrayRef accessGroups
, int *pParam
, CFErrorRef
*error
) {
649 CFIndex ix
, attr_count
= query_attr_count(q
);
650 for (ix
= 0; ix
< attr_count
; ++ix
) {
651 result
= SecDbBindObject(stmt
, param
++, query_attr_at(q
, ix
).value
, error
);
657 result
= sqlBindMusr(stmt
, q
, ¶m
, error
);
660 /* Bind the access group to the sql. */
662 result
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
669 bool SecDbItemQuery(SecDbQueryRef query
, CFArrayRef accessGroups
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
670 void (^handle_row
)(SecDbItemRef item
, bool *stop
)) {
671 __block
bool ok
= true;
672 /* Sanity check the query. */
674 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
676 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
677 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
679 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
682 CFStringRef sql
= s3dl_select_sql(query
, accessGroups
);
685 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
686 /* Bind the values being searched for to the SELECT statement. */
688 if (query
->q_class
== &identity_class
) {
689 /* Bind the access groups to cert.agrp. */
690 ok
&= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
693 ok
&= sqlBindWhereClause(stmt
, query
, accessGroups
, ¶m
, error
);
695 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
) {
696 SecDbItemRef itemFromStatement
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, query
->q_class
, stmt
, query
->q_keybag
, error
, return_attr
);
697 if (itemFromStatement
) {
698 CFTransferRetained(itemFromStatement
->credHandle
, query
->q_use_cred_handle
);
699 if (match_item(dbconn
, query
, accessGroups
, itemFromStatement
->attributes
))
700 handle_row(itemFromStatement
, stop
);
701 CFReleaseNull(itemFromStatement
);
703 secerror("failed to create item from stmt: %@", error
? *error
: (CFErrorRef
)"no error");
705 CFReleaseNull(*error
);
720 s3dl_query(s3dl_handle_row handle_row
,
721 void *context
, CFErrorRef
*error
)
723 struct s3dl_query_ctx
*c
= context
;
724 SecDbConnectionRef dbt
= c
->dbt
;
726 CFArrayRef accessGroups
= c
->accessGroups
;
728 /* Sanity check the query. */
730 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
732 /* Actual work here. */
733 if (q
->q_limit
== 1) {
736 c
->result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
738 CFStringRef sql
= s3dl_select_sql(q
, accessGroups
);
739 bool ok
= SecDbWithSQL(dbt
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
741 /* Bind the values being searched for to the SELECT statement. */
743 if (q
->q_class
== &identity_class
) {
744 /* Bind the access groups to cert.agrp. */
745 sql_ok
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
748 sql_ok
= sqlBindWhereClause(stmt
, q
, accessGroups
, ¶m
, error
);
750 SecDbForEach(dbt
, stmt
, error
, ^bool (int row_index
) {
751 handle_row(stmt
, context
);
753 bool needs_auth
= q
->q_error
&& CFErrorGetCode(q
->q_error
) == errSecAuthNeeded
;
754 if (q
->q_skip_acl_items
&& needs_auth
)
755 // Skip items needing authentication if we are told to do so.
756 CFReleaseNull(q
->q_error
);
758 bool stop
= q
->q_limit
!= kSecMatchUnlimited
&& c
->found
>= q
->q_limit
;
759 stop
= stop
|| (q
->q_error
&& !needs_auth
);
768 // First get the error from the query, since errSecDuplicateItem from an
769 // update query should superceed the errSecItemNotFound below.
770 if (!query_error(q
, error
))
772 if (ok
&& c
->found
== 0) {
773 ok
= SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
774 if (q
->q_spindump_on_failure
) {
775 __security_stackshotreport(CFSTR("ItemNotFound"), __sec_exception_code_LostInMist
);
783 s3dl_copy_matching(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
,
784 CFArrayRef accessGroups
, CFErrorRef
*error
)
786 struct s3dl_query_ctx ctx
= {
787 .q
= q
, .accessGroups
= accessGroups
, .dbt
= dbt
,
789 if (q
->q_row_id
&& query_attr_count(q
))
790 return SecError(errSecItemIllegalQuery
, error
,
791 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
793 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
794 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
795 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
796 bool ok
= s3dl_query(s3dl_query_row
, &ctx
, error
);
798 *result
= ctx
.result
;
800 CFReleaseSafe(ctx
.result
);
805 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
806 static void query_set_attribute_with_desc(const SecDbAttr
*desc
, const void *value
, Query
*q
) {
807 if (CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
809 for (ix
= 0; ix
< q
->q_attr_end
; ++ix
) {
810 if (CFEqual(desc
->name
, q
->q_pairs
[ix
].key
)) {
811 CFReleaseSafe(q
->q_pairs
[ix
].value
);
813 for (; ix
< q
->q_attr_end
; ++ix
) {
814 q
->q_pairs
[ix
] = q
->q_pairs
[ix
+ 1];
816 CFDictionaryRemoveValue(q
->q_item
, desc
->name
);
821 query_add_attribute_with_desc(desc
, value
, q
);
824 /* Update modification_date if needed. */
825 static void query_pre_update(Query
*q
) {
826 SecDbForEachAttr(q
->q_class
, desc
) {
827 if (desc
->kind
== kSecDbModificationDateAttr
) {
828 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
829 query_set_attribute_with_desc(desc
, now
, q
);
835 /* Make sure all attributes that are marked as not_null have a value. If
836 force_date is false, only set mdat and cdat if they aren't already set. */
837 void query_pre_add(Query
*q
, bool force_date
) {
838 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
839 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInFlag
) {
840 if (desc
->kind
== kSecDbCreationDateAttr
||
841 desc
->kind
== kSecDbModificationDateAttr
) {
843 query_set_attribute_with_desc(desc
, now
, q
);
844 } else if (!CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
845 query_add_attribute_with_desc(desc
, now
, q
);
847 } else if ((desc
->flags
& kSecDbNotNullFlag
) &&
848 !CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
849 CFTypeRef value
= NULL
;
850 if (desc
->flags
& kSecDbDefault0Flag
) {
851 if (desc
->kind
== kSecDbDateAttr
)
852 value
= CFDateCreate(kCFAllocatorDefault
, 0.0);
855 value
= CFNumberCreate(0, kCFNumberSInt32Type
, &vzero
);
857 } else if (desc
->flags
& kSecDbDefaultEmptyFlag
) {
858 if (desc
->kind
== kSecDbDataAttr
|| desc
->kind
== kSecDbUUIDAttr
)
859 value
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
866 /* Safe to use query_add_attribute here since the attr wasn't
868 query_add_attribute_with_desc(desc
, value
, q
);
876 // Return a tri state value false->never make a tombstone, true->always make a
877 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
878 static CFBooleanRef
s3dl_should_make_tombstone(Query
*q
, bool item_is_syncable
, SecDbItemRef item
) {
880 return q
->q_use_tomb
;
881 else if (item_is_syncable
&& !SecDbItemIsTombstone(item
))
884 return kCFBooleanFalse
;
886 /* AUDIT[securityd](done):
887 attributesToUpdate (ok) is a caller provided dictionary,
888 only its cf types have been checked.
891 s3dl_query_update(SecDbConnectionRef dbt
, Query
*q
,
892 CFDictionaryRef attributesToUpdate
, CFArrayRef accessGroups
, CFErrorRef
*error
)
894 /* Sanity check the query. */
895 if (query_match_count(q
) != 0)
896 return SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported in attributes to update"));
898 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported in attributes to update"));
899 if (q
->q_row_id
&& query_attr_count(q
))
900 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
902 __block
bool result
= true;
903 Query
*u
= query_create(q
->q_class
, NULL
, attributesToUpdate
, error
);
904 if (u
== NULL
) return false;
905 require_action_quiet(query_update_parse(u
, attributesToUpdate
, error
), errOut
, result
= false);
907 result
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
908 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
909 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
910 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
911 result
&= SecDbItemQuery(q
, accessGroups
, dbt
, error
, ^(SecDbItemRef item
, bool *stop
) {
912 // We always need to know the error here.
913 CFErrorRef localError
= NULL
;
914 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
915 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
916 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
917 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, u
->q_item
, &localError
);
918 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
919 CFReleaseSafe(storedSHA1
);
920 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
921 // We just ignore this, and treat as if item is not found.
922 secwarning("deleting corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
923 CFReleaseNull(localError
);
924 if (!SecDbItemDelete(item
, dbt
, false, &localError
)) {
925 secerror("failed to delete corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
926 CFReleaseNull(localError
);
930 if (new_item
!= NULL
&& u
->q_access_control
!= NULL
)
931 SecDbItemSetAccessControl(new_item
, u
->q_access_control
, &localError
);
932 result
= SecErrorPropagate(localError
, error
) && new_item
;
934 bool item_is_sync
= SecDbItemIsSyncable(item
);
935 result
= SecDbItemUpdate(item
, new_item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
938 if (item_is_sync
|| SecDbItemIsSyncable(new_item
))
939 q
->q_sync_changed
= true;
947 if (result
&& !q
->q_changed
)
948 result
= SecError(errSecItemNotFound
, error
, CFSTR("No items updated"));
950 if (!query_destroy(u
, error
))
955 static bool SecDbItemNeedAuth(SecDbItemRef item
, CFErrorRef
*error
)
957 CFErrorRef localError
= NULL
;
958 if (!SecDbItemEnsureDecrypted(item
, &localError
) && localError
&& CFErrorGetCode(localError
) == errSecAuthNeeded
) {
964 CFReleaseSafe(localError
);
969 s3dl_query_delete(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFErrorRef
*error
)
971 __block
bool ok
= true;
972 __block
bool needAuth
= false;
973 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
974 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
975 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
976 ok
&= SecDbItemSelect(q
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
978 },^bool(CFMutableStringRef sql
, bool *needWhere
) {
979 SecDbAppendWhereClause(sql
, q
, accessGroups
);
981 },^bool(sqlite3_stmt
* stmt
, int col
) {
982 return sqlBindWhereClause(stmt
, q
, accessGroups
, &col
, error
);
983 }, ^(SecDbItemRef item
, bool *stop
) {
984 // Check if item need to be authenticated by LocalAuthentication
985 item
->cryptoOp
= kAKSKeyOpDelete
;
986 if (SecDbItemNeedAuth(item
, error
)) {
990 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
991 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
992 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
993 bool item_is_sync
= SecDbItemIsSyncable(item
);
994 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
995 CFReleaseSafe(storedSHA1
);
996 ok
= SecDbItemDelete(item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
1000 q
->q_sync_changed
= true;
1003 if (ok
&& !q
->q_changed
&& !needAuth
) {
1004 ok
= SecError(errSecItemNotFound
, error
, CFSTR("Delete failed to delete anything"));
1006 return ok
&& !needAuth
;
1010 matchAnyString(CFStringRef needle
, CFStringRef
*haystack
)
1013 if (CFEqual(needle
, *haystack
))
1020 /* Return true iff the item in question should not be backed up, nor restored,
1021 but when restoring a backup the original version of the item should be
1022 added back to the keychain again after the restore completes. */
1023 static bool SecItemIsSystemBound(CFDictionaryRef item
, const SecDbClass
*cls
, bool multiUser
) {
1024 CFStringRef agrp
= CFDictionaryGetValue(item
, kSecAttrAccessGroup
);
1025 if (!isString(agrp
))
1028 if (CFEqualSafe(agrp
, kSOSInternalAccessGroup
)) {
1029 secdebug("backup", "found sysbound item: %@", item
);
1033 if (CFEqual(agrp
, CFSTR("lockdown-identities"))) {
1034 secdebug("backup", "found sys_bound item: %@", item
);
1038 if (CFEqual(agrp
, CFSTR("apple")) && cls
== &genp_class
) {
1039 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1040 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1042 if (isString(service
) && isString(account
)) {
1043 static CFStringRef mcAccounts
[] = {
1049 if (CFEqual(service
, CFSTR("com.apple.managedconfiguration"))
1050 && matchAnyString(account
, mcAccounts
))
1052 secdebug("backup", "found sys_bound item: %@", item
);
1058 if (multiUser
&& CFEqual(agrp
, CFSTR("com.apple.apsd")) && cls
== &genp_class
) {
1059 static CFStringRef pushServices
[] = {
1060 CFSTR("push.apple.com"),
1061 CFSTR("push.apple.com,PerAppToken.v0"),
1064 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1066 if (isString(service
) && matchAnyString(service
, pushServices
)) {
1067 secdebug("backup", "found sys_bound item: %@", item
);
1072 if (multiUser
&& CFEqual(agrp
, CFSTR("appleaccount")) && cls
== &genp_class
) {
1073 static CFStringRef accountServices
[] = {
1074 CFSTR("com.apple.appleaccount.fmf.token"), /* temporary tokens while accout is being setup */
1075 CFSTR("com.apple.appleaccount.fmf.apptoken"),
1076 CFSTR("com.apple.appleaccount.fmip.siritoken"),
1077 CFSTR("com.apple.appleaccount.cloudkit.token"),
1080 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1082 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1083 secdebug("backup", "found exact sys_bound item: %@", item
);
1088 if (multiUser
&& CFEqual(agrp
, CFSTR("apple")) && cls
== &genp_class
) {
1089 static CFStringRef accountServices
[] = {
1090 CFSTR("com.apple.account.AppleAccount.token"),
1091 CFSTR("com.apple.account.AppleAccount.password"),
1092 CFSTR("com.apple.account.AppleAccount.rpassword"),
1093 CFSTR("com.apple.account.idms.token"),
1094 CFSTR("com.apple.account.idms.heartbeat-token"),
1095 CFSTR("com.apple.account.idms.continuation-key"),
1096 CFSTR("com.apple.account.CloudKit.token"),
1097 CFSTR("com.apple.account.IdentityServices.password"), /* accountsd for ids */
1098 CFSTR("com.apple.account.IdentityServices.rpassword"),
1099 CFSTR("com.apple.account.IdentityServices.token"),
1100 CFSTR("BackupIDSAccountToken"),
1101 CFSTR("com.apple.ids"),
1106 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1108 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1109 secdebug("backup", "found exact sys_bound item: %@", item
);
1112 if (isString(service
) && CFEqual(service
, CFSTR("com.apple.facetime"))) {
1113 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1114 if (isString(account
) && CFEqual(account
, CFSTR("registrationV1"))) {
1115 secdebug("backup", "found exact sys_bound item: %@", item
);
1121 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== &genp_class
) {
1122 static CFStringRef accountServices
[] = {
1126 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1128 if (isString(service
) && matchAnyString(service
, accountServices
)) {
1129 secdebug("backup", "found exact sys_bound item: %@", item
);
1134 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== &keys_class
) {
1135 static CFStringRef exactMatchingLabel
[] = {
1136 CFSTR("iMessage Encryption Key"),
1137 CFSTR("iMessage Signing Key"),
1139 CFStringRef label
= CFDictionaryGetValue(item
, kSecAttrLabel
);
1140 if (isString(label
)) {
1141 if (matchAnyString(label
, exactMatchingLabel
)) {
1142 secdebug("backup", "found exact sys_bound item: %@", item
);
1149 secdebug("backup", "found non sys_bound item: %@", item
);
1153 /* Delete all items from the current keychain. If this is not an in
1154 place upgrade we don't delete items in the 'lockdown-identities'
1155 access group, this ensures that an import or restore of a backup
1156 will never overwrite an existing activation record. */
1157 static bool SecServerDeleteAll(SecDbConnectionRef dbt
, CFErrorRef
*error
) {
1158 secwarning("SecServerDeleteAll");
1160 return kc_transaction(dbt
, error
, ^{
1162 bool ok
= (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1163 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1164 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1165 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1170 #if TARGET_OS_IPHONE
1172 static bool DeleteAllFromTableForMUSRView(SecDbConnectionRef dbt
,
1178 sqlite3_stmt
*stmt
= NULL
;
1179 CFStringRef sql2
= NULL
;
1183 sql2
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@ AND pdmn NOT IN ('aku','akpu','cku','dku')"), sql
);
1185 sql2
= CFRetain(sql
);
1187 require(sql2
, fail
);
1189 stmt
= SecDbCopyStmt(dbt
, sql2
, NULL
, error
);
1190 require(stmt
, fail
);
1192 ok
= SecDbBindObject(stmt
, 1, musr
, error
);
1195 ok
= SecDbStep(dbt
, stmt
, error
, ^(bool *stop
) { });
1200 ok
= SecDbFinalize(stmt
, error
);
1203 secwarning("DeleteAllFromTableForMUSRView failed for %@ for musr: %@: %@", sql2
, musr
, error
? *error
: NULL
);
1205 CFReleaseNull(sql2
);
1210 bool SecServerDeleteAllForUser(SecDbConnectionRef dbt
, CFDataRef musrView
, bool keepU
, CFErrorRef
*error
) {
1211 secwarning("SecServerDeleteAllForUser for user: %@ keepU %s", musrView
, keepU
? "yes" : "no");
1213 return kc_transaction(dbt
, error
, ^{
1216 ok
= (DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM genp WHERE musr = ?"), musrView
, keepU
, error
) &&
1217 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM inet WHERE musr = ?"), musrView
, keepU
, error
) &&
1218 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM cert WHERE musr = ?"), musrView
, keepU
, error
) &&
1219 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM keys WHERE musr = ?"), musrView
, keepU
, error
));
1227 struct s3dl_export_row_ctx
{
1228 struct s3dl_query_ctx qc
;
1229 keybag_handle_t dest_keybag
;
1230 enum SecItemFilter filter
;
1234 static void s3dl_export_row(sqlite3_stmt
*stmt
, void *context
) {
1235 struct s3dl_export_row_ctx
*c
= context
;
1237 SecAccessControlRef access_control
= NULL
;
1238 CFErrorRef localError
= NULL
;
1240 /* Skip akpu items when backing up, those are intentionally lost across restores. The same applies to SEP-based keys */
1241 bool skip_akpu_or_token
= c
->filter
== kSecBackupableItemFilter
;
1243 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
1244 CFMutableDictionaryRef item
= NULL
;
1245 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->qc
.accessGroups
, &item
, &access_control
, &localError
);
1247 bool is_akpu
= access_control
? CFEqualSafe(SecAccessControlGetProtection(access_control
),
1248 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
) : false;
1249 bool is_token
= (ok
&& item
!= NULL
) ? CFDictionaryContainsKey(item
, kSecAttrTokenID
) : false;
1251 if (ok
&& item
&& !(skip_akpu_or_token
&& (is_akpu
|| is_token
))) {
1252 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1253 bool do_sys_bound
= c
->filter
== kSecSysBoundItemFilter
;
1254 if (c
->filter
== kSecNoItemFilter
||
1255 SecItemIsSystemBound(item
, q
->q_class
, c
->multiUser
) == do_sys_bound
) {
1256 /* Re-encode the item. */
1257 secdebug("item", "export rowid %llu item: %@", rowid
, item
);
1258 /* The code below could be moved into handle_row. */
1259 CFDataRef pref
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
1261 if (c
->dest_keybag
!= KEYBAG_NONE
) {
1262 CFMutableDictionaryRef auth_attribs
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1263 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInAuthenticatedDataFlag
) {
1264 CFTypeRef value
= CFDictionaryGetValue(item
, desc
->name
);
1266 CFDictionaryAddValue(auth_attribs
, desc
->name
, value
);
1267 CFDictionaryRemoveValue(item
, desc
->name
);
1271 /* Encode and encrypt the item to the specified keybag. */
1272 CFDataRef edata
= NULL
;
1273 bool encrypted
= ks_encrypt_data(c
->dest_keybag
, access_control
, q
->q_use_cred_handle
, item
, auth_attribs
, &edata
, false, &q
->q_error
);
1274 CFDictionaryRemoveAllValues(item
);
1275 CFRelease(auth_attribs
);
1277 CFDictionarySetValue(item
, kSecValueData
, edata
);
1278 CFReleaseSafe(edata
);
1280 seccritical("ks_encrypt_data %@,rowid=%" PRId64
": failed: %@", q
->q_class
->name
, rowid
, q
->q_error
);
1281 CFReleaseNull(q
->q_error
);
1284 if (CFDictionaryGetCount(item
)) {
1285 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
1286 CFArrayAppendValue((CFMutableArrayRef
)c
->qc
.result
, item
);
1289 CFReleaseSafe(pref
);
1294 OSStatus status
= SecErrorGetOSStatus(localError
);
1296 if (status
== errSecInteractionNotAllowed
&& is_akpu
&& skip_akpu_or_token
) {
1297 // We expect akpu items to be inaccessible when the device is locked.
1298 CFReleaseNull(localError
);
1300 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1301 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1302 secinfo("item","Could not export item for rowid %llu: %@", rowid
, localError
);
1304 if(status
== errSecDecode
) {
1305 CFReleaseNull(localError
);
1307 CFReleaseSafe(q
->q_error
);
1308 q
->q_error
=localError
;
1312 CFReleaseSafe(access_control
);
1316 SecCreateKeybagUUID(keybag_handle_t keybag
)
1318 #if !TARGET_HAS_KEYSTORE
1323 if (aks_get_bag_uuid(keybag
, uuid
) != KERN_SUCCESS
)
1325 uuid_unparse_lower(uuid
, uuidstr
);
1326 return CFStringCreateWithCString(NULL
, uuidstr
, kCFStringEncodingUTF8
);
1332 SecServerCopyKeychainPlist(SecDbConnectionRef dbt
,
1333 SecurityClient
*client
,
1334 keybag_handle_t src_keybag
,
1335 keybag_handle_t dest_keybag
,
1336 enum SecItemFilter filter
,
1337 CFErrorRef
*error
) {
1338 CFMutableDictionaryRef keychain
;
1339 keychain
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1340 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1342 bool inMultiUser
= false;
1343 CFStringRef keybaguuid
= NULL
;
1344 Query q
= { .q_keybag
= src_keybag
,
1349 if (error
&& !*error
)
1350 SecError(errSecAllocate
, error
, CFSTR("Can't create keychain dictionary"));
1355 kSecReturnDataMask
|
1356 kSecReturnAttributesMask
|
1357 kSecReturnPersistentRefMask
;
1358 q
.q_limit
= kSecMatchUnlimited
;
1359 q
.q_skip_acl_items
= true;
1362 #if TARGET_OS_IPHONE
1363 if (client
&& client
->inMultiUser
) {
1364 q
.q_musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1369 q
.q_musrView
= SecMUSRGetSingleUserKeychainUUID();
1370 CFRetain(q
.q_musrView
);
1372 keybaguuid
= SecCreateKeybagUUID(dest_keybag
);
1374 CFDictionarySetValue(keychain
, kSecBackupKeybagUUIDKey
, keybaguuid
);
1377 /* Get rid of this duplicate. */
1378 const SecDbClass
*SecDbClasses
[] = {
1385 for (class_ix
= 0; class_ix
< array_size(SecDbClasses
);
1387 q
.q_class
= SecDbClasses
[class_ix
];
1388 struct s3dl_export_row_ctx ctx
= {
1389 .qc
= { .q
= &q
, .dbt
= dbt
},
1390 .dest_keybag
= dest_keybag
, .filter
= filter
,
1391 .multiUser
= inMultiUser
,
1394 secnotice("item", "exporting class '%@'", q
.q_class
->name
);
1396 CFErrorRef localError
= NULL
;
1397 if (s3dl_query(s3dl_export_row
, &ctx
, &localError
)) {
1398 if (CFArrayGetCount(ctx
.qc
.result
))
1399 CFDictionaryAddValue(keychain
, q
.q_class
->name
, ctx
.qc
.result
);
1402 OSStatus status
= (OSStatus
)CFErrorGetCode(localError
);
1403 if (status
== errSecItemNotFound
) {
1404 CFRelease(localError
);
1406 secerror("Export failed: %@", localError
);
1408 CFReleaseSafe(*error
);
1409 *error
= localError
;
1411 CFRelease(localError
);
1413 CFReleaseNull(keychain
);
1414 CFReleaseNull(ctx
.qc
.result
);
1418 CFReleaseNull(ctx
.qc
.result
);
1422 CFReleaseNull(q
.q_musrView
);
1423 CFReleaseNull(keybaguuid
);
1428 struct SecServerImportClassState
{
1429 SecDbConnectionRef dbt
;
1431 keybag_handle_t src_keybag
;
1432 keybag_handle_t dest_keybag
;
1433 SecurityClient
*client
;
1434 enum SecItemFilter filter
;
1437 struct SecServerImportItemState
{
1438 const SecDbClass
*class;
1439 struct SecServerImportClassState
*s
;
1443 SecServerImportItem(const void *value
, void *context
)
1445 struct SecServerImportItemState
*state
= (struct SecServerImportItemState
*)context
;
1446 bool inMultiUser
= false;
1447 #if TARGET_OS_IPHONE
1448 if (state
->s
->client
->inMultiUser
)
1452 if (state
->s
->error
)
1455 if (!isDictionary(value
)) {
1456 SecError(errSecParam
, &state
->s
->error
, CFSTR("value %@ is not a dictionary"), value
);
1460 CFDictionaryRef dict
= (CFDictionaryRef
)value
;
1462 secdebug("item", "Import Item : %@", dict
);
1464 /* We use the kSecSysBoundItemFilter to indicate that we don't
1465 * preserve rowid's during import.
1467 if (state
->s
->filter
== kSecBackupableItemFilter
) {
1470 /* We don't filter non sys_bound items during import since we know we
1471 * will never have any in this case.
1473 if (SecItemIsSystemBound(dict
, state
->class, inMultiUser
))
1477 * Don't bother with u items when in edu mode since our current backup system
1478 * don't keep track of items that blongs to the device (u) but rather just
1479 * merge them into one blob.
1481 if (inMultiUser
&& (pdmu
= CFDictionaryGetValue(dict
, kSecAttrAccessible
))) {
1482 if (CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1483 CFEqual(pdmu
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
) ||
1484 CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1485 CFEqual(pdmu
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
))
1487 secdebug("item", "Skipping KU item : %@", dict
);
1492 /* Avoid importing token-based items. Although newer backups should not have them,
1493 * older (iOS9, iOS10.0) produced backups with token-based items.
1495 if (CFDictionaryContainsKey(dict
, kSecAttrTokenID
)) {
1496 secdebug("item", "Skipping token-based item : %@", dict
);
1503 /* This is sligthly confusing:
1504 - During upgrade all items are exported with KEYBAG_NONE.
1505 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1506 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1508 if (state
->s
->src_keybag
== KEYBAG_NONE
) {
1509 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, state
->class, dict
, state
->s
->dest_keybag
, &state
->s
->error
);
1511 item
= SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault
, state
->class, dict
, state
->s
->src_keybag
, state
->s
->dest_keybag
, &state
->s
->error
);
1519 CFDataRef musr
= NULL
;
1520 CFDataRef musrBackup
= CFDictionaryGetValue(dict
, kSecAttrMultiUser
);
1521 CFDataRef systemKeychainUUID
= SecMUSRGetSystemKeychainUUID();
1522 bool systemKeychain
= CFEqualSafe(musrBackup
, systemKeychainUUID
);
1524 #if TARGET_OS_IPHONE
1525 if (state
->s
->client
&& state
->s
->client
->inMultiUser
) {
1526 if (systemKeychain
) {
1527 secwarning("system keychain not allowed in multi user mode for item: %@", item
);
1529 musr
= SecMUSRCreateActiveUserUUID(state
->s
->client
->uid
);
1534 if (systemKeychain
) {
1535 musr
= SecMUSRCopySystemKeychainUUID();
1537 musr
= SecMUSRGetSingleUserKeychainUUID();
1542 CFReleaseNull(item
);
1544 SecDbItemSetValueWithName(item
, CFSTR("musr"), musr
, &state
->s
->error
);
1556 if(state
->s
->filter
!= kSecSysBoundItemFilter
) {
1557 SecDbItemExtractRowIdFromBackupDictionary(item
, dict
, &state
->s
->error
);
1559 SecDbItemInferSyncable(item
, &state
->s
->error
);
1560 insertStatus
= SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1561 if (!insertStatus
) {
1563 When running in EduMode, multiple users share the same
1564 keychain and unfortionaly the rowid is used a
1565 persistant reference and is part of the contraints (its
1566 UNIQUE), so lets clear the rowid and try to insert the
1569 This even happens for normal operation because of
1570 SysBound entries, so in case of a failure, lets try
1571 again to insert the record.
1573 SecDbItemClearRowId(item
, NULL
);
1574 SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1578 /* Reset error if we had one, since we just skip the current item
1579 and continue importing what we can. */
1580 if (state
->s
->error
) {
1581 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1582 item
, state
->class->name
, state
->s
->error
);
1583 CFReleaseNull(state
->s
->error
);
1586 CFReleaseSafe(item
);
1589 static void SecServerImportClass(const void *key
, const void *value
,
1591 struct SecServerImportClassState
*state
=
1592 (struct SecServerImportClassState
*)context
;
1595 if (!isString(key
)) {
1596 SecError(errSecParam
, &state
->error
, CFSTR("class name %@ is not a string"), key
);
1599 /* ignore the Keybag UUID */
1600 if (CFEqual(key
, kSecBackupKeybagUUIDKey
))
1602 const SecDbClass
*class = kc_class_with_name(key
);
1604 secwarning("Ignoring unknown key class '%@'", key
);
1607 if (class == &identity_class
) {
1608 SecError(errSecParam
, &state
->error
, CFSTR("attempt to import an identity"));
1611 struct SecServerImportItemState item_state
= {
1612 .class = class, .s
= state
,
1614 if (isArray(value
)) {
1615 CFArrayRef items
= (CFArrayRef
)value
;
1616 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items
), key
, state
->filter
);
1617 CFArrayApplyFunction(items
, CFRangeMake(0, CFArrayGetCount(items
)),
1618 SecServerImportItem
, &item_state
);
1619 } else if (isDictionary(value
)) {
1620 CFDictionaryRef item
= (CFDictionaryRef
)value
;
1621 secwarning("Import %ld items of class %@ (filter %d)", (long)CFDictionaryGetCount(item
), key
, state
->filter
);
1622 SecServerImportItem(item
, &item_state
);
1624 secwarning("Unknown value type for class %@ (filter %d)", key
, state
->filter
);
1628 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt
, SecurityClient
*client
,
1629 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
,
1630 CFDictionaryRef keychain
, enum SecItemFilter filter
, CFErrorRef
*error
) {
1631 CFStringRef keybaguuid
= NULL
;
1634 CFDictionaryRef sys_bound
= NULL
;
1635 if (filter
== kSecBackupableItemFilter
) {
1636 /* Grab a copy of all the items for which SecItemIsSystemBound()
1638 require(sys_bound
= SecServerCopyKeychainPlist(dbt
, client
, KEYBAG_DEVICE
,
1639 KEYBAG_NONE
, kSecSysBoundItemFilter
,
1644 * Validate the uuid of the source keybag matches what we have in the backup
1646 keybaguuid
= SecCreateKeybagUUID(src_keybag
);
1648 CFStringRef uuid
= CFDictionaryGetValue(keychain
, kSecBackupKeybagUUIDKey
);
1649 if (isString(uuid
)) {
1650 require_action(CFEqual(keybaguuid
, uuid
), errOut
,
1651 SecError(errSecDecode
, error
, CFSTR("Keybag UUID (%@) mismatch with backup (%@)"),
1656 /* Delete everything in the keychain. */
1657 #if TARGET_OS_IPHONE
1658 if (client
->inMultiUser
) {
1659 CFDataRef musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1660 require_action(musrView
, errOut
, ok
= false);
1661 require_action(ok
= SecServerDeleteAllForUser(dbt
, musrView
, true, error
), errOut
, CFReleaseNull(musrView
));
1662 CFReleaseNull(musrView
);
1666 require(ok
= SecServerDeleteAll(dbt
, error
), errOut
);
1669 struct SecServerImportClassState state
= {
1671 .src_keybag
= src_keybag
,
1672 .dest_keybag
= dest_keybag
,
1676 /* Import the provided items, preserving rowids. */
1677 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain
));
1678 CFDictionaryApplyFunction(keychain
, SecServerImportClass
, &state
);
1681 state
.src_keybag
= KEYBAG_NONE
;
1682 /* Import the items we preserved with random rowids. */
1683 state
.filter
= kSecSysBoundItemFilter
;
1684 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound
));
1685 CFDictionaryApplyFunction(sys_bound
, SecServerImportClass
, &state
);
1689 CFReleaseSafe(*error
);
1690 *error
= state
.error
;
1692 CFRelease(state
.error
);
1698 CFReleaseSafe(sys_bound
);
1699 CFReleaseSafe(keybaguuid
);
1705 SecServerBackupGetKeybagUUID(CFDictionaryRef keychain
)
1707 CFStringRef uuid
= CFDictionaryGetValue(keychain
, kSecBackupKeybagUUIDKey
);
1708 if (!isString(uuid
))
1713 #pragma mark - key rolling support
1716 struct check_generation_ctx
{
1717 struct s3dl_query_ctx query_ctx
;
1718 uint32_t current_generation
;
1721 static void check_generation(sqlite3_stmt
*stmt
, void *context
) {
1722 struct check_generation_ctx
*c
= context
;
1723 CFDataRef blob
= NULL
;
1725 const uint8_t *cursor
= NULL
;
1727 keyclass_t keyclass
;
1728 uint32_t current_generation
= c
->current_generation
;
1730 require(blob
= s3dl_copy_data_from_col(stmt
, 1, &c
->query_ctx
.q
->q_error
), out
);
1731 blobLen
= CFDataGetLength(blob
);
1732 cursor
= CFDataGetBytePtr(blob
);
1734 /* Check for underflow, ensuring we have at least one full AES block left. */
1735 if (blobLen
< sizeof(version
) + sizeof(keyclass
)) {
1736 SecError(errSecDecode
, &c
->query_ctx
.q
->q_error
, CFSTR("check_generation: Check for underflow"));
1740 version
= *((uint32_t *)cursor
);
1741 cursor
+= sizeof(version
);
1743 (void) version
; // TODO: do something with the version number.
1745 keyclass
= *((keyclass_t
*)cursor
);
1747 // TODO: export get_key_gen macro
1748 if (((keyclass
& ~key_class_last
) == 0) != (current_generation
== 0)) {
1749 c
->query_ctx
.found
++;
1752 CFReleaseSafe(blob
);
1756 c
->query_ctx
.found
++;
1757 CFReleaseSafe(blob
);
1760 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt
, uint32_t current_generation
, CFErrorRef
*error
) {
1761 CFErrorRef localError
= NULL
;
1762 struct check_generation_ctx ctx
= { .query_ctx
= { .dbt
= dbt
}, .current_generation
= current_generation
};
1764 const SecDbClass
*classes
[] = {
1771 for (size_t class_ix
= 0; class_ix
< array_size(classes
); ++class_ix
) {
1772 Query
*q
= query_create(classes
[class_ix
], NULL
, NULL
, &localError
);
1776 ctx
.query_ctx
.q
= q
;
1777 q
->q_limit
= kSecMatchUnlimited
;
1779 bool ok
= s3dl_query(check_generation
, &ctx
, &localError
);
1780 query_destroy(q
, NULL
);
1781 CFReleaseNull(ctx
.query_ctx
.result
);
1783 if (!ok
&& localError
&& (CFErrorGetCode(localError
) == errSecItemNotFound
)) {
1784 CFReleaseNull(localError
);
1787 secerror("Class %@ not up to date", classes
[class_ix
]->name
);
1793 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt
, SecurityClient
*client
, CFErrorRef
*error
) {
1794 return SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1795 __block
bool ok
= false;
1796 uint32_t keystore_generation_status
;
1798 /* can we migrate to new class keys right now? */
1799 if (!aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
) &&
1800 (keystore_generation_status
& generation_change_in_progress
)) {
1802 /* take a lock assertion */
1803 bool operated_while_unlocked
= SecAKSDoWhileUserBagLocked(error
, ^{
1804 CFErrorRef localError
= NULL
;
1805 CFDictionaryRef backup
= SecServerCopyKeychainPlist(dbt
, NULL
,
1806 KEYBAG_DEVICE
, KEYBAG_NONE
, kSecNoItemFilter
, &localError
);
1809 secerror("Ignoring export error: %@ during roll export", localError
);
1810 CFReleaseNull(localError
);
1812 ok
= SecServerImportKeychainInPlist(dbt
, client
, KEYBAG_NONE
,
1813 KEYBAG_DEVICE
, backup
, kSecNoItemFilter
, &localError
);
1815 secerror("Ignoring export error: %@ during roll export", localError
);
1816 CFReleaseNull(localError
);
1821 if (!operated_while_unlocked
)
1824 ok
= SecError(errSecBadReq
, error
, CFSTR("No key roll in progress."));