--- /dev/null
+/*
+ * 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;
+}
+
+