]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/SecItemDataSource.c
Security-59754.60.13.tar.gz
[apple/security.git] / keychain / securityd / SecItemDataSource.c
1 /*
2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * SecItemDataSource.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
27 passwords.)
28 */
29
30 #include "keychain/securityd/SecItemDataSource.h"
31
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>
46
47 /*
48 *
49 *
50 * SecItemDataSource
51 *
52 *
53 */
54 typedef struct SecItemDataSource *SecItemDataSourceRef;
55
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.
60 };
61
62
63 static const SecDbClass *dsSyncedClassesV0Ptrs[] = {
64 NULL,
65 NULL,
66 NULL,
67 };
68 static size_t dsSyncedClassesV0Size = (array_size(dsSyncedClassesV0Ptrs));
69
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();
76 });
77 return dsSyncedClassesV0Ptrs;
78 }
79
80
81 static const SecDbClass *dsSyncedClassesPtrs[] = {
82 NULL,
83 NULL,
84 NULL,
85 NULL,
86 };
87 static const size_t dsSyncedClassesSize = array_size(dsSyncedClassesPtrs);
88
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();
96 });
97 return dsSyncedClassesPtrs;
98 }
99
100
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;
109 };
110 CFStringRef sql = SecDbItemCopySelectSQL(query, return_attr, use_attr_in_where, add_where_sql);
111 if (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); }));
115 });
116 CFRelease(sql);
117 } else {
118 ok = false;
119 }
120 return ok;
121 }
122
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;
128 Query *q;
129 bool ok = true;
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);
138 } else {
139 SOSDigestVectorAppend(&dv, digest);
140 }
141 }))) {
142 secerror("SecDbItemSelectSHA1 failed: %@", localError);
143 break;
144 }
145 }
146 if (ok) {
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);
150 }
151 SOSDigestVectorFree(&dv);
152 return ok;
153 })) {
154 CFReleaseSafe(manifest);
155 }
156 CFErrorPropagate(localError, error);
157 return manifest;
158 }
159
160 static Query *SecItemDataSourceAppendQuery(CFMutableArrayRef queries, const SecDbClass *qclass, bool noTombstones, CFErrorRef *error) {
161 Query *q = query_create(qclass, NULL, NULL, NULL, error);
162 if (q) {
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);
173
174 if (noTombstones) {
175 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
176 }
177
178 CFArrayAppendValue(queries, q);
179 }
180 return q;
181 }
182
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);
185 if (q) {
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);
195 } else {
196 CFTypeRef value = SecDbAttrCopyDefaultValue(attr, &q->q_error);
197 if (value) {
198 query_add_attribute_with_desc(attr, value, q);
199 CFRelease(value);
200 }
201 }
202 }
203 }
204 }
205 }
206 return q;
207 }
208
209 static Query *SecItemDataSourceAppendQueryWithClass(CFMutableArrayRef queries, const SecDbClass *qclass, bool noTombstones, bool allowTkid, CFErrorRef *error) {
210 return SecItemDataSourceAppendQueryWithClassAndViewHint(queries, qclass, noTombstones, allowTkid, NULL, error);
211 }
212
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);
215 if (q && agrp) {
216 query_add_attribute(kSecAttrAccessGroup, agrp, q);
217 }
218 return q;
219 }
220
221 static bool SecItemDataSourceAppendQueriesForViewName(SecItemDataSourceRef ds, CFMutableArrayRef queries, CFStringRef compositeViewName, CFErrorRef *error) {
222 bool ok = true;
223 CFStringRef viewName;
224 bool noTombstones = CFStringHasSuffix(compositeViewName, CFSTR("-tomb"));
225 if (noTombstones) {
226 viewName = CFStringCreateWithSubstring(kCFAllocatorDefault, compositeViewName, CFRangeMake(0, CFStringGetLength(compositeViewName) - 5));
227 } else {
228 viewName = CFRetain(compositeViewName);
229 }
230
231 // short-circuit for CKKS-handled views here
232 if(!SOSViewInSOSSystem(viewName)) {
233 CFReleaseSafe(viewName);
234 return ok;
235 }
236
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);
242 }
243 } else if (CFEqual(viewName, kSOSViewWiFi)) {
244 Query *q = SecItemDataSourceAppendQueryWithClassAndAgrp(queries, genp_class(), noTombstones, allowTKID, CFSTR("apple"), error);
245 if (q) {
246 query_add_attribute(kSecAttrService, CFSTR("AirPort"), q);
247 }
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);
258
259 Query *q1_genp = SecItemDataSourceAppendQueryWithClassAndAgrp(queries, genp_class(), noTombstones, allowTKID, CFSTR("apple"), error);
260 query_add_not_attribute(kSecAttrService, CFSTR("AirPort"), q1_genp);
261
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);
266
267 Query *q_inet = SecItemDataSourceAppendQueryWithClass(queries, inet_class(), noTombstones, allowTKID, error);
268 query_add_not_attribute(kSecAttrAccessGroup, CFSTR("com.apple.cfnetwork"), q_inet);
269
270 Query *q_keys = SecItemDataSourceAppendQueryWithClass(queries, keys_class(), noTombstones, allowTKID, error);
271 query_add_not_attribute(kSecAttrAccessGroup, CFSTR("com.apple.security.sos"), q_keys);
272 } else {
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);
276 }
277 }
278
279 CFReleaseSafe(viewName);
280 return ok;
281 }
282
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);
290 });
291 if (ok)
292 manifest = SecItemDataSourceCopyManifestWithQueries(ds, queries, error);
293 Query *q;
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);
300 }
301 }
302 CFReleaseSafe(queries);
303 return manifest;
304 }
305
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;
311 CFDateRef m1, m2;
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:
317 result = item1;
318 break;
319 case kCFCompareLessThan:
320 result = item2;
321 break;
322 case kCFCompareEqualTo:
323 {
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:
330 result = item2;
331 break;
332 case kCFCompareLessThan:
333 result = item1;
334 break;
335 } else if (SecErrorGetOSStatus(localError) == errSecDecode) {
336 if (digest1) result = item1;
337 if (digest2) result = item2;
338 }
339 break;
340 }
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;
346 }
347
348 if (localError) {
349 if (!result && error && !*error)
350 *error = localError;
351 else
352 CFRelease(localError);
353 }
354
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);
362 }
363 }
364
365 SecDbForEachAttrWithMask(SecDbItemGetClass(result), attr, kSecDbSyncSOSCannotSyncFlag) {
366 SecItemPreserveAttribute(result, item2, attr);
367 }
368 }
369
370 return CFRetainSafe(result);
371 }
372
373 //
374 // MARK: DataSource protocol implementation
375 //
376
377 static CFStringRef dsGetName(SOSDataSourceRef data_source) {
378 SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
379 return ds->name;
380 }
381
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)
385 {
386 notifyBlock(&ds->ds, (SOSTransactionRef)dbconn, phase, source, changes);
387 });
388 }
389
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);
393 }
394
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;
402 };
403 bool (^use_attr_in_where)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
404 return attr->kind == kSecDbSHA1Attr;
405 };
406 Query *select_queries[dsSyncedClassesSize] = {};
407 CFStringRef select_sql[dsSyncedClassesSize] = {};
408 sqlite3_stmt *select_stmts[dsSyncedClassesSize] = {};
409
410 __block Query **queries = select_queries;
411 __block CFStringRef *sqls = select_sql;
412 __block sqlite3_stmt **stmts = select_stmts;
413
414 bool (^readBlock)(SecDbConnectionRef dbconn) = ^bool(SecDbConnectionRef dbconn)
415 {
416 // Setup
417 for (size_t class_ix = 0; class_ix < dsSyncedClassesSize; ++class_ix) {
418 result = (result
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)));
422 }
423
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);
429 if (result) {
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);
432 });
433 }
434 if (result)
435 result &= SecDbReset(stmts[class_ix], error);
436 }
437 handle_object(key, (SOSObjectRef)item, stop);
438 CFReleaseSafe(item);
439 });
440
441 // Cleanup
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);
447 }
448
449 return true;
450 };
451
452 if (txn) {
453 readBlock((SecDbConnectionRef)txn);
454 } else {
455 result &= kc_with_custom_db(false, true, ds->db, error, readBlock);
456 }
457
458 return result;
459 }
460
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.
464 return true;
465 }
466
467 static SOSObjectRef objectCreateWithPropertyList(CFDictionaryRef plist, CFErrorRef *error) {
468 SecDbItemRef item = NULL;
469 const SecDbClass *class = NULL;
470 CFTypeRef cname = CFDictionaryGetValue(plist, kSecClass);
471 if (cname) {
472 class = kc_class_with_name(cname);
473 if (class) {
474 item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, class, plist, KEYBAG_DEVICE, error);
475 } else {
476 SecError(errSecNoSuchClass, error, CFSTR("can find class named: %@"), cname);
477 }
478 } else {
479 SecError(errSecItemClassMissing, error, CFSTR("query missing %@ attribute"), kSecClass);
480 }
481 return (SOSObjectRef)item;
482 }
483
484 static CFDataRef copyObjectDigest(SOSObjectRef object, CFErrorRef *error) {
485 SecDbItemRef item = (SecDbItemRef) object;
486 CFDataRef digest = SecDbItemGetSHA1(item, error);
487 CFRetainSafe(digest);
488 return digest;
489 }
490
491 static CFDateRef copyObjectModDate(SOSObjectRef object, CFErrorRef *error) {
492 SecDbItemRef item = (SecDbItemRef) object;
493 CFDateRef modDate = SecDbItemGetValueKind(item, kSecDbModificationDateAttr, NULL);
494 CFRetainSafe(modDate);
495 return modDate;
496 }
497
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);
503
504 if (cryptoDataDict) {
505 if (authDataDict) {
506 CFDictionaryForEach(authDataDict, ^(const void *key, const void *value) {
507 CFDictionarySetValue(cryptoDataDict, key, value);
508 });
509 }
510 if (secretDataDict) {
511 CFDictionaryForEach(secretDataDict, ^(const void* key, const void* value) {
512 CFDictionarySetValue(cryptoDataDict, key, value);
513 });
514 }
515 CFDictionaryAddValue(cryptoDataDict, kSecClass, SecDbItemGetClass(item)->name);
516 }
517
518 CFReleaseNull(secretDataDict);
519 CFReleaseNull(authDataDict);
520 return cryptoDataDict;
521 }
522
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) {
530 if (onCommitQueue) {
531 SecDbPerformOnCommitQueue(dbconn, false, ^{
532 transaction((SOSTransactionRef)dbconn, commit);
533 });
534 } else {
535 transaction((SOSTransactionRef)dbconn, commit);
536 }
537 });
538 });
539 return ok;
540 }
541
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);
548 });
549 return true;
550 });
551 return ok;
552 }
553
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;
562
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;
569 }
570
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);
576 if (!mergedItem) {
577 mr = kSOSMergeFailure;
578 return; // from block
579 }
580 if (mergedObject)
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);
586 else
587 secnotice("ds", "Conflict resolver chose my (local) item: %@", myItem);
588 mr = kSOSMergeLocalObject;
589 } else {
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);
596 else
597 secnotice("ds", "Conflict resolver chose peers item: %@", peersItem);
598 mr = kSOSMergePeersObject;
599 } else {
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);
603 else
604 secnotice("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem);
605 mr = kSOSMergeCreatedObject;
606 }
607 }
608 });
609
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;
615
616 // Report only if we had an error, too. Shouldn't happen in practice.
617 if (localError) {
618 secnotice("ds", "kSOSMergeFailure => kSOSMergePeersObject, %@", localError);
619 CFReleaseSafe(localError);
620 }
621 }
622
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);
629 localError = NULL;
630 }
631 }
632
633 CFReleaseSafe(mergedItem);
634 CFReleaseSafe(replacedItem);
635 CFReleaseSafe(localError);
636 return mr;
637 }
638
639 /*
640 Truthy backup format is a dictionary from sha1 => item.
641 Each item has class, hash and item data.
642
643 TODO: sha1 is included as binary blob to avoid parsing key.
644 */
645 enum {
646 kSecBackupIndexHash = 0,
647 kSecBackupIndexClass,
648 kSecBackupIndexData,
649 };
650
651 static const void *kSecBackupKeys[] = {
652 [kSecBackupIndexHash] = kSecItemBackupHashKey,
653 [kSecBackupIndexClass] = kSecItemBackupClassKey,
654 [kSecBackupIndexData] = kSecItemBackupDataKey
655 };
656
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;
661
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]);
667 }
668 }
669
670 return backup_item;
671 }
672
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,
682 NULL);
683 CFReleaseSafe(dataSourceID);
684 __block CFDataRef data = NULL;
685 SecDbQueryRef query = query_create(genp_class(), NULL, dict, NULL, error);
686 if (query) {
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));
695 });
696 };
697 if (txn) {
698 read_it((SecDbConnectionRef) txn);
699 } else {
700 kc_with_custom_db(false, true, ds->db, error, read_it);
701 }
702 query_destroy(query, error);
703 } else {
704 CFReleaseSafe(dict);
705 }
706 if (!data) secnotice("ds", "failed to load %@@%@ state: %@", key, pdmn, error ? *error : NULL);
707 return data;
708 }
709
710 static CFDataRef dsCopyItemDataWithKeys(SOSDataSourceRef data_source, CFDictionaryRef keys, CFErrorRef *error) {
711 /*
712 Values for V0 are:
713 kSecAttrAccessGroup ==> CFSTR("com.apple.sbd")
714 kSecAttrAccessible ==> kSecAttrAccessibleWhenUnlocked
715 kSecAttrAccount ==> CFSTR("SecureBackupPublicKeybag")
716 kSecAttrService ==> CFSTR("SecureBackupService")
717
718 CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
719 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
720 kSecAttrAccount, account,
721 kSecAttrService, service,
722 kSecAttrAccessible, pdmn,
723 kSecAttrSynchronizable, kCFBooleanTrue,
724 NULL);
725 */
726
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);
731 if (query) {
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));
740 });
741 });
742 query_destroy(query, error);
743 } else {
744 CFReleaseSafe(dict);
745 }
746 if (!data) secnotice("ds", "failed to load item %@: %@", keys, error ? *error : NULL);
747 return data;
748 }
749
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,
760 NULL);
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);
765 CFReleaseSafe(item);
766 CFReleaseSafe(dict);
767 return mr != kSOSMergeFailure;
768 }
769
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,
779 NULL);
780 CFReleaseSafe(dataSourceID);
781 SecDbItemRef item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, genp_class(), dict, KEYBAG_DEVICE, error);
782 bool ok = SecDbItemDoDeleteSilently(item, (SecDbConnectionRef)txn, error);
783 CFReleaseNull(dict);
784 CFReleaseSafe(item);
785 return ok;
786 }
787
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;
792
793 if (!item_class || !data)
794 return SecError(errSecDecode, error, CFSTR("no class or data in object"));
795
796 dbclass = kc_class_with_name(item_class);
797 if (!dbclass)
798 return SecError(errSecDecode, error, CFSTR("no such class %@; update kc_class_with_name "), item_class);
799
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);
803 return ok;
804 }
805
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;
813
814 ds->ds.dsForEachObject = dsForEachObject;
815 ds->ds.dsWith = dsWith;
816 ds->ds.dsReadWith = dsReadWith;
817 ds->ds.dsRelease = dsRelease;
818
819 ds->ds.dsMergeObject = dsMergeObject;
820 ds->ds.dsSetStateWithKey = dsSetStateWithKey;
821 ds->ds.dsDeleteStateWithKey = dsDeleteStateWithKey;
822 ds->ds.dsRestoreObject = dsRestoreObject;
823
824 // Object field accessors
825 ds->ds.objectCopyDigest = copyObjectDigest;
826 ds->ds.objectCopyModDate = copyObjectModDate;
827
828 // Object encode and decode.
829 ds->ds.objectCreateWithPropertyList = objectCreateWithPropertyList;
830 ds->ds.objectCopyPropertyList = objectCopyPropertyList;
831 ds->ds.objectCopyBackup = objectCopyBackup;
832
833 ds->db = CFRetainSafe(db);
834 ds->name = CFRetainSafe(name);
835
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) {
839 free(ds);
840 ds = NULL;
841 }
842 return &ds->ds;
843 }
844
845 static CFStringRef SecItemDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory)
846 {
847 // This is the name of the v0 datasource, a.k.a. "ak"
848 return kSecAttrAccessibleWhenUnlocked;
849 }
850
851 struct SecItemDataSourceFactory {
852 struct SOSDataSourceFactory factory;
853 CFMutableDictionaryRef dsCache;
854 dispatch_queue_t queue;
855 SecDbRef db;
856 };
857
858 static SOSDataSourceRef SecItemDataSourceFactoryCopyDataSource(SOSDataSourceFactoryRef factory, CFStringRef dataSourceName, CFErrorRef *error)
859 {
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);
867 }
868 });
869 return dataSource;
870 }
871
872 static void SecItemDataSourceFactoryDispose(SOSDataSourceFactoryRef factory)
873 {
874 // Nothing to do here.
875 }
876
877 // Fire up the SOSEngines so they can
878 static bool SOSDataSourceFactoryStartYourEngines(SOSDataSourceFactoryRef factory) {
879 #if OCTAGON
880 if(!SecCKKSTestDisableSOS() && !SecCKKSTestsEnabled()) {
881 #endif // OCTAGON
882 bool ok = true;
883 CFStringRef dsName = SOSDataSourceFactoryCopyName(factory);
884 CFErrorRef localError = NULL;
885 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(factory, dsName, &localError);
886 if (!ds)
887 secerror("create_datasource %@ failed %@", dsName, localError);
888 CFReleaseNull(localError);
889 SOSDataSourceRelease(ds, &localError);
890 CFReleaseNull(localError);
891 CFReleaseNull(dsName);
892 return ok;
893 #if OCTAGON
894 } else {
895 return false;
896 }
897 #endif // OCTAGON
898 }
899
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;
905
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;
912 }
913
914
915 static dispatch_once_t sDSFQueueOnce;
916 static dispatch_queue_t sDSFQueue;
917 static CFMutableDictionaryRef sDSTable = NULL;
918
919 void SecItemDataSourceFactoryReleaseAll() {
920 // Ensure that the queue is set up
921 (void) SecItemDataSourceFactoryGetShared(nil);
922
923 dispatch_sync(sDSFQueue, ^{
924 if(sDSTable) {
925 CFDictionaryRemoveAllValues(sDSTable);
926 }
927 });
928 }
929
930 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetShared(SecDbRef db) {
931
932 dispatch_once(&sDSFQueueOnce, ^{
933 sDSFQueue = dispatch_queue_create("dataSourceFactory queue", DISPATCH_QUEUE_SERIAL);
934 sDSTable = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
935 });
936
937 __block SOSDataSourceFactoryRef result = NULL;
938 dispatch_sync(sDSFQueue, ^{
939 if(db) {
940 CFStringRef dbPath = SecDbGetPath(db);
941 if(dbPath) {
942 result = (SOSDataSourceFactoryRef) CFDictionaryGetValue(sDSTable, dbPath);
943
944 if(!result) {
945 result = SecItemDataSourceFactoryCreate(db);
946 CFDictionaryAddValue(sDSTable, dbPath, result);
947 }
948 }
949 }
950 });
951
952 return result;
953 }
954
955 // TODO: These should move to SecItemServer.c
956
957 void SecItemServerAppendItemDescription(CFMutableStringRef desc, CFDictionaryRef object) {
958 SOSObjectRef item = objectCreateWithPropertyList(object, NULL);
959 if (item) {
960 CFStringRef itemDesc = CFCopyDescription(item);
961 if (itemDesc) {
962 CFStringAppend(desc, itemDesc);
963 CFReleaseSafe(itemDesc);
964 }
965 CFRelease(item);
966 }
967 }
968
969 SOSManifestRef SOSCreateManifestWithBackup(CFDictionaryRef backup, CFErrorRef *error)
970 {
971 __block struct SOSDigestVector dv = SOSDigestVectorInit;
972 if (backup) {
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));
979 }
980 });
981 }
982 SOSManifestRef manifest = SOSManifestCreateWithDigestVector(&dv, error);
983 SOSDigestVectorFree(&dv);
984 return manifest;
985 }