X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/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 index 00000000..bc9cf3fe --- /dev/null +++ b/Security/libsecurity_apple_x509_tp/lib/TPNetwork.cpp @@ -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 +#include +#include +#include +#include + +#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 _(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; nameDexnumNames; 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; dexnumDistPoints; 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; +} + +