/* * Copyright (c) 2012 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * SecDbItem.c - CoreFoundation-based constants and functions representing * database items (certificates, keys, identities, and passwords.) * Created by Michael Brouwer on 11/15/12. */ #include #include #include #include #include #include #include #include #include // MARK: type converters CFStringRef copyString(CFTypeRef obj) { CFTypeID tid = CFGetTypeID(obj); if (tid == CFStringGetTypeID()) return CFStringCreateCopy(0, obj); else if (tid == CFDataGetTypeID()) return CFStringCreateFromExternalRepresentation(0, obj, kCFStringEncodingUTF8); else return NULL; } CFDataRef copyData(CFTypeRef obj) { CFTypeID tid = CFGetTypeID(obj); if (tid == CFDataGetTypeID()) { return CFDataCreateCopy(0, obj); } else if (tid == CFStringGetTypeID()) { return CFStringCreateExternalRepresentation(0, obj, kCFStringEncodingUTF8, 0); } else if (tid == CFNumberGetTypeID()) { SInt32 value; CFNumberGetValue(obj, kCFNumberSInt32Type, &value); return CFDataCreate(0, (const UInt8 *)&value, sizeof(value)); } else { return NULL; } } CFTypeRef copyBlob(CFTypeRef obj) { CFTypeID tid = CFGetTypeID(obj); if (tid == CFDataGetTypeID()) { return CFDataCreateCopy(0, obj); } else if (tid == CFStringGetTypeID()) { return CFStringCreateCopy(0, obj); } else if (tid == CFNumberGetTypeID()) { CFRetain(obj); return obj; } else { return NULL; } } CFDataRef copySHA1(CFTypeRef obj) { CFTypeID tid = CFGetTypeID(obj); if (tid == CFDataGetTypeID() && CFDataGetLength(obj) == CCSHA1_OUTPUT_SIZE) { return CFDataCreateCopy(CFGetAllocator(obj), obj); } else { return NULL; } } CFTypeRef copyNumber(CFTypeRef obj) { CFTypeID tid = CFGetTypeID(obj); if (tid == CFNumberGetTypeID()) { CFRetain(obj); return obj; } else if (tid == CFBooleanGetTypeID()) { SInt32 value = CFBooleanGetValue(obj); return CFNumberCreate(0, kCFNumberSInt32Type, &value); } else if (tid == CFStringGetTypeID()) { SInt32 value = CFStringGetIntValue(obj); CFStringRef t = CFStringCreateWithFormat(0, 0, CFSTR("%ld"), (long) value); /* If a string converted to an int isn't equal to the int printed as a string, return a CFStringRef instead. */ if (!CFEqual(t, obj)) { CFRelease(t); return CFStringCreateCopy(0, obj); } CFRelease(t); return CFNumberCreate(0, kCFNumberSInt32Type, &value); } else return NULL; } CFDateRef copyDate(CFTypeRef obj) { CFTypeID tid = CFGetTypeID(obj); if (tid == CFDateGetTypeID()) { CFRetain(obj); return obj; } else return NULL; } // MARK: SecDbColumn accessors, to retrieve values as CF types in SecDbStep. static CFDataRef SecDbColumnCopyData(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) { return CFDataCreate(allocator, sqlite3_column_blob(stmt, col), sqlite3_column_bytes(stmt, col)); //return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt, col), // sqlite3_column_bytes(stmt, col), // kCFAllocatorNull); } static CFDateRef SecDbColumnCopyDate(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) { return CFDateCreate(allocator, sqlite3_column_double(stmt, col)); } static CFNumberRef SecDbColumnCopyDouble(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) { double number = sqlite3_column_double(stmt, col); return CFNumberCreate(allocator, kCFNumberDoubleType, &number); } static CFNumberRef SecDbColumnCopyNumber64(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) { sqlite_int64 number = sqlite3_column_int64(stmt, col); return CFNumberCreate(allocator, kCFNumberSInt64Type, &number); } static CFNumberRef SecDbColumnCopyNumber(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) { sqlite_int64 number = sqlite3_column_int64(stmt, col); if (INT32_MIN <= number && number <= INT32_MAX) { int32_t num32 = (int32_t)number; return CFNumberCreate(allocator, kCFNumberSInt32Type, &num32); } else { return CFNumberCreate(allocator, kCFNumberSInt64Type, &number); } } static CFStringRef SecDbColumnCopyString(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) { const unsigned char *text = sqlite3_column_text(stmt, col); return CFStringCreateWithBytes(allocator, text, strlen((const char *)text), kCFStringEncodingUTF8, false); } // MARK: SecDbClass helpers const SecDbAttr *SecDbClassAttrWithKind(const SecDbClass *class, SecDbAttrKind kind, CFErrorRef *error) { const SecDbAttr *result = NULL; SecDbForEachAttr(class, desc) { if (desc->kind == kind) result = desc; } if (!result) SecError(errSecInternal, error, CFSTR("Can't find attribute of kind %d in class %@"), kind, class->name); return result; } // MARK: SecDbAttr helpers static bool SecDbIsTombstoneDbSelectAttr(const SecDbAttr *attr) { return attr->flags & kSecDbPrimaryKeyFlag || attr->kind == kSecDbTombAttr; } #if 0 static bool SecDbIsTombstoneDbInsertAttr(const SecDbAttr *attr) { return SecDbIsTombstoneDbSelectAttr(attr) || attr->kind == kSecDbAccessAttr || attr->kind == kSecDbCreationDateAttr || attr->kind == kSecDbModificationDateAttr; } #endif static bool SecDbIsTombstoneDbUpdateAttr(const SecDbAttr *attr) { return SecDbIsTombstoneDbSelectAttr(attr) || attr->kind == kSecDbAccessAttr || attr->kind == kSecDbCreationDateAttr || attr->kind == kSecDbRowIdAttr; } static bool SecDbAttrBind(const SecDbAttr *attr, sqlite3_stmt *stmt, int col, CFTypeRef value, CFErrorRef *error) { bool ok = true; if (value && !CFEqual(kCFNull, value) && attr->flags & kSecDbSHA1ValueInFlag) { CFDataRef data = copyData(value); if (!data) { SecError(errSecInternal, error, CFSTR("failed to get attribute %@ data"), attr->name); return false; } CFMutableDataRef digest = CFDataCreateMutable(kCFAllocatorDefault, CCSHA1_OUTPUT_SIZE); CFDataSetLength(digest, CCSHA1_OUTPUT_SIZE); /* 64 bits cast: worst case is we generate the wrong hash */ assert((unsigned long)CFDataGetLength(data)attributes, name); } static CFTypeRef SecDbItemGetCachedValue(SecDbItemRef item, const SecDbAttr *desc) { return CFDictionaryGetValue(item->attributes, desc->name); } CFMutableDictionaryRef SecDbItemCopyPListWithMask(SecDbItemRef item, CFOptionFlags mask, CFErrorRef *error) { CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); SecDbForEachAttrWithMask(item->class, desc, mask) { CFTypeRef value = SecDbItemGetValue(item, desc, error); if (value) { if (!CFEqual(kCFNull, value)) { CFDictionarySetValue(dict, desc->name, value); } else if (desc->flags & kSecDbNotNullFlag) { SecError(errSecInternal, error, CFSTR("attribute %@ has NULL value"), desc->name); secerror("%@", error ? *error : (CFErrorRef)CFSTR("error == NULL")); CFReleaseNull(dict); break; } } else { CFReleaseNull(dict); break; } } return dict; } static CFDataRef SecDbItemCopyDERWithMask(SecDbItemRef item, CFOptionFlags mask, CFErrorRef *error) { CFDataRef der = NULL; CFMutableDictionaryRef dict = SecDbItemCopyPListWithMask(item, mask, error); if (dict) { der = kc_plist_copy_der(dict, error); CFRelease(dict); } return der; } static CFDataRef SecDbItemCopyDigestWithMask(SecDbItemRef item, CFOptionFlags mask, CFErrorRef *error) { CFDataRef digest = NULL; CFDataRef der = SecDbItemCopyDERWithMask(item, mask, error); if (der) { digest = kc_copy_sha1(CFDataGetLength(der), CFDataGetBytePtr(der), error); CFRelease(der); } return digest; } static CFDataRef SecDbItemCopyPrimaryKey(SecDbItemRef item, CFErrorRef *error) { return SecDbItemCopyDigestWithMask(item, kSecDbPrimaryKeyFlag, error); } static CFDataRef SecDbItemCopySHA1(SecDbItemRef item, CFErrorRef *error) { return SecDbItemCopyDigestWithMask(item, kSecDbInHashFlag, error); } static CFDataRef SecDbItemCopyUnencryptedData(SecDbItemRef item, CFErrorRef *error) { return SecDbItemCopyDERWithMask(item, kSecDbInCryptoDataFlag, error); } static keyclass_t SecDbItemParseKeyclass(CFTypeRef value, CFErrorRef *error) { if (!isString(value)) { SecError(errSecParam, error, CFSTR("accessible attribute %@ not a string"), value); } else if (CFEqual(value, kSecAttrAccessibleWhenUnlocked)) { return key_class_ak; } else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlock)) { return key_class_ck; } else if (CFEqual(value, kSecAttrAccessibleAlways)) { return key_class_dk; } else if (CFEqual(value, kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) { return key_class_aku; } else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)) { return key_class_cku; } else if (CFEqual(value, kSecAttrAccessibleAlwaysThisDeviceOnly)) { return key_class_dku; } else { SecError(errSecParam, error, CFSTR("accessible attribute %@ unknown"), value); } return 0; } keyclass_t SecDbItemGetKeyclass(SecDbItemRef item, CFErrorRef *error) { if (!item->keyclass) { const SecDbAttr *desc = SecDbClassAttrWithKind(item->class, kSecDbAccessAttr, error); if (desc) { CFTypeRef value = SecDbItemGetValue(item, desc, error); if (value) { item->keyclass = SecDbItemParseKeyclass(value, error); } } } return item->keyclass; } static CFDataRef SecDbItemCopyEncryptedData(SecDbItemRef item, CFErrorRef *error) { CFDataRef edata = NULL; CFDataRef plain = SecDbItemCopyUnencryptedData(item, error); if (plain) { keyclass_t keyclass = SecDbItemGetKeyclass(item, error); if (keyclass) { if (ks_encrypt_data(item->keybag, keyclass, plain, &edata, error)) { item->_edataState = kSecDbItemEncrypting; } else { seccritical("ks_encrypt_data (db): failed: %@", error ? *error : (CFErrorRef)CFSTR("")); } } CFRelease(plain); } return edata; } CFDataRef SecDbItemCopyEncryptedDataToBackup(SecDbItemRef item, uint64_t handle, CFErrorRef *error) { CFDataRef edata = NULL; keybag_handle_t keybag = (keybag_handle_t)handle; CFDataRef plain = SecDbItemCopyUnencryptedData(item, error); if (plain) { keyclass_t keyclass = SecDbItemGetKeyclass(item, error); if (keyclass) { if (!ks_encrypt_data(keybag, keyclass, plain, &edata, error)) seccritical("ks_encrypt_data (db): failed: %@", error ? *error : (CFErrorRef)CFSTR("")); } CFRelease(plain); } return edata; } static bool SecDbItemEnsureDecrypted(SecDbItemRef item, CFErrorRef *error) { // If we haven't yet decrypted the item, make sure we do so now bool result = true; if (item->_edataState == kSecDbItemEncrypted) { const SecDbAttr *attr = SecDbClassAttrWithKind(item->class, kSecDbEncryptedDataAttr, error); if (attr) { CFDataRef edata = SecDbItemGetCachedValue(item, attr); if (!edata) return SecError(errSecInternal, error, CFSTR("state= encrypted but edata is NULL")); // Decrypt calls set value a bunch of times which clears our edata and changes our state. item->_edataState = kSecDbItemDecrypting; result = SecDbItemDecrypt(item, edata, error); if (result) item->_edataState = kSecDbItemClean; else item->_edataState = kSecDbItemEncrypted; } } return result; } // Only called if cached value is not found. static CFTypeRef SecDbItemCopyValue(SecDbItemRef item, const SecDbAttr *attr, CFErrorRef *error) { CFTypeRef value = NULL; switch (attr->kind) { case kSecDbSHA1Attr: value = SecDbItemCopySHA1(item, error); break; case kSecDbEncryptedDataAttr: value = SecDbItemCopyEncryptedData(item, error); break; case kSecDbPrimaryKeyAttr: value = SecDbItemCopyPrimaryKey(item, error); break; case kSecDbAccessAttr: case kSecDbStringAttr: case kSecDbBlobAttr: if (attr->flags & kSecDbNotNullFlag) { if (attr->flags & kSecDbDefault0Flag) { value = CFSTR("0"); break; } else if (attr->kind != kSecDbBlobAttr && attr->flags & kSecDbDefaultEmptyFlag) { // blob drops through to data everything else is empty string value = CFSTR(""); break; } } //DROPTHROUGH case kSecDbDataAttr: if (attr->flags & kSecDbNotNullFlag && attr->flags & kSecDbDefaultEmptyFlag) { value = CFDataCreate(CFGetAllocator(item), NULL, 0); } else { value = kCFNull; } break; case kSecDbNumberAttr: case kSecDbSyncAttr: case kSecDbTombAttr: if (attr->flags & kSecDbNotNullFlag) { int32_t zero = 0; value = CFNumberCreate(CFGetAllocator(item), kCFNumberSInt32Type, &zero); } else { value = kCFNull; } break; case kSecDbDateAttr: if (attr->flags & kSecDbNotNullFlag && attr->flags & kSecDbDefault0Flag) { value = CFDateCreate(kCFAllocatorDefault, 0.0); } else { value = kCFNull; } break; case kSecDbRowIdAttr: if (attr->flags & kSecDbNotNullFlag) { // No can do, error? } value = kCFNull; break; case kSecDbCreationDateAttr: case kSecDbModificationDateAttr: value = CFDateCreate(CFGetAllocator(item), CFAbsoluteTimeGetCurrent()); break; } return value; } // SecDbItemGetValue will return kCFNull if there is no value for an attribute and this was not // an error. It will return NULL and optionally set *error if there was an error computing an // attribute, or if a required attribute was missing a value and had no known way to compute // it's value. CF_RETURNS_NOT_RETAINED CFTypeRef SecDbItemGetValue(SecDbItemRef item, const SecDbAttr *desc, CFErrorRef *error) { // Propagate chained errors if (!desc) return NULL; if (desc->flags & kSecDbInCryptoDataFlag) { if (!SecDbItemEnsureDecrypted(item, error)) return NULL; } CFTypeRef value = SecDbItemGetCachedValue(item, desc); if (!value) { value = SecDbItemCopyValue(item, desc, error); if (value) { if (!CFEqual(kCFNull, value)) { SecDbItemSetValue(item, desc, value, error); CFRelease(value); value = SecDbItemGetCachedValue(item, desc); } } } return value; } static bool SecDbItemGetBoolValue(SecDbItemRef item, const SecDbAttr *desc, bool *bvalue, CFErrorRef *error) { CFTypeRef value = SecDbItemGetValue(item, desc, error); if (!value) return false; char cvalue; *bvalue = (isNumber(value) && CFNumberGetValue(value, kCFNumberCharType, &cvalue) && cvalue == 1); return true; } static CFStringRef SecDbItemCopyDescription(CFTypeRef cf) { #if 0 //defined(DEBUG) && DEBUG != 0 SecDbItemRef item = (SecDbItemRef)cf; CFMutableStringRef desc = CFStringCreateMutable(CFGetAllocator(cf), 0); CFStringAppendFormat(desc, NULL, CFSTR("<%@"), item->class->name); SecDbForEachAttr(item->class, attr) { CFTypeRef value = SecDbItemGetValue(item, attr, NULL); if (value) { CFStringAppend(desc, CFSTR(",")); CFStringAppend(desc, attr->name); CFStringAppend(desc, CFSTR("=")); if (CFEqual(CFSTR("data"), attr->name)) { CFStringAppendEncryptedData(desc, value); } else if (CFEqual(CFSTR("v_Data"), attr->name)) { CFStringAppend(desc, CFSTR("")); } else if (isData(value)) { CFStringAppendHexData(desc, value); } else { CFStringAppendFormat(desc, 0, CFSTR("%@"), value); } } } CFStringAppend(desc, CFSTR(">")); #else SecDbItemRef item = (SecDbItemRef)cf; const UInt8 zero4[4] = {}; const UInt8 *pk = &zero4[0], *sha1 = &zero4[0]; char sync = 0; char tomb = 0; SInt64 rowid = 0; CFStringRef access = NULL; uint8_t mdatbuf[32] = {}; uint8_t *mdat = &mdatbuf[0]; CFMutableStringRef attrs = CFStringCreateMutable(kCFAllocatorDefault, 0); CFStringRef agrp = NULL; SecDbForEachAttr(item->class, attr) { CFTypeRef value; switch (attr->kind) { case kSecDbBlobAttr: case kSecDbDataAttr: case kSecDbStringAttr: case kSecDbNumberAttr: case kSecDbDateAttr: case kSecDbEncryptedDataAttr: if (attr->flags & (kSecDbReturnAttrFlag | kSecDbReturnDataFlag) && (value = SecDbItemGetValue(item, attr, NULL)) && !CFEqual(value, kCFNull)) { if (isString(value) && CFEqual(attr->name, kSecAttrAccessGroup)) { agrp = value; } else { // We don't log these, just record that we saw the attribute. CFStringAppend(attrs, CFSTR(",")); CFStringAppend(attrs, attr->name); } } break; case kSecDbCreationDateAttr: // We don't care about this and every object has one. break; case kSecDbModificationDateAttr: value = SecDbItemGetValue(item, attr, NULL); if (isDate(value)) mdat = der_encode_generalizedtime_body(CFDateGetAbsoluteTime(value), NULL, mdat, &mdatbuf[31]); break; case kSecDbSHA1Attr: value = SecDbItemGetValue(item, attr, NULL); if (isData(value)) sha1 = CFDataGetBytePtr(value); break; case kSecDbRowIdAttr: value = SecDbItemGetValue(item, attr, NULL); if (isNumber(value)) CFNumberGetValue(value, kCFNumberSInt64Type, &rowid); break; case kSecDbPrimaryKeyAttr: value = SecDbItemGetValue(item, attr, NULL); if (isData(value)) pk = CFDataGetBytePtr(value); break; case kSecDbSyncAttr: value = SecDbItemGetValue(item, attr, NULL); if (isNumber(value)) CFNumberGetValue(value, kCFNumberCharType, &sync); break; case kSecDbTombAttr: value = SecDbItemGetValue(item, attr, NULL); if (isNumber(value)) CFNumberGetValue(value, kCFNumberCharType, &tomb); break; case kSecDbAccessAttr: value = SecDbItemGetValue(item, attr, NULL); if (isString(value)) access = value; break; } } CFStringRef desc = CFStringCreateWithFormat(CFGetAllocator(cf), NULL, CFSTR( "%s," "%@," "%02X%02X%02X%02X," "%s," "%@," "%@," "%"PRId64 "%@," "%s," "%02X%02X%02X%02X"), tomb ? "T" : "O", item->class->name, pk[0], pk[1], pk[2], pk[3], sync ? "S" : "L", access, agrp, rowid, attrs, mdat, sha1[0], sha1[1], sha1[2], sha1[3]); CFReleaseSafe(attrs); #endif return desc; } static void SecDbItemDestroy(CFTypeRef cf) { SecDbItemRef item = (SecDbItemRef)cf; CFReleaseSafe(item->attributes); } static CFHashCode SecDbItemHash(CFTypeRef cf) { SecDbItemRef item = (SecDbItemRef)cf; CFDataRef digest = SecDbItemGetSHA1(item, NULL); CFHashCode code; const UInt8 *p = CFDataGetBytePtr(digest); // Read first 8 bytes of digest in order code = p[0] + ((p[1] + ((p[2] + ((p[3] + ((p[4] + ((p[5] + ((p[6] + (p[7] << 8)) << 8)) << 8)) << 8)) << 8)) << 8)) << 8); return code; } static Boolean SecDbItemCompare(CFTypeRef cf1, CFTypeRef cf2) { SecDbItemRef item1 = (SecDbItemRef)cf1; SecDbItemRef item2 = (SecDbItemRef)cf2; CFDataRef digest1 = NULL; CFDataRef digest2 = NULL; if (item1) digest1 = SecDbItemGetSHA1(item1, NULL); if (item2) digest2 = SecDbItemGetSHA1(item2, NULL); Boolean equal = CFEqual(digest1, digest2); return equal; } CFGiblisWithHashFor(SecDbItem) static SecDbItemRef SecDbItemCreate(CFAllocatorRef allocator, const SecDbClass *class, keybag_handle_t keybag) { SecDbItemRef item = CFTypeAllocate(SecDbItem, struct SecDbItem, allocator); item->class = class; item->attributes = CFDictionaryCreateMutableForCFTypes(allocator); item->keybag = keybag; item->_edataState = kSecDbItemDirty; return item; } const SecDbClass *SecDbItemGetClass(SecDbItemRef item) { return item->class; } const keybag_handle_t SecDbItemGetKeybag(SecDbItemRef item) { return item->keybag; } bool SecDbItemSetKeybag(SecDbItemRef item, keybag_handle_t keybag, CFErrorRef *error) { if (!SecDbItemEnsureDecrypted(item, error)) return false; if (item->keybag != keybag) { item->keybag = keybag; if (item->_edataState == kSecDbItemClean) { SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbEncryptedDataAttr, NULL), kCFNull, NULL); } } return true; } bool SecDbItemSetValue(SecDbItemRef item, const SecDbAttr *desc, CFTypeRef value, CFErrorRef *error) { // Propagate chained errors. if (!desc) return false; bool changed = false; CFTypeRef attr = NULL; if (desc->flags & kSecDbInCryptoDataFlag) if (!SecDbItemEnsureDecrypted(item, error)) return false; switch (desc->kind) { case kSecDbPrimaryKeyAttr: case kSecDbDataAttr: attr = copyData(value); break; case kSecDbEncryptedDataAttr: attr = copyData(value); if (attr) { if (item->_edataState == kSecDbItemEncrypting) item->_edataState = kSecDbItemClean; else item->_edataState = kSecDbItemEncrypted; } else if (!value || CFEqual(kCFNull, value)) { item->_edataState = kSecDbItemDirty; } break; case kSecDbBlobAttr: attr = copyBlob(value); break; case kSecDbDateAttr: case kSecDbCreationDateAttr: case kSecDbModificationDateAttr: attr = copyDate(value); break; case kSecDbNumberAttr: case kSecDbSyncAttr: case kSecDbTombAttr: case kSecDbRowIdAttr: attr = copyNumber(value); break; case kSecDbAccessAttr: case kSecDbStringAttr: attr = copyString(value); break; case kSecDbSHA1Attr: attr = copySHA1(value); break; } if (attr) { CFTypeRef ovalue = CFDictionaryGetValue(item->attributes, desc->name); changed = (!ovalue || !CFEqual(ovalue, attr)); CFDictionarySetValue(item->attributes, desc->name, attr); CFRelease(attr); } else { if (value && !CFEqual(kCFNull, value)) { SecError(errSecItemInvalidValue, error, CFSTR("attribute %@: value: %@ failed to convert"), desc->name, value); return false; } CFTypeRef ovalue = CFDictionaryGetValue(item->attributes, desc->name); changed = (ovalue && !CFEqual(ovalue, kCFNull)); CFDictionaryRemoveValue(item->attributes, desc->name); } if (changed) { if (desc->flags & kSecDbInHashFlag) SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, NULL), kCFNull, NULL); if (desc->flags & kSecDbPrimaryKeyFlag) SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbPrimaryKeyAttr, NULL), kCFNull, NULL); if (desc->flags & kSecDbInCryptoDataFlag && item->_edataState == kSecDbItemClean) SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbEncryptedDataAttr, NULL), kCFNull, NULL); } return true; } bool SecDbItemSetValues(SecDbItemRef item, CFDictionaryRef values, CFErrorRef *error) { SecDbForEachAttr(item->class, attr) { CFTypeRef value = CFDictionaryGetValue(values, attr->name); if (value && !SecDbItemSetValue(item, attr, value, error)) return false; } return true; } bool SecDbItemSetValueWithName(SecDbItemRef item, CFStringRef name, CFTypeRef value, CFErrorRef *error) { SecDbForEachAttr(item->class, attr) { if (CFEqual(attr->name, name)) { return SecDbItemSetValue(item, attr, value, error); } } return false; } static bool SecDbItemSetNumber(SecDbItemRef item, const SecDbAttr *desc, int32_t number, CFErrorRef *error) { bool ok = true; CFNumberRef value = CFNumberCreate(CFGetAllocator(item), kCFNumberSInt32Type, &number); if (value) { ok = SecDbItemSetValue(item, desc, value, error); CFRelease(value); } else { ok = SecError(errSecInternal, error, CFSTR("Failed to create CFNumber for %" PRId32), number); } return ok; } bool SecDbItemSetKeyclass(SecDbItemRef item, keyclass_t keyclass, CFErrorRef *error) { bool ok = SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbEncryptedDataAttr, error), kCFNull, error); if (ok) { item->_edataState = kSecDbItemDirty; ok = SecDbItemSetNumber(item, SecDbClassAttrWithKind(item->class, kSecDbAccessAttr, error), keyclass, error); } return ok; } SecDbItemRef SecDbItemCreateWithAttributes(CFAllocatorRef allocator, const SecDbClass *class, CFDictionaryRef attributes, keybag_handle_t keybag, CFErrorRef *error) { SecDbItemRef item = SecDbItemCreate(kCFAllocatorDefault, class, keybag); if (item && !SecDbItemSetValues(item, attributes, error)) CFReleaseNull(item); return item; } static CFTypeRef SecDbColumnCopyValueWithAttr(CFAllocatorRef allocator, sqlite3_stmt *stmt, const SecDbAttr *attr, int col, CFErrorRef *error) { CFTypeRef value = NULL; switch (attr->kind) { case kSecDbDateAttr: case kSecDbCreationDateAttr: case kSecDbModificationDateAttr: value = SecDbColumnCopyDate(allocator, stmt, col, error); break; case kSecDbBlobAttr: switch (sqlite3_column_type(stmt, col)) { case SQLITE_INTEGER: value = SecDbColumnCopyNumber(allocator, stmt, col, error); break; case SQLITE_FLOAT: value = SecDbColumnCopyDouble(allocator, stmt, col, error); break; case SQLITE_TEXT: value = SecDbColumnCopyString(allocator, stmt, col, error); break; case SQLITE_BLOB: value = SecDbColumnCopyData(allocator, stmt, col, error); break; case SQLITE_NULL: value = kCFNull; break; } break; case kSecDbAccessAttr: case kSecDbStringAttr: value = SecDbColumnCopyString(allocator, stmt, col, error); break; case kSecDbDataAttr: case kSecDbSHA1Attr: case kSecDbPrimaryKeyAttr: value = SecDbColumnCopyData(allocator, stmt, col, error); break; case kSecDbEncryptedDataAttr: value = SecDbColumnCopyData(allocator, stmt, col, error); break; case kSecDbSyncAttr: case kSecDbTombAttr: case kSecDbNumberAttr: value = SecDbColumnCopyNumber(allocator, stmt, col, error); break; case kSecDbRowIdAttr: value = SecDbColumnCopyNumber64(allocator, stmt, col, error); break; } return value; } SecDbItemRef SecDbItemCreateWithStatement(CFAllocatorRef allocator, const SecDbClass *class, sqlite3_stmt *stmt, keybag_handle_t keybag, CFErrorRef *error, bool (^return_attr)(const SecDbAttr *attr)) { SecDbItemRef item = SecDbItemCreate(allocator, class, keybag); int col = 0; SecDbForEachAttr(class, attr) { if (return_attr(attr)) { CFTypeRef value = SecDbColumnCopyValueWithAttr(allocator, stmt, attr, col++, error); if (value) { SecDbItemSetValue(item, attr, value, error); CFRelease(value); } } } return item; } SecDbItemRef SecDbItemCreateWithEncryptedData(CFAllocatorRef allocator, const SecDbClass *class, CFDataRef edata, keybag_handle_t keybag, CFErrorRef *error) { SecDbItemRef item = SecDbItemCreate(allocator, class, keybag); const SecDbAttr *edata_attr = SecDbClassAttrWithKind(class, kSecDbEncryptedDataAttr, error); if (edata_attr) { if (!SecDbItemSetValue(item, edata_attr, edata, error)) CFReleaseNull(item); } return item; } #if 0 SecDbItemRef SecDbItemCreateWithRowId(CFAllocatorRef allocator, const SecDbClass *class, sqlite_int64 row_id, keybag_handle_t keybag, CFErrorRef *error) { SecDbItemRef item = SecDbItemCreate(allocator, class, keybag); if (!SecDbItemSetRowId(item, row_id, error)) CFReleaseNull(item); return item; } #endif SecDbItemRef SecDbItemCopyWithUpdates(SecDbItemRef item, CFDictionaryRef updates, CFErrorRef *error) { SecDbItemRef new_item = SecDbItemCreate(CFGetAllocator(item), item->class, item->keybag); SecDbForEachAttr(item->class, attr) { // Copy each attribute, except the mod date attribute (it will be reset to now when needed), // from the updates dict unless it's not there in which case we copy the attribute from the passed in item. if (attr->kind != kSecDbModificationDateAttr && attr->kind != kSecDbEncryptedDataAttr && attr->kind != kSecDbSHA1Attr && attr->kind != kSecDbPrimaryKeyAttr) { CFTypeRef value = NULL; if (CFDictionaryGetValueIfPresent(updates, attr->name, &value)) { if (!value) SecError(errSecParam, error, CFSTR("NULL value in dictionary")); } else { value = SecDbItemGetValue(item, attr, error); } if (!value || !SecDbItemSetValue(new_item, attr, value, error)) { CFReleaseNull(new_item); break; } } } return new_item; } // Ensure that the date value of attr of new_item is greater than that of old_item. static bool SecDbItemMakeAttrYounger(SecDbItemRef new_item, SecDbItemRef old_item, const SecDbAttr *attr, CFErrorRef *error) { CFDateRef old_date = SecDbItemGetValue(old_item, attr, error); if (!old_date) return false; CFDateRef new_date = SecDbItemGetValue(new_item, attr, error); if (!new_date) return false; bool ok = true; if (CFDateCompare(new_date, old_date, NULL) != kCFCompareGreaterThan) { CFDateRef adjusted_date = CFDateCreate(kCFAllocatorDefault, CFDateGetAbsoluteTime(old_date) + 0.001); if (adjusted_date) { ok = SecDbItemSetValue(new_item, attr, adjusted_date, error); CFRelease(adjusted_date); } } return ok; } // Ensure that the mod date of new_item is greater than that of old_item. static bool SecDbItemMakeYounger(SecDbItemRef new_item, SecDbItemRef old_item, CFErrorRef *error) { const SecDbAttr *attr = SecDbClassAttrWithKind(new_item->class, kSecDbModificationDateAttr, error); return attr && SecDbItemMakeAttrYounger(new_item, old_item, attr, error); } SecDbItemRef SecDbItemCopyTombstone(SecDbItemRef item, CFErrorRef *error) { SecDbItemRef new_item = SecDbItemCreate(CFGetAllocator(item), item->class, item->keybag); SecDbForEachAttr(item->class, attr) { if (attr->kind == kSecDbTombAttr) { // Set the tomb attr to true to indicate a tombstone. if (!SecDbItemSetValue(new_item, attr, kCFBooleanTrue, error)) { CFReleaseNull(new_item); break; } } else if (SecDbIsTombstoneDbUpdateAttr(attr)) { // Copy all primary key attributes and creation timestamps from the original item. CFTypeRef value = SecDbItemGetValue(item, attr, error); if (!value || (!CFEqual(kCFNull, value) && !SecDbItemSetValue(new_item, attr, value, error))) { CFReleaseNull(new_item); break; } } else if (attr->kind == kSecDbModificationDateAttr) { if (!SecDbItemMakeAttrYounger(new_item, item, attr, error)) { CFReleaseNull(new_item); break; } } } return new_item; } // MARK: - // MARK: SQL Construction helpers -- These should become private in the future void SecDbAppendElement(CFMutableStringRef sql, CFStringRef value, bool *needComma) { assert(needComma); if (*needComma) { CFStringAppend(sql, CFSTR(",")); } else { *needComma = true; } CFStringAppend(sql, value); } static void SecDbAppendElementEquals(CFMutableStringRef sql, CFStringRef value, bool *needComma) { SecDbAppendElement(sql, value, needComma); CFStringAppend(sql, CFSTR("=?")); } /* Append AND is needWhere is NULL or *needWhere is false. Append WHERE otherwise. Upon return *needWhere will be false. */ void SecDbAppendWhereOrAnd(CFMutableStringRef sql, bool *needWhere) { if (!needWhere || !*needWhere) { CFStringAppend(sql, CFSTR(" AND ")); } else { CFStringAppend(sql, CFSTR(" WHERE ")); *needWhere = false; } } void SecDbAppendWhereOrAndEquals(CFMutableStringRef sql, CFStringRef col, bool *needWhere) { SecDbAppendWhereOrAnd(sql, needWhere); CFStringAppend(sql, col); CFStringAppend(sql, CFSTR("=?")); } static CFStringRef SecDbItemCopyInsertSQL(SecDbItemRef item, bool(^use_attr)(const SecDbAttr *attr)) { CFMutableStringRef sql = CFStringCreateMutable(CFGetAllocator(item), 0); CFStringAppend(sql, CFSTR("INSERT INTO ")); CFStringAppend(sql, item->class->name); CFStringAppend(sql, CFSTR("(")); bool needComma = false; CFIndex used_attr = 0; SecDbForEachAttr(item->class, attr) { if (use_attr(attr)) { ++used_attr; SecDbAppendElement(sql, attr->name, &needComma); } } CFStringAppend(sql, CFSTR(")VALUES(?")); while (used_attr-- > 1) { CFStringAppend(sql, CFSTR(",?")); } CFStringAppend(sql, CFSTR(")")); return sql; } static bool SecDbItemInsertBind(SecDbItemRef item, sqlite3_stmt *stmt, CFErrorRef *error, bool(^use_attr)(const SecDbAttr *attr)) { bool ok = true; int param = 0; SecDbForEachAttr(item->class, attr) { if (use_attr(attr)) { CFTypeRef value = SecDbItemGetValue(item, attr, error); if (!value || !SecDbAttrBind(attr, stmt, ++param, value, error)) { ok = false; break; } } } return ok; } sqlite3_int64 SecDbItemGetRowId(SecDbItemRef item, CFErrorRef *error) { sqlite3_int64 row_id = 0; const SecDbAttr *attr = SecDbClassAttrWithKind(item->class, kSecDbRowIdAttr, error); if (attr) { CFNumberRef number = SecDbItemGetValue(item, attr, error); if (!isNumber(number)|| !CFNumberGetValue(number, kCFNumberSInt64Type, &row_id)) SecDbError(SQLITE_ERROR, error, CFSTR("rowid %@ is not a 64 bit number"), number); } return row_id; } static CFNumberRef SecDbItemCreateRowId(SecDbItemRef item, sqlite3_int64 rowid, CFErrorRef *error) { return CFNumberCreate(CFGetAllocator(item), kCFNumberSInt64Type, &rowid); } bool SecDbItemSetRowId(SecDbItemRef item, sqlite3_int64 rowid, CFErrorRef *error) { bool ok = true; const SecDbAttr *attr = SecDbClassAttrWithKind(item->class, kSecDbRowIdAttr, error); if (attr) { CFNumberRef value = SecDbItemCreateRowId(item, rowid, error); if (!value) return false; ok = SecDbItemSetValue(item, attr, value, error); CFRelease(value); } return ok; } static bool SecDbItemClearRowId(SecDbItemRef item, CFErrorRef *error) { bool ok = true; const SecDbAttr *attr = SecDbClassAttrWithKind(item->class, kSecDbRowIdAttr, error); if (attr) { CFDictionaryRemoveValue(item->attributes, attr->name); //ok = SecDbItemSetValue(item, attr, kCFNull, error); } return ok; } static bool SecDbItemSetLastInsertRowId(SecDbItemRef item, SecDbConnectionRef dbconn, CFErrorRef *error) { sqlite3_int64 rowid = sqlite3_last_insert_rowid(SecDbHandle(dbconn)); return SecDbItemSetRowId(item, rowid, error); } bool SecDbItemIsSyncable(SecDbItemRef item) { bool is_syncable; if (SecDbItemGetBoolValue(item, SecDbClassAttrWithKind(item->class, kSecDbSyncAttr, NULL), &is_syncable, NULL)) return is_syncable; return false; } bool SecDbItemSetSyncable(SecDbItemRef item, bool sync, CFErrorRef *error) { return SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSyncAttr, error), sync ? kCFBooleanTrue : kCFBooleanFalse, error); } bool SecDbItemIsTombstone(SecDbItemRef item) { bool is_tomb; if (SecDbItemGetBoolValue(item, SecDbClassAttrWithKind(item->class, kSecDbTombAttr, NULL), &is_tomb, NULL)) return is_tomb; return false; } CFDataRef SecDbItemGetPrimaryKey(SecDbItemRef item, CFErrorRef *error) { return SecDbItemGetValue(item, SecDbClassAttrWithKind(item->class, kSecDbPrimaryKeyAttr, error), error); } CFDataRef SecDbItemGetSHA1(SecDbItemRef item, CFErrorRef *error) { return SecDbItemGetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, error), error); } static SecDbQueryRef SecDbQueryCreateWithItemPrimaryKey(SecDbItemRef item, CFErrorRef *error) { CFMutableDictionaryRef dict = SecDbItemCopyPListWithMask(item, kSecDbPrimaryKeyFlag, error); if (!dict) return NULL; SecDbQueryRef query = query_create(item->class, NULL, error); if (query) query->q_item = dict; else CFRelease(dict); return query; } static bool SecDbItemIsCorrupt(SecDbItemRef item, bool *is_corrupt, CFErrorRef *error) { CFErrorRef localError = NULL; bool ok = SecDbItemEnsureDecrypted(item, &localError); if (localError) { if (SecErrorGetOSStatus(localError) == errSecDecode) { // We failed to decrypt the item secerror("error %@ reading item %@ (corrupted)", localError, item); __security_simulatecrash(CFSTR("Corrupted item found in keychain"), __sec_exception_code_CorruptItem); *is_corrupt = true; ok = true; } else if (error && *error == NULL) { *error = localError; localError = NULL; } CFReleaseSafe(localError); } return ok; } static bool SecDbItemDoInsert(SecDbItemRef item, SecDbConnectionRef dbconn, CFErrorRef *error) { bool (^use_attr)(const SecDbAttr *attr) = ^bool(const SecDbAttr *attr) { return (attr->flags & kSecDbInFlag); }; CFStringRef sql = SecDbItemCopyInsertSQL(item, use_attr); __block bool ok = sql; if (sql) { ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) { ok = (SecDbItemInsertBind(item, stmt, error, use_attr) && SecDbStep(dbconn, stmt, error, NULL) && SecDbItemSetLastInsertRowId(item, dbconn, error)); }); CFRelease(sql); } if (ok) secnotice("item", "inserted %@", item); return ok; } bool SecDbItemInsertOrReplace(SecDbItemRef item, SecDbConnectionRef dbconn, CFErrorRef *error, void(^duplicate)(SecDbItemRef item, SecDbItemRef *replace)) { __block CFErrorRef localError = NULL; __block bool ok = SecDbItemDoInsert(item, dbconn, &localError); if (!ok && localError && CFErrorGetCode(localError) == SQLITE_CONSTRAINT && CFEqual(kSecDbErrorDomain, CFErrorGetDomain(localError))) { SecDbQueryRef query = SecDbQueryCreateWithItemPrimaryKey(item, error); if (query) { SecDbItemSelect(query, dbconn, error, ^bool(const SecDbAttr *attr) { return attr->flags & kSecDbPrimaryKeyFlag; }, NULL, NULL, ^(SecDbItemRef old_item, bool *stop) { bool is_corrupt = false; ok = SecDbItemIsCorrupt(old_item, &is_corrupt, error); SecDbItemRef replace = NULL; if (is_corrupt) { // If old_item is corrupted pretend it's not there and just replace it. replace = item; CFRetain(replace); } else if (ok && duplicate) { duplicate(old_item, &replace); } if (replace) { const SecDbAttr *rowid_attr = SecDbClassAttrWithKind(old_item->class, kSecDbRowIdAttr, error); CFNumberRef oldrowid = SecDbItemGetCachedValue(old_item, rowid_attr); if (oldrowid) { ok = SecDbItemSetValue(replace, rowid_attr, oldrowid, &localError); if (ok && !is_corrupt) { ok = SecDbItemMakeYounger(replace, old_item, error); } ok = ok && SecDbItemDoUpdate(old_item, replace, dbconn, &localError, ^bool (const SecDbAttr *attr) { return attr->kind == kSecDbRowIdAttr; }); } else { ok = SecError(errSecInternal, &localError, CFSTR("no rowid for %@"), old_item); } CFRelease(replace); if (ok) CFReleaseNull(localError); // Clear the error, since we replaced the item. } }); ok &= query_destroy(query, error); } if (localError) ok = false; // We didn't clear the error so we're failing again. } if (!ok) { /* CFErrorRef e = localError ? localError : error ? *error : NULL; secerror("INSERT %@ failed: %@%s", item, e, (e && CFErrorGetCode(e) == SQLITE_CONSTRAINT) ? " and SELECT failed to find tombstone" : ""); */ secerror("INSERT failed: %@", localError); if (localError) { if (error && *error == NULL) { *error = localError; localError = NULL; } else { CFReleaseNull(localError); } } } return ok; } bool SecDbItemInsert(SecDbItemRef item, SecDbConnectionRef dbconn, CFErrorRef *error) { return SecDbItemInsertOrReplace(item, dbconn, error, ^(SecDbItemRef old_item, SecDbItemRef *replace) { if (SecDbItemIsTombstone(old_item)) { CFRetain(item); *replace = item; } }); } static CFStringRef SecDbItemCopyUpdateSQL(SecDbItemRef old_item, SecDbItemRef new_item, bool(^use_attr_in_where)(const SecDbAttr *attr)) { CFMutableStringRef sql = CFStringCreateMutable(CFGetAllocator(new_item), 0); CFStringAppend(sql, CFSTR("UPDATE ")); CFStringAppend(sql, new_item->class->name); CFStringAppend(sql, CFSTR(" SET ")); bool needComma = false; CFIndex used_attr = 0; SecDbForEachAttrWithMask(new_item->class, attr, kSecDbInFlag) { ++used_attr; SecDbAppendElementEquals(sql, attr->name, &needComma); } bool needWhere = true; SecDbForEachAttr(old_item->class, attr) { if (use_attr_in_where(attr)) { SecDbAppendWhereOrAndEquals(sql, attr->name, &needWhere); } } return sql; } static bool SecDbItemUpdateBind(SecDbItemRef old_item, SecDbItemRef new_item, sqlite3_stmt *stmt, CFErrorRef *error, bool(^use_attr_in_where)(const SecDbAttr *attr)) { bool ok = true; int param = 0; SecDbForEachAttrWithMask(new_item->class, attr, kSecDbInFlag) { CFTypeRef value = SecDbItemGetValue(new_item, attr, error); ok &= value && SecDbAttrBind(attr, stmt, ++param, value, error); if (!ok) break; } SecDbForEachAttr(old_item->class, attr) { if (use_attr_in_where(attr)) { CFTypeRef value = SecDbItemGetValue(old_item, attr, error); ok &= value && SecDbAttrBind(attr, stmt, ++param, value, error); if (!ok) break; } } return ok; } // Primary keys are the same -- do an update bool SecDbItemDoUpdate(SecDbItemRef old_item, SecDbItemRef new_item, SecDbConnectionRef dbconn, CFErrorRef *error, bool (^use_attr_in_where)(const SecDbAttr *attr)) { CFStringRef sql = SecDbItemCopyUpdateSQL(old_item, new_item, use_attr_in_where); __block bool ok = sql; if (sql) { ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) { ok = SecDbItemUpdateBind(old_item, new_item, stmt, error, use_attr_in_where) && SecDbStep(dbconn, stmt, error, NULL); }); CFRelease(sql); } if (ok) secnotice("item", "replaced %@ with %@ in %@", old_item, new_item, dbconn); return ok; } static CFStringRef SecDbItemCopyDeleteSQL(SecDbItemRef item, bool(^use_attr_in_where)(const SecDbAttr *attr)) { CFMutableStringRef sql = CFStringCreateMutable(CFGetAllocator(item), 0); CFStringAppend(sql, CFSTR("DELETE FROM ")); CFStringAppend(sql, item->class->name); bool needWhere = true; SecDbForEachAttr(item->class, attr) { if (use_attr_in_where(attr)) { SecDbAppendWhereOrAndEquals(sql, attr->name, &needWhere); } } return sql; } static bool SecDbItemDeleteBind(SecDbItemRef item, sqlite3_stmt *stmt, CFErrorRef *error, bool(^use_attr_in_where)(const SecDbAttr *attr)) { bool ok = true; int param = 0; SecDbForEachAttr(item->class, attr) { if (use_attr_in_where(attr)) { CFTypeRef value = SecDbItemGetValue(item, attr, error); ok &= value && SecDbAttrBind(attr, stmt, ++param, value, error); if (!ok) break; } } return ok; } static bool SecDbItemDoDelete(SecDbItemRef item, SecDbConnectionRef dbconn, CFErrorRef *error, bool (^use_attr_in_where)(const SecDbAttr *attr)) { CFStringRef sql = SecDbItemCopyDeleteSQL(item, use_attr_in_where); __block bool ok = sql; if (sql) { ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) { ok = SecDbItemDeleteBind(item, stmt, error, use_attr_in_where) && SecDbStep(dbconn, stmt, error, NULL); }); CFRelease(sql); } if (ok) secnotice("item", "deleted %@ from %@", item, dbconn); return ok; } #if 0 static bool SecDbItemDeleteTombstone(SecDbItemRef item, SecDbConnectionRef dbconn, CFErrorRef *error) { bool ok = true; // TODO: Treat non decryptable items like tombstones here too and delete them SecDbItemRef tombstone = SecDbItemCopyTombstone(item, error); ok = tombstone; if (tombstone) { ok = SecDbItemClearRowId(tombstone, error); if (ok) { ok = SecDbItemDoDelete(tombstone, dbconn, error, ^bool (const SecDbAttr *attr) { return SecDbIsTombstoneDbSelectAttr(attr); }); } CFRelease(tombstone); } return ok; } #endif // Replace old_item with new_item. If primary keys are the same this does an update otherwise it does a delete + add bool SecDbItemUpdate(SecDbItemRef old_item, SecDbItemRef new_item, SecDbConnectionRef dbconn, bool makeTombstone, CFErrorRef *error) { __block bool ok = true; __block CFErrorRef localError = NULL; CFDataRef old_pk = SecDbItemGetPrimaryKey(old_item, error); CFDataRef new_pk = SecDbItemGetPrimaryKey(new_item, error); ok = old_pk && new_pk; bool pk_equal = ok && CFEqual(old_pk, new_pk); if (pk_equal) { ok = SecDbItemMakeYounger(new_item, old_item, error); } ok = ok && SecDbItemDoUpdate(old_item, new_item, dbconn, &localError, ^bool(const SecDbAttr *attr) { return attr->kind == kSecDbRowIdAttr; }); if (localError) { if(CFErrorGetCode(localError) == SQLITE_CONSTRAINT && CFEqual(kSecDbErrorDomain, CFErrorGetDomain(localError))) { /* Update failed because we changed the PrimaryKey and there was a dup. Find the dup and see if it is a tombstone or corrupted item. */ SecDbQueryRef query = SecDbQueryCreateWithItemPrimaryKey(new_item, error); ok = query; if (query) { ok &= SecDbItemSelect(query, dbconn, error, ^bool(const SecDbAttr *attr) { return attr->flags & kSecDbPrimaryKeyFlag; }, NULL, NULL, ^(SecDbItemRef duplicate_item, bool *stop) { bool is_corrupt = false; bool is_tomb = false; ok = SecDbItemIsCorrupt(duplicate_item, &is_corrupt, error); if (ok && !is_corrupt) { if ((is_tomb = SecDbItemIsTombstone(duplicate_item))) ok = SecDbItemMakeYounger(new_item, duplicate_item, error); } if (ok && (is_corrupt || is_tomb)) { ok = SecDbItemDoDelete(old_item, dbconn, error, ^bool (const SecDbAttr *attr) { return attr->kind == kSecDbRowIdAttr; }); ok = ok && SecDbItemDoUpdate(duplicate_item, new_item, dbconn, error, ^bool (const SecDbAttr *attr) { return attr->kind == kSecDbRowIdAttr; }); CFReleaseNull(localError); } }); ok &= query_destroy(query, error); } } if (localError) { ok = false; if (error && *error == NULL) { *error = localError; localError = NULL; } CFReleaseSafe(localError); } } if (ok && !pk_equal && makeTombstone) { /* The primary key of new_item is different than that of old_item, we have been asked to make a tombstone so leave one for the old_item. */ SecDbItemRef tombstone = SecDbItemCopyTombstone(old_item, error); ok = tombstone; if (tombstone) { ok = (SecDbItemClearRowId(tombstone, error) && SecDbItemDoInsert(tombstone, dbconn, error)); CFRelease(tombstone); } } return ok; } // Replace the object with a tombstone bool SecDbItemDelete(SecDbItemRef item, SecDbConnectionRef dbconn, bool makeTombstone, CFErrorRef *error) { bool ok = false; if (makeTombstone) { SecDbItemRef tombstone = SecDbItemCopyTombstone(item, error); if (tombstone) { ok = SecDbItemDoUpdate(item, tombstone, dbconn, error, ^bool(const SecDbAttr *attr) { return attr->kind == kSecDbRowIdAttr; }); CFRelease(tombstone); } } else { ok = SecDbItemDoDelete(item, dbconn, error, ^bool(const SecDbAttr *attr) { return attr->kind == kSecDbRowIdAttr; }); } return ok; } CFStringRef SecDbItemCopySelectSQL(SecDbQueryRef query, bool (^return_attr)(const SecDbAttr *attr), bool (^use_attr_in_where)(const SecDbAttr *attr), bool (^add_where_sql)(CFMutableStringRef sql, bool *needWhere)) { CFMutableStringRef sql = CFStringCreateMutable(kCFAllocatorDefault, 0); CFStringAppend(sql, CFSTR("SELECT ")); // What are we selecting? bool needComma = false; SecDbForEachAttr(query->q_class, attr) { if (return_attr(attr)) SecDbAppendElement(sql, attr->name, &needComma); } // From which table? CFStringAppend(sql, CFSTR(" FROM ")); CFStringAppend(sql, query->q_class->name); // And which elements do we want to select bool needWhere = true; SecDbForEachAttr(query->q_class, attr) { if (use_attr_in_where(attr)) { SecDbAppendWhereOrAndEquals(sql, attr->name, &needWhere); } } // Append SQL for access groups and limits. if (add_where_sql) add_where_sql(sql, &needWhere); return sql; } bool SecDbItemSelectBind(SecDbQueryRef query, sqlite3_stmt *stmt, CFErrorRef *error, bool (^use_attr_in_where)(const SecDbAttr *attr), bool (^bind_added_where)(sqlite3_stmt *stmt, int col)) { bool ok = true; int param = 0; SecDbForEachAttr(query->q_class, attr) { if (use_attr_in_where(attr)) { CFTypeRef value = CFDictionaryGetValue(query->q_item, attr->name); ok &= SecDbAttrBind(attr, stmt, ++param, value, error); if (!ok) break; } } // TODO: Bind arguments for access groups and limits. if (bind_added_where) bind_added_where(stmt, ++param); return ok; } bool SecDbItemSelect(SecDbQueryRef query, SecDbConnectionRef dbconn, CFErrorRef *error, bool (^use_attr_in_where)(const SecDbAttr *attr), bool (^add_where_sql)(CFMutableStringRef sql, bool *needWhere), bool (^bind_added_where)(sqlite3_stmt *stmt, int col), void (^handle_row)(SecDbItemRef item, bool *stop)) { __block bool ok = true; bool (^return_attr)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) { return attr->kind == kSecDbRowIdAttr || attr->kind == kSecDbEncryptedDataAttr; }; CFStringRef sql = SecDbItemCopySelectSQL(query, return_attr, use_attr_in_where, add_where_sql); if (sql) { ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) { ok = (SecDbItemSelectBind(query, stmt, error, use_attr_in_where, bind_added_where) && SecDbStep(dbconn, stmt, error, ^(bool *stop) { SecDbItemRef item = SecDbItemCreateWithStatement(kCFAllocatorDefault, query->q_class, stmt, query->q_keybag, error, return_attr); if (item) { handle_row(item, stop); CFRelease(item); } else { //*stop = true; //ok = false; } })); }); CFRelease(sql); } else { ok = false; } return ok; }