/* * 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: sslKeychain.c Contains: Apple Keychain routines Written by: Doug Mitchell Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved. */ #include "ssl.h" #include "sslContext.h" #include "sslMemory.h" #include "appleCdsa.h" #include "sslDebug.h" #include "sslKeychain.h" #include "sslUtils.h" #include #include #include #include /* these are to be replaced by Security/Security.h */ #include #include #include #include #include #include #if ST_MANAGES_TRUSTED_ROOTS static OSStatus addCertData( SSLContext *ctx, KCItemRef kcItem, CSSM_DATA_PTR certData, Boolean *goodCert); /* RETURNED */ #endif /* ST_MANAGES_TRUSTED_ROOTS */ /* * Given an array of certs (as SecIdentityRefs, specified by caller * in SSLSetCertificate or SSLSetEncryptionCertificate) and a * destination SSLCertificate: * * -- free destCerts if we have any * -- Get raw cert data, convert to array of SSLCertificates in *destCert * -- validate cert chain * -- get pub, priv keys from certRef[0], store in *pubKey, *privKey */ /* Convert a SecCertificateRef to an SSLCertificate * */ static OSStatus secCertToSslCert( SSLContext *ctx, SecCertificateRef certRef, SSLCertificate **sslCert) { CSSM_DATA certData; // struct is transient, referent owned by // Sec layer OSStatus ortn; SSLCertificate *thisSslCert = NULL; ortn = SecCertificateGetData(certRef, &certData); if(ortn) { sslErrorLog("SecCertificateGetData() returned %d\n", (int)ortn); return ortn; } thisSslCert = (SSLCertificate *)sslMalloc(sizeof(SSLCertificate)); if(thisSslCert == NULL) { return memFullErr; } if(SSLAllocBuffer(thisSslCert->derCert, certData.Length, ctx)) { return memFullErr; } memcpy(thisSslCert->derCert.data, certData.Data, certData.Length); thisSslCert->derCert.length = certData.Length; *sslCert = thisSslCert; return noErr; } OSStatus parseIncomingCerts( SSLContext *ctx, CFArrayRef certs, SSLCertificate **destCert, /* &ctx->{localCert,encryptCert} */ CSSM_KEY_PTR *pubKey, /* &ctx->signingPubKey, etc. */ CSSM_KEY_PTR *privKey, /* &ctx->signingPrivKey, etc. */ CSSM_CSP_HANDLE *cspHand /* &ctx->signingKeyCsp, etc. */ #if ST_KC_KEYS_NEED_REF , SecKeychainRef *privKeyRef) /* &ctx->signingKeyRef, etc. */ #else ) #endif /* ST_KC_KEYS_NEED_REF */ { CFIndex numCerts; CFIndex cert; SSLCertificate *certChain = NULL; SSLCertificate *thisSslCert; SecKeychainRef kcRef; OSStatus ortn; SecIdentityRef identity; SecCertificateRef certRef; SecKeyRef keyRef; CSSM_DATA certData; CSSM_CL_HANDLE clHand; // carefully derive from a SecCertificateRef CSSM_RETURN crtn; assert(ctx != NULL); assert(destCert != NULL); /* though its referent may be NULL */ assert(pubKey != NULL); assert(privKey != NULL); assert(cspHand != NULL); sslDeleteCertificateChain(*destCert, ctx); *destCert = NULL; *pubKey = NULL; *privKey = NULL; *cspHand = 0; if(certs == NULL) { sslErrorLog("parseIncomingCerts: NULL incoming cert array\n"); return errSSLBadCert; } numCerts = CFArrayGetCount(certs); if(numCerts == 0) { sslErrorLog("parseIncomingCerts: empty incoming cert array\n"); return errSSLBadCert; } /* * Certs[0] is an SecIdentityRef from which we extract subject cert, * privKey, pubKey, and cspHand. * * 1. ensure the first element is a SecIdentityRef. */ identity = (SecIdentityRef)CFArrayGetValueAtIndex(certs, 0); if(identity == NULL) { sslErrorLog("parseIncomingCerts: bad cert array (1)\n"); return paramErr; } if(CFGetTypeID(identity) != SecIdentityGetTypeID()) { sslErrorLog("parseIncomingCerts: bad cert array (2)\n"); return paramErr; } /* * 2. Extract cert, keys, CSP handle and convert to local format. */ ortn = SecIdentityCopyCertificate(identity, &certRef); if(ortn) { sslErrorLog("parseIncomingCerts: bad cert array (3)\n"); return ortn; } ortn = secCertToSslCert(ctx, certRef, &thisSslCert); if(ortn) { sslErrorLog("parseIncomingCerts: bad cert array (4)\n"); return ortn; } /* enqueue onto head of cert chain */ thisSslCert->next = certChain; certChain = thisSslCert; /* fetch private key from identity */ ortn = SecIdentityCopyPrivateKey(identity, &keyRef); if(ortn) { sslErrorLog("parseIncomingCerts: SecIdentityCopyPrivateKey err %d\n", (int)ortn); return ortn; } ortn = SecKeyGetCSSMKey(keyRef, (const CSSM_KEY **)privKey); if(ortn) { sslErrorLog("parseIncomingCerts: SecKeyGetCSSMKey err %d\n", (int)ortn); return ortn; } /* FIXME = release keyRef? */ /* obtain public key from cert */ ortn = SecCertificateGetCLHandle(certRef, &clHand); if(ortn) { sslErrorLog("parseIncomingCerts: SecCertificateGetCLHandle err %d\n", (int)ortn); return ortn; } certData.Data = thisSslCert->derCert.data; certData.Length = thisSslCert->derCert.length; crtn = CSSM_CL_CertGetKeyInfo(clHand, &certData, pubKey); if(crtn) { sslErrorLog("parseIncomingCerts: CSSM_CL_CertGetKeyInfo err\n"); return (OSStatus)crtn; } /* obtain keychain from key, CSP handle from keychain */ ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef); if(ortn) { sslErrorLog("parseIncomingCerts: SecKeychainItemCopyKeychain err %d\n", (int)ortn); return ortn; } ortn = SecKeychainGetCSPHandle(kcRef, cspHand); if(ortn) { sslErrorLog("parseIncomingCerts: SecKeychainGetCSPHandle err %d\n", (int)ortn); return ortn; } /* OK, that's the subject cert. Fetch optional remaining certs. */ /* * Convert: CFArray of SecCertificateRefs --> chain of SSLCertificates. * Incoming certs have root last; SSLCertificate chain has root * first. */ for(cert=1; certnext = certChain; certChain = thisSslCert; } /* validate the whole mess, skipping host name verify */ ortn = sslVerifyCertChain(ctx, *certChain, false); if(ortn) { goto errOut; } /* SUCCESS */ *destCert = certChain; return noErr; errOut: /* free certChain, everything in it, other vars, return ortn */ sslDeleteCertificateChain(certChain, ctx); /* FIXME - anything else? */ return ortn; } /* * Add Apple built-in root certs to ctx->trustedCerts. */ OSStatus addBuiltInCerts (SSLContextRef ctx) { #if ST_MANAGES_TRUSTED_ROOTS OSStatus ortn; KCRef kc = nil; ortn = KCDispatch(kKCGetRootCertificateKeychain, &kc); if(ortn) { sslErrorLog("KCDispatch(kKCGetRootCertificateKeychain) returned %d\n", ortn); return ortn; } return parseTrustedKeychain(ctx, kc); #else /* nothing for now */ return noErr; #endif /* ST_MANAGES_TRUSTED_ROOTS */ } #if ST_MANAGES_TRUSTED_ROOTS /* * Given an open Keychain: * -- Get raw cert data, add to array of CSSM_DATAs in * ctx->trustedCerts * -- verify that each of these is a valid (self-verifying) * root cert * -- add each subject name to acceptableDNList */ OSStatus parseTrustedKeychain (SSLContextRef ctx, KCRef keyChainRef) { CFMutableArrayRef kcCerts = NULL; /* all certs in one keychain */ uint32 numGoodCerts = 0; /* # of good root certs */ CSSM_DATA_PTR certData = NULL; /* array of CSSM_DATAs */ CFIndex certDex; /* index into kcCerts */ CFIndex certsPerKc; /* # of certs in this KC */ OSStatus ortn; KCItemRef kcItem; /* one cert */ Boolean goodCert; assert(ctx != NULL); if(keyChainRef == NULL) { return paramErr; } ortn = KCFindX509Certificates(keyChainRef, NULL, // name, XXX NULL, // emailAddress, XXX kCertSearchAny, // options &kcCerts); // results switch(ortn) { case noErr: break; // proceed case errKCItemNotFound: return noErr; // no certs; done default: sslErrorLog("parseTrustedKeychains: KCFindX509Certificates returned %d\n", ortn); return ortn; } if(kcCerts == NULL) { sslErrorLog("parseTrustedKeychains: no certs in KC\n"); return noErr; } /* Note kcCerts must be released on any exit, successful or * otherwise. */ certsPerKc = CFArrayGetCount(kcCerts); /* * This array gets allocd locally; we'll add it to * ctx->trustedCerts when we're done. */ certData = sslMalloc(certsPerKc * sizeof(CSSM_DATA)); if(certData == NULL) { ortn = memFullErr; goto errOut; } memset(certData, 0, certsPerKc * sizeof(CSSM_DATA)); /* * Build up local certData one root cert at a time. * Some certs might not pass muster, hence the numGoodCerts * which may or may not increment each time thru. */ for(certDex=0; certDextrustedCerts, add new root certs */ ctx->trustedCerts = sslRealloc(ctx->trustedCerts, ctx->numTrustedCerts * sizeof(CSSM_DATA), (ctx->numTrustedCerts + numGoodCerts) * sizeof(CSSM_DATA)); if(ctx->trustedCerts == NULL) { ortn = memFullErr; goto errOut; } for(certDex=0; certDextrustedCerts[ctx->numTrustedCerts + certDex] = certData[certDex]; } ctx->numTrustedCerts += numGoodCerts; ortn = noErr; #if SSL_DEBUG verifyTrustedRoots(ctx, ctx->trustedCerts, ctx->numTrustedCerts); #endif errOut: sslFree(certData); if(kcCerts != NULL) { CFRelease(kcCerts); } return ortn; } /* * Given a (supposedly) root cert as a KCItemRef: * -- verify that the cert self-verifies * -- add its DER-encoded data *certData. * -- Add its subjectName to acceptableDNList. * -- If all is well, return True in *goodCert. * * The actual CSSM_DATA.Data is mallocd via CSSM_Malloc. */ static OSStatus addCertData( SSLContext *ctx, KCItemRef kcItem, CSSM_DATA_PTR certData, Boolean *goodCert) /* RETURNED */ { UInt32 certSize; OSStatus ortn; CSSM_BOOL subjectExpired; assert(ctx != NULL); assert(certData != NULL); assert(kcItem != NULL); assert(goodCert != NULL); *goodCert = false; /* how big is the cert? */ ortn = KCGetData (kcItem, 0, NULL, &certSize); if(ortn != noErr) { sslErrorLog("addCertData: KCGetData(1) returned %d\n", ortn); return ortn; } /* Allocate the buffer. */ ortn = stSetUpCssmData(certData, certSize); if(ortn) { return ortn; } /* Get the data. */ ortn = KCGetData (kcItem, certSize, certData->Data, &certSize); if(ortn) { sslErrorLog("addCertData: KCGetData(2) returned %d\n", ortn); stFreeCssmData(certData, CSSM_FALSE); return ortn; } /* * Do actual cert verify, which * KCIsRootCertificate does not do. A failure isn't * fatal; we just don't add the cert to the array in * that case. * * FIXME - we assume here that our common cspHand can * do this cert verify; if not, we have some API work to * do (to let the caller specify which CSP to use with * trusted certs). */ if(!sslVerifyCert(ctx, certData, certData, ctx->cspHand, &subjectExpired)) { sslErrorLog("addCertData: cert does not self-verify!\n"); stFreeCssmData(certData, CSSM_FALSE); return noErr; } /* FIXME - needs update for MANAGES_TRUSTED_ROOTS */ /* Add this cert's subject name to (poss. existing) acceptableDNList */ CSSM_DATA_PTR dnData = sslGetCertSubjectName(ctx, certData); if(dnData) { DNListElem *dn = sslMalloc(sizeof(DNListElem)); if(dn == NULL) { return memFullErr; } dn->next = ctx->acceptableDNList; ctx->acceptableDNList = dn; /* move actual data to dn; free the CSSM_DATA struct (must be * via CSSM_Free()!) */ CSSM_TO_SSLBUF(dnData, &dn->derDN); sslFree(dnData); } *goodCert = true; return noErr; } /* * Given a newly encountered root cert (obtained from a peer's cert chain), * add it to newRootCertKc if the user so allows, and if so, add it to * trustedCerts. */ OSStatus sslAddNewRoot( SSLContext *ctx, const CSSM_DATA_PTR rootCert) { KCRef defaultKc; Boolean bDefaultKcExists; KCItemRef certRef = NULL; OSStatus ortn; CSSM_DATA_PTR newTrustee; OSStatus serr; assert(ctx != NULL); assert(rootCert != NULL); assert(ctx->newRootCertKc != NULL); /* caller verifies this */ /* * Get default KC, temporarily set new default. */ ortn = KCGetDefaultKeychain(&defaultKc); if(ortn) { bDefaultKcExists = false; } else { bDefaultKcExists = true; } ortn = KCSetDefaultKeychain(ctx->newRootCertKc); if(ortn) { sslErrorLog("sslAddNewRoot: KCSetDefaultKeychain returned %d\n", ortn); return errSSLUnknownRootCert; } /* * Add cert to newRootCertKc. This may well fail due to user * interaction ("Do you want to add this root cert...?"). */ ortn = KCAddX509Certificate(rootCert->Data, rootCert->Length, &certRef); /* restore default KC in any case */ if(bDefaultKcExists) { KCSetDefaultKeychain(defaultKc); } if(ortn) { sslErrorLog("sslAddNewRoot: KCAddX509Certificate returned %d\n", ortn); return errSSLUnknownRootCert; } /* * OK, user accepted new root. Now add to our private stash of * trusted roots. Realloc the whole pile... */ ctx->trustedCerts = (CSSM_DATA_PTR)sslRealloc(ctx->trustedCerts, (ctx->numTrustedCerts * sizeof(CSSM_DATA)), ((ctx->numTrustedCerts + 1) * sizeof(CSSM_DATA))); if(ctx->trustedCerts == NULL) { return memFullErr; } /* Now add a copy of the new root. */ newTrustee = &ctx->trustedCerts[ctx->numTrustedCerts]; newTrustee->Data = NULL; newTrustee->Length = 0; serr = stSetUpCssmData(newTrustee, rootCert->Length); if(serr) { return serr; } BlockMove(rootCert->Data, newTrustee->Data, rootCert->Length); (ctx->numTrustedCerts)++; return noErr; } #endif /* ST_MANAGES_TRUSTED_ROOTS */