]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_apple_x509_tp/lib/TPNetwork.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_apple_x509_tp / lib / TPNetwork.cpp
diff --git a/Security/libsecurity_apple_x509_tp/lib/TPNetwork.cpp b/Security/libsecurity_apple_x509_tp/lib/TPNetwork.cpp
new file mode 100644 (file)
index 0000000..bc9cf3f
--- /dev/null
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2002-2012 Apple 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.
+ */
+
+
+/*
+ * TPNetwork.h - LDAP, HTTP and (eventually) other network tools 
+ */
+#include "TPNetwork.h"
+#include "tpdebugging.h"
+#include "tpTime.h"
+#include "cuEnc64.h"
+#include <Security/cssmtype.h>
+#include <Security/cssmapple.h>
+#include <Security/oidscert.h>
+#include <security_utilities/logging.h>
+#include <security_ocspd/ocspdClient.h>
+
+#define CA_ISSUERS_OID              OID_PKIX, 0x30, 0x02
+#define CA_ISSUERS_OID_LEN          OID_PKIX_LENGTH + 2
+
+static const uint8     OID_CA_ISSUERS[]      = {CA_ISSUERS_OID};
+const CSSM_OID CSSMOID_CA_ISSUERS        = {CA_ISSUERS_OID_LEN, (uint8 *)OID_CA_ISSUERS};
+
+typedef enum {
+       LT_Crl = 1,
+       LT_Cert
+} LF_Type;
+
+static CSSM_RETURN tpDecodeCert(
+       Allocator               &alloc,
+       CSSM_DATA               &rtnBlob)               // will be reallocated if needed
+{
+       const unsigned char *inbuf = (const unsigned char *)rtnBlob.Data;
+       unsigned inlen = (unsigned)rtnBlob.Length;
+       unsigned char *outbuf = NULL;
+       unsigned outlen = 0;
+       CSSM_RETURN ortn = cuConvertPem(inbuf, inlen, &outbuf, &outlen);
+
+       if(ortn == 0 && outbuf != NULL) {
+               /* Decoded result needs to be malloc'd via input allocator */
+               unsigned char *rtnP = (unsigned char *) alloc.malloc(outlen);
+               if(rtnP != NULL) {
+                       memcpy(rtnP, outbuf, outlen);
+                       rtnBlob.Data = rtnP;
+                       rtnBlob.Length = outlen;
+               }
+               free(outbuf);
+               alloc.free((void *)inbuf);
+       }
+       return ortn;
+}
+
+static CSSM_RETURN tpFetchViaNet(
+       const CSSM_DATA &url,
+       const CSSM_DATA *issuer,                // optional
+       LF_Type                 lfType,
+       CSSM_TIMESTRING verifyTime,             // CRL only
+       Allocator               &alloc,
+       CSSM_DATA               &rtnBlob)               // mallocd and RETURNED
+{
+       if(lfType == LT_Crl) {
+               return ocspdCRLFetch(alloc, url, issuer,
+                       true, true,                             // cache r/w both enable
+                       verifyTime, rtnBlob);
+       }
+       else {
+               CSSM_RETURN result = ocspdCertFetch(alloc, url, rtnBlob);
+               if(result == CSSM_OK) {
+                       /* The data might be in PEM format; if so, convert it here */
+                       (void)tpDecodeCert(alloc, rtnBlob);
+               }
+               return result;
+       }
+}
+
+static CSSM_RETURN tpCrlViaNet(
+       const CSSM_DATA &url,
+       const CSSM_DATA *issuer,        // optional, only if cert and CRL have same issuer
+       TPVerifyContext &vfyCtx,
+       TPCertInfo &forCert,            // for verifyWithContext
+       TPCrlInfo *&rtnCrl)
+{
+       TPCrlInfo *crl = NULL;
+       CSSM_DATA crlData;
+       CSSM_RETURN crtn;
+       Allocator &alloc = Allocator::standard();
+       char cssmTime[CSSM_TIME_STRLEN+1];
+       
+       rtnCrl = NULL;
+       
+       /* verifyTime: we want a CRL that's valid right now. */
+       {
+               StLock<Mutex> _(tpTimeLock());
+               timeAtNowPlus(0, TIME_CSSM, cssmTime);
+       }
+
+       crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData);
+       if(crtn) {
+               return crtn;
+       }
+       try {
+               crl = new TPCrlInfo(vfyCtx.clHand,
+                       vfyCtx.cspHand,
+                       &crlData,
+                       TIC_CopyData,
+                       NULL);                  // verifyTime = Now
+       }
+       catch(...) {
+               alloc.free(crlData.Data);
+               
+               /* 
+                * There is a slight possibility of recovering from this error. In case
+                * the CRL came from disk cache, flush the cache and try to get the CRL
+                * from the net.
+                */
+               tpDebug("   bad CRL; flushing from cache and retrying"); 
+               ocspdCRLFlush(url);
+               crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData);
+               if(crtn == CSSM_OK) {
+                       try {
+                               crl = new TPCrlInfo(vfyCtx.clHand,
+                                       vfyCtx.cspHand,
+                                       &crlData,
+                                       TIC_CopyData,
+                                       NULL);  
+                               tpDebug("   RECOVERY: good CRL obtained from net"); 
+                       }
+                       catch(...) {
+                               alloc.free(crlData.Data);
+                               tpDebug("   bad CRL; recovery FAILED (1)"); 
+                               return CSSMERR_APPLETP_CRL_NOT_FOUND;
+                       }
+               }
+               else {
+                       /* it was in cache but we can't find it on the net */
+                       tpDebug("   bad CRL; recovery FAILED (2)"); 
+                       return CSSMERR_APPLETP_CRL_NOT_FOUND;
+               }
+       }
+       alloc.free(crlData.Data);
+       
+       /* 
+        * Full CRL verify.
+        * The verify time in the TPVerifyContext is the time at which various
+        * entities (CRL and its own cert chain) are to be verified; that's
+        * NULL for "right now". The current vfyCtx.verifyTime is the time at
+        * which the cert's revocation status to be determined; this call to 
+        * verifyWithContextNow() doesn't do that. 
+        */
+       crtn = crl->verifyWithContextNow(vfyCtx, &forCert);
+       if(crtn == CSSM_OK) {
+               crl->uri(url);
+       }
+       else {
+               delete crl;
+               crl = NULL;
+       }
+       rtnCrl = crl;
+       return crtn;
+}
+
+static CSSM_RETURN tpIssuerCertViaNet(
+       const CSSM_DATA &url,
+       CSSM_CL_HANDLE  clHand,
+       CSSM_CSP_HANDLE cspHand,
+       const char              *verifyTime,
+       TPCertInfo              &subject,       
+       TPCertInfo              *&rtnCert)
+{
+       TPCertInfo *issuer = NULL;
+       CSSM_DATA certData;
+       CSSM_RETURN crtn;
+       Allocator &alloc = Allocator::standard();
+       
+       crtn = tpFetchViaNet(url, NULL, LT_Cert, NULL, alloc, certData);
+       if(crtn) {
+               tpErrorLog("tpIssuerCertViaNet: net fetch failed\n");
+               return CSSMERR_APPLETP_CERT_NOT_FOUND_FROM_ISSUER;
+       }       
+       try {
+               issuer = new TPCertInfo(clHand,
+                       cspHand,
+                       &certData,
+                       TIC_CopyData,
+                       verifyTime);
+       }
+       catch(...) {
+               tpErrorLog("tpIssuerCertViaNet: bad cert via net fetch\n");
+               alloc.free(certData.Data);
+               rtnCert = NULL;
+               return CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER;
+       }
+       alloc.free(certData.Data);
+       
+       /* subject/issuer match? */
+       if(!issuer->isIssuerOf(subject)) {
+               tpErrorLog("tpIssuerCertViaNet: wrong issuer cert via net fetch\n");
+               crtn = CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER;
+       }
+       else {
+               /* yep, do a sig verify */
+               crtn = subject.verifyWithIssuer(issuer);
+               if(crtn) {
+                       tpErrorLog("tpIssuerCertViaNet: sig verify fail for cert via net "
+                               "fetch\n");
+                       crtn = CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER;
+               }
+       }
+       if(crtn) {
+               assert(issuer != NULL);
+               delete issuer;
+               issuer = NULL;
+       }
+       rtnCert = issuer;
+       return crtn;
+}
+
+/*
+ * Fetch a CRL or a cert via a GeneralNames.
+ * Shared by cert and CRL code to avoid duplicating GeneralNames traversal
+ * code, despite the awkward interface for this function. 
+ */
+static CSSM_RETURN tpFetchViaGeneralNames(
+       const CE_GeneralNames   *names,
+       TPCertInfo                              &forCert,
+       const CSSM_DATA                 *issuer,                        // optional, and only for CRLs
+       TPVerifyContext                 *verifyContext,         // only for CRLs
+       CSSM_CL_HANDLE                  clHand,                         // only for certs
+       CSSM_CSP_HANDLE                 cspHand,                        // only for certs
+       const char                              *verifyTime,            // optional
+       /* exactly one must be non-NULL, that one is returned */
+       TPCertInfo                              **certInfo,
+       TPCrlInfo                               **crlInfo)
+{      
+       assert(certInfo || crlInfo);
+       assert(!certInfo || !crlInfo);
+       CSSM_RETURN crtn;
+       
+       for(unsigned nameDex=0; nameDex<names->numNames; nameDex++) {
+               CE_GeneralName *name = &names->generalName[nameDex];
+               switch(name->nameType) {
+                       case GNT_URI:
+                               if(name->name.Length < 5) {
+                                       continue;
+                               }
+                               if(strncmp((char *)name->name.Data, "ldap:", 5) &&
+                                  strncmp((char *)name->name.Data, "http:", 5) && 
+                                  strncmp((char *)name->name.Data, "https:", 6)) {
+                                       /* eventually handle other schemes here */
+                                       continue;
+                               }
+                               if(certInfo) {
+                                       tpDebug("   fetching cert via net"); 
+                                       crtn = tpIssuerCertViaNet(name->name, 
+                                               clHand,
+                                               cspHand,
+                                               verifyTime,
+                                               forCert,
+                                               *certInfo);
+                               }
+                               else {
+                                       tpDebug("   fetching CRL via net"); 
+                                       assert(verifyContext != NULL);
+                                       crtn = tpCrlViaNet(name->name, 
+                                               issuer,
+                                               *verifyContext,
+                                               forCert,
+                                               *crlInfo);
+                               }
+                               switch(crtn) {
+                                       case CSSM_OK:
+                                       case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:   // caller handles
+                                               return crtn;
+                                       default:
+                                               break;
+                               }
+                               /* not found/no good; try again */
+                               break;
+                       default:
+                               tpCrlDebug("  tpFetchCrlFromNet: unknown"
+                                       "nameType (%u)", (unsigned)name->nameType); 
+                               break;
+               }       /* switch nameType */
+       }       /* for each name */
+       if(certInfo) {
+               return CSSMERR_TP_CERTGROUP_INCOMPLETE;
+       }
+       else {
+               return CSSMERR_APPLETP_CRL_NOT_FOUND;
+       }
+}
+
+/*
+ * Fetch CRL(s) from specified cert if the cert has a cRlDistributionPoint
+ * extension.
+ *
+ * Return values:
+ *   CSSM_OK - found and returned fully verified CRL 
+ *   CSSMERR_APPLETP_CRL_NOT_FOUND - no CRL in cRlDistributionPoint
+ *   Anything else - gross error, typically from last LDAP/HTTP attempt
+ *
+ * FIXME - this whole mechanism sort of falls apart if verifyContext.verifyTime
+ * is non-NULL. How are we supposed to get the CRL which was valid at 
+ * a specified time in the past?
+ */
+CSSM_RETURN tpFetchCrlFromNet(
+       TPCertInfo                      &cert,
+       TPVerifyContext         &vfyCtx,
+       TPCrlInfo                       *&crl)                  // RETURNED
+{
+       /* does the cert have a cRlDistributionPoint? */
+       CSSM_DATA_PTR fieldValue;                       // mallocd by CL
+       
+       CSSM_RETURN crtn = cert.fetchField(&CSSMOID_CrlDistributionPoints,
+               &fieldValue);
+       switch(crtn) {
+               case CSSM_OK:
+                       break;
+               case CSSMERR_CL_NO_FIELD_VALUES:
+                       /* field not present */
+                       return CSSMERR_APPLETP_CRL_NOT_FOUND;
+               default:
+                       /* gross error */
+                       return crtn;
+       }
+       if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
+               tpErrorLog("tpFetchCrlFromNet: malformed CSSM_FIELD");
+               return CSSMERR_TP_UNKNOWN_FORMAT;
+       }
+       CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
+       CE_CRLDistPointsSyntax *dps = 
+               (CE_CRLDistPointsSyntax *)cssmExt->value.parsedValue;
+       TPCrlInfo *rtnCrl = NULL;
+
+       /* default return if we don't find anything */
+       crtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
+       for(unsigned dex=0; dex<dps->numDistPoints; dex++) {
+               CE_CRLDistributionPoint *dp = &dps->distPoints[dex];
+               if(dp->distPointName == NULL) {
+                       continue;
+               }
+               /*
+                * FIXME if this uses an indirect CRL, we need to follow the 
+                * crlIssuer field... TBD.
+                */
+               switch(dp->distPointName->nameType) {
+                       case CE_CDNT_NameRelativeToCrlIssuer:
+                               /* not yet */
+                               tpErrorLog("tpFetchCrlFromNet: "
+                                       "CE_CDNT_NameRelativeToCrlIssuer not implemented\n");
+                               break;
+                               
+                       case CE_CDNT_FullName:
+                       {
+                               /*
+                                * Since we don't support indirect CRLs (yet), we always pass
+                                * the cert-to-be-verified's issuer as the CRL issuer for 
+                                * cache lookup.
+                                */
+                               CE_GeneralNames *names = dp->distPointName->dpn.fullName;
+                               crtn = tpFetchViaGeneralNames(names,
+                                       cert,
+                                       cert.issuerName(),
+                                       &vfyCtx,
+                                       0,                      // clHand, use the one in vfyCtx
+                                       0,                      // cspHand, ditto
+                                       vfyCtx.verifyTime,      
+                                       NULL,           
+                                       &rtnCrl);
+                               break;
+                       }       /* CE_CDNT_FullName */
+                       
+                       default:
+                               /* not yet */
+                               tpErrorLog("tpFetchCrlFromNet: "
+                                       "unknown distPointName->nameType (%u)\n",
+                                               (unsigned)dp->distPointName->nameType);
+                               break;
+               }       /* switch distPointName->nameType */
+               if(crtn == CSSM_OK) {
+                       /* i.e., tpFetchViaGeneralNames SUCCEEDED */
+                       break;
+               }
+       }       /* for each distPoints */
+
+       cert.freeField(&CSSMOID_CrlDistributionPoints,  fieldValue);
+       if(crtn == CSSM_OK) {
+               assert(rtnCrl != NULL);
+               crl = rtnCrl;
+       }
+       return crtn;
+}
+
+/*
+ * Fetch issuer cert of specified cert if the cert has an issuerAltName
+ * with a URI. If non-NULL cert is returned, it has passed subject/issuer
+ * name comparison and signature verification with target cert.
+ *
+ * Return values:
+ *   CSSM_OK - found and returned issuer cert 
+ *   CSSMERR_TP_CERTGROUP_INCOMPLETE - no URL in issuerAltName
+ *   CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE - found and returned issuer
+ *      cert, but signature verification needs subsequent retry.
+ *   Anything else - gross error, typically from last LDAP/HTTP attempt
+ */
+CSSM_RETURN tpFetchIssuerFromNet(
+       TPCertInfo                      &subject,
+       CSSM_CL_HANDLE          clHand,
+       CSSM_CSP_HANDLE         cspHand,
+       const char                      *verifyTime,
+       TPCertInfo                      *&issuer)               // RETURNED
+{
+       CSSM_OID_PTR fieldOid = NULL;
+       CSSM_DATA_PTR fieldValue = NULL;        // mallocd by CL
+       CSSM_RETURN crtn;
+       bool hasAIA = false;
+
+       /* look for the Authority Info Access extension first */
+       fieldOid = (CSSM_OID_PTR)&CSSMOID_AuthorityInfoAccess;
+       crtn = subject.fetchField(fieldOid,
+               &fieldValue);
+       hasAIA = (crtn == CSSM_OK);
+       if (!hasAIA) {
+               /* fall back to Issuer Alternative Name extension */
+               fieldOid = (CSSM_OID_PTR)&CSSMOID_IssuerAltName;
+               crtn = subject.fetchField(fieldOid,
+                                                                 &fieldValue);
+       }
+       switch(crtn) {
+               case CSSM_OK:
+                       break;
+               case CSSMERR_CL_NO_FIELD_VALUES:
+                       /* field not present */
+                       return CSSMERR_TP_CERTGROUP_INCOMPLETE;
+               default:
+                       /* gross error */
+                       return crtn;
+       }
+       if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
+               tpPolicyError("tpFetchIssuerFromNet: malformed CSSM_FIELD");
+               return CSSMERR_TP_UNKNOWN_FORMAT;
+       }
+       CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
+       CE_GeneralNames *names = (CE_GeneralNames *)cssmExt->value.parsedValue;
+       TPCertInfo *rtnCert = NULL;
+       if (hasAIA) {   /* authority info access */
+               CE_AuthorityInfoAccess *access = (CE_AuthorityInfoAccess *)cssmExt->value.parsedValue;
+               for (uint32 index = 0; access && index < access->numAccessDescriptions; index++) {
+                       CE_AccessDescription *accessDesc = &access->accessDescriptions[index];
+                       CSSM_OID_PTR methodOid = (CSSM_OID_PTR)&accessDesc->accessMethod;
+                       /* look for the CA Issuers method */
+                       if(methodOid->Data != NULL && methodOid->Length == CSSMOID_CA_ISSUERS.Length &&
+                          !memcmp(methodOid->Data, CSSMOID_CA_ISSUERS.Data, methodOid->Length)) {
+                               CE_GeneralNames aiaNames = { 1, &accessDesc->accessLocation };
+                               /* attempt to fetch cert from named location */
+                               crtn = tpFetchViaGeneralNames(&aiaNames,
+                                                                                         subject,
+                                                                                         NULL,         // issuer - not used
+                                                                                         NULL,         // verifyContext
+                                                                                         clHand,
+                                                                                         cspHand,
+                                                                                         verifyTime,
+                                                                                         &rtnCert,
+                                                                                         NULL);
+                               if (crtn == CSSM_OK ||
+                                       crtn == CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE) {
+                                       break; // got one
+                               }
+                       }
+               }
+               subject.freeField(fieldOid,     fieldValue);
+       }
+       else {  /* issuer alt name */
+               crtn = tpFetchViaGeneralNames(names,
+                                               subject,
+                                               NULL,           // issuer - not used
+                                               NULL,           // verifyContext
+                                               clHand,
+                                               cspHand,
+                                               verifyTime,
+                                               &rtnCert,
+                                               NULL);
+               subject.freeField(fieldOid,     fieldValue);
+       }
+       switch(crtn) {
+               case CSSM_OK:
+               case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
+                       issuer = rtnCert;
+                       break;
+               default:
+                       break;
+       }
+       return crtn;
+}
+
+