#include <Security/SecAccessControlPriv.h>
#include <securityd/SecItemSchema.h>
+#include <keychain/ckks/CKKS.h>
+
// MARK: type converters
CFStringRef copyString(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
- if (tid == CFStringGetTypeID())
+ if (tid == CFStringGetTypeID()) {
return CFStringCreateCopy(0, obj);
- else if (tid == CFDataGetTypeID())
+ }else if (tid == CFDataGetTypeID()) {
return CFStringCreateFromExternalRepresentation(0, obj, kCFStringEncodingUTF8);
- else
+ } else if (tid == CFUUIDGetTypeID()) {
+ return CFUUIDCreateString(NULL, obj);
+ } else {
return NULL;
+ }
}
CFDataRef copyData(CFTypeRef obj) {
}
}
+CFTypeRef copyUUID(CFTypeRef obj) {
+ CFTypeID tid = CFGetTypeID(obj);
+ if (tid == CFDataGetTypeID()) {
+ CFIndex length = CFDataGetLength(obj);
+ if (length != 0 && length != 16)
+ return NULL;
+ return CFDataCreateCopy(NULL, obj);
+ } else if (tid == CFNullGetTypeID()) {
+ return CFDataCreate(NULL, NULL, 0);
+ } else if (tid == CFUUIDGetTypeID()) {
+ CFUUIDBytes uuidbytes = CFUUIDGetUUIDBytes(obj);
+ CFDataRef uuiddata = CFDataCreate(NULL, (void*) &uuidbytes, sizeof(uuidbytes));
+ return uuiddata;
+ } else {
+ return NULL;
+ }
+}
+
+
CFTypeRef copyBlob(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
if (tid == CFDataGetTypeID()) {
}
}
-static CFStringRef SecDbColumnCopyString(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error) {
+static CFTypeRef SecDbColumnCopyString(CFAllocatorRef allocator, sqlite3_stmt *stmt, int col, CFErrorRef *error,
+ CFOptionFlags flags) {
const unsigned char *text = sqlite3_column_text(stmt, col);
+ if (!text || 0 == strlen((const char *)text)) {
+ if (flags & kSecDbDefaultEmptyFlag) {
+ return CFSTR("");
+ } else if (flags & kSecDbDefault0Flag) {
+ return CFSTR("0");
+ } else {
+ return kCFNull;
+ }
+ }
return CFStringCreateWithBytes(allocator, text, strlen((const char *)text), kCFStringEncodingUTF8, false);
}
#endif
static bool SecDbIsTombstoneDbUpdateAttr(const SecDbAttr *attr) {
- return SecDbIsTombstoneDbSelectAttr(attr) || attr->kind == kSecDbAccessAttr || attr->kind == kSecDbCreationDateAttr || attr->kind == kSecDbRowIdAttr;
+ // We add AuthenticatedData to include UUIDs, which can't be primary keys
+ return SecDbIsTombstoneDbSelectAttr(attr) || attr->kind == kSecDbAccessAttr || attr->kind == kSecDbCreationDateAttr || attr->kind == kSecDbRowIdAttr || (attr->flags & kSecDbInAuthenticatedDataFlag);
}
CFTypeRef SecDbAttrCopyDefaultValue(const SecDbAttr *attr, CFErrorRef *error) {
case kSecDbDataAttr:
value = CFDataCreate(kCFAllocatorDefault, NULL, 0);
break;
+ case kSecDbUUIDAttr:
+ value = CFDataCreate(kCFAllocatorDefault, NULL, 0);
+ break;
case kSecDbNumberAttr:
case kSecDbSyncAttr:
case kSecDbTombAttr:
if (attributes || auth_attributes) {
SecAccessControlRef access_control = SecDbItemCopyAccessControl(item, error);
if (access_control) {
- if (ks_encrypt_data(keybag, access_control, item->credHandle, attributes, auth_attributes, &edata, error)) {
+ if (ks_encrypt_data(keybag, access_control, item->credHandle, attributes, auth_attributes, &edata, false, error)) {
item->_edataState = kSecDbItemEncrypting;
} else {
seccritical("ks_encrypt_data (db): failed: %@", error ? *error : (CFErrorRef)CFSTR(""));
value = kCFNull;
}
break;
+ case kSecDbUUIDAttr:
+ value = CFDataCreate(CFGetAllocator(item), NULL, 0);
+ break;
case kSecDbNumberAttr:
case kSecDbSyncAttr:
case kSecDbTombAttr:
return value;
}
+CFTypeRef SecDbItemGetValueKind(SecDbItemRef item, SecDbAttrKind descKind, CFErrorRef *error) {
+ CFTypeRef result = NULL;
+
+ const SecDbClass * itemClass = SecDbItemGetClass(item);
+ const SecDbAttr * desc = SecDbClassAttrWithKind(itemClass, descKind, error);
+
+ if (desc) {
+ result = SecDbItemGetValue(item, desc, error);
+ }
+
+ return result;
+}
+
+
// Similar as SecDbItemGetValue, but if attr represents attribute stored into DB field as hash, returns
// hashed value for the attribute.
static CFTypeRef SecDbItemCopyValueForDb(SecDbItemRef item, const SecDbAttr *desc, CFErrorRef *error) {
CFTypeRef value = NULL;
CFStringRef hash_name = NULL;
hash_name = SecDbAttrGetHashName(desc);
- if ((desc->flags & (kSecDbSHA1ValueInFlag | kSecDbInFlag)) != 0) {
+ if ((desc->flags & kSecDbSHA1ValueInFlag) && (desc->flags & kSecDbInFlag)) {
value = CFRetainSafe(CFDictionaryGetValue(item->attributes, hash_name));
}
}
}
break;
+ case kSecDbUUIDAttr:
+ if ((value = SecDbItemGetValue(item, attr, NULL))) {
+ if (CFEqual(attr->name, kSecAttrMultiUser)) {
+ if (isData(value)) {
+ CFStringAppend(attrs, CFSTR(","));
+ if (CFDataGetLength(value)) {
+ CFStringAppendHexData(attrs, value);
+ } else {
+ CFStringAppend(attrs, attr->name);
+ }
+ }
+ }
+ }
+ break;
case kSecDbCreationDateAttr:
// We don't care about this and every object has one.
break;
item->keybag = keybag;
item->_edataState = kSecDbItemDirty;
item->cryptoOp = kAKSKeyOpDecrypt;
+
return item;
}
break;
case kSecDbUTombAttr:
attr = CFRetainSafe(asBoolean(value, NULL));
+ break;
+ case kSecDbUUIDAttr:
+ attr = copyUUID(value);
+ break;
}
if (attr) {
value = SecDbColumnCopyDouble(allocator, stmt, col, error);
break;
case SQLITE_TEXT:
- value = SecDbColumnCopyString(allocator, stmt, col, error);
+ value = SecDbColumnCopyString(allocator, stmt, col, error,
+ attr->flags);
break;
case SQLITE_BLOB:
value = SecDbColumnCopyData(allocator, stmt, col, error);
break;
case kSecDbAccessAttr:
case kSecDbStringAttr:
- value = SecDbColumnCopyString(allocator, stmt, col, error);
+ value = SecDbColumnCopyString(allocator, stmt, col, error,
+ attr->flags);
break;
case kSecDbDataAttr:
+ case kSecDbUUIDAttr:
case kSecDbSHA1Attr:
case kSecDbPrimaryKeyAttr:
case kSecDbEncryptedDataAttr:
SecDbForEachAttr(class, attr) {
if (return_attr(attr)) {
CFTypeRef value = SecDbColumnCopyValueWithAttr(allocator, stmt, attr, col++, error);
- if (value) {
- CFDictionarySetValue(item->attributes, SecDbAttrGetHashName(attr), value);
- CFRelease(value);
- }
+ require_action_quiet(value, errOut, CFReleaseNull(item));
+
+ CFDictionarySetValue(item->attributes, SecDbAttrGetHashName(attr), value);
+ CFRelease(value);
}
- const SecDbAttr *data_attr = SecDbClassAttrWithKind(class, kSecDbEncryptedDataAttr, error);
+ const SecDbAttr *data_attr = SecDbClassAttrWithKind(class, kSecDbEncryptedDataAttr, NULL);
if (data_attr != NULL && CFDictionaryGetValue(item->attributes, data_attr->name) != NULL) {
item->_edataState = kSecDbItemEncrypted;
}
}
+errOut:
return item;
}
bool SecDbItemInV2(SecDbItemRef item) {
const SecDbClass *iclass = SecDbItemGetClass(item);
return (SecDbItemGetCachedValueWithName(item, kSecAttrSyncViewHint) == NULL &&
- (iclass == &genp_class || iclass == &inet_class || iclass == &keys_class || iclass == &cert_class));
+ (iclass == genp_class() || iclass == inet_class() || iclass == keys_class() || iclass == cert_class()));
}
// Return true iff an item for which SecDbItemIsSyncable() and SecDbItemInV2() already return true should be part of the v0 view.
bool SecDbItemInV2AlsoInV0(SecDbItemRef item) {
- return (SecDbItemGetCachedValueWithName(item, kSecAttrTokenID) == NULL && SecDbItemGetClass(item) != &cert_class);
+ return (SecDbItemGetCachedValueWithName(item, kSecAttrTokenID) == NULL && SecDbItemGetClass(item) != cert_class());
}
SecDbItemRef SecDbItemCopyWithUpdates(SecDbItemRef item, CFDictionaryRef updates, CFErrorRef *error) {
return new_item;
}
+bool SecDbItemIsEngineInternalState(SecDbItemRef itemObject) {
+ // Only used for controlling logging
+ // Use agrp=com.apple.security.sos, since it is not encrypted
+ if (!itemObject) {
+ return false;
+ }
+ const SecDbAttr *agrp = SecDbAttrWithKey(SecDbItemGetClass(itemObject), kSecAttrAccessGroup, NULL);
+ CFTypeRef cfval = SecDbItemGetValue(itemObject, agrp, NULL);
+ return cfval && CFStringCompareSafe(cfval, kSOSInternalAccessGroup, NULL) == kCFCompareEqualTo;
+}
+
+
// MARK: -
// MARK: SQL Construction helpers -- These should become private in the future
return SecDbAppendWhereOrAndEquals(sql, col, needWhere);
SecDbAppendWhereOrAnd(sql, needWhere);
CFStringAppend(sql, col);
- CFStringAppend(sql, CFSTR(" in ("));
+ CFStringAppend(sql, CFSTR(" IN ("));
SecDbAppendCountArgsAndCloseParen(sql, count);
}
return SecDbAppendWhereOrAndNotEquals(sql, col, needWhere);
SecDbAppendWhereOrAnd(sql, needWhere);
CFStringAppend(sql, col);
- CFStringAppend(sql, CFSTR(" not in ("));
+ CFStringAppend(sql, CFSTR(" NOT IN ("));
SecDbAppendCountArgsAndCloseParen(sql, count);
}
return ok;
}
-static bool SecDbItemClearRowId(SecDbItemRef item, CFErrorRef *error) {
+bool SecDbItemClearRowId(SecDbItemRef item, CFErrorRef *error) {
bool ok = true;
const SecDbAttr *attr = SecDbClassAttrWithKind(item->class, kSecDbRowIdAttr, error);
if (attr) {
if (!dict)
return NULL;
- SecDbQueryRef query = query_create(item->class, NULL, error);
+ SecDbQueryRef query = query_create(item->class, NULL, NULL, error);
if (query) {
CFReleaseSafe(query->q_item);
query->q_item = dict;
if (ok) {
secnotice("item", "inserted %@", item);
SecDbItemRecordUpdate(dbconn, NULL, item);
+ } else {
+ if (SecDbItemIsEngineInternalState(item)) {
+ secdebug ("item", "insert failed for item %@ with %@", item, error ? *error : NULL);
+ } else {
+ secnotice("item", "insert failed for item %@ with %@", item, error ? *error : NULL);
+ }
}
return ok;
}
+bool SecErrorIsSqliteDuplicateItemError(CFErrorRef error) {
+ return error && CFErrorGetCode(error) == SQLITE_CONSTRAINT && CFEqual(kSecDbErrorDomain, CFErrorGetDomain(error));
+}
+
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);
CFRelease(sql);
}
if (ok) {
- secnotice("item", "replaced %@ with %@ in %@", old_item, new_item, dbconn);
+ if (SecDbItemIsEngineInternalState(old_item)) {
+ secdebug ("item", "replaced %@ in %@", old_item, dbconn);
+ secdebug ("item", " with %@ in %@", new_item, dbconn);
+ } else {
+ secnotice("item", "replaced %@ in %@", old_item, dbconn);
+ secnotice("item", " with %@ in %@", new_item, dbconn);
+ }
SecDbItemRecordUpdate(dbconn, old_item, new_item);
}
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, CFBooleanRef makeTombstone, CFErrorRef *error) {
+bool SecDbItemUpdate(SecDbItemRef old_item, SecDbItemRef new_item, SecDbConnectionRef dbconn, CFBooleanRef makeTombstone, bool uuid_from_primary_key, CFErrorRef *error) {
__block bool ok = true;
__block CFErrorRef localError = NULL;
bool pk_equal = ok && CFEqual(old_pk, new_pk);
if (pk_equal) {
ok = SecDbItemMakeYounger(new_item, old_item, error);
+ } else if(!CFEqualSafe(makeTombstone, kCFBooleanFalse) && SecCKKSIsEnabled()) {
+ // The primary keys aren't equal, and we're going to make a tombstone.
+ // Help CKKS out: the tombstone should have the existing item's UUID, and the newly updated item should have a new UUID.
+
+ s3dl_item_make_new_uuid(new_item, uuid_from_primary_key, error);
}
ok = ok && SecDbItemDoUpdate(old_item, new_item, dbconn, &localError, ^bool(const SecDbAttr *attr) {
return attr->kind == kSecDbRowIdAttr;