X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_keychain/lib/TrustRevocation.cpp diff --git a/libsecurity_keychain/lib/TrustRevocation.cpp b/libsecurity_keychain/lib/TrustRevocation.cpp new file mode 100644 index 00000000..4817eabb --- /dev/null +++ b/libsecurity_keychain/lib/TrustRevocation.cpp @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2002-2004 Apple Computer, 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@ + */ + +/* +* TrustRevocation.cpp - private revocation policy manipulation +*/ + +#include +#include +#include +#include +#include "SecBridge.h" +#include +#include + +/* + * These may go into an SPI header for the SecTrust object. + */ +typedef enum { + /* this revocation policy disabled */ + kSecDisabled, + /* try, but tolerate inability to complete */ + kSecBestAttempt, + /* require successful revocation check if certificate indicates + * the policy is supported */ + kSecRequireIfPresentInCertificate, + /* require for every cert */ + kSecRequireForAllCertificates +} SecRevocationPolicyStyle; + +using namespace KeychainCore; + +/* + * Given an app-specified array of Policies, determine if at least one of them + * matches the given policy OID. + */ +bool Trust::policySpecified(CFArrayRef policies, const CSSM_OID &inOid) +{ + if(policies == NULL) { + return false; + } + CFIndex numPolicies = CFArrayGetCount(policies); + for(CFIndex dex=0; dex pol = Policy::required(SecPolicyRef(secPol)); + const CssmOid &oid = pol->oid(); + if(oid == CssmOid::overlay(inOid)) { + return true; + } + } + return false; +} + +/* + * Given an app-specified array of Policies, determine if at least one of them + * is an explicit revocation policy. + */ +bool Trust::revocationPolicySpecified(CFArrayRef policies) +{ + if(policies == NULL) { + return false; + } + CFIndex numPolicies = CFArrayGetCount(policies); + for(CFIndex dex=0; dex pol = Policy::required(SecPolicyRef(secPol)); + const CssmOid &oid = pol->oid(); + if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL)) { + return true; + } + if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP)) { + return true; + } + } + return false; +} + +CFMutableArrayRef Trust::addSpecifiedRevocationPolicies( + uint32 &numAdded, + Allocator &alloc) +{ + /* policies specified by SPI not implemented */ + return NULL; +} + +void Trust::freeSpecifiedRevocationPolicies( + CFArrayRef policies, + uint32 numAdded, + Allocator &alloc) +{ + /* shouldn't be called */ + MacOSError::throwMe(unimpErr); +} + +static SecRevocationPolicyStyle parseRevStyle(CFStringRef val) +{ + if(CFEqual(val, kSecRevocationOff)) { + return kSecDisabled; + } + else if(CFEqual(val, kSecRevocationBestAttempt)) { + return kSecBestAttempt; + } + else if(CFEqual(val, kSecRevocationRequireIfPresent)) { + return kSecRequireIfPresentInCertificate; + } + else if(CFEqual(val, kSecRevocationRequireForAll)) { + return kSecRequireForAllCertificates; + } + else { + return kSecDisabled; + } +} + +CFDictionaryRef Trust::defaultRevocationSettings() +{ + /* + defaults read ~/Library/Preferences/com.apple.security.revocation + { + CRLStyle = BestAttempt; + CRLSufficientPerCert = 1; + OCSPStyle = BestAttempt; + OCSPSufficientPerCert = 1; + RevocationFirst = OCSP; + } + */ + const void *keys[] = { + kSecRevocationCrlStyle, + kSecRevocationCRLSufficientPerCert, + kSecRevocationOcspStyle, + kSecRevocationOCSPSufficientPerCert, + kSecRevocationWhichFirst + }; + const void *values[] = { + kSecRevocationBestAttempt, + kCFBooleanTrue, + kSecRevocationBestAttempt, + kCFBooleanTrue, + kSecRevocationOcspFirst + }; + + return CFDictionaryCreate(kCFAllocatorDefault, keys, + values, sizeof(keys) / sizeof(*keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); +} + +CFMutableArrayRef Trust::addPreferenceRevocationPolicies( + uint32 &numAdded, + Allocator &alloc) +{ + numAdded = 0; + + /* any per-user prefs? */ + Dictionary* pd = Dictionary::CreateDictionary(kSecRevocationDomain, Dictionary::US_User, true); + if (pd) + { + if (!pd->dict()) { + delete pd; + pd = NULL; + } + } + + if(pd == NULL) + { + pd = Dictionary::CreateDictionary(kSecRevocationDomain, Dictionary::US_System, true); + if (!pd->dict()) { + delete pd; + pd = NULL; + } + } + + if(pd == NULL) + { + CFDictionaryRef tempDict = defaultRevocationSettings(); + if (tempDict == NULL) + return NULL; + + pd = new Dictionary(tempDict); + CFRelease(tempDict); + } + + auto_ptr prefsDict(pd); + + bool doOcsp = false; + bool doCrl = false; + CFStringRef val; + SecRevocationPolicyStyle ocspStyle = kSecBestAttempt; + SecRevocationPolicyStyle crlStyle = kSecBestAttempt; + SecPointer ocspPolicy; + SecPointer crlPolicy; + + /* Are any revocation policies enabled? */ + val = prefsDict->getStringValue(kSecRevocationOcspStyle); + if(val != NULL) { + ocspStyle = parseRevStyle(val); + if(ocspStyle != kSecDisabled) { + doOcsp = true; + } + } + val = prefsDict->getStringValue(kSecRevocationCrlStyle); + if(val != NULL) { + crlStyle = parseRevStyle(val); + if(crlStyle != kSecDisabled) { + doCrl = true; + } + } + if(!doCrl && !doOcsp) { + return NULL; + } + + /* which policy first? */ + bool ocspFirst = true; // default if both present + if(doCrl && doOcsp) { + val = prefsDict->getStringValue(kSecRevocationWhichFirst); + if((val != NULL) && CFEqual(val, kSecRevocationCrlFirst)) { + ocspFirst = false; + } + } + + /* We're adding something to mPolicies, so make a copy we can work with */ + CFMutableArrayRef policies = CFArrayCreateMutableCopy(NULL, 0, mPolicies); + if(policies == NULL) { + throw std::bad_alloc(); + } + + if(doOcsp) { + /* Cook up a new Policy object */ + ocspPolicy = new Policy(mTP, CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP)); + CSSM_APPLE_TP_OCSP_OPTIONS opts; + memset(&opts, 0, sizeof(opts)); + opts.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + + /* Now fill in the OCSP-related blanks */ + switch(ocspStyle) { + case kSecDisabled: + assert(0); + break; + case kSecBestAttempt: + /* default, nothing to set */ + break; + case kSecRequireIfPresentInCertificate: + opts.Flags |= CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT; + break; + case kSecRequireForAllCertificates: + opts.Flags |= CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT; + break; + } + + if(prefsDict->getBoolValue(kSecRevocationOCSPSufficientPerCert)) { + opts.Flags |= CSSM_TP_ACTION_OCSP_SUFFICIENT; + } + + val = prefsDict->getStringValue(kSecOCSPLocalResponder); + if(val != NULL) { + CFDataRef cfData = CFStringCreateExternalRepresentation(NULL, + val, kCFStringEncodingUTF8, 0); + CFIndex len = CFDataGetLength(cfData); + opts.LocalResponder = (CSSM_DATA_PTR)alloc.malloc(sizeof(CSSM_DATA)); + opts.LocalResponder->Data = (uint8 *)alloc.malloc(len); + opts.LocalResponder->Length = len; + memmove(opts.LocalResponder->Data, CFDataGetBytePtr(cfData), len); + CFRelease(cfData); + } + + /* Policy manages its own copy of this data */ + CSSM_DATA optData = {sizeof(opts), (uint8 *)&opts}; + ocspPolicy->value() = optData; + numAdded++; + } + + if(doCrl) { + /* Cook up a new Policy object */ + crlPolicy = new Policy(mTP, CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL)); + CSSM_APPLE_TP_CRL_OPTIONS opts; + memset(&opts, 0, sizeof(opts)); + opts.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + + /* Now fill in the CRL-related blanks */ + opts.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET; // default true + switch(crlStyle) { + case kSecDisabled: + assert(0); + break; + case kSecBestAttempt: + /* default, nothing to set */ + break; + case kSecRequireIfPresentInCertificate: + opts.CrlFlags |= CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT; + break; + case kSecRequireForAllCertificates: + opts.CrlFlags |= CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT; + break; + } + if(prefsDict->getBoolValue(kSecRevocationCRLSufficientPerCert)) { + opts.CrlFlags |= CSSM_TP_ACTION_CRL_SUFFICIENT; + } + + /* Policy manages its own copy of this data */ + CSSM_DATA optData = {sizeof(opts), (uint8 *)&opts}; + crlPolicy->value() = optData; + numAdded++; + } + + /* append in order */ + if(doOcsp) { + if(doCrl) { + if(ocspFirst) { + /* these SecCFObject go away when the policies array does */ + CFArrayAppendValue(policies, ocspPolicy->handle(false)); + CFArrayAppendValue(policies, crlPolicy->handle(false)); + } + else { + CFArrayAppendValue(policies, crlPolicy->handle(false)); + CFArrayAppendValue(policies, ocspPolicy->handle(false)); + } + } + else { + CFArrayAppendValue(policies, ocspPolicy->handle(false)); + } + + } + else { + assert(doCrl); + CFArrayAppendValue(policies, crlPolicy->handle(false)); + } + return policies; +} + +/* + * Called when we created the last numAdded Policies in the specified Policy array + */ +void Trust::freePreferenceRevocationPolicies( + CFArrayRef policies, + uint32 numAdded, + Allocator &alloc) +{ + uint32 numPolicies = (uint32)CFArrayGetCount(policies); + if(numPolicies < numAdded) { + /* should never happen - throw? */ + assert(0); + return; + } + for(unsigned dex=numPolicies-numAdded; dex pol = Policy::required(SecPolicyRef(secPol)); + Policy *pol = Policy::required(secPol); + const CssmOid &oid = pol->oid(); // required + const CssmData &optData = pol->value(); // optional + + if(optData.Data) { + if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL)) { + /* currently no CRL-specific policy data */ + } + else if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP)) { + CSSM_APPLE_TP_OCSP_OPTIONS *opts = (CSSM_APPLE_TP_OCSP_OPTIONS *)optData.Data; + if(opts->LocalResponder != NULL) { + if(opts->LocalResponder->Data != NULL) { + alloc.free(opts->LocalResponder->Data); + } + alloc.free(opts->LocalResponder); + } + } + // managed by Policy alloc.free(optData.Data); + } + } + CFRelease(policies); +} + +/* + * Comparator function to correctly order revocation policies. + */ +static CFComparisonResult compareRevocationPolicies( + const void *policy1, + const void *policy2, + void *context) +{ + SecPointer pol1 = Policy::required(SecPolicyRef(policy1)); + SecPointer pol2 = Policy::required(SecPolicyRef(policy2)); + const CssmOid &oid1 = pol1->oid(); + const CssmOid &oid2 = pol2->oid(); + if(oid1 == oid2) { + return kCFCompareEqualTo; + } + bool ocspFirst = true; + if(context != NULL && CFEqual((CFBooleanRef)context, kCFBooleanFalse)) { + ocspFirst = false; + } + const CssmOid lastRevocationOid = (ocspFirst) ? + CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL) : + CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP); + const CssmOid firstRevocationOid = (ocspFirst) ? + CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP) : + CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL); + if(oid1 == lastRevocationOid) { + /* should be ordered last, after all other policies */ + return kCFCompareGreaterThan; + } + if(oid1 == firstRevocationOid) { + /* should be ordered after any policy except lastRevocationOid */ + if(oid2 == lastRevocationOid) { + return kCFCompareLessThan; + } + return kCFCompareGreaterThan; + } + /* normal policy in first position, anything else in second position */ + return kCFCompareLessThan; +} + +/* + * This method reorders any revocation policies which may be present + * in the provided array so they are at the end and evaluated last. + */ +void Trust::orderRevocationPolicies( + CFMutableArrayRef policies) +{ + if(!policies || CFGetTypeID(policies) != CFArrayGetTypeID()) { + return; + } + /* check revocation prefs to determine which policy goes first */ + CFBooleanRef ocspFirst = kCFBooleanTrue; + Dictionary* pd = Dictionary::CreateDictionary(kSecRevocationDomain, Dictionary::US_User, true); + if (pd) { + if (!pd->dict()) { + delete pd; + } else { + auto_ptr prefsDict(pd); + CFStringRef val = prefsDict->getStringValue(kSecRevocationWhichFirst); + if((val != NULL) && CFEqual(val, kSecRevocationCrlFirst)) { + ocspFirst = kCFBooleanFalse; + } + } + } +#if POLICIES_DEBUG + CFShow(policies); // before sort + CFArraySortValues(policies, CFRangeMake(0, CFArrayGetCount(policies)), compareRevocationPolicies, (void*)ocspFirst); + CFShow(policies); // after sort, to see what changed + // check that policy order is what we expect + CFIndex numPolicies = CFArrayGetCount(policies); + for(CFIndex dex=0; dex pol = Policy::required(SecPolicyRef(secPol)); + const CssmOid &oid = pol->oid(); + if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP)) { + CFStringRef s = CFStringCreateWithFormat(NULL, NULL, CFSTR("idx %d = OCSP"), dex); + CFShow(s); + CFRelease(s); + } + else if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL)) { + CFStringRef s = CFStringCreateWithFormat(NULL, NULL, CFSTR("idx %d = CRL"), dex); + CFShow(s); + CFRelease(s); + } + else { + CFStringRef s = CFStringCreateWithFormat(NULL, NULL, CFSTR("idx %d = normal"), dex); + CFShow(s); + CFRelease(s); + } + } +#else + CFArraySortValues(policies, CFRangeMake(0, CFArrayGetCount(policies)), compareRevocationPolicies, (void*)ocspFirst); +#endif +} + +/* + * This method returns a copy of the mPolicies array which ensures that + * revocation checking (preferably OCSP, otherwise CRL) will be attempted. + * + * If OCSP is already in the mPolicies array, this makes sure the + * CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT and CSSM_TP_ACTION_OCSP_SUFFICIENT + * flags are set. If it's not already in the array, a new policy object is added. + * + * If CRL is already in the mPolicies array, this makes sure the + * CSSM_TP_ACTION_FETCH_CRL_FROM_NET and CSSM_TP_ACTION_CRL_SUFFICIENT flags are + * set. If it's not already in the array, a new policy object is added. + * + * Caller is responsible for releasing the returned policies array. + */ +CFMutableArrayRef Trust::forceRevocationPolicies( + uint32 &numAdded, + Allocator &alloc, + bool requirePerCert) +{ + SecPointer ocspPolicy; + SecPointer crlPolicy; + CSSM_APPLE_TP_OCSP_OPT_FLAGS ocspFlags; + CSSM_APPLE_TP_CRL_OPT_FLAGS crlFlags; + bool hasOcspPolicy = false; + bool hasCrlPolicy = false; + numAdded = 0; + + ocspFlags = CSSM_TP_ACTION_OCSP_SUFFICIENT; + crlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT; + if (requirePerCert) { + ocspFlags |= CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT; + crlFlags |= CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT; + } + + CFIndex numPolicies = (mPolicies) ? CFArrayGetCount(mPolicies) : 0; + for(CFIndex dex=0; dex pol = Policy::required(SecPolicyRef(secPol)); + const CssmOid &oid = pol->oid(); + const CssmData &optData = pol->value(); + if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP)) { + // make sure OCSP options are set correctly + CSSM_APPLE_TP_OCSP_OPTIONS *opts = (CSSM_APPLE_TP_OCSP_OPTIONS *)optData.Data; + if (opts) { + opts->Flags |= ocspFlags; + } else { + CSSM_APPLE_TP_OCSP_OPTIONS newOpts; + memset(&newOpts, 0, sizeof(newOpts)); + newOpts.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + newOpts.Flags = ocspFlags; + CSSM_DATA optData = {sizeof(newOpts), (uint8 *)&newOpts}; + pol->value() = optData; + } + hasOcspPolicy = true; + } + else if(oid == CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL)) { + // make sure CRL options are set correctly + CSSM_APPLE_TP_CRL_OPTIONS *opts = (CSSM_APPLE_TP_CRL_OPTIONS *)optData.Data; + if (opts) { + opts->CrlFlags |= crlFlags; + } else { + CSSM_APPLE_TP_CRL_OPTIONS newOpts; + memset(&newOpts, 0, sizeof(newOpts)); + newOpts.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + newOpts.CrlFlags = crlFlags; + CSSM_DATA optData = {sizeof(newOpts), (uint8 *)&newOpts}; + pol->value() = optData; + } + hasCrlPolicy = true; + } + } + + /* We're potentially adding something to mPolicies, so make a copy we can work with */ + CFMutableArrayRef policies = CFArrayCreateMutableCopy(NULL, 0, mPolicies); + if(policies == NULL) { + throw std::bad_alloc(); + } + + if(!hasOcspPolicy) { + /* Cook up a new Policy object */ + ocspPolicy = new Policy(mTP, CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_OCSP)); + CSSM_APPLE_TP_OCSP_OPTIONS opts; + memset(&opts, 0, sizeof(opts)); + opts.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + opts.Flags = ocspFlags; + + /* Check prefs dict for local responder info */ + Dictionary *prefsDict = NULL; + try { /* per-user prefs */ + prefsDict = Dictionary::CreateDictionary(kSecRevocationDomain, Dictionary::US_User, true); + if (!prefsDict->dict()) { + delete prefsDict; + prefsDict = NULL; + } + } + catch(...) {} + if(prefsDict == NULL) { + try { /* system prefs */ + prefsDict = Dictionary::CreateDictionary(kSecRevocationDomain, Dictionary::US_System, true); + if (!prefsDict->dict()) { + delete prefsDict; + prefsDict = NULL; + } + } + catch(...) {} + } + if(prefsDict != NULL) { + CFStringRef val = prefsDict->getStringValue(kSecOCSPLocalResponder); + if(val != NULL) { + CFDataRef cfData = CFStringCreateExternalRepresentation(NULL, + val, kCFStringEncodingUTF8, 0); + CFIndex len = CFDataGetLength(cfData); + opts.LocalResponder = (CSSM_DATA_PTR)alloc.malloc(sizeof(CSSM_DATA)); + opts.LocalResponder->Data = (uint8 *)alloc.malloc(len); + opts.LocalResponder->Length = len; + memmove(opts.LocalResponder->Data, CFDataGetBytePtr(cfData), len); + CFRelease(cfData); + } + } + + /* Policy manages its own copy of the options data */ + CSSM_DATA optData = {sizeof(opts), (uint8 *)&opts}; + ocspPolicy->value() = optData; + + /* Policies array retains the Policy object */ + CFArrayAppendValue(policies, ocspPolicy->handle(false)); + numAdded++; + + if(prefsDict != NULL) + delete prefsDict; + } + + if(!hasCrlPolicy) { + /* Cook up a new Policy object */ + crlPolicy = new Policy(mTP, CssmOid::overlay(CSSMOID_APPLE_TP_REVOCATION_CRL)); + CSSM_APPLE_TP_CRL_OPTIONS opts; + memset(&opts, 0, sizeof(opts)); + opts.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + opts.CrlFlags = crlFlags; + + /* Policy manages its own copy of this data */ + CSSM_DATA optData = {sizeof(opts), (uint8 *)&opts}; + crlPolicy->value() = optData; + + /* Policies array retains the Policy object */ + CFArrayAppendValue(policies, crlPolicy->handle(false)); + numAdded++; + } + + return policies; +}