X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/libsecurity_keychain/Security/TrustSettings.cpp diff --git a/OSX/libsecurity_keychain/Security/TrustSettings.cpp b/OSX/libsecurity_keychain/Security/TrustSettings.cpp new file mode 100644 index 00000000..bcd2d033 --- /dev/null +++ b/OSX/libsecurity_keychain/Security/TrustSettings.cpp @@ -0,0 +1,1585 @@ +/* + * 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 +#include "SecTrustedApplicationPriv.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; addDexmPropList = tsInitialDict(); + t->mDirty = true; + } + else { + trustSettingsDbg("TrustSettings: record not found for domain %d", + (int)domain); + delete t; + return ortn; + } + } + else { + CFRef 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) : ""); + 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 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 _(SecTrustKeychainsGetMutex()); + + /* + * a set, hopefully with a good hash function for CFData, to keep track of what's + * been added to the outgoing array. + */ + CFRef 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 certRef((SecCertificateRef)certItem->handle()); + #else + /* must convert to unified SecCertificateRef */ + SecPointer certificate(static_cast(&*certItem)); + CssmData certCssmData = certificate->data(); + if (!(certCssmData.Data && certCssmData.Length)) { + continue; + } + CFRef cfDataRef(CFDataCreate(NULL, certCssmData.Data, certCssmData.Length)); + CFRef 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 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 outArray(CFArrayCreateMutable(NULL, numSpecs, + &kCFTypeArrayCallBacks)); + for(CFIndex dex=0; dex 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 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 issuer; + CFRef 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 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(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 certHashStr(SecTrustSettingsCertHashStrFromCert(certRef)); + if(!certHashStr) { + MacOSError::throwMe(errSecItemNotFound); + } + + /* present in top-level mTrustDict? */ + certDict = findDictionaryForCertHash(certHashStr); + if(certDict != NULL) { + CFDictionaryRemoveValue(mTrustDict, static_cast(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 certHashStr(SecTrustSettingsCertHashStrFromCert(certRef)); + if (certHashStr.get() == NULL) + { + return NULL; + } + + return findDictionaryForCertHash(static_cast(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 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 certificate = SecCertificateCreateItemImplInstance(certRef); +#else + CFRef certificate = (SecCertificateRef) ((certRef) ? CFRetain(certRef) : NULL); +#endif + + SecPointer 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); +} +