--- /dev/null
+/*
+ * Copyright (c) 2005,2011-2015 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@
+ */
+
+/*
+ * TrustSettings.h - class to manage cert trust settings.
+ *
+ */
+
+#include "TrustSettings.h"
+#include "TrustSettingsSchema.h"
+#include "SecTrustSettings.h"
+#include "TrustSettingsUtils.h"
+#include "TrustKeychains.h"
+#include "Certificate.h"
+#include "cssmdatetime.h"
+#include <Security/SecBase.h>
+#include "SecTrustedApplicationPriv.h"
+#include <security_utilities/errors.h>
+#include <security_utilities/debugging.h>
+#include <security_utilities/logging.h>
+#include <security_utilities/cfutilities.h>
+#include <security_utilities/alloc.h>
+#include <Security/cssmapplePriv.h>
+#include <Security/oidscert.h>
+#include <Security/SecCertificatePriv.h>
+#include <Security/SecPolicyPriv.h>
+#include <security_keychain/KCCursor.h>
+#include <security_ocspd/ocspdClient.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <assert.h>
+#include <Security/Authorization.h>
+#include <sys/stat.h>
+
+#define trustSettingsDbg(args...) secdebug("trustSettings", ## args)
+#define trustSettingsEvalDbg(args...) secdebug("trustSettingsEval", ## args)
+
+/*
+ * Common error return for "malformed TrustSettings record"
+ */
+#define errSecInvalidTrustedRootRecord errSecInvalidTrustSettings
+
+using namespace KeychainCore;
+
+#pragma mark --- Static functions ---
+
+/*
+ * Comparator atoms to determine if an app's specified usage
+ * matches an individual trust setting. Each returns true on a match, false
+ * if the trust setting does not match the app's spec.
+ *
+ * A match fails iff:
+ *
+ * -- the app has specified a field, and the cert has a spec for that
+ * field, and the two specs do not match;
+ *
+ * OR
+ *
+ * -- the cert has a spec for the field and the app hasn't specified the field
+ */
+static bool tsCheckPolicy(
+ const CSSM_OID *appPolicy,
+ CFDataRef certPolicy)
+{
+ if(certPolicy != NULL) {
+ if(appPolicy == NULL) {
+ trustSettingsEvalDbg("tsCheckPolicy: certPolicy, !appPolicy");
+ return false;
+ }
+ unsigned cLen = (unsigned)CFDataGetLength(certPolicy);
+ const UInt8 *cData = CFDataGetBytePtr(certPolicy);
+ if((cLen != appPolicy->Length) || memcmp(appPolicy->Data, cData, cLen)) {
+ trustSettingsEvalDbg("tsCheckPolicy: policy mismatch");
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * This one's slightly different: the match is for *this* app, not one
+ * specified by the app.
+ */
+static bool tsCheckApp(
+ CFDataRef certApp)
+{
+ if(certApp != NULL) {
+ SecTrustedApplicationRef appRef;
+ OSStatus ortn;
+ ortn = SecTrustedApplicationCreateWithExternalRepresentation(certApp, &appRef);
+ if(ortn) {
+ trustSettingsDbg("tsCheckApp: bad trustedApp data\n");
+ return false;
+ }
+ ortn = SecTrustedApplicationValidateWithPath(appRef, NULL);
+ if(ortn) {
+ /* Not this app */
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool tsCheckKeyUse(
+ SecTrustSettingsKeyUsage appKeyUse,
+ CFNumberRef certKeyUse)
+{
+ if(certKeyUse != NULL) {
+ SInt32 certUse;
+ CFNumberGetValue(certKeyUse, kCFNumberSInt32Type, &certUse);
+ SecTrustSettingsKeyUsage cku = (SecTrustSettingsKeyUsage)certUse;
+ if(cku == kSecTrustSettingsKeyUseAny) {
+ /* explicitly allows anything */
+ return true;
+ }
+ /* cert specification must be a superset of app's intended use */
+ if(appKeyUse == 0) {
+ trustSettingsEvalDbg("tsCheckKeyUse: certKeyUsage, !appKeyUsage");
+ return false;
+ }
+
+ if((cku & appKeyUse) != appKeyUse) {
+ trustSettingsEvalDbg("tsCheckKeyUse: keyUse mismatch");
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool tsCheckPolicyStr(
+ const char *appPolicyStr,
+ CFStringRef certPolicyStr)
+{
+ if(certPolicyStr != NULL) {
+ if(appPolicyStr == NULL) {
+ trustSettingsEvalDbg("tsCheckPolicyStr: certPolicyStr, !appPolicyStr");
+ return false;
+ }
+ /* Let CF do the string compare */
+ CFStringRef cfPolicyStr = CFStringCreateWithCString(NULL, appPolicyStr,
+ kCFStringEncodingUTF8);
+ if(cfPolicyStr == NULL) {
+ /* I really don't see how this can happen */
+ trustSettingsEvalDbg("tsCheckPolicyStr: policyStr string conversion error");
+ return false;
+ }
+
+ // Some trust setting strings were created with a NULL character at the
+ // end, which was included in the length. Strip those off before compare
+
+ CFMutableStringRef certPolicyStrNoNULL = CFStringCreateMutableCopy(NULL, 0, certPolicyStr);
+ if (certPolicyStrNoNULL == NULL) {
+ /* I really don't see how this can happen either */
+ trustSettingsEvalDbg("tsCheckPolicyStr: policyStr string conversion error 2");
+ return false;
+ }
+
+ CFStringFindAndReplace(certPolicyStrNoNULL, CFSTR("\00"),
+ CFSTR(""), CFRangeMake(0, CFStringGetLength(certPolicyStrNoNULL)), kCFCompareBackwards);
+
+ CFComparisonResult res = CFStringCompare(cfPolicyStr, certPolicyStrNoNULL, 0);
+ CFRelease(cfPolicyStr);
+ CFRelease(certPolicyStrNoNULL);
+ if(res != kCFCompareEqualTo) {
+ trustSettingsEvalDbg("tsCheckPolicyStr: policyStr mismatch");
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Determine if a cert's trust settings dictionary satisfies the specified
+ * usage constraints. Returns true if so.
+ * Only certs with a SecTrustSettingsResult of kSecTrustSettingsResultTrustRoot
+ * or kSecTrustSettingsResultTrustAsRoot will match.
+ */
+static bool qualifyUsageWithCertDict(
+ CFDictionaryRef certDict,
+ const CSSM_OID *policyOID, /* optional */
+ const char *policyStr, /* optional */
+ SecTrustSettingsKeyUsage keyUsage, /* optional; default = any (actually "all" here) */
+ bool onlyRoots)
+{
+ /* this array is optional */
+ CFArrayRef trustSettings = (CFArrayRef)CFDictionaryGetValue(certDict,
+ kTrustRecordTrustSettings);
+ CFIndex numSpecs = 0;
+ if(trustSettings != NULL) {
+ numSpecs = CFArrayGetCount(trustSettings);
+ }
+ if(numSpecs == 0) {
+ /*
+ * Trivial case: cert has no trust settings, indicating that
+ * it's used for everything.
+ */
+ trustSettingsEvalDbg("qualifyUsageWithCertDict: no trust settings");
+ return true;
+ }
+ for(CFIndex addDex=0; addDex<numSpecs; addDex++) {
+ CFDictionaryRef tsDict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings,
+ addDex);
+
+ /* per-cert specs: all optional */
+ CFDataRef certPolicy = (CFDataRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsPolicy);
+ CFDataRef certApp = (CFDataRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsApplication);
+ CFStringRef certPolicyStr = (CFStringRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsPolicyString);
+ CFNumberRef certKeyUsage = (CFNumberRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsKeyUsage);
+ CFNumberRef certResultType = (CFNumberRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsResult);
+
+ if(!tsCheckPolicy(policyOID, certPolicy)) {
+ continue;
+ }
+ if(!tsCheckApp(certApp)) {
+ continue;
+ }
+ if(!tsCheckKeyUse(keyUsage, certKeyUsage)) {
+ continue;
+ }
+ if(!tsCheckPolicyStr(policyStr, certPolicyStr)) {
+ continue;
+ }
+
+ /*
+ * This is a match, take whatever SecTrustSettingsResult is here,
+ * including the default if not specified.
+ */
+ SecTrustSettingsResult resultType = kSecTrustSettingsResultTrustRoot;
+ if(certResultType) {
+ SInt32 s;
+ CFNumberGetValue(certResultType, kCFNumberSInt32Type, &s);
+ resultType = (SecTrustSettingsResult)s;
+ }
+ switch(resultType) {
+ case kSecTrustSettingsResultTrustRoot:
+ trustSettingsEvalDbg("qualifyUsageWithCertDict: TrustRoot MATCH");
+ return true;
+ case kSecTrustSettingsResultTrustAsRoot:
+ if(onlyRoots) {
+ trustSettingsEvalDbg("qualifyUsageWithCertDict: TrustAsRoot but not root");
+ return false;
+ }
+ trustSettingsEvalDbg("qualifyUsageWithCertDict: TrustAsRoot MATCH");
+ return true;
+ default:
+ trustSettingsEvalDbg("qualifyUsageWithCertDict: bad resultType "
+ "(%lu)", (unsigned long)resultType);
+ return false;
+ }
+ }
+ trustSettingsEvalDbg("qualifyUsageWithCertDict: NO MATCH");
+ return false;
+}
+
+/*
+ * Create initial top-level dictionary when constructing a new TrustSettings.
+ */
+static CFMutableDictionaryRef tsInitialDict()
+{
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL,
+ kSecTrustRecordNumTopDictKeys,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+ /* the dictionary of per-cert entries */
+ CFMutableDictionaryRef trustDict = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(dict, kTrustRecordTrustList, trustDict);
+ CFRelease(trustDict);
+
+ SInt32 vers = kSecTrustRecordVersionCurrent;
+ CFNumberRef cfVers = CFNumberCreate(NULL, kCFNumberSInt32Type, &vers);
+ CFDictionaryAddValue(dict, kTrustRecordVersion, cfVers);
+ CFRelease(cfVers);
+ return dict;
+}
+
+/*
+ * Set the modification date of a per-cert dictionary to current time.
+ */
+static void tsSetModDate(
+ CFMutableDictionaryRef dict)
+{
+ CFDateRef modDate;
+
+ modDate = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
+ CFDictionarySetValue(dict, kTrustRecordModDate, modDate);
+ CFRelease(modDate);
+}
+
+/* make sure a presumed CFNumber can be converted to a 32-bit number */
+static
+bool tsIsGoodCfNum(CFNumberRef cfn, SInt32 *num = NULL)
+{
+ if(cfn == NULL) {
+ /* by convention */
+ if(num) {
+ *num = 0;
+ }
+ return true;
+ }
+ if(CFGetTypeID(cfn) != CFNumberGetTypeID()) {
+ return false;
+ }
+
+ SInt32 s;
+ if(!CFNumberGetValue(cfn, kCFNumberSInt32Type, &s)) {
+ return false;
+ }
+ else {
+ if(num) {
+ *num = s;
+ }
+ return true;
+ }
+}
+
+TrustSettings::TrustSettings(SecTrustSettingsDomain domain)
+ : mPropList(NULL),
+ mTrustDict(NULL),
+ mDictVersion(0),
+ mDomain(domain),
+ mDirty(false)
+{
+}
+
+
+
+#pragma mark --- Public methods ---
+
+/*
+ * Normal constructor, from disk.
+ * If create is true, the absence of an on-disk TrustSettings file
+ * results in the creation of a new empty TrustSettings. If create is
+ * false and no on-disk TrustSettings exists, errSecNoTrustSettings is
+ * thrown.
+ * If trim is true, the components of the on-disk TrustSettings not
+ * needed for cert evaluation are discarded. This is for TrustSettings
+ * that will be cached in memory long-term.
+ */
+OSStatus TrustSettings::CreateTrustSettings(
+ SecTrustSettingsDomain domain,
+ bool create,
+ bool trim,
+ TrustSettings*& ts)
+{
+ TrustSettings* t = new TrustSettings(domain);
+
+ Allocator &alloc = Allocator::standard();
+ CSSM_DATA fileData = {0, NULL};
+ OSStatus ortn = errSecSuccess;
+ struct stat sb;
+ const char *path;
+
+ /* get trust settings from file, one way or another */
+ switch(domain) {
+ case kSecTrustSettingsDomainAdmin:
+ /*
+ * Quickie optimization: if it's not there, don't try to
+ * get it from ocspd. This is possible because the name of the
+ * admin file is hard coded, but the per-user files aren't.
+ */
+ path = TRUST_SETTINGS_PATH "/" ADMIN_TRUST_SETTINGS;
+ if(stat(path, &sb)) {
+ trustSettingsDbg("TrustSettings: no admin record; skipping");
+ ortn = errSecNoTrustSettings;
+ break;
+ }
+ /* else drop thru, get it from ocspd */
+ case kSecTrustSettingsDomainUser:
+ /* get settings from ocspd */
+ ortn = ocspdTrustSettingsRead(alloc, domain, fileData);
+ break;
+ case kSecTrustSettingsDomainSystem:
+ /* immutable; it's safe for us to read this directly */
+ if(tsReadFile(SYSTEM_TRUST_SETTINGS_PATH, alloc, fileData)) {
+ ortn = errSecNoTrustSettings;
+ }
+ break;
+ default:
+ delete t;
+ return errSecParam;
+ }
+ if(ortn) {
+ if(create) {
+ trustSettingsDbg("TrustSettings: creating new record for domain %d",
+ (int)domain);
+ t->mPropList = tsInitialDict();
+ t->mDirty = true;
+ }
+ else {
+ trustSettingsDbg("TrustSettings: record not found for domain %d",
+ (int)domain);
+ delete t;
+ return ortn;
+ }
+ }
+ else {
+ CFRef<CFDataRef> propList(CFDataCreate(NULL, fileData.Data, fileData.Length));
+ t->initFromData(propList);
+ alloc.free(fileData.Data);
+ }
+ t->validatePropList(trim);
+
+ ts = t;
+ return errSecSuccess;
+}
+
+/*
+ * Create from external data, obtained by createExternal().
+ * If externalData is NULL, we'll create an empty mTrustDict.
+ */
+OSStatus TrustSettings::CreateTrustSettings(
+ SecTrustSettingsDomain domain,
+ CFDataRef externalData,
+ TrustSettings*& ts)
+{
+ switch(domain) {
+ case kSecTrustSettingsDomainUser:
+ case kSecTrustSettingsDomainAdmin:
+ case kSecTrustSettingsDomainMemory:
+ break;
+ case kSecTrustSettingsDomainSystem: /* no can do, that implies writing to it */
+ default:
+ return errSecParam;
+ }
+
+ TrustSettings* t = new TrustSettings(domain);
+
+ if(externalData != NULL) {
+ t->initFromData(externalData);
+ }
+ else {
+ t->mPropList = tsInitialDict();
+ }
+ t->validatePropList(TRIM_NO); /* never trim this */
+ t->mDirty = true;
+
+ ts = t;
+ return errSecSuccess;
+}
+
+
+TrustSettings::~TrustSettings()
+{
+ trustSettingsDbg("TrustSettings(domain %d) destructor", (int)mDomain);
+ CFRELEASE(mPropList); /* may be null if trimmed */
+ CFRELEASE(mTrustDict); /* normally always non-NULL */
+
+}
+
+/* common code to init mPropList from raw data */
+void TrustSettings::initFromData(
+ CFDataRef trustSettingsData)
+{
+ CFStringRef errStr = NULL;
+
+ mPropList = (CFMutableDictionaryRef)CFPropertyListCreateFromXMLData(
+ NULL,
+ trustSettingsData,
+ kCFPropertyListMutableContainersAndLeaves,
+ &errStr);
+ if(mPropList == NULL) {
+ trustSettingsDbg("TrustSettings::initFromData decode err (%s)",
+ errStr ? CFStringGetCStringPtr(errStr, kCFStringEncodingUTF8) : "<no err>");
+ if(errStr != NULL) {
+ CFRelease(errStr);
+ }
+ MacOSError::throwMe(errSecInvalidTrustSettings);
+ }
+}
+
+/*
+ * Flush property list data out to disk if dirty.
+ */
+void TrustSettings::flushToDisk()
+{
+ if(!mDirty) {
+ trustSettingsDbg("flushToDisk, domain %d, !dirty!", (int)mDomain);
+ return;
+ }
+ if(mPropList == NULL) {
+ trustSettingsDbg("flushToDisk, domain %d, trimmed!", (int)mDomain);
+ assert(0);
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ switch(mDomain) {
+ case kSecTrustSettingsDomainSystem:
+ case kSecTrustSettingsDomainMemory:
+ /* caller shouldn't even try this */
+ default:
+ trustSettingsDbg("flushToDisk, bad domain (%d)", (int)mDomain);
+ MacOSError::throwMe(errSecInternalComponent);
+ case kSecTrustSettingsDomainUser:
+ case kSecTrustSettingsDomainAdmin:
+ break;
+ }
+
+ /*
+ * Optimization: if there are no certs in the mTrustDict dictionary,
+ * we tell ocspd to *remove* the settings for the specified domain.
+ * Having *no* settings uses less memory and is faster than having
+ * an empty settings file, especially for the admin domain, where we
+ * can avoid
+ * an RPC if the settings file is simply not there.
+ */
+ CFRef<CFDataRef> xmlData;
+ CSSM_DATA cssmXmlData = {0, NULL};
+ CFIndex numCerts = CFDictionaryGetCount(mTrustDict);
+ if(numCerts) {
+ xmlData.take(CFPropertyListCreateXMLData(NULL, mPropList));
+ if(!xmlData) {
+ /* we've been very careful; this should never happen */
+ trustSettingsDbg("flushToDisk, domain %d: error converting to XML", (int)mDomain);
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ cssmXmlData.Data = (uint8 *)CFDataGetBytePtr(xmlData);
+ cssmXmlData.Length = CFDataGetLength(xmlData);
+ }
+ else {
+ trustSettingsDbg("flushToDisk, domain %d: DELETING trust settings", (int)mDomain);
+ }
+
+ /* cook up auth stuff so ocspd can act on our behalf */
+ AuthorizationRef authRef;
+ OSStatus ortn;
+ ortn = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
+ 0, &authRef);
+ if(ortn) {
+ trustSettingsDbg("flushToDisk, domain %d: AuthorizationCreate returned %ld",
+ (int)mDomain, (long)ortn);
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ AuthorizationExternalForm authExt;
+ CSSM_DATA authBlob = {sizeof(authExt), (uint8 *)&authExt};
+ ortn = AuthorizationMakeExternalForm(authRef, &authExt);
+ if(ortn) {
+ trustSettingsDbg("flushToDisk, domain %d: AuthorizationMakeExternalForm returned %ld",
+ (int)mDomain, (long)ortn);
+ ortn = errSecInternalComponent;
+ goto errOut;
+ }
+
+ ortn = ocspdTrustSettingsWrite(mDomain, authBlob, cssmXmlData);
+ if(ortn) {
+ trustSettingsDbg("flushToDisk, domain %d: ocspdTrustSettingsWrite returned %ld",
+ (int)mDomain, (long)ortn);
+ goto errOut;
+ }
+ trustSettingsDbg("flushToDisk, domain %d: wrote to disk", (int)mDomain);
+ mDirty = false;
+errOut:
+ AuthorizationFree(authRef, 0);
+ if(ortn) {
+ MacOSError::throwMe(ortn);
+ }
+}
+
+/*
+ * Obtain external representation of TrustSettings data.
+ */
+CFDataRef TrustSettings::createExternal()
+{
+ assert(mPropList);
+ CFDataRef xmlData = CFPropertyListCreateXMLData(NULL, mPropList);
+ if(xmlData == NULL) {
+ trustSettingsDbg("createExternal, domain %d: error converting to XML",
+ (int)mDomain);
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ return xmlData;
+}
+
+/*
+ * Evaluate specified cert. Returns true if we found a record for the cert
+ * matching specified constraints.
+ * Note that a true return with a value of kSecTrustSettingsResultUnspecified for
+ * the resultType means that a cert isn't to be trusted or untrusted
+ * per se; it just means that we only found allowedErrors entries.
+ *
+ * Found "allows errors" values are added to the incoming allowedErrors
+ * array which is reallocd as needed (and which may be NULL or non-NULL on
+ * entry).
+ */
+bool TrustSettings::evaluateCert(
+ CFStringRef certHashStr,
+ const CSSM_OID *policyOID, /* optional */
+ const char *policyStr, /* optional */
+ SecTrustSettingsKeyUsage keyUsage, /* optional */
+ bool isRootCert, /* for checking default setting */
+ CSSM_RETURN **allowedErrors, /* IN/OUT; reallocd as needed */
+ uint32 *numAllowedErrors, /* IN/OUT */
+ SecTrustSettingsResult *resultType, /* RETURNED */
+ bool *foundAnyEntry) /* RETURNED */
+{
+ assert(mTrustDict != NULL);
+
+ /* get trust settings dictionary for this cert */
+ CFDictionaryRef certDict = findDictionaryForCertHash(certHashStr);
+ if((certDict == NULL) && isRootCert) {
+ /* No? How about default root setting for this domain? */
+ certDict = findDictionaryForCertHash(kSecTrustRecordDefaultRootCert);
+ }
+#if CERT_HASH_DEBUG
+ /* @@@ debug only @@@ */
+ /* print certificate hash and found dictionary reference */
+ const size_t maxHashStrLen = 512;
+ char *buf = (char*)malloc(maxHashStrLen);
+ if (buf) {
+ if (!CFStringGetCString(certHashStr, buf, (CFIndex)maxHashStrLen, kCFStringEncodingUTF8)) {
+ buf[0]='\0';
+ }
+ trustSettingsEvalDbg("evaluateCert for \"%s\", found dict %p", buf, certDict);
+ free(buf);
+ }
+#endif
+
+ if(certDict == NULL) {
+ *foundAnyEntry = false;
+ return false;
+ }
+ *foundAnyEntry = true;
+
+ /* to-be-returned array of allowed errors */
+ CSSM_RETURN *allowedErrs = *allowedErrors;
+ uint32 numAllowedErrs = *numAllowedErrors;
+
+ /* this means "we found something other than allowedErrors" if true */
+ bool foundSettings = false;
+
+ /* to be returned in *resultType if it ends up something other than Invalid */
+ SecTrustSettingsResult returnedResult = kSecTrustSettingsResultInvalid;
+
+ /*
+ * Note since we validated the entire mPropList in our constructor, and we're careful
+ * about what we put into it, we don't bother typechecking its contents here.
+ * Also note that the kTrustRecordTrustSettings entry is optional.
+ */
+ CFArrayRef trustSettings = (CFArrayRef)CFDictionaryGetValue(certDict,
+ kTrustRecordTrustSettings);
+ CFIndex numSpecs = 0;
+ if(trustSettings != NULL) {
+ numSpecs = CFArrayGetCount(trustSettings);
+ }
+ if(numSpecs == 0) {
+ /*
+ * Trivial case: cert has no trust settings, indicating that
+ * it's used for everything.
+ */
+ trustSettingsEvalDbg("evaluateCert: no trust settings");
+ /* the default... */
+ *resultType = kSecTrustSettingsResultTrustRoot;
+ return true;
+ }
+
+ /*
+ * The decidedly nontrivial part: grind thru all of the cert's trust
+ * settings, see if the cert matches the caller's specified usage.
+ */
+ for(CFIndex addDex=0; addDex<numSpecs; addDex++) {
+ CFDictionaryRef tsDict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings,
+ addDex);
+
+ /* per-cert specs: all optional */
+ CFDataRef certPolicy = (CFDataRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsPolicy);
+ CFDataRef certApp = (CFDataRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsApplication);
+ CFStringRef certPolicyStr = (CFStringRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsPolicyString);
+ CFNumberRef certKeyUsage = (CFNumberRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsKeyUsage);
+ CFNumberRef certResultType = (CFNumberRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsResult);
+ CFNumberRef certAllowedErr = (CFNumberRef)CFDictionaryGetValue(tsDict,
+ kSecTrustSettingsAllowedError);
+
+ /* now, skip if we find a constraint that doesn't match intended use */
+ if(!tsCheckPolicy(policyOID, certPolicy)) {
+ continue;
+ }
+ if(!tsCheckApp(certApp)) {
+ continue;
+ }
+ if(!tsCheckKeyUse(keyUsage, certKeyUsage)) {
+ continue;
+ }
+ if(!tsCheckPolicyStr(policyStr, certPolicyStr)) {
+ continue;
+ }
+
+ trustSettingsEvalDbg("evaluateCert: MATCH");
+ foundSettings = true;
+
+ if(certAllowedErr) {
+ /* note we already validated this value */
+ SInt32 s;
+ CFNumberGetValue(certAllowedErr, kCFNumberSInt32Type, &s);
+ allowedErrs = (CSSM_RETURN *)::realloc(allowedErrs,
+ ++numAllowedErrs * sizeof(CSSM_RETURN));
+ allowedErrs[numAllowedErrs-1] = (CSSM_RETURN) s;
+ }
+
+ /*
+ * We found a match, but we only return the current result type
+ * to caller if we haven't already returned something other than
+ * kSecTrustSettingsResultUnspecified. Once we find a valid result type,
+ * we keep on searching, but only for additional allowed errors.
+ */
+ switch(returnedResult) {
+ /* found match but no valid resultType yet */
+ case kSecTrustSettingsResultUnspecified:
+ /* haven't been thru here */
+ case kSecTrustSettingsResultInvalid:
+ if(certResultType) {
+ /* note we already validated this */
+ SInt32 s;
+ CFNumberGetValue(certResultType, kCFNumberSInt32Type, &s);
+ returnedResult = (SecTrustSettingsResult)s;
+ }
+ else {
+ /* default is "copacetic" */
+ returnedResult = kSecTrustSettingsResultTrustRoot;
+ }
+ break;
+ default:
+ /* we already have a definitive resultType, don't change it */
+ break;
+ }
+ } /* for each dictionary in trustSettings */
+
+ *allowedErrors = allowedErrs;
+ *numAllowedErrors = numAllowedErrs;
+ if(returnedResult != kSecTrustSettingsResultInvalid) {
+ *resultType = returnedResult;
+ }
+ return foundSettings;
+}
+
+
+/*
+ * Find all certs in specified keychain list which have entries in this trust record.
+ * Certs already in the array are not added.
+ */
+void TrustSettings::findCerts(
+ StorageManager::KeychainList &keychains,
+ CFMutableArrayRef certArray)
+{
+ findQualifiedCerts(keychains,
+ true, /* findAll */
+ false, /* onlyRoots */
+ NULL, NULL, kSecTrustSettingsKeyUseAny,
+ certArray);
+}
+
+void TrustSettings::findQualifiedCerts(
+ StorageManager::KeychainList &keychains,
+ /*
+ * If findAll is true, all certs are returned and the subsequent
+ * qualifiers are ignored
+ */
+ bool findAll,
+ /* if true, only return root (self-signed) certs */
+ bool onlyRoots,
+ const CSSM_OID *policyOID, /* optional */
+ const char *policyString, /* optional */
+ SecTrustSettingsKeyUsage keyUsage, /* optional */
+ CFMutableArrayRef certArray) /* certs appended here */
+{
+ StLock<Mutex> _(SecTrustKeychainsGetMutex());
+
+ /*
+ * a set, hopefully with a good hash function for CFData, to keep track of what's
+ * been added to the outgoing array.
+ */
+ CFRef<CFMutableSetRef> certSet(CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks));
+
+ /* search: all certs, no attributes */
+ KCCursor cursor(keychains, CSSM_DL_DB_RECORD_X509_CERTIFICATE, NULL);
+ Item certItem;
+ bool found;
+ do {
+ found = cursor->next(certItem);
+ if(!found) {
+ break;
+ }
+ #if !SECTRUST_OSX
+ CFRef<SecCertificateRef> certRef((SecCertificateRef)certItem->handle());
+ #else
+ /* must convert to unified SecCertificateRef */
+ SecPointer<Certificate> certificate(static_cast<Certificate *>(&*certItem));
+ CssmData certCssmData = certificate->data();
+ if (!(certCssmData.Data && certCssmData.Length)) {
+ continue;
+ }
+ CFRef<CFDataRef> cfDataRef(CFDataCreate(NULL, certCssmData.Data, certCssmData.Length));
+ CFRef<SecCertificateRef> certRef(SecCertificateCreateWithData(NULL, cfDataRef));
+ #endif
+
+ /* do we have an entry for this cert? */
+ CFDictionaryRef certDict = findDictionaryForCert(certRef);
+ if(certDict == NULL) {
+ continue;
+ }
+
+ if(!findAll) {
+ /* qualify */
+ if(!qualifyUsageWithCertDict(certDict, policyOID,
+ policyString, keyUsage, onlyRoots)) {
+ continue;
+ }
+ }
+
+ /* see if we already have this one - get in CFData form */
+ CSSM_DATA certData;
+ OSStatus ortn = SecCertificateGetData(certRef, &certData);
+ if(ortn) {
+ trustSettingsEvalDbg("findQualifiedCerts: SecCertificateGetData error");
+ continue;
+ }
+ CFRef<CFDataRef> cfData(CFDataCreate(NULL, certData.Data, certData.Length));
+ CFDataRef cfd = cfData;
+ if(CFSetContainsValue(certSet, cfd)) {
+ trustSettingsEvalDbg("findQualifiedCerts: dup cert");
+ continue;
+ }
+ else {
+ /* add to the tracking set, which owns the CFData now */
+ CFSetAddValue(certSet, cfd);
+ /* and add the SecCert to caller's array, which owns that now */
+ CFArrayAppendValue(certArray, certRef);
+ }
+ } while(found);
+}
+
+/*
+ * Obtain trust settings for the specified cert. Returned settings array
+ * is in the public API form; caller must release. Returns NULL
+ * (does not throw) if the cert is not present in this TrustRecord.
+ */
+CFArrayRef TrustSettings::copyTrustSettings(
+ SecCertificateRef certRef)
+{
+ CFDictionaryRef certDict = NULL;
+
+ /* find the on-disk usage constraints for this cert */
+ certDict = findDictionaryForCert(certRef);
+ if(certDict == NULL) {
+ trustSettingsDbg("copyTrustSettings: dictionary not found");
+ return NULL;
+ }
+ CFArrayRef diskTrustSettings = (CFArrayRef)CFDictionaryGetValue(certDict,
+ kTrustRecordTrustSettings);
+ CFIndex numSpecs = 0;
+ if(diskTrustSettings != NULL) {
+ /* this field is optional */
+ numSpecs = CFArrayGetCount(diskTrustSettings);
+ }
+
+ /*
+ * Convert to API-style array of dictionaries.
+ * We give the caller an array even if it's empty.
+ */
+ CFRef<CFMutableArrayRef> outArray(CFArrayCreateMutable(NULL, numSpecs,
+ &kCFTypeArrayCallBacks));
+ for(CFIndex dex=0; dex<numSpecs; dex++) {
+ CFDictionaryRef diskTsDict =
+ (CFDictionaryRef)CFArrayGetValueAtIndex(diskTrustSettings, dex);
+ /* already validated... */
+ assert(CFGetTypeID(diskTsDict) == CFDictionaryGetTypeID());
+
+ CFDataRef certPolicy = (CFDataRef) CFDictionaryGetValue(diskTsDict, kSecTrustSettingsPolicy);
+ CFDataRef certApp = (CFDataRef) CFDictionaryGetValue(diskTsDict, kSecTrustSettingsApplication);
+ CFStringRef policyStr = (CFStringRef)CFDictionaryGetValue(diskTsDict, kSecTrustSettingsPolicyString);
+ CFNumberRef allowedErr = (CFNumberRef)CFDictionaryGetValue(diskTsDict, kSecTrustSettingsAllowedError);
+ CFNumberRef resultType = (CFNumberRef)CFDictionaryGetValue(diskTsDict, kSecTrustSettingsResult);
+ CFNumberRef keyUsage = (CFNumberRef)CFDictionaryGetValue(diskTsDict, kSecTrustSettingsKeyUsage);
+
+ if((certPolicy == NULL) &&
+ (certApp == NULL) &&
+ (policyStr == NULL) &&
+ (allowedErr == NULL) &&
+ (resultType == NULL) &&
+ (keyUsage == NULL)) {
+ /* weird but legal */
+ continue;
+ }
+ CFRef<CFMutableDictionaryRef> outTsDict(CFDictionaryCreateMutable(NULL,
+ 0, // capacity
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ if(certPolicy != NULL) {
+ /* convert OID as CFDataRef to SecPolicyRef */
+ SecPolicyRef policyRef = NULL;
+ CSSM_OID policyOid = { CFDataGetLength(certPolicy),
+ (uint8 *)CFDataGetBytePtr(certPolicy) };
+ OSStatus ortn = SecPolicyCopy(CSSM_CERT_X_509v3, &policyOid, &policyRef);
+ if(ortn) {
+ trustSettingsDbg("copyTrustSettings: OID conversion error");
+ abort("Bad Policy OID in trusted root list", errSecInvalidTrustedRootRecord);
+ }
+ CFDictionaryAddValue(outTsDict, kSecTrustSettingsPolicy, policyRef);
+ CFRelease(policyRef); // owned by dictionary
+ }
+
+ if(certApp != NULL) {
+ /* convert app as CFDataRef to SecTrustedApplicationRef */
+ SecTrustedApplicationRef appRef;
+ OSStatus ortn = SecTrustedApplicationCreateWithExternalRepresentation(certApp, &appRef);
+ if(ortn) {
+ trustSettingsDbg("copyTrustSettings: App conversion error");
+ abort("Bad application data in trusted root list", errSecInvalidTrustedRootRecord);
+ }
+ CFDictionaryAddValue(outTsDict, kSecTrustSettingsApplication, appRef);
+ CFRelease(appRef); // owned by dictionary
+ }
+
+ /* remaining 4 are trivial */
+ if(policyStr != NULL) {
+ /*
+ * copy, since policyStr is in our mutable dictionary and could change out from
+ * under the caller
+ */
+ CFStringRef str = CFStringCreateCopy(NULL, policyStr);
+ CFDictionaryAddValue(outTsDict, kSecTrustSettingsPolicyString, str);
+ CFRelease(str); // owned by dictionary
+ }
+ if(allowedErr != NULL) {
+ /* there is no mutable CFNumber, so.... */
+ CFDictionaryAddValue(outTsDict, kSecTrustSettingsAllowedError, allowedErr);
+ }
+ if(resultType != NULL) {
+ CFDictionaryAddValue(outTsDict, kSecTrustSettingsResult, resultType);
+ }
+ if(keyUsage != NULL) {
+ CFDictionaryAddValue(outTsDict, kSecTrustSettingsKeyUsage, keyUsage);
+ }
+ CFArrayAppendValue(outArray, outTsDict);
+ /* outTsDict autoreleases; owned by outArray now */
+ }
+ CFRetain(outArray); // now that it's good to go....
+ return outArray;
+}
+
+CFDateRef TrustSettings::copyModDate(
+ SecCertificateRef certRef)
+{
+ CFDictionaryRef certDict = NULL;
+
+ /* find the on-disk usage constraints dictionary for this cert */
+ certDict = findDictionaryForCert(certRef);
+ if(certDict == NULL) {
+ trustSettingsDbg("copyModDate: dictionary not found");
+ return NULL;
+ }
+ CFDateRef modDate = (CFDateRef)CFDictionaryGetValue(certDict, kTrustRecordModDate);
+ if(modDate == NULL) {
+ return NULL;
+ }
+
+ /* this only works becuase there is no mutable CFDateRef */
+ CFRetain(modDate);
+ return modDate;
+}
+
+/*
+ * Modify cert's trust settings, or add a new cert to the record.
+ */
+void TrustSettings::setTrustSettings(
+ SecCertificateRef certRef,
+ CFTypeRef trustSettingsDictOrArray)
+{
+ /* to validate, we need to know if the cert is self-signed */
+ OSStatus ortn;
+ Boolean isSelfSigned = false;
+
+ if(certRef == kSecTrustSettingsDefaultRootCertSetting) {
+ /*
+ * Validate settings as if this were root, specifically,
+ * kSecTrustSettingsResultTrustRoot (explicitly or by
+ * default) is OK.
+ */
+ isSelfSigned = true;
+ }
+ else {
+ ortn = SecCertificateIsSelfSigned(certRef, &isSelfSigned);
+ if(ortn) {
+ MacOSError::throwMe(ortn);
+ }
+ }
+
+ /* caller's app/policy spec OK? */
+ CFRef<CFArrayRef> trustSettings(validateApiTrustSettings(
+ trustSettingsDictOrArray, isSelfSigned));
+
+ /* caller is responsible for ensuring these */
+ assert(mPropList != NULL);
+ assert(mDomain != kSecTrustSettingsDomainSystem);
+
+ /* extract issuer and serial number from the cert, if it's a cert */
+ CFRef<CFDataRef> issuer;
+ CFRef<CFDataRef> serial;
+ if(certRef != kSecTrustSettingsDefaultRootCertSetting) {
+ copyIssuerAndSerial(certRef, issuer.take(), serial.take());
+ }
+ else {
+ UInt8 dummy;
+ issuer = CFDataCreate(NULL, &dummy, 0);
+ serial = CFDataCreate(NULL, &dummy, 0);
+ }
+
+ /* SHA1 digest as string */
+ CFRef<CFStringRef> certHashStr(SecTrustSettingsCertHashStrFromCert(certRef));
+ if(!certHashStr) {
+ trustSettingsDbg("TrustSettings::setTrustSettings: CertHashStrFromCert error");
+ MacOSError::throwMe(errSecItemNotFound);
+ }
+
+ /*
+ * Find entry for this cert, if present.
+ */
+ CFMutableDictionaryRef certDict =
+ (CFMutableDictionaryRef)findDictionaryForCertHash(certHashStr);
+ if(certDict == NULL) {
+ /* create new dictionary */
+ certDict = CFDictionaryCreateMutable(NULL, kSecTrustRecordNumCertDictKeys,
+ &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if(certDict == NULL) {
+ MacOSError::throwMe(errSecAllocate);
+ }
+ CFDictionaryAddValue(certDict, kTrustRecordIssuer, issuer);
+ CFDictionaryAddValue(certDict, kTrustRecordSerialNumber, serial);
+ if(CFArrayGetCount(trustSettings) != 0) {
+ /* skip this if the settings array is empty */
+ CFDictionaryAddValue(certDict, kTrustRecordTrustSettings, trustSettings);
+ }
+ tsSetModDate(certDict);
+
+ /* add this new cert dictionary to top-level mTrustDict */
+ CFDictionaryAddValue(mTrustDict, static_cast<CFStringRef>(certHashStr), certDict);
+
+ /* mTrustDict owns the dictionary now */
+ CFRelease(certDict);
+ }
+ else {
+ /* update */
+ tsSetModDate(certDict);
+ if(CFArrayGetCount(trustSettings) != 0) {
+ CFDictionarySetValue(certDict, kTrustRecordTrustSettings, trustSettings);
+ }
+ else {
+ /* empty settings array: remove from dictionary */
+ CFDictionaryRemoveValue(certDict, kTrustRecordTrustSettings);
+ }
+ }
+ mDirty = true;
+}
+
+/*
+ * Delete a certificate's trust settings.
+ */
+void TrustSettings::deleteTrustSettings(
+ SecCertificateRef certRef)
+{
+ CFDictionaryRef certDict = NULL;
+
+ /* caller is responsible for ensuring these */
+ assert(mPropList != NULL);
+ assert(mDomain != kSecTrustSettingsDomainSystem);
+
+ /* SHA1 digest as string */
+ CFRef<CFStringRef> certHashStr(SecTrustSettingsCertHashStrFromCert(certRef));
+ if(!certHashStr) {
+ MacOSError::throwMe(errSecItemNotFound);
+ }
+
+ /* present in top-level mTrustDict? */
+ certDict = findDictionaryForCertHash(certHashStr);
+ if(certDict != NULL) {
+ CFDictionaryRemoveValue(mTrustDict, static_cast<CFStringRef>(certHashStr));
+ mDirty = true;
+ }
+ else {
+ /*
+ * Throwing this error is the only reason we don't blindly do
+ * a CFDictionaryRemoveValue() without first doing
+ * findDictionaryForCertHash().
+ */
+ trustSettingsDbg("TrustSettings::deleteRoot: cert dictionary not found");
+ MacOSError::throwMe(errSecItemNotFound);
+ }
+}
+
+#pragma mark --- Private methods ---
+
+/*
+ * Find a given cert's entry in the top-level mTrustDict. Return the
+ * entry as a dictionary. Returned dictionary is not refcounted.
+ * The mutability of the returned dictionary is the same as the mutability
+ * of the underlying StickRecord::mPropList, which the caller is just
+ * going to have to know (and cast accordingly if a mutable dictionary
+ * is needed).
+ */
+CFDictionaryRef TrustSettings::findDictionaryForCert(
+ SecCertificateRef certRef)
+{
+ CFRef<CFStringRef> certHashStr(SecTrustSettingsCertHashStrFromCert(certRef));
+ if (certHashStr.get() == NULL)
+ {
+ return NULL;
+ }
+
+ return findDictionaryForCertHash(static_cast<CFStringRef>(certHashStr));
+}
+
+/*
+ * Find entry in mTrustDict given cert hash string.
+ */
+CFDictionaryRef TrustSettings::findDictionaryForCertHash(
+ CFStringRef certHashStr)
+{
+ assert(mTrustDict != NULL);
+ return (CFDictionaryRef)CFDictionaryGetValue(mTrustDict, certHashStr);
+}
+
+/*
+ * Validate incoming trust settings, which may be NULL, a dictionary, or
+ * an array of dictionaries. Convert from the API-style dictionaries
+ * to the internal style suitable for writing to disk as part of
+ * mPropList.
+ *
+ * We return a refcounted CFArray in any case if the incoming parameter is good.
+ */
+CFArrayRef TrustSettings::validateApiTrustSettings(
+ CFTypeRef trustSettingsDictOrArray,
+ Boolean isSelfSigned)
+{
+ CFArrayRef tmpInArray = NULL;
+
+ if(trustSettingsDictOrArray == NULL) {
+#if SECTRUST_OSX
+#warning STU: temporarily unblocking build
+#else
+ /* trivial case, only valid for roots */
+ if(!isSelfSigned) {
+ trustSettingsDbg("validateApiUsageConstraints: !isSelfSigned, no settings");
+ MacOSError::throwMe(errSecParam);
+ }
+#endif
+ return CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
+ }
+ else if(CFGetTypeID(trustSettingsDictOrArray) == CFDictionaryGetTypeID()) {
+ /* array-ize it */
+ tmpInArray = CFArrayCreate(NULL, &trustSettingsDictOrArray, 1,
+ &kCFTypeArrayCallBacks);
+ }
+ else if(CFGetTypeID(trustSettingsDictOrArray) == CFArrayGetTypeID()) {
+ /* as is, refcount - we'll release later */
+ tmpInArray = (CFArrayRef)trustSettingsDictOrArray;
+ CFRetain(tmpInArray);
+ }
+ else {
+ trustSettingsDbg("validateApiUsageConstraints: bad trustSettingsDictOrArray");
+ MacOSError::throwMe(errSecParam);
+ }
+
+ CFIndex numSpecs = CFArrayGetCount(tmpInArray);
+ CFMutableArrayRef outArray = CFArrayCreateMutable(NULL, numSpecs, &kCFTypeArrayCallBacks);
+ CSSM_OID oid;
+ OSStatus ortn = errSecSuccess;
+ SecPolicyRef certPolicy;
+ SecTrustedApplicationRef certApp;
+
+ /* convert */
+ for(CFIndex dex=0; dex<numSpecs; dex++) {
+ CFDataRef oidData = NULL;
+ CFDataRef appData = NULL;
+ CFStringRef policyStr = NULL;
+ CFNumberRef allowedErr = NULL;
+ CFNumberRef resultType = NULL;
+ CFNumberRef keyUsage = NULL;
+ SInt32 resultNum;
+ SecTrustSettingsResult result;
+
+ /* each element is a dictionary */
+ CFDictionaryRef ucDict = (CFDictionaryRef)CFArrayGetValueAtIndex(tmpInArray, dex);
+ if(CFGetTypeID(ucDict) != CFDictionaryGetTypeID()) {
+ trustSettingsDbg("validateAppPolicyArray: malformed usageConstraint dictionary");
+ ortn = errSecParam;
+ break;
+ }
+
+ /* policy - optional */
+ certPolicy = (SecPolicyRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsPolicy);
+ if(certPolicy != NULL) {
+ if(CFGetTypeID(certPolicy) != SecPolicyGetTypeID()) {
+ trustSettingsDbg("validateAppPolicyArray: malformed certPolicy");
+ ortn = errSecParam;
+ break;
+ }
+ ortn = SecPolicyGetOID(certPolicy, &oid);
+ if(ortn) {
+ trustSettingsDbg("validateAppPolicyArray: SecPolicyGetOID error");
+ break;
+ }
+ oidData = CFDataCreate(NULL, oid.Data, oid.Length);
+ }
+
+ /* application - optional */
+ certApp = (SecTrustedApplicationRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsApplication);
+ if(certApp != NULL) {
+ if(CFGetTypeID(certApp) != SecTrustedApplicationGetTypeID()) {
+ trustSettingsDbg("validateAppPolicyArray: malformed certApp");
+ ortn = errSecParam;
+ break;
+ }
+ ortn = SecTrustedApplicationCopyExternalRepresentation(certApp, &appData);
+ if(ortn) {
+ trustSettingsDbg("validateAppPolicyArray: "
+ "SecTrustedApplicationCopyExternalRepresentation error");
+ break;
+ }
+ }
+
+ policyStr = (CFStringRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsPolicyString);
+ if(policyStr != NULL) {
+ if(CFGetTypeID(policyStr) != CFStringGetTypeID()) {
+ trustSettingsDbg("validateAppPolicyArray: malformed policyStr");
+ ortn = errSecParam;
+ break;
+ }
+ }
+ allowedErr = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsAllowedError);
+ if(!tsIsGoodCfNum(allowedErr)) {
+ trustSettingsDbg("validateAppPolicyArray: malformed allowedErr");
+ ortn = errSecParam;
+ break;
+ }
+ resultType = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsResult);
+ if(!tsIsGoodCfNum(resultType, &resultNum)) {
+ trustSettingsDbg("validateAppPolicyArray: malformed resultType");
+ ortn = errSecParam;
+ break;
+ }
+ result = resultNum;
+ /* validate result later */
+
+ keyUsage = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsKeyUsage);
+ if(!tsIsGoodCfNum(keyUsage)) {
+ trustSettingsDbg("validateAppPolicyArray: malformed keyUsage");
+ ortn = errSecParam;
+ break;
+ }
+
+ if(!oidData && !appData && !policyStr &&
+ !allowedErr && !resultType && !keyUsage) {
+ /* nothing here - weird, but legal - skip it */
+ continue;
+ }
+
+ /* create dictionary for this usageConstraint */
+ CFMutableDictionaryRef outDict = CFDictionaryCreateMutable(NULL,
+ 2,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if(oidData) {
+ CFDictionaryAddValue(outDict, kSecTrustSettingsPolicy, oidData);
+ CFRelease(oidData); // owned by dictionary
+ }
+ if(appData) {
+ CFDictionaryAddValue(outDict, kSecTrustSettingsApplication, appData);
+ CFRelease(appData); // owned by dictionary
+ }
+ if(policyStr) {
+ CFDictionaryAddValue(outDict, kSecTrustSettingsPolicyString, policyStr);
+ /* still owned by ucDict */
+ }
+ if(allowedErr) {
+ CFDictionaryAddValue(outDict, kSecTrustSettingsAllowedError, allowedErr);
+ }
+#if SECTRUST_OSX
+#warning STU: temporarily unblocking build
+ ortn = errSecSuccess;
+#else
+ if(resultType) {
+ /* let's be really picky on this one */
+ switch(result) {
+ case kSecTrustSettingsResultInvalid:
+ ortn = errSecParam;
+ break;
+ case kSecTrustSettingsResultTrustRoot:
+ if(!isSelfSigned) {
+ trustSettingsDbg("validateAppPolicyArray: TrustRoot, !isSelfSigned");
+ ortn = errSecParam;
+ }
+ break;
+ case kSecTrustSettingsResultTrustAsRoot:
+ if(isSelfSigned) {
+ trustSettingsDbg("validateAppPolicyArray: TrustAsRoot, isSelfSigned");
+ ortn = errSecParam;
+ }
+ break;
+ case kSecTrustSettingsResultDeny:
+ case kSecTrustSettingsResultUnspecified:
+ break;
+ default:
+ trustSettingsDbg("validateAppPolicyArray: bogus resultType");
+ ortn = errSecParam;
+ break;
+ }
+ if(ortn) {
+ break;
+ }
+ CFDictionaryAddValue(outDict, kSecTrustSettingsResult, resultType);
+ }
+ else {
+ /* no resultType; default of TrustRoot only valid for root */
+ if(!isSelfSigned) {
+ trustSettingsDbg("validateAppPolicyArray: default result, !isSelfSigned");
+ ortn = errSecParam;
+ break;
+ }
+ }
+#endif
+ if(keyUsage) {
+ CFDictionaryAddValue(outDict, kSecTrustSettingsKeyUsage, keyUsage);
+ }
+
+ /* append dictionary to output */
+ CFArrayAppendValue(outArray, outDict);
+ /* array owns the dictionary now */
+ CFRelease(outDict);
+
+ } /* for each usage constraint dictionary */
+
+ CFRelease(tmpInArray);
+ if(ortn) {
+ CFRelease(outArray);
+ MacOSError::throwMe(ortn);
+ }
+ return outArray;
+}
+
+/*
+ * Validate an trust settings array obtained from disk.
+ * Returns true if OK, else returns false.
+ */
+bool TrustSettings::validateTrustSettingsArray(
+ CFArrayRef trustSettings)
+{
+ CFIndex numSpecs = CFArrayGetCount(trustSettings);
+ for(CFIndex dex=0; dex<numSpecs; dex++) {
+ CFDictionaryRef ucDict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings,
+ dex);
+ if(CFGetTypeID(ucDict) != CFDictionaryGetTypeID()) {
+ trustSettingsDbg("validateAppPolicyArray: malformed app/policy dictionary");
+ return false;
+ }
+ CFDataRef certPolicy = (CFDataRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsPolicy);
+ if((certPolicy != NULL) && (CFGetTypeID(certPolicy) != CFDataGetTypeID())) {
+ trustSettingsDbg("validateAppPolicyArray: malformed certPolicy");
+ return false;
+ }
+ CFDataRef certApp = (CFDataRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsApplication);
+ if((certApp != NULL) && (CFGetTypeID(certApp) != CFDataGetTypeID())) {
+ trustSettingsDbg("validateAppPolicyArray: malformed certApp");
+ return false;
+ }
+ CFStringRef policyStr = (CFStringRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsPolicyString);
+ if((policyStr != NULL) && (CFGetTypeID(policyStr) != CFStringGetTypeID())) {
+ trustSettingsDbg("validateAppPolicyArray: malformed policyStr");
+ return false;
+ }
+ CFNumberRef cfNum = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsAllowedError);
+ if(!tsIsGoodCfNum(cfNum)) {
+ trustSettingsDbg("validateAppPolicyArray: malformed allowedErr");
+ return false;
+ }
+ cfNum = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsResult);
+ if(!tsIsGoodCfNum(cfNum)) {
+ trustSettingsDbg("validateAppPolicyArray: malformed resultType");
+ return false;
+ }
+ cfNum = (CFNumberRef)CFDictionaryGetValue(ucDict, kSecTrustSettingsKeyUsage);
+ if(!tsIsGoodCfNum(cfNum)) {
+ trustSettingsDbg("validateAppPolicyArray: malformed keyUsage");
+ return false;
+ }
+ } /* for each usageConstraint dictionary */
+ return true;
+}
+
+/*
+ * Validate mPropList after it's read from disk or supplied as an external
+ * representation. Allows subsequent use of mTrustDict to proceed with
+ * relative impunity.
+ */
+void TrustSettings::validatePropList(bool trim)
+{
+ /* top level dictionary */
+ if(!mPropList) {
+ trustSettingsDbg("TrustSettings::validatePropList missing mPropList");
+ abort("missing propList", errSecInvalidTrustedRootRecord);
+ }
+
+ if(CFGetTypeID(mPropList) != CFDictionaryGetTypeID()) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed mPropList");
+ abort("malformed propList", errSecInvalidTrustedRootRecord);
+ }
+
+ /* That dictionary has two entries */
+ CFNumberRef cfVers = (CFNumberRef)CFDictionaryGetValue(mPropList, kTrustRecordVersion);
+ if((cfVers == NULL) || (CFGetTypeID(cfVers) != CFNumberGetTypeID())) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed version");
+ abort("malformed version", errSecInvalidTrustedRootRecord);
+ }
+ if(!CFNumberGetValue(cfVers, kCFNumberSInt32Type, &mDictVersion)) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed version");
+ abort("malformed version", errSecInvalidTrustedRootRecord);
+ }
+ if((mDictVersion > kSecTrustRecordVersionCurrent) ||
+ (mDictVersion == kSecTrustRecordVersionInvalid)) {
+ trustSettingsDbg("TrustSettings::validatePropList: incompatible version");
+ abort("incompatible version", errSecInvalidTrustedRootRecord);
+ }
+ /* other backwards-compatibility handling done later, if needed, per mDictVersion */
+
+ mTrustDict = (CFMutableDictionaryRef)CFDictionaryGetValue(mPropList, kTrustRecordTrustList);
+ if(mTrustDict != NULL) {
+ CFRetain(mTrustDict);
+ }
+ if((mTrustDict == NULL) || (CFGetTypeID(mTrustDict) != CFDictionaryGetTypeID())) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed mTrustDict");
+ abort("malformed TrustArray", errSecInvalidTrustedRootRecord);
+ }
+
+ /* grind through the per-cert entries */
+ CFIndex numCerts = CFDictionaryGetCount(mTrustDict);
+ const void *dictKeys[numCerts];
+ const void *dictValues[numCerts];
+ CFDictionaryGetKeysAndValues(mTrustDict, dictKeys, dictValues);
+
+ for(CFIndex dex=0; dex<numCerts; dex++) {
+ /* get per-cert dictionary */
+ CFMutableDictionaryRef certDict = (CFMutableDictionaryRef)dictValues[dex];
+ if((certDict == NULL) || (CFGetTypeID(certDict) != CFDictionaryGetTypeID())) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed certDict");
+ abort("malformed certDict", errSecInvalidTrustedRootRecord);
+ }
+
+ /*
+ * That dictionary has exactly four entries.
+ * If we're trimming, all we need is the actual trust settings.
+ */
+
+ /* issuer */
+ CFDataRef cfd = (CFDataRef)CFDictionaryGetValue(certDict, kTrustRecordIssuer);
+ if(cfd == NULL) {
+ trustSettingsDbg("TrustSettings::validatePropList: missing issuer");
+ abort("missing issuer", errSecInvalidTrustedRootRecord);
+ }
+ if(CFGetTypeID(cfd) != CFDataGetTypeID()) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed issuer");
+ abort("malformed issuer", errSecInvalidTrustedRootRecord);
+ }
+ if(trim) {
+ CFDictionaryRemoveValue(certDict, kTrustRecordIssuer);
+ }
+
+ /* serial number */
+ cfd = (CFDataRef)CFDictionaryGetValue(certDict, kTrustRecordSerialNumber);
+ if(cfd == NULL) {
+ trustSettingsDbg("TrustSettings::validatePropList: missing serial number");
+ abort("missing serial number", errSecInvalidTrustedRootRecord);
+ }
+ if(CFGetTypeID(cfd) != CFDataGetTypeID()) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed serial number");
+ abort("malformed serial number", errSecInvalidTrustedRootRecord);
+ }
+ if(trim) {
+ CFDictionaryRemoveValue(certDict, kTrustRecordSerialNumber);
+ }
+
+ /* modification date */
+ CFDateRef modDate = (CFDateRef)CFDictionaryGetValue(certDict, kTrustRecordModDate);
+ if(modDate == NULL) {
+ trustSettingsDbg("TrustSettings::validatePropList: missing modDate");
+ abort("missing modDate", errSecInvalidTrustedRootRecord);
+ }
+ if(CFGetTypeID(modDate) != CFDateGetTypeID()) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed modDate");
+ abort("malformed modDate", errSecInvalidTrustedRootRecord);
+ }
+ if(trim) {
+ CFDictionaryRemoveValue(certDict, kTrustRecordModDate);
+ }
+
+ /* the actual trust settings */
+ CFArrayRef trustSettings = (CFArrayRef)CFDictionaryGetValue(certDict,
+ kTrustRecordTrustSettings);
+ if(trustSettings == NULL) {
+ /* optional; this cert's entry is good */
+ continue;
+ }
+ if(CFGetTypeID(trustSettings) != CFArrayGetTypeID()) {
+ trustSettingsDbg("TrustSettings::validatePropList: malformed useConstraint"
+ "array");
+ abort("malformed useConstraint array", errSecInvalidTrustedRootRecord);
+ }
+
+ /* Now validate the usageConstraint array contents */
+ if(!validateTrustSettingsArray(trustSettings)) {
+ abort("malformed useConstraint array", errSecInvalidTrustedRootRecord);
+ }
+ } /* for each cert dictionary in top-level array */
+
+ if(trim) {
+ /* we don't need the top-level dictionary any more */
+ CFRelease(mPropList);
+ mPropList = NULL;
+ }
+}
+
+/*
+ * Obtain non-normalized issuer and serial number for specified cert, both
+ * returned as CFDataRefs owned by caller.
+ */
+void TrustSettings::copyIssuerAndSerial(
+ SecCertificateRef certRef,
+ CFDataRef *issuer, /* optional, RETURNED */
+ CFDataRef *serial) /* RETURNED */
+{
+#if SECTRUST_OSX
+ CFRef<SecCertificateRef> certificate = SecCertificateCreateItemImplInstance(certRef);
+#else
+ CFRef<SecCertificateRef> certificate = (SecCertificateRef) ((certRef) ? CFRetain(certRef) : NULL);
+#endif
+
+ SecPointer<Certificate> cert = Certificate::required(certificate);
+ CSSM_DATA_PTR fieldVal;
+
+ if(issuer != NULL) {
+ fieldVal = cert->copyFirstFieldValue(CSSMOID_X509V1IssuerNameStd);
+ *issuer = CFDataCreate(NULL, fieldVal->Data, fieldVal->Length);
+ cert->releaseFieldValue(CSSMOID_X509V1IssuerNameStd, fieldVal);
+ }
+
+ fieldVal = cert->copyFirstFieldValue(CSSMOID_X509V1SerialNumber);
+ *serial = CFDataCreate(NULL, fieldVal->Data, fieldVal->Length);
+ cert->releaseFieldValue(CSSMOID_X509V1SerialNumber, fieldVal);
+}
+
+void TrustSettings::abort(
+ const char *why,
+ OSStatus err)
+{
+ Syslog::error("TrustSettings: %s", why);
+ MacOSError::throwMe(err);
+}
+