X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/sec/Security/SecItem.c?ds=sidebyside diff --git a/OSX/sec/Security/SecItem.c b/OSX/sec/Security/SecItem.c new file mode 100644 index 00000000..6f95431d --- /dev/null +++ b/OSX/sec/Security/SecItem.c @@ -0,0 +1,1654 @@ +/* + * 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@ + */ + +/* + * SecItem.c - CoreFoundation-based constants and functions for + access to Security items (certificates, keys, identities, and + passwords.) + */ + +#include +#include +#include +#include +#include +#include +#ifndef SECITEM_SHIM_OSX +#include +#include +#include +#include +#include +#include +#include +#endif // *** END SECITEM_SHIM_OSX *** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef SECITEM_SHIM_OSX +#include +#endif // *** END SECITEM_SHIM_OSX *** + +/* label when certificate data is joined with key data */ +#define CERTIFICATE_DATA_COLUMN_LABEL "certdata" + +#include +#include + +#include +#include +#include + +#include + +/* Return an OSStatus for a sqlite3 error code. */ +static OSStatus osstatus_for_s3e(int s3e) +{ + if (s3e > 0 && s3e <= SQLITE_DONE) switch (s3e) + { + case SQLITE_OK: + return 0; + case SQLITE_ERROR: + return errSecNotAvailable; /* errSecDuplicateItem; */ + case SQLITE_FULL: /* Happens if we run out of uniqueids */ + return errSecNotAvailable; /* TODO: Replace with a better error code. */ + case SQLITE_PERM: + case SQLITE_READONLY: + return errSecNotAvailable; + case SQLITE_CANTOPEN: + return errSecNotAvailable; + case SQLITE_EMPTY: + return errSecNotAvailable; + case SQLITE_CONSTRAINT: + return errSecDuplicateItem; + case SQLITE_ABORT: + return -1; + case SQLITE_MISMATCH: + return errSecNoSuchAttr; + case SQLITE_AUTH: + return errSecNotAvailable; + case SQLITE_NOMEM: + return -2; /* TODO: Replace with a real error code. */ + case SQLITE_INTERNAL: + default: + return errSecNotAvailable; /* TODO: Replace with a real error code. */ + } + return s3e; +} + +static OSStatus osstatus_for_kern_return(CFIndex kernResult) +{ + switch (kernResult) + { + case KERN_SUCCESS: + return errSecSuccess; + case kIOReturnNotReadable: + case kIOReturnNotWritable: + return errSecAuthFailed; + case kIOReturnNotPermitted: + case kIOReturnNotPrivileged: + case kIOReturnLockedRead: + case kIOReturnLockedWrite: + return errSecInteractionNotAllowed; + case kIOReturnError: + return errSecDecode; + case kIOReturnBadArgument: + return errSecParam; + default: + return errSecNotAvailable; /* TODO: Replace with a real error code. */ + } +} + +static OSStatus osstatus_for_xpc_error(CFIndex xpcError) { + switch (xpcError) + { + case kSecXPCErrorSuccess: + return errSecSuccess; + case kSecXPCErrorUnexpectedType: + case kSecXPCErrorUnexpectedNull: + return errSecParam; + case kSecXPCErrorConnectionFailed: + return errSecNotAvailable; + case kSecXPCErrorUnknown: + default: + return errSecInternal; + } +} + +static OSStatus osstatus_for_der_error(CFIndex derError) { + switch (derError) + { + case kSecDERErrorUnknownEncoding: + case kSecDERErrorUnsupportedDERType: + case kSecDERErrorUnsupportedNumberType: + return errSecDecode; + case kSecDERErrorUnsupportedCFObject: + return errSecParam; + case kSecDERErrorAllocationFailure: + return errSecAllocate; + default: + return errSecInternal; + } +} + +static OSStatus osstatus_for_ids_error(CFIndex idsError) { + switch (idsError) + { + case kSecIDSErrorNoDeviceID: + return errSecDeviceIDNeeded; + case kSecIDSErrorNotRegistered: + return errSecIDSNotRegistered; + case kSecIDSErrorFailedToSend: + return errSecFailedToSendIDSMessage; + case kSecIDSErrorCouldNotFindMatchingAuthToken: + return errSecDeviceIDNoMatch; + default: + return errSecInternal; + } +} + +static OSStatus osstatus_for_localauthentication_error(CFIndex laError) { + // Wrap LA error in Sec error. + switch (laError) { + case kLAErrorUserCancel: + return errSecUserCanceled; + case kLAErrorParameter: + return errSecParam; + case kLAErrorNotInteractive: + return errSecInteractionNotAllowed; + default: + return errSecAuthFailed; + } +} + +static OSStatus osstatus_for_ctk_error(CFIndex ctkError) { + // Hack, get rid of it once dep lands: Export error code constants from ctkclient.h header +#ifndef kTKErrorCodeNotImplemented +#define kTKErrorCodeNotImplemented -1 +#endif +#ifndef kTKErrorCodeCanceledByUser +#define kTKErrorCodeCanceledByUser -4 +#endif + + switch (ctkError) { + case kTKErrorCodeBadParameter: + return errSecParam; + case kTKErrorCodeNotImplemented: + return errSecUnimplemented; + case kTKErrorCodeCanceledByUser: + return errSecUserCanceled; + default: + return errSecInternal; + } +} + +// Convert from securityd error codes to OSStatus for legacy API. +OSStatus SecErrorGetOSStatus(CFErrorRef error) { + OSStatus status; + if (error == NULL) { + status = errSecSuccess; + } else { + CFStringRef domain = CFErrorGetDomain(error); + if (domain == NULL) { + secerror("No error domain for error: %@", error); + status = errSecInternal; + } else if (CFEqual(kSecErrorDomain, domain)) { + status = (OSStatus)CFErrorGetCode(error); + } else if (CFEqual(kSecDbErrorDomain, domain)) { + status = osstatus_for_s3e((int)CFErrorGetCode(error)); + } else if (CFEqual(kSecErrnoDomain, domain)) { + status = (OSStatus)CFErrorGetCode(error); + } else if (CFEqual(kSecKernDomain, domain)) { + status = osstatus_for_kern_return(CFErrorGetCode(error)); + } else if (CFEqual(sSecXPCErrorDomain, domain)) { + status = osstatus_for_xpc_error(CFErrorGetCode(error)); + } else if (CFEqual(sSecDERErrorDomain, domain)) { + status = osstatus_for_der_error(CFErrorGetCode(error)); + } else if (CFEqual(kSecIDSErrorDomain, domain)) { + status = osstatus_for_ids_error(CFErrorGetCode(error)); + } else if (CFEqual(CFSTR(kLAErrorDomain), domain)) { + status = osstatus_for_localauthentication_error(CFErrorGetCode(error)); + } else if (CFEqual(CFSTR(kTKErrorDomain), domain)) { + status = osstatus_for_ctk_error(CFErrorGetCode(error)); + } else { + secnotice("securityd", "unknown error domain: %@ for error: %@", domain, error); + status = errSecInternal; + } + } + return status; +} + +// Wrapper to provide a CFErrorRef for legacy API. +OSStatus SecOSStatusWith(bool (^perform)(CFErrorRef *error)) { + CFErrorRef error = NULL; + OSStatus status; + if (perform(&error)) { + assert(error == NULL); + status = errSecSuccess; + } else { + assert(error); + status = SecErrorGetOSStatus(error); + if (status != errSecItemNotFound) // Occurs in normal operation, so exclude + secerror("error:[%" PRIdOSStatus "] %@", status, error); + CFReleaseNull(error); + } + return status; +} + +/* IPC uses CFPropertyList to un/marshall input/output data and can handle: + CFData, CFString, CFArray, CFDictionary, CFDate, CFBoolean, and CFNumber + + Currently in need of conversion below: + @@@ kSecValueRef allows SecKeychainItemRef and SecIdentityRef + @@@ kSecMatchPolicy allows a query with a SecPolicyRef + @@@ kSecUseItemList allows a query against a list of itemrefs, this isn't + currently implemented at all, but when it is needs to short circuit to + local evaluation, different from the sql query abilities +*/ + +#ifndef SECITEM_SHIM_OSX +static CFDictionaryRef +SecItemCopyAttributeDictionary(CFTypeRef ref) { + CFDictionaryRef refDictionary = NULL; + CFTypeID typeID = CFGetTypeID(ref); + if (typeID == SecKeyGetTypeID()) { + refDictionary = SecKeyCopyAttributeDictionary((SecKeyRef)ref); + } else if (typeID == SecCertificateGetTypeID()) { + refDictionary = + SecCertificateCopyAttributeDictionary((SecCertificateRef)ref); + } else if (typeID == SecIdentityGetTypeID()) { + assert(false); + SecIdentityRef identity = (SecIdentityRef)ref; + SecCertificateRef cert = NULL; + SecKeyRef key = NULL; + if (!SecIdentityCopyCertificate(identity, &cert) && + !SecIdentityCopyPrivateKey(identity, &key)) + { + CFDataRef data = SecCertificateCopyData(cert); + CFDictionaryRef key_dict = SecKeyCopyAttributeDictionary(key); + + if (key_dict && data) { + refDictionary = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, key_dict); + CFDictionarySetValue((CFMutableDictionaryRef)refDictionary, + CFSTR(CERTIFICATE_DATA_COLUMN_LABEL), data); + } + CFReleaseNull(key_dict); + CFReleaseNull(data); + } + CFReleaseNull(cert); + CFReleaseNull(key); + } else { + refDictionary = NULL; + } + return refDictionary; +} + +static CFTypeRef +SecItemCreateFromAttributeDictionary(CFDictionaryRef refAttributes) { + CFTypeRef ref = NULL; + CFStringRef class = CFDictionaryGetValue(refAttributes, kSecClass); + if (CFEqual(class, kSecClassCertificate)) { + ref = SecCertificateCreateFromAttributeDictionary(refAttributes); + } else if (CFEqual(class, kSecClassKey)) { + ref = SecKeyCreateFromAttributeDictionary(refAttributes); + } else if (CFEqual(class, kSecClassIdentity)) { + CFAllocatorRef allocator = NULL; + CFDataRef data = CFDictionaryGetValue(refAttributes, CFSTR(CERTIFICATE_DATA_COLUMN_LABEL)); + SecCertificateRef cert = SecCertificateCreateWithData(allocator, data); + SecKeyRef key = SecKeyCreateFromAttributeDictionary(refAttributes); + if (key && cert) + ref = SecIdentityCreate(allocator, cert, key); + CFReleaseSafe(cert); + CFReleaseSafe(key); +#if 0 + /* We don't support SecKeychainItemRefs yet. */ + } else if (CFEqual(class, kSecClassGenericPassword)) { + } else if (CFEqual(class, kSecClassInternetPassword)) { + } else if (CFEqual(class, kSecClassAppleSharePassword)) { +#endif + } else { + ref = NULL; + } + return ref; +} +#else + +extern CFTypeRef SecItemCreateFromAttributeDictionary(CFDictionaryRef refAttributes); + +#endif + +OSStatus +SecItemCopyDisplayNames(CFArrayRef items, CFArrayRef *displayNames) +{ + // @@@ TBI + return -1 /* errSecUnimplemented */; +} + +#ifndef SECITEM_SHIM_OSX +typedef OSStatus (*secitem_operation)(CFDictionaryRef attributes, CFTypeRef *result); + +static bool explode_identity(CFDictionaryRef attributes, secitem_operation operation, + OSStatus *return_status, CFTypeRef *return_result) +{ + bool handled = false; + CFTypeRef value = CFDictionaryGetValue(attributes, kSecValueRef); + if (value) { + CFTypeID typeID = CFGetTypeID(value); + if (typeID == SecIdentityGetTypeID()) { + handled = true; + OSStatus status = errSecSuccess; + SecIdentityRef identity = (SecIdentityRef)value; + SecCertificateRef cert = NULL; + SecKeyRef key = NULL; + if (!SecIdentityCopyCertificate(identity, &cert) && + !SecIdentityCopyPrivateKey(identity, &key)) + { + CFMutableDictionaryRef partial_query = + CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, attributes); + CFDictionarySetValue(partial_query, kSecValueRef, cert); + CFTypeRef result = NULL; + bool duplicate_cert = false; + /* an identity is first and foremost a key, but it can have multiple + certs associated with it: so we identify it by the cert */ + status = operation(partial_query, return_result ? &result : NULL); + if ((operation == (secitem_operation)SecItemAdd) && + (status == errSecDuplicateItem)) { + duplicate_cert = true; + status = errSecSuccess; + } + + if (!status || status == errSecItemNotFound) { + bool skip_key_operation = false; + + /* if the key is still in use, skip deleting it */ + if (operation == (secitem_operation)SecItemDelete) { + // find certs with cert.pkhh == keys.klbl + CFDictionaryRef key_dict = NULL, query_dict = NULL; + CFDataRef pkhh = NULL; + + key_dict = SecKeyCopyAttributeDictionary(key); + if (key_dict) + pkhh = (CFDataRef)CFDictionaryGetValue(key_dict, kSecAttrApplicationLabel); + const void *keys[] = { kSecClass, kSecAttrPublicKeyHash }; + const void *vals[] = { kSecClassCertificate, pkhh }; + if (pkhh) + query_dict = CFDictionaryCreate(NULL, keys, + vals, (array_size(keys)), + NULL, NULL); + if (query_dict) + if (errSecSuccess == SecItemCopyMatching(query_dict, NULL)) + skip_key_operation = true; + CFReleaseSafe(query_dict); + CFReleaseSafe(key_dict); + } + + if (!skip_key_operation) { + /* now perform the operation for the key */ + CFDictionarySetValue(partial_query, kSecValueRef, key); + CFDictionarySetValue(partial_query, kSecReturnPersistentRef, kCFBooleanFalse); + status = operation(partial_query, NULL); + if ((operation == (secitem_operation)SecItemAdd) && + (status == errSecDuplicateItem) && + !duplicate_cert) + status = errSecSuccess; + } + + /* add and copy matching for an identityref have a persistent ref result */ + if (result) { + if (!status) { + /* result is a persistent ref to a cert */ + sqlite_int64 rowid; + if (_SecItemParsePersistentRef(result, NULL, &rowid)) { + *return_result = _SecItemMakePersistentRef(kSecClassIdentity, rowid); + } + } + CFRelease(result); + } + } + CFReleaseNull(partial_query); + } + else + status = errSecInvalidItemRef; + + CFReleaseNull(cert); + CFReleaseNull(key); + *return_status = status; + } + } else { + value = CFDictionaryGetValue(attributes, kSecClass); + if (value && CFEqual(kSecClassIdentity, value) && + (operation == (secitem_operation)SecItemDelete)) { + CFMutableDictionaryRef dict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, attributes); + CFDictionaryRemoveValue(dict, kSecClass); + CFDictionarySetValue(dict, kSecClass, kSecClassCertificate); + OSStatus status = SecItemDelete(dict); + if (!status) { + CFDictionarySetValue(dict, kSecClass, kSecClassKey); + status = SecItemDelete(dict); + } + CFRelease(dict); + *return_status = status; + handled = true; + } + } + return handled; +} + +static void infer_cert_label(SecCFDictionaryCOW *attributes) +{ + if (!CFDictionaryContainsKey(attributes->dictionary, kSecAttrLabel)) { + CFTypeRef value_ref = CFDictionaryGetValue(attributes->dictionary, kSecValueRef); + if (value_ref && CFGetTypeID(value_ref) == SecCertificateGetTypeID()) { + SecCertificateRef certificate = (SecCertificateRef)value_ref; + CFStringRef label = SecCertificateCopySubjectSummary(certificate); + if (label) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(attributes), kSecAttrLabel, label); + CFReleaseNull(label); + } + } + } +} + +/* A persistent ref is just the class and the rowid of the record. */ +CF_RETURNS_RETAINED CFDataRef _SecItemMakePersistentRef(CFTypeRef class, sqlite_int64 rowid) +{ + uint8_t bytes[sizeof(sqlite_int64) + 4]; + if (rowid < 0) + return NULL; + if (CFStringGetCString(class, (char *)bytes, 4 + 1 /*null-term*/, + kCFStringEncodingUTF8)) + { + OSWriteBigInt64(bytes + 4, 0, rowid); + return CFDataCreate(NULL, bytes, sizeof(bytes)); + } + return NULL; +} + +/* AUDIT[securityd](done): + persistent_ref (ok) is a caller provided, non NULL CFTypeRef. + */ +bool _SecItemParsePersistentRef(CFDataRef persistent_ref, CFStringRef *return_class, sqlite_int64 *return_rowid) +{ + bool valid_ref = false; + if (CFGetTypeID(persistent_ref) == CFDataGetTypeID() && + CFDataGetLength(persistent_ref) == (CFIndex)(sizeof(sqlite_int64) + 4)) { + const uint8_t *bytes = CFDataGetBytePtr(persistent_ref); + sqlite_int64 rowid = OSReadBigInt64(bytes + 4, 0); + + CFStringRef class = CFStringCreateWithBytes(kCFAllocatorDefault, + bytes, CFStringGetLength(kSecClassGenericPassword), + kCFStringEncodingUTF8, true); + const void *valid_classes[] = { kSecClassGenericPassword, + kSecClassInternetPassword, + kSecClassAppleSharePassword, + kSecClassCertificate, + kSecClassKey, + kSecClassIdentity }; + + unsigned i; + for (i=0; i< array_size(valid_classes); i++) { + if (CFEqual(valid_classes[i], class)) { + if (return_class) + *return_class = valid_classes[i]; + if (return_rowid) + *return_rowid = rowid; + valid_ref = true; + break; + } + } + CFRelease(class); + } + return valid_ref; +} + +#endif // *** END SECITEM_SHIM_OSX *** + +static bool cf_bool_value(CFTypeRef cf_bool) +{ + return (cf_bool && CFEqual(kCFBooleanTrue, cf_bool)); +} + +CFMutableDictionaryRef SecCFDictionaryCOWGetMutable(SecCFDictionaryCOW *cow_dictionary) { + if (cow_dictionary->mutable_dictionary == NULL) { + cow_dictionary->mutable_dictionary = CFDictionaryCreateMutableForCFTypes(NULL); + if (cow_dictionary->dictionary != NULL) { + CFDictionaryForEach(cow_dictionary->dictionary, ^(const void *key, const void *value) { + CFDictionarySetValue(cow_dictionary->mutable_dictionary, key, value); + }); + } + cow_dictionary->dictionary = cow_dictionary->mutable_dictionary; + } + + return cow_dictionary->mutable_dictionary; +} + +// Keys for dictionary of kSecvalueData of token-based items. +static const CFStringRef kSecTokenValueObjectIDKey = CFSTR("oid"); +static const CFStringRef kSecTokenValueAccessControlKey = CFSTR("ac"); +static const CFStringRef kSecTokenValueDataKey = CFSTR("data"); + +// Creates kSecValueData field stored in the DB for token-based items. Data field consists of objectID, real +// access_control and optionally of the data value. +static CFDataRef SecTokenItemValueCreate(CFDataRef oid, CFDataRef access_control, CFDataRef object_value, CFErrorRef *error) { + CFMutableDictionaryRef value = NULL; + value = CFDictionaryCreateMutableForCFTypesWith(NULL, + kSecTokenValueObjectIDKey, oid, + kSecTokenValueAccessControlKey, access_control, + NULL); + if (object_value != NULL) { + CFDictionarySetValue(value, kSecTokenValueDataKey, object_value); + } + + CFDataRef value_data = CFPropertyListCreateDERData(NULL, value, error); + CFRelease(value); + return value_data; +} + +static CFDictionaryRef SecTokenItemValueCopy(CFDataRef db_value, CFErrorRef *error) { + CFPropertyListRef plist = NULL; + const uint8_t *der = CFDataGetBytePtr(db_value); + const uint8_t *der_end = der + CFDataGetLength(db_value); + require_quiet(der = der_decode_plist(0, kCFPropertyListImmutable, &plist, error, der, der_end), out); + require_action_quiet(der == der_end, out, SecError(errSecDecode, error, CFSTR("trailing garbage at end of token data field"))); + require_action_quiet(CFDictionaryGetValue(plist, kSecTokenValueObjectIDKey) != NULL, out, + SecError(errSecInternal, error, CFSTR("token based item data does not have OID"))); + +out: + return plist; +} + +TKTokenRef SecTokenCreate(CFStringRef token_id, CFDictionaryRef auth_params, CFErrorRef *error) { + CFMutableDictionaryRef token_attrs = NULL; + TKTokenRef token = NULL; + token_attrs = (auth_params != NULL) ? + CFDictionaryCreateMutableCopy(NULL, 0, auth_params) : + CFDictionaryCreateMutableForCFTypes(NULL); + CFDictionarySetValue(token_attrs, kSecAttrTokenID, token_id); + + CFDictionaryRemoveValue(token_attrs, kSecUseAuthenticationContext); + token = TKTokenCreate(token_attrs, error); + + CFReleaseSafe(token_attrs); + return token; +} + +static bool SecTokenItemCreateFromAttributes(CFDictionaryRef attributes, CFDictionaryRef auth_params, + TKTokenRef token, CFDataRef object_id, CFTypeRef *ref, CFErrorRef *error) { + bool ok = false; + CFMutableDictionaryRef attrs = CFDictionaryCreateMutableCopy(NULL, 0, attributes); + CFTypeRef token_id = CFDictionaryGetValue(attributes, kSecAttrTokenID); + if (token_id != NULL) { + if (CFRetainSafe(token) == NULL) { + require_quiet(token = SecTokenCreate(token_id, auth_params, error), out); + } + + if (auth_params != NULL) { + CFDictionaryForEach(auth_params, ^(const void *key, const void *value) { + CFDictionarySetValue(attrs, key, value); + }); + } + CFDictionarySetValue(attrs, kSecUseToken, token); + CFDictionarySetValue(attrs, kSecUseTokenObjectID, object_id); + CFRelease(token); + } + *ref = SecItemCreateFromAttributeDictionary(attrs); + ok = true; + +out: + CFReleaseSafe(attrs); + return ok; +} + + +/* Turn the returned single value or dictionary that contains all the attributes to create a + ref into the exact result the client asked for */ +static bool SecItemResultCopyPrepared(CFTypeRef raw_result, TKTokenRef token, + CFDictionaryRef query, CFDictionaryRef auth_params, + CFTypeRef *result, CFErrorRef *error) { + bool ok = false; + CFDataRef ac_data = NULL; + CFDataRef value = NULL; + CFTypeRef persistent_ref = NULL; + CFStringRef token_id = NULL; + CFDataRef object_id = NULL; + CFMutableDictionaryRef attrs = NULL; + + bool wants_ref = cf_bool_value(CFDictionaryGetValue(query, kSecReturnRef)); + bool wants_data = cf_bool_value(CFDictionaryGetValue(query, kSecReturnData)); + bool wants_attributes = cf_bool_value(CFDictionaryGetValue(query, kSecReturnAttributes)); + bool wants_persistent_ref = cf_bool_value(CFDictionaryGetValue(query, kSecReturnPersistentRef)); + + // Get token value if not provided by the caller. + bool token_item = false; + if (token == NULL) { + if (CFGetTypeID(raw_result) == CFDictionaryGetTypeID()) { + token_id = CFDictionaryGetValue(raw_result, kSecAttrTokenID); + token_item = (token_id != NULL); + } + } else { + token_item = true; + CFRetain(token); + } + + // Decode and prepare data value, if it is requested at the output, or if we want attributes from token. + if (wants_data || wants_ref || (token_item && wants_attributes)) { + if (CFGetTypeID(raw_result) == CFDictionaryGetTypeID()) + value = CFRetainSafe(CFDictionaryGetValue(raw_result, kSecValueData)); + else + value = CFRetainSafe(raw_result); + if (token_item && value != NULL) { + // Parse token-based item's data field. + CFDataRef object_value = NULL; + CFDictionaryRef parsed_value = NULL; + require_quiet(parsed_value = SecTokenItemValueCopy(value, error), out); + object_id = CFRetainSafe(CFDictionaryGetValue(parsed_value, kSecTokenValueObjectIDKey)); + ac_data = CFRetainSafe(CFDictionaryGetValue(parsed_value, kSecTokenValueAccessControlKey)); + object_value = CFRetainSafe(CFDictionaryGetValue(parsed_value, kSecTokenValueDataKey)); + CFRelease(parsed_value); + if (wants_data && object_value == NULL) { + // Retrieve value directly from the token. + if (token == NULL) { + require_quiet(token = SecTokenCreate(token_id, auth_params, error), out); + } + require_quiet(object_value = TKTokenCopyObjectData(token, object_id, error), out); + if (CFEqual(object_value, kCFNull)) + CFReleaseNull(object_value); + } + CFAssignRetained(value, object_value); + } + + // If only thing requested is data, return them directly. + if (!(wants_attributes || wants_persistent_ref || wants_ref)) { + *result = CFRetainSafe(value); + ok = true; + goto out; + } + } + + // Extract persistent_ref, if caller wants it. + if (wants_persistent_ref) { + if (CFGetTypeID(raw_result) == CFDictionaryGetTypeID()) + persistent_ref = CFRetainSafe(CFDictionaryGetValue(raw_result, kSecValuePersistentRef)); + else + persistent_ref = CFRetainSafe(raw_result); + + // If only thing requested is persistentref, extract it from dictionary if needed and return it. + if (!(wants_attributes || wants_data || wants_ref)) { + *result = CFRetainSafe(persistent_ref); + ok = true; + goto out; + } + } + + if (wants_ref || wants_attributes || (wants_data && wants_persistent_ref)) { + // For these cases we need output dictionary. + if (CFGetTypeID(raw_result) == CFDictionaryGetTypeID()) + *result = CFDictionaryCreateMutableCopy(NULL, 0, raw_result); + else + *result = CFDictionaryCreateForCFTypes(NULL, NULL); + CFMutableDictionaryRef output = (CFMutableDictionaryRef)*result; + + if ((wants_data || wants_ref) && value != NULL) + CFDictionarySetValue(output, kSecValueData, value); + else + CFDictionaryRemoveValue(output, kSecValueData); + + if (wants_persistent_ref && persistent_ref != NULL) + CFDictionarySetValue(output, kSecValuePersistentRef, persistent_ref); + else + CFDictionaryRemoveValue(output, kSecValuePersistentRef); + + if (wants_ref) { + CFTypeRef ref; + require_quiet(SecTokenItemCreateFromAttributes(output, auth_params, token, object_id, &ref, error), out); + if (!(wants_attributes || wants_data || wants_persistent_ref)) { + CFAssignRetained(*result, ref); + } else if (ref != NULL) { + CFDictionarySetValue(output, kSecValueRef, ref); + CFRelease(ref); + if (!wants_data) { + // We could have stored data value previously to make ref creation succeed. + // They are not needed any more and in case that caller did not want the data, avoid returning them. + CFDictionaryRemoveValue(output, kSecValueData); + } + } + } + + if (wants_attributes) { + // Convert serialized form of access control to object form. + if (!token_item) { + CFRetainAssign(ac_data, CFDictionaryGetValue(output, kSecAttrAccessControl)); + } + + if (ac_data != NULL) { + SecAccessControlRef ac; + require_quiet(ac = SecAccessControlCreateFromData(kCFAllocatorDefault, ac_data, error), out); + CFDictionarySetValue(output, kSecAttrAccessControl, ac); + CFRelease(ac); + } + } + } else { + *result = NULL; + } + + ok = true; + +out: + CFReleaseSafe(ac_data); + CFReleaseSafe(value); + CFReleaseSafe(persistent_ref); + CFReleaseSafe(object_id); + CFReleaseSafe(attrs); + CFReleaseSafe(token); + return ok; +} + +static bool SecItemResultProcess(CFDictionaryRef query, CFDictionaryRef auth_params, TKTokenRef token, + CFTypeRef raw_result, CFTypeRef *result, CFErrorRef *error) { + bool ok = false; + require_action_quiet(raw_result != NULL, out, ok = true); + require_action_quiet(result != NULL, out, ok = true); + + if (CFGetTypeID(raw_result) == CFArrayGetTypeID()) { + CFIndex i, count = CFArrayGetCount(raw_result); + *result = CFArrayCreateMutableForCFTypes(NULL); + for (i = 0; i < count; i++) { + CFTypeRef ref; + require_quiet(SecItemResultCopyPrepared(CFArrayGetValueAtIndex(raw_result, i), + token, query, auth_params, &ref, error), out); + if (ref != NULL) { + CFArrayAppendValue((CFMutableArrayRef)*result, ref); + CFRelease(ref); + } + } + } else { + require_quiet(SecItemResultCopyPrepared(raw_result, token, query, auth_params, result, error), out); + } + + ok = true; + +out: + return ok; +} + +static bool SecItemAttributesPrepare(SecCFDictionaryCOW *attrs, CFErrorRef *error) { + bool ok = false; + CFDataRef ac_data = NULL, acm_context = NULL; + void *la_lib = NULL; + + SecAccessControlRef access_control = (SecAccessControlRef)CFDictionaryGetValue(attrs->dictionary, kSecAttrAccessControl); + if (access_control != NULL) { + require_quiet(ac_data = SecAccessControlCopyData(access_control), out); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(attrs), kSecAttrAccessControl, ac_data); + } + + const CFTypeRef la_context = CFDictionaryGetValue(attrs->dictionary, kSecUseAuthenticationContext); + if (la_context) { + require_action_quiet(!CFDictionaryContainsKey(attrs->dictionary, kSecUseCredentialReference), out, + SecError(errSecParam, error, CFSTR("kSecUseAuthenticationContext cannot be used together with kSecUseCredentialReference"))); + require_action_quiet(la_lib = dlopen("/System/Library/Frameworks/LocalAuthentication.framework/LocalAuthentication", RTLD_LAZY), out, + SecError(errSecInternal, error, CFSTR("failed to open LocalAuthentication.framework"))); + LAFunctionCopyExternalizedContext fnCopyExternalizedContext = NULL; + require_action_quiet(fnCopyExternalizedContext = dlsym(la_lib, "LACopyExternalizedContext"), out, + SecError(errSecInternal, error, CFSTR("failed to obtain LACopyExternalizedContext"))); + require_action_quiet(acm_context = fnCopyExternalizedContext(la_context), out, + SecError(errSecInternal, error, CFSTR("failed to get ACM handle from LAContext"))); + CFDictionaryRemoveValue(SecCFDictionaryCOWGetMutable(attrs), kSecUseAuthenticationContext); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(attrs), kSecUseCredentialReference, acm_context); + } + +#ifndef SECITEM_SHIM_OSX + // If a ref was specified we get it's attribute dictionary and parse it. + CFTypeRef value = CFDictionaryGetValue(attrs->dictionary, kSecValueRef); + if (value) { + CFDictionaryRef ref_attributes; + require_action_quiet(ref_attributes = SecItemCopyAttributeDictionary(value), out, + SecError(errSecValueRefUnsupported, error, CFSTR("unsupported kSecValueRef in query"))); + + /* Replace any attributes we already got from the ref with the ones + from the attributes dictionary the caller passed us. This allows + a caller to add an item using attributes from the ref and still + override some of them in the dictionary directly. */ + CFMutableDictionaryRef new_query = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, ref_attributes); + CFRelease(ref_attributes); + CFDictionaryForEach(attrs->dictionary, ^(const void *key, const void *value) { + if (!CFEqual(key, kSecValueRef)) + CFDictionarySetValue(new_query, key, value); + }); + CFAssignRetained(attrs->mutable_dictionary, new_query); + attrs->dictionary = attrs->mutable_dictionary; + } + value = CFDictionaryGetValue(attrs->dictionary, kSecAttrIssuer); + if (value) { + /* convert DN to canonical issuer, if value is DN (top level sequence) */ + const DERItem name = { (unsigned char *)CFDataGetBytePtr(value), CFDataGetLength(value) }; + DERDecodedInfo content; + if (!DERDecodeItem(&name, &content) && + (content.tag == ASN1_CONSTR_SEQUENCE)) + { + CFDataRef canonical_issuer = createNormalizedX501Name(kCFAllocatorDefault, &content.content); + if (canonical_issuer) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(attrs), kSecAttrIssuer, canonical_issuer); + CFRelease(canonical_issuer); + } + } + } +#endif + + ok = true; + +out: + if (la_lib != NULL) { + dlclose(la_lib); + } + CFReleaseSafe(ac_data); + CFReleaseSafe(acm_context); + return ok; +} + +static bool SecItemAuthMaxAttemptsReached(CFArrayRef ac_pairs, CFErrorRef *error) +{ + CFMutableStringRef log_string = CFStringCreateMutable(kCFAllocatorDefault, 0); + CFArrayRef ac_pair; + CFArrayForEachC(ac_pairs, ac_pair) { + CFStringRef acl_hex_string = CFDataCopyHexString(CFArrayGetValueAtIndex(ac_pair, 0)); + CFStringRef str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("operation: %@ acl:%@\n"), CFArrayGetValueAtIndex(ac_pair, 1), acl_hex_string); + CFStringAppend(log_string, str); + CFRelease(acl_hex_string); + CFRelease(str); + } + + CFStringRef reason = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("Reached maximum count of authentication attempts\n %@"), log_string); + SecError(errSecAuthFailed, error, reason); + __security_simulatecrash(reason, __sec_exception_code_AuthLoop); + + CFRelease(reason); + CFRelease(log_string); + return false; +} + +bool SecItemAuthDo(SecCFDictionaryCOW *auth_params, CFErrorRef *error, SecItemAuthResult (^perform)(CFDictionaryRef auth_params, CFArrayRef *ac_pairs, CFErrorRef *error)) { + bool ok = false; + CFArrayRef ac_pairs = NULL; + SecCFDictionaryCOW auth_options = { NULL }; + + for (uint32_t i = 0;; ++i) { + // If the operation succeeded or failed with other than auth-needed error, just leave. + SecItemAuthResult auth_result = perform(auth_params->dictionary, &ac_pairs, error); + require_quiet(auth_result != kSecItemAuthResultError, out); + require_action_quiet(auth_result == kSecItemAuthResultNeedAuth, out, ok = true); + + // If auth_params were not created up to now, do create them because we will definitely need them. + SecCFDictionaryCOWGetMutable(auth_params); + + // Retrieve or create authentication handle and/or ACM context. + CFTypeRef auth_handle = CFDictionaryGetValue(auth_params->dictionary, kSecUseAuthenticationContext); + if (auth_handle == NULL) { + CFDataRef acm_context = CFDictionaryGetValue(auth_params->dictionary, kSecUseCredentialReference); + require_quiet(auth_handle = LACreateNewContextWithACMContext(acm_context, error), out); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(auth_params), kSecUseAuthenticationContext, auth_handle); + CFRelease(auth_handle); + if (acm_context == NULL) { + require_quiet(acm_context = LACopyACMContext(auth_handle, error), out); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(auth_params), kSecUseCredentialReference, acm_context); + CFRelease(acm_context); + } + } + + // Throttle max authentication attempts. This is mainly protection against exceptional states, not ordinary + // user retry limit. + require_action(i < 20, out, SecItemAuthMaxAttemptsReached(ac_pairs, error)); + + // Prepare auth options dictionary. + if (auth_options.dictionary == NULL) { + CFStringRef operation_prompt = CFDictionaryGetValue(auth_params->dictionary, kSecUseOperationPrompt); + if (operation_prompt != NULL) { + CFNumberRef key = CFNumberCreateWithCFIndex(NULL, kLAOptionAuthenticationReason); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(&auth_options), key, operation_prompt); + CFRelease(key); + } + + CFTypeRef auth_ui = CFDictionaryGetValue(auth_params->dictionary, kSecUseAuthenticationUI); + if (CFEqualSafe(auth_ui, kSecUseAuthenticationUIFail)) { + CFNumberRef key = CFNumberCreateWithCFIndex(NULL, kLAOptionNotInteractive); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(&auth_options), key, kCFBooleanTrue); + CFRelease(key); + } + } + + // Go through all access_control-operation pairs and evaluate them. + CFArrayRef ac_pair; + CFArrayForEachC(ac_pairs, ac_pair) { + CFDataRef updated_acl = NULL; + require_quiet(LAEvaluateAndUpdateACL(auth_handle, + CFArrayGetValueAtIndex(ac_pair, 0), CFArrayGetValueAtIndex(ac_pair, 1), + auth_options.dictionary, &updated_acl, error), out); + + if (updated_acl || CFEqual(CFArrayGetValueAtIndex(ac_pair, 1), CFSTR(""))) { + // we assume that only one ACL can be modified during ItemAdd or ItemUpdate + SecAccessControlRef ac = NULL; + require(ac = SecAccessControlCreateFromData(kCFAllocatorDefault, + updated_acl ? updated_acl : CFArrayGetValueAtIndex(ac_pair, 0), error), out); + SecAccessControlSetBound(ac, true); + CFAssignRetained(updated_acl, SecAccessControlCopyData(ac)); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(auth_params), kSecAttrAccessControl, updated_acl); + CFRelease(updated_acl); + CFRelease(ac); + } + } + } + + ok = true; + +out: + CFReleaseSafe(auth_options.mutable_dictionary); + CFReleaseSafe(ac_pairs); + return ok; +} + +void SecItemAuthCopyParams(SecCFDictionaryCOW *auth_params, SecCFDictionaryCOW *query) { + // Store operation prompt. + CFStringRef operation_prompt = CFDictionaryGetValue(query->dictionary, kSecUseOperationPrompt); + if (operation_prompt != NULL) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(auth_params), kSecUseOperationPrompt, operation_prompt); + CFDictionaryRemoveValue(SecCFDictionaryCOWGetMutable(query), kSecUseOperationPrompt); + } + + // Find out whether we are allowed to pop up a UI. + CFTypeRef auth_ui = CFDictionaryGetValue(query->dictionary, kSecUseAuthenticationUI) ?: + (CFEqualSafe(CFDictionaryGetValue(query->dictionary, kSecUseNoAuthenticationUI), kCFBooleanTrue) ? + kSecUseAuthenticationUIFail : kSecUseAuthenticationUIAllow); + if (!CFEqual(auth_ui, kSecUseAuthenticationUISkip) || CFDictionaryGetValue(query->dictionary, kSecUseNoAuthenticationUI)) { + if (CFDictionaryContainsKey(query->dictionary, kSecUseNoAuthenticationUI)) + CFDictionaryRemoveValue(SecCFDictionaryCOWGetMutable(query), kSecUseNoAuthenticationUI); + if (!CFEqualSafe(auth_ui, kSecUseAuthenticationUISkip) && CFDictionaryContainsKey(query->dictionary, kSecUseAuthenticationUI)) + CFDictionaryRemoveValue(SecCFDictionaryCOWGetMutable(query), kSecUseAuthenticationUI); + } + + if (!CFEqual(auth_ui, kSecUseAuthenticationUIAllow)) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(auth_params), kSecUseAuthenticationUI, auth_ui); + } + + CFDataRef acm_context = CFDictionaryGetValue(query->dictionary, kSecUseCredentialReference); + if (acm_context != NULL) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(auth_params), kSecUseCredentialReference, acm_context); + } +} + +static SecItemAuthResult SecItemCreatePairsFromError(CFErrorRef *error, CFArrayRef *ac_pairs) +{ + if (error && CFErrorGetCode(*error) == errSecAuthNeeded && CFEqualSafe(CFErrorGetDomain(*error), kSecErrorDomain)) { + // Extract ACLs to be verified from the error. + CFDictionaryRef user_info = CFErrorCopyUserInfo(*error); + CFNumberRef key = CFNumberCreateWithCFIndex(NULL, errSecAuthNeeded); + CFRetainAssign(*ac_pairs, CFDictionaryGetValue(user_info, key)); + if (*ac_pairs == NULL) + CFAssignRetained(*ac_pairs, CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + + CFRelease(key); + CFRelease(user_info); + CFReleaseNull(*error); + return kSecItemAuthResultNeedAuth; + } + return kSecItemAuthResultError; +} + +// Wrapper to handle automatic authentication and token/secd case switching. +static bool SecItemAuthDoQuery(SecCFDictionaryCOW *query, SecCFDictionaryCOW *attributes, const void *secItemOperation, CFErrorRef *error, + bool (^perform)(TKTokenRef token, CFDictionaryRef query, CFDictionaryRef attributes, CFDictionaryRef auth_params, CFErrorRef *error)) { + bool ok = false; + SecCFDictionaryCOW auth_params = { NULL }; + SecAccessControlRef access_control = NULL; + __block TKTokenRef token = NULL; + + if (secItemOperation == SecItemAdd || secItemOperation == SecItemUpdate) { + CFDictionaryRef dict = attributes ? attributes->dictionary : query->dictionary; + access_control = (SecAccessControlRef)CFDictionaryGetValue(dict, kSecAttrAccessControl); + if (access_control && SecAccessControlGetConstraints(access_control) && + CFEqualSafe(CFDictionaryGetValue(dict, kSecAttrSynchronizable), kCFBooleanTrue)) + require_quiet(SecError(errSecParam, error, CFSTR("item with kSecAttrAccessControl is not synchronizable")), out); + } + + // Perform initial surgery on query/attributes (resolve LAContext to serialized ACM handle, resolve + // SecAccessControlRef to serialized forms, expand kSecValueRef etc.) + require_quiet(SecItemAttributesPrepare(query, error), out); + if (attributes != NULL) + require_quiet(SecItemAttributesPrepare(attributes, error), out); + + // Populate auth_params dictionary according to initial query contents. + SecItemAuthCopyParams(&auth_params, query); + + if (secItemOperation != SecItemCopyMatching) { + // UISkip is allowed only for CopyMatching. + require_action_quiet(!CFEqualSafe(CFDictionaryGetValue(query->dictionary, kSecUseAuthenticationUI), kSecUseAuthenticationUISkip), out, + SecError(errSecParam, error, + CFSTR("kSecUseAuthenticationUISkip is allowed only for SecItemCopyMatching"))); + } + + ok = SecItemAuthDo(&auth_params, error, ^SecItemAuthResult(CFDictionaryRef auth_params, CFArrayRef *ac_pairs, CFErrorRef *error) { + SecItemAuthResult result = kSecItemAuthResultError; + + // Propagate actual credential reference to the query. + if (auth_params != NULL) { + CFDataRef acm_context = CFDictionaryGetValue(auth_params, kSecUseCredentialReference); + if (acm_context != NULL) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(query), kSecUseCredentialReference, acm_context); + } + + CFDataRef acl_data_ref = CFDictionaryGetValue(auth_params, kSecAttrAccessControl); + if (acl_data_ref != NULL) { + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(attributes ?: query), kSecAttrAccessControl, acl_data_ref); + } + } + + // Prepare connection to target token if it is present. + CFStringRef token_id = CFDictionaryGetValue(query->dictionary, kSecAttrTokenID); + if (secItemOperation != SecItemCopyMatching && token_id != NULL) { + require_quiet(CFAssignRetained(token, SecTokenCreate(token_id, auth_params, error)), out); + } + + CFDictionaryRef attrs = (attributes != NULL) ? attributes->dictionary : NULL; + if(!perform(token, query->dictionary, attrs, auth_params, error)) { + require_quiet((result = SecItemCreatePairsFromError(error, ac_pairs)) == kSecItemAuthResultOK, out); + } + + result = kSecItemAuthResultOK; + + out: + return result; + }); + require_quiet(ok, out); + + ok = true; + +out: + CFReleaseSafe(token); + CFReleaseSafe(auth_params.mutable_dictionary); + return ok; +} + +#if SECITEM_SHIM_OSX +/* TODO: Should be in some header */ +OSStatus SecItemAdd_ios(CFDictionaryRef attributes, CFTypeRef *result); +OSStatus SecItemCopyMatching_ios(CFDictionaryRef query, CFTypeRef *result); +OSStatus SecItemUpdate_ios(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); +OSStatus SecItemDelete_ios(CFDictionaryRef query); +#endif + +static bool cftype_to_bool_cftype_error_request(enum SecXPCOperation op, CFTypeRef attributes, CFTypeRef *result, CFErrorRef *error) +{ + return securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { + return SecXPCDictionarySetPList(message, kSecXPCKeyQuery, attributes, error); + }, ^bool(xpc_object_t response, CFErrorRef *error) { + if (result) { + return SecXPCDictionaryCopyPListOptional(response, kSecXPCKeyResult, result, error); + } + return true; + }); +} + +static CFArrayRef dict_to_array_error_request(enum SecXPCOperation op, CFDictionaryRef attributes, CFErrorRef *error) +{ + CFArrayRef result = NULL; + bool success = cftype_to_bool_cftype_error_request(op, attributes, (CFTypeRef*)&result, error); + if(success && !isArray(result)){ + SecError(errSecUnimplemented, error, CFSTR("Unexpected nonarray returned: %@"), result); + CFReleaseNull(result); + } + return result; +} + +bool cftype_ag_to_bool_cftype_error_request(enum SecXPCOperation op, CFTypeRef attributes, __unused CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) { + return cftype_to_bool_cftype_error_request(op, attributes, result, error); +} + +static bool dict_to_error_request(enum SecXPCOperation op, CFDictionaryRef query, CFErrorRef *error) +{ + return securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) { + return SecXPCDictionarySetPList(message, kSecXPCKeyQuery, query, error); + }, NULL); +} + +static bool dict_ag_to_error_request(enum SecXPCOperation op, CFDictionaryRef query, __unused CFArrayRef accessGroups, CFErrorRef *error) +{ + return dict_to_error_request(op, query, error); +} + +static bool SecTokenCreateAccessControlError(CFStringRef operation, CFDataRef access_control, CFErrorRef *error) { + CFArrayRef ac_pair = CFArrayCreateForCFTypes(NULL, access_control, operation, NULL); + const void *ac_pairs[] = { CFArrayCreateForCFTypes(NULL, ac_pair, NULL) }; + const void *keys[] = { CFNumberCreateWithCFIndex(NULL, errSecAuthNeeded) }; + CFAssignRetained(*error, CFErrorCreateWithUserInfoKeysAndValues(NULL, kSecErrorDomain, errSecAuthNeeded, + keys, ac_pairs, 1)); + CFRelease(keys[0]); + CFRelease(ac_pairs[0]); + CFRelease(ac_pair); + return false; +} + +static bool SecTokenProcessError(CFStringRef operation, TKTokenRef token, CFTypeRef object_or_attrs, CFErrorRef *error) { + if (CFEqualSafe(CFErrorGetDomain(*error), CFSTR(kTKErrorDomain)) && + CFErrorGetCode(*error) == kTKErrorCodeAuthenticationFailed) { + // Replace error with the one which is augmented with access control and operation which failed, + // which will cause SecItemDoWithAuth to throw UI. + // Create array containing tuple (array) with error and requested operation. + CFDataRef access_control = (CFGetTypeID(object_or_attrs) == CFDataGetTypeID()) ? + TKTokenCopyObjectAccessControl(token, object_or_attrs, error) : + TKTokenCopyObjectCreationAccessControl(token, object_or_attrs, error); + if (access_control != NULL) { + SecTokenCreateAccessControlError(operation, access_control, error); + CFRelease(access_control); + } + } + return false; +} + +static CFTypeRef SecTokenCopyUpdatedObjectID(TKTokenRef token, CFDataRef object_id, CFMutableDictionaryRef attributes, CFErrorRef *error) { + CFDataRef access_control = NULL, db_value = NULL, new_object_id = NULL; + SecAccessControlRef ac = NULL; + + // Make sure that ACL is bound - if not, generate an error which will trigger binding. + CFDataRef ac_data = CFDictionaryGetValue(attributes, kSecAttrAccessControl); + if (ac_data != NULL) { + require_quiet(ac = SecAccessControlCreateFromData(NULL, ac_data, error), out); + require_action_quiet(SecAccessControlIsBound(ac), out, + SecTokenCreateAccessControlError(CFSTR(""), ac_data, error)); + } + + // Create or update the object on the token. + require_action_quiet(new_object_id = TKTokenCreateOrUpdateObject(token, object_id, attributes, error), out, + SecTokenProcessError(kAKSKeyOpEncrypt, token, object_id ?: (CFTypeRef)attributes, error)); + + // Prepare kSecValueData field for the item to be stored into the keychain DB. + require_quiet(access_control = TKTokenCopyObjectAccessControl(token, new_object_id, error), out); + require_quiet(db_value = SecTokenItemValueCreate(new_object_id, access_control, + CFDictionaryGetValue(attributes, kSecValueData), error), out); + CFDictionarySetValue(attributes, kSecValueData, db_value); + + // kSecAttrAccessControl is handled directly by the token and stored inside data field. + CFDictionaryRemoveValue(attributes, kSecAttrAccessControl); + +out: + CFReleaseSafe(ac); + CFReleaseSafe(access_control); + CFReleaseSafe(db_value); + return new_object_id; +} + +static bool SecTokenItemAdd(TKTokenRef token, CFDictionaryRef attributes, CFDictionaryRef auth_params, + CFTypeRef *result, CFErrorRef *error) { + bool ok = false; + CFTypeRef object_id = NULL, ref = NULL; + CFDictionaryRef ref_attrs = NULL; + CFTypeRef db_result = NULL; + + CFMutableDictionaryRef attrs = CFDictionaryCreateMutableCopy(NULL, 0, attributes); + require_quiet(object_id = SecTokenCopyUpdatedObjectID(token, NULL, attrs, error), out); + +#ifndef SECITEM_SHIM_OSX + // Augment attributes from default attributes of the related ref (SecKeyRef, SecCertificateRef). This is best done + // by creating ref and getting back its attributes. + require_quiet(SecTokenItemCreateFromAttributes(attrs, auth_params, token, object_id, &ref, error), out); + if (ref != NULL) { + if ((ref_attrs = SecItemCopyAttributeDictionary(ref)) != NULL) { + CFDictionaryForEach(ref_attrs, ^(const void *key, const void *value) { + if (!CFEqual(key, kSecValueData)) { + CFDictionaryAddValue(attrs, key, value); + } + }); + } + } +#endif + + // Make sure that both attributes and data are returned. + CFDictionarySetValue(attrs, kSecReturnAttributes, kCFBooleanTrue); + CFDictionarySetValue(attrs, kSecReturnData, kCFBooleanTrue); + + if (!CFEqualSafe(CFDictionaryGetValue(attrs, kSecAttrIsPermanent), kCFBooleanFalse)) { + // IsPermanent is not present or is true, so add item to the db. + require_quiet(SECURITYD_XPC(sec_item_add, cftype_ag_to_bool_cftype_error_request, attrs, + SecAccessGroupsGetCurrent(), &db_result, error), out); + } else { + // Process directly result of token call. + db_result = CFRetain(attrs); + } + require_quiet(SecItemResultProcess(attributes, auth_params, token, db_result, result, error), out); + + ok = true; + +out: + CFReleaseSafe(db_result); + CFReleaseSafe(attrs); + CFReleaseSafe(ref_attrs); + CFReleaseSafe(object_id); + CFReleaseSafe(ref); + return ok; +} + +OSStatus +#if SECITEM_SHIM_OSX +SecItemAdd_ios(CFDictionaryRef attributes, CFTypeRef *result) +#else +SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) +#endif // *** END SECITEM_SHIM_OSX *** +{ + __block SecCFDictionaryCOW attrs = { attributes }; + OSStatus status; + + os_activity_t trace_activity = os_activity_start("SecItemAdd_ios", OS_ACTIVITY_FLAG_DEFAULT); + +#ifndef SECITEM_SHIM_OSX + require_quiet(!explode_identity(attrs.dictionary, (secitem_operation)SecItemAdd, &status, result), errOut); + infer_cert_label(&attrs); +#endif // *** END SECITEM_SHIM_OSX *** + + status = SecOSStatusWith(^bool(CFErrorRef *error) { + return SecItemAuthDoQuery(&attrs, NULL, SecItemAdd, error, ^bool(TKTokenRef token, CFDictionaryRef attributes, CFDictionaryRef unused, CFDictionaryRef auth_params, CFErrorRef *error) { + if (token == NULL) { + CFTypeRef raw_result = NULL; + if (!SECURITYD_XPC(sec_item_add, cftype_ag_to_bool_cftype_error_request, attributes, SecAccessGroupsGetCurrent(), &raw_result, error)) + return false; + + bool ok = SecItemResultProcess(attributes, auth_params, token, raw_result, result, error); + CFReleaseSafe(raw_result); + return ok; + } else { + // Send request to an appropriate token instead of secd. + return SecTokenItemAdd(token, attributes, auth_params, result, error); + } + }); + }); + +#ifndef SECITEM_SHIM_OSX +errOut: +#endif + CFReleaseSafe(attrs.mutable_dictionary); + + os_activity_end(trace_activity); + return status; +} + + +OSStatus +#if SECITEM_SHIM_OSX +SecItemCopyMatching_ios(CFDictionaryRef inQuery, CFTypeRef *result) +#else +SecItemCopyMatching(CFDictionaryRef inQuery, CFTypeRef *result) +#endif // *** END SECITEM_SHIM_OSX *** +{ + OSStatus status; + __block SecCFDictionaryCOW query = { inQuery }; + + os_activity_t trace_activity = os_activity_start("SecItemCopyMatching_ios", OS_ACTIVITY_FLAG_DEFAULT); + +#ifndef SECITEM_SHIM_OSX + require_quiet(!explode_identity(query.dictionary, (secitem_operation)SecItemCopyMatching, &status, result), errOut); +#endif // *** END SECITEM_SHIM_OSX *** + + bool wants_data = cf_bool_value(CFDictionaryGetValue(query.dictionary, kSecReturnData)); + bool wants_attributes = cf_bool_value(CFDictionaryGetValue(query.dictionary, kSecReturnAttributes)); + if ((wants_data && !wants_attributes) || (!wants_data && wants_attributes)) { + // When either attributes or data are requested, we need to query both, because for token based items, + // both are needed in order to generate proper data and/or attributes results. + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(&query), kSecReturnAttributes, kCFBooleanTrue); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(&query), kSecReturnData, kCFBooleanTrue); + } + + status = SecOSStatusWith(^bool(CFErrorRef *error) { + return SecItemAuthDoQuery(&query, NULL, SecItemCopyMatching, error, ^bool(TKTokenRef token, CFDictionaryRef query, CFDictionaryRef attributes, CFDictionaryRef auth_params, CFErrorRef *error) { + CFTypeRef raw_result = NULL; + if (!SECURITYD_XPC(sec_item_copy_matching, cftype_ag_to_bool_cftype_error_request, query, SecAccessGroupsGetCurrent(), &raw_result, error)) + return false; + + // We intentionally pass NULL as token argument, because we want to be able to decide about token on which the item lives + // on per-record basis, not wholesale. Logic inside SecItemResultCopyPrepared will open proper token according + // to currently processed item. + bool ok = SecItemResultProcess(inQuery, auth_params, NULL, raw_result, result, error); + CFReleaseSafe(raw_result); + return ok; + }); + }); + +#ifndef SECITEM_SHIM_OSX +errOut: +#endif + + CFReleaseSafe(query.mutable_dictionary); + os_activity_end(trace_activity); + return status; +} + +// Invokes token-object handler for each item matching specified query. +static bool SecTokenItemForEachMatching(CFDictionaryRef query, CFErrorRef *error, + bool (^perform)(CFDictionaryRef item_value, CFDictionaryRef item_query, + CFErrorRef *error)) { + bool ok = false; + CFMutableDictionaryRef list_query = NULL; + CFTypeRef items = NULL; + CFArrayRef ref_array = NULL; + CFDictionaryRef item_query = NULL, item_data = NULL; + + // Query all items with data and persistent_ref, so that we can extract objectIDs and also identify originating + // items in the keychain. + list_query = CFDictionaryCreateMutableCopy(NULL, 0, query); + if (CFDictionaryGetValue(list_query, kSecMatchLimit) == NULL) { + CFDictionarySetValue(list_query, kSecMatchLimit, kSecMatchLimitAll); + } + CFDictionarySetValue(list_query, kSecReturnData, kCFBooleanTrue); + CFDictionarySetValue(list_query, kSecReturnPersistentRef, kCFBooleanTrue); + require_quiet(SECURITYD_XPC(sec_item_copy_matching, cftype_ag_to_bool_cftype_error_request, list_query, + SecAccessGroupsGetCurrent(), &items, error), out); + if (CFGetTypeID(items) != CFArrayGetTypeID()) { + // Wrap single returned item into the array. + CFArrayRef item_array = CFArrayCreateForCFTypes(NULL, items, NULL); + CFAssignRetained(items, item_array); + } + + CFTypeRef item; + CFArrayForEachC(items, item) { + CFDataRef data = CFDictionaryGetValue(item, kSecValueData); + require_action_quiet(data != NULL, out, SecError(errSecInternal, error, CFSTR("value not present for token item"))); + + CFAssignRetained(item_data, SecTokenItemValueCopy(data, error)); + require_quiet(item_data, out); + + CFAssignRetained(item_query, + CFDictionaryCreateForCFTypes(NULL, + kSecValuePersistentRef, CFDictionaryGetValue(item, kSecValuePersistentRef), + NULL)); + require_quiet(perform(item_data, item_query, error), out); + } + + ok = true; + +out: + CFReleaseSafe(list_query); + CFReleaseSafe(items); + CFReleaseSafe(item_data); + CFReleaseSafe(ref_array); + CFReleaseSafe(item_query); + return ok; +} + +static bool SecItemRawUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate, CFErrorRef *error) { + bool ok = false; + if (gSecurityd) { + // Ensure the dictionary passed to securityd has proper kCFTypeDictionaryKeyCallBacks. + CFMutableDictionaryRef tmp = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL); + CFDictionaryForEach(attributesToUpdate, ^(const void *key, const void *value) { CFDictionaryAddValue(tmp, key, value); }); + ok = gSecurityd->sec_item_update(query, tmp, SecAccessGroupsGetCurrent(), error); + CFRelease(tmp); + } else { + xpc_object_t message = securityd_create_message(sec_item_update_id, error); + if (message) { + if (SecXPCDictionarySetPList(message, kSecXPCKeyQuery, query, error) && + SecXPCDictionarySetPList(message, kSecXPCKeyAttributesToUpdate, attributesToUpdate, error)) { + xpc_object_t reply = securityd_message_with_reply_sync(message, error); + if (reply) { + ok = securityd_message_no_error(reply, error); + xpc_release(reply); + } + } + xpc_release(message); + } + } + return ok; +} + +static bool SecTokenItemUpdate(TKTokenRef token, CFDictionaryRef query, CFDictionaryRef attributesToUpdate, CFErrorRef *error) { + return SecTokenItemForEachMatching(query, error, ^bool(CFDictionaryRef object_data, CFDictionaryRef item_query, CFErrorRef *error) { + bool ok = false; + CFDataRef object_id = NULL; + CFMutableDictionaryRef db_value = NULL; + + // Update attributes on the token. + CFMutableDictionaryRef attributes = CFDictionaryCreateMutableCopy(NULL, 0, attributesToUpdate); + require_quiet(object_id = SecTokenCopyUpdatedObjectID(token, CFDictionaryGetValue(object_data, kSecTokenValueObjectIDKey), + attributes, error), out); + + // Update attributes in the database. + require_quiet(SecItemRawUpdate(item_query, attributes, error), out); + + ok = true; + + out: + CFReleaseSafe(object_id); + CFReleaseSafe(attributes); + CFReleaseSafe(db_value); + return ok; + }); +} + +OSStatus +#if SECITEM_SHIM_OSX +SecItemUpdate_ios(CFDictionaryRef inQuery, CFDictionaryRef inAttributesToUpdate) +#else +SecItemUpdate(CFDictionaryRef inQuery, CFDictionaryRef inAttributesToUpdate) +#endif // *** END SECITEM_SHIM_OSX *** +{ + OSStatus status; + __block SecCFDictionaryCOW query = { inQuery }; + __block SecCFDictionaryCOW attributesToUpdate = { inAttributesToUpdate }; + + status = SecOSStatusWith(^bool(CFErrorRef *error) { + return SecItemAuthDoQuery(&query, &attributesToUpdate, SecItemUpdate, error, ^bool(TKTokenRef token, CFDictionaryRef query, CFDictionaryRef attributes, CFDictionaryRef auth_params, CFErrorRef *error) { + if (token == NULL) { + return SecItemRawUpdate(query, attributes, error); + } else { + return SecTokenItemUpdate(token, query, attributes, error); + } + }); + }); + + CFReleaseSafe(query.mutable_dictionary); + CFReleaseSafe(attributesToUpdate.mutable_dictionary); + return status; +} + +#ifndef SECITEM_SHIM_OSX +static OSStatus explode_persistent_identity_ref(SecCFDictionaryCOW *query) +{ + OSStatus status = errSecSuccess; + CFTypeRef persist = CFDictionaryGetValue(query->dictionary, kSecValuePersistentRef); + CFStringRef class; + if (persist && _SecItemParsePersistentRef(persist, &class, NULL) + && CFEqual(class, kSecClassIdentity)) { + const void *keys[] = { kSecReturnRef, kSecValuePersistentRef }; + const void *vals[] = { kCFBooleanTrue, persist }; + CFDictionaryRef persistent_query = CFDictionaryCreate(NULL, keys, + vals, (array_size(keys)), NULL, NULL); + CFTypeRef item_query = NULL; + status = SecItemCopyMatching(persistent_query, &item_query); + CFReleaseNull(persistent_query); + if (status) + return status; + + CFDictionaryRemoveValue(SecCFDictionaryCOWGetMutable(query), kSecValuePersistentRef); + CFDictionarySetValue(SecCFDictionaryCOWGetMutable(query), kSecValueRef, item_query); + CFRelease(item_query); + } + + return status; +} +#endif + +OSStatus +#if SECITEM_SHIM_OSX +SecItemDelete_ios(CFDictionaryRef inQuery) +#else +SecItemDelete(CFDictionaryRef inQuery) +#endif // *** END SECITEM_SHIM_OSX *** +{ + OSStatus status; + __block SecCFDictionaryCOW query = { inQuery }; + + os_activity_t trace_activity = os_activity_start("SecItemDelete_ios", OS_ACTIVITY_FLAG_DEFAULT); + +#ifndef SECITEM_SHIM_OSX + require_noerr_quiet(status = explode_persistent_identity_ref(&query), errOut); + require_quiet(!explode_identity(query.dictionary, (secitem_operation)SecItemDelete, &status, NULL), errOut); +#endif // *** END SECITEM_SHIM_OSX *** + + status = SecOSStatusWith(^bool(CFErrorRef *error) { + return SecItemAuthDoQuery(&query, NULL, SecItemDelete, error, ^bool(TKTokenRef token, CFDictionaryRef query, CFDictionaryRef attributes, CFDictionaryRef auth_params, CFErrorRef *error) { + if (token == NULL) { + return SECURITYD_XPC(sec_item_delete, dict_ag_to_error_request, query, SecAccessGroupsGetCurrent(), error); + } else { + return SecTokenItemForEachMatching(query, error, ^bool(CFDictionaryRef object_data, CFDictionaryRef item_query, CFErrorRef *error) { + bool ok = false; + + // Delete item from the token. + CFDataRef object_id = CFDictionaryGetValue(object_data, kSecTokenValueObjectIDKey); + require_action_quiet(TKTokenDeleteObject(token, object_id, error), out, + SecTokenProcessError(kAKSKeyOpDelete, token, object_id, error)); + + // Delete the item from the keychain. + require_quiet(SECURITYD_XPC(sec_item_delete, dict_ag_to_error_request, item_query, + SecAccessGroupsGetCurrent(), error), out); + ok = true; + + out: + return ok; + }); + } + }); + }); + +#ifndef SECITEM_SHIM_OSX +errOut: +#endif + + CFReleaseSafe(query.mutable_dictionary); + + os_activity_end(trace_activity); + + return status; +} + +OSStatus +SecItemDeleteAll(void) +{ + return SecOSStatusWith(^bool (CFErrorRef *error) { + bool ok; + if (gSecurityd) { + ok = true; +#ifndef SECITEM_SHIM_OSX + SecTrustStoreRef ts = SecTrustStoreForDomain(kSecTrustStoreDomainUser); + if (!gSecurityd->sec_truststore_remove_all(ts, error)) + ok = SecError(errSecInternal, error, CFSTR("sec_truststore_remove_all is NULL")); +#endif // *** END SECITEM_SHIM_OSX *** + if (!gSecurityd->sec_item_delete_all(error)) + ok = SecError(errSecInternal, error, CFSTR("sec_item_delete_all is NULL")); + } else { + ok = securityd_send_sync_and_do(sec_delete_all_id, error, NULL, NULL); + } + return ok; + }); +} + + +CFArrayRef _SecKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) { + __block CFArrayRef result; + os_activity_initiate("_SecKeychainSyncUpdateMessage", OS_ACTIVITY_FLAG_DEFAULT, ^{ + result = SECURITYD_XPC(sec_keychain_sync_update_message, dict_to_array_error_request, updates, error); + }); + return result; +} + +#ifndef SECITEM_SHIM_OSX +OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement); + +OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement) +{ + return -1; /* this is only on OS X currently */ +} + +#else + +extern OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement); + +#endif + +#define do_if_registered(sdp, ...) if (gSecurityd && gSecurityd->sdp) { return gSecurityd->sdp(__VA_ARGS__); } + +bool _SecKeychainRollKeys(bool force, CFErrorRef *error) +{ + do_if_registered(sec_roll_keys, force, error); + + __block bool result = false; + + secdebug("secitem","enter - %s", __FUNCTION__); + securityd_send_sync_and_do(kSecXPCOpRollKeys, error, + ^bool(xpc_object_t message, CFErrorRef *error) { + xpc_dictionary_set_bool(message, "force", force); + return true; + }, + ^bool(xpc_object_t response, __unused CFErrorRef *error) { + result = xpc_dictionary_get_bool(response, kSecXPCKeyResult); + return result; + }); + return result; +} + +