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 <securityd/SecItemDataSource.h>
32 #include <securityd/SecItemDb.h>
33 #include <securityd/SecDbItem.h>
34 #include <securityd/SecItemSchema.h>
35 #include <securityd/SecItemServer.h>
36 #include <securityd/SOSCloudCircleServer.h>
37 #include <Security/SecureObjectSync/SOSDigestVector.h>
38 #include <Security/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
, 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 static SecDbItemRef
SecItemDataSourceCopyMergedItem(SecDbItemRef item1
, SecDbItemRef item2
, CFErrorRef
*error
) {
308 CFErrorRef localError
= NULL
;
309 SecDbItemRef result
= NULL
;
311 const SecDbAttr
*desc
= SecDbAttrWithKey(SecDbItemGetClass(item1
), kSecAttrModificationDate
, error
);
312 m1
= SecDbItemGetValue(item1
, desc
, &localError
);
313 m2
= SecDbItemGetValue(item2
, desc
, &localError
);
314 if (m1
&& m2
) switch (CFDateCompare(m1
, m2
, NULL
)) {
315 case kCFCompareGreaterThan
:
318 case kCFCompareLessThan
:
321 case kCFCompareEqualTo
:
323 // Return the item with the smallest digest.
324 CFDataRef digest1
= SecDbItemGetSHA1(item1
, &localError
);
325 CFDataRef digest2
= SecDbItemGetSHA1(item2
, &localError
);
326 if (digest1
&& digest2
) switch (CFDataCompare(digest1
, digest2
)) {
327 case kCFCompareGreaterThan
:
328 case kCFCompareEqualTo
:
331 case kCFCompareLessThan
:
334 } else if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
335 if (digest1
) result
= item1
;
336 if (digest2
) result
= item2
;
340 } else if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
341 // If one of the two objects has an unparsable date,
342 // the object with the parsable date wins.
343 if (m1
) result
= item1
;
344 if (m2
) result
= item2
;
348 if (!result
&& error
&& !*error
)
351 CFRelease(localError
);
353 return CFRetainSafe(result
);
357 // MARK: DataSource protocol implementation
360 static CFStringRef
dsGetName(SOSDataSourceRef data_source
) {
361 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
365 static void dsAddNotifyPhaseBlock(SOSDataSourceRef data_source
, SOSDataSourceNotifyBlock notifyBlock
) {
366 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
367 SecDbAddNotifyPhaseBlock(ds
->db
, ^(SecDbConnectionRef dbconn
, SecDbTransactionPhase phase
, SecDbTransactionSource source
, CFArrayRef changes
)
369 notifyBlock(&ds
->ds
, (SOSTransactionRef
)dbconn
, phase
, source
, changes
);
373 static SOSManifestRef
dsCopyManifestWithViewNameSet(SOSDataSourceRef data_source
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
374 struct SecItemDataSource
*ds
= (struct SecItemDataSource
*)data_source
;
375 return SecItemDataSourceCopyManifestWithViewNameSet(ds
, viewNameSet
, error
);
378 static bool dsForEachObject(SOSDataSourceRef data_source
, SOSTransactionRef txn
, SOSManifestRef manifest
, CFErrorRef
*error
, void (^handle_object
)(CFDataRef key
, SOSObjectRef object
, bool *stop
)) {
379 struct SecItemDataSource
*ds
= (struct SecItemDataSource
*)data_source
;
380 __block
bool result
= true;
381 const SecDbAttr
*sha1Attr
= SecDbClassAttrWithKind(genp_class(), kSecDbSHA1Attr
, error
);
382 if (!sha1Attr
) return false;
383 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
384 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
386 bool (^use_attr_in_where
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
387 return attr
->kind
== kSecDbSHA1Attr
;
389 Query
*select_queries
[dsSyncedClassesSize
] = {};
390 CFStringRef select_sql
[dsSyncedClassesSize
] = {};
391 sqlite3_stmt
*select_stmts
[dsSyncedClassesSize
] = {};
393 __block Query
**queries
= select_queries
;
394 __block CFStringRef
*sqls
= select_sql
;
395 __block sqlite3_stmt
**stmts
= select_stmts
;
397 bool (^readBlock
)(SecDbConnectionRef dbconn
) = ^bool(SecDbConnectionRef dbconn
)
400 for (size_t class_ix
= 0; class_ix
< dsSyncedClassesSize
; ++class_ix
) {
402 && (queries
[class_ix
] = query_create(dsSyncedClasses()[class_ix
], NULL
, NULL
, error
))
403 && (sqls
[class_ix
] = SecDbItemCopySelectSQL(queries
[class_ix
], return_attr
, use_attr_in_where
, NULL
))
404 && (stmts
[class_ix
] = SecDbCopyStmt(dbconn
, sqls
[class_ix
], NULL
, error
)));
407 if (result
) SOSManifestForEach(manifest
, ^(CFDataRef key
, bool *stop
) {
408 __block SecDbItemRef item
= NULL
;
409 for (size_t class_ix
= 0; result
&& !item
&& class_ix
< dsSyncedClassesSize
; ++class_ix
) {
410 CFDictionarySetValue(queries
[class_ix
]->q_item
, sha1Attr
->name
, key
);
411 result
= SecDbItemSelectBind(queries
[class_ix
], stmts
[class_ix
], error
, use_attr_in_where
, NULL
);
413 result
&= SecDbStep(dbconn
, stmts
[class_ix
], error
, ^(bool *unused_stop
) {
414 item
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, queries
[class_ix
]->q_class
, stmts
[class_ix
], KEYBAG_DEVICE
, error
, return_attr
);
418 result
&= SecDbReset(stmts
[class_ix
], error
);
420 handle_object(key
, (SOSObjectRef
)item
, stop
);
425 for (size_t class_ix
= 0; class_ix
< dsSyncedClassesSize
; ++class_ix
) {
426 result
&= SecDbReleaseCachedStmt(dbconn
, sqls
[class_ix
], stmts
[class_ix
], error
);
427 CFReleaseSafe(sqls
[class_ix
]);
428 if (queries
[class_ix
])
429 result
&= query_destroy(queries
[class_ix
], error
);
436 readBlock((SecDbConnectionRef
)txn
);
438 result
&= kc_with_custom_db(false, true, ds
->db
, error
, readBlock
);
444 static bool dsRelease(SOSDataSourceRef data_source
, CFErrorRef
*error
) {
445 // We never release our dataSource since it's tracking changes
446 // to the keychain for the engine and its peers.
450 static SOSObjectRef
objectCreateWithPropertyList(CFDictionaryRef plist
, CFErrorRef
*error
) {
451 SecDbItemRef item
= NULL
;
452 const SecDbClass
*class = NULL
;
453 CFTypeRef cname
= CFDictionaryGetValue(plist
, kSecClass
);
455 class = kc_class_with_name(cname
);
457 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, class, plist
, KEYBAG_DEVICE
, error
);
459 SecError(errSecNoSuchClass
, error
, CFSTR("can find class named: %@"), cname
);
462 SecError(errSecItemClassMissing
, error
, CFSTR("query missing %@ attribute"), kSecClass
);
464 return (SOSObjectRef
)item
;
467 static CFDataRef
copyObjectDigest(SOSObjectRef object
, CFErrorRef
*error
) {
468 SecDbItemRef item
= (SecDbItemRef
) object
;
469 CFDataRef digest
= SecDbItemGetSHA1(item
, error
);
470 CFRetainSafe(digest
);
474 static CFDateRef
copyObjectModDate(SOSObjectRef object
, CFErrorRef
*error
) {
475 SecDbItemRef item
= (SecDbItemRef
) object
;
476 CFDateRef modDate
= SecDbItemGetValueKind(item
, kSecDbModificationDateAttr
, NULL
);
477 CFRetainSafe(modDate
);
481 static CFDictionaryRef
objectCopyPropertyList(SOSObjectRef object
, CFErrorRef
*error
) {
482 SecDbItemRef item
= (SecDbItemRef
) object
;
483 CFMutableDictionaryRef secretDataDict
= SecDbItemCopyPListWithMask(item
, kSecDbReturnDataFlag
, error
);
484 CFMutableDictionaryRef cryptoDataDict
= SecDbItemCopyPListWithMask(item
, kSecDbInCryptoDataFlag
, error
);
485 CFMutableDictionaryRef authDataDict
= SecDbItemCopyPListWithMask(item
, kSecDbInAuthenticatedDataFlag
, error
);
487 if (cryptoDataDict
) {
489 CFDictionaryForEach(authDataDict
, ^(const void *key
, const void *value
) {
490 CFDictionarySetValue(cryptoDataDict
, key
, value
);
493 if (secretDataDict
) {
494 CFDictionaryForEach(secretDataDict
, ^(const void* key
, const void* value
) {
495 CFDictionarySetValue(cryptoDataDict
, key
, value
);
498 CFDictionaryAddValue(cryptoDataDict
, kSecClass
, SecDbItemGetClass(item
)->name
);
501 CFReleaseNull(secretDataDict
);
502 CFReleaseNull(authDataDict
);
503 return cryptoDataDict
;
506 static bool dsWith(SOSDataSourceRef data_source
, CFErrorRef
*error
, SOSDataSourceTransactionSource source
, bool onCommitQueue
, void(^transaction
)(SOSTransactionRef txn
, bool *commit
)) {
507 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
508 __block
bool ok
= true;
509 ok
&= kc_with_custom_db(true, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
510 return SecDbTransaction(dbconn
,
511 source
== kSOSDataSourceAPITransaction
? kSecDbExclusiveTransactionType
: kSecDbExclusiveRemoteSOSTransactionType
,
512 error
, ^(bool *commit
) {
514 SecDbPerformOnCommitQueue(dbconn
, false, ^{
515 transaction((SOSTransactionRef
)dbconn
, commit
);
518 transaction((SOSTransactionRef
)dbconn
, commit
);
525 static bool dsReadWith(SOSDataSourceRef data_source
, CFErrorRef
*error
, SOSDataSourceTransactionSource source
, void(^perform
)(SOSTransactionRef txn
)) {
526 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
527 __block
bool ok
= true;
528 ok
&= kc_with_custom_db(false, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
529 SecDbPerformOnCommitQueue(dbconn
, false, ^{
530 perform((SOSTransactionRef
)dbconn
);
537 static SOSMergeResult
dsMergeObject(SOSTransactionRef txn
, SOSObjectRef peersObject
, SOSObjectRef
*mergedObject
, CFErrorRef
*error
) {
538 SecDbConnectionRef dbconn
= (SecDbConnectionRef
)txn
;
539 SecDbItemRef peersItem
= (SecDbItemRef
)peersObject
;
540 __block SOSMergeResult mr
= kSOSMergeFailure
;
541 __block SecDbItemRef mergedItem
= NULL
;
542 __block SecDbItemRef replacedItem
= NULL
;
543 __block CFErrorRef localError
= NULL
;
544 __block
bool attemptedMerge
= false;
546 if (!peersItem
|| !dbconn
)
547 return kSOSMergeFailure
;
548 if (!SecDbItemSetKeybag(peersItem
, KEYBAG_DEVICE
, &localError
)) {
549 secnotice("ds", "kSOSMergeFailure => SecDbItemSetKeybag: %@", localError
);
550 CFErrorPropagate(localError
, error
);
551 return kSOSMergeFailure
;
554 bool insertedOrReplaced
= SecDbItemInsertOrReplace(peersItem
, dbconn
, &localError
, ^(SecDbItemRef myItem
, SecDbItemRef
*replace
) {
555 // An item with the same primary key as dbItem already exists in the the database. That item is old_item.
556 // Let the conflict resolver choose which item to keep.
557 attemptedMerge
= true;
558 mergedItem
= SecItemDataSourceCopyMergedItem(peersItem
, myItem
, &localError
);
560 mr
= kSOSMergeFailure
;
561 return; // from block
564 *mergedObject
= (SOSObjectRef
)CFRetain(mergedItem
);
565 if (CFEqual(mergedItem
, myItem
)) {
566 // Conflict resolver choose my (local) item
567 if (SecDbItemIsEngineInternalState(myItem
))
568 secdebug ("ds", "Conflict resolver chose my (local) item: %@", myItem
);
570 secnotice("ds", "Conflict resolver chose my (local) item: %@", myItem
);
571 mr
= kSOSMergeLocalObject
;
573 CFRetainAssign(replacedItem
, myItem
);
574 *replace
= CFRetainSafe(mergedItem
);
575 if (CFEqual(mergedItem
, peersItem
)) {
576 // Conflict resolver chose peer's item
577 if (SecDbItemIsEngineInternalState(peersItem
))
578 secdebug ("ds", "Conflict resolver chose peers item: %@", peersItem
);
580 secnotice("ds", "Conflict resolver chose peers item: %@", peersItem
);
581 mr
= kSOSMergePeersObject
;
583 // Conflict resolver created a new item; return it to our caller
584 if (SecDbItemIsEngineInternalState(mergedItem
))
585 secdebug ("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem
);
587 secnotice("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem
);
588 mr
= kSOSMergeCreatedObject
;
593 if (insertedOrReplaced
&& !attemptedMerge
) {
594 // SecDbItemInsertOrReplace succeeded and conflict block was never called -> insert succeeded.
595 // We have peersItem in the database so we need to report that
596 secnotice("ds", "Insert succeeded for: %@", peersItem
);
597 mr
= kSOSMergePeersObject
;
599 // Report only if we had an error, too. Shouldn't happen in practice.
601 secnotice("ds", "kSOSMergeFailure => kSOSMergePeersObject, %@", localError
);
602 CFReleaseSafe(localError
);
606 if (localError
&& !SecErrorIsSqliteDuplicateItemError(localError
)) {
607 secnotice("ds", "dsMergeObject failed: mr=%ld, %@", mr
, localError
);
608 // We should probably always propogate this, but for now we are only logging
609 // See rdar://problem/26451072 for case where we might need to propogate
610 if (mr
== kSOSMergeFailure
) {
611 CFErrorPropagate(localError
, error
);
616 CFReleaseSafe(mergedItem
);
617 CFReleaseSafe(replacedItem
);
618 CFReleaseSafe(localError
);
623 Truthy backup format is a dictionary from sha1 => item.
624 Each item has class, hash and item data.
626 TODO: sha1 is included as binary blob to avoid parsing key.
629 kSecBackupIndexHash
= 0,
630 kSecBackupIndexClass
,
634 static const void *kSecBackupKeys
[] = {
635 [kSecBackupIndexHash
] = kSecItemBackupHashKey
,
636 [kSecBackupIndexClass
] = kSecItemBackupClassKey
,
637 [kSecBackupIndexData
] = kSecItemBackupDataKey
640 static CFDictionaryRef
objectCopyBackup(SOSObjectRef object
, uint64_t handle
, CFErrorRef
*error
) {
641 const void *values
[array_size(kSecBackupKeys
)];
642 SecDbItemRef item
= (SecDbItemRef
)object
;
643 CFDictionaryRef backup_item
= NULL
;
645 if ((values
[kSecBackupIndexHash
] = SecDbItemGetSHA1(item
, error
))) {
646 if ((values
[kSecBackupIndexData
] = SecDbItemCopyEncryptedDataToBackup(item
, handle
, error
))) {
647 values
[kSecBackupIndexClass
] = SecDbItemGetClass(item
)->name
;
648 backup_item
= CFDictionaryCreate(kCFAllocatorDefault
, kSecBackupKeys
, values
, array_size(kSecBackupKeys
), &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
649 CFRelease(values
[kSecBackupIndexData
]);
656 static CFDataRef
dsCopyStateWithKey(SOSDataSourceRef data_source
, CFStringRef key
, CFStringRef pdmn
, SOSTransactionRef txn
, CFErrorRef
*error
) {
657 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
658 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
659 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
660 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
661 kSecAttrAccount
, key
,
662 kSecAttrService
, dataSourceID
,
663 kSecAttrAccessible
, pdmn
,
664 kSecAttrSynchronizable
, kCFBooleanFalse
,
666 CFReleaseSafe(dataSourceID
);
667 __block CFDataRef data
= NULL
;
668 SecDbQueryRef query
= query_create(genp_class(), NULL
, dict
, error
);
670 if (query
->q_item
) CFReleaseSafe(query
->q_item
);
671 query
->q_item
= dict
;
672 bool (^read_it
)(SecDbConnectionRef dbconn
) = ^(SecDbConnectionRef dbconn
) {
673 return SecDbItemSelect(query
, dbconn
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
674 return CFDictionaryContainsKey(dict
, attr
->name
);
675 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
676 secnotice("ds", "found item for key %@@%@", key
, pdmn
);
677 data
= CFRetainSafe(SecDbItemGetValue(item
, &v6v_Data
, error
));
681 read_it((SecDbConnectionRef
) txn
);
683 kc_with_custom_db(false, true, ds
->db
, error
, read_it
);
685 query_destroy(query
, error
);
689 if (!data
) secnotice("ds", "failed to load %@@%@ state: %@", key
, pdmn
, error
? *error
: NULL
);
693 static CFDataRef
dsCopyItemDataWithKeys(SOSDataSourceRef data_source
, CFDictionaryRef keys
, CFErrorRef
*error
) {
696 kSecAttrAccessGroup ==> CFSTR("com.apple.sbd")
697 kSecAttrAccessible ==> kSecAttrAccessibleWhenUnlocked
698 kSecAttrAccount ==> CFSTR("SecureBackupPublicKeybag")
699 kSecAttrService ==> CFSTR("SecureBackupService")
701 CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
702 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
703 kSecAttrAccount, account,
704 kSecAttrService, service,
705 kSecAttrAccessible, pdmn,
706 kSecAttrSynchronizable, kCFBooleanTrue,
710 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
711 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, keys
);
712 __block CFDataRef data
= NULL
;
713 SecDbQueryRef query
= query_create(genp_class(), NULL
, dict
, error
);
715 if (query
->q_item
) CFReleaseSafe(query
->q_item
);
716 query
->q_item
= dict
;
717 kc_with_custom_db(false, true, ds
->db
, error
, ^bool(SecDbConnectionRef dbconn
) {
718 return SecDbItemSelect(query
, dbconn
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
719 return CFDictionaryContainsKey(dict
, attr
->name
);
720 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
721 secnotice("ds", "found item for keys %@", keys
);
722 data
= CFRetainSafe(SecDbItemGetValue(item
, &v6v_Data
, error
));
725 query_destroy(query
, error
);
729 if (!data
) secnotice("ds", "failed to load item %@: %@", keys
, error
? *error
: NULL
);
733 static bool dsSetStateWithKey(SOSDataSourceRef data_source
, SOSTransactionRef txn
, CFStringRef key
, CFStringRef pdmn
, CFDataRef state
, CFErrorRef
*error
) {
734 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
735 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
736 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
737 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
738 kSecAttrAccount
, key
,
739 kSecAttrService
, dataSourceID
,
740 kSecAttrAccessible
, pdmn
,
741 kSecAttrSynchronizable
, kCFBooleanFalse
,
742 kSecValueData
, state
,
744 CFReleaseSafe(dataSourceID
);
745 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, genp_class(), dict
, KEYBAG_DEVICE
, error
);
746 SOSMergeResult mr
= dsMergeObject(txn
, (SOSObjectRef
)item
, NULL
, error
);
747 if (mr
== kSOSMergeFailure
) secerror("failed to save %@@%@ state: %@", key
, pdmn
, error
? *error
: NULL
);
750 return mr
!= kSOSMergeFailure
;
753 static bool dsDeleteStateWithKey(SOSDataSourceRef data_source
, CFStringRef key
, CFStringRef pdmn
, SOSTransactionRef txn
, CFErrorRef
*error
) {
754 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
755 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
756 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
757 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
758 kSecAttrAccount
, key
,
759 kSecAttrService
, dataSourceID
,
760 kSecAttrAccessible
, pdmn
,
761 kSecAttrSynchronizable
, kCFBooleanFalse
,
763 CFReleaseSafe(dataSourceID
);
764 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, genp_class(), dict
, KEYBAG_DEVICE
, error
);
765 bool ok
= SecDbItemDoDeleteSilently(item
, (SecDbConnectionRef
)txn
, error
);
771 static bool dsRestoreObject(SOSTransactionRef txn
, uint64_t handle
, CFDictionaryRef item
, CFErrorRef
*error
) {
772 CFStringRef item_class
= CFDictionaryGetValue(item
, kSecItemBackupClassKey
);
773 CFDataRef data
= CFDictionaryGetValue(item
, kSecItemBackupDataKey
);
774 const SecDbClass
*dbclass
= NULL
;
776 if (!item_class
|| !data
)
777 return SecError(errSecDecode
, error
, CFSTR("no class or data in object"));
779 dbclass
= kc_class_with_name(item_class
);
781 return SecError(errSecDecode
, error
, CFSTR("no such class %@; update kc_class_with_name "), item_class
);
783 SecDbItemRef dbitem
= SecDbItemCreateWithEncryptedData(kCFAllocatorDefault
, dbclass
, data
, (keybag_handle_t
)handle
, error
);
784 bool ok
= dbitem
&& (dsMergeObject(txn
, (SOSObjectRef
)dbitem
, NULL
, error
) != kSOSMergeFailure
);
785 CFReleaseSafe(dbitem
);
789 SOSDataSourceRef
SecItemDataSourceCreate(SecDbRef db
, CFStringRef name
, CFErrorRef
*error
) {
790 SecItemDataSourceRef ds
= calloc(1, sizeof(struct SecItemDataSource
));
791 ds
->ds
.dsGetName
= dsGetName
;
792 ds
->ds
.dsAddNotifyPhaseBlock
= dsAddNotifyPhaseBlock
;
793 ds
->ds
.dsCopyManifestWithViewNameSet
= dsCopyManifestWithViewNameSet
;
794 ds
->ds
.dsCopyStateWithKey
= dsCopyStateWithKey
;
795 ds
->ds
.dsCopyItemDataWithKeys
= dsCopyItemDataWithKeys
;
797 ds
->ds
.dsForEachObject
= dsForEachObject
;
798 ds
->ds
.dsWith
= dsWith
;
799 ds
->ds
.dsReadWith
= dsReadWith
;
800 ds
->ds
.dsRelease
= dsRelease
;
802 ds
->ds
.dsMergeObject
= dsMergeObject
;
803 ds
->ds
.dsSetStateWithKey
= dsSetStateWithKey
;
804 ds
->ds
.dsDeleteStateWithKey
= dsDeleteStateWithKey
;
805 ds
->ds
.dsRestoreObject
= dsRestoreObject
;
807 // Object field accessors
808 ds
->ds
.objectCopyDigest
= copyObjectDigest
;
809 ds
->ds
.objectCopyModDate
= copyObjectModDate
;
811 // Object encode and decode.
812 ds
->ds
.objectCreateWithPropertyList
= objectCreateWithPropertyList
;
813 ds
->ds
.objectCopyPropertyList
= objectCopyPropertyList
;
814 ds
->ds
.objectCopyBackup
= objectCopyBackup
;
816 ds
->db
= CFRetainSafe(db
);
817 ds
->name
= CFRetainSafe(name
);
819 // Do this after the ds is fully setup so the engine can query us right away.
820 ds
->ds
.engine
= SOSEngineCreate(&ds
->ds
, error
);
821 if (!ds
->ds
.engine
) {
828 static CFStringRef
SecItemDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory
)
830 // This is the name of the v0 datasource, a.k.a. "ak"
831 return kSecAttrAccessibleWhenUnlocked
;
834 struct SecItemDataSourceFactory
{
835 struct SOSDataSourceFactory factory
;
836 CFMutableDictionaryRef dsCache
;
837 dispatch_queue_t queue
;
841 static SOSDataSourceRef
SecItemDataSourceFactoryCopyDataSource(SOSDataSourceFactoryRef factory
, CFStringRef dataSourceName
, CFErrorRef
*error
)
843 struct SecItemDataSourceFactory
*f
= (struct SecItemDataSourceFactory
*)factory
;
844 __block SOSDataSourceRef dataSource
= NULL
;
845 dispatch_sync(f
->queue
, ^{
846 dataSource
= (SOSDataSourceRef
)CFDictionaryGetValue(f
->dsCache
, dataSourceName
);
847 if (!dataSource
&& f
->db
) {
848 dataSource
= (SOSDataSourceRef
)SecItemDataSourceCreate(f
->db
, dataSourceName
, error
);
849 CFDictionarySetValue(f
->dsCache
, dataSourceName
, dataSource
);
855 static void SecItemDataSourceFactoryDispose(SOSDataSourceFactoryRef factory
)
857 // Nothing to do here.
860 static void SecItemDataSourceFactoryCircleChanged(SOSDataSourceFactoryRef factory
, CFStringRef myPeerID
, CFArrayRef trustedPeerIDs
, CFArrayRef untrustedPeerIDs
) {
861 CFStringRef dsName
= SOSDataSourceFactoryCopyName(factory
);
862 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(factory
, dsName
, NULL
);
864 SOSEngineCircleChanged(engine
, myPeerID
, trustedPeerIDs
, untrustedPeerIDs
);
865 CFReleaseSafe(dsName
);
868 // Fire up the SOSEngines so they can
869 static bool SOSDataSourceFactoryStartYourEngines(SOSDataSourceFactoryRef factory
) {
871 if(!SecCKKSTestDisableSOS() && !SecCKKSTestsEnabled()) {
874 CFStringRef dsName
= SOSDataSourceFactoryCopyName(factory
);
875 CFErrorRef localError
= NULL
;
876 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(factory
, dsName
, &localError
);
878 secerror("create_datasource %@ failed %@", dsName
, localError
);
879 CFReleaseNull(localError
);
880 SOSDataSourceRelease(ds
, &localError
);
881 CFReleaseNull(localError
);
882 CFReleaseNull(dsName
);
891 static SOSDataSourceFactoryRef
SecItemDataSourceFactoryCreate(SecDbRef db
) {
892 struct SecItemDataSourceFactory
*dsf
= calloc(1, sizeof(struct SecItemDataSourceFactory
));
893 dsf
->factory
.copy_name
= SecItemDataSourceFactoryCopyName
;
894 dsf
->factory
.create_datasource
= SecItemDataSourceFactoryCopyDataSource
;
895 dsf
->factory
.release
= SecItemDataSourceFactoryDispose
;
896 dsf
->factory
.circle_changed
= SecItemDataSourceFactoryCircleChanged
;
898 dsf
->dsCache
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
899 dsf
->queue
= dispatch_queue_create("dsf queue", DISPATCH_QUEUE_SERIAL
);
900 dsf
->db
= CFRetainSafe(db
);
901 if (!SOSDataSourceFactoryStartYourEngines(&dsf
->factory
))
902 secerror("Failed to start engines, gonna lose the race.");
903 return &dsf
->factory
;
906 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetShared(SecDbRef db
) {
907 static dispatch_once_t sDSFQueueOnce
;
908 static dispatch_queue_t sDSFQueue
;
909 static CFMutableDictionaryRef sDSTable
= NULL
;
911 dispatch_once(&sDSFQueueOnce
, ^{
912 sDSFQueue
= dispatch_queue_create("dataSourceFactory queue", DISPATCH_QUEUE_SERIAL
);
913 sDSTable
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
916 __block SOSDataSourceFactoryRef result
= NULL
;
917 dispatch_sync(sDSFQueue
, ^{
919 CFStringRef dbPath
= SecDbGetPath(db
);
921 result
= (SOSDataSourceFactoryRef
) CFDictionaryGetValue(sDSTable
, dbPath
);
924 result
= SecItemDataSourceFactoryCreate(db
);
925 CFDictionaryAddValue(sDSTable
, dbPath
, result
);
934 // TODO: These should move to SecItemServer.c
936 void SecItemServerAppendItemDescription(CFMutableStringRef desc
, CFDictionaryRef object
) {
937 SOSObjectRef item
= objectCreateWithPropertyList(object
, NULL
);
939 CFStringRef itemDesc
= CFCopyDescription(item
);
941 CFStringAppend(desc
, itemDesc
);
942 CFReleaseSafe(itemDesc
);
948 SOSManifestRef
SOSCreateManifestWithBackup(CFDictionaryRef backup
, CFErrorRef
*error
)
950 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
952 CFDictionaryForEach(backup
, ^void (const void * key
, const void * value
) {
953 if (isDictionary(value
)) {
954 /* converting key back to binary blob is horrible */
955 CFDataRef sha1
= CFDictionaryGetValue(value
, kSecItemBackupHashKey
);
956 if (isData(sha1
) && CFDataGetLength(sha1
) == CCSHA1_OUTPUT_SIZE
)
957 SOSDigestVectorAppend(&dv
, CFDataGetBytePtr(sha1
));
961 SOSManifestRef manifest
= SOSManifestCreateWithDigestVector(&dv
, error
);
962 SOSDigestVectorFree(&dv
);