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 * SecItemDataSource.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
30 #include "keychain/securityd/SecItemDataSource.h"
32 #include "keychain/securityd/SecItemDb.h"
33 #include "keychain/securityd/SecDbItem.h"
34 #include "keychain/securityd/SecItemSchema.h"
35 #include "keychain/securityd/SecItemServer.h"
36 #include "keychain/securityd/SOSCloudCircleServer.h"
37 #include "keychain/SecureObjectSync/SOSDigestVector.h"
38 #include "keychain/SecureObjectSync/SOSEngine.h"
39 #include <Security/SecureObjectSync/SOSViews.h>
40 #include <Security/SecBasePriv.h>
41 #include <Security/SecItem.h>
42 #include <Security/SecItemBackup.h>
43 #include <Security/SecItemPriv.h>
44 #include <utilities/array_size.h>
45 #include <keychain/ckks/CKKS.h>
54 typedef struct SecItemDataSource
*SecItemDataSourceRef
;
56 struct SecItemDataSource
{
57 struct SOSDataSource ds
;
58 SecDbRef db
; // The database we operate on
59 CFStringRef name
; // The name of the slice of the database we represent.
63 static const SecDbClass
*dsSyncedClassesV0Ptrs
[] = {
68 static size_t dsSyncedClassesV0Size
= (array_size(dsSyncedClassesV0Ptrs
));
70 static const SecDbClass
** dsSyncedClassesV0() {
71 static dispatch_once_t onceToken
;
72 dispatch_once(&onceToken
, ^{
73 dsSyncedClassesV0Ptrs
[0] = genp_class();
74 dsSyncedClassesV0Ptrs
[1] = inet_class();
75 dsSyncedClassesV0Ptrs
[2] = keys_class();
77 return dsSyncedClassesV0Ptrs
;
81 static const SecDbClass
*dsSyncedClassesPtrs
[] = {
87 static const size_t dsSyncedClassesSize
= array_size(dsSyncedClassesPtrs
);
89 static const SecDbClass
** dsSyncedClasses() {
90 static dispatch_once_t onceToken
;
91 dispatch_once(&onceToken
, ^{
92 dsSyncedClassesPtrs
[0] = genp_class(); // genp must be first!
93 dsSyncedClassesPtrs
[1] = inet_class();
94 dsSyncedClassesPtrs
[2] = keys_class();
95 dsSyncedClassesPtrs
[3] = cert_class();
97 return dsSyncedClassesPtrs
;
101 static bool SecDbItemSelectSHA1(SecDbQueryRef query
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
102 bool (^use_attr_in_where
)(const SecDbAttr
*attr
),
103 bool (^add_where_sql
)(CFMutableStringRef sql
, bool *needWhere
),
104 bool (^bind_added_where
)(sqlite3_stmt
*stmt
, int col
),
105 void (^row
)(sqlite3_stmt
*stmt
, bool *stop
)) {
106 __block
bool ok
= true;
107 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
108 return attr
->kind
== kSecDbSHA1Attr
;
110 CFStringRef sql
= SecDbItemCopySelectSQL(query
, return_attr
, use_attr_in_where
, add_where_sql
);
112 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
113 ok
= (SecDbItemSelectBind(query
, stmt
, error
, use_attr_in_where
, bind_added_where
) &&
114 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
){ row(stmt
, stop
); }));
123 static SOSManifestRef
SecItemDataSourceCopyManifestWithQueries(SecItemDataSourceRef ds
, CFArrayRef queries
, CFErrorRef
*error
) {
124 __block SOSManifestRef manifest
= NULL
;
125 __block CFErrorRef localError
= NULL
;
126 if (!kc_with_custom_db(false, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
127 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
130 CFArrayForEachC(queries
, q
) {
131 if (!(ok
&= SecDbItemSelectSHA1(q
, dbconn
, &localError
, ^bool(const SecDbAttr
*attr
) {
132 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
133 }, NULL
, NULL
, ^(sqlite3_stmt
*stmt
, bool *stop
) {
134 const uint8_t *digest
= sqlite3_column_blob(stmt
, 0);
135 size_t digestLen
= sqlite3_column_bytes(stmt
, 0);
136 if (digestLen
!= SOSDigestSize
) {
137 secerror("digest %zu bytes", digestLen
);
139 SOSDigestVectorAppend(&dv
, digest
);
142 secerror("SecDbItemSelectSHA1 failed: %@", localError
);
147 // TODO: This code assumes that the passed in queries do not overlap, otherwise we'd need something to eliminate dupes:
148 //SOSDigestVectorUniqueSorted(&dv);
149 manifest
= SOSManifestCreateWithDigestVector(&dv
, &localError
);
151 SOSDigestVectorFree(&dv
);
154 CFReleaseSafe(manifest
);
156 CFErrorPropagate(localError
, error
);
160 static Query
*SecItemDataSourceAppendQuery(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, CFErrorRef
*error
) {
161 Query
*q
= query_create(qclass
, NULL
, NULL
, NULL
, error
);
163 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
164 q
->q_limit
= kSecMatchUnlimited
;
165 q
->q_keybag
= KEYBAG_DEVICE
;
166 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
167 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
168 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
169 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysPrivate
, q
);
170 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
171 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
172 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
, q
);
175 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
178 CFArrayAppendValue(queries
, q
);
183 static Query
*SecItemDataSourceAppendQueryWithClassAndViewHint(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, bool allowTkid
, CFStringRef viewHint
, CFErrorRef
*error
) {
184 Query
*q
= SecItemDataSourceAppendQuery(queries
, qclass
, noTombstones
, error
);
186 // For each attribute in current schema but not in v6, look for the
187 // default value of those attributes in the query, since old items
188 // will all have that as their values for these new attributes.
189 SecDbForEachAttr(qclass
, attr
) {
190 if (attr
== &v7tkid
|| attr
== &v7vwht
) {
191 // attr is a primary key attribute added in schema version 7 or later
192 if (!allowTkid
|| attr
!= &v7tkid
) {
193 if (attr
== &v7vwht
&& viewHint
) {
194 query_add_attribute_with_desc(attr
, viewHint
, q
);
196 CFTypeRef value
= SecDbAttrCopyDefaultValue(attr
, &q
->q_error
);
198 query_add_attribute_with_desc(attr
, value
, q
);
209 static Query
*SecItemDataSourceAppendQueryWithClass(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, bool allowTkid
, CFErrorRef
*error
) {
210 return SecItemDataSourceAppendQueryWithClassAndViewHint(queries
, qclass
, noTombstones
, allowTkid
, NULL
, error
);
213 static Query
*SecItemDataSourceAppendQueryWithClassAndAgrp(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, bool allowTkid
, CFStringRef agrp
, CFErrorRef
*error
) {
214 Query
*q
= SecItemDataSourceAppendQueryWithClass(queries
, qclass
, noTombstones
, allowTkid
, error
);
216 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
221 static bool SecItemDataSourceAppendQueriesForViewName(SecItemDataSourceRef ds
, CFMutableArrayRef queries
, CFStringRef compositeViewName
, CFErrorRef
*error
) {
223 CFStringRef viewName
;
224 bool noTombstones
= CFStringHasSuffix(compositeViewName
, CFSTR("-tomb"));
226 viewName
= CFStringCreateWithSubstring(kCFAllocatorDefault
, compositeViewName
, CFRangeMake(0, CFStringGetLength(compositeViewName
) - 5));
228 viewName
= CFRetain(compositeViewName
);
231 // short-circuit for CKKS-handled views here
232 if(!SOSViewInSOSSystem(viewName
)) {
233 CFReleaseSafe(viewName
);
237 const bool noTKID
= false;
238 const bool allowTKID
= true;
239 if (CFEqual(viewName
, kSOSViewKeychainV0
)) {
240 for (size_t class_ix
= 0; class_ix
< dsSyncedClassesV0Size
; ++class_ix
) {
241 SecItemDataSourceAppendQueryWithClass(queries
, dsSyncedClassesV0()[class_ix
], noTombstones
, noTKID
, error
);
243 } else if (CFEqual(viewName
, kSOSViewWiFi
)) {
244 Query
*q
= SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, genp_class(), noTombstones
, allowTKID
, CFSTR("apple"), error
);
246 query_add_attribute(kSecAttrService
, CFSTR("AirPort"), q
);
248 } else if (CFEqual(viewName
, kSOSViewAutofillPasswords
)) {
249 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, inet_class(), noTombstones
, allowTKID
, CFSTR("com.apple.cfnetwork"), error
);
250 } else if (CFEqual(viewName
, kSOSViewSafariCreditCards
)) {
251 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, genp_class(), noTombstones
, allowTKID
, CFSTR("com.apple.safari.credit-cards"), error
);
252 } else if (CFEqual(viewName
, kSOSViewiCloudIdentity
)) {
253 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, keys_class(), noTombstones
, allowTKID
, CFSTR("com.apple.security.sos"), error
);
254 } else if (CFEqual(viewName
, kSOSViewBackupBagV0
)) {
255 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, genp_class(), noTombstones
, allowTKID
, CFSTR("com.apple.sbd"), error
);
256 } else if (CFEqual(viewName
, kSOSViewOtherSyncable
)) {
257 SecItemDataSourceAppendQueryWithClass(queries
, cert_class(), noTombstones
, allowTKID
, error
);
259 Query
*q1_genp
= SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, genp_class(), noTombstones
, allowTKID
, CFSTR("apple"), error
);
260 query_add_not_attribute(kSecAttrService
, CFSTR("AirPort"), q1_genp
);
262 Query
*q2_genp
= SecItemDataSourceAppendQueryWithClass(queries
, genp_class(), noTombstones
, allowTKID
, error
);
263 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("apple"), q2_genp
);
264 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.safari.credit-cards"), q2_genp
);
265 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.sbd"), q2_genp
);
267 Query
*q_inet
= SecItemDataSourceAppendQueryWithClass(queries
, inet_class(), noTombstones
, allowTKID
, error
);
268 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.cfnetwork"), q_inet
);
270 Query
*q_keys
= SecItemDataSourceAppendQueryWithClass(queries
, keys_class(), noTombstones
, allowTKID
, error
);
271 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.security.sos"), q_keys
);
273 // All other viewNames should match on the ViewHint attribute.
274 for (size_t class_ix
= 0; class_ix
< dsSyncedClassesSize
; ++class_ix
) {
275 SecItemDataSourceAppendQueryWithClassAndViewHint(queries
, dsSyncedClasses()[class_ix
], noTombstones
, allowTKID
, viewName
, error
);
279 CFReleaseSafe(viewName
);
283 static SOSManifestRef
SecItemDataSourceCopyManifestWithViewNameSet(SecItemDataSourceRef ds
, CFSetRef viewNames
, CFErrorRef
*error
) {
284 CFMutableArrayRef queries
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, NULL
);
285 SOSManifestRef manifest
= NULL
;
286 __block
bool ok
= true;
287 CFSetForEach(viewNames
, ^(const void *value
) {
288 CFStringRef viewName
= (CFStringRef
)value
;
289 ok
&= SecItemDataSourceAppendQueriesForViewName(ds
, queries
, viewName
, error
);
292 manifest
= SecItemDataSourceCopyManifestWithQueries(ds
, queries
, error
);
294 CFArrayForEachC(queries
, q
) {
295 CFErrorRef localError
= NULL
;
296 if (!query_destroy(q
, &localError
)) {
297 secerror("query_destroy failed: %@", localError
);
298 CFErrorPropagate(localError
, error
);
299 CFReleaseNull(manifest
);
302 CFReleaseSafe(queries
);
306 // Return the newest object (conflict resolver)
307 // Any fields marked as "kSecDbSyncSOSCannotSyncFlag" that are in item2 will be present in the returned item.
308 static SecDbItemRef
SecItemDataSourceCopyMergedItem(SecDbItemRef item1
, SecDbItemRef item2
, CFErrorRef
*error
) {
309 CFErrorRef localError
= NULL
;
310 SecDbItemRef result
= NULL
;
312 const SecDbAttr
*desc
= SecDbAttrWithKey(SecDbItemGetClass(item1
), kSecAttrModificationDate
, error
);
313 m1
= SecDbItemGetValue(item1
, desc
, &localError
);
314 m2
= SecDbItemGetValue(item2
, desc
, &localError
);
315 if (m1
&& m2
) switch (CFDateCompare(m1
, m2
, NULL
)) {
316 case kCFCompareGreaterThan
:
319 case kCFCompareLessThan
:
322 case kCFCompareEqualTo
:
324 // Return the item with the smallest digest.
325 CFDataRef digest1
= SecDbItemGetSHA1(item1
, &localError
);
326 CFDataRef digest2
= SecDbItemGetSHA1(item2
, &localError
);
327 if (digest1
&& digest2
) switch (CFDataCompareDERData(digest1
, digest2
)) {
328 case kCFCompareGreaterThan
:
329 case kCFCompareEqualTo
:
332 case kCFCompareLessThan
:
335 } else if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
336 if (digest1
) result
= item1
;
337 if (digest2
) result
= item2
;
341 } else if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
342 // If one of the two objects has an unparsable date,
343 // the object with the parsable date wins.
344 if (m1
) result
= item1
;
345 if (m2
) result
= item2
;
349 if (!result
&& error
&& !*error
)
352 CFRelease(localError
);
355 // Note, if we chose item2 as result above, there's no need to move attributes from item2 to item2
356 if(result
&& item2
&& result
!= item2
) {
357 // We'd like to preserve our local UUID, no matter what. UUIDs are not sent across SOS channels, and so items
358 // arriving via SOS have randomly generated UUIDs.
359 SecDbForEachAttr(SecDbItemGetClass(result
), attr
) {
360 if(CFEqualSafe(attr
->name
, v10itemuuid
.name
)) {
361 SecItemPreserveAttribute(result
, item2
, attr
);
365 SecDbForEachAttrWithMask(SecDbItemGetClass(result
), attr
, kSecDbSyncSOSCannotSyncFlag
) {
366 SecItemPreserveAttribute(result
, item2
, attr
);
370 return CFRetainSafe(result
);
374 // MARK: DataSource protocol implementation
377 static CFStringRef
dsGetName(SOSDataSourceRef data_source
) {
378 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
382 static void dsAddNotifyPhaseBlock(SOSDataSourceRef data_source
, SOSDataSourceNotifyBlock notifyBlock
) {
383 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
384 SecDbAddNotifyPhaseBlock(ds
->db
, ^(SecDbConnectionRef dbconn
, SecDbTransactionPhase phase
, SecDbTransactionSource source
, CFArrayRef changes
)
386 notifyBlock(&ds
->ds
, (SOSTransactionRef
)dbconn
, phase
, source
, changes
);
390 static SOSManifestRef
dsCopyManifestWithViewNameSet(SOSDataSourceRef data_source
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
391 struct SecItemDataSource
*ds
= (struct SecItemDataSource
*)data_source
;
392 return SecItemDataSourceCopyManifestWithViewNameSet(ds
, viewNameSet
, error
);
395 static bool dsForEachObject(SOSDataSourceRef data_source
, SOSTransactionRef txn
, SOSManifestRef manifest
, CFErrorRef
*error
, void (^handle_object
)(CFDataRef key
, SOSObjectRef object
, bool *stop
)) {
396 struct SecItemDataSource
*ds
= (struct SecItemDataSource
*)data_source
;
397 __block
bool result
= true;
398 const SecDbAttr
*sha1Attr
= SecDbClassAttrWithKind(genp_class(), kSecDbSHA1Attr
, error
);
399 if (!sha1Attr
) return false;
400 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
401 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
403 bool (^use_attr_in_where
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
404 return attr
->kind
== kSecDbSHA1Attr
;
406 Query
*select_queries
[dsSyncedClassesSize
] = {};
407 CFStringRef select_sql
[dsSyncedClassesSize
] = {};
408 sqlite3_stmt
*select_stmts
[dsSyncedClassesSize
] = {};
410 __block Query
**queries
= select_queries
;
411 __block CFStringRef
*sqls
= select_sql
;
412 __block sqlite3_stmt
**stmts
= select_stmts
;
414 bool (^readBlock
)(SecDbConnectionRef dbconn
) = ^bool(SecDbConnectionRef dbconn
)
417 for (size_t class_ix
= 0; class_ix
< dsSyncedClassesSize
; ++class_ix
) {
419 && (queries
[class_ix
] = query_create(dsSyncedClasses()[class_ix
], NULL
, NULL
, NULL
, error
))
420 && (sqls
[class_ix
] = SecDbItemCopySelectSQL(queries
[class_ix
], return_attr
, use_attr_in_where
, NULL
))
421 && (stmts
[class_ix
] = SecDbCopyStmt(dbconn
, sqls
[class_ix
], NULL
, error
)));
424 if (result
) SOSManifestForEach(manifest
, ^(CFDataRef key
, bool *stop
) {
425 __block SecDbItemRef item
= NULL
;
426 for (size_t class_ix
= 0; result
&& !item
&& class_ix
< dsSyncedClassesSize
; ++class_ix
) {
427 CFDictionarySetValue(queries
[class_ix
]->q_item
, sha1Attr
->name
, key
);
428 result
= SecDbItemSelectBind(queries
[class_ix
], stmts
[class_ix
], error
, use_attr_in_where
, NULL
);
430 result
&= SecDbStep(dbconn
, stmts
[class_ix
], error
, ^(bool *unused_stop
) {
431 item
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, queries
[class_ix
]->q_class
, stmts
[class_ix
], KEYBAG_DEVICE
, error
, return_attr
);
435 result
&= SecDbReset(stmts
[class_ix
], error
);
437 handle_object(key
, (SOSObjectRef
)item
, stop
);
442 for (size_t class_ix
= 0; class_ix
< dsSyncedClassesSize
; ++class_ix
) {
443 result
&= SecDbReleaseCachedStmt(dbconn
, sqls
[class_ix
], stmts
[class_ix
], error
);
444 CFReleaseSafe(sqls
[class_ix
]);
445 if (queries
[class_ix
])
446 result
&= query_destroy(queries
[class_ix
], error
);
453 readBlock((SecDbConnectionRef
)txn
);
455 result
&= kc_with_custom_db(false, true, ds
->db
, error
, readBlock
);
461 static bool dsRelease(SOSDataSourceRef data_source
, CFErrorRef
*error
) {
462 // We never release our dataSource since it's tracking changes
463 // to the keychain for the engine and its peers.
467 static SOSObjectRef
objectCreateWithPropertyList(CFDictionaryRef plist
, CFErrorRef
*error
) {
468 SecDbItemRef item
= NULL
;
469 const SecDbClass
*class = NULL
;
470 CFTypeRef cname
= CFDictionaryGetValue(plist
, kSecClass
);
472 class = kc_class_with_name(cname
);
474 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, class, plist
, KEYBAG_DEVICE
, error
);
476 SecError(errSecNoSuchClass
, error
, CFSTR("can find class named: %@"), cname
);
479 SecError(errSecItemClassMissing
, error
, CFSTR("query missing %@ attribute"), kSecClass
);
481 return (SOSObjectRef
)item
;
484 static CFDataRef
copyObjectDigest(SOSObjectRef object
, CFErrorRef
*error
) {
485 SecDbItemRef item
= (SecDbItemRef
) object
;
486 CFDataRef digest
= SecDbItemGetSHA1(item
, error
);
487 CFRetainSafe(digest
);
491 static CFDateRef
copyObjectModDate(SOSObjectRef object
, CFErrorRef
*error
) {
492 SecDbItemRef item
= (SecDbItemRef
) object
;
493 CFDateRef modDate
= SecDbItemGetValueKind(item
, kSecDbModificationDateAttr
, NULL
);
494 CFRetainSafe(modDate
);
498 static CFDictionaryRef
objectCopyPropertyList(SOSObjectRef object
, CFErrorRef
*error
) {
499 SecDbItemRef item
= (SecDbItemRef
) object
;
500 CFMutableDictionaryRef secretDataDict
= SecDbItemCopyPListWithFlagAndSkip(item
, kSecDbReturnDataFlag
, kSecDbSyncSOSCannotSyncFlag
, error
);
501 CFMutableDictionaryRef cryptoDataDict
= SecDbItemCopyPListWithFlagAndSkip(item
, kSecDbInCryptoDataFlag
, kSecDbSyncSOSCannotSyncFlag
, error
);
502 CFMutableDictionaryRef authDataDict
= SecDbItemCopyPListWithFlagAndSkip(item
, kSecDbInAuthenticatedDataFlag
, kSecDbSyncSOSCannotSyncFlag
, error
);
504 if (cryptoDataDict
) {
506 CFDictionaryForEach(authDataDict
, ^(const void *key
, const void *value
) {
507 CFDictionarySetValue(cryptoDataDict
, key
, value
);
510 if (secretDataDict
) {
511 CFDictionaryForEach(secretDataDict
, ^(const void* key
, const void* value
) {
512 CFDictionarySetValue(cryptoDataDict
, key
, value
);
515 CFDictionaryAddValue(cryptoDataDict
, kSecClass
, SecDbItemGetClass(item
)->name
);
518 CFReleaseNull(secretDataDict
);
519 CFReleaseNull(authDataDict
);
520 return cryptoDataDict
;
523 static bool dsWith(SOSDataSourceRef data_source
, CFErrorRef
*error
, SOSDataSourceTransactionSource source
, bool onCommitQueue
, void(^transaction
)(SOSTransactionRef txn
, bool *commit
)) {
524 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
525 __block
bool ok
= true;
526 ok
&= kc_with_custom_db(true, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
527 return SecDbTransaction(dbconn
,
528 source
== kSOSDataSourceAPITransaction
? kSecDbExclusiveTransactionType
: kSecDbExclusiveRemoteSOSTransactionType
,
529 error
, ^(bool *commit
) {
531 SecDbPerformOnCommitQueue(dbconn
, false, ^{
532 transaction((SOSTransactionRef
)dbconn
, commit
);
535 transaction((SOSTransactionRef
)dbconn
, commit
);
542 static bool dsReadWith(SOSDataSourceRef data_source
, CFErrorRef
*error
, SOSDataSourceTransactionSource source
, void(^perform
)(SOSTransactionRef txn
)) {
543 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
544 __block
bool ok
= true;
545 ok
&= kc_with_custom_db(false, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
546 SecDbPerformOnCommitQueue(dbconn
, false, ^{
547 perform((SOSTransactionRef
)dbconn
);
554 static SOSMergeResult
dsMergeObject(SOSTransactionRef txn
, SOSObjectRef peersObject
, SOSObjectRef
*mergedObject
, CFErrorRef
*error
) {
555 SecDbConnectionRef dbconn
= (SecDbConnectionRef
)txn
;
556 SecDbItemRef peersItem
= (SecDbItemRef
)peersObject
;
557 __block SOSMergeResult mr
= kSOSMergeFailure
;
558 __block SecDbItemRef mergedItem
= NULL
;
559 __block SecDbItemRef replacedItem
= NULL
;
560 __block CFErrorRef localError
= NULL
;
561 __block
bool attemptedMerge
= false;
563 if (!peersItem
|| !dbconn
)
564 return kSOSMergeFailure
;
565 if (!SecDbItemSetKeybag(peersItem
, KEYBAG_DEVICE
, &localError
)) {
566 secnotice("ds", "kSOSMergeFailure => SecDbItemSetKeybag: %@", localError
);
567 CFErrorPropagate(localError
, error
);
568 return kSOSMergeFailure
;
571 bool insertedOrReplaced
= SecDbItemInsertOrReplace(peersItem
, dbconn
, &localError
, ^(SecDbItemRef myItem
, SecDbItemRef
*replace
) {
572 // An item with the same primary key as dbItem already exists in the the database. That item is old_item.
573 // Let the conflict resolver choose which item to keep.
574 attemptedMerge
= true;
575 mergedItem
= SecItemDataSourceCopyMergedItem(peersItem
, myItem
, &localError
);
577 mr
= kSOSMergeFailure
;
578 return; // from block
581 *mergedObject
= (SOSObjectRef
)CFRetain(mergedItem
);
582 if (CFEqual(mergedItem
, myItem
)) {
583 // Conflict resolver choose my (local) item
584 if (SecDbItemIsEngineInternalState(myItem
))
585 secdebug ("ds", "Conflict resolver chose my (local) item: %@", myItem
);
587 secnotice("ds", "Conflict resolver chose my (local) item: %@", myItem
);
588 mr
= kSOSMergeLocalObject
;
590 CFRetainAssign(replacedItem
, myItem
);
591 *replace
= CFRetainSafe(mergedItem
);
592 if (CFEqual(mergedItem
, peersItem
)) {
593 // Conflict resolver chose peer's item
594 if (SecDbItemIsEngineInternalState(peersItem
))
595 secdebug ("ds", "Conflict resolver chose peers item: %@", peersItem
);
597 secnotice("ds", "Conflict resolver chose peers item: %@", peersItem
);
598 mr
= kSOSMergePeersObject
;
600 // Conflict resolver created a new item; return it to our caller
601 if (SecDbItemIsEngineInternalState(mergedItem
))
602 secdebug ("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem
);
604 secnotice("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem
);
605 mr
= kSOSMergeCreatedObject
;
610 if (insertedOrReplaced
&& !attemptedMerge
) {
611 // SecDbItemInsertOrReplace succeeded and conflict block was never called -> insert succeeded.
612 // We have peersItem in the database so we need to report that
613 secnotice("ds", "Insert succeeded for: %@", peersItem
);
614 mr
= kSOSMergePeersObject
;
616 // Report only if we had an error, too. Shouldn't happen in practice.
618 secnotice("ds", "kSOSMergeFailure => kSOSMergePeersObject, %@", localError
);
619 CFReleaseSafe(localError
);
623 if (localError
&& !SecErrorIsSqliteDuplicateItemError(localError
)) {
624 secnotice("ds", "dsMergeObject failed: mr=%ld, %@", mr
, localError
);
625 // We should probably always propogate this, but for now we are only logging
626 // See rdar://problem/26451072 for case where we might need to propogate
627 if (mr
== kSOSMergeFailure
) {
628 CFErrorPropagate(localError
, error
);
633 CFReleaseSafe(mergedItem
);
634 CFReleaseSafe(replacedItem
);
635 CFReleaseSafe(localError
);
640 Truthy backup format is a dictionary from sha1 => item.
641 Each item has class, hash and item data.
643 TODO: sha1 is included as binary blob to avoid parsing key.
646 kSecBackupIndexHash
= 0,
647 kSecBackupIndexClass
,
651 static const void *kSecBackupKeys
[] = {
652 [kSecBackupIndexHash
] = kSecItemBackupHashKey
,
653 [kSecBackupIndexClass
] = kSecItemBackupClassKey
,
654 [kSecBackupIndexData
] = kSecItemBackupDataKey
657 static CFDictionaryRef
objectCopyBackup(SOSObjectRef object
, uint64_t handle
, CFErrorRef
*error
) {
658 const void *values
[array_size(kSecBackupKeys
)];
659 SecDbItemRef item
= (SecDbItemRef
)object
;
660 CFDictionaryRef backup_item
= NULL
;
662 if ((values
[kSecBackupIndexHash
] = SecDbItemGetSHA1(item
, error
))) {
663 if ((values
[kSecBackupIndexData
] = SecDbItemCopyEncryptedDataToBackup(item
, handle
, error
))) {
664 values
[kSecBackupIndexClass
] = SecDbItemGetClass(item
)->name
;
665 backup_item
= CFDictionaryCreate(kCFAllocatorDefault
, kSecBackupKeys
, values
, array_size(kSecBackupKeys
), &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
666 CFRelease(values
[kSecBackupIndexData
]);
673 static CFDataRef
dsCopyStateWithKey(SOSDataSourceRef data_source
, CFStringRef key
, CFStringRef pdmn
, SOSTransactionRef txn
, CFErrorRef
*error
) {
674 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
675 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
676 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
677 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
678 kSecAttrAccount
, key
,
679 kSecAttrService
, dataSourceID
,
680 kSecAttrAccessible
, pdmn
,
681 kSecAttrSynchronizable
, kCFBooleanFalse
,
683 CFReleaseSafe(dataSourceID
);
684 __block CFDataRef data
= NULL
;
685 SecDbQueryRef query
= query_create(genp_class(), NULL
, dict
, NULL
, error
);
687 if (query
->q_item
) CFReleaseSafe(query
->q_item
);
688 query
->q_item
= dict
;
689 bool (^read_it
)(SecDbConnectionRef dbconn
) = ^(SecDbConnectionRef dbconn
) {
690 return SecDbItemSelect(query
, dbconn
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
691 return CFDictionaryContainsKey(dict
, attr
->name
);
692 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
693 secnotice("ds", "found item for key %@@%@", key
, pdmn
);
694 data
= CFRetainSafe(SecDbItemGetValue(item
, &v6v_Data
, error
));
698 read_it((SecDbConnectionRef
) txn
);
700 kc_with_custom_db(false, true, ds
->db
, error
, read_it
);
702 query_destroy(query
, error
);
706 if (!data
) secnotice("ds", "failed to load %@@%@ state: %@", key
, pdmn
, error
? *error
: NULL
);
710 static CFDataRef
dsCopyItemDataWithKeys(SOSDataSourceRef data_source
, CFDictionaryRef keys
, CFErrorRef
*error
) {
713 kSecAttrAccessGroup ==> CFSTR("com.apple.sbd")
714 kSecAttrAccessible ==> kSecAttrAccessibleWhenUnlocked
715 kSecAttrAccount ==> CFSTR("SecureBackupPublicKeybag")
716 kSecAttrService ==> CFSTR("SecureBackupService")
718 CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
719 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
720 kSecAttrAccount, account,
721 kSecAttrService, service,
722 kSecAttrAccessible, pdmn,
723 kSecAttrSynchronizable, kCFBooleanTrue,
727 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
728 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, keys
);
729 __block CFDataRef data
= NULL
;
730 SecDbQueryRef query
= query_create(genp_class(), NULL
, dict
, NULL
, error
);
732 if (query
->q_item
) CFReleaseSafe(query
->q_item
);
733 query
->q_item
= dict
;
734 kc_with_custom_db(false, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
735 return SecDbItemSelect(query
, dbconn
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
736 return CFDictionaryContainsKey(dict
, attr
->name
);
737 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
738 secnotice("ds", "found item for keys %@", keys
);
739 data
= CFRetainSafe(SecDbItemGetValue(item
, &v6v_Data
, error
));
742 query_destroy(query
, error
);
746 if (!data
) secnotice("ds", "failed to load item %@: %@", keys
, error
? *error
: NULL
);
750 static bool dsSetStateWithKey(SOSDataSourceRef data_source
, SOSTransactionRef txn
, CFStringRef key
, CFStringRef pdmn
, CFDataRef state
, CFErrorRef
*error
) {
751 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
752 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
753 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
754 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
755 kSecAttrAccount
, key
,
756 kSecAttrService
, dataSourceID
,
757 kSecAttrAccessible
, pdmn
,
758 kSecAttrSynchronizable
, kCFBooleanFalse
,
759 kSecValueData
, state
,
761 CFReleaseSafe(dataSourceID
);
762 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, genp_class(), dict
, KEYBAG_DEVICE
, error
);
763 SOSMergeResult mr
= dsMergeObject(txn
, (SOSObjectRef
)item
, NULL
, error
);
764 if (mr
== kSOSMergeFailure
) secerror("failed to save %@@%@ state: %@", key
, pdmn
, error
? *error
: NULL
);
767 return mr
!= kSOSMergeFailure
;
770 static bool dsDeleteStateWithKey(SOSDataSourceRef data_source
, CFStringRef key
, CFStringRef pdmn
, SOSTransactionRef txn
, CFErrorRef
*error
) {
771 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
772 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
773 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
774 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
775 kSecAttrAccount
, key
,
776 kSecAttrService
, dataSourceID
,
777 kSecAttrAccessible
, pdmn
,
778 kSecAttrSynchronizable
, kCFBooleanFalse
,
780 CFReleaseSafe(dataSourceID
);
781 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, genp_class(), dict
, KEYBAG_DEVICE
, error
);
782 bool ok
= SecDbItemDoDeleteSilently(item
, (SecDbConnectionRef
)txn
, error
);
788 static bool dsRestoreObject(SOSTransactionRef txn
, uint64_t handle
, CFDictionaryRef item
, CFErrorRef
*error
) {
789 CFStringRef item_class
= CFDictionaryGetValue(item
, kSecItemBackupClassKey
);
790 CFDataRef data
= CFDictionaryGetValue(item
, kSecItemBackupDataKey
);
791 const SecDbClass
*dbclass
= NULL
;
793 if (!item_class
|| !data
)
794 return SecError(errSecDecode
, error
, CFSTR("no class or data in object"));
796 dbclass
= kc_class_with_name(item_class
);
798 return SecError(errSecDecode
, error
, CFSTR("no such class %@; update kc_class_with_name "), item_class
);
800 SecDbItemRef dbitem
= SecDbItemCreateWithEncryptedData(kCFAllocatorDefault
, dbclass
, data
, (keybag_handle_t
)handle
, error
);
801 bool ok
= dbitem
&& (dsMergeObject(txn
, (SOSObjectRef
)dbitem
, NULL
, error
) != kSOSMergeFailure
);
802 CFReleaseSafe(dbitem
);
806 SOSDataSourceRef
SecItemDataSourceCreate(SecDbRef db
, CFStringRef name
, CFErrorRef
*error
) {
807 SecItemDataSourceRef ds
= calloc(1, sizeof(struct SecItemDataSource
));
808 ds
->ds
.dsGetName
= dsGetName
;
809 ds
->ds
.dsAddNotifyPhaseBlock
= dsAddNotifyPhaseBlock
;
810 ds
->ds
.dsCopyManifestWithViewNameSet
= dsCopyManifestWithViewNameSet
;
811 ds
->ds
.dsCopyStateWithKey
= dsCopyStateWithKey
;
812 ds
->ds
.dsCopyItemDataWithKeys
= dsCopyItemDataWithKeys
;
814 ds
->ds
.dsForEachObject
= dsForEachObject
;
815 ds
->ds
.dsWith
= dsWith
;
816 ds
->ds
.dsReadWith
= dsReadWith
;
817 ds
->ds
.dsRelease
= dsRelease
;
819 ds
->ds
.dsMergeObject
= dsMergeObject
;
820 ds
->ds
.dsSetStateWithKey
= dsSetStateWithKey
;
821 ds
->ds
.dsDeleteStateWithKey
= dsDeleteStateWithKey
;
822 ds
->ds
.dsRestoreObject
= dsRestoreObject
;
824 // Object field accessors
825 ds
->ds
.objectCopyDigest
= copyObjectDigest
;
826 ds
->ds
.objectCopyModDate
= copyObjectModDate
;
828 // Object encode and decode.
829 ds
->ds
.objectCreateWithPropertyList
= objectCreateWithPropertyList
;
830 ds
->ds
.objectCopyPropertyList
= objectCopyPropertyList
;
831 ds
->ds
.objectCopyBackup
= objectCopyBackup
;
833 ds
->db
= CFRetainSafe(db
);
834 ds
->name
= CFRetainSafe(name
);
836 // Do this after the ds is fully setup so the engine can query us right away.
837 ds
->ds
.engine
= SOSEngineCreate(&ds
->ds
, error
);
838 if (!ds
->ds
.engine
) {
845 static CFStringRef
SecItemDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory
)
847 // This is the name of the v0 datasource, a.k.a. "ak"
848 return kSecAttrAccessibleWhenUnlocked
;
851 struct SecItemDataSourceFactory
{
852 struct SOSDataSourceFactory factory
;
853 CFMutableDictionaryRef dsCache
;
854 dispatch_queue_t queue
;
858 static SOSDataSourceRef
SecItemDataSourceFactoryCopyDataSource(SOSDataSourceFactoryRef factory
, CFStringRef dataSourceName
, CFErrorRef
*error
)
860 struct SecItemDataSourceFactory
*f
= (struct SecItemDataSourceFactory
*)factory
;
861 __block SOSDataSourceRef dataSource
= NULL
;
862 dispatch_sync(f
->queue
, ^{
863 dataSource
= (SOSDataSourceRef
)CFDictionaryGetValue(f
->dsCache
, dataSourceName
);
864 if (!dataSource
&& f
->db
) {
865 dataSource
= (SOSDataSourceRef
)SecItemDataSourceCreate(f
->db
, dataSourceName
, error
);
866 CFDictionarySetValue(f
->dsCache
, dataSourceName
, dataSource
);
872 static void SecItemDataSourceFactoryDispose(SOSDataSourceFactoryRef factory
)
874 // Nothing to do here.
877 // Fire up the SOSEngines so they can
878 static bool SOSDataSourceFactoryStartYourEngines(SOSDataSourceFactoryRef factory
) {
880 if(!SecCKKSTestDisableSOS() && !SecCKKSTestsEnabled()) {
883 CFStringRef dsName
= SOSDataSourceFactoryCopyName(factory
);
884 CFErrorRef localError
= NULL
;
885 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(factory
, dsName
, &localError
);
887 secerror("create_datasource %@ failed %@", dsName
, localError
);
888 CFReleaseNull(localError
);
889 SOSDataSourceRelease(ds
, &localError
);
890 CFReleaseNull(localError
);
891 CFReleaseNull(dsName
);
900 static SOSDataSourceFactoryRef
SecItemDataSourceFactoryCreate(SecDbRef db
) {
901 struct SecItemDataSourceFactory
*dsf
= calloc(1, sizeof(struct SecItemDataSourceFactory
));
902 dsf
->factory
.copy_name
= SecItemDataSourceFactoryCopyName
;
903 dsf
->factory
.create_datasource
= SecItemDataSourceFactoryCopyDataSource
;
904 dsf
->factory
.release
= SecItemDataSourceFactoryDispose
;
906 dsf
->dsCache
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
907 dsf
->queue
= dispatch_queue_create("dsf queue", DISPATCH_QUEUE_SERIAL
);
908 dsf
->db
= CFRetainSafe(db
);
909 if (!SOSDataSourceFactoryStartYourEngines(&dsf
->factory
))
910 secerror("Failed to start engines, gonna lose the race.");
911 return &dsf
->factory
;
915 static dispatch_once_t sDSFQueueOnce
;
916 static dispatch_queue_t sDSFQueue
;
917 static CFMutableDictionaryRef sDSTable
= NULL
;
919 void SecItemDataSourceFactoryReleaseAll() {
920 // Ensure that the queue is set up
921 (void) SecItemDataSourceFactoryGetShared(nil
);
923 dispatch_sync(sDSFQueue
, ^{
925 CFDictionaryRemoveAllValues(sDSTable
);
930 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetShared(SecDbRef db
) {
932 dispatch_once(&sDSFQueueOnce
, ^{
933 sDSFQueue
= dispatch_queue_create("dataSourceFactory queue", DISPATCH_QUEUE_SERIAL
);
934 sDSTable
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
937 __block SOSDataSourceFactoryRef result
= NULL
;
938 dispatch_sync(sDSFQueue
, ^{
940 CFStringRef dbPath
= SecDbGetPath(db
);
942 result
= (SOSDataSourceFactoryRef
) CFDictionaryGetValue(sDSTable
, dbPath
);
945 result
= SecItemDataSourceFactoryCreate(db
);
946 CFDictionaryAddValue(sDSTable
, dbPath
, result
);
955 // TODO: These should move to SecItemServer.c
957 void SecItemServerAppendItemDescription(CFMutableStringRef desc
, CFDictionaryRef object
) {
958 SOSObjectRef item
= objectCreateWithPropertyList(object
, NULL
);
960 CFStringRef itemDesc
= CFCopyDescription(item
);
962 CFStringAppend(desc
, itemDesc
);
963 CFReleaseSafe(itemDesc
);
969 SOSManifestRef
SOSCreateManifestWithBackup(CFDictionaryRef backup
, CFErrorRef
*error
)
971 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
973 CFDictionaryForEach(backup
, ^void (const void * key
, const void * value
) {
974 if (isDictionary(value
)) {
975 /* converting key back to binary blob is horrible */
976 CFDataRef sha1
= CFDictionaryGetValue(value
, kSecItemBackupHashKey
);
977 if (isData(sha1
) && CFDataGetLength(sha1
) == CCSHA1_OUTPUT_SIZE
)
978 SOSDigestVectorAppend(&dv
, CFDataGetBytePtr(sha1
));
982 SOSManifestRef manifest
= SOSManifestCreateWithDigestVector(&dv
, error
);
983 SOSDigestVectorFree(&dv
);