/* * Copyright (c) 2004-2012 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The 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. * * @APPLE_LICENSE_HEADER_END@ */ /* * ocspdServer.cpp - Server class for OCSP helper */ #if OCSP_DEBUG #define OCSP_USE_SYSLOG 1 #endif #include "ocspdServer.h" #include #include #include #include #include "ocspdNetwork.h" #include "ocspdDb.h" #include "crlDb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* created by MIG */ /* How long to wait for a CRL download to complete before ignoring result * and letting it complete in the background. */ #define CRL_MAX_DOWNLOAD_WAIT 3.0 /* Maximum CRL length to consider putting in the cache db (128KB); * larger CRLs will be written to individual files. */ #define CRL_MAX_DATA_LENGTH (1024*128) /* Lock while a shared parameters struct is being read or updated */ Mutex gParamsLock; /* Lock while a file is being written */ Mutex gFileWriteLock; /* Lock while shared lists are being read or updated */ Mutex gListLock; /* Global list of files being downloaded */ CFMutableArrayRef gDownloadList = NULL; /* Global list of OCSP URIs currently being contacted */ CFMutableArrayRef gURIList = NULL; /* Global dictionary of CRL issuers */ CFMutableDictionaryRef gIssuersDict = NULL; const char *gCrlPath = "/var/db/crls/"; #pragma mark ----- OCSP utilities ----- /* * Once we've gotten a response from a server, cook up a SecAsn1OCSPDReply. */ static SecAsn1OCSPDReply *ocspdGenReply( SecAsn1CoderRef coder, const CSSM_DATA &resp, const CSSM_DATA &certID) { SecAsn1OCSPDReply *ocspdRep = (SecAsn1OCSPDReply *)SecAsn1Malloc(coder, sizeof(*ocspdRep)); SecAsn1AllocCopyItem(coder, &resp, &ocspdRep->ocspResp); SecAsn1AllocCopyItem(coder, &certID, &ocspdRep->certID); return ocspdRep; } static SecAsn1OCSPDReply *ocspdHandleReq( SecAsn1CoderRef coder, SecAsn1OCSPDRequest &request, bool recursing) { CSSM_DATA derResp = {0, NULL}; CSSM_RETURN crtn; bool cacheReadDisable = false; bool cacheWriteDisable = false; if((request.cacheReadDisable != NULL) && (request.cacheReadDisable->Length != 0) && (request.cacheReadDisable->Data[0] != 0)) { cacheReadDisable = true; } if((request.cacheWriteDisable != NULL) && (request.cacheWriteDisable->Length != 0) && (request.cacheWriteDisable->Data[0] != 0)) { cacheWriteDisable = true; } if(!cacheReadDisable) { /* do a cache lookup */ bool found = ocspdDbCacheLookup(coder, request.certID, request.localRespURI, derResp); if(found) { return ocspdGenReply(coder, derResp, request.certID); } } if(request.localRespURI) { if(request.ocspReq == NULL) { ocspdErrorLog("ocspdHandleReq: localRespURI but no request to send\n"); return NULL; } crtn = ocspdHttpFetch(coder, *request.localRespURI, *request.ocspReq, derResp); if(crtn == CSSM_OK) { SecAsn1OCSPDReply *reply = ocspdGenReply(coder, derResp, request.certID); if(!cacheWriteDisable) { ocspdDbCacheAdd(derResp, *request.localRespURI); } return reply; } } /* now try everything in requests.urls, the normal case */ unsigned numUris = ocspdArraySize((const void **)request.urls); for(unsigned dex=0; dexLength > 0 && uri->Data != NULL); if(reqOK && recursing) { /* We are being called from within a concurrent request, so must * be careful to avoid repeating the same lookups endlessly. */ uriStr = CFStringCreateWithBytes(kCFAllocatorDefault, uri->Data, uri->Length, kCFStringEncodingUTF8, false); if(!uriStr) { reqOK = false; } else { StLock _(gListLock); /* lock before examining list */ if(gURIList == NULL) { gURIList = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if(!gURIList) { reqOK = false; } } if(reqOK) { bool inProgress = CFArrayContainsValue(gURIList, CFRangeMake(0, CFArrayGetCount(gURIList)), uriStr); if(!inProgress) { /* Add the URI to our "reentrant URIs in progress" list * and proceed with the request. This allows legitimate * reentrancy when processing redirects; however, if * execution comes back here with the same URI before we * finish this function and can remove it from the list, * we shouldn't make the request. */ CFArrayAppendValue(gURIList, uriStr); } else { /* Don't repeat this request */ char *ustr = (char *)malloc(uri->Length + 1); memmove(ustr, uri->Data, uri->Length); ustr[uri->Length] = '\0'; ocspdErrorLog("ocspdHandleReq: request for \"%s\" is already in progress\n", ustr); free(ustr); reqOK = false; } } } } if(reqOK) { /* go ahead with this OCSP request */ crtn = ocspdHttpFetch(coder, *uri, *request.ocspReq, derResp); } else { crtn = CSSMERR_APPLETP_OCSP_BAD_REQUEST; } if(uriStr) { if(reqOK) { /* remove URI from list */ StLock _(gListLock); CFIndex idx = CFArrayGetFirstIndexOfValue(gURIList, CFRangeMake(0, CFArrayGetCount(gURIList)), uriStr); if(idx >= 0) { CFArrayRemoveValueAtIndex(gURIList, idx); } } CFRelease(uriStr); } if(crtn == CSSM_OK) { SecAsn1OCSPDReply *reply = ocspdGenReply(coder, derResp, request.certID); if(!cacheWriteDisable) { ocspdDbCacheAdd(derResp, *uri); } return reply; } } return NULL; } #pragma mark ----- CRL utilities ----- /* * Generate and malloc a CRL filename given a lookup key, * which can be either the issuer's distinguished name or * a distribution point URL. Caller must free. */ static char* crlGenerateFileName( unsigned char *key, size_t keyLen, const char *pathPrefix, const char *extension) { if(!key || !keyLen) { return NULL; } const size_t prefixLen = strlen(pathPrefix); const size_t suffixLen = strlen(extension); const size_t fileNameLen = prefixLen+(CC_SHA1_DIGEST_LENGTH*2)+suffixLen+1; char *fileName = (char*)malloc(fileNameLen); if(!fileName) { return NULL; } unsigned char digest[CC_SHA1_DIGEST_LENGTH]; unsigned char *dataPtr = key; unsigned int dataLen = (unsigned int)keyLen; CC_SHA1(dataPtr, dataLen, digest); char *outPtr = &fileName[0]; unsigned int outLen = (unsigned int)fileNameLen; strlcpy(outPtr, pathPrefix, outLen); outPtr += prefixLen; outLen -= prefixLen; dataPtr = &digest[0]; for(dataLen=CC_SHA1_DIGEST_LENGTH; dataLen > 0; dataLen--) { snprintf(outPtr, outLen, "%02X", *dataPtr++); outPtr+=2; outLen-=2; *outPtr='\0'; } strncat(fileName, extension, outLen-1); return fileName; } /* * Given a pointer and length, malloc and return a string which contains * the hex representation of the data. Caller must free. */ static char* crlPrintableStringWithData( unsigned char *inData, size_t inLen) { size_t outStrLen = (inLen*2)+1; char *outStr = (char*)malloc(outStrLen); if(!outStr) { return NULL; } unsigned char *dataPtr = inData; size_t dataLen = inLen; char *outPtr = &outStr[0]; size_t outLen = outStrLen; for(; dataLen > 0; dataLen--) { snprintf(outPtr, outLen, "%02X", *dataPtr++); outPtr+=2; outLen-=2; *outPtr='\0'; } return outStr; } /* Given a path to a DER-encoded CRL file and a path to a PEM-encoded * CA issuers file, use OpenSSL to validate the CRL. This is a hack, * necessitated by performance issues with inserting extremely large * numbers of CRL entries into a CSSM DB (see ). * * Returns true if the signature on crlFileName is valid, false otherwise. */ bool crlSignatureValid( const char *crlFileName, const char *issuersFileName, const char *updateFileName, const char *revokedFileName) { struct stat sb; if(stat(crlFileName, &sb) != 0 || stat(issuersFileName, &sb) != 0) { /* must have both files to proceed past this point */ return false; } const char *vc1 = "/usr/bin/openssl crl -inform DER -noout -in \""; const char *vc2 = "\" -CAfile \""; const char *vc3 = "\" 2>&1 | /usr/bin/grep OK"; size_t cmdLen = strlen(vc1)+strlen(crlFileName)+ strlen(vc2)+strlen(issuersFileName)+ strlen(vc3)+1; char *command = (char*)malloc(cmdLen); size_t tmpLen = cmdLen; strlcpy(command, vc1, tmpLen); tmpLen -= strlen(vc1); strncat(command, crlFileName, tmpLen); tmpLen -= strlen(crlFileName); strncat(command, vc2, tmpLen); tmpLen -= strlen(vc2); strncat(command, issuersFileName, tmpLen); tmpLen -= strlen(issuersFileName); strncat(command, vc3, tmpLen); bool valid = (system(command) == 0); free(command); if(!valid) { ocspdCrlDebug("crlSignatureValid: CRL failed to verify: %s\n", crlFileName); if(updateFileName) { /* to prevent constant refetching of a CRL we cannot verify, * create .update file with a nextUpdate value in 5 minutes */ const char *uc1 = "/bin/date -u -v +5M \'+%b %d %H:%M:%S %Y GMT\' > \""; const char *uc2 = "\""; cmdLen = strlen(uc1)+strlen(updateFileName)+strlen(uc2)+1; command = (char*)malloc(cmdLen); tmpLen = cmdLen; strlcpy(command, uc1, tmpLen); tmpLen -= strlen(uc1); strncat(command, updateFileName, tmpLen); tmpLen -= strlen(updateFileName); strncat(command, uc2, tmpLen); system(command); free(command); if(chmod(updateFileName, 0644)) { ocspdErrorLog("crlSignatureValid: chmod error %d for %s", errno, updateFileName); } } if(revokedFileName) { int fd = open(revokedFileName, O_CREAT | O_TRUNC, 0644); if(fd == -1) { ocspdErrorLog("crlSignatureValid: truncate error %d for %s", errno, revokedFileName); } else { close(fd); if(chmod(revokedFileName, 0644)) { ocspdErrorLog("crlSignatureValid: chmod error %d for %s", errno, revokedFileName); } } } return false; } if(updateFileName) { /* create .update file to hold nextUpdate value */ const char *uc1 = "/usr/bin/openssl crl -inform DER -noout -nextupdate -in \""; const char *uc2 = "\" | /usr/bin/awk -F= '{print $2}' > \""; const char *uc3 = "\""; cmdLen = strlen(uc1)+strlen(crlFileName)+ strlen(uc2)+strlen(updateFileName)+ strlen(uc3)+1; command = (char*)malloc(cmdLen); tmpLen = cmdLen; strlcpy(command, uc1, tmpLen); tmpLen -= strlen(uc1); strncat(command, crlFileName, tmpLen); tmpLen -= strlen(crlFileName); strncat(command, uc2, tmpLen); tmpLen -= strlen(uc2); strncat(command, updateFileName, tmpLen); tmpLen -= strlen(updateFileName); strncat(command, uc3, tmpLen); system(command); free(command); if(chmod(updateFileName, 0644)) { ocspdErrorLog("crlSignatureValid: chmod error %d for %s", errno, updateFileName); } } if(revokedFileName) { /* create .revoked file to hold validated cache of revoked serials */ const char *rc1 = "/usr/bin/openssl crl -inform DER -noout -text -in \""; const char *rc2 = "\" | /usr/bin/grep \"Number:\" | /usr/bin/awk '{print $3}' > \""; const char *rc3 = "\""; cmdLen = strlen(rc1)+strlen(crlFileName)+ strlen(rc2)+strlen(revokedFileName)+ strlen(rc3)+1; command = (char*)malloc(cmdLen); tmpLen = cmdLen; strlcpy(command, rc1, tmpLen); tmpLen -= strlen(rc1); strncat(command, crlFileName, tmpLen); tmpLen -= strlen(crlFileName); strncat(command, rc2, tmpLen); tmpLen -= strlen(rc2); strncat(command, revokedFileName, tmpLen); tmpLen -= strlen(revokedFileName); strncat(command, rc3, tmpLen); system(command); free(command); if(chmod(revokedFileName, 0644)) { ocspdErrorLog("crlSignatureValid: chmod error %d for %s", errno, revokedFileName); } } return true; } /* Given a path to a file containing the CRL's nextUpdate date, * return true if this date is greater than the current date, * otherwise false. */ bool crlUpdateValid( const char *updateFileName) { bool result = false; unsigned char *updateBytes = NULL; unsigned int updateLen = 0; int err; if((err=readFile(updateFileName, &updateBytes, &updateLen) != 0)) { ocspdCrlDebug("crlUpdateValid: error %d reading %s\n", err, updateFileName); return result; } /* check for special case where nextUpdate value is NONE */ if(updateLen >= 4 && !memcmp(updateBytes, "NONE", 4)) { ocspdCrlDebug("crlUpdateValid: nextUpdate is NONE\n"); result = true; } else { /* update time is expressed in POSIX locale and GMT timezone */ tm tm_next; const char *format = "%b %d %H:%M:%S %Y %Z"; setlocale(LC_TIME, "POSIX"); if(strptime((const char *)updateBytes, format, &tm_next)) { time_t now = time(NULL); time_t next = timelocal(&tm_next); result = (now < next); #if OCSP_DEBUG char buf[updateLen+1]; strncpy(buf, (char *)updateBytes, updateLen); buf[updateLen-1]='\0'; /* deliberately cutting off final LF byte */ ocspdCrlDebug("crlUpdateValid: nextUpdate=%s (%s)\n", buf, (result) ? "valid" : "must refetch!"); #endif } else { ocspdCrlDebug("crlUpdateValid: no nextUpdate date found!\n"); } } free(updateBytes); return result; } /* Given a path to a validated cache file and a serial number string, * determine whether that serial number is revoked in the cache. * This should only be called after crlSignatureValid has confirmed the * validity of the CRL and the cache file. */ bool crlSerialNumberRevoked( const char *revokedFileName, const char *serialNumber) { bool result = false; size_t serialNumberLen = (serialNumber) ? strlen(serialNumber) : 0; if (!serialNumberLen) { return result; } unsigned char *revokedBytes = NULL; unsigned int revokedLen = 0; int err; if((err=readFile(revokedFileName, &revokedBytes, &revokedLen) != 0)) { ocspdCrlDebug("crlSerialNumberRevoked: error %d reading %s\n", err, revokedFileName); return result; } char *start = (char *)revokedBytes; size_t bytesRemaining = revokedLen; while (bytesRemaining > 0) { /* find next EOL (or EOF) */ char *end = (char *)memchr(start, 0x0A, bytesRemaining); size_t bytesRead = (end) ? ((uintptr_t)end - (uintptr_t)start) + 1 : bytesRemaining; bytesRemaining -= bytesRead; if (bytesRead >= serialNumberLen) { if (!memcmp(start, serialNumber, serialNumberLen)) { result = true; break; } } if (bytesRemaining) { start = ++end; } } free(revokedBytes); return result; } /* * Attempt to create the CRL cache path if it doesn't exist. */ int crlCheckCachePath() { return mkpath_np((char*)gCrlPath, 0755); } #pragma mark ----- Mig-referenced OCSP routines ----- /* all of these Mig-referenced routines are called out from ocspd_server() */ kern_return_t ocsp_server_ocspdFetch ( mach_port_t serverport, audit_token_t auditToken, Data ocspd_req, mach_msg_type_number_t ocspd_reqCnt, Data *ocspd_rep, mach_msg_type_number_t *ocspd_repCnt) { ServerActivity(); ocspdDebug("ocsp_server_ocspFetch top"); *ocspd_rep = NULL; *ocspd_repCnt = 0; kern_return_t krtn = 0; unsigned numRequests; SecAsn1OCSPReplies replies; unsigned numReplies = 0; uint8 version = OCSPD_REPLY_VERS; pid_t pid = -1; audit_token_to_au32(auditToken, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL); bool recursing = (getpid() == pid); /* decode top-level SecAsn1OCSPDRequests */ SecAsn1CoderRef coder; SecAsn1CoderCreate(&coder); SecAsn1OCSPDRequests requests; memset(&requests, 0, sizeof(requests)); if(SecAsn1Decode(coder, ocspd_req, ocspd_reqCnt, kSecAsn1OCSPDRequestsTemplate, &requests)) { ocspdErrorLog("ocsp_server_ocspdFetch: decode error\n"); krtn = CSSMERR_APPLETP_OCSP_BAD_REQUEST; goto errOut; } if((requests.version.Length == 0) || (requests.version.Data[0] != OCSPD_REQUEST_VERS)) { /* * Eventually handle backwards compatibility here */ ocspdErrorLog("ocsp_server_ocspdFetch: request version mismatch\n"); krtn = CSSMERR_APPLETP_OCSP_BAD_REQUEST; goto errOut; } numRequests = ocspdArraySize((const void **)requests.requests); replies.replies = (SecAsn1OCSPDReply **)SecAsn1Malloc(coder, (numRequests + 1) * sizeof(SecAsn1OCSPDReply *)); memset(replies.replies, 0, (numRequests + 1) * sizeof(SecAsn1OCSPDReply *)); replies.version.Data = &version; replies.version.Length = 1; /* preparing for net fetch: enable another thread */ OcspdServer::active().longTermActivity(); /* This may need to be threaded, one thread per request */ for(unsigned dex=0; dex _(gListLock); /* lock before examining lists */ if(gDownloadList == NULL) { gDownloadList = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if(!gDownloadList) { krtn = CSSMERR_TP_INTERNAL_ERROR; /* can't continue */ goto crlStatus_cleanup; } } Boolean downloadInProgress = false; CFStringRef crlNameStr = CFStringCreateWithCString(kCFAllocatorDefault, names.crlFile, kCFStringEncodingUTF8); if(crlNameStr) { downloadInProgress = CFArrayContainsValue(gDownloadList, CFRangeMake(0, CFArrayGetCount(gDownloadList)), crlNameStr); CFRelease(crlNameStr); } if(downloadInProgress) { ocspdCrlDebug("ocsp_server_crlStatus: download already in progress for \"%s\"", names.crlFile); krtn = CSSMERR_APPLETP_NETWORK_FAILURE; /* busy, download not yet complete! */ goto crlStatus_cleanup; } /* Add issuers to dictionary, so we can find them later */ if(gIssuersDict == NULL) { gIssuersDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if(!gIssuersDict) { krtn = CSSMERR_TP_INTERNAL_ERROR; /* can't continue */ goto crlStatus_cleanup; } } if(cert_issuers != NULL && cert_issuersCnt > 0) { CFStringRef pemNameStr = CFStringCreateWithCString(kCFAllocatorDefault, names.pemFile, kCFStringEncodingUTF8); if(pemNameStr) { CFDataRef pemData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)cert_issuers, (CFIndex)cert_issuersCnt); if(pemData) { CFDictionarySetValue(gIssuersDict, pemNameStr, pemData); CFRelease(pemData); } CFRelease(pemNameStr); } } } /* Check whether the CRL file is present */ if(stat(names.crlFile, &sb) != 0) { /* Note that returning "not found" will trigger a subsequent call * to crlFetch, which will first look for the CRL in our cache * before attempting to fetch it from the network. */ ocspdCrlDebug("ocsp_server_crlStatus: CRL file \"%s\" not found", names.crlFile); krtn = CSSMERR_APPLETP_CRL_NOT_FOUND; goto crlStatus_cleanup; } /* Check whether we have previously validated the CRL, and if so, whether * the nextUpdate date has passed. */ if(stat(names.updateFile, &sb) == 0) { if(!crlUpdateValid(names.updateFile) || !(stat(names.revokedFile, &sb) == 0)) { /* Remove invalid files and bail out */ StLock _(gFileWriteLock); remove(names.updateFile); remove(names.revokedFile); remove(names.pemFile); remove(names.crlFile); ocspdCrlDebug("ocsp_server_crlStatus: CRL file \"%s\" needs update, removing", names.crlFile); krtn = CSSMERR_APPLETP_CRL_NOT_FOUND; goto crlStatus_cleanup; } crlValid = true; } if(crlValid) { char *serialStr = crlPrintableStringWithData((unsigned char*)serial_number, serial_numberCnt); if(serialStr) { if(crlSerialNumberRevoked(names.revokedFile, serialStr)) { krtn = CSSMERR_TP_CERT_REVOKED; ocspdCrlDebug("crlSignatureValid: found revoked serial number %s\n", serialStr); } else { krtn = CSSM_OK; ocspdCrlDebug("crlSignatureValid: CRL did not contain serial number %s\n", serialStr); } free(serialStr); } else { ocspdCrlDebug("ocsp_server_crlStatus: no serial number provided!"); krtn = CSSMERR_TP_INTERNAL_ERROR; } } else { /* CRL file isn't present or isn't valid; need to download it */ ocspdCrlDebug("ocsp_server_crlStatus: CRL file \"%s\" was invalid", names.crlFile); krtn = CSSMERR_APPLETP_CRL_NOT_FOUND; } crlStatus_cleanup: if(names.updateFile) free(names.updateFile); if(names.revokedFile) free(names.revokedFile); if(names.pemFile) free(names.pemFile); if(names.crlFile) free(names.crlFile); return krtn; } /* * Fetch a CRL from the net. */ kern_return_t ocsp_server_crlFetch ( mach_port_t serverport, audit_token_t auditToken, Data crl_url, mach_msg_type_number_t crl_urlCnt, Data crl_issuer, // optional mach_msg_type_number_t crl_issuerCnt, boolean_t cache_read, boolean_t cache_write, Data verifyTime, mach_msg_type_number_t verifyTimeCnt, Data *crl_data, mach_msg_type_number_t *crl_dataCnt) { ocspdCrlDebug("Processing crlFetch request"); ServerActivity(); const CSSM_DATA urlData = {crl_urlCnt, (uint8 *)crl_url}; CSSM_DATA crlData = {0, NULL}; Allocator &alloc = OcspdServer::active().alloc(); /* * 1. Read from cache if enabled. Look up by issuer if we have it, else * look up by URL. Per Radar 4565280, the same CRL might be * vended from different URLs; we don't care where we got it * from at this point as long as the client knew - by the absence * of a crlIssuer field in the crlDistributionPoints extension - * that the issuer of the CRL is the same as the issuer of the cert * being verified. */ if(cache_read) { const CSSM_DATA vfyTimeData = {verifyTimeCnt, (uint8 *)verifyTime}; const CSSM_DATA issuerData = {crl_issuerCnt, (uint8 *)crl_issuer}; const CSSM_DATA *issuerPtr; const CSSM_DATA *urlPtr; bool brtn; if(crl_issuerCnt) { /* look up by issuer */ ocspdCrlDebug("Cache lookup with %ld bytes of issuer data", issuerData.Length); issuerPtr = &issuerData; urlPtr = NULL; } else { /* look up by URL */ ocspdCrlDebug("Cache lookup with %ld bytes of URL data", urlData.Length); issuerPtr = NULL; urlPtr = &urlData; } #if OCSP_DEBUG if(verifyTimeCnt) { char *buf = (char*)malloc(verifyTimeCnt+1); memcpy(buf, verifyTime, verifyTimeCnt); buf[verifyTimeCnt]=0; ocspdCrlDebug("Cache lookup verify time: %s", buf); free(buf); } #endif brtn = crlCacheLookup(alloc, urlPtr, issuerPtr, vfyTimeData, crlData); if(!brtn && issuerPtr) { /* unable to find by issuer; retry lookup with URL */ issuerPtr = NULL; urlPtr = &urlData; ocspdCrlDebug("Cache lookup with %ld bytes of URL data", urlData.Length); brtn = crlCacheLookup(alloc, urlPtr, issuerPtr, vfyTimeData, crlData); } if(brtn) { /* Cache hit: Pass CRL back to caller & dealloc */ ocspdCrlDebug("Cache lookup succeeded, returning %ld bytes", crlData.Length); assert((crlData.Data != NULL) && (crlData.Length != 0)); passDataToCaller(crlData, crl_data, crl_dataCnt); return 0; } ocspdCrlDebug("Cache lookup failed; will attempt net fetch"); } /* * 2. Obtain from net */ CSSM_RETURN crtn; /* preparing for net fetch: enable another thread */ OcspdServer::active().longTermActivity(); if(!callerHasNetworkEntitlement(auditToken)) { /* client can't access network */ return CSSMERR_APPLETP_NETWORK_FAILURE; } size_t dataLen = (crl_issuerCnt) ? crl_issuerCnt : crl_urlCnt; unsigned char *dataPtr = (unsigned char *)((crl_issuerCnt) ? crl_issuer : crl_url); if(!dataLen || !dataPtr) { return CSSMERR_TP_INTERNAL_ERROR; } async_fetch_t *fetchParams = (async_fetch_t *)malloc(sizeof(async_fetch_t)); if(!fetchParams) { return CSSMERR_TP_INTERNAL_ERROR; } memset(fetchParams, 0, sizeof(async_fetch_t)); fetchParams->alloc = &alloc; fetchParams->url.Data = (uint8*)malloc(urlData.Length); fetchParams->url.Length = urlData.Length; memmove(fetchParams->url.Data, urlData.Data, urlData.Length); fetchParams->lfType = LT_Crl; fetchParams->outFile = crlGenerateFileName(dataPtr, dataLen, gCrlPath, ".crl"); fetchParams->crlNames.crlFile = crlGenerateFileName(dataPtr, dataLen, gCrlPath, ".crl"); fetchParams->crlNames.pemFile = crlGenerateFileName(dataPtr, dataLen, gCrlPath, ".pem"); fetchParams->crlNames.updateFile = crlGenerateFileName(dataPtr, dataLen, gCrlPath, ".update"); fetchParams->crlNames.revokedFile = crlGenerateFileName(dataPtr, dataLen, gCrlPath, ".revoked"); crtn = ocspdStartNetFetch(fetchParams); if(!crtn) { /* cycle the run loop until we finish downloading or time out */ CFAbsoluteTime stopTime = CFAbsoluteTimeGetCurrent() + CRL_MAX_DOWNLOAD_WAIT; while (!fetchParams->finished) { (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, TRUE); CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent(); if (curTime > stopTime) { StLock _(gParamsLock); if(!fetchParams->finished) { /* fetchParams are now dead to us; other thread will clean up */ fetchParams->freeOnDone = 1; ocspdCrlDebug("ocsp_server_crlFetch waited for %f seconds", CRL_MAX_DOWNLOAD_WAIT); return CSSMERR_APPLETP_NETWORK_FAILURE; } } } /* we finished the download and can provide data right away */ crtn = fetchParams->result; if(fetchParams->fetched.Data && fetchParams->fetched.Length) { /* Pass CRL back to caller & schedule dealloc after we return */ crlData = fetchParams->fetched; passDataToCaller(crlData, crl_data, crl_dataCnt); ocspdCrlDebug("ocsp_server_crlFetch got %lu bytes from net", crlData.Length); } } /* clean up allocations */ if(fetchParams->url.Data) { free(fetchParams->url.Data); } if(fetchParams->outFile) { free(fetchParams->outFile); } if(fetchParams->crlNames.crlFile) { free(fetchParams->crlNames.crlFile); } if(fetchParams->crlNames.pemFile) { free(fetchParams->crlNames.pemFile); } if(fetchParams->crlNames.updateFile) { free(fetchParams->crlNames.updateFile); } if(fetchParams->crlNames.revokedFile) { free(fetchParams->crlNames.revokedFile); } free(fetchParams); if(crlData.Data == NULL || crlData.Length == 0) { ocspdCrlDebug("ocsp_server_crlFetch will not cache (length=%lu, data=%p)", crlData.Length, crlData.Data); return crtn; } /* * 3. Add to cache if enabled */ if(cache_write) { crlCacheAdd(crlData, urlData); ocspdCrlDebug("ocsp_server_crlFetch added CRL to cache db"); } return 0; } kern_return_t ocsp_server_crlRefresh ( mach_port_t serverport, uint32_t stale_days, uint32_t expire_overlap_seconds, boolean_t purge_all, boolean_t full_crypto_verify) { /* preparing for possible CRL verify, requiring an RPC to ourself * for Trust Settings fetch. enable another thread. */ ServerActivity(); OcspdServer::active().longTermActivity(); crlCacheRefresh(stale_days, expire_overlap_seconds, purge_all, full_crypto_verify, true); return 0; } kern_return_t ocsp_server_crlFlush( mach_port_t serverport, Data cert_url, mach_msg_type_number_t cert_urlCnt) { ServerActivity(); CSSM_DATA urlData = {cert_urlCnt, (uint8 *)cert_url}; crlCacheFlush(urlData); return 0; } #pragma mark ----- MachServer::Timer subclass to handle periodic flushes of DB caches ----- #define OCSPD_REFRESH_DEBUG 0 #if !OCSPD_REFRESH_DEBUG /* fire a minute after we launch, then once a week */ #define OCSPD_TIMER_FIRST (60.0) #define OCSPD_TIMER_INTERVAL (60.0 * 60.0 * 24.0 * 7.0) #else #define OCSPD_TIMER_FIRST (10.0) #define OCSPD_TIMER_INTERVAL (60.0) #endif void OcspdServer::OcspdTimer::action() { secdebug("ocspdRefresh", "OcspdTimer firing"); ocspdDbCacheFlushStale(); crlCacheRefresh(0, // stale_days 0, // expire_overlap_seconds, false, // purge_all false, // full_crypto_verify false); // do Refresh Time::Interval nextFire = OCSPD_TIMER_INTERVAL; secdebug("ocspdRefresh", "OcspdTimer scheduling"); mServer.setTimer(this, nextFire); } #pragma mark ----- OcspdServer, trivial subclass of MachPlusPlus::MachServer ----- OcspdServer::OcspdServer(const char *bootstrapName) : MachServer(bootstrapName), mAlloc(Allocator::standard()), mTimer(*this) { maxThreads(MAX_OCSPD_THREADS); /* schedule a refresh */ Time::Interval nextFire = OCSPD_TIMER_FIRST; setTimer(&mTimer, nextFire); } OcspdServer::~OcspdServer() { } /* the boundary between MachServer and MIG-oriented code */ boolean_t ocspd_server(mach_msg_header_t *, mach_msg_header_t *); boolean_t OcspdServer::handle(mach_msg_header_t *in, mach_msg_header_t *out) { ocspdDebug("OcspdServer::handle msg_id %d", (int)in->msgh_id); return ocspd_server(in, out); }