X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/sec/securityd/SecDbQuery.c diff --git a/Security/sec/securityd/SecDbQuery.c b/Security/sec/securityd/SecDbQuery.c new file mode 100644 index 00000000..98c6e539 --- /dev/null +++ b/Security/sec/securityd/SecDbQuery.c @@ -0,0 +1,842 @@ +/* + * Copyright (c) 2006-2014 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@ + */ + +/* + * SecDbQuery.c - CoreFoundation-based constants and functions for + access to Security items (certificates, keys, identities, and + passwords.) + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_KEYSTORE +#include +#include +#include +#endif + +/* Upper limit for number of keys in a QUERY dictionary. */ +#define QUERY_KEY_LIMIT_BASE (128) +#ifdef NO_SERVER +#define QUERY_KEY_LIMIT (31 + QUERY_KEY_LIMIT_BASE) +#else +#define QUERY_KEY_LIMIT QUERY_KEY_LIMIT_BASE +#endif + +/* Inline accessors to attr and match values in a query. */ +CFIndex query_attr_count(const Query *q) +{ + return q->q_attr_end; +} + +Pair query_attr_at(const Query *q, CFIndex ix) +{ + return q->q_pairs[ix]; +} + +CFIndex query_match_count(const Query *q) +{ + return q->q_match_end - q->q_match_begin; +} + +__unused static inline Pair query_match_at(const Query *q, CFIndex ix) +{ + return q->q_pairs[q->q_match_begin + ix]; +} + +/* Private routines used to parse a query. */ + +const SecDbClass *kc_class_with_name(CFStringRef name) { + if (isString(name)) { +#if 0 + // TODO Iterate kc_db_classes and look for name == class->name. + // Or get clever and switch on first letter of class name and compare to verify + static const void *kc_db_classes[] = { + &genp_class, + &inet_class, + &cert_class, + &keys_class, + &identity_class + }; +#endif + if (CFEqual(name, kSecClassGenericPassword)) + return &genp_class; + else if (CFEqual(name, kSecClassInternetPassword)) + return &inet_class; + else if (CFEqual(name, kSecClassCertificate)) + return &cert_class; + else if (CFEqual(name, kSecClassKey)) + return &keys_class; + else if (CFEqual(name, kSecClassIdentity)) + return &identity_class; + } + return NULL; +} + +static void query_set_access_control(Query *q, SecAccessControlRef access_control) { + if (q->q_access_control) { + if (!CFEqual(q->q_access_control, access_control)) { + SecError(errSecItemIllegalQuery, &q->q_error, CFSTR("conflicting kSecAccess and kSecAccessControl attributes")); + } + } else { + /* Store access control virtual attribute. */ + q->q_access_control = (SecAccessControlRef)CFRetain(access_control); + + /* Also set legacy access attribute. */ + CFDictionarySetValue(q->q_item, kSecAttrAccessible, SecAccessControlGetProtection(q->q_access_control)); + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, string or number of length 4. + value (ok) is a caller provided, non NULL CFTypeRef. + */ +static void query_add_attribute_with_desc(const SecDbAttr *desc, const void *value, Query *q) +{ + if (CFEqual(desc->name, kSecAttrSynchronizable)) { + q->q_sync = true; + if (CFEqual(value, kSecAttrSynchronizableAny)) + return; /* skip the attribute so it isn't part of the search */ + } + + CFTypeRef attr = NULL; + switch (desc->kind) { + case kSecDbDataAttr: + attr = copyData(value); + break; + case kSecDbBlobAttr: + case kSecDbAccessControlAttr: + attr = copyBlob(value); + break; + case kSecDbDateAttr: + case kSecDbCreationDateAttr: + case kSecDbModificationDateAttr: + attr = copyDate(value); + break; + case kSecDbNumberAttr: + case kSecDbSyncAttr: + case kSecDbTombAttr: + attr = copyNumber(value); + break; + case kSecDbAccessAttr: + case kSecDbStringAttr: + attr = copyString(value); + break; + case kSecDbSHA1Attr: + attr = copySHA1(value); + break; + case kSecDbRowIdAttr: + case kSecDbPrimaryKeyAttr: + case kSecDbEncryptedDataAttr: + break; + } + + if (!attr) { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("attribute %@: value: %@ failed to convert"), desc->name, value); + return; + } + + /* Store plaintext attr data in q_item unless it's a kSecDbSHA1Attr. */ + if (q->q_item && desc->kind != kSecDbSHA1Attr) { + CFDictionarySetValue(q->q_item, desc->name, attr); + } + + if (desc->kind == kSecDbAccessControlAttr) { + SecAccessControlRef access_control = SecAccessControlCreateFromData(kCFAllocatorDefault, attr, &q->q_error); + if (access_control) { + query_set_access_control(q, access_control); + CFRelease(access_control); + } + } + + if (desc->kind == kSecDbAccessAttr) { + SecAccessControlRef access_control = SecAccessControlCreateWithFlags(kCFAllocatorDefault, attr, 0, &q->q_error); + if (access_control) { + query_set_access_control(q, access_control); + CFRelease(access_control); + } + } + + /* Convert attr to (sha1) digest if requested. */ + if (desc->flags & kSecDbSHA1ValueInFlag) { + CFDataRef data = copyData(attr); + CFRelease(attr); + if (!data) { + SecError(errSecInternal, &q->q_error, CFSTR("failed to get attribute %@ data"), desc->name); + return; + } + + CFMutableDataRef digest = CFDataCreateMutable(0, CC_SHA1_DIGEST_LENGTH); + CFDataSetLength(digest, CC_SHA1_DIGEST_LENGTH); + /* 64 bits cast: worst case is we generate the wrong hash */ + assert((unsigned long)CFDataGetLength(data)kind != kSecDbAccessControlAttr) { + /* Record the new attr key, value in q_pairs. */ + q->q_pairs[q->q_attr_end].key = desc->name; + q->q_pairs[q->q_attr_end++].value = attr; + } else { + CFReleaseSafe(attr); + } +} + +void query_add_attribute(const void *key, const void *value, Query *q) +{ + const SecDbAttr *desc = SecDbAttrWithKey(q->q_class, key, &q->q_error); + if (desc) + query_add_attribute_with_desc(desc, value, q); +} + +/* First remove key from q->q_pairs if it's present, then add the attribute again. */ +static void query_set_attribute_with_desc(const SecDbAttr *desc, const void *value, Query *q) { + if (CFDictionaryContainsKey(q->q_item, desc->name)) { + CFIndex ix; + for (ix = 0; ix < q->q_attr_end; ++ix) { + if (CFEqual(desc->name, q->q_pairs[ix].key)) { + CFReleaseSafe(q->q_pairs[ix].value); + --q->q_attr_end; + for (; ix < q->q_attr_end; ++ix) { + q->q_pairs[ix] = q->q_pairs[ix + 1]; + } + CFDictionaryRemoveValue(q->q_item, desc->name); + break; + } + } + } + query_add_attribute_with_desc(desc, value, q); +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, string starting with 'm'. + value (ok) is a caller provided, non NULL CFTypeRef. + */ +static void query_add_match(const void *key, const void *value, Query *q) +{ + /* Record the match key, value in q_pairs. */ + --(q->q_match_begin); + q->q_pairs[q->q_match_begin].key = key; + q->q_pairs[q->q_match_begin].value = value; + + if (CFEqual(kSecMatchLimit, key)) { + /* Figure out what the value for kSecMatchLimit is if specified. */ + if (CFGetTypeID(value) == CFNumberGetTypeID()) { + if (!CFNumberGetValue(value, kCFNumberCFIndexType, &q->q_limit)) + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("failed to convert match limit %@ to CFIndex"), value); + } else if (CFEqual(kSecMatchLimitAll, value)) { + q->q_limit = kSecMatchUnlimited; + } else if (CFEqual(kSecMatchLimitOne, value)) { + q->q_limit = 1; + } else { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("unsupported match limit %@"), value); + } + } else if (CFEqual(kSecMatchIssuers, key) && + (CFGetTypeID(value) == CFArrayGetTypeID())) + { + CFMutableArrayRef canonical_issuers = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if (canonical_issuers) { + CFIndex i, count = CFArrayGetCount(value); + for (i = 0; i < count; i++) { + CFTypeRef issuer_data = CFArrayGetValueAtIndex(value, i); + CFDataRef issuer_canonical = NULL; + if (CFDataGetTypeID() == CFGetTypeID(issuer_data)) + issuer_canonical = SecDistinguishedNameCopyNormalizedContent((CFDataRef)issuer_data); + if (issuer_canonical) { + CFArrayAppendValue(canonical_issuers, issuer_canonical); + CFRelease(issuer_canonical); + } + } + + if (CFArrayGetCount(canonical_issuers) > 0) { + q->q_match_issuer = canonical_issuers; + } else + CFRelease(canonical_issuers); + } + } +} + +static bool query_set_class(Query *q, CFStringRef c_name, CFErrorRef *error) { + const SecDbClass *value; + if (c_name && CFGetTypeID(c_name) == CFStringGetTypeID() && + (value = kc_class_with_name(c_name)) && + (q->q_class == 0 || q->q_class == value)) { + q->q_class = value; + return true; + } + + if (error && !*error) + SecError((c_name ? errSecNoSuchClass : errSecItemClassMissing), error, CFSTR("can find class named: %@"), c_name); + + + return false; +} + +static const SecDbClass *query_get_class(CFDictionaryRef query, CFErrorRef *error) { + CFStringRef c_name = NULL; + const void *value = CFDictionaryGetValue(query, kSecClass); + if (isString(value)) { + c_name = value; + } else { + value = CFDictionaryGetValue(query, kSecValuePersistentRef); + if (isData(value)) { + CFDataRef pref = value; + _SecItemParsePersistentRef(pref, &c_name, 0); + } + } + + if (c_name && (value = kc_class_with_name(c_name))) { + return value; + } else { + if (c_name) + SecError(errSecNoSuchClass, error, CFSTR("can't find class named: %@"), c_name); + else + SecError(errSecItemClassMissing, error, CFSTR("query missing class name")); + return NULL; + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, string starting with 'c'. + value (ok) is a caller provided, non NULL CFTypeRef. + */ +static void query_add_class(const void *key, const void *value, Query *q) +{ + if (CFEqual(key, kSecClass)) { + query_set_class(q, value, &q->q_error); + } else { + SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_class: key %@ is not %@"), key, kSecClass); + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, string starting with 'r'. + value (ok) is a caller provided, non NULL CFTypeRef. + */ +static void query_add_return(const void *key, const void *value, Query *q) +{ + ReturnTypeMask mask; + if (CFGetTypeID(value) != CFBooleanGetTypeID()) { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_return: value %@ is not CFBoolean"), value); + return; + } + + int set_it = CFEqual(value, kCFBooleanTrue); + + if (CFEqual(key, kSecReturnData)) + mask = kSecReturnDataMask; + else if (CFEqual(key, kSecReturnAttributes)) + mask = kSecReturnAttributesMask; + else if (CFEqual(key, kSecReturnRef)) + mask = kSecReturnRefMask; + else if (CFEqual(key, kSecReturnPersistentRef)) + mask = kSecReturnPersistentRefMask; + else { + SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_return: unknown key %@"), key); + return; + } + + if ((q->q_return_type & mask) && !set_it) { + /* Clear out this bit (it's set so xor with the mask will clear it). */ + q->q_return_type ^= mask; + } else if (!(q->q_return_type & mask) && set_it) { + /* Set this bit. */ + q->q_return_type |= mask; + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, string starting with 'u'. + value (ok since q_use_item_list is unused) is a caller provided, non + NULL CFTypeRef. + */ +static void query_add_use(const void *key, const void *value, Query *q) +{ + if (CFEqual(key, kSecUseItemList)) { + /* TODO: Add sanity checking when we start using this. */ + q->q_use_item_list = value; + } else if (CFEqual(key, kSecUseTombstones)) { + if (CFGetTypeID(value) == CFBooleanGetTypeID()) { + q->q_use_tomb = value; + } else if (CFGetTypeID(value) == CFNumberGetTypeID()) { + q->q_use_tomb = CFBooleanGetValue(value) ? kCFBooleanTrue : kCFBooleanFalse; + } else if (CFGetTypeID(value) == CFStringGetTypeID()) { + q->q_use_tomb = CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse; + } else { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_use: value %@ for key %@ is neither CFBoolean nor CFNumber"), value, key); + return; + } + } else if (CFEqual(key, kSecUseCredentialReference)) { + if (isData(value)) { + CFRetainAssign(q->q_use_cred_handle, value); + } else { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_use: value %@ for key %@ is not CFData"), value, key); + return; + } + } else if (CFEqual(key, kSecUseOperationPrompt)) { + if (isString(value)) { + CFRetainAssign(q->q_use_operation_prompt, value); + } else { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_use: value %@ for key %@ is not CFString"), value, key); + return; + } + } else if (CFEqual(key, kSecUseNoAuthenticationUI)) { + if (isBoolean(value)) { + q->q_use_no_authentication_ui = value; + } else if (isNumber(value)) { + q->q_use_no_authentication_ui = CFBooleanGetValue(value) ? kCFBooleanTrue : kCFBooleanFalse; + } else if (isString(value)) { + q->q_use_no_authentication_ui = CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse; + } else { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_use: value %@ for key %@ is neither CFBoolean nor CFNumber"), value, key); + return; + } +#if defined(MULTIPLE_KEYCHAINS) + } else if (CFEqual(key, kSecUseKeychain)) { + q->q_use_keychain = value; + } else if (CFEqual(key, kSecUseKeychainList)) { + q->q_use_keychain_list = value; +#endif /* !defined(MULTIPLE_KEYCHAINS) */ + } else { + SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_use: unknown key %@"), key); + return; + } +} + +static void query_set_data(const void *value, Query *q) { + if (!isData(value)) { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("set_data: value %@ is not type data"), value); + } else { + q->q_data = value; + if (q->q_item) + CFDictionarySetValue(q->q_item, kSecValueData, value); + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, string starting with 'u'. + value (ok) is a caller provided, non NULL CFTypeRef. + */ +static void query_add_value(const void *key, const void *value, Query *q) +{ + if (CFEqual(key, kSecValueData)) { + query_set_data(value, q); +#ifdef NO_SERVER + } else if (CFEqual(key, kSecValueRef)) { + q->q_ref = value; + /* TODO: Add value type sanity checking. */ +#endif + } else if (CFEqual(key, kSecValuePersistentRef)) { + CFStringRef c_name; + if (_SecItemParsePersistentRef(value, &c_name, &q->q_row_id)) + query_set_class(q, c_name, &q->q_error); + else + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_value: value %@ is not a valid persitent ref"), value); + } else { + SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_value: unknown key %@"), key); + return; + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, unchecked. + value (ok) is a caller provided, unchecked. + */ +static void query_update_applier(const void *key, const void *value, + void *context) +{ + Query *q = (Query *)context; + /* If something went wrong there is no point processing any more args. */ + if (q->q_error) + return; + + /* Make sure we have a string key. */ + if (!isString(key)) { + SecError(errSecItemInvalidKeyType, &q->q_error, CFSTR("update_applier: unknown key type %@"), key); + return; + } + + if (!value) { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("update_applier: key %@ has NULL value"), key); + return; + } + + if (CFEqual(key, kSecValueData)) { + query_set_data(value, q); + } else { + query_add_attribute(key, value, q); + } +} + +/* AUDIT[securityd](done): + key (ok) is a caller provided, unchecked. + value (ok) is a caller provided, unchecked. + */ +static void query_applier(const void *key, const void *value, void *context) +{ + Query *q = (Query *)context; + /* If something went wrong there is no point processing any more args. */ + if (q->q_error) + return; + + /* Make sure we have a key. */ + if (!key) { + SecError(errSecItemInvalidKeyType, &q->q_error, CFSTR("applier: NULL key")); + return; + } + + /* Make sure we have a value. */ + if (!value) { + SecError(errSecItemInvalidValue, &q->q_error, CFSTR("applier: key %@ has NULL value"), key); + return; + } + + /* Figure out what type of key we are dealing with. */ + CFTypeID key_id = CFGetTypeID(key); + if (key_id == CFStringGetTypeID()) { + CFIndex key_len = CFStringGetLength(key); + /* String keys can be different things. The subtype is determined by: + length 4 strings are all attributes. Otherwise the first char + determines the type: + c: class must be kSecClass + m: match like kSecMatchPolicy + r: return like kSecReturnData + u: use keys + v: value + */ + if (key_len == 4) { + /* attributes */ + query_add_attribute(key, value, q); + } else if (key_len > 1) { + UniChar k_first_char = CFStringGetCharacterAtIndex(key, 0); + switch (k_first_char) + { + case 'c': /* class */ + query_add_class(key, value, q); + break; + case 'm': /* match */ + query_add_match(key, value, q); + break; + case 'r': /* return */ + query_add_return(key, value, q); + break; + case 'u': /* use */ + query_add_use(key, value, q); + break; + case 'v': /* value */ + query_add_value(key, value, q); + break; + default: + SecError(errSecItemInvalidKey, &q->q_error, CFSTR("applier: key %@ invalid"), key); + break; + } + } else { + SecError(errSecItemInvalidKey, &q->q_error, CFSTR("applier: key %@ invalid length"), key); + } + } else if (key_id == CFNumberGetTypeID()) { + /* Numeric keys are always (extended) attributes. */ + /* TODO: Why is this here? query_add_attribute() doesn't take numbers. */ + query_add_attribute(key, value, q); + } else { + /* We only support string and number type keys. */ + SecError(errSecItemInvalidKeyType, &q->q_error, CFSTR("applier: key %@ neither string nor number"), key); + } +} + +static CFStringRef query_infer_keyclass(Query *q, CFStringRef agrp) { + /* apsd and lockdown are always dku. */ + if (CFEqual(agrp, CFSTR("com.apple.apsd")) + || CFEqual(agrp, CFSTR("lockdown-identities"))) { + return kSecAttrAccessibleAlwaysThisDeviceOnly; + } + /* All other certs or in the apple agrp is dk. */ + if (q->q_class == &cert_class) { + /* third party certs are always dk. */ + return kSecAttrAccessibleAlways; + } + /* The rest defaults to ak. */ + return kSecAttrAccessibleWhenUnlocked; +} + +void query_ensure_access_control(Query *q, CFStringRef agrp) { + if (q->q_access_control == 0) { + CFStringRef accessible = query_infer_keyclass(q, agrp); + query_add_attribute(kSecAttrAccessible, accessible, q); + } +} + +bool query_error(Query *q, CFErrorRef *error) { + CFErrorRef tmp = q->q_error; + q->q_error = NULL; + return CFErrorPropagate(tmp, error); +} + +bool query_destroy(Query *q, CFErrorRef *error) { + bool ok = query_error(q, error); + CFIndex ix, attr_count = query_attr_count(q); + for (ix = 0; ix < attr_count; ++ix) { + CFReleaseSafe(query_attr_at(q, ix).value); + } + CFReleaseSafe(q->q_item); + CFReleaseSafe(q->q_primary_key_digest); + CFReleaseSafe(q->q_match_issuer); + CFReleaseSafe(q->q_access_control); + CFReleaseSafe(q->q_use_cred_handle); + CFReleaseSafe(q->q_required_access_controls); + CFReleaseSafe(q->q_use_operation_prompt); + CFReleaseSafe(q->q_caller_access_groups); + + free(q); + return ok; +} + +bool query_notify_and_destroy(Query *q, bool ok, CFErrorRef *error) { + if (ok && !q->q_error && q->q_sync_changed) { + SecKeychainChanged(true); + } + return query_destroy(q, error) && ok; +} + +/* Allocate and initialize a Query object for query. */ +Query *query_create(const SecDbClass *qclass, CFDictionaryRef query, + CFErrorRef *error) +{ + if (!qclass) { + if (error && !*error) + SecError(errSecItemClassMissing, error, CFSTR("Missing class")); + return NULL; + } + + /* Number of pairs we need is the number of attributes in this class + plus the number of keys in the dictionary, minus one for each key in + the dictionary that is a regular attribute. */ + CFIndex key_count = SecDbClassAttrCount(qclass); + if (key_count == 0) { + // Identities claim to have 0 attributes, but they really support any keys or cert attribute. + key_count = SecDbClassAttrCount(&cert_class) + SecDbClassAttrCount(&keys_class); + } + + if (query) { + key_count += CFDictionaryGetCount(query); + SecDbForEachAttr(qclass, attr) { + if (CFDictionaryContainsKey(query, attr->name)) + --key_count; + } + } + + if (key_count > QUERY_KEY_LIMIT) { + if (error && !*error) + { + secerror("key_count: %ld, QUERY_KEY_LIMIT: %d", (long)key_count, QUERY_KEY_LIMIT); + SecError(errSecItemIllegalQuery, error, CFSTR("Past query key limit")); + } + return NULL; + } + + Query *q = calloc(1, sizeof(Query) + sizeof(Pair) * key_count); + if (q == NULL) { + if (error && !*error) + SecError(errSecAllocate, error, CFSTR("Out of memory")); + return NULL; + } + + q->q_keybag = KEYBAG_DEVICE; + q->q_class = qclass; + q->q_match_begin = q->q_match_end = key_count; + q->q_item = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + q->q_crypto_op = kSecKsUnwrap; + + return q; +} + +/* Parse query for a Query object q. */ +static bool query_parse_with_applier(Query *q, CFDictionaryRef query, + CFDictionaryApplierFunction applier, + CFErrorRef *error) { + CFDictionaryApplyFunction(query, applier, q); + return query_error(q, error); +} + +/* Parse query for a Query object q. */ +static bool query_parse(Query *q, CFDictionaryRef query, + CFErrorRef *error) { + return query_parse_with_applier(q, query, query_applier, error); +} + +/* Parse query for a Query object q. */ +bool query_update_parse(Query *q, CFDictionaryRef update, + CFErrorRef *error) { + return query_parse_with_applier(q, update, query_update_applier, error); +} + +Query *query_create_with_limit(CFDictionaryRef query, CFIndex limit, + CFErrorRef *error) { + Query *q; + q = query_create(query_get_class(query, error), query, error); + if (q) { + q->q_limit = limit; + if (!query_parse(q, query, error)) { + query_destroy(q, error); + return NULL; + } + if (!q->q_sync && !q->q_row_id) { + /* query did not specify a kSecAttrSynchronizable attribute, + * and did not contain a persistent reference. */ + query_add_attribute(kSecAttrSynchronizable, kCFBooleanFalse, q); + } + } + return q; +} + + +//TODO: Move this to SecDbItemRef + +/* Make sure all attributes that are marked as not_null have a value. If + force_date is false, only set mdat and cdat if they aren't already set. */ +void +query_pre_add(Query *q, bool force_date) { + CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent()); + SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInFlag) { + if (desc->kind == kSecDbCreationDateAttr || + desc->kind == kSecDbModificationDateAttr) { + if (force_date) { + query_set_attribute_with_desc(desc, now, q); + } else if (!CFDictionaryContainsKey(q->q_item, desc->name)) { + query_add_attribute_with_desc(desc, now, q); + } + } else if ((desc->flags & kSecDbNotNullFlag) && + !CFDictionaryContainsKey(q->q_item, desc->name)) { + CFTypeRef value = NULL; + if (desc->flags & kSecDbDefault0Flag) { + if (desc->kind == kSecDbDateAttr) + value = CFDateCreate(kCFAllocatorDefault, 0.0); + else { + SInt32 vzero = 0; + value = CFNumberCreate(0, kCFNumberSInt32Type, &vzero); + } + } else if (desc->flags & kSecDbDefaultEmptyFlag) { + if (desc->kind == kSecDbDataAttr) + value = CFDataCreate(kCFAllocatorDefault, NULL, 0); + else { + value = CFSTR(""); + CFRetain(value); + } + } + if (value) { + /* Safe to use query_add_attribute here since the attr wasn't + set yet. */ + query_add_attribute_with_desc(desc, value, q); + CFRelease(value); + } + } + } + CFReleaseSafe(now); +} + +//TODO: Move this to SecDbItemRef + +/* Update modification_date if needed. */ +void +query_pre_update(Query *q) { + SecDbForEachAttr(q->q_class, desc) { + if (desc->kind == kSecDbModificationDateAttr) { + CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent()); + query_set_attribute_with_desc(desc, now, q); + CFReleaseSafe(now); + } + } +} + +void +query_set_caller_access_groups(Query *q, CFArrayRef caller_access_groups) { + CFRetainAssign(q->q_caller_access_groups, caller_access_groups); +} + +bool +query_needs_authentication(Query *q) { + return q->q_required_access_controls && CFArrayGetCount(q->q_required_access_controls) > 0; +} + +void +query_enable_interactive(Query *q) { + CFAssignRetained(q->q_required_access_controls, CFArrayCreateMutableForCFTypes(kCFAllocatorDefault)); +} + +bool +query_authenticate(Query *q, CFErrorRef **error) { + bool ok = true; + CFMutableDictionaryRef hints = NULL; +#if USE_KEYSTORE + if (q->q_use_no_authentication_ui && CFBooleanGetValue(q->q_use_no_authentication_ui)) { + SecError(errSecInteractionNotAllowed, *error, CFSTR("authentication UI is not allowed")); + return false; + } + if (isString(q->q_use_operation_prompt)) { + if (!hints) { + hints = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + } + // VRHintAppAction + CFNumberRef reasonKey = CFNumberCreateWithCFIndex(kCFAllocatorDefault, kLAOptionAuthenticationReason); + CFDictionaryAddValue(hints, reasonKey, q->q_use_operation_prompt); + CFRelease(reasonKey); + } + + if (*error) + CFReleaseNull(**error); + + CFErrorRef authError = NULL; + CFDataRef ac_data; + CFArrayForEachC(q->q_required_access_controls, ac_data) { + ok = VREvaluateACL(q->q_use_cred_handle, ac_data, kAKSKeyOpDecrypt, hints, &authError); + if (!ok) { + ok = SecCFCreateError(errSecAuthFailed, kSecErrorDomain, CFSTR("LocalAuthentication failed"), authError, *error); + CFReleaseSafe(authError); + goto out; + } + } + CFArrayRemoveAllValues(q->q_required_access_controls); + +out: +#endif + CFReleaseSafe(hints); + return ok; +}