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
))
67 SecError(errSecNoSuchAttr
, error
, CFSTR("attribute %@ not found in class %@"), key
, c
->name
);
72 bool kc_transaction(SecDbConnectionRef dbt
, CFErrorRef
*error
, bool(^perform
)()) {
73 __block
bool ok
= true;
74 return ok
&& SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
75 ok
= *commit
= perform();
79 static CFStringRef
SecDbGetKindSQL(SecDbAttrKind kind
) {
84 case kSecDbPrimaryKeyAttr
:
85 case kSecDbEncryptedDataAttr
:
87 case kSecDbAccessAttr
:
88 case kSecDbStringAttr
:
90 case kSecDbNumberAttr
:
93 return CFSTR("INTEGER");
95 case kSecDbCreationDateAttr
:
96 case kSecDbModificationDateAttr
:
99 return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
100 case kSecDbAccessControlAttr
:
101 case kSecDbUTombAttr
:
102 /* This attribute does not exist in the DB. */
107 static void SecDbAppendUnique(CFMutableStringRef sql
, CFStringRef value
, bool *haveUnique
) {
110 CFStringAppend(sql
, CFSTR("UNIQUE("));
112 SecDbAppendElement(sql
, value
, haveUnique
);
115 static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
116 CFStringAppendFormat(sql
, 0, CFSTR("CREATE TABLE %@("), c
->name
);
117 SecDbForEachAttrWithMask(c
,desc
,kSecDbInFlag
) {
118 CFStringAppendFormat(sql
, 0, CFSTR("%@ %@"), desc
->name
, SecDbGetKindSQL(desc
->kind
));
119 if (desc
->flags
& kSecDbNotNullFlag
)
120 CFStringAppend(sql
, CFSTR(" NOT NULL"));
121 if (desc
->flags
& kSecDbDefault0Flag
)
122 CFStringAppend(sql
, CFSTR(" DEFAULT 0"));
123 if (desc
->flags
& kSecDbDefaultEmptyFlag
)
124 CFStringAppend(sql
, CFSTR(" DEFAULT ''"));
125 CFStringAppend(sql
, CFSTR(","));
128 bool haveUnique
= false;
129 SecDbForEachAttrWithMask(c
,desc
,kSecDbPrimaryKeyFlag
| kSecDbInFlag
) {
130 SecDbAppendUnique(sql
, desc
->name
, &haveUnique
);
133 CFStringAppend(sql
, CFSTR(")"));
135 CFStringAppend(sql
, CFSTR(");"));
138 SecDbForEachAttrWithMask(c
,desc
, kSecDbIndexFlag
| kSecDbInFlag
) {
139 CFStringAppendFormat(sql
, 0, CFSTR("CREATE INDEX %@%@ ON %@(%@);"), c
->name
, desc
->name
, c
->name
, desc
->name
);
143 static void SecDbAppendDropTableWithClass(CFMutableStringRef sql
, const SecDbClass
*c
) {
144 CFStringAppendFormat(sql
, 0, CFSTR("DROP TABLE %@;"), c
->name
);
147 static CFDataRef
SecPersistentRefCreateWithItem(SecDbItemRef item
, CFErrorRef
*error
) {
148 sqlite3_int64 row_id
= SecDbItemGetRowId(item
, error
);
150 return _SecItemMakePersistentRef(SecDbItemGetClass(item
)->name
, row_id
);
154 bool SecItemDbCreateSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
156 __block
bool ok
= true;
157 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
158 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
159 SecDbAppendCreateTableWithClass(sql
, *pclass
);
161 // TODO: Use tversion_class to do this.
162 CFStringAppendFormat(sql
, NULL
, CFSTR("INSERT INTO tversion(version) VALUES(%d);"), schema
->version
);
163 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
164 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
165 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
171 bool SecItemDbDeleteSchema(SecDbConnectionRef dbt
, const SecDbSchema
*schema
, CFErrorRef
*error
)
173 __block
bool ok
= true;
174 CFMutableStringRef sql
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
175 for (const SecDbClass
* const *pclass
= schema
->classes
; *pclass
; ++pclass
) {
176 SecDbAppendDropTableWithClass(sql
, *pclass
);
178 CFStringPerformWithCString(sql
, ^(const char *sql_string
) {
179 ok
= SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt
), sql_string
, NULL
, NULL
, NULL
),
180 SecDbHandle(dbt
), error
, CFSTR("sqlite3_exec: %s"), sql_string
);
186 CFTypeRef
SecDbItemCopyResult(SecDbItemRef item
, ReturnTypeMask return_type
, CFErrorRef
*error
) {
189 if (return_type
== 0) {
190 /* Caller isn't interested in any results at all. */
192 } else if (return_type
== kSecReturnDataMask
) {
193 a_result
= SecDbItemGetCachedValueWithName(item
, kSecValueData
);
195 CFRetainSafe(a_result
);
197 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
199 } else if (return_type
== kSecReturnPersistentRefMask
) {
200 a_result
= SecPersistentRefCreateWithItem(item
, error
);
202 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item
));
203 /* We need to return more than one value. */
204 if (return_type
& kSecReturnRefMask
) {
205 CFDictionarySetValue(dict
, kSecClass
, SecDbItemGetClass(item
)->name
);
207 CFOptionFlags mask
= (((return_type
& kSecReturnDataMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnDataFlag
: 0) |
208 ((return_type
& kSecReturnAttributesMask
|| return_type
& kSecReturnRefMask
) ? kSecDbReturnAttrFlag
: 0));
209 SecDbForEachAttr(SecDbItemGetClass(item
), desc
) {
210 if ((desc
->flags
& mask
) != 0) {
211 CFTypeRef value
= SecDbItemGetValue(item
, desc
, error
);
212 if (value
&& !CFEqual(kCFNull
, value
)) {
213 CFDictionarySetValue(dict
, desc
->name
, value
);
214 } else if (value
== NULL
) {
220 if (return_type
& kSecReturnPersistentRefMask
) {
221 CFDataRef pref
= SecPersistentRefCreateWithItem(item
, error
);
222 CFDictionarySetValue(dict
, kSecValuePersistentRef
, pref
);
232 /* AUDIT[securityd](done):
233 attributes (ok) is a caller provided dictionary, only its cf type has
237 s3dl_query_add(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
, CFErrorRef
*error
)
239 if (query_match_count(q
) != 0)
240 return errSecItemMatchUnsupported
;
242 /* Add requires a class to be specified unless we are adding a ref. */
243 if (q
->q_use_item_list
)
244 return errSecUseItemListUnsupported
;
246 /* Actual work here. */
247 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, q
->q_class
, q
->q_item
, KEYBAG_DEVICE
, error
);
250 if (SecDbItemIsTombstone(item
))
251 SecDbItemSetValue(item
, &v7utomb
, q
->q_use_tomb
? q
->q_use_tomb
: kCFBooleanTrue
, NULL
);
255 ok
= SecDbItemSetValueWithName(item
, CFSTR("v_Data"), q
->q_data
, error
);
257 ok
= SecDbItemSetRowId(item
, q
->q_row_id
, error
);
258 SecDbItemSetCredHandle(item
, q
->q_use_cred_handle
);
261 ok
= SecDbItemInsert(item
, dbt
, error
);
264 if (result
&& q
->q_return_type
) {
265 *result
= SecDbItemCopyResult(item
, q
->q_return_type
, error
);
268 if (!ok
&& error
&& *error
) {
269 if (CFEqual(CFErrorGetDomain(*error
), kSecDbErrorDomain
) && CFErrorGetCode(*error
) == SQLITE_CONSTRAINT
) {
270 CFReleaseNull(*error
);
271 SecError(errSecDuplicateItem
, error
, CFSTR("duplicate item %@"), item
);
272 } else if (CFEqual(CFErrorGetDomain(*error
), kSecErrorDomain
) && CFErrorGetCode(*error
) == errSecDecode
) { //handle situation when item have pdmn=akpu but passcode is not set
273 CFTypeRef value
= SecDbItemGetValue(item
, SecDbClassAttrWithKind(item
->class, kSecDbAccessAttr
, error
), error
);
274 if (value
&& CFEqual(value
, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
)) {
275 CFReleaseNull(*error
);
276 SecError(errSecAuthFailed
, error
, CFSTR("authentication failed"));
283 if (SecDbItemIsSyncable(item
))
284 q
->q_sync_changed
= true;
287 secdebug("dbitem", "inserting item %@%s%@", item
, ok
? "" : "failed: ", ok
|| error
== NULL
? (CFErrorRef
)CFSTR("") : *error
);
294 typedef void (*s3dl_handle_row
)(sqlite3_stmt
*stmt
, void *context
);
297 s3dl_copy_data_from_col(sqlite3_stmt
*stmt
, int col
, CFErrorRef
*error
) {
298 return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt
, col
),
299 sqlite3_column_bytes(stmt
, col
),
304 s3dl_item_from_col(sqlite3_stmt
*stmt
, Query
*q
, int col
, CFArrayRef accessGroups
,
305 CFMutableDictionaryRef
*item
, SecAccessControlRef
*access_control
, CFErrorRef
*error
) {
306 CFDataRef edata
= NULL
;
308 require(edata
= s3dl_copy_data_from_col(stmt
, col
, error
), out
);
309 ok
= s3dl_item_from_data(edata
, q
, accessGroups
, item
, access_control
, error
);
312 CFReleaseSafe(edata
);
316 struct s3dl_query_ctx
{
318 CFArrayRef accessGroups
;
319 SecDbConnectionRef dbt
;
324 /* Return whatever the caller requested based on the value of q->q_return_type.
325 keys and values must be 3 larger than attr_count in size to accomadate the
326 optional data, class and persistent ref results. This is so we can use
327 the CFDictionaryCreate() api here rather than appending to a
328 mutable dictionary. */
329 static CF_RETURNS_RETAINED CFTypeRef
handle_result(Query
*q
, CFMutableDictionaryRef item
,
330 sqlite_int64 rowid
) {
333 data
= CFDictionaryGetValue(item
, kSecValueData
);
334 if (q
->q_return_type
== 0) {
335 /* Caller isn't interested in any results at all. */
337 } else if (q
->q_return_type
== kSecReturnDataMask
) {
342 a_result
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
344 } else if (q
->q_return_type
== kSecReturnPersistentRefMask
) {
345 a_result
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
347 /* We need to return more than one value. */
348 if (q
->q_return_type
& kSecReturnRefMask
) {
349 CFDictionarySetValue(item
, kSecClass
, q
->q_class
->name
);
350 } else if ((q
->q_return_type
& kSecReturnAttributesMask
)) {
351 if (!(q
->q_return_type
& kSecReturnDataMask
)) {
352 CFDictionaryRemoveValue(item
, kSecValueData
);
356 CFDictionaryRemoveAllValues(item
);
357 if ((q
->q_return_type
& kSecReturnDataMask
) && data
) {
358 CFDictionarySetValue(item
, kSecValueData
, data
);
362 if (q
->q_return_type
& kSecReturnPersistentRefMask
) {
363 CFDataRef pref
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
364 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
375 static void s3dl_merge_into_dict(const void *key
, const void *value
, void *context
) {
376 CFDictionarySetValue(context
, key
, value
);
379 static void s3dl_query_row(sqlite3_stmt
*stmt
, void *context
) {
380 struct s3dl_query_ctx
*c
= context
;
383 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
384 CFMutableDictionaryRef item
;
385 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->accessGroups
, &item
, NULL
, &q
->q_error
);
387 OSStatus status
= SecErrorGetOSStatus(q
->q_error
);
388 // errSecDecode means the item is corrupted, stash it for delete.
389 if (status
== errSecDecode
) {
390 secwarning("ignoring corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, rowid
, q
->q_error
);
392 CFDataRef edata
= s3dl_copy_data_from_col(stmt
, 1, NULL
);
393 CFMutableStringRef edatastring
= CFStringCreateMutable(kCFAllocatorDefault
, 0);
395 CFStringAppendEncryptedData(edatastring
, edata
);
396 secnotice("item", "corrupted edata=%@", edatastring
);
398 CFReleaseSafe(edata
);
399 CFReleaseSafe(edatastring
);
401 CFReleaseNull(q
->q_error
);
402 } else if (status
== errSecAuthNeeded
) {
403 secwarning("Authentication is needed for %@,rowid=%" PRId64
" (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
405 secerror("decode %@,rowid=%" PRId64
" failed (%" PRIdOSStatus
"): %@", q
->q_class
->name
, rowid
, status
, q
->q_error
);
407 // q->q_error will be released appropriately by a call to query_error
414 if (q
->q_class
== &identity_class
) {
415 // TODO: Use col 2 for key rowid and use both rowids in persistent ref.
417 CFMutableDictionaryRef key
;
418 /* TODO : if there is a errSecDecode error here, we should cleanup */
419 if (!s3dl_item_from_col(stmt
, q
, 3, c
->accessGroups
, &key
, NULL
, &q
->q_error
) || !key
)
422 CFDataRef certData
= CFDictionaryGetValue(item
, kSecValueData
);
424 CFDictionarySetValue(key
, CFSTR(CERTIFICATE_DATA_COLUMN_LABEL
),
426 CFDictionaryRemoveValue(item
, kSecValueData
);
428 CFDictionaryApplyFunction(item
, s3dl_merge_into_dict
, key
);
433 if (!match_item(c
->dbt
, q
, c
->accessGroups
, item
))
436 CFTypeRef a_result
= handle_result(q
, item
, rowid
);
438 if (a_result
== kCFNull
) {
439 /* Caller wasn't interested in a result, but we still
440 count this row as found. */
441 CFRelease(a_result
); // Help shut up clang
442 } else if (q
->q_limit
== 1) {
443 c
->result
= a_result
;
445 CFArrayAppendValue((CFMutableArrayRef
)c
->result
, a_result
);
456 SecDbAppendWhereROWID(CFMutableStringRef sql
,
457 CFStringRef col
, sqlite_int64 row_id
,
460 SecDbAppendWhereOrAnd(sql
, needWhere
);
461 CFStringAppendFormat(sql
, NULL
, CFSTR("%@=%lld"), col
, row_id
);
466 SecDbAppendWhereAttrs(CFMutableStringRef sql
, const Query
*q
, bool *needWhere
) {
467 CFIndex ix
, attr_count
= query_attr_count(q
);
468 for (ix
= 0; ix
< attr_count
; ++ix
) {
469 SecDbAppendWhereOrAndEquals(sql
, query_attr_at(q
, ix
).key
, needWhere
);
474 SecDbAppendWhereAccessGroups(CFMutableStringRef sql
,
476 CFArrayRef accessGroups
,
478 CFIndex ix
, ag_count
;
479 if (!accessGroups
|| 0 == (ag_count
= CFArrayGetCount(accessGroups
))) {
483 SecDbAppendWhereOrAnd(sql
, needWhere
);
484 CFStringAppend(sql
, col
);
485 CFStringAppend(sql
, CFSTR(" IN (?"));
486 for (ix
= 1; ix
< ag_count
; ++ix
) {
487 CFStringAppend(sql
, CFSTR(",?"));
489 CFStringAppend(sql
, CFSTR(")"));
492 static void SecDbAppendWhereClause(CFMutableStringRef sql
, const Query
*q
,
493 CFArrayRef accessGroups
) {
494 bool needWhere
= true;
495 SecDbAppendWhereROWID(sql
, CFSTR("ROWID"), q
->q_row_id
, &needWhere
);
496 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
497 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
500 static void SecDbAppendLimit(CFMutableStringRef sql
, CFIndex limit
) {
501 if (limit
!= kSecMatchUnlimited
)
502 CFStringAppendFormat(sql
, NULL
, CFSTR(" LIMIT %" PRIdCFIndex
), limit
);
505 static CFStringRef
s3dl_select_sql(Query
*q
, CFArrayRef accessGroups
) {
506 CFMutableStringRef sql
= CFStringCreateMutable(NULL
, 0);
507 if (q
->q_class
== &identity_class
) {
508 CFStringAppendFormat(sql
, NULL
, CFSTR("SELECT crowid, "
509 CERTIFICATE_DATA_COLUMN_LABEL
", rowid,data FROM "
510 "(SELECT cert.rowid AS crowid, cert.labl AS labl,"
511 " cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
512 " keys.*,cert.data AS " CERTIFICATE_DATA_COLUMN_LABEL
514 " WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"));
515 SecDbAppendWhereAccessGroups(sql
, CFSTR("cert.agrp"), accessGroups
, 0);
516 /* The next 3 SecDbAppendWhere calls are in the same order as in
517 SecDbAppendWhereClause(). This makes sqlBindWhereClause() work,
518 as long as we do an extra sqlBindAccessGroups first. */
519 SecDbAppendWhereROWID(sql
, CFSTR("crowid"), q
->q_row_id
, 0);
520 CFStringAppend(sql
, CFSTR(")"));
521 bool needWhere
= true;
522 SecDbAppendWhereAttrs(sql
, q
, &needWhere
);
523 SecDbAppendWhereAccessGroups(sql
, CFSTR("agrp"), accessGroups
, &needWhere
);
525 CFStringAppend(sql
, CFSTR("SELECT rowid, data FROM "));
526 CFStringAppend(sql
, q
->q_class
->name
);
527 SecDbAppendWhereClause(sql
, q
, accessGroups
);
529 SecDbAppendLimit(sql
, q
->q_limit
);
534 static bool sqlBindAccessGroups(sqlite3_stmt
*stmt
, CFArrayRef accessGroups
,
535 int *pParam
, CFErrorRef
*error
) {
538 CFIndex ix
, count
= accessGroups
? CFArrayGetCount(accessGroups
) : 0;
539 for (ix
= 0; ix
< count
; ++ix
) {
540 result
= SecDbBindObject(stmt
, param
++,
541 CFArrayGetValueAtIndex(accessGroups
, ix
),
550 static bool sqlBindWhereClause(sqlite3_stmt
*stmt
, const Query
*q
,
551 CFArrayRef accessGroups
, int *pParam
, CFErrorRef
*error
) {
554 CFIndex ix
, attr_count
= query_attr_count(q
);
555 for (ix
= 0; ix
< attr_count
; ++ix
) {
556 result
= SecDbBindObject(stmt
, param
++, query_attr_at(q
, ix
).value
, error
);
561 /* Bind the access group to the sql. */
563 result
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
570 bool SecDbItemQuery(SecDbQueryRef query
, CFArrayRef accessGroups
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
571 void (^handle_row
)(SecDbItemRef item
, bool *stop
)) {
572 __block
bool ok
= true;
573 /* Sanity check the query. */
575 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
577 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
578 // The attributes here must match field list hardcoded in s3dl_select_sql used below, which is
580 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
583 CFStringRef sql
= s3dl_select_sql(query
, accessGroups
);
586 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
587 /* Bind the values being searched for to the SELECT statement. */
589 if (query
->q_class
== &identity_class
) {
590 /* Bind the access groups to cert.agrp. */
591 ok
&= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
594 ok
&= sqlBindWhereClause(stmt
, query
, accessGroups
, ¶m
, error
);
596 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
) {
597 SecDbItemRef itemFromStatement
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, query
->q_class
, stmt
, query
->q_keybag
, error
, return_attr
);
598 if (itemFromStatement
) {
599 CFTransferRetained(itemFromStatement
->credHandle
, query
->q_use_cred_handle
);
600 if (match_item(dbconn
, query
, accessGroups
, itemFromStatement
->attributes
))
601 handle_row(itemFromStatement
, stop
);
602 CFReleaseNull(itemFromStatement
);
604 secerror("failed to create item from stmt: %@", error
? *error
: (CFErrorRef
)"no error");
606 CFReleaseNull(*error
);
621 s3dl_query(s3dl_handle_row handle_row
,
622 void *context
, CFErrorRef
*error
)
624 struct s3dl_query_ctx
*c
= context
;
625 SecDbConnectionRef dbt
= c
->dbt
;
627 CFArrayRef accessGroups
= c
->accessGroups
;
629 /* Sanity check the query. */
631 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported by queries"));
633 /* Actual work here. */
634 if (q
->q_limit
== 1) {
637 c
->result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
639 CFStringRef sql
= s3dl_select_sql(q
, accessGroups
);
640 bool ok
= SecDbWithSQL(dbt
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
642 /* Bind the values being searched for to the SELECT statement. */
644 if (q
->q_class
== &identity_class
) {
645 /* Bind the access groups to cert.agrp. */
646 sql_ok
= sqlBindAccessGroups(stmt
, accessGroups
, ¶m
, error
);
649 sql_ok
= sqlBindWhereClause(stmt
, q
, accessGroups
, ¶m
, error
);
651 SecDbForEach(stmt
, error
, ^bool (int row_index
) {
652 handle_row(stmt
, context
);
654 bool needs_auth
= q
->q_error
&& CFErrorGetCode(q
->q_error
) == errSecAuthNeeded
;
655 if (q
->q_skip_acl_items
&& needs_auth
)
656 // Skip items needing authentication if we are told to do so.
657 CFReleaseNull(q
->q_error
);
659 bool stop
= q
->q_limit
!= kSecMatchUnlimited
&& c
->found
>= q
->q_limit
;
660 stop
= stop
|| (q
->q_error
&& !needs_auth
);
669 // First get the error from the query, since errSecDuplicateItem from an
670 // update query should superceed the errSecItemNotFound below.
671 if (!query_error(q
, error
))
673 if (ok
&& c
->found
== 0)
674 ok
= SecError(errSecItemNotFound
, error
, CFSTR("no matching items found"));
680 s3dl_copy_matching(SecDbConnectionRef dbt
, Query
*q
, CFTypeRef
*result
,
681 CFArrayRef accessGroups
, CFErrorRef
*error
)
683 struct s3dl_query_ctx ctx
= {
684 .q
= q
, .accessGroups
= accessGroups
, .dbt
= dbt
,
686 if (q
->q_row_id
&& query_attr_count(q
))
687 return SecError(errSecItemIllegalQuery
, error
,
688 CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
690 // Only copy things that aren't tombstones unless the client explicitly asks otherwise.
691 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
692 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
693 bool ok
= s3dl_query(s3dl_query_row
, &ctx
, error
);
695 *result
= ctx
.result
;
697 CFReleaseSafe(ctx
.result
);
702 /* First remove key from q->q_pairs if it's present, then add the attribute again. */
703 static void query_set_attribute_with_desc(const SecDbAttr
*desc
, const void *value
, Query
*q
) {
704 if (CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
706 for (ix
= 0; ix
< q
->q_attr_end
; ++ix
) {
707 if (CFEqual(desc
->name
, q
->q_pairs
[ix
].key
)) {
708 CFReleaseSafe(q
->q_pairs
[ix
].value
);
710 for (; ix
< q
->q_attr_end
; ++ix
) {
711 q
->q_pairs
[ix
] = q
->q_pairs
[ix
+ 1];
713 CFDictionaryRemoveValue(q
->q_item
, desc
->name
);
718 query_add_attribute_with_desc(desc
, value
, q
);
721 /* Update modification_date if needed. */
722 static void query_pre_update(Query
*q
) {
723 SecDbForEachAttr(q
->q_class
, desc
) {
724 if (desc
->kind
== kSecDbModificationDateAttr
) {
725 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
726 query_set_attribute_with_desc(desc
, now
, q
);
732 /* Make sure all attributes that are marked as not_null have a value. If
733 force_date is false, only set mdat and cdat if they aren't already set. */
734 void query_pre_add(Query
*q
, bool force_date
) {
735 CFDateRef now
= CFDateCreate(0, CFAbsoluteTimeGetCurrent());
736 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInFlag
) {
737 if (desc
->kind
== kSecDbCreationDateAttr
||
738 desc
->kind
== kSecDbModificationDateAttr
) {
740 query_set_attribute_with_desc(desc
, now
, q
);
741 } else if (!CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
742 query_add_attribute_with_desc(desc
, now
, q
);
744 } else if ((desc
->flags
& kSecDbNotNullFlag
) &&
745 !CFDictionaryContainsKey(q
->q_item
, desc
->name
)) {
746 CFTypeRef value
= NULL
;
747 if (desc
->flags
& kSecDbDefault0Flag
) {
748 if (desc
->kind
== kSecDbDateAttr
)
749 value
= CFDateCreate(kCFAllocatorDefault
, 0.0);
752 value
= CFNumberCreate(0, kCFNumberSInt32Type
, &vzero
);
754 } else if (desc
->flags
& kSecDbDefaultEmptyFlag
) {
755 if (desc
->kind
== kSecDbDataAttr
)
756 value
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
763 /* Safe to use query_add_attribute here since the attr wasn't
765 query_add_attribute_with_desc(desc
, value
, q
);
773 // Return a tri state value false->never make a tombstone, true->always make a
774 // tombstone, NULL->make a tombstone, but delete it if the tombstone itself is not currently being synced.
775 static CFBooleanRef
s3dl_should_make_tombstone(Query
*q
, bool item_is_syncable
, SecDbItemRef item
) {
777 return q
->q_use_tomb
;
778 else if (item_is_syncable
&& !SecDbItemIsTombstone(item
))
781 return kCFBooleanFalse
;
783 /* AUDIT[securityd](done):
784 attributesToUpdate (ok) is a caller provided dictionary,
785 only its cf types have been checked.
788 s3dl_query_update(SecDbConnectionRef dbt
, Query
*q
,
789 CFDictionaryRef attributesToUpdate
, CFArrayRef accessGroups
, CFErrorRef
*error
)
791 /* Sanity check the query. */
792 if (query_match_count(q
) != 0)
793 return SecError(errSecItemMatchUnsupported
, error
, CFSTR("match not supported in attributes to update"));
795 return SecError(errSecValueRefUnsupported
, error
, CFSTR("value ref not supported in attributes to update"));
796 if (q
->q_row_id
&& query_attr_count(q
))
797 return SecError(errSecItemIllegalQuery
, error
, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
799 __block
bool result
= true;
800 Query
*u
= query_create(q
->q_class
, attributesToUpdate
, error
);
801 if (u
== NULL
) return false;
802 require_action_quiet(query_update_parse(u
, attributesToUpdate
, error
), errOut
, result
= false);
804 result
&= SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
805 // Make sure we only update real items, not tombstones, unless the client explicitly asks otherwise.
806 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
807 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
808 result
&= SecDbItemQuery(q
, accessGroups
, dbt
, error
, ^(SecDbItemRef item
, bool *stop
) {
809 // We always need to know the error here.
810 CFErrorRef localError
= NULL
;
811 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
812 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
813 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
814 SecDbItemRef new_item
= SecDbItemCopyWithUpdates(item
, u
->q_item
, &localError
);
815 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
816 CFReleaseSafe(storedSHA1
);
817 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
818 // We just ignore this, and treat as if item is not found.
819 secwarning("deleting corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
820 CFReleaseNull(localError
);
821 if (!SecDbItemDelete(item
, dbt
, false, &localError
)) {
822 secerror("failed to delete corrupt %@,rowid=%" PRId64
" %@", q
->q_class
->name
, SecDbItemGetRowId(item
, NULL
), localError
);
823 CFReleaseNull(localError
);
827 if (new_item
!= NULL
&& u
->q_access_control
!= NULL
)
828 SecDbItemSetAccessControl(new_item
, u
->q_access_control
, &localError
);
829 result
= SecErrorPropagate(localError
, error
) && new_item
;
831 bool item_is_sync
= SecDbItemIsSyncable(item
);
832 result
= SecDbItemUpdate(item
, new_item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
835 if (item_is_sync
|| SecDbItemIsSyncable(new_item
))
836 q
->q_sync_changed
= true;
844 if (result
&& !q
->q_changed
)
845 result
= SecError(errSecItemNotFound
, error
, CFSTR("No items updated"));
847 if (!query_destroy(u
, error
))
852 static bool SecDbItemNeedAuth(SecDbItemRef item
, CFErrorRef
*error
)
854 CFErrorRef localError
= NULL
;
855 if (!SecDbItemEnsureDecrypted(item
, &localError
) && localError
&& CFErrorGetCode(localError
) == errSecAuthNeeded
) {
861 CFReleaseSafe(localError
);
866 s3dl_query_delete(SecDbConnectionRef dbt
, Query
*q
, CFArrayRef accessGroups
, CFErrorRef
*error
)
868 __block
bool ok
= true;
869 __block
bool needAuth
= false;
870 // Only delete things that aren't tombstones, unless the client explicitly asks otherwise.
871 if (!CFDictionaryContainsKey(q
->q_item
, kSecAttrTombstone
))
872 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
873 ok
&= SecDbItemSelect(q
, dbt
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
875 },^bool(CFMutableStringRef sql
, bool *needWhere
) {
876 SecDbAppendWhereClause(sql
, q
, accessGroups
);
878 },^bool(sqlite3_stmt
* stmt
, int col
) {
879 return sqlBindWhereClause(stmt
, q
, accessGroups
, &col
, error
);
880 }, ^(SecDbItemRef item
, bool *stop
) {
881 // Check if item need to be authenticated by LocalAuthentication
882 item
->cryptoOp
= kAKSKeyOpDelete
;
883 if (SecDbItemNeedAuth(item
, error
)) {
887 // Cache the storedSHA1 digest so we use the one from the db not the recomputed one for notifications.
888 const SecDbAttr
*sha1attr
= SecDbClassAttrWithKind(item
->class, kSecDbSHA1Attr
, NULL
);
889 CFDataRef storedSHA1
= CFRetainSafe(SecDbItemGetValue(item
, sha1attr
, NULL
));
890 bool item_is_sync
= SecDbItemIsSyncable(item
);
891 SecDbItemSetValue(item
, sha1attr
, storedSHA1
, NULL
);
892 CFReleaseSafe(storedSHA1
);
893 ok
= SecDbItemDelete(item
, dbt
, s3dl_should_make_tombstone(q
, item_is_sync
, item
), error
);
897 q
->q_sync_changed
= true;
900 if (ok
&& !q
->q_changed
&& !needAuth
) {
901 ok
= SecError(errSecItemNotFound
, error
, CFSTR("Delete failed to delete anything"));
903 return ok
&& !needAuth
;
906 /* Return true iff the item in question should not be backed up, nor restored,
907 but when restoring a backup the original version of the item should be
908 added back to the keychain again after the restore completes. */
909 static bool SecItemIsSystemBound(CFDictionaryRef item
, const SecDbClass
*class) {
910 CFStringRef agrp
= CFDictionaryGetValue(item
, kSecAttrAccessGroup
);
914 if (CFEqualSafe(agrp
, kSOSInternalAccessGroup
)) {
915 secdebug("backup", "found sysbound item: %@", item
);
919 if (CFEqual(agrp
, CFSTR("lockdown-identities"))) {
920 secdebug("backup", "found sys_bound item: %@", item
);
924 if (CFEqual(agrp
, CFSTR("apple")) && class == &genp_class
) {
925 CFStringRef service
= CFDictionaryGetValue(item
, kSecAttrService
);
926 CFStringRef account
= CFDictionaryGetValue(item
, kSecAttrAccount
);
927 if (isString(service
) && isString(account
) &&
928 CFEqual(service
, CFSTR("com.apple.managedconfiguration")) &&
929 (CFEqual(account
, CFSTR("Public")) ||
930 CFEqual(account
, CFSTR("Private")))) {
931 secdebug("backup", "found sys_bound item: %@", item
);
935 secdebug("backup", "found non sys_bound item: %@", item
);
939 /* Delete all items from the current keychain. If this is not an in
940 place upgrade we don't delete items in the 'lockdown-identities'
941 access group, this ensures that an import or restore of a backup
942 will never overwrite an existing activation record. */
943 static bool SecServerDeleteAll(SecDbConnectionRef dbt
, CFErrorRef
*error
) {
944 return kc_transaction(dbt
, error
, ^{
945 bool ok
= (SecDbExec(dbt
, CFSTR("DELETE from genp;"), error
) &&
946 SecDbExec(dbt
, CFSTR("DELETE from inet;"), error
) &&
947 SecDbExec(dbt
, CFSTR("DELETE from cert;"), error
) &&
948 SecDbExec(dbt
, CFSTR("DELETE from keys;"), error
));
953 struct s3dl_export_row_ctx
{
954 struct s3dl_query_ctx qc
;
955 keybag_handle_t dest_keybag
;
956 enum SecItemFilter filter
;
959 static void s3dl_export_row(sqlite3_stmt
*stmt
, void *context
) {
960 struct s3dl_export_row_ctx
*c
= context
;
962 SecAccessControlRef access_control
= NULL
;
963 CFErrorRef localError
= NULL
;
965 /* Skip akpu items when backing up, those are intentionally lost across restores. */
966 bool skip_akpu
= c
->filter
== kSecBackupableItemFilter
;
968 sqlite_int64 rowid
= sqlite3_column_int64(stmt
, 0);
969 CFMutableDictionaryRef item
;
970 bool ok
= s3dl_item_from_col(stmt
, q
, 1, c
->qc
.accessGroups
, &item
, &access_control
, &localError
);
972 bool is_akpu
= access_control
? CFEqualSafe(SecAccessControlGetProtection(access_control
),
973 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
) : false;
975 if (ok
&& item
&& !(skip_akpu
&& is_akpu
)) {
976 /* Only export sysbound items if do_sys_bound is true, only export non sysbound items otherwise. */
977 bool do_sys_bound
= c
->filter
== kSecSysBoundItemFilter
;
978 if (c
->filter
== kSecNoItemFilter
||
979 SecItemIsSystemBound(item
, q
->q_class
) == do_sys_bound
) {
980 /* Re-encode the item. */
981 secdebug("item", "export rowid %llu item: %@", rowid
, item
);
982 /* The code below could be moved into handle_row. */
983 CFDataRef pref
= _SecItemMakePersistentRef(q
->q_class
->name
, rowid
);
985 if (c
->dest_keybag
!= KEYBAG_NONE
) {
986 CFMutableDictionaryRef auth_attribs
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
987 SecDbForEachAttrWithMask(q
->q_class
, desc
, kSecDbInAuthenticatedDataFlag
) {
988 CFTypeRef value
= CFDictionaryGetValue(item
, desc
->name
);
990 CFDictionaryAddValue(auth_attribs
, desc
->name
, value
);
991 CFDictionaryRemoveValue(item
, desc
->name
);
995 /* Encode and encrypt the item to the specified keybag. */
996 CFDataRef edata
= NULL
;
997 bool encrypted
= ks_encrypt_data(c
->dest_keybag
, access_control
, q
->q_use_cred_handle
, item
, auth_attribs
, &edata
, &q
->q_error
);
998 CFDictionaryRemoveAllValues(item
);
999 CFRelease(auth_attribs
);
1001 CFDictionarySetValue(item
, kSecValueData
, edata
);
1002 CFReleaseSafe(edata
);
1004 seccritical("ks_encrypt_data %@,rowid=%" PRId64
": failed: %@", q
->q_class
->name
, rowid
, q
->q_error
);
1005 CFReleaseNull(q
->q_error
);
1008 if (CFDictionaryGetCount(item
)) {
1009 CFDictionarySetValue(item
, kSecValuePersistentRef
, pref
);
1010 CFArrayAppendValue((CFMutableArrayRef
)c
->qc
.result
, item
);
1013 CFReleaseSafe(pref
);
1018 OSStatus status
= SecErrorGetOSStatus(localError
);
1020 if (status
== errSecInteractionNotAllowed
&& is_akpu
&& skip_akpu
) {
1021 // We expect akpu items to be inaccessible when the device is locked.
1022 CFReleaseNull(localError
);
1024 /* This happens a lot when trying to migrate keychain before first unlock, so only a notice */
1025 /* If the error is "corrupted item" then we just ignore it, otherwise we save it in the query */
1026 secinfo("item","Could not export item for rowid %llu: %@", rowid
, localError
);
1028 if(status
== errSecDecode
) {
1029 CFReleaseNull(localError
);
1031 CFReleaseSafe(q
->q_error
);
1032 q
->q_error
=localError
;
1036 CFReleaseSafe(access_control
);
1039 CF_RETURNS_RETAINED CFDictionaryRef
SecServerExportKeychainPlist(SecDbConnectionRef dbt
,
1040 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
,
1041 enum SecItemFilter filter
, CFErrorRef
*error
) {
1042 CFMutableDictionaryRef keychain
;
1043 keychain
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1044 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1046 if (error
&& !*error
)
1047 SecError(errSecAllocate
, error
, CFSTR("Can't create keychain dictionary"));
1051 Query q
= { .q_keybag
= src_keybag
};
1052 q
.q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
| \
1053 kSecReturnPersistentRefMask
;
1054 q
.q_limit
= kSecMatchUnlimited
;
1055 q
.q_skip_acl_items
= true;
1057 /* Get rid of this duplicate. */
1058 const SecDbClass
*SecDbClasses
[] = {
1065 for (class_ix
= 0; class_ix
< array_size(SecDbClasses
);
1067 q
.q_class
= SecDbClasses
[class_ix
];
1068 struct s3dl_export_row_ctx ctx
= {
1069 .qc
= { .q
= &q
, .dbt
= dbt
},
1070 .dest_keybag
= dest_keybag
, .filter
= filter
,
1073 secnotice("item", "exporting class '%@'", q
.q_class
->name
);
1075 CFErrorRef localError
= NULL
;
1076 if (s3dl_query(s3dl_export_row
, &ctx
, &localError
)) {
1077 if (CFArrayGetCount(ctx
.qc
.result
))
1078 CFDictionaryAddValue(keychain
, q
.q_class
->name
, ctx
.qc
.result
);
1081 OSStatus status
= (OSStatus
)CFErrorGetCode(localError
);
1082 if (status
== errSecItemNotFound
) {
1083 CFRelease(localError
);
1085 secerror("Export failed: %@", localError
);
1087 CFReleaseSafe(*error
);
1088 *error
= localError
;
1090 CFRelease(localError
);
1092 CFReleaseNull(keychain
);
1093 CFReleaseNull(ctx
.qc
.result
);
1097 CFReleaseNull(ctx
.qc
.result
);
1104 struct SecServerImportClassState
{
1105 SecDbConnectionRef dbt
;
1107 keybag_handle_t src_keybag
;
1108 keybag_handle_t dest_keybag
;
1109 enum SecItemFilter filter
;
1112 struct SecServerImportItemState
{
1113 const SecDbClass
*class;
1114 struct SecServerImportClassState
*s
;
1117 static void SecServerImportItem(const void *value
, void *context
) {
1118 struct SecServerImportItemState
*state
=
1119 (struct SecServerImportItemState
*)context
;
1120 if (state
->s
->error
)
1122 if (!isDictionary(value
)) {
1123 SecError(errSecParam
, &state
->s
->error
, CFSTR("value %@ is not a dictionary"), value
);
1127 CFDictionaryRef dict
= (CFDictionaryRef
)value
;
1129 secdebug("item", "Import Item : %@", dict
);
1131 /* We don't filter non sys_bound items during import since we know we
1132 will never have any in this case, we use the kSecSysBoundItemFilter
1133 to indicate that we don't preserve rowid's during import instead. */
1134 if (state
->s
->filter
== kSecBackupableItemFilter
&&
1135 SecItemIsSystemBound(dict
, state
->class))
1140 /* This is sligthly confusing:
1141 - During upgrade all items are exported with KEYBAG_NONE.
1142 - During restore from backup, existing sys_bound items are exported with KEYBAG_NONE, and are exported as dictionary of attributes.
1143 - Item in the actual backup are export with a real keybag, and are exported as encrypted v_Data and v_PersistentRef
1145 if (state
->s
->src_keybag
== KEYBAG_NONE
) {
1146 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, state
->class, dict
, state
->s
->dest_keybag
, &state
->s
->error
);
1148 item
= SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault
, state
->class, dict
, state
->s
->src_keybag
, state
->s
->dest_keybag
, &state
->s
->error
);
1152 if(state
->s
->filter
!= kSecSysBoundItemFilter
) {
1153 SecDbItemExtractRowIdFromBackupDictionary(item
, dict
, &state
->s
->error
);
1155 SecDbItemInferSyncable(item
, &state
->s
->error
);
1156 SecDbItemInsert(item
, state
->s
->dbt
, &state
->s
->error
);
1159 /* Reset error if we had one, since we just skip the current item
1160 and continue importing what we can. */
1161 if (state
->s
->error
) {
1162 secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
1163 item
, state
->class->name
, state
->s
->error
);
1164 CFReleaseNull(state
->s
->error
);
1167 CFReleaseSafe(item
);
1170 static void SecServerImportClass(const void *key
, const void *value
,
1172 struct SecServerImportClassState
*state
=
1173 (struct SecServerImportClassState
*)context
;
1176 if (!isString(key
)) {
1177 SecError(errSecParam
, &state
->error
, CFSTR("class name %@ is not a string"), key
);
1180 const SecDbClass
*class = kc_class_with_name(key
);
1181 if (!class || class == &identity_class
) {
1182 SecError(errSecParam
, &state
->error
, CFSTR("attempt to import an identity"));
1185 struct SecServerImportItemState item_state
= {
1186 .class = class, .s
= state
1188 if (isArray(value
)) {
1189 CFArrayRef items
= (CFArrayRef
)value
;
1190 CFArrayApplyFunction(items
, CFRangeMake(0, CFArrayGetCount(items
)),
1191 SecServerImportItem
, &item_state
);
1193 CFDictionaryRef item
= (CFDictionaryRef
)value
;
1194 SecServerImportItem(item
, &item_state
);
1198 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt
,
1199 keybag_handle_t src_keybag
, keybag_handle_t dest_keybag
,
1200 CFDictionaryRef keychain
, enum SecItemFilter filter
, CFErrorRef
*error
) {
1203 CFDictionaryRef sys_bound
= NULL
;
1204 if (filter
== kSecBackupableItemFilter
) {
1205 /* Grab a copy of all the items for which SecItemIsSystemBound()
1207 require(sys_bound
= SecServerExportKeychainPlist(dbt
, KEYBAG_DEVICE
,
1208 KEYBAG_NONE
, kSecSysBoundItemFilter
,
1212 /* Delete everything in the keychain. */
1213 require(ok
= SecServerDeleteAll(dbt
, error
), errOut
);
1215 struct SecServerImportClassState state
= {
1217 .src_keybag
= src_keybag
,
1218 .dest_keybag
= dest_keybag
,
1221 /* Import the provided items, preserving rowids. */
1222 CFDictionaryApplyFunction(keychain
, SecServerImportClass
, &state
);
1225 state
.src_keybag
= KEYBAG_NONE
;
1226 /* Import the items we preserved with random rowids. */
1227 state
.filter
= kSecSysBoundItemFilter
;
1228 CFDictionaryApplyFunction(sys_bound
, SecServerImportClass
, &state
);
1232 CFReleaseSafe(*error
);
1233 *error
= state
.error
;
1235 CFRelease(state
.error
);
1241 CFReleaseSafe(sys_bound
);
1246 #pragma mark - key rolling support
1249 struct check_generation_ctx
{
1250 struct s3dl_query_ctx query_ctx
;
1251 uint32_t current_generation
;
1254 static void check_generation(sqlite3_stmt
*stmt
, void *context
) {
1255 struct check_generation_ctx
*c
= context
;
1256 CFDataRef blob
= NULL
;
1258 const uint8_t *cursor
= NULL
;
1260 keyclass_t keyclass
;
1261 uint32_t current_generation
= c
->current_generation
;
1263 require(blob
= s3dl_copy_data_from_col(stmt
, 1, &c
->query_ctx
.q
->q_error
), out
);
1264 blobLen
= CFDataGetLength(blob
);
1265 cursor
= CFDataGetBytePtr(blob
);
1267 /* Check for underflow, ensuring we have at least one full AES block left. */
1268 if (blobLen
< sizeof(version
) + sizeof(keyclass
)) {
1269 SecError(errSecDecode
, &c
->query_ctx
.q
->q_error
, CFSTR("check_generation: Check for underflow"));
1273 version
= *((uint32_t *)cursor
);
1274 cursor
+= sizeof(version
);
1276 (void) version
; // TODO: do something with the version number.
1278 keyclass
= *((keyclass_t
*)cursor
);
1280 // TODO: export get_key_gen macro
1281 if (((keyclass
& ~key_class_last
) == 0) != (current_generation
== 0)) {
1282 c
->query_ctx
.found
++;
1285 CFReleaseSafe(blob
);
1289 c
->query_ctx
.found
++;
1290 CFReleaseSafe(blob
);
1293 bool s3dl_dbt_keys_current(SecDbConnectionRef dbt
, uint32_t current_generation
, CFErrorRef
*error
) {
1294 CFErrorRef localError
= NULL
;
1295 struct check_generation_ctx ctx
= { .query_ctx
= { .dbt
= dbt
}, .current_generation
= current_generation
};
1297 const SecDbClass
*classes
[] = {
1304 for (size_t class_ix
= 0; class_ix
< array_size(classes
); ++class_ix
) {
1305 Query
*q
= query_create(classes
[class_ix
], NULL
, &localError
);
1309 ctx
.query_ctx
.q
= q
;
1310 q
->q_limit
= kSecMatchUnlimited
;
1312 bool ok
= s3dl_query(check_generation
, &ctx
, &localError
);
1313 query_destroy(q
, NULL
);
1314 CFReleaseNull(ctx
.query_ctx
.result
);
1316 if (!ok
&& localError
&& (CFErrorGetCode(localError
) == errSecItemNotFound
)) {
1317 CFReleaseNull(localError
);
1320 secerror("Class %@ not up to date", classes
[class_ix
]->name
);
1326 bool s3dl_dbt_update_keys(SecDbConnectionRef dbt
, CFErrorRef
*error
) {
1327 return SecDbTransaction(dbt
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1328 __block
bool ok
= false;
1329 uint32_t keystore_generation_status
;
1331 /* can we migrate to new class keys right now? */
1332 if (!aks_generation(KEYBAG_DEVICE
, generation_noop
, &keystore_generation_status
) &&
1333 (keystore_generation_status
& generation_change_in_progress
)) {
1335 /* take a lock assertion */
1336 bool operated_while_unlocked
= SecAKSDoWhileUserBagLocked(error
, ^{
1337 CFErrorRef localError
= NULL
;
1338 CFDictionaryRef backup
= SecServerExportKeychainPlist(dbt
,
1339 KEYBAG_DEVICE
, KEYBAG_NONE
, kSecNoItemFilter
, &localError
);
1342 secerror("Ignoring export error: %@ during roll export", localError
);
1343 CFReleaseNull(localError
);
1345 ok
= SecServerImportKeychainInPlist(dbt
, KEYBAG_NONE
,
1346 KEYBAG_DEVICE
, backup
, kSecNoItemFilter
, &localError
);
1348 secerror("Ignoring export error: %@ during roll export", localError
);
1349 CFReleaseNull(localError
);
1354 if (!operated_while_unlocked
)
1357 ok
= SecError(errSecBadReq
, error
, CFSTR("No key roll in progress."));