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>
32 #include <securityd/SecDbKeychainItem.h>
33 #include <securityd/SecItemSchema.h>
34 #include <securityd/SecItemServer.h>
35 #include <Security/SecAccessControlPriv.h>
36 #include <Security/SecBasePriv.h>
37 #include <Security/SecItem.h>
38 #include <Security/SecItemPriv.h>
39 #include <Security/SecItemInternal.h>
40 #include <securityd/SOSCloudCircleServer.h>
41 #include <utilities/array_size.h>
42 #include <utilities/SecIOFormat.h>
43 #include <SecAccessControlPriv.h>
45 /* label when certificate data is joined with key data */
46 #define CERTIFICATE_DATA_COLUMN_LABEL "certdata"
48 const SecDbAttr
*SecDbAttrWithKey(const SecDbClass
*c
,
51 /* Special case: identites can have all attributes of either cert
53 if (c
== &identity_class
) {
54 const SecDbAttr
*desc
;
55 if (!(desc
= SecDbAttrWithKey(&cert_class
, key
, 0)))
56 desc
= SecDbAttrWithKey(&keys_class
, key
, error
);
61 SecDbForEachAttr(c
, a
) {
62 if (CFEqual(a
->name
, key
))
65 if (CFEqual(kSecAttrNoLegacy
, key
)) {
66 return NULL
; /* results in no ops for this attribute */
70 SecError(errSecNoSuchAttr
, error
, CFSTR("attribute %@ not found in class %@"), key
, c
->name
);
75 bool kc_transaction(SecDbConnectionRef dbt
, CFErrorRef
*error
, bool(^perform
)()) {
76 __block
bool ok
= true;
77 return ok
&& SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
78 ok
= *commit
= perform();
82 static CFStringRef
SecDbGetKindSQL(SecDbAttrKind kind
) {
88 case kSecDbPrimaryKeyAttr
:
89 case kSecDbEncryptedDataAttr
:
91 case kSecDbAccessAttr
:
92 case kSecDbStringAttr
:
94 case kSecDbNumberAttr
:
97 return CFSTR("INTEGER");
99 case kSecDbCreationDateAttr
:
100 case kSecDbModificationDateAttr
:
101 return CFSTR("REAL");
102 case kSecDbRowIdAttr
:
103 return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
104 case kSecDbAccessControlAttr
:
105 case kSecDbUTombAttr
:
106 /* This attribute does not exist in the DB. */
111 static void SecDbAppendUnique(CFMutableStringRef sql
, CFStringRef value
, bool *haveUnique
) {
114 CFStringAppend(sql
, CFSTR("UNIQUE("));
116 SecDbAppendElement(sql
, value
, haveUnique
);
119 static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
120 CFStringAppendFormat(sql
, 0, CFSTR("CREATE TABLE %@("), c
->name
);
121 SecDbForEachAttrWithMask(c
,desc
,kSecDbInFlag
) {
122 CFStringAppendFormat(sql
, 0, CFSTR("%@ %@"), desc
->name
, SecDbGetKindSQL(desc
->kind
));
123 if (desc
->flags
& kSecDbNotNullFlag
)
124 CFStringAppend(sql
, CFSTR(" NOT NULL"));
125 if (desc
->flags
& kSecDbDefault0Flag
)
126 CFStringAppend(sql
, CFSTR(" DEFAULT 0"));
127 if (desc
->flags
& kSecDbDefaultEmptyFlag
)
128 CFStringAppend(sql
, CFSTR(" DEFAULT ''"));
129 CFStringAppend(sql
, CFSTR(","));
132 bool haveUnique
= false;
133 SecDbForEachAttrWithMask(c
,desc
,kSecDbPrimaryKeyFlag
| kSecDbInFlag
) {
134 SecDbAppendUnique(sql
, desc
->name
, &haveUnique
);
137 CFStringAppend(sql
, CFSTR(")"));
139 CFStringAppend(sql
, CFSTR(");"));
142 SecDbForEachAttrWithMask(c
,desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
143 CFStringAppendFormat(sql
, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c
->name
, desc
->name
, c
->name
, desc
->name
);
147 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
148 CFStringAppendFormat(sql
, 0, CFSTR("DROP TABLE %@;"), c
->name
);
151 static CFDataRef
SecPersistentRefCreateWithItem(SecDbItemRef item
, CFErrorRef
*error
) {
152 sqlite3_int64 row_id
= SecDbItemGetRowId(item
, error
);
154 return _SecItemMakePersistentRef(SecDbItemGetClass(item
)->name
, row_id
);
158 bool SecItemDbCreateSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
160 __block
bool ok
= true;
161 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
162 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
163 SecDbAppendCreateTableWithClass(sql
, *pclass
);
165 // TODO: Use tversion_class to do this.
166 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT INTO tversion(version) VALUES(%d);"), schema
->version
);
167 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
168 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
169 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
175 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
177 __block
bool ok
= true;
178 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
179 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
180 SecDbAppendDropTableWithClass(sql
, *pclass
);
182 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
183 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
184 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
190 CFTypeRef
SecDbItemCopyResult(SecDbItemRef item
, ReturnTypeMask return_type
, CFErrorRef
*error
) {
193 if (return_type
== 0) {
194 /* Caller isn't interested in any results at all. */
196 } else if (return_type
== kSecReturnDataMask
) {
197 a_result
= SecDbItemGetCachedValueWithName(item
, kSecValueData
);
199 CFRetainSafe(a_result
);
201 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
203 } else if (return_type
== kSecReturnPersistentRefMask
) {
204 a_result
= SecPersistentRefCreateWithItem(item
, error
);
206 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item
));
207 /* We need to return more than one value. */
208 if (return_type
& kSecReturnRefMask
) {
209 CFDictionarySetValue(dict
, kSecClass
, SecDbItemGetClass(item
)->name
);
211 CFOptionFlags mask
= (((return_type
& kSecReturnDataMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnDataFlag
: 0) |
212 ((return_type
& kSecReturnAttributesMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnAttrFlag
: 0));
213 SecDbForEachAttr(SecDbItemGetClass(item
), desc
) {
214 if ((desc
->flags
& mask
) != 0) {
215 CFTypeRef value
= SecDbItemGetValue(item
, desc
, error
);
216 if (value
&& !CFEqual(kCFNull
, value
)) {
217 CFDictionarySetValue(dict
, desc
->name
, value
);
218 } else if (value
== NULL
) {
224 if (return_type
& kSecReturnPersistentRefMask
) {
225 CFDataRef pref
= SecPersistentRefCreateWithItem(item
, error
);
226 CFDictionarySetValue(dict
, kSecValuePersistentRef
, pref
);
236 /* AUDIT[securityd](done):
237 attributes (ok) is a caller provided dictionary, only its cf type has
241 s3dl_query_add(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
, CFErrorRef
*error
)
243 if (query_match_count(q
) != 0)
244 return errSecItemMatchUnsupported
;
246 /* Add requires a class to be specified unless we are adding a ref. */
247 if (q
->q_use_item_list
)
248 return errSecUseItemListUnsupported
;
250 /* Actual work here. */
251 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, q
->q_class
, q
->q_item
, KEYBAG_DEVICE
, error
);
254 if (SecDbItemIsTombstone(item
))
255 SecDbItemSetValue(item
, &v7utomb
, q
->q_use_tomb
? q
->q_use_tomb
: kCFBooleanTrue
, NULL
);
259 ok
= SecDbItemSetValueWithName(item
, CFSTR("v_Data"), q
->q_data
, error
);
261 ok
= SecDbItemSetRowId(item
, q
->q_row_id
, error
);
263 ok
= SecDbItemSetValueWithName(item
, CFSTR("musr"), q
->q_musrView
, error
);
264 SecDbItemSetCredHandle(item
, q
->q_use_cred_handle
);
267 ok
= SecDbItemInsert(item
, dbt
, error
);
270 if (result
&& q
->q_return_type
) {
271 *result
= SecDbItemCopyResult(item
, q
->q_return_type
, error
);
274 if (!ok
&& error
&& *error
) {
275 if (CFEqual(CFErrorGetDomain(*error
), kSecDbErrorDomain
) && CFErrorGetCode(*error
) == SQLITE_CONSTRAINT
) {
276 CFReleaseNull(*error
);
277 SecError(errSecDuplicateItem
, error
, CFSTR("duplicate item %@"), item
);
278 } else if (CFEqual(CFErrorGetDomain(*error
), kSecErrorDomain
) && CFErrorGetCode(*error
) == errSecDecode
) { //handle situation when item have pdmn=akpu but passcode is not set
279 CFTypeRef value
= SecDbItemGetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbAccessAttr
, error
), error
);
280 if (value
&& CFEqual(value
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
)) {
281 CFReleaseNull(*error
);
282 SecError(errSecAuthFailed
, error
, CFSTR("authentication failed"));
289 if (SecDbItemIsSyncable(item
))
290 q
->q_sync_changed
= true;
293 secdebug("dbitem", "inserting item %@%s%@", item
, ok
? "" : "failed: ", ok
|| error
== NULL
? (CFErrorRef
)CFSTR("") : *error
);
300 typedef void (*s3dl_handle_row
)(sqlite3_stmt
*stmt
, void *context
);
303 s3dl_copy_data_from_col(sqlite3_stmt
*stmt
, int col
, CFErrorRef
*error
) {
304 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt
, col
),
305 sqlite3_column_bytes(stmt
, col
),
310 s3dl_item_from_col(sqlite3_stmt
*stmt
, Query
*q
, int col
, CFArrayRef accessGroups
,
311 CFMutableDictionaryRef
*item
, SecAccessControlRef
*access_control
, CFErrorRef
*error
) {
312 CFDataRef edata
= NULL
;
314 require(edata
= s3dl_copy_data_from_col(stmt
, col
, error
), out
);
315 ok
= s3dl_item_from_data(edata
, q
, accessGroups
, item
, access_control
, error
);
318 CFReleaseSafe(edata
);
322 struct s3dl_query_ctx
{
324 CFArrayRef accessGroups
;
325 SecDbConnectionRef dbt
;
330 /* Return whatever the caller requested based on the value of q->q_return_type.
331 keys and values must be 3 larger than attr_count in size to accomadate the
332 optional data, class and persistent ref results. This is so we can use
333 the CFDictionaryCreate() api here rather than appending to a
334 mutable dictionary. */
335 static CF_RETURNS_RETAINED CFTypeRef
handle_result(Query
*q
, CFMutableDictionaryRef item
,
336 sqlite_int64 rowid
) {
339 data
= CFDictionaryGetValue(item
, kSecValueData
);
340 if (q
->q_return_type
== 0) {
341 /* Caller isn't interested in any results at all. */
343 } else if (q
->q_return_type
== kSecReturnDataMask
) {
348 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
350 } else if (q
->q_return_type
== kSecReturnPersistentRefMask
) {
351 a_result
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
353 /* We need to return more than one value. */
354 if (q
->q_return_type
& kSecReturnRefMask
) {
355 CFDictionarySetValue(item
, kSecClass
, q
->q_class
->name
);
356 } else if ((q
->q_return_type
& kSecReturnAttributesMask
)) {
357 if (!(q
->q_return_type
& kSecReturnDataMask
)) {
358 CFDictionaryRemoveValue(item
, kSecValueData
);
362 CFDictionaryRemoveAllValues(item
);
363 if ((q
->q_return_type
& kSecReturnDataMask
) && data
) {
364 CFDictionarySetValue(item
, kSecValueData
, data
);
368 if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
369 CFDataRef pref
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
370 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
381 static void s3dl_merge_into_dict(const void *key
, const void *value
, void *context
) {
382 CFDictionarySetValue(context
, key
, value
);
385 static void s3dl_query_row(sqlite3_stmt
*stmt
, void *context
) {
386 struct s3dl_query_ctx
*c
= context
;
389 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
390 CFMutableDictionaryRef item
;
391 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->accessGroups
, &item
, NULL
, &q
->q_error
);
393 OSStatus status
= SecErrorGetOSStatus(q
->q_error
);
394 // errSecDecode means the item is corrupted, stash it for delete.
395 if (status
== errSecDecode
) {
396 secwarning("ignoring corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, rowid
, q
->q_error
);
398 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
399 CFMutableStringRef edatastring
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
401 CFStringAppendEncryptedData(edatastring
, edata
);
402 secnotice("item", "corrupted edata=%@", edatastring
);
404 CFReleaseSafe(edata
);
405 CFReleaseSafe(edatastring
);
407 CFReleaseNull(q
->q_error
);
408 } else if (status
== errSecAuthNeeded
) {
409 secwarning("Authentication is needed for %@,rowid=%" PRId64
" (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
411 secerror("decode %@,rowid=%" PRId64
" failed (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
413 // q->q_error will be released appropriately by a call to query_error
420 if (q
->q_class
== &identity_class
) {
421 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
423 CFMutableDictionaryRef key
;
424 /* TODO : if there is a errSecDecode error here, we should cleanup */
425 if (!s3dl_item_from_col(stmt
, q
, 3, c
->accessGroups
, &key
, NULL
, &q
->q_error
) || !key
)
428 CFDataRef certData
= CFDictionaryGetValue(item
, kSecValueData
);
430 CFDictionarySetValue(key
, CFSTR(CERTIFICATE_DATA_COLUMN_LABEL
),
432 CFDictionaryRemoveValue(item
, kSecValueData
);
434 CFDictionaryApplyFunction(item
, s3dl_merge_into_dict
, key
);
439 if (!match_item(c
->dbt
, q
, c
->accessGroups
, item
))
442 CFTypeRef a_result
= handle_result(q
, item
, rowid
);
444 if (a_result
== kCFNull
) {
445 /* Caller wasn't interested in a result, but we still
446 count this row as found. */
447 CFRelease(a_result
); // Help shut up clang
448 } else if (q
->q_limit
== 1) {
449 c
->result
= a_result
;
451 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, a_result
);
462 SecDbAppendWhereROWID(CFMutableStringRef sql
,
463 CFStringRef col
, sqlite_int64 row_id
,
466 SecDbAppendWhereOrAnd(sql
, needWhere
);
467 CFStringAppendFormat(sql
, NULL
, CFSTR("%@=%lld"), col
, row_id
);
472 SecDbAppendWhereAttrs(CFMutableStringRef sql
, const Query
*q
, bool *needWhere
) {
473 CFIndex ix
, attr_count
= query_attr_count(q
);
474 for (ix
= 0; ix
< attr_count
; ++ix
) {
475 SecDbAppendWhereOrAndEquals(sql
, query_attr_at(q
, ix
).key
, needWhere
);
480 SecDbAppendWhereAccessGroups(CFMutableStringRef sql
,
482 CFArrayRef accessGroups
,
484 CFIndex ix
, ag_count
;
485 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
489 SecDbAppendWhereOrAnd(sql
, needWhere
);
490 CFStringAppend(sql
, col
);
491 CFStringAppend(sql
, CFSTR(" IN (?"));
492 for (ix
= 1; ix
< ag_count
; ++ix
) {
493 CFStringAppend(sql
, CFSTR(",?"));
495 CFStringAppend(sql
, CFSTR(")"));
499 isQueryOverAllMUSRViews(CFTypeRef musrView
)
501 return SecMUSRIsViewAllViews(musrView
);
505 isQueryOverSingleUserView(CFTypeRef musrView
)
507 return isNull(musrView
);
512 isQueryOverBothUserAndSystem(CFTypeRef musrView
, uid_t
*uid
)
514 return SecMUSRGetBothUserAndSystemUUID(musrView
, uid
);
519 SecDbAppendWhereMusr(CFMutableStringRef sql
,
523 SecDbAppendWhereOrAnd(sql
, needWhere
);
526 if (isQueryOverBothUserAndSystem(q
->q_musrView
, NULL
)) {
527 CFStringAppend(sql
, CFSTR("(musr = ? OR musr = ?)"));
530 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
531 /* query over all items, regardless of view */
532 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
533 CFStringAppend(sql
, CFSTR("musr = ?"));
535 CFStringAppend(sql
, CFSTR("musr = ?"));
539 static void SecDbAppendWhereClause(CFMutableStringRef sql
, const Query
*q
,
540 CFArrayRef accessGroups
) {
541 bool needWhere
= true;
542 SecDbAppendWhereROWID(sql
, CFSTR("ROWID"), q
->q_row_id
, &needWhere
);
543 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
544 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
545 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
548 static void SecDbAppendLimit(CFMutableStringRef sql
, CFIndex limit
) {
549 if (limit
!= kSecMatchUnlimited
)
550 CFStringAppendFormat(sql
, NULL
, CFSTR(" LIMIT %" PRIdCFIndex
), limit
);
553 static CFStringRef
s3dl_select_sql(Query
*q
, CFArrayRef accessGroups
) {
554 CFMutableStringRef sql
= CFStringCreateMutable(NULL
, 0);
555 if (q
->q_class
== &identity_class
) {
556 CFStringAppendFormat(sql
, NULL
, CFSTR("SELECT crowid, "
557 CERTIFICATE_DATA_COLUMN_LABEL
", rowid,data FROM "
558 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
559 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
560 " keys.*,cert.data AS " CERTIFICATE_DATA_COLUMN_LABEL
562 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"));
563 SecDbAppendWhereAccessGroups(sql
, CFSTR("cert.agrp"), accessGroups
, 0);
564 /* The next 3 SecDbAppendWhere calls are in the same order as in
565 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
566 as long as we do an extra sqlBindAccessGroups first. */
567 SecDbAppendWhereROWID(sql
, CFSTR("crowid"), q
->q_row_id
, 0);
568 CFStringAppend(sql
, CFSTR(")"));
569 bool needWhere
= true;
570 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
571 SecDbAppendWhereMusr(sql
, q
, &needWhere
);
572 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
574 CFStringAppend(sql
, CFSTR("SELECT rowid, data FROM "));
575 CFStringAppend(sql
, q
->q_class
->name
);
576 SecDbAppendWhereClause(sql
, q
, accessGroups
);
578 SecDbAppendLimit(sql
, q
->q_limit
);
583 static bool sqlBindMusr(sqlite3_stmt
*stmt
, const Query
*q
, int *pParam
, CFErrorRef
*error
) {
589 if (isQueryOverBothUserAndSystem(q
->q_musrView
, &uid
)) {
590 /* network extensions are special and get to query both user and system views */
591 CFDataRef systemUUID
= SecMUSRGetSystemKeychainUUID();
592 result
= SecDbBindObject(stmt
, param
++, systemUUID
, error
);
594 CFDataRef activeUser
= SecMUSRCreateActiveUserUUID(uid
);
595 result
= SecDbBindObject(stmt
, param
++, activeUser
, error
);
596 CFReleaseNull(activeUser
);
600 if (isQueryOverAllMUSRViews(q
->q_musrView
)) {
601 /* query over all items, regardless of view */
602 } else if (isQueryOverSingleUserView(q
->q_musrView
)) {
603 CFDataRef singleUUID
= SecMUSRGetSingleUserKeychainUUID();
604 result
= SecDbBindObject(stmt
, param
++, singleUUID
, error
);
606 result
= SecDbBindObject(stmt
, param
++, q
->q_musrView
, error
);
614 static bool sqlBindAccessGroups(sqlite3_stmt
*stmt
, CFArrayRef accessGroups
,
615 int *pParam
, CFErrorRef
*error
) {
618 CFIndex ix
, count
= accessGroups
? CFArrayGetCount(accessGroups
) : 0;
619 for (ix
= 0; ix
< count
; ++ix
) {
620 result
= SecDbBindObject(stmt
, param
++,
621 CFArrayGetValueAtIndex(accessGroups
, ix
),
630 static bool sqlBindWhereClause(sqlite3_stmt
*stmt
, const Query
*q
,
631 CFArrayRef accessGroups
, int *pParam
, CFErrorRef
*error
) {
634 CFIndex ix
, attr_count
= query_attr_count(q
);
635 for (ix
= 0; ix
< attr_count
; ++ix
) {
636 result
= SecDbBindObject(stmt
, param
++, query_attr_at(q
, ix
).value
, error
);
642 result
= sqlBindMusr(stmt
, q
, ¶m
, error
);
645 /* Bind the access group to the sql. */
647 result
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
654 bool SecDbItemQuery(SecDbQueryRef query
, CFArrayRef accessGroups
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
655 void (^handle_row
)(SecDbItemRef item
, bool *stop
)) {
656 __block
bool ok
= true;
657 /* Sanity check the query. */
659 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
661 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
662 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
664 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
667 CFStringRef sql
= s3dl_select_sql(query
, accessGroups
);
670 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
671 /* Bind the values being searched for to the SELECT statement. */
673 if (query
->q_class
== &identity_class
) {
674 /* Bind the access groups to cert.agrp. */
675 ok
&= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
678 ok
&= sqlBindWhereClause(stmt
, query
, accessGroups
, ¶m
, error
);
680 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
) {
681 SecDbItemRef itemFromStatement
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, query
->q_class
, stmt
, query
->q_keybag
, error
, return_attr
);
682 if (itemFromStatement
) {
683 CFTransferRetained(itemFromStatement
->credHandle
, query
->q_use_cred_handle
);
684 if (match_item(dbconn
, query
, accessGroups
, itemFromStatement
->attributes
))
685 handle_row(itemFromStatement
, stop
);
686 CFReleaseNull(itemFromStatement
);
688 secerror("failed to create item from stmt: %@", error
? *error
: (CFErrorRef
)"no error");
690 CFReleaseNull(*error
);
705 s3dl_query(s3dl_handle_row handle_row
,
706 void *context
, CFErrorRef
*error
)
708 struct s3dl_query_ctx
*c
= context
;
709 SecDbConnectionRef dbt
= c
->dbt
;
711 CFArrayRef accessGroups
= c
->accessGroups
;
713 /* Sanity check the query. */
715 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
717 /* Actual work here. */
718 if (q
->q_limit
== 1) {
721 c
->result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
723 CFStringRef sql
= s3dl_select_sql(q
, accessGroups
);
724 bool ok
= SecDbWithSQL(dbt
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
726 /* Bind the values being searched for to the SELECT statement. */
728 if (q
->q_class
== &identity_class
) {
729 /* Bind the access groups to cert.agrp. */
730 sql_ok
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
733 sql_ok
= sqlBindWhereClause(stmt
, q
, accessGroups
, ¶m
, error
);
735 SecDbForEach(stmt
, error
, ^bool (int row_index
) {
736 handle_row(stmt
, context
);
738 bool needs_auth
= q
->q_error
&& CFErrorGetCode(q
->q_error
) == errSecAuthNeeded
;
739 if (q
->q_skip_acl_items
&& needs_auth
)
740 // Skip items needing authentication if we are told to do so.
741 CFReleaseNull(q
->q_error
);
743 bool stop
= q
->q_limit
!= kSecMatchUnlimited
&& c
->found
>= q
->q_limit
;
744 stop
= stop
|| (q
->q_error
&& !needs_auth
);
753 // First get the error from the query, since errSecDuplicateItem from an
754 // update query should superceed the errSecItemNotFound below.
755 if (!query_error(q
, error
))
757 if (ok
&& c
->found
== 0)
758 ok
= SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
764 s3dl_copy_matching(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
,
765 CFArrayRef accessGroups
, CFErrorRef
*error
)
767 struct s3dl_query_ctx ctx
= {
768 .q
= q
, .accessGroups
= accessGroups
, .dbt
= dbt
,
770 if (q
->q_row_id
&& query_attr_count(q
))
771 return SecError(errSecItemIllegalQuery
, error
,
772 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
774 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
775 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
776 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
777 bool ok
= s3dl_query(s3dl_query_row
, &ctx
, error
);
779 *result
= ctx
.result
;
781 CFReleaseSafe(ctx
.result
);
786 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
787 static void query_set_attribute_with_desc(const SecDbAttr
*desc
, const void *value
, Query
*q
) {
788 if (CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
790 for (ix
= 0; ix
< q
->q_attr_end
; ++ix
) {
791 if (CFEqual(desc
->name
, q
->q_pairs
[ix
].key
)) {
792 CFReleaseSafe(q
->q_pairs
[ix
].value
);
794 for (; ix
< q
->q_attr_end
; ++ix
) {
795 q
->q_pairs
[ix
] = q
->q_pairs
[ix
+ 1];
797 CFDictionaryRemoveValue(q
->q_item
, desc
->name
);
802 query_add_attribute_with_desc(desc
, value
, q
);
805 /* Update modification_date if needed. */
806 static void query_pre_update(Query
*q
) {
807 SecDbForEachAttr(q
->q_class
, desc
) {
808 if (desc
->kind
== kSecDbModificationDateAttr
) {
809 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
810 query_set_attribute_with_desc(desc
, now
, q
);
816 /* Make sure all attributes that are marked as not_null have a value. If
817 force_date is false, only set mdat and cdat if they aren't already set. */
818 void query_pre_add(Query
*q
, bool force_date
) {
819 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
820 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInFlag
) {
821 if (desc
->kind
== kSecDbCreationDateAttr
||
822 desc
->kind
== kSecDbModificationDateAttr
) {
824 query_set_attribute_with_desc(desc
, now
, q
);
825 } else if (!CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
826 query_add_attribute_with_desc(desc
, now
, q
);
828 } else if ((desc
->flags
& kSecDbNotNullFlag
) &&
829 !CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
830 CFTypeRef value
= NULL
;
831 if (desc
->flags
& kSecDbDefault0Flag
) {
832 if (desc
->kind
== kSecDbDateAttr
)
833 value
= CFDateCreate(kCFAllocatorDefault
, 0.0);
836 value
= CFNumberCreate(0, kCFNumberSInt32Type
, &vzero
);
838 } else if (desc
->flags
& kSecDbDefaultEmptyFlag
) {
839 if (desc
->kind
== kSecDbDataAttr
|| desc
->kind
== kSecDbUUIDAttr
)
840 value
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
847 /* Safe to use query_add_attribute here since the attr wasn't
849 query_add_attribute_with_desc(desc
, value
, q
);
857 // Return a tri state value false->never make a tombstone, true->always make a
858 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
859 static CFBooleanRef
s3dl_should_make_tombstone(Query
*q
, bool item_is_syncable
, SecDbItemRef item
) {
861 return q
->q_use_tomb
;
862 else if (item_is_syncable
&& !SecDbItemIsTombstone(item
))
865 return kCFBooleanFalse
;
867 /* AUDIT[securityd](done):
868 attributesToUpdate (ok) is a caller provided dictionary,
869 only its cf types have been checked.
872 s3dl_query_update(SecDbConnectionRef dbt
, Query
*q
,
873 CFDictionaryRef attributesToUpdate
, CFArrayRef accessGroups
, CFErrorRef
*error
)
875 /* Sanity check the query. */
876 if (query_match_count(q
) != 0)
877 return SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported in attributes to update"));
879 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported in attributes to update"));
880 if (q
->q_row_id
&& query_attr_count(q
))
881 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
883 __block
bool result
= true;
884 Query
*u
= query_create(q
->q_class
, NULL
, attributesToUpdate
, error
);
885 if (u
== NULL
) return false;
886 require_action_quiet(query_update_parse(u
, attributesToUpdate
, error
), errOut
, result
= false);
888 result
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
889 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
890 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
891 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
892 result
&= SecDbItemQuery(q
, accessGroups
, dbt
, error
, ^(SecDbItemRef item
, bool *stop
) {
893 // We always need to know the error here.
894 CFErrorRef localError
= NULL
;
895 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
896 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
897 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
898 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, u
->q_item
, &localError
);
899 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
900 CFReleaseSafe(storedSHA1
);
901 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
902 // We just ignore this, and treat as if item is not found.
903 secwarning("deleting corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
904 CFReleaseNull(localError
);
905 if (!SecDbItemDelete(item
, dbt
, false, &localError
)) {
906 secerror("failed to delete corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
907 CFReleaseNull(localError
);
911 if (new_item
!= NULL
&& u
->q_access_control
!= NULL
)
912 SecDbItemSetAccessControl(new_item
, u
->q_access_control
, &localError
);
913 result
= SecErrorPropagate(localError
, error
) && new_item
;
915 bool item_is_sync
= SecDbItemIsSyncable(item
);
916 result
= SecDbItemUpdate(item
, new_item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
919 if (item_is_sync
|| SecDbItemIsSyncable(new_item
))
920 q
->q_sync_changed
= true;
928 if (result
&& !q
->q_changed
)
929 result
= SecError(errSecItemNotFound
, error
, CFSTR("No items updated"));
931 if (!query_destroy(u
, error
))
936 static bool SecDbItemNeedAuth(SecDbItemRef item
, CFErrorRef
*error
)
938 CFErrorRef localError
= NULL
;
939 if (!SecDbItemEnsureDecrypted(item
, &localError
) && localError
&& CFErrorGetCode(localError
) == errSecAuthNeeded
) {
945 CFReleaseSafe(localError
);
950 s3dl_query_delete(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFErrorRef
*error
)
952 __block
bool ok
= true;
953 __block
bool needAuth
= false;
954 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
955 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
956 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
957 ok
&= SecDbItemSelect(q
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
959 },^bool(CFMutableStringRef sql
, bool *needWhere
) {
960 SecDbAppendWhereClause(sql
, q
, accessGroups
);
962 },^bool(sqlite3_stmt
* stmt
, int col
) {
963 return sqlBindWhereClause(stmt
, q
, accessGroups
, &col
, error
);
964 }, ^(SecDbItemRef item
, bool *stop
) {
965 // Check if item need to be authenticated by LocalAuthentication
966 item
->cryptoOp
= kAKSKeyOpDelete
;
967 if (SecDbItemNeedAuth(item
, error
)) {
971 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
972 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
973 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
974 bool item_is_sync
= SecDbItemIsSyncable(item
);
975 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
976 CFReleaseSafe(storedSHA1
);
977 ok
= SecDbItemDelete(item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
981 q
->q_sync_changed
= true;
984 if (ok
&& !q
->q_changed
&& !needAuth
) {
985 ok
= SecError(errSecItemNotFound
, error
, CFSTR("Delete failed to delete anything"));
987 return ok
&& !needAuth
;
991 matchAnyString(CFStringRef needle
, CFStringRef
*haystack
)
994 if (CFEqual(needle
, *haystack
))
1001 /* Return true iff the item in question should not be backed up, nor restored,
1002 but when restoring a backup the original version of the item should be
1003 added back to the keychain again after the restore completes. */
1004 static bool SecItemIsSystemBound(CFDictionaryRef item
, const SecDbClass
*cls
, bool multiUser
) {
1005 CFStringRef agrp
= CFDictionaryGetValue(item
, kSecAttrAccessGroup
);
1006 if (!isString(agrp
))
1009 if (CFEqualSafe(agrp
, kSOSInternalAccessGroup
)) {
1010 secdebug("backup", "found sysbound item: %@", item
);
1014 if (CFEqual(agrp
, CFSTR("lockdown-identities"))) {
1015 secdebug("backup", "found sys_bound item: %@", item
);
1019 if (CFEqual(agrp
, CFSTR("apple")) && cls
== &genp_class
) {
1020 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1021 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1023 if (isString(service
) && isString(account
)) {
1024 static CFStringRef mcAccounts
[] = {
1030 if (CFEqual(service
, CFSTR("com.apple.managedconfiguration"))
1031 && matchAnyString(account
, mcAccounts
))
1033 secdebug("backup", "found sys_bound item: %@", item
);
1039 if (multiUser
&& CFEqual(agrp
, CFSTR("com.apple.apsd")) && cls
== &genp_class
) {
1040 static CFStringRef pushServices
[] = {
1041 CFSTR("push.apple.com"),
1042 CFSTR("push.apple.com,PerAppToken.v0"),
1045 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1047 if (isString(service
) && matchAnyString(service
, pushServices
)) {
1048 secdebug("backup", "found sys_bound item: %@", item
);
1053 if (multiUser
&& CFEqual(agrp
, CFSTR("apple")) && cls
== &genp_class
) {
1054 static CFStringRef acountServices
[] = {
1055 CFSTR("com.apple.account.AppleAccount.token"),
1056 CFSTR("com.apple.account.AppleAccount.password"),
1057 CFSTR("com.apple.account.AppleAccount.rpassword"),
1058 CFSTR("com.apple.account.idms.token"),
1059 CFSTR("com.apple.account.idms.heartbeat-token"),
1060 CFSTR("com.apple.account.idms.continuation-key"),
1061 CFSTR("com.apple.account.CloudKit.token"),
1062 CFSTR("com.apple.account.IdentityServices.password"), /* accountsd for ids */
1063 CFSTR("com.apple.account.IdentityServices.rpassword"),
1064 CFSTR("com.apple.account.IdentityServices.token"),
1065 CFSTR("BackupIDSAccountToken"),
1066 CFSTR("com.apple.ids"),
1071 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1073 if (isString(service
) && matchAnyString(service
, acountServices
)) {
1074 secdebug("backup", "found exact sys_bound item: %@", item
);
1077 if (isString(service
) && CFEqual(service
, CFSTR("com.apple.facetime"))) {
1078 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
1079 if (isString(account
) && CFEqual(account
, CFSTR("registrationV1"))) {
1080 secdebug("backup", "found exact sys_bound item: %@", item
);
1086 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== &genp_class
) {
1087 static CFStringRef acountServices
[] = {
1091 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
1093 if (isString(service
) && matchAnyString(service
, acountServices
)) {
1094 secdebug("backup", "found exact sys_bound item: %@", item
);
1099 if (multiUser
&& CFEqual(agrp
, CFSTR("ichat")) && cls
== &keys_class
) {
1100 static CFStringRef exactMatchingLabel
[] = {
1101 CFSTR("iMessage Encryption Key"),
1102 CFSTR("iMessage Signing Key"),
1104 CFStringRef label
= CFDictionaryGetValue(item
, kSecAttrLabel
);
1105 if (isString(label
)) {
1106 if (matchAnyString(label
, exactMatchingLabel
)) {
1107 secdebug("backup", "found exact sys_bound item: %@", item
);
1114 secdebug("backup", "found non sys_bound item: %@", item
);
1118 /* Delete all items from the current keychain. If this is not an in
1119 place upgrade we don't delete items in the 'lockdown-identities'
1120 access group, this ensures that an import or restore of a backup
1121 will never overwrite an existing activation record. */
1122 static bool SecServerDeleteAll(SecDbConnectionRef dbt
, CFErrorRef
*error
) {
1123 secwarning("SecServerDeleteAll");
1125 return kc_transaction(dbt
, error
, ^{
1127 bool ok
= (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
1128 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
1129 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
1130 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
1135 #if TARGET_OS_IPHONE
1137 static bool DeleteAllFromTableForMUSRView(SecDbConnectionRef dbt
,
1143 sqlite3_stmt
*stmt
= NULL
;
1144 CFStringRef sql2
= NULL
;
1148 sql2
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@ AND pdmn NOT IN ('aku','akpu','cku','dku')"), sql
);
1150 sql2
= CFRetain(sql
);
1152 require(sql2
, fail
);
1154 stmt
= SecDbCopyStmt(dbt
, sql2
, NULL
, error
);
1155 require(stmt
, fail
);
1157 ok
= SecDbBindObject(stmt
, 1, musr
, error
);
1160 ok
= SecDbStep(dbt
, stmt
, error
, ^(bool *stop
) { });
1165 ok
= SecDbFinalize(stmt
, error
);
1168 secwarning("DeleteAllFromTableForMUSRView failed for %@ for musr: %@: %@", sql2
, musr
, error
? *error
: NULL
);
1170 CFReleaseNull(sql2
);
1175 bool SecServerDeleteAllForUser(SecDbConnectionRef dbt
, CFDataRef musrView
, bool keepU
, CFErrorRef
*error
) {
1176 secwarning("SecServerDeleteAllForUser for user: %@ keepU %s", musrView
, keepU
? "yes" : "no");
1178 return kc_transaction(dbt
, error
, ^{
1181 ok
= (DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM genp WHERE musr = ?"), musrView
, keepU
, error
) &&
1182 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM inet WHERE musr = ?"), musrView
, keepU
, error
) &&
1183 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM cert WHERE musr = ?"), musrView
, keepU
, error
) &&
1184 DeleteAllFromTableForMUSRView(dbt
, CFSTR("DELETE FROM keys WHERE musr = ?"), musrView
, keepU
, error
));
1192 struct s3dl_export_row_ctx
{
1193 struct s3dl_query_ctx qc
;
1194 keybag_handle_t dest_keybag
;
1195 enum SecItemFilter filter
;
1199 static void s3dl_export_row(sqlite3_stmt
*stmt
, void *context
) {
1200 struct s3dl_export_row_ctx
*c
= context
;
1202 SecAccessControlRef access_control
= NULL
;
1203 CFErrorRef localError
= NULL
;
1205 /* Skip akpu items when backing up, those are intentionally lost across restores. */
1206 bool skip_akpu
= c
->filter
== kSecBackupableItemFilter
;
1208 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
1209 CFMutableDictionaryRef item
;
1210 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->qc
.accessGroups
, &item
, &access_control
, &localError
);
1212 bool is_akpu
= access_control
? CFEqualSafe(SecAccessControlGetProtection(access_control
),
1213 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
) : false;
1215 if (ok
&& item
&& !(skip_akpu
&& is_akpu
)) {
1216 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
1217 bool do_sys_bound
= c
->filter
== kSecSysBoundItemFilter
;
1218 if (c
->filter
== kSecNoItemFilter
||
1219 SecItemIsSystemBound(item
, q
->q_class
, c
->multiUser
) == do_sys_bound
) {
1220 /* Re-encode the item. */
1221 secdebug("item", "export rowid %llu item: %@", rowid
, item
);
1222 /* The code below could be moved into handle_row. */
1223 CFDataRef pref
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
1225 if (c
->dest_keybag
!= KEYBAG_NONE
) {
1226 CFMutableDictionaryRef auth_attribs
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1227 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInAuthenticatedDataFlag
) {
1228 CFTypeRef value
= CFDictionaryGetValue(item
, desc
->name
);
1230 CFDictionaryAddValue(auth_attribs
, desc
->name
, value
);
1231 CFDictionaryRemoveValue(item
, desc
->name
);
1235 /* Encode and encrypt the item to the specified keybag. */
1236 CFDataRef edata
= NULL
;
1237 bool encrypted
= ks_encrypt_data(c
->dest_keybag
, access_control
, q
->q_use_cred_handle
, item
, auth_attribs
, &edata
, &q
->q_error
);
1238 CFDictionaryRemoveAllValues(item
);
1239 CFRelease(auth_attribs
);
1241 CFDictionarySetValue(item
, kSecValueData
, edata
);
1242 CFReleaseSafe(edata
);
1244 seccritical("ks_encrypt_data %@,rowid=%" PRId64
": failed: %@", q
->q_class
->name
, rowid
, q
->q_error
);
1245 CFReleaseNull(q
->q_error
);
1248 if (CFDictionaryGetCount(item
)) {
1249 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
1250 CFArrayAppendValue((CFMutableArrayRef
)c
->qc
.result
, item
);
1253 CFReleaseSafe(pref
);
1258 OSStatus status
= SecErrorGetOSStatus(localError
);
1260 if (status
== errSecInteractionNotAllowed
&& is_akpu
&& skip_akpu
) {
1261 // We expect akpu items to be inaccessible when the device is locked.
1262 CFReleaseNull(localError
);
1264 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1265 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1266 secinfo("item","Could not export item for rowid %llu: %@", rowid
, localError
);
1268 if(status
== errSecDecode
) {
1269 CFReleaseNull(localError
);
1271 CFReleaseSafe(q
->q_error
);
1272 q
->q_error
=localError
;
1276 CFReleaseSafe(access_control
);
1280 SecServerCopyKeychainPlist(SecDbConnectionRef dbt
,
1281 SecurityClient
*client
,
1282 keybag_handle_t src_keybag
,
1283 keybag_handle_t dest_keybag
,
1284 enum SecItemFilter filter
,
1285 CFErrorRef
*error
) {
1286 CFMutableDictionaryRef keychain
;
1287 keychain
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1288 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1290 if (error
&& !*error
)
1291 SecError(errSecAllocate
, error
, CFSTR("Can't create keychain dictionary"));
1295 bool inMultiUser
= false;
1296 Query q
= { .q_keybag
= src_keybag
};
1298 kSecReturnDataMask
|
1299 kSecReturnAttributesMask
|
1300 kSecReturnPersistentRefMask
;
1301 q
.q_limit
= kSecMatchUnlimited
;
1302 q
.q_skip_acl_items
= true;
1304 #if TARGET_OS_IPHONE
1305 if (client
&& client
->inMultiUser
) {
1306 q
.q_musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1311 q
.q_musrView
= SecMUSRGetSingleUserKeychainUUID();
1312 CFRetain(q
.q_musrView
);
1315 /* Get rid of this duplicate. */
1316 const SecDbClass
*SecDbClasses
[] = {
1323 for (class_ix
= 0; class_ix
< array_size(SecDbClasses
);
1325 q
.q_class
= SecDbClasses
[class_ix
];
1326 struct s3dl_export_row_ctx ctx
= {
1327 .qc
= { .q
= &q
, .dbt
= dbt
},
1328 .dest_keybag
= dest_keybag
, .filter
= filter
,
1329 .multiUser
= inMultiUser
,
1332 secnotice("item", "exporting class '%@'", q
.q_class
->name
);
1334 CFErrorRef localError
= NULL
;
1335 if (s3dl_query(s3dl_export_row
, &ctx
, &localError
)) {
1336 if (CFArrayGetCount(ctx
.qc
.result
))
1337 CFDictionaryAddValue(keychain
, q
.q_class
->name
, ctx
.qc
.result
);
1340 OSStatus status
= (OSStatus
)CFErrorGetCode(localError
);
1341 if (status
== errSecItemNotFound
) {
1342 CFRelease(localError
);
1344 secerror("Export failed: %@", localError
);
1346 CFReleaseSafe(*error
);
1347 *error
= localError
;
1349 CFRelease(localError
);
1351 CFReleaseNull(keychain
);
1352 CFReleaseNull(ctx
.qc
.result
);
1356 CFReleaseNull(ctx
.qc
.result
);
1360 CFReleaseNull(q
.q_musrView
);
1365 struct SecServerImportClassState
{
1366 SecDbConnectionRef dbt
;
1368 keybag_handle_t src_keybag
;
1369 keybag_handle_t dest_keybag
;
1370 SecurityClient
*client
;
1371 enum SecItemFilter filter
;
1374 struct SecServerImportItemState
{
1375 const SecDbClass
*class;
1376 struct SecServerImportClassState
*s
;
1380 SecServerImportItem(const void *value
, void *context
)
1382 struct SecServerImportItemState
*state
= (struct SecServerImportItemState
*)context
;
1383 bool inMultiUser
= false;
1384 #if TARGET_OS_IPHONE
1385 if (state
->s
->client
->inMultiUser
)
1389 if (state
->s
->error
)
1392 if (!isDictionary(value
)) {
1393 SecError(errSecParam
, &state
->s
->error
, CFSTR("value %@ is not a dictionary"), value
);
1397 CFDictionaryRef dict
= (CFDictionaryRef
)value
;
1399 secdebug("item", "Import Item : %@", dict
);
1401 /* We use the kSecSysBoundItemFilte to indicate that we don't
1402 * preserve rowid's during import.
1404 if (state
->s
->filter
== kSecBackupableItemFilter
) {
1407 /* We don't filter non sys_bound items during import since we know we
1408 * will never have any in this case.
1410 if (SecItemIsSystemBound(dict
, state
->class, inMultiUser
))
1414 * Don't bother with u items when in edu mode since our current backup system
1415 * don't keep track of items that blongs to the device (u) but rather just
1416 * merge them into one blob.
1418 if (inMultiUser
&& (pdmu
= CFDictionaryGetValue(dict
, kSecAttrAccessible
))) {
1419 if (CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1420 CFEqual(pdmu
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
) ||
1421 CFEqual(pdmu
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
) ||
1422 CFEqual(pdmu
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
))
1424 secdebug("item", "Skipping KU item : %@", dict
);
1432 /* This is sligthly confusing:
1433 - During upgrade all items are exported with KEYBAG_NONE.
1434 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1435 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1437 if (state
->s
->src_keybag
== KEYBAG_NONE
) {
1438 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, state
->class, dict
, state
->s
->dest_keybag
, &state
->s
->error
);
1440 item
= SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault
, state
->class, dict
, state
->s
->src_keybag
, state
->s
->dest_keybag
, &state
->s
->error
);
1448 CFDataRef musr
= NULL
;
1449 CFDataRef musrBackup
= CFDictionaryGetValue(dict
, kSecAttrMultiUser
);
1450 CFDataRef systemKeychainUUID
= SecMUSRGetSystemKeychainUUID();
1451 bool systemKeychain
= CFEqualSafe(musrBackup
, systemKeychainUUID
);
1453 #if TARGET_OS_IPHONE
1454 if (state
->s
->client
&& state
->s
->client
->inMultiUser
) {
1455 if (systemKeychain
) {
1456 secwarning("system keychain not allowed in multi user mode for item: %@", item
);
1458 musr
= SecMUSRCreateActiveUserUUID(state
->s
->client
->uid
);
1463 if (systemKeychain
) {
1464 musr
= SecMUSRCopySystemKeychainUUID();
1466 musr
= SecMUSRGetSingleUserKeychainUUID();
1471 CFReleaseNull(item
);
1473 SecDbItemSetValueWithName(item
, CFSTR("musr"), musr
, &state
->s
->error
);
1485 if(state
->s
->filter
!= kSecSysBoundItemFilter
) {
1486 SecDbItemExtractRowIdFromBackupDictionary(item
, dict
, &state
->s
->error
);
1488 SecDbItemInferSyncable(item
, &state
->s
->error
);
1489 insertStatus
= SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1490 if (!insertStatus
) {
1492 When running in EduMode, multiple users share the same
1493 keychain and unfortionaly the rowid is used a
1494 persistant reference and is part of the contraints (its
1495 UNIQUE), so lets clear the rowid and try to insert the
1498 This even happens for normal operation because of
1499 SysBound entries, so in case of a failure, lets try
1500 again to insert the record.
1502 SecDbItemClearRowId(item
, NULL
);
1503 SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1507 /* Reset error if we had one, since we just skip the current item
1508 and continue importing what we can. */
1509 if (state
->s
->error
) {
1510 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1511 item
, state
->class->name
, state
->s
->error
);
1512 CFReleaseNull(state
->s
->error
);
1515 CFReleaseSafe(item
);
1518 static void SecServerImportClass(const void *key
, const void *value
,
1520 struct SecServerImportClassState
*state
=
1521 (struct SecServerImportClassState
*)context
;
1524 if (!isString(key
)) {
1525 SecError(errSecParam
, &state
->error
, CFSTR("class name %@ is not a string"), key
);
1528 const SecDbClass
*class = kc_class_with_name(key
);
1530 secwarning("Ignoring unknown key class '%@'", key
);
1533 if (class == &identity_class
) {
1534 SecError(errSecParam
, &state
->error
, CFSTR("attempt to import an identity"));
1537 struct SecServerImportItemState item_state
= {
1538 .class = class, .s
= state
,
1540 if (isArray(value
)) {
1541 CFArrayRef items
= (CFArrayRef
)value
;
1542 secwarning("Import %ld items of class %@ (filter %d)", (long)CFArrayGetCount(items
), key
, state
->filter
);
1543 CFArrayApplyFunction(items
, CFRangeMake(0, CFArrayGetCount(items
)),
1544 SecServerImportItem
, &item_state
);
1545 } else if (isDictionary(value
)) {
1546 CFDictionaryRef item
= (CFDictionaryRef
)value
;
1547 secwarning("Import %ld items of class %@ (filter %d)", (long)CFDictionaryGetCount(item
), key
, state
->filter
);
1548 SecServerImportItem(item
, &item_state
);
1550 secwarning("Unknown value type for class %@ (filter %d)", key
, state
->filter
);
1554 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt
, SecurityClient
*client
,
1555 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
,
1556 CFDictionaryRef keychain
, enum SecItemFilter filter
, CFErrorRef
*error
) {
1559 CFDictionaryRef sys_bound
= NULL
;
1560 if (filter
== kSecBackupableItemFilter
) {
1561 /* Grab a copy of all the items for which SecItemIsSystemBound()
1563 require(sys_bound
= SecServerCopyKeychainPlist(dbt
, client
, KEYBAG_DEVICE
,
1564 KEYBAG_NONE
, kSecSysBoundItemFilter
,
1568 /* Delete everything in the keychain. */
1569 #if TARGET_OS_IPHONE
1570 if (client
->inMultiUser
) {
1571 CFDataRef musrView
= SecMUSRCreateActiveUserUUID(client
->uid
);
1572 require_action(musrView
, errOut
, ok
= false);
1573 require_action(ok
= SecServerDeleteAllForUser(dbt
, musrView
, true, error
), errOut
, CFReleaseNull(musrView
));
1574 CFReleaseNull(musrView
);
1578 require(ok
= SecServerDeleteAll(dbt
, error
), errOut
);
1581 struct SecServerImportClassState state
= {
1583 .src_keybag
= src_keybag
,
1584 .dest_keybag
= dest_keybag
,
1588 /* Import the provided items, preserving rowids. */
1589 secwarning("Restoring backup items '%ld'", (long)CFDictionaryGetCount(keychain
));
1590 CFDictionaryApplyFunction(keychain
, SecServerImportClass
, &state
);
1593 state
.src_keybag
= KEYBAG_NONE
;
1594 /* Import the items we preserved with random rowids. */
1595 state
.filter
= kSecSysBoundItemFilter
;
1596 secwarning("Restoring sysbound items '%ld'", (long)CFDictionaryGetCount(sys_bound
));
1597 CFDictionaryApplyFunction(sys_bound
, SecServerImportClass
, &state
);
1601 CFReleaseSafe(*error
);
1602 *error
= state
.error
;
1604 CFRelease(state
.error
);
1610 CFReleaseSafe(sys_bound
);
1615 #pragma mark - key rolling support
1618 struct check_generation_ctx
{
1619 struct s3dl_query_ctx query_ctx
;
1620 uint32_t current_generation
;
1623 static void check_generation(sqlite3_stmt
*stmt
, void *context
) {
1624 struct check_generation_ctx
*c
= context
;
1625 CFDataRef blob
= NULL
;
1627 const uint8_t *cursor
= NULL
;
1629 keyclass_t keyclass
;
1630 uint32_t current_generation
= c
->current_generation
;
1632 require(blob
= s3dl_copy_data_from_col(stmt
, 1, &c
->query_ctx
.q
->q_error
), out
);
1633 blobLen
= CFDataGetLength(blob
);
1634 cursor
= CFDataGetBytePtr(blob
);
1636 /* Check for underflow, ensuring we have at least one full AES block left. */
1637 if (blobLen
< sizeof(version
) + sizeof(keyclass
)) {
1638 SecError(errSecDecode
, &c
->query_ctx
.q
->q_error
, CFSTR("check_generation: Check for underflow"));
1642 version
= *((uint32_t *)cursor
);
1643 cursor
+= sizeof(version
);
1645 (void) version
; // TODO: do something with the version number.
1647 keyclass
= *((keyclass_t
*)cursor
);
1649 // TODO: export get_key_gen macro
1650 if (((keyclass
& ~key_class_last
) == 0) != (current_generation
== 0)) {
1651 c
->query_ctx
.found
++;
1654 CFReleaseSafe(blob
);
1658 c
->query_ctx
.found
++;
1659 CFReleaseSafe(blob
);
1662 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt
, uint32_t current_generation
, CFErrorRef
*error
) {
1663 CFErrorRef localError
= NULL
;
1664 struct check_generation_ctx ctx
= { .query_ctx
= { .dbt
= dbt
}, .current_generation
= current_generation
};
1666 const SecDbClass
*classes
[] = {
1673 for (size_t class_ix
= 0; class_ix
< array_size(classes
); ++class_ix
) {
1674 Query
*q
= query_create(classes
[class_ix
], NULL
, NULL
, &localError
);
1678 ctx
.query_ctx
.q
= q
;
1679 q
->q_limit
= kSecMatchUnlimited
;
1681 bool ok
= s3dl_query(check_generation
, &ctx
, &localError
);
1682 query_destroy(q
, NULL
);
1683 CFReleaseNull(ctx
.query_ctx
.result
);
1685 if (!ok
&& localError
&& (CFErrorGetCode(localError
) == errSecItemNotFound
)) {
1686 CFReleaseNull(localError
);
1689 secerror("Class %@ not up to date", classes
[class_ix
]->name
);
1695 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt
, SecurityClient
*client
, CFErrorRef
*error
) {
1696 return SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1697 __block
bool ok
= false;
1698 uint32_t keystore_generation_status
;
1700 /* can we migrate to new class keys right now? */
1701 if (!aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
) &&
1702 (keystore_generation_status
& generation_change_in_progress
)) {
1704 /* take a lock assertion */
1705 bool operated_while_unlocked
= SecAKSDoWhileUserBagLocked(error
, ^{
1706 CFErrorRef localError
= NULL
;
1707 CFDictionaryRef backup
= SecServerCopyKeychainPlist(dbt
, NULL
,
1708 KEYBAG_DEVICE
, KEYBAG_NONE
, kSecNoItemFilter
, &localError
);
1711 secerror("Ignoring export error: %@ during roll export", localError
);
1712 CFReleaseNull(localError
);
1714 ok
= SecServerImportKeychainInPlist(dbt
, client
, KEYBAG_NONE
,
1715 KEYBAG_DEVICE
, backup
, kSecNoItemFilter
, &localError
);
1717 secerror("Ignoring export error: %@ during roll export", localError
);
1718 CFReleaseNull(localError
);
1723 if (!operated_while_unlocked
)
1726 ok
= SecError(errSecBadReq
, error
, CFSTR("No key roll in progress."));