]> git.saurik.com Git - apple/security.git/blobdiff - SecureTransport/sslKeychain.cpp
Security-54.1.3.tar.gz
[apple/security.git] / SecureTransport / sslKeychain.cpp
diff --git a/SecureTransport/sslKeychain.cpp b/SecureTransport/sslKeychain.cpp
new file mode 100644 (file)
index 0000000..9ad65d5
--- /dev/null
@@ -0,0 +1,587 @@
+/*
+ * 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 <string.h>
+#include <assert.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <Security/cssm.h>
+/* these are to be replaced by Security/Security.h */
+#include <Security/SecCertificate.h>
+#include <Security/SecKeychainItem.h>
+#include <Security/SecKeychain.h>
+#include <Security/SecIdentity.h>
+#include <Security/SecIdentitySearch.h>
+#include <Security/SecKey.h>
+
+#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; cert<numCerts; cert++) {
+               certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certs, cert);
+               if(certRef == NULL) {
+                       sslErrorLog("parseIncomingCerts: bad cert array (5)\n");
+                       return paramErr;
+               }       
+               if(CFGetTypeID(certRef) != SecCertificateGetTypeID()) {
+                       sslErrorLog("parseIncomingCerts: bad cert array (6)\n");
+                       return paramErr;
+               }
+               
+               /* Extract cert, convert to local format. 
+               */
+               ortn = secCertToSslCert(ctx, certRef, &thisSslCert);
+               if(ortn) {
+                       sslErrorLog("parseIncomingCerts: bad cert array (7)\n");
+                       return ortn;
+               }
+               /* enqueue onto head of cert chain */
+               thisSslCert->next = 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; certDex<certsPerKc; certDex++) {
+               kcItem = (KCItemRef)CFArrayGetValueAtIndex(kcCerts, certDex);
+               if(kcItem == NULL) {
+                       sslErrorLog("parseTrustedKeychains: CF error 1\n");
+                       ortn = errSSLInternal;
+                       goto errOut;
+               }
+               if(!KCIsRootCertificate(kcItem)) {
+                       /* not root, OK, skip to next cert */
+                       sslErrorLog("parseTrustedKeychains: cert %d NOT ROOT\n",
+                                       certDex);
+                       continue;
+               }
+               ortn = addCertData(ctx,
+                       kcItem, 
+                       &certData[numGoodCerts], 
+                       &goodCert);
+               if(ortn) {
+                       goto errOut;
+               }
+               if(goodCert) {
+                       /* added valid root to certData */
+                       numGoodCerts++;
+               }
+       }       /* for each cert in kcCerts */
+
+       #if     SSL_DEBUG
+       verifyTrustedRoots(ctx, certData, numGoodCerts);
+       #endif
+
+       /* Realloc ctx->trustedCerts, 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; certDex<numGoodCerts; certDex++) {
+               ctx->trustedCerts[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 */
+