TrustRevocation.cpp   [plain text]


/*
 * 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 <security_keychain/Trust.h>
#include <security_utilities/cfutilities.h>
#include <security_utilities/simpleprefs.h>
#include <CoreFoundation/CFData.h>
#include "SecBridge.h"
#include <Security/cssmapplePriv.h>
#include <Security/oidsalg.h>

/* 
 * 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<numPolicies; dex++) {
		SecPolicyRef secPol = (SecPolicyRef)CFArrayGetValueAtIndex(policies, dex);
		SecPointer<Policy> 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<numPolicies; dex++) {
		SecPolicyRef secPol = (SecPolicyRef)CFArrayGetValueAtIndex(policies, dex);
		SecPointer<Policy> 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<Dictionary> prefsDict(pd);
	
	bool doOcsp = false;
	bool doCrl = false;
	CFStringRef val;
	SecRevocationPolicyStyle ocspStyle = kSecBestAttempt;
	SecRevocationPolicyStyle crlStyle = kSecBestAttempt;
	SecPointer<Policy> ocspPolicy;
	SecPointer<Policy> 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<numPolicies; dex++) {
		SecPolicyRef secPol = (SecPolicyRef)CFArrayGetValueAtIndex(policies, dex);
		//SecPointer<Policy> 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<Policy> pol1 = Policy::required(SecPolicyRef(policy1));
	SecPointer<Policy> 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<Dictionary> 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<numPolicies; dex++) {
		SecPolicyRef secPol = (SecPolicyRef)CFArrayGetValueAtIndex(policies, dex);
		SecPointer<Policy> 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<Policy> ocspPolicy;
	SecPointer<Policy> 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<numPolicies; dex++) {
		SecPolicyRef secPol = (SecPolicyRef)CFArrayGetValueAtIndex(mPolicies, dex);
		SecPointer<Policy> 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;
}