+/*
+ * Copyright (c) 2007-2008,2010 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@
+ */
+
+/*
+ * SecTrustSettings.c - Implement trust settings for certificates
+ */
+
+#include "SecTrustSettings.h"
+#include "SecTrustSettingsPriv.h"
+#include <Security/SecCertificatePriv.h>
+#include <AssertMacros.h>
+#include <pthread.h>
+#include <security_utilities/debugging.h>
+#include "SecBasePriv.h"
+#include <Security/SecInternal.h>
+#include <CoreFoundation/CFRuntime.h>
+
+#define trustSettingsDbg(args...) secdebug("trustSettings", ## args)
+#define trustSettingsEvalDbg(args...) secdebug("trustSettingsEval", ## args)
+
+#pragma mark -
+#pragma mark Static Functions
+
+#if 0
+/* Return a (hex)string representation of a CFDataRef. */
+static CFStringRef SecCopyHexStringFromData(CFDataRef data)
+{
+ CFIndex ix, length;
+ const UInt8 *bytes;
+ CFMutableStringRef string;
+
+ if (data) {
+ length = CFDataGetLength(data);
+ bytes = CFDataGetBytePtr(data);
+ } else {
+ length = 0;
+ bytes = NULL;
+ }
+ string = CFStringCreateMutable(kCFAllocatorDefault, length * 2);
+ for (ix = 0; ix < length; ++ix)
+ CFStringAppendFormat(string, NULL, CFSTR("%02X"), bytes[ix]);
+
+ return string;
+}
+#endif
+
+/* Return a CFDataRef representation of a (hex)string. */
+static CFDataRef SecCopyDataFromHexString(CFStringRef string) {
+ CFMutableDataRef data;
+ CFIndex ix, length;
+ UInt8 *bytes;
+
+ length = string ? CFStringGetLength(string) : 0;
+ if (length & 1) {
+ secwarning("Odd length string: %@ returning NULL", string);
+ return NULL;
+ }
+
+ data = CFDataCreateMutable(kCFAllocatorDefault, length / 2);
+ CFDataSetLength(data, length / 2);
+ bytes = CFDataGetMutableBytePtr(data);
+
+ CFStringInlineBuffer buf;
+ CFRange range = { 0, length };
+ CFStringInitInlineBuffer(string, &buf, range);
+ UInt8 lastv = 0;
+ for (ix = 0; ix < length; ++ix) {
+ UniChar c = CFStringGetCharacterFromInlineBuffer(&buf, ix);
+ UInt8 v;
+ if ('0' <= c && c <= '9')
+ v = c - '0';
+ else if ('A' <= c && c <= 'F')
+ v = c = 'A' + 10;
+ else if ('a' <= c && c <= 'a')
+ v = c = 'a' + 10;
+ else {
+ secwarning("Non hex string: %@ returning NULL", string);
+ CFRelease(data);
+ return NULL;
+ }
+ if (ix & 1) {
+ *bytes++ = (lastv << 4) + v;
+ } else {
+ lastv = v;
+ }
+ }
+
+ return data;
+}
+
+#if 0
+/*
+ * Obtain a string representing a cert's SHA1 digest. This string is
+ * the key used to look up per-cert trust settings in a TrustSettings record.
+ */
+static CFStringRef SecTrustSettingsCertHashStrFromCert(
+ SecCertificateRef certRef)
+{
+ if (certRef == NULL) {
+ return NULL;
+ }
+
+ if(certRef == kSecTrustSettingsDefaultRootCertSetting) {
+ /* use this string instead of the cert hash as the dictionary key */
+ secdebug("trustsettings","DefaultSetting");
+ return kSecTrustRecordDefaultRootCert;
+ }
+
+ CFDataRef digest = SecCertificateGetSHA1Digest(certRef);
+ return SecCopyHexStringFromData(digest);
+}
+#endif
+
+#pragma mark -
+#pragma mark SecTrustSettings
+/********************************************************
+ ************** SecTrustSettings object *****************
+ ********************************************************/
+
+struct __SecTrustSettings {
+ CFRuntimeBase _base;
+
+ /* the overall parsed TrustSettings - may be NULL if this is trimmed */
+ CFDictionaryRef _propList;
+
+ /* and the main thing we work with, the dictionary of per-cert trust settings */
+ CFMutableDictionaryRef _trustDict;
+
+ /* version number of mPropDict */
+ SInt32 _dictVersion;
+
+ SecTrustSettingsDomain _domain;
+ bool _dirty; /* we've changed _trustDict since creation */
+};
+
+/* CFRuntime regsitration data. */
+static pthread_once_t kSecTrustSettingsRegisterClass = PTHREAD_ONCE_INIT;
+static CFTypeID kSecTrustSettingsTypeID = _kCFRuntimeNotATypeID;
+
+static void SecTrustSettingsDestroy(CFTypeRef cf) {
+ SecTrustSettingsRef ts = (SecTrustSettingsRef) cf;
+ CFReleaseSafe(ts->_propList);
+ CFReleaseSafe(ts->_trustDict);
+}
+
+static void SecTrustSettingsRegisterClass(void) {
+ static const CFRuntimeClass kSecTrustSettingsClass = {
+ 0, /* version */
+ "SecTrustSettings", /* class name */
+ NULL, /* init */
+ NULL, /* copy */
+ SecTrustSettingsDestroy, /* dealloc */
+ NULL, /* equal */
+ NULL, /* hash */
+ NULL, /* copyFormattingDesc */
+ NULL /* copyDebugDesc */
+ };
+
+ kSecTrustSettingsTypeID = _CFRuntimeRegisterClass(&kSecTrustSettingsClass);
+}
+
+/* SecTrustSettings API functions. */
+CFTypeID SecTrustSettingsGetTypeID(void) {
+ pthread_once(&kSecTrustSettingsRegisterClass, SecTrustSettingsRegisterClass);
+ return kSecTrustSettingsTypeID;
+}
+
+/* Make sure a presumed CFNumber can be converted to a 32-bit number */
+static bool tsIsGoodCfNum(CFNumberRef cfn, SInt32 *num)
+{
+ /* by convention */
+ if (cfn == NULL) {
+ *num = 0;
+ return true;
+ }
+ require(CFGetTypeID(cfn) == CFNumberGetTypeID(), errOut);
+ return CFNumberGetValue(cfn, kCFNumberSInt32Type, num);
+errOut:
+ return false;
+}
+
+static bool validateUsageConstraint(const void *value) {
+ CFDictionaryRef ucDict = (CFDictionaryRef)value;
+ require(CFGetTypeID(ucDict) == CFDictionaryGetTypeID(), errOut);
+
+ CFDataRef certPolicy = (CFDataRef)CFDictionaryGetValue(ucDict,
+ kSecTrustSettingsPolicy);
+ require(certPolicy && CFGetTypeID(certPolicy) == CFDataGetTypeID(), errOut);
+
+ CFStringRef certApp = (CFStringRef)CFDictionaryGetValue(ucDict,
+ kSecTrustSettingsApplication);
+ require(certApp && CFGetTypeID(certApp) == CFStringGetTypeID(), errOut);
+
+ CFStringRef policyStr = (CFStringRef)CFDictionaryGetValue(ucDict,
+ kSecTrustSettingsPolicyString);
+ require(policyStr && CFGetTypeID(policyStr) == CFStringGetTypeID(), errOut);
+
+ SInt32 dummy;
+ CFNumberRef allowedError = (CFNumberRef)CFDictionaryGetValue(ucDict,
+ kSecTrustSettingsAllowedError);
+ require(tsIsGoodCfNum(allowedError, &dummy), errOut);
+
+ CFNumberRef trustSettingsResult = (CFNumberRef)CFDictionaryGetValue(ucDict,
+ kSecTrustSettingsResult);
+ require(tsIsGoodCfNum(trustSettingsResult, &dummy), errOut);
+
+ CFNumberRef keyUsage = (CFNumberRef)CFDictionaryGetValue(ucDict,
+ kSecTrustSettingsKeyUsage);
+ require(tsIsGoodCfNum(keyUsage, &dummy), errOut);
+
+ return true;
+errOut:
+ return false;
+}
+
+static bool validateTrustSettingsArray(CFArrayRef usageConstraints) {
+ CFIndex ix, numConstraints = CFArrayGetCount(usageConstraints);
+ bool result = true;
+ for (ix = 0; ix < numConstraints; ++ix) {
+ if (!validateUsageConstraint(CFArrayGetValueAtIndex(usageConstraints, ix)))
+ result = false;
+ }
+ return result;
+}
+
+struct trustListContext {
+ CFMutableDictionaryRef dict;
+ SInt32 version;
+ bool trim;
+ OSStatus status;
+};
+
+static void trustListApplierFunction(const void *key, const void *value, void *context) {
+ CFStringRef digest = (CFStringRef)key;
+ CFDictionaryRef certDict = (CFDictionaryRef)value;
+ struct trustListContext *tlc = (struct trustListContext *)context;
+ CFDataRef newKey = NULL;
+ CFMutableDictionaryRef newDict = NULL;
+
+ /* Get the key. */
+ require(digest && CFGetTypeID(digest) == CFStringGetTypeID(), errOut);
+ /* Convert it to a CFDataRef. */
+ require(newKey = SecCopyDataFromHexString(digest), errOut);
+
+ /* get per-cert dictionary */
+ require(certDict && CFGetTypeID(certDict) == CFDictionaryGetTypeID(), errOut);
+
+ /* Create the to be inserted dictionary. */
+ require(newDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
+ tlc->trim ? 1 : 4, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks), errOut);
+
+ /* The certDict dictionary should have exactly four entries.
+ If we're trimming, all we need is the actual trust settings. */
+
+ /* issuer */
+ CFDataRef issuer = (CFDataRef)CFDictionaryGetValue(certDict,
+ kTrustRecordIssuer);
+ require(issuer && CFGetTypeID(issuer) == CFDataGetTypeID(), errOut);
+
+ /* serial number */
+ CFDataRef serial = (CFDataRef)CFDictionaryGetValue(certDict,
+ kTrustRecordSerialNumber);
+ require(serial && CFGetTypeID(serial) == CFDataGetTypeID(), errOut);
+
+ /* modification date */
+ CFDateRef modDate = (CFDateRef)CFDictionaryGetValue(certDict,
+ kTrustRecordModDate);
+ require(modDate && CFGetTypeID(modDate) == CFDateGetTypeID(), errOut);
+
+ /* If we are not trimming we copy these extra values as well. */
+ if (!tlc->trim) {
+ CFDictionaryAddValue(newDict, kTrustRecordIssuer, issuer);
+ CFDictionaryAddValue(newDict, kTrustRecordSerialNumber, serial);
+ CFDictionaryAddValue(newDict, kTrustRecordModDate, modDate);
+ }
+
+ /* The actual trust settings */
+ CFArrayRef trustSettings = (CFArrayRef)CFDictionaryGetValue(certDict,
+ kTrustRecordTrustSettings);
+ /* optional; this cert's entry is good */
+ if(trustSettings) {
+ require(CFGetTypeID(trustSettings) == CFArrayGetTypeID(), errOut);
+
+ /* Now validate the usageConstraint array contents */
+ require(validateTrustSettingsArray(trustSettings), errOut);
+
+ /* Add the trustSettings to the dict. */
+ CFDictionaryAddValue(newDict, kTrustRecordTrustSettings, trustSettings);
+ }
+
+ CFDictionaryAddValue(tlc->dict, newKey, newDict);
+ CFRelease(newKey);
+ CFRelease(newDict);
+
+ return;
+errOut:
+ CFReleaseSafe(newKey);
+ CFReleaseSafe(newDict);
+ tlc->status = errSecInvalidTrustSettings;
+}
+
+static OSStatus SecTrustSettingsValidate(SecTrustSettingsRef ts, bool trim) {
+ /* top level dictionary */
+ require(ts->_propList, errOut);
+ require(CFGetTypeID(ts->_propList) == CFDictionaryGetTypeID(), errOut);
+
+ /* That dictionary has two entries */
+ CFNumberRef cfVers = (CFNumberRef)CFDictionaryGetValue(ts->_propList, kTrustRecordVersion);
+ require(cfVers != NULL && CFGetTypeID(cfVers) == CFNumberGetTypeID(), errOut);
+ require(CFNumberGetValue(cfVers, kCFNumberSInt32Type, &ts->_dictVersion), errOut);
+ require((ts->_dictVersion <= kSecTrustRecordVersionCurrent) &&
+ (ts->_dictVersion != kSecTrustRecordVersionInvalid), errOut);
+
+ /* other backwards-compatibility handling done later, if needed, per _dictVersion */
+
+ require(ts->_trustDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks), errOut);
+
+ CFDictionaryRef trustList = (CFDictionaryRef)CFDictionaryGetValue(
+ ts->_propList, kTrustRecordTrustList);
+ require(trustList != NULL &&
+ CFGetTypeID(trustList) == CFDictionaryGetTypeID(), errOut);
+
+ /* Convert the per-cert entries from on disk to in memory format. */
+ struct trustListContext context = {
+ ts->_trustDict, ts->_dictVersion, trim, noErr
+ };
+ CFDictionaryApplyFunction(trustList, trustListApplierFunction, &context);
+
+ if (trim) {
+ /* we don't need the top-level dictionary any more */
+ CFRelease(ts->_propList);
+ ts->_propList = NULL;
+ }
+
+ return context.status;
+
+errOut:
+ return errSecInvalidTrustSettings;
+}
+
+OSStatus SecTrustSettingsCreateFromExternal(SecTrustSettingsDomain domain,
+ CFDataRef external, SecTrustSettingsRef *ts) {
+ CFAllocatorRef allocator = kCFAllocatorDefault;
+ CFIndex size = sizeof(struct __SecTrustSettings);
+ SecTrustSettingsRef result;
+
+ require(result = (SecTrustSettingsRef)_CFRuntimeCreateInstance(allocator,
+ SecTrustSettingsGetTypeID(), size - sizeof(CFRuntimeBase), 0), errOut);
+
+ CFStringRef errorString = NULL;
+ CFPropertyListRef plist = CFPropertyListCreateFromXMLData(
+ kCFAllocatorDefault, external, kCFPropertyListImmutable, &errorString);
+ if (!plist) {
+ secwarning("CFPropertyListCreateFromXMLData: %@", errorString);
+ CFReleaseSafe(errorString);
+ goto errOut;
+ }
+
+ result->_propList = plist;
+ result->_trustDict = NULL;
+ SecTrustSettingsValidate(result, false);
+
+ *ts = result;
+
+ return noErr;
+
+errOut:
+ return errSecInvalidTrustSettings;
+}
+
+SecTrustSettingsRef SecTrustSettingsCreate(SecTrustSettingsDomain domain,
+ bool create, bool trim) {
+ CFAllocatorRef allocator = kCFAllocatorDefault;
+ CFIndex size = sizeof(struct __SecTrustSettings);
+ SecTrustSettingsRef result =
+ (SecTrustSettingsRef)_CFRuntimeCreateInstance(allocator,
+ SecTrustSettingsGetTypeID(), size - sizeof(CFRuntimeBase), 0);
+ if (!result)
+ return NULL;
+
+ //result->_data = NULL;
+
+ return result;
+}
+
+CFDataRef SecTrustSettingsCopyExternal(SecTrustSettingsRef ts) {
+ /* Transform the internal represantation of the trustSettings to an
+ external form. */
+ // @@@ SecTrustSettingsUpdatePropertyList(SecTrustSettingsRef ts);
+ CFDataRef xmlData;
+ verify(xmlData = CFPropertyListCreateXMLData(kCFAllocatorDefault,
+ ts->_propList));
+ return xmlData;
+}
+
+void SecTrustSettingsSet(SecCertificateRef certRef,
+ CFTypeRef trustSettingsDictOrArray) {
+}
+
+
+
+#pragma mark -
+#pragma mark SPI functions
+
+
+/*
+ * Fundamental routine used by TP to ascertain status of one cert.
+ *
+ * Returns true in *foundMatchingEntry if a trust setting matching
+ * specific constraints was found for the cert. Returns true in
+ * *foundAnyEntry if any entry was found for the cert, even if it
+ * did not match the specified constraints. The TP uses this to
+ * optimize for the case where a cert is being evaluated for
+ * one type of usage, and then later for another type. If
+ * foundAnyEntry is false, the second evaluation need not occur.
+ *
+ * Returns the domain in which a setting was found in *foundDomain.
+ *
+ * Allowed errors applying to the specified cert evaluation
+ * are returned in a mallocd array in *allowedErrors and must
+ * be freed by caller.
+ *
+ * The design of the entire TrustSettings module is centered around
+ * optimizing the performance of this routine (security concerns
+ * aside, that is). It's why the per-cert dictionaries are stored
+ * as a dictionary, keyed off of the cert hash. It's why TrustSettings
+ * are cached in memory by tsGetGlobalTrustSettings(), and why those
+ * cached TrustSettings objects are 'trimmed' of dictionary fields
+ * which are not needed to verify a cert.
+ *
+ * The API functions which are used to manipulate Trust Settings
+ * are called infrequently and need not be particularly fast since
+ * they result in user interaction for authentication. Thus they do
+ * not use cached TrustSettings as this function does.
+ */
+OSStatus SecTrustSettingsEvaluateCertificate(
+ SecCertificateRef certificate,
+ SecPolicyRef policy,
+ SecTrustSettingsKeyUsage keyUsage, /* optional */
+ bool isSelfSignedCert, /* for checking default setting */
+ /* RETURNED values */
+ SecTrustSettingsDomain *foundDomain,
+ CFArrayRef *allowedErrors, /* RETURNED */
+ SecTrustSettingsResult *resultType, /* RETURNED */
+ bool *foundMatchingEntry,/* RETURNED */
+ bool *foundAnyEntry) /* RETURNED */
+{
+#if 0
+ BEGIN_RCSAPI
+
+ StLock<Mutex> _(sutCacheLock());
+
+ TS_REQUIRED(certHashStr)
+ TS_REQUIRED(foundDomain)
+ TS_REQUIRED(allowedErrors)
+ TS_REQUIRED(numAllowedErrors)
+ TS_REQUIRED(resultType)
+ TS_REQUIRED(foundMatchingEntry)
+ TS_REQUIRED(foundMatchingEntry)
+
+ /* ensure a NULL_terminated string */
+ auto_array<char> polStr;
+ if(policyString != NULL) {
+ polStr.allocate(policyStringLen + 1);
+ memmove(polStr.get(), policyString, policyStringLen);
+ if(policyString[policyStringLen - 1] != '\0') {
+ (polStr.get())[policyStringLen] = '\0';
+ }
+ }
+
+ /* initial condition - this can grow if we inspect multiple TrustSettings */
+ *allowedErrors = NULL;
+ *numAllowedErrors = 0;
+
+ /*
+ * This loop relies on the ordering of the SecTrustSettingsDomain enum:
+ * search user first, then admin, then system.
+ */
+ assert(kSecTrustSettingsDomainAdmin == (kSecTrustSettingsDomainUser + 1));
+ assert(kSecTrustSettingsDomainSystem == (kSecTrustSettingsDomainAdmin + 1));
+ bool foundAny = false;
+ for(unsigned domain=kSecTrustSettingsDomainUser;
+ domain<=kSecTrustSettingsDomainSystem;
+ domain++) {
+ TrustSettings *ts = tsGetGlobalTrustSettings(domain);
+ if(ts == NULL) {
+ continue;
+ }
+
+ /* validate cert returns true if matching entry was found */
+ bool foundAnyHere = false;
+ bool found = ts->evaluateCert(certHashStr, policyOID,
+ polStr.get(), keyUsage, isRootCert,
+ allowedErrors, numAllowedErrors, resultType, &foundAnyHere);
+
+ if(found) {
+ /*
+ * Note this, even though we may overwrite it later if this
+ * is an Unspecified entry and we find a definitive entry
+ * later
+ */
+ *foundDomain = domain;
+ }
+ if(found && (*resultType != kSecTrustSettingsResultUnspecified)) {
+ trustSettingsDbg("SecTrustSettingsEvaluateCert: found in domain %d", domain);
+ *foundAnyEntry = true;
+ *foundMatchingEntry = true;
+ return noErr;
+ }
+ foundAny |= foundAnyHere;
+ }
+ trustSettingsDbg("SecTrustSettingsEvaluateCert: NOT FOUND");
+ *foundAnyEntry = foundAny;
+ *foundMatchingEntry = false;
+ return noErr;
+ END_RCSAPI
+#endif
+ return noErr;
+}
+
+/*
+ * Add a cert's TrustSettings to a non-persistent TrustSettings record.
+ * No locking or cache flushing here; it's all local to the TrustSettings
+ * we construct here.
+ */
+OSStatus SecTrustSettingsSetTrustSettingsExternal(
+ CFDataRef settingsIn, /* optional */
+ SecCertificateRef certRef, /* optional */
+ CFTypeRef trustSettingsDictOrArray, /* optional */
+ CFDataRef *settingsOut) /* RETURNED */
+{
+ SecTrustSettingsRef ts = NULL;
+ OSStatus status;
+
+ require_noerr(status = SecTrustSettingsCreateFromExternal(
+ kSecTrustSettingsDomainMemory, settingsIn, &ts), errOut);
+ SecTrustSettingsSet(certRef, trustSettingsDictOrArray);
+ *settingsOut = SecTrustSettingsCopyExternal(ts);
+
+errOut:
+ CFReleaseSafe(ts);
+ return status;
+}