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/SecItemSchema.h>
34 #include <securityd/SOSCloudCircleServer.h>
35 #include <Security/SecureObjectSync/SOSDigestVector.h>
36 #include <Security/SecureObjectSync/SOSViews.h>
37 #include <Security/SecBasePriv.h>
38 #include <Security/SecItem.h>
39 #include <Security/SecItemBackup.h>
40 #include <Security/SecItemPriv.h>
41 #include <utilities/array_size.h>
50 typedef struct SecItemDataSource
*SecItemDataSourceRef
;
52 struct SecItemDataSource
{
53 struct SOSDataSource ds
;
54 SecDbRef db
; // The database we operate on
55 CFStringRef name
; // The name of the slice of the database we represent.
58 static const SecDbClass
*dsSyncedClassesV0
[] = {
64 static const SecDbClass
*dsSyncedClasses
[] = {
65 &genp_class
, // genp must be first!
71 static bool SecDbItemSelectSHA1(SecDbQueryRef query
, SecDbConnectionRef dbconn
, CFErrorRef
*error
,
72 bool (^use_attr_in_where
)(const SecDbAttr
*attr
),
73 bool (^add_where_sql
)(CFMutableStringRef sql
, bool *needWhere
),
74 bool (^bind_added_where
)(sqlite3_stmt
*stmt
, int col
),
75 void (^row
)(sqlite3_stmt
*stmt
, bool *stop
)) {
76 __block
bool ok
= true;
77 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
78 return attr
->kind
== kSecDbSHA1Attr
;
80 CFStringRef sql
= SecDbItemCopySelectSQL(query
, return_attr
, use_attr_in_where
, add_where_sql
);
82 ok
&= SecDbPrepare(dbconn
, sql
, error
, ^(sqlite3_stmt
*stmt
) {
83 ok
= (SecDbItemSelectBind(query
, stmt
, error
, use_attr_in_where
, bind_added_where
) &&
84 SecDbStep(dbconn
, stmt
, error
, ^(bool *stop
){ row(stmt
, stop
); }));
93 static SOSManifestRef
SecItemDataSourceCopyManifestWithQueries(SecItemDataSourceRef ds
, CFArrayRef queries
, CFErrorRef
*error
) {
94 __block SOSManifestRef manifest
= NULL
;
95 __block CFErrorRef localError
= NULL
;
96 if (!SecDbPerformRead(ds
->db
, error
, ^(SecDbConnectionRef dbconn
) {
97 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
100 CFArrayForEachC(queries
, q
) {
101 if (!(ok
&= SecDbItemSelectSHA1(q
, dbconn
, &localError
, ^bool(const SecDbAttr
*attr
) {
102 return CFDictionaryContainsKey(q
->q_item
, attr
->name
);
103 }, NULL
, NULL
, ^(sqlite3_stmt
*stmt
, bool *stop
) {
104 const uint8_t *digest
= sqlite3_column_blob(stmt
, 0);
105 size_t digestLen
= sqlite3_column_bytes(stmt
, 0);
106 if (digestLen
!= SOSDigestSize
) {
107 secerror("digest %zu bytes", digestLen
);
109 SOSDigestVectorAppend(&dv
, digest
);
112 secerror("SecDbItemSelectSHA1 failed: %@", localError
);
117 // TODO: This code assumes that the passed in queries do not overlap, otherwise we'd need something to eliminate dupes:
118 //SOSDigestVectorUniqueSorted(&dv);
119 manifest
= SOSManifestCreateWithDigestVector(&dv
, &localError
);
121 SOSDigestVectorFree(&dv
);
123 CFReleaseSafe(manifest
);
125 CFErrorPropagate(localError
, error
);
129 static Query
*SecItemDataSourceAppendQuery(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, CFErrorRef
*error
) {
130 Query
*q
= query_create(qclass
, NULL
, error
);
132 q
->q_return_type
= kSecReturnDataMask
| kSecReturnAttributesMask
;
133 q
->q_limit
= kSecMatchUnlimited
;
134 q
->q_keybag
= KEYBAG_DEVICE
;
135 query_add_attribute(kSecAttrSynchronizable
, kCFBooleanTrue
, q
);
136 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
, q
);
137 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlock
, q
);
138 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlways
, q
);
139 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
, q
);
140 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
, q
);
141 query_add_or_attribute(kSecAttrAccessible
, kSecAttrAccessibleAlwaysThisDeviceOnly
, q
);
144 query_add_attribute(kSecAttrTombstone
, kCFBooleanFalse
, q
);
147 CFArrayAppendValue(queries
, q
);
152 static Query
*SecItemDataSourceAppendQueryWithClassAndViewHint(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, bool allowTkid
, CFStringRef viewHint
, CFErrorRef
*error
) {
153 Query
*q
= SecItemDataSourceAppendQuery(queries
, qclass
, noTombstones
, error
);
155 // For each attribute in current schema but not in v6, look for the
156 // default value of those attributes in the query, since old items
157 // will all have that as their values for these new attributes.
158 SecDbForEachAttr(qclass
, attr
) {
159 if ((attr
->flags
& (kSecDbPrimaryKeyFlag
| kSecDbDefaultEmptyFlag
| kSecDbDefault0Flag
| kSecDbNotNullFlag
)) == kSecDbPrimaryKeyFlag
) {
160 // attr is a primary key attribute added in schema version 7 or later
161 if (!allowTkid
|| attr
!= &v7tkid
) {
162 if (attr
== &v7vwht
&& viewHint
) {
163 query_add_attribute_with_desc(attr
, viewHint
, q
);
165 CFTypeRef value
= SecDbAttrCopyDefaultValue(attr
, &q
->q_error
);
167 query_add_attribute_with_desc(attr
, value
, q
);
178 static Query
*SecItemDataSourceAppendQueryWithClass(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, bool allowTkid
, CFErrorRef
*error
) {
179 return SecItemDataSourceAppendQueryWithClassAndViewHint(queries
, qclass
, noTombstones
, allowTkid
, NULL
, error
);
182 static Query
*SecItemDataSourceAppendQueryWithClassAndAgrp(CFMutableArrayRef queries
, const SecDbClass
*qclass
, bool noTombstones
, bool allowTkid
, CFStringRef agrp
, CFErrorRef
*error
) {
183 Query
*q
= SecItemDataSourceAppendQueryWithClass(queries
, qclass
, noTombstones
, allowTkid
, error
);
185 query_add_attribute(kSecAttrAccessGroup
, agrp
, q
);
190 static bool SecItemDataSourceAppendQueriesForViewName(SecItemDataSourceRef ds
, CFMutableArrayRef queries
, CFStringRef compositeViewName
, CFErrorRef
*error
) {
192 CFStringRef viewName
;
193 bool noTombstones
= CFStringHasSuffix(compositeViewName
, CFSTR("-tomb"));
195 viewName
= CFStringCreateWithSubstring(kCFAllocatorDefault
, compositeViewName
, CFRangeMake(0, CFStringGetLength(compositeViewName
) - 5));
197 viewName
= CFRetain(compositeViewName
);
200 const bool noTKID
= false;
201 const bool allowTKID
= true;
202 if (CFEqual(viewName
, kSOSViewKeychainV0
)) {
203 for (size_t class_ix
= 0; class_ix
< array_size(dsSyncedClassesV0
); ++class_ix
) {
204 SecItemDataSourceAppendQueryWithClass(queries
, dsSyncedClassesV0
[class_ix
], noTombstones
, noTKID
, error
);
206 } else if (CFEqual(viewName
, kSOSViewWiFi
)) {
207 Query
*q
= SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, &genp_class
, noTombstones
, allowTKID
, CFSTR("apple"), error
);
209 query_add_attribute(kSecAttrService
, CFSTR("AirPort"), q
);
211 } else if (CFEqual(viewName
, kSOSViewAutofillPasswords
)) {
212 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, &inet_class
, noTombstones
, allowTKID
, CFSTR("com.apple.cfnetwork"), error
);
213 } else if (CFEqual(viewName
, kSOSViewSafariCreditCards
)) {
214 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, &genp_class
, noTombstones
, allowTKID
, CFSTR("com.apple.safari.credit-cards"), error
);
215 } else if (CFEqual(viewName
, kSOSViewiCloudIdentity
)) {
216 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, &keys_class
, noTombstones
, allowTKID
, CFSTR("com.apple.security.sos"), error
);
217 } else if (CFEqual(viewName
, kSOSViewBackupBagV0
)) {
218 SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, &genp_class
, noTombstones
, allowTKID
, CFSTR("com.apple.sbd"), error
);
219 } else if (CFEqual(viewName
, kSOSViewOtherSyncable
)) {
220 SecItemDataSourceAppendQueryWithClass(queries
, &cert_class
, noTombstones
, allowTKID
, error
);
222 Query
*q1_genp
= SecItemDataSourceAppendQueryWithClassAndAgrp(queries
, &genp_class
, noTombstones
, allowTKID
, CFSTR("apple"), error
);
223 query_add_not_attribute(kSecAttrService
, CFSTR("AirPort"), q1_genp
);
225 Query
*q2_genp
= SecItemDataSourceAppendQueryWithClass(queries
, &genp_class
, noTombstones
, allowTKID
, error
);
226 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("apple"), q2_genp
);
227 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.safari.credit-cards"), q2_genp
);
228 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.sbd"), q2_genp
);
230 Query
*q_inet
= SecItemDataSourceAppendQueryWithClass(queries
, &inet_class
, noTombstones
, allowTKID
, error
);
231 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.cfnetwork"), q_inet
);
233 Query
*q_keys
= SecItemDataSourceAppendQueryWithClass(queries
, &keys_class
, noTombstones
, allowTKID
, error
);
234 query_add_not_attribute(kSecAttrAccessGroup
, CFSTR("com.apple.security.sos"), q_keys
);
236 // All other viewNames should match on the ViewHint attribute.
237 for (size_t class_ix
= 0; class_ix
< array_size(dsSyncedClasses
); ++class_ix
) {
238 SecItemDataSourceAppendQueryWithClassAndViewHint(queries
, dsSyncedClasses
[class_ix
], noTombstones
, allowTKID
, viewName
, error
);
242 CFReleaseSafe(viewName
);
246 static SOSManifestRef
SecItemDataSourceCopyManifestWithViewNameSet(SecItemDataSourceRef ds
, CFSetRef viewNames
, CFErrorRef
*error
) {
247 CFMutableArrayRef queries
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, NULL
);
248 SOSManifestRef manifest
= NULL
;
249 __block
bool ok
= true;
250 CFSetForEach(viewNames
, ^(const void *value
) {
251 CFStringRef viewName
= (CFStringRef
)value
;
252 ok
&= SecItemDataSourceAppendQueriesForViewName(ds
, queries
, viewName
, error
);
255 manifest
= SecItemDataSourceCopyManifestWithQueries(ds
, queries
, error
);
257 CFArrayForEachC(queries
, q
) {
258 CFErrorRef localError
= NULL
;
259 if (!query_destroy(q
, &localError
)) {
260 secerror("query_destroy failed: %@", localError
);
261 CFErrorPropagate(localError
, error
);
262 CFReleaseNull(manifest
);
265 CFReleaseSafe(queries
);
269 // Return the newest object (conflict resolver)
270 static SecDbItemRef
SecItemDataSourceCopyMergedItem(SecDbItemRef item1
, SecDbItemRef item2
, CFErrorRef
*error
) {
271 CFErrorRef localError
= NULL
;
272 SecDbItemRef result
= NULL
;
274 const SecDbAttr
*desc
= SecDbAttrWithKey(SecDbItemGetClass(item1
), kSecAttrModificationDate
, error
);
275 m1
= SecDbItemGetValue(item1
, desc
, &localError
);
276 m2
= SecDbItemGetValue(item2
, desc
, &localError
);
277 if (m1
&& m2
) switch (CFDateCompare(m1
, m2
, NULL
)) {
278 case kCFCompareGreaterThan
:
281 case kCFCompareLessThan
:
284 case kCFCompareEqualTo
:
286 // Return the item with the smallest digest.
287 CFDataRef digest1
= SecDbItemGetSHA1(item1
, &localError
);
288 CFDataRef digest2
= SecDbItemGetSHA1(item2
, &localError
);
289 if (digest1
&& digest2
) switch (CFDataCompare(digest1
, digest2
)) {
290 case kCFCompareGreaterThan
:
291 case kCFCompareEqualTo
:
294 case kCFCompareLessThan
:
297 } else if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
298 if (digest1
) result
= item1
;
299 if (digest2
) result
= item2
;
303 } else if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
304 // If one of the two objects has an unparsable date,
305 // the object with the parsable date wins.
306 if (m1
) result
= item1
;
307 if (m2
) result
= item2
;
311 if (!result
&& error
&& !*error
)
314 CFRelease(localError
);
316 return CFRetainSafe(result
);
320 // MARK: DataSource protocol implementation
323 static CFStringRef
dsGetName(SOSDataSourceRef data_source
) {
324 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
328 static void dsSetNotifyPhaseBlock(SOSDataSourceRef data_source
, dispatch_queue_t queue
, SOSDataSourceNotifyBlock notifyBlock
) {
329 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
330 SecDbSetNotifyPhaseBlock(ds
->db
, queue
, notifyBlock
331 ? ^(SecDbConnectionRef dbconn
, SecDbTransactionPhase phase
, SecDbTransactionSource source
, CFArrayRef changes
) {
332 notifyBlock(&ds
->ds
, (SOSTransactionRef
)dbconn
, phase
, source
, changes
);
337 static SOSManifestRef
dsCopyManifestWithViewNameSet(SOSDataSourceRef data_source
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
338 struct SecItemDataSource
*ds
= (struct SecItemDataSource
*)data_source
;
339 return SecItemDataSourceCopyManifestWithViewNameSet(ds
, viewNameSet
, error
);
342 static bool dsForEachObject(SOSDataSourceRef data_source
, SOSManifestRef manifest
, CFErrorRef
*error
, void (^handle_object
)(CFDataRef key
, SOSObjectRef object
, bool *stop
)) {
343 struct SecItemDataSource
*ds
= (struct SecItemDataSource
*)data_source
;
344 __block
bool result
= true;
345 const SecDbAttr
*sha1Attr
= SecDbClassAttrWithKind(&genp_class
, kSecDbSHA1Attr
, error
);
346 if (!sha1Attr
) return false;
347 bool (^return_attr
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
348 return attr
->kind
== kSecDbRowIdAttr
|| attr
->kind
== kSecDbEncryptedDataAttr
;
350 bool (^use_attr_in_where
)(const SecDbAttr
*attr
) = ^bool (const SecDbAttr
* attr
) {
351 return attr
->kind
== kSecDbSHA1Attr
;
353 Query
*select_queries
[array_size(dsSyncedClasses
)] = {};
354 CFStringRef select_sql
[array_size(dsSyncedClasses
)] = {};
355 sqlite3_stmt
*select_stmts
[array_size(dsSyncedClasses
)] = {};
357 __block Query
**queries
= select_queries
;
358 __block CFStringRef
*sqls
= select_sql
;
359 __block sqlite3_stmt
**stmts
= select_stmts
;
361 result
&= SecDbPerformRead(ds
->db
, error
, ^(SecDbConnectionRef dbconn
) {
363 for (size_t class_ix
= 0; class_ix
< array_size(dsSyncedClasses
); ++class_ix
) {
365 && (queries
[class_ix
] = query_create(dsSyncedClasses
[class_ix
], NULL
, error
))
366 && (sqls
[class_ix
] = SecDbItemCopySelectSQL(queries
[class_ix
], return_attr
, use_attr_in_where
, NULL
))
367 && (stmts
[class_ix
] = SecDbCopyStmt(dbconn
, sqls
[class_ix
], NULL
, error
)));
370 if (result
) SOSManifestForEach(manifest
, ^(CFDataRef key
, bool *stop
) {
371 __block SecDbItemRef item
= NULL
;
372 for (size_t class_ix
= 0; result
&& !item
&& class_ix
< array_size(dsSyncedClasses
); ++class_ix
) {
373 CFDictionarySetValue(queries
[class_ix
]->q_item
, sha1Attr
->name
, key
);
374 result
= (SecDbItemSelectBind(queries
[class_ix
], stmts
[class_ix
], error
, use_attr_in_where
, NULL
) && SecDbStep(dbconn
, stmts
[class_ix
], error
, ^(bool *unused_stop
) {
375 item
= SecDbItemCreateWithStatement(kCFAllocatorDefault
, queries
[class_ix
]->q_class
, stmts
[class_ix
], KEYBAG_DEVICE
, error
, return_attr
);
376 })) && SecDbReset(stmts
[class_ix
], error
);
378 handle_object(key
, (SOSObjectRef
)item
, stop
);
383 for (size_t class_ix
= 0; class_ix
< array_size(dsSyncedClasses
); ++class_ix
) {
384 result
&= SecDbReleaseCachedStmt(dbconn
, sqls
[class_ix
], stmts
[class_ix
], error
);
385 CFReleaseSafe(sqls
[class_ix
]);
386 result
&= query_destroy(queries
[class_ix
], error
);
392 static bool dsRelease(SOSDataSourceRef data_source
, CFErrorRef
*error
) {
393 // We never release our dataSource since it's tracking changes
394 // to the keychain for the engine and its peers.
398 static SOSObjectRef
objectCreateWithPropertyList(CFDictionaryRef plist
, CFErrorRef
*error
) {
399 SecDbItemRef item
= NULL
;
400 const SecDbClass
*class = NULL
;
401 CFTypeRef cname
= CFDictionaryGetValue(plist
, kSecClass
);
403 class = kc_class_with_name(cname
);
405 item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, class, plist
, KEYBAG_DEVICE
, error
);
407 SecError(errSecNoSuchClass
, error
, CFSTR("can find class named: %@"), cname
);
410 SecError(errSecItemClassMissing
, error
, CFSTR("query missing %@ attribute"), kSecClass
);
412 return (SOSObjectRef
)item
;
415 static CFDataRef
copyObjectDigest(SOSObjectRef object
, CFErrorRef
*error
) {
416 SecDbItemRef item
= (SecDbItemRef
) object
;
417 CFDataRef digest
= SecDbItemGetSHA1(item
, error
);
418 CFRetainSafe(digest
);
422 static CFDataRef
objectCopyPrimaryKey(SOSObjectRef object
, CFErrorRef
*error
) {
423 SecDbItemRef item
= (SecDbItemRef
) object
;
424 CFDataRef pk
= SecDbItemGetPrimaryKey(item
, error
);
429 static CFDictionaryRef
objectCopyPropertyList(SOSObjectRef object
, CFErrorRef
*error
) {
430 SecDbItemRef item
= (SecDbItemRef
) object
;
431 CFMutableDictionaryRef cryptoDataDict
= SecDbItemCopyPListWithMask(item
, kSecDbInCryptoDataFlag
, error
);
432 CFMutableDictionaryRef authDataDict
= SecDbItemCopyPListWithMask(item
, kSecDbInAuthenticatedDataFlag
, error
);
434 if (cryptoDataDict
) {
436 CFDictionaryForEach(authDataDict
, ^(const void *key
, const void *value
) {
437 CFDictionarySetValue(cryptoDataDict
, key
, value
);
440 CFDictionaryAddValue(cryptoDataDict
, kSecClass
, SecDbItemGetClass(item
)->name
);
443 CFReleaseSafe(authDataDict
);
444 return cryptoDataDict
;
447 static bool dsWith(SOSDataSourceRef data_source
, CFErrorRef
*error
, SOSDataSourceTransactionSource source
, void(^transaction
)(SOSTransactionRef txn
, bool *commit
)) {
448 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
449 __block
bool ok
= true;
450 ok
&= SecDbPerformWrite(ds
->db
, error
, ^(SecDbConnectionRef dbconn
) {
451 ok
&= SecDbTransaction(dbconn
,
452 source
== kSOSDataSourceAPITransaction
? kSecDbExclusiveTransactionType
: kSecDbExclusiveRemoteTransactionType
,
453 error
, ^(bool *commit
) {
454 transaction((SOSTransactionRef
)dbconn
, commit
);
460 static SOSMergeResult
dsMergeObject(SOSTransactionRef txn
, SOSObjectRef peersObject
, SOSObjectRef
*mergedObject
, CFErrorRef
*error
) {
461 SecDbConnectionRef dbconn
= (SecDbConnectionRef
)txn
;
462 SecDbItemRef peersItem
= (SecDbItemRef
)peersObject
;
463 __block SOSMergeResult mr
= kSOSMergeFailure
;
464 __block SecDbItemRef mergedItem
= NULL
;
465 __block SecDbItemRef replacedItem
= NULL
;
466 if (!peersItem
|| !dbconn
|| !SecDbItemSetKeybag(peersItem
, KEYBAG_DEVICE
, error
)) return mr
;
467 if (SecDbItemInsertOrReplace(peersItem
, dbconn
, error
, ^(SecDbItemRef myItem
, SecDbItemRef
*replace
) {
468 // An item with the same primary key as dbItem already exists in the the database. That item is old_item.
469 // Let the conflict resolver choose which item to keep.
470 mergedItem
= SecItemDataSourceCopyMergedItem(peersItem
, myItem
, error
);
471 if (mergedObject
) *mergedObject
= (SOSObjectRef
)CFRetain(mergedItem
);
472 if (!mergedItem
) return;
473 if (CFEqual(mergedItem
, myItem
)) {
474 // Conflict resolver choose my (local) item
475 secnotice("ds", "Conflict resolver choose my (local) item: %@", myItem
);
476 mr
= kSOSMergeLocalObject
;
478 CFRetainAssign(replacedItem
, myItem
);
479 *replace
= CFRetainSafe(mergedItem
);
480 if (CFEqual(mergedItem
, peersItem
)) {
481 // Conflict resolver choose peers item
482 secnotice("ds", "Conflict resolver choose peers item: %@", peersItem
);
483 mr
= kSOSMergePeersObject
;
485 // Conflict resolver created a new item; return it to our caller
486 secnotice("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem
);
487 mr
= kSOSMergeCreatedObject
;
491 if (mr
== kSOSMergeFailure
)
493 secnotice("ds", "kSOSMergeFailure => kSOSMergePeersObject");
494 mr
= kSOSMergePeersObject
;
498 if (error
&& *error
&& mr
!= kSOSMergeFailure
)
499 CFReleaseNull(*error
);
501 CFReleaseSafe(mergedItem
);
502 CFReleaseSafe(replacedItem
);
507 Truthy backup format is a dictionary from sha1 => item.
508 Each item has class, hash and item data.
510 TODO: sha1 is included as binary blob to avoid parsing key.
513 kSecBackupIndexHash
= 0,
514 kSecBackupIndexClass
,
518 static const void *kSecBackupKeys
[] = {
519 [kSecBackupIndexHash
] = kSecItemBackupHashKey
,
520 [kSecBackupIndexClass
] = kSecItemBackupClassKey
,
521 [kSecBackupIndexData
] = kSecItemBackupDataKey
524 static CFDictionaryRef
objectCopyBackup(SOSObjectRef object
, uint64_t handle
, CFErrorRef
*error
) {
525 const void *values
[array_size(kSecBackupKeys
)];
526 SecDbItemRef item
= (SecDbItemRef
)object
;
527 CFDictionaryRef backup_item
= NULL
;
529 if ((values
[kSecBackupIndexHash
] = SecDbItemGetSHA1(item
, error
))) {
530 if ((values
[kSecBackupIndexData
] = SecDbItemCopyEncryptedDataToBackup(item
, handle
, error
))) {
531 values
[kSecBackupIndexClass
] = SecDbItemGetClass(item
)->name
;
532 backup_item
= CFDictionaryCreate(kCFAllocatorDefault
, kSecBackupKeys
, values
, array_size(kSecBackupKeys
), &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
533 CFRelease(values
[kSecBackupIndexData
]);
540 static CFDataRef
dsCopyStateWithKey(SOSDataSourceRef data_source
, CFStringRef key
, CFStringRef pdmn
, CFErrorRef
*error
) {
541 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
542 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
543 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
544 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
545 kSecAttrAccount
, key
,
546 kSecAttrService
, dataSourceID
,
547 kSecAttrAccessible
, pdmn
,
548 kSecAttrSynchronizable
, kCFBooleanFalse
,
550 CFReleaseSafe(dataSourceID
);
551 __block CFDataRef data
= NULL
;
552 SecDbQueryRef query
= query_create(&genp_class
, dict
, error
);
554 if (query
->q_item
) CFReleaseSafe(query
->q_item
);
555 query
->q_item
= dict
;
556 SecDbPerformRead(ds
->db
, error
, ^(SecDbConnectionRef dbconn
) {
557 SecDbItemSelect(query
, dbconn
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
558 return CFDictionaryContainsKey(dict
, attr
->name
);
559 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
560 secnotice("ds", "found item for key %@@%@", key
, pdmn
);
561 data
= CFRetainSafe(SecDbItemGetValue(item
, &v6v_Data
, error
));
564 query_destroy(query
, error
);
568 if (!data
) secnotice("ds", "failed to load %@@%@ state: %@", key
, pdmn
, error
? *error
: NULL
);
572 static CFDataRef
dsCopyItemDataWithKeys(SOSDataSourceRef data_source
, CFDictionaryRef keys
, CFErrorRef
*error
) {
575 kSecAttrAccessGroup ==> CFSTR("com.apple.sbd")
576 kSecAttrAccessible ==> kSecAttrAccessibleWhenUnlocked
577 kSecAttrAccount ==> CFSTR("SecureBackupPublicKeybag")
578 kSecAttrService ==> CFSTR("SecureBackupService")
580 CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
581 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
582 kSecAttrAccount, account,
583 kSecAttrService, service,
584 kSecAttrAccessible, pdmn,
585 kSecAttrSynchronizable, kCFBooleanTrue,
589 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
590 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, keys
);
591 __block CFDataRef data
= NULL
;
592 SecDbQueryRef query
= query_create(&genp_class
, dict
, error
);
594 if (query
->q_item
) CFReleaseSafe(query
->q_item
);
595 query
->q_item
= dict
;
596 SecDbPerformRead(ds
->db
, error
, ^(SecDbConnectionRef dbconn
) {
597 SecDbItemSelect(query
, dbconn
, error
, NULL
, ^bool(const SecDbAttr
*attr
) {
598 return CFDictionaryContainsKey(dict
, attr
->name
);
599 }, NULL
, NULL
, ^(SecDbItemRef item
, bool *stop
) {
600 secnotice("ds", "found item for keys %@", keys
);
601 data
= CFRetainSafe(SecDbItemGetValue(item
, &v6v_Data
, error
));
604 query_destroy(query
, error
);
608 if (!data
) secnotice("ds", "failed to load item %@: %@", keys
, error
? *error
: NULL
);
612 static bool dsSetStateWithKey(SOSDataSourceRef data_source
, SOSTransactionRef txn
, CFStringRef key
, CFStringRef pdmn
, CFDataRef state
, CFErrorRef
*error
) {
613 SecItemDataSourceRef ds
= (SecItemDataSourceRef
)data_source
;
614 CFStringRef dataSourceID
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("SOSDataSource-%@"), ds
->name
);
615 CFMutableDictionaryRef dict
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
616 kSecAttrAccessGroup
, kSOSInternalAccessGroup
,
617 kSecAttrAccount
, key
,
618 kSecAttrService
, dataSourceID
,
619 kSecAttrAccessible
, pdmn
,
620 kSecAttrSynchronizable
, kCFBooleanFalse
,
621 kSecValueData
, state
,
623 CFReleaseSafe(dataSourceID
);
624 SecDbItemRef item
= SecDbItemCreateWithAttributes(kCFAllocatorDefault
, &genp_class
, dict
, KEYBAG_DEVICE
, error
);
625 SOSMergeResult mr
= dsMergeObject(txn
, (SOSObjectRef
)item
, NULL
, error
);
626 if (mr
== kSOSMergeFailure
) secerror("failed to save %@@%@ state: %@", key
, pdmn
, error
? *error
: NULL
);
629 return mr
!= kSOSMergeFailure
;
632 static bool dsRestoreObject(SOSTransactionRef txn
, uint64_t handle
, CFDictionaryRef item
, CFErrorRef
*error
) {
633 CFStringRef item_class
= CFDictionaryGetValue(item
, kSecItemBackupClassKey
);
634 CFDataRef data
= CFDictionaryGetValue(item
, kSecItemBackupDataKey
);
635 const SecDbClass
*dbclass
= NULL
;
637 if (!item_class
|| !data
)
638 return SecError(errSecDecode
, error
, CFSTR("no class or data in object"));
640 dbclass
= kc_class_with_name(item_class
);
642 return SecError(errSecDecode
, error
, CFSTR("no such class %@; update kc_class_with_name "), item_class
);
644 SecDbItemRef dbitem
= SecDbItemCreateWithEncryptedData(kCFAllocatorDefault
, dbclass
, data
, (keybag_handle_t
)handle
, error
);
645 bool ok
= dbitem
&& (dsMergeObject(txn
, (SOSObjectRef
)dbitem
, NULL
, error
) != kSOSMergeFailure
);
646 CFReleaseSafe(dbitem
);
650 SOSDataSourceRef
SecItemDataSourceCreate(SecDbRef db
, CFStringRef name
, CFErrorRef
*error
) {
651 SecItemDataSourceRef ds
= calloc(1, sizeof(struct SecItemDataSource
));
652 ds
->ds
.dsGetName
= dsGetName
;
653 ds
->ds
.dsSetNotifyPhaseBlock
= dsSetNotifyPhaseBlock
;
654 ds
->ds
.dsCopyManifestWithViewNameSet
= dsCopyManifestWithViewNameSet
;
655 ds
->ds
.dsCopyStateWithKey
= dsCopyStateWithKey
;
656 ds
->ds
.dsCopyItemDataWithKeys
= dsCopyItemDataWithKeys
;
658 ds
->ds
.dsForEachObject
= dsForEachObject
;
659 ds
->ds
.dsWith
= dsWith
;
660 ds
->ds
.dsRelease
= dsRelease
;
662 ds
->ds
.dsMergeObject
= dsMergeObject
;
663 ds
->ds
.dsSetStateWithKey
= dsSetStateWithKey
;
664 ds
->ds
.dsRestoreObject
= dsRestoreObject
;
666 // Object field accessors
667 ds
->ds
.objectCopyDigest
= copyObjectDigest
;
668 ds
->ds
.objectCopyPrimaryKey
= objectCopyPrimaryKey
;
670 // Object encode and decode.
671 ds
->ds
.objectCreateWithPropertyList
= objectCreateWithPropertyList
;
672 ds
->ds
.objectCopyPropertyList
= objectCopyPropertyList
;
673 ds
->ds
.objectCopyBackup
= objectCopyBackup
;
675 ds
->db
= CFRetainSafe(db
);
676 ds
->name
= CFRetainSafe(name
);
678 // Do this after the ds is fully setup so the engine can query us right away.
679 ds
->ds
.engine
= SOSEngineCreate(&ds
->ds
, error
);
680 if (!ds
->ds
.engine
) {
687 static CFStringRef
SecItemDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory
)
689 // This is the name of the v0 datasource, a.k.a. "ak"
690 return kSecAttrAccessibleWhenUnlocked
;
693 struct SecItemDataSourceFactory
{
694 struct SOSDataSourceFactory factory
;
695 CFMutableDictionaryRef dsCache
;
696 dispatch_queue_t queue
;
700 static SOSDataSourceRef
SecItemDataSourceFactoryCopyDataSource(SOSDataSourceFactoryRef factory
, CFStringRef dataSourceName
, CFErrorRef
*error
)
702 struct SecItemDataSourceFactory
*f
= (struct SecItemDataSourceFactory
*)factory
;
703 __block SOSDataSourceRef dataSource
= NULL
;
704 dispatch_sync(f
->queue
, ^{
705 dataSource
= (SOSDataSourceRef
)CFDictionaryGetValue(f
->dsCache
, dataSourceName
);
707 dataSource
= (SOSDataSourceRef
)SecItemDataSourceCreate(f
->db
, dataSourceName
, error
);
708 CFDictionarySetValue(f
->dsCache
, dataSourceName
, dataSource
);
714 static void SecItemDataSourceFactoryDispose(SOSDataSourceFactoryRef factory
)
716 // Nothing to do here.
719 static void SecItemDataSourceFactoryCircleChanged(SOSDataSourceFactoryRef factory
, CFStringRef myPeerID
, CFArrayRef trustedPeerIDs
, CFArrayRef untrustedPeerIDs
) {
720 CFStringRef dsName
= SOSDataSourceFactoryCopyName(factory
);
721 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(factory
, dsName
, NULL
);
723 SOSEngineCircleChanged(engine
, myPeerID
, trustedPeerIDs
, untrustedPeerIDs
);
724 CFReleaseSafe(dsName
);
727 // Fire up the SOSEngines so they can
728 static bool SOSDataSourceFactoryStartYourEngines(SOSDataSourceFactoryRef factory
) {
730 CFStringRef dsName
= SOSDataSourceFactoryCopyName(factory
);
731 CFErrorRef localError
= NULL
;
732 SOSDataSourceRef ds
= SOSDataSourceFactoryCreateDataSource(factory
, dsName
, &localError
);
734 secerror("create_datasource %@ failed %@", dsName
, localError
);
735 CFReleaseNull(localError
);
736 SOSDataSourceRelease(ds
, &localError
);
737 CFReleaseNull(localError
);
738 CFReleaseNull(dsName
);
742 static SOSDataSourceFactoryRef
SecItemDataSourceFactoryCreate(SecDbRef db
) {
743 struct SecItemDataSourceFactory
*dsf
= calloc(1, sizeof(struct SecItemDataSourceFactory
));
744 dsf
->factory
.copy_name
= SecItemDataSourceFactoryCopyName
;
745 dsf
->factory
.create_datasource
= SecItemDataSourceFactoryCopyDataSource
;
746 dsf
->factory
.release
= SecItemDataSourceFactoryDispose
;
747 dsf
->factory
.circle_changed
= SecItemDataSourceFactoryCircleChanged
;
749 dsf
->dsCache
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
750 dsf
->queue
= dispatch_queue_create("dsf queue", DISPATCH_QUEUE_SERIAL
);
751 dsf
->db
= CFRetainSafe(db
);
752 if (!SOSDataSourceFactoryStartYourEngines(&dsf
->factory
))
753 secerror("Failed to start engines, gonna lose the race.");
754 return &dsf
->factory
;
757 SOSDataSourceFactoryRef
SecItemDataSourceFactoryGetShared(SecDbRef db
) {
758 static dispatch_once_t sDSFQueueOnce
;
759 static dispatch_queue_t sDSFQueue
;
760 static CFMutableDictionaryRef sDSTable
= NULL
;
762 dispatch_once(&sDSFQueueOnce
, ^{
763 sDSFQueue
= dispatch_queue_create("dataSourceFactory queue", DISPATCH_QUEUE_SERIAL
);
766 __block SOSDataSourceFactoryRef result
= NULL
;
767 dispatch_sync(sDSFQueue
, ^{
768 CFStringRef dbPath
= SecDbGetPath(db
);
770 result
= (SOSDataSourceFactoryRef
) CFDictionaryGetValue(sDSTable
, dbPath
);
772 sDSTable
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
776 result
= SecItemDataSourceFactoryCreate(db
);
778 CFDictionaryAddValue(sDSTable
, dbPath
, result
);
785 // TODO: These should move to SecItemServer.c
787 void SecItemServerAppendItemDescription(CFMutableStringRef desc
, CFDictionaryRef object
) {
788 SOSObjectRef item
= objectCreateWithPropertyList(object
, NULL
);
790 CFStringRef itemDesc
= CFCopyDescription(item
);
792 CFStringAppend(desc
, itemDesc
);
793 CFReleaseSafe(itemDesc
);
799 SOSManifestRef
SOSCreateManifestWithBackup(CFDictionaryRef backup
, CFErrorRef
*error
)
801 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
803 CFDictionaryForEach(backup
, ^void (const void * key
, const void * value
) {
804 if (isDictionary(value
)) {
805 /* converting key back to binary blob is horrible */
806 CFDataRef sha1
= CFDictionaryGetValue(value
, kSecItemBackupHashKey
);
807 if (isData(sha1
) && CFDataGetLength(sha1
) == CCSHA1_OUTPUT_SIZE
)
808 SOSDigestVectorAppend(&dv
, CFDataGetBytePtr(sha1
));
812 SOSManifestRef manifest
= SOSManifestCreateWithDigestVector(&dv
, error
);
813 SOSDigestVectorFree(&dv
);