/*
* Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
*
* The contents of this file constitute Original Code as defined in and are
* subject to the Apple Public Source License Version 1.2 (the 'License').
* You may not use this file except in compliance with the License. Please obtain
* a copy of the License at http://www.apple.com/publicsource and read it before
* using this file.
*
* This 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.
*/
/*
File: sslctx.c
Contains: SSLContext accessors
Written by: Doug Mitchell, based on Netscape SSLRef 3.0
Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
*/
/* *********************************************************************
File: sslctx.c
SSLRef 3.0 Final -- 11/19/96
Copyright (c)1996 by Netscape Communications Corp.
By retrieving this software you are bound by the licensing terms
disclosed in the file "LICENSE.txt". Please read it, and if you don't
accept the terms, delete this software.
SSLRef 3.0 was developed by Netscape Communications Corp. of Mountain
View, California and Consensus Development
Corporation of Berkeley, California .
*********************************************************************
File: sslctx.c SSLContext accessors
Functions called by the end user which configure an SSLContext
structure or access data stored there.
****************************************************************** */
#include "ssl.h"
#include "sslctx.h"
#include "sslalloc.h"
#include
#include "digests.h"
#include "sslDebug.h"
#include "appleCdsa.h"
#include "appleGlue.h"
#include "sslKeychain.h"
#include "sslutil.h"
#include "cipherSpecs.h"
#include "appleSession.h"
#include
#include
static void sslFreeDnList(
SSLContext *ctx)
{
DNListElem *dn, *nextDN;
SSLBuffer buf;
dn = ctx->acceptableDNList;
while (dn)
{
SSLFreeBuffer(&dn->derDN, &ctx->sysCtx);
nextDN = dn->next;
buf.data = (uint8*)dn;
buf.length = sizeof(DNListElem);
SSLFreeBuffer(&buf, &ctx->sysCtx);
dn = nextDN;
}
ctx->acceptableDNList = NULL;
}
static SSLErr sslFreeTrustedRoots(
SSLContext *ctx)
{
int i;
CASSERT(ctx != NULL);
if((ctx->numTrustedCerts == 0) || (ctx->trustedCerts == NULL)) {
/* they really should both be zero, right? */
CASSERT((ctx->numTrustedCerts == 0) && (ctx->trustedCerts == NULL));
}
else {
for(i=0; inumTrustedCerts; i++) {
stFreeCssmData(&ctx->trustedCerts[i], CSSM_FALSE);
}
sslFree(ctx->trustedCerts);
}
ctx->numTrustedCerts = 0;
ctx->trustedCerts = NULL;
sslFreeDnList(ctx);
return SSLNoErr;
}
/*
* Default attempted version.
*/
#define DEFAULT_MAX_VERSION TLS_Version_1_0
OSStatus
SSLNewContext (Boolean isServer,
SSLContextRef *contextPtr) /* RETURNED */
{
SSLContext *ctx;
OSStatus oerr;
SSLErr serr;
if(contextPtr == NULL) {
return paramErr;
}
*contextPtr = NULL;
ctx = (SSLContext *)sslMalloc(sizeof(SSLContext));
if(ctx == NULL) {
return memFullErr;
}
/* subsequent errors to errOut: */
memset(ctx, 0, sizeof(SSLContext));
ctx->state = SSLUninitialized;
/* different defaults for client and server ... */
if(isServer) {
ctx->protocolSide = SSL_ServerSide;
ctx->reqProtocolVersion = DEFAULT_MAX_VERSION;
}
else {
ctx->protocolSide = SSL_ClientSide;
ctx->reqProtocolVersion = SSL_Version_Undetermined;
}
ctx->negProtocolVersion = SSL_Version_Undetermined;
ctx->maxProtocolVersion = DEFAULT_MAX_VERSION;
/* Default value so we can send and receive hello msgs */
ctx->sslTslCalls = &Ssl3Callouts;
/* Initialize the cipher state to NULL_WITH_NULL_NULL */
ctx->selectedCipherSpec = &SSL_NULL_WITH_NULL_NULL_CipherSpec;
ctx->selectedCipher = ctx->selectedCipherSpec->cipherSpec;
ctx->writeCipher.macRef = ctx->selectedCipherSpec->macAlgorithm;
ctx->readCipher.macRef = ctx->selectedCipherSpec->macAlgorithm;
ctx->readCipher.symCipher = ctx->selectedCipherSpec->cipher;
ctx->writeCipher.symCipher = ctx->selectedCipherSpec->cipher;
/* these two are invariant */
ctx->writeCipher.encrypting = 1;
ctx->writePending.encrypting = 1;
/* this gets init'd on first call to SSLHandshake() */
ctx->validCipherSpecs = NULL;
ctx->numValidCipherSpecs = 0;
ctx->peerDomainName = NULL;
ctx->peerDomainNameLen = 0;
SSLInitMACPads();
/* attach to CSP, CL, TP */
serr = attachToAll(ctx);
if(serr) {
oerr = sslErrToOsStatus(serr);
goto errOut;
}
/* snag root certs from Keychain, tolerate error */
addBuiltInCerts(ctx);
*contextPtr = ctx;
return noErr;
errOut:
sslFree(ctx);
return oerr;
}
/*
* Dispose of an SSLContext.
*/
OSStatus
SSLDisposeContext (SSLContext *ctx)
{
WaitingRecord *wait, *next;
SSLBuffer buf;
if(ctx == NULL) {
return paramErr;
}
sslDeleteCertificateChain(ctx->localCert, ctx);
sslDeleteCertificateChain(ctx->encryptCert, ctx);
sslDeleteCertificateChain(ctx->peerCert, ctx);
ctx->localCert = ctx->encryptCert = ctx->peerCert = NULL;
SSLFreeBuffer(&ctx->partialReadBuffer, &ctx->sysCtx);
wait = ctx->recordWriteQueue;
while (wait)
{ SSLFreeBuffer(&wait->data, &ctx->sysCtx);
next = wait->next;
buf.data = (uint8*)wait;
buf.length = sizeof(WaitingRecord);
SSLFreeBuffer(&buf, &ctx->sysCtx);
wait = next;
}
SSLFreeBuffer(&ctx->dhPeerPublic, &ctx->sysCtx);
SSLFreeBuffer(&ctx->dhExchangePublic, &ctx->sysCtx);
SSLFreeBuffer(&ctx->dhPrivate, &ctx->sysCtx);
CloseHash(&SSLHashSHA1, &ctx->shaState, ctx);
CloseHash(&SSLHashMD5, &ctx->md5State, ctx);
SSLFreeBuffer(&ctx->sessionID, &ctx->sysCtx);
SSLFreeBuffer(&ctx->peerID, &ctx->sysCtx);
SSLFreeBuffer(&ctx->resumableSession, &ctx->sysCtx);
SSLFreeBuffer(&ctx->preMasterSecret, &ctx->sysCtx);
SSLFreeBuffer(&ctx->partialReadBuffer, &ctx->sysCtx);
SSLFreeBuffer(&ctx->fragmentedMessageCache, &ctx->sysCtx);
SSLFreeBuffer(&ctx->receivedDataBuffer, &ctx->sysCtx);
if(ctx->peerDomainName) {
sslFree(ctx->peerDomainName);
ctx->peerDomainName = NULL;
ctx->peerDomainNameLen = 0;
}
SSLDisposeCipherSuite(&ctx->readCipher, ctx);
SSLDisposeCipherSuite(&ctx->writeCipher, ctx);
SSLDisposeCipherSuite(&ctx->readPending, ctx);
SSLDisposeCipherSuite(&ctx->writePending, ctx);
sslFree(ctx->validCipherSpecs);
ctx->validCipherSpecs = NULL;
ctx->numValidCipherSpecs = 0;
/* free APPLE_CDSA stuff */
#if 0
/* As of 5/3/02, we don't need to free these keys; they belong
* to SecKeychain */
#if ST_KEYCHAIN_ENABLE && ST_KC_KEYS_NEED_REF
sslFreeKey(ctx->signingKeyCsp, &ctx->signingPrivKey, &ctx->signingKeyRef);
sslFreeKey(ctx->encryptKeyCsp, &ctx->encryptPrivKey, &ctx->encryptKeyRef);
#else
sslFreeKey(ctx->signingKeyCsp, (CSSM_KEY_PTR *)&ctx->signingPrivKey, NULL);
sslFreeKey(ctx->encryptKeyCsp, (CSSM_KEY_PTR *)&ctx->encryptPrivKey, NULL);
#endif /* ST_KEYCHAIN_ENABLE && ST_KC_KEYS_NEED_REF */
#endif /* 0 */
/*
* NOTE: currently, all public keys come from the CL via CSSM_CL_CertGetKeyInfo.
* We really don't know what CSP the CL used to generate a public key (in fact,
* it uses the raw CSP only to get LogicalKeySizeInBits, but we can't know
* that). Thus using e.g. signingKeyCsp (or any other CSP) to free
* signingPubKey is not tecnically accurate. However, our public keys
* are all raw keys, and all Apple CSPs dispose of raw keys in the same
* way.
*/
sslFreeKey(ctx->signingKeyCsp, &ctx->signingPubKey, NULL);
sslFreeKey(ctx->encryptKeyCsp, &ctx->encryptPubKey, NULL);
sslFreeKey(ctx->peerPubKeyCsp, &ctx->peerPubKey, NULL);
#if SSL_DEBUG
if(ctx->rootCertName != NULL) {
sslFree(ctx->rootCertName);
}
#endif /* SSL_DEBUG */
sslFreeTrustedRoots(ctx);
detachFromAll(ctx);
memset(ctx, 0, sizeof(SSLContext));
sslFree(ctx);
sslCleanupSession();
return noErr;
}
/*
* Determine the state of an SSL session.
*/
OSStatus
SSLGetSessionState (SSLContextRef context,
SSLSessionState *state) /* RETURNED */
{
SSLSessionState rtnState = kSSLIdle;
if(context == NULL) {
return paramErr;
}
*state = rtnState;
switch(context->state) {
case SSLUninitialized:
case HandshakeServerUninit:
case HandshakeClientUninit:
rtnState = kSSLIdle;
break;
case SSLGracefulClose:
rtnState = kSSLClosed;
break;
case SSLErrorClose:
case SSLNoNotifyClose:
rtnState = kSSLAborted;
break;
case HandshakeServerReady:
case HandshakeClientReady:
rtnState = kSSLConnected;
break;
default:
CASSERT((context->state >= HandshakeServerHello) &&
(context->state <= HandshakeSSL2ServerFinished));
rtnState = kSSLHandshake;
break;
}
*state = rtnState;
return noErr;
}
OSStatus
SSLSetIOFuncs (SSLContextRef ctx,
SSLReadFunc read,
SSLWriteFunc write)
{
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
ctx->ioCtx.read = read;
ctx->ioCtx.write = write;
return noErr;
}
OSStatus
SSLSetConnection (SSLContextRef ctx,
SSLConnectionRef connection)
{
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
ctx->ioCtx.ioRef = connection;
return noErr;
}
OSStatus
SSLSetPeerDomainName (SSLContextRef ctx,
const char *peerName,
size_t peerNameLen)
{
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
/* free possible existing name */
if(ctx->peerDomainName) {
sslFree(ctx->peerDomainName);
}
/* copy in */
ctx->peerDomainName = sslMalloc(peerNameLen);
if(ctx->peerDomainName == NULL) {
return memFullErr;
}
memmove(ctx->peerDomainName, peerName, peerNameLen);
ctx->peerDomainNameLen = peerNameLen;
return noErr;
}
/*
* Determine the buffer size needed for SSLGetPeerDomainName().
*/
OSStatus
SSLGetPeerDomainNameLength (SSLContextRef ctx,
size_t *peerNameLen) // RETURNED
{
if(ctx == NULL) {
return paramErr;
}
*peerNameLen = ctx->peerDomainNameLen;
return noErr;
}
OSStatus
SSLGetPeerDomainName (SSLContextRef ctx,
char *peerName, // returned here
size_t *peerNameLen) // IN/OUT
{
if(ctx == NULL) {
return paramErr;
}
if(*peerNameLen < ctx->peerDomainNameLen) {
return errSSLBufferOverflow;
}
memmove(peerName, ctx->peerDomainName, ctx->peerDomainNameLen);
*peerNameLen = ctx->peerDomainNameLen;
return noErr;
}
OSStatus
SSLSetProtocolVersion (SSLContextRef ctx,
SSLProtocol version)
{
SSLProtocolVersion versInt;
SSLProtocolVersion versMax;
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
/* convert external representation to private */
switch(version) {
case kSSLProtocolUnknown:
versInt = SSL_Version_Undetermined;
versMax = DEFAULT_MAX_VERSION;
break;
case kSSLProtocol2:
versInt = versMax = SSL_Version_2_0;
break;
case kSSLProtocol3:
/* this tells us to do our best but allows 2.0 */
versInt = SSL_Version_Undetermined;
versMax = SSL_Version_3_0;
break;
case kSSLProtocol3Only:
versInt = SSL_Version_3_0_Only;
versMax = SSL_Version_3_0;
break;
case kTLSProtocol1:
/* this tells us to do our best but allows 2.0 */
versInt = SSL_Version_Undetermined;
versMax = TLS_Version_1_0;
break;
case kTLSProtocol1Only:
versInt = TLS_Version_1_0_Only;
versMax = TLS_Version_1_0;
break;
default:
return paramErr;
}
ctx->reqProtocolVersion = ctx->negProtocolVersion = versInt;
ctx->maxProtocolVersion = versMax;
return noErr;
}
static SSLProtocol convertProtToExtern(SSLProtocolVersion prot)
{
switch(prot) {
case SSL_Version_Undetermined:
return kSSLProtocolUnknown;
case SSL_Version_3_0_Only:
return kSSLProtocol3Only;
case SSL_Version_2_0:
return kSSLProtocol2;
case SSL_Version_3_0:
return kSSLProtocol3;
case TLS_Version_1_0_Only:
return kTLSProtocol1Only;
case TLS_Version_1_0:
return kTLSProtocol1;
/* this can happen in an intermediate state while negotiation
* is in progress...right? */
case SSL_Version_3_0_With_2_0_Hello:
return kSSLProtocolUnknown;
default:
sslPanic("convertProtToExtern: bad prot");
}
/* not reached but make compiler happy */
return kSSLProtocolUnknown;
}
OSStatus
SSLGetProtocolVersion (SSLContextRef ctx,
SSLProtocol *protocol) /* RETURNED */
{
if(ctx == NULL) {
return paramErr;
}
*protocol = convertProtToExtern(ctx->reqProtocolVersion);
return noErr;
}
OSStatus
SSLGetNegotiatedProtocolVersion (SSLContextRef ctx,
SSLProtocol *protocol) /* RETURNED */
{
if(ctx == NULL) {
return paramErr;
}
*protocol = convertProtToExtern(ctx->negProtocolVersion);
return noErr;
}
OSStatus
SSLSetAllowsExpiredCerts(SSLContextRef ctx,
Boolean allowExpired)
{
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
ctx->allowExpiredCerts = allowExpired;
return noErr;
}
OSStatus
SSLGetAllowsExpiredCerts (SSLContextRef ctx,
Boolean *allowExpired)
{
if(ctx == NULL) {
return paramErr;
}
*allowExpired = ctx->allowExpiredCerts;
return noErr;
}
OSStatus SSLSetAllowsAnyRoot(
SSLContextRef ctx,
Boolean anyRoot)
{
if(ctx == NULL) {
return paramErr;
}
ctx->allowAnyRoot = anyRoot;
return noErr;
}
OSStatus
SSLGetAllowsAnyRoot(
SSLContextRef ctx,
Boolean *anyRoot)
{
if(ctx == NULL) {
return paramErr;
}
*anyRoot = ctx->allowAnyRoot;
return noErr;
}
#if ST_SERVER_MODE_ENABLE
OSStatus
SSLSetClientSideAuthenticate (SSLContext *ctx,
SSLAuthenticate auth)
{
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
ctx->clientAuth = auth;
switch(auth) {
case kNeverAuthenticate:
ctx->tryClientAuth = false;
break;
case kAlwaysAuthenticate:
case kTryAuthenticate:
/* FIXME - needs work to distinguish these cases at
* handshake time */
ctx->tryClientAuth = true;
break;
}
return noErr;
}
#endif /* ST_SERVER_MODE_ENABLE */
#if (ST_SERVER_MODE_ENABLE || ST_CLIENT_AUTHENTICATION)
OSStatus
SSLSetCertificate (SSLContextRef ctx,
CFArrayRef certRefs)
{
/*
* -- free localCerts if we have any
* -- Get raw cert data, convert to ctx->localCert
* -- get pub, priv keys from certRef[0]
* -- validate cert chain
*/
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
return parseIncomingCerts(ctx,
certRefs,
&ctx->localCert,
&ctx->signingPubKey,
&ctx->signingPrivKey,
&ctx->signingKeyCsp
#if ST_KC_KEYS_NEED_REF
,
&ctx->signingKeyRef
#else
);
#endif
}
#endif /* (ST_SERVER_MODE_ENABLE || ST_CLIENT_AUTHENTICATION) */
#if ST_SERVER_MODE_ENABLE
OSStatus
SSLSetEncryptionCertificate (SSLContextRef ctx,
CFArrayRef certRefs)
{
/*
* -- free encryptCert if we have any
* -- Get raw cert data, convert to ctx->encryptCert
* -- get pub, priv keys from certRef[0]
* -- validate cert chain
*/
if(ctx == NULL) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
return parseIncomingCerts(ctx,
certRefs,
&ctx->encryptCert,
&ctx->encryptPubKey,
&ctx->encryptPrivKey,
&ctx->encryptKeyCsp
#if ST_KC_KEYS_NEED_REF
,
&ctx->encryptKeyRef);
#else
);
#endif
}
#endif /* ST_SERVER_MODE_ENABLE*/
#if ST_KEYCHAIN_ENABLE && ST_MANAGES_TRUSTED_ROOTS
/*
* Add (optional, additional) trusted root certs.
*/
OSStatus
SSLSetTrustedRootCertKC (SSLContextRef ctx,
KCRef keyChainRef,
Boolean deleteExisting)
{
/*
* -- free trustedCerts if deleteExisting
* -- Get raw cert data, add to ctx->trustedCerts
* -- verify that each of these is a valid (self-verifying)
* root cert
* -- add each subject name to acceptableDNList
*/
if((ctx == NULL) || (keyChainRef == nil)) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
if(deleteExisting) {
sslFreeTrustedRoots(ctx);
}
return parseTrustedKeychain(ctx, keyChainRef);
}
OSStatus
SSLSetNewRootKC (SSLContextRef ctx,
KCRef keyChainRef,
void *accessCreds)
{
if((ctx == NULL) || (keyChainRef == nil)) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
if(ctx->newRootCertKc != NULL) {
/* can't do this multiple times */
return badReqErr;
}
ctx->newRootCertKc = keyChainRef;
ctx->accessCreds = accessCreds;
return noErr;
}
#endif /* ST_KEYCHAIN_ENABLE && ST_MANAGES_TRUSTED_ROOTS */
OSStatus
SSLSetPeerID (SSLContext *ctx,
const void *peerID,
size_t peerIDLen)
{
SSLErr serr;
/* copy peerId to context->peerId */
if((ctx == NULL) ||
(peerID == NULL) ||
(peerIDLen == 0)) {
return paramErr;
}
if(sslIsSessionActive(ctx)) {
/* can't do this with an active session */
return badReqErr;
}
SSLFreeBuffer(&ctx->peerID, &ctx->sysCtx);
serr = SSLAllocBuffer(&ctx->peerID, peerIDLen, &ctx->sysCtx);
if(serr) {
return sslErrToOsStatus(serr);
}
memmove(ctx->peerID.data, peerID, peerIDLen);
return noErr;
}
OSStatus
SSLGetPeerID (SSLContextRef ctx,
const void **peerID,
size_t *peerIDLen)
{
*peerID = ctx->peerID.data; // may be NULL
*peerIDLen = ctx->peerID.length;
return noErr;
}
OSStatus
SSLGetNegotiatedCipher (SSLContextRef ctx,
SSLCipherSuite *cipherSuite)
{
if(ctx == NULL) {
return paramErr;
}
if(!sslIsSessionActive(ctx)) {
return badReqErr;
}
*cipherSuite = (SSLCipherSuite)ctx->selectedCipher;
return noErr;
}
/*
* Add an acceptable distinguished name.
* FIXME - this looks like a big hole in the SSLRef code;
* acceptableDNList is set here and in SSLProcessCertificateRequest();
* it's used and sent to a client in SSLEncodeCertificateRequest();
* but the list is never used to decide what certs to send!
*
* Also FIXME - this allocation of dnBufs is preposterous. The
* SSLBufs can never get freed. Why not just allocate the
* raw DNListElems? Sheesh.
*/
#if 0
/* not used */
static SSLErr
SSLAddDistinguishedName(SSLContext *ctx, SSLBuffer derDN)
{ SSLBuffer dnBuf;
DNListElem *dn;
SSLErr err;
if ((err = SSLAllocBuffer(&dnBuf, sizeof(DNListElem), &ctx->sysCtx)) != 0)
return err;
dn = (DNListElem*)dnBuf.data;
if ((err = SSLAllocBuffer(&dn->derDN, derDN.length, &ctx->sysCtx)) != 0)
{ SSLFreeBuffer(&dnBuf, &ctx->sysCtx);
return err;
}
memcpy(dn->derDN.data, derDN.data, derDN.length);
dn->next = ctx->acceptableDNList;
ctx->acceptableDNList = dn;
return SSLNoErr;
}
#endif /* not used */
/*
* Request peer certificates. Valid anytime, subsequent to
* a handshake attempt.
*/
OSStatus
SSLGetPeerCertificates (SSLContextRef ctx,
CFArrayRef *certs)
{
uint32 numCerts;
CFMutableArrayRef ca;
CFIndex i;
SecCertificateRef cfd;
OSStatus ortn;
CSSM_DATA certData;
SSLCertificate *scert;
if(ctx == NULL) {
return paramErr;
}
*certs = NULL;
/*
* Copy peerCert, a chain of SSLCertificates, to a CFArray of
* CFDataRefs, each of which is one DER-encoded cert.
*/
numCerts = SSLGetCertificateChainLength(ctx->peerCert);
if(numCerts == 0) {
return noErr;
}
ca = CFArrayCreateMutable(kCFAllocatorDefault,
(CFIndex)numCerts, &kCFTypeArrayCallBacks);
if(ca == NULL) {
return memFullErr;
}
/*
* Caller gets leaf cert first, the opposite of the way we store them.
*/
scert = ctx->peerCert;
for(i=0; iderCert, &certData);
ortn = SecCertificateCreateFromData(&certData,
CSSM_CERT_X_509v3,
CSSM_CERT_ENCODING_DER,
&cfd);
if(ortn) {
CFRelease(ca);
return ortn;
}
/* insert at head of array */
CFArrayInsertValueAtIndex(ca, 0, cfd);
scert = scert->next;
}
*certs = ca;
return noErr;
}