]> git.saurik.com Git - apple/security.git/blobdiff - OSX/sec/securityd/SecRevocationServer.c
Security-58286.1.32.tar.gz
[apple/security.git] / OSX / sec / securityd / SecRevocationServer.c
diff --git a/OSX/sec/securityd/SecRevocationServer.c b/OSX/sec/securityd/SecRevocationServer.c
new file mode 100644 (file)
index 0000000..60abe20
--- /dev/null
@@ -0,0 +1,1096 @@
+/*
+ * Copyright (c) 2008-2017 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@
+ */
+
+/*
+ * SecRevocationServer.c - Engine for evaluating certificate revocation.
+ */
+
+#include <AssertMacros.h>
+
+#include <Security/SecCertificatePriv.h>
+#include <Security/SecCertificateInternal.h>
+#include <Security/SecPolicyPriv.h>
+#include <Security/SecTrustPriv.h>
+#include <Security/SecInternal.h>
+
+#include <utilities/debugging.h>
+#include <utilities/SecCFWrappers.h>
+#include <utilities/SecIOFormat.h>
+
+#include <securityd/SecTrustServer.h>
+#include <securityd/SecOCSPRequest.h>
+#include <securityd/SecOCSPResponse.h>
+#include <securityd/asynchttp.h>
+#include <securityd/SecOCSPCache.h>
+#include <securityd/SecRevocationDb.h>
+#include <securityd/SecCertificateServer.h>
+
+#include <securityd/SecRevocationServer.h>
+
+// MARK: SecORVCRef
+/********************************************************
+ ****************** OCSP RVC Functions ******************
+ ********************************************************/
+const CFAbsoluteTime kSecDefaultOCSPResponseTTL = 24.0 * 60.0 * 60.0;
+const CFAbsoluteTime kSecOCSPResponseOnlineTTL = 5.0 * 60.0;
+#define OCSP_RESPONSE_TIMEOUT       (3 * NSEC_PER_SEC)
+
+/* OCSP Revocation verification context. */
+struct OpaqueSecORVC {
+    /* Will contain the response data. */
+    asynchttp_t http;
+
+    /* Pointer to the builder for this revocation check. */
+    SecPathBuilderRef builder;
+
+    /* Pointer to the generic rvc for this revocation check */
+    SecRVCRef rvc;
+
+    /* The ocsp request we send to each responder. */
+    SecOCSPRequestRef ocspRequest;
+
+    /* The freshest response we received so far, from stapling or cache or responder. */
+    SecOCSPResponseRef ocspResponse;
+
+    /* The best validated candidate single response we received so far, from stapling or cache or responder. */
+    SecOCSPSingleResponseRef ocspSingleResponse;
+
+    /* Index of cert in builder that this RVC is for 0 = leaf, etc. */
+    CFIndex certIX;
+
+    /* Index in array returned by SecCertificateGetOCSPResponders() for current
+     responder. */
+    CFIndex responderIX;
+
+    /* URL of current responder. */
+    CFURLRef responder;
+
+    /* Date until which this revocation status is valid. */
+    CFAbsoluteTime nextUpdate;
+
+    bool done;
+};
+
+static void SecORVCFinish(SecORVCRef orvc) {
+    secdebug("alloc", "%p", orvc);
+    asynchttp_free(&orvc->http);
+    if (orvc->ocspRequest) {
+        SecOCSPRequestFinalize(orvc->ocspRequest);
+        orvc->ocspRequest = NULL;
+    }
+    if (orvc->ocspResponse) {
+        SecOCSPResponseFinalize(orvc->ocspResponse);
+        orvc->ocspResponse = NULL;
+        if (orvc->ocspSingleResponse) {
+            SecOCSPSingleResponseDestroy(orvc->ocspSingleResponse);
+            orvc->ocspSingleResponse = NULL;
+        }
+    }
+}
+
+#define MAX_OCSP_RESPONDERS 3
+#define OCSP_REQUEST_THRESHOLD 10
+
+/* Return the next responder we should contact for this rvc or NULL if we
+ exhausted them all. */
+static CFURLRef SecORVCGetNextResponder(SecORVCRef rvc) {
+    SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+    CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert);
+    if (ocspResponders) {
+        CFIndex responderCount = CFArrayGetCount(ocspResponders);
+        if (responderCount >= OCSP_REQUEST_THRESHOLD) {
+            secnotice("rvc", "too many ocsp responders (%ld)", (long)responderCount);
+            return NULL;
+        }
+        while (rvc->responderIX < responderCount && rvc->responderIX < MAX_OCSP_RESPONDERS) {
+            CFURLRef responder = CFArrayGetValueAtIndex(ocspResponders, rvc->responderIX);
+            rvc->responderIX++;
+            CFStringRef scheme = CFURLCopyScheme(responder);
+            if (scheme) {
+                /* We only support http and https responders currently. */
+                bool valid_responder = (CFEqual(CFSTR("http"), scheme) ||
+                                        CFEqual(CFSTR("https"), scheme));
+                CFRelease(scheme);
+                if (valid_responder)
+                    return responder;
+            }
+        }
+    }
+    return NULL;
+}
+
+/* Fire off an async http request for this certs revocation status, return
+ false if request was queued, true if we're done. */
+static bool SecORVCFetchNext(SecORVCRef rvc) {
+    while ((rvc->responder = SecORVCGetNextResponder(rvc))) {
+        CFDataRef request = SecOCSPRequestGetDER(rvc->ocspRequest);
+        if (!request)
+            goto errOut;
+
+        secinfo("rvc", "Sending http ocsp request for cert %ld", rvc->certIX);
+        if (!asyncHttpPost(rvc->responder, request, OCSP_RESPONSE_TIMEOUT, &rvc->http)) {
+            /* Async request was posted, wait for reply. */
+            return false;
+        }
+    }
+
+errOut:
+    rvc->done = true;
+    return true;
+}
+
+/* Process a verified ocsp response for a given cert. Return true if the
+ certificate status was obtained. */
+static bool SecOCSPSingleResponseProcess(SecOCSPSingleResponseRef this,
+                                         SecORVCRef rvc) {
+    bool processed;
+    switch (this->certStatus) {
+        case CS_Good:
+            secdebug("ocsp", "CS_Good for cert %" PRIdCFIndex, rvc->certIX);
+            /* @@@ Mark cert as valid until a given date (nextUpdate if we have one)
+             in the info dictionary. */
+            //cert.revokeCheckGood(true);
+            rvc->nextUpdate = this->nextUpdate == NULL_TIME ? this->thisUpdate + kSecDefaultOCSPResponseTTL : this->nextUpdate;
+            processed = true;
+            break;
+        case CS_Revoked:
+            secdebug("ocsp", "CS_Revoked for cert %" PRIdCFIndex, rvc->certIX);
+            /* @@@ Mark cert as revoked (with reason) at revocation date in
+             the info dictionary, or perhaps we should use a different key per
+             reason?   That way a client using exceptions can ignore some but
+             not all reasons. */
+            SInt32 reason = this->crlReason;
+            CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
+            SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
+                                  cfreason, true, kSecTrustResultFatalTrustFailure);
+            if (rvc->builder) {
+                CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
+                if (info) {
+                    /* make the revocation reason available in the trust result */
+                    CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason);
+                }
+            }
+            CFRelease(cfreason);
+            processed = true;
+            break;
+        case CS_Unknown:
+            /* not an error, no per-cert status, nothing here */
+            secdebug("ocsp", "CS_Unknown for cert %" PRIdCFIndex, rvc->certIX);
+            processed = false;
+            break;
+        default:
+            secnotice("ocsp", "BAD certStatus (%d) for cert %" PRIdCFIndex,
+                      (int)this->certStatus, rvc->certIX);
+            processed = false;
+            break;
+    }
+
+    return processed;
+}
+
+static void SecORVCUpdatePVC(SecORVCRef rvc) {
+    if (rvc->ocspSingleResponse) {
+        SecOCSPSingleResponseProcess(rvc->ocspSingleResponse, rvc);
+    }
+    if (rvc->ocspResponse) {
+        rvc->nextUpdate = SecOCSPResponseGetExpirationTime(rvc->ocspResponse);
+    }
+}
+
+typedef void (^SecOCSPEvaluationCompleted)(SecTrustResultType tr);
+
+static void
+SecOCSPEvaluateCompleted(const void *userData,
+                         SecCertificatePathRef chain, CFArrayRef details, CFDictionaryRef info,
+                         SecTrustResultType result) {
+    SecOCSPEvaluationCompleted evaluated = (SecOCSPEvaluationCompleted)userData;
+    evaluated(result);
+    Block_release(evaluated);
+
+}
+
+static bool SecOCSPResponseEvaluateSigner(SecORVCRef rvc, CFArrayRef signers, CFArrayRef issuers, CFAbsoluteTime verifyTime) {
+    __block bool evaluated = false;
+    bool trusted = false;
+    if (!signers || !issuers) {
+        return trusted;
+    }
+
+    /* Verify the signer chain against the OCSPSigner policy, using the issuer chain as anchors. */
+    const void *ocspSigner = SecPolicyCreateOCSPSigner();
+    CFArrayRef policies = CFArrayCreate(kCFAllocatorDefault,
+                                        &ocspSigner, 1, &kCFTypeArrayCallBacks);
+    CFRelease(ocspSigner);
+
+    SecOCSPEvaluationCompleted completed = Block_copy(^(SecTrustResultType result) {
+        if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
+            evaluated = true;
+        }
+    });
+
+    CFDataRef clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder);
+    SecPathBuilderRef oBuilder = SecPathBuilderCreate(clientAuditToken,
+                                                      signers, issuers, true, false,
+                                                      policies, NULL, NULL,  NULL,
+                                                      verifyTime, NULL, NULL,
+                                                      SecOCSPEvaluateCompleted, completed);
+    /* Build the chain(s), evaluate them, call the completed block, free the block and builder */
+    SecPathBuilderStep(oBuilder);
+    CFReleaseNull(clientAuditToken);
+    CFReleaseNull(policies);
+
+    /* verify the public key of the issuer signed the OCSP signer */
+    if (evaluated) {
+        SecCertificateRef issuer = NULL, signer = NULL;
+        SecKeyRef issuerPubKey = NULL;
+
+        issuer = (SecCertificateRef)CFArrayGetValueAtIndex(issuers, 0);
+        signer = (SecCertificateRef)CFArrayGetValueAtIndex(signers, 0);
+
+        if (issuer) {
+#if TARGET_OS_IPHONE
+            issuerPubKey = SecCertificateCopyPublicKey(issuer);
+#else
+            issuerPubKey = SecCertificateCopyPublicKey_ios(issuer);
+#endif
+        }
+        if (signer && issuerPubKey && (errSecSuccess == SecCertificateIsSignedBy(signer, issuerPubKey))) {
+            trusted = true;
+        } else {
+            secnotice("ocsp", "ocsp signer cert not signed by issuer");
+        }
+        CFReleaseNull(issuerPubKey);
+    }
+
+    return trusted;
+}
+
+static bool SecOCSPResponseVerify(SecOCSPResponseRef ocspResponse, SecORVCRef rvc, CFAbsoluteTime verifyTime) {
+    bool trusted;
+    SecCertificatePathVCRef issuers = SecCertificatePathVCCopyFromParent(SecPathBuilderGetPath(rvc->builder), rvc->certIX + 1);
+    SecCertificateRef issuer = issuers ? CFRetainSafe(SecCertificatePathVCGetCertificateAtIndex(issuers, 0)) : NULL;
+    CFArrayRef signers = SecOCSPResponseCopySigners(ocspResponse);
+    SecCertificateRef signer = SecOCSPResponseCopySigner(ocspResponse, issuer);
+
+    if (signer && signers) {
+        if (issuer && CFEqual(signer, issuer)) {
+            /* We already know we trust issuer since it's the issuer of the
+             * cert we are verifying. */
+            secinfo("ocsp", "ocsp responder: %@ response signed by issuer",
+                    rvc->responder);
+            trusted = true;
+        } else {
+            secinfo("ocsp", "ocsp responder: %@ response signed by cert issued by issuer",
+                    rvc->responder);
+            CFMutableArrayRef signerCerts = NULL;
+            CFArrayRef issuerCerts = NULL;
+
+            /* Ensure the signer cert is the 0th cert for trust evaluation */
+            signerCerts = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+            CFArrayAppendValue(signerCerts, signer);
+            CFArrayAppendArray(signerCerts, signers, CFRangeMake(0, CFArrayGetCount(signers)));
+
+            if (issuers) {
+                issuerCerts = SecCertificatePathVCCopyCertificates(issuers);
+            }
+
+            if (SecOCSPResponseEvaluateSigner(rvc, signerCerts, issuerCerts, verifyTime)) {
+                secdebug("ocsp", "response satisfies ocspSigner policy (%@)",
+                         rvc->responder);
+                trusted = true;
+            } else {
+                /* @@@ We don't trust the cert so don't use this response. */
+                secnotice("ocsp", "ocsp response signed by certificate which "
+                          "does not satisfy ocspSigner policy");
+                trusted = false;
+            }
+            CFReleaseNull(signerCerts);
+            CFReleaseNull(issuerCerts);
+        }
+    } else {
+        /* @@@ No signer found for this ocsp response, discard it. */
+        secnotice("ocsp", "ocsp responder: %@ no signer found for response",
+                  rvc->responder);
+        trusted = false;
+    }
+
+#if DUMP_OCSPRESPONSES
+    char buf[40];
+    snprintf(buf, 40, "/tmp/ocspresponse%ld%s.der",
+             rvc->certIX, (trusted ? "t" : "u"));
+    secdumpdata(ocspResponse->data, buf);
+#endif
+    CFReleaseNull(issuers);
+    CFReleaseNull(issuer);
+    CFReleaseNull(signers);
+    CFReleaseNull(signer);
+    return trusted;
+}
+
+static void SecORVCConsumeOCSPResponse(SecORVCRef rvc, SecOCSPResponseRef ocspResponse /*CF_CONSUMED*/, CFTimeInterval maxAge, bool updateCache) {
+    SecOCSPSingleResponseRef sr = NULL;
+    require_quiet(ocspResponse, errOut);
+    SecOCSPResponseStatus orStatus = SecOCSPGetResponseStatus(ocspResponse);
+    require_action_quiet(orStatus == kSecOCSPSuccess, errOut,
+                         secnotice("ocsp", "responder: %@ returned status: %d",  rvc->responder, orStatus));
+    require_action_quiet(sr = SecOCSPResponseCopySingleResponse(ocspResponse, rvc->ocspRequest), errOut,
+                         secnotice("ocsp",  "ocsp responder: %@ did not include status of requested cert", rvc->responder));
+    // Check if this response is fresher than any (cached) response we might still have in the rvc.
+    require_quiet(!rvc->ocspSingleResponse || rvc->ocspSingleResponse->thisUpdate < sr->thisUpdate, errOut);
+
+    CFAbsoluteTime verifyTime = CFAbsoluteTimeGetCurrent();
+    /* TODO: If the responder doesn't have the ocsp-nocheck extension we should
+     check whether the leaf was revoked (we are already checking the rest of
+     the chain). */
+    /* Check the OCSP response signature and verify the response. */
+    require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
+                                        sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
+
+    // If we get here, we have a properly signed ocsp response
+    // but we haven't checked dates yet.
+
+    bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime);
+    if (sr->certStatus == CS_Good) {
+        // Side effect of SecOCSPResponseCalculateValidity sets ocspResponse->expireTime
+        require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut);
+    } else if (sr->certStatus == CS_Revoked) {
+        // Expire revoked responses when the subject certificate itself expires.
+        ocspResponse->expireTime = SecCertificateNotValidAfter(SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX));
+    }
+
+    // Ok we like the new response, let's toss the old one.
+    if (updateCache)
+        SecOCSPCacheReplaceResponse(rvc->ocspResponse, ocspResponse, rvc->responder, verifyTime);
+
+    if (rvc->ocspResponse) SecOCSPResponseFinalize(rvc->ocspResponse);
+    rvc->ocspResponse = ocspResponse;
+    ocspResponse = NULL;
+
+    if (rvc->ocspSingleResponse) SecOCSPSingleResponseDestroy(rvc->ocspSingleResponse);
+    rvc->ocspSingleResponse = sr;
+    sr = NULL;
+
+    rvc->done = sr_valid;
+
+errOut:
+    if (sr) SecOCSPSingleResponseDestroy(sr);
+    if (ocspResponse) SecOCSPResponseFinalize(ocspResponse);
+}
+
+/* Callback from async http code after an ocsp response has been received. */
+static void SecOCSPFetchCompleted(asynchttp_t *http, CFTimeInterval maxAge) {
+    SecORVCRef rvc = (SecORVCRef)http->info;
+    SecPathBuilderRef builder = rvc->builder;
+    SecOCSPResponseRef ocspResponse = NULL;
+    if (http->response) {
+        CFDataRef data = CFHTTPMessageCopyBody(http->response);
+        if (data) {
+            /* Parse the returned data as if it's an ocspResponse. */
+            ocspResponse = SecOCSPResponseCreate(data);
+            CFRelease(data);
+        }
+    }
+
+    SecORVCConsumeOCSPResponse(rvc, ocspResponse, maxAge, true);
+    // TODO: maybe we should set the cache-control: false in the http header and try again if the response is stale
+
+    if (!rvc->done) {
+        /* Clear the data for the next response. */
+        asynchttp_free(http);
+        SecORVCFetchNext(rvc);
+    }
+
+    if (rvc->done) {
+        secdebug("rvc", "got OCSP response for cert: %ld", rvc->certIX);
+        SecORVCUpdatePVC(rvc);
+        if (!SecPathBuilderDecrementAsyncJobCount(builder)) {
+            secdebug("rvc", "done with all async jobs");
+            SecPathBuilderStep(builder);
+        }
+    }
+}
+
+static SecORVCRef SecORVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
+    SecORVCRef orvc = NULL;
+    orvc = malloc(sizeof(struct OpaqueSecORVC));
+    if (orvc) {
+        memset(orvc, 0, sizeof(struct OpaqueSecORVC));
+        orvc->builder = builder;
+        orvc->rvc = rvc;
+        orvc->certIX = certIX;
+        orvc->http.queue = SecPathBuilderGetQueue(builder);
+        orvc->http.token = SecPathBuilderCopyClientAuditToken(builder);
+        orvc->http.completed = SecOCSPFetchCompleted;
+        orvc->http.info = orvc;
+        orvc->ocspRequest = NULL;
+        orvc->responderIX = 0;
+        orvc->responder = NULL;
+        orvc->nextUpdate = NULL_TIME;
+        orvc->ocspResponse = NULL;
+        orvc->ocspSingleResponse = NULL;
+        orvc->done = false;
+
+        SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(builder, certIX);
+        if (SecPathBuilderGetCertificateCount(builder) > (certIX + 1)) {
+            SecCertificateRef issuer = SecPathBuilderGetCertificateAtIndex(builder, certIX + 1);
+            orvc->ocspRequest = SecOCSPRequestCreate(cert, issuer);
+        }
+    }
+    return orvc;
+}
+
+static void SecORVCProcessStapledResponses(SecORVCRef rvc) {
+    /* Get stapled OCSP responses */
+    CFArrayRef ocspResponsesData = SecPathBuilderCopyOCSPResponses(rvc->builder);
+
+    if(ocspResponsesData) {
+        secdebug("rvc", "Checking stapled responses for cert %ld", rvc->certIX);
+        CFArrayForEach(ocspResponsesData, ^(const void *value) {
+            SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value);
+            SecORVCConsumeOCSPResponse(rvc, ocspResponse, NULL_TIME, false);
+        });
+        CFRelease(ocspResponsesData);
+    }
+}
+
+// MARK: SecCRVCRef
+/********************************************************
+ ******************* CRL RVC Functions ******************
+ ********************************************************/
+#if ENABLE_CRLS
+#include <../trustd/macOS/SecTrustOSXEntryPoints.h>
+#define kSecDefaultCRLTTL kSecDefaultOCSPResponseTTL
+
+/* CRL Revocation verification context. */
+struct OpaqueSecCRVC {
+    /* Response data from ocspd. Yes, ocspd does CRLs, but not OCSP... */
+    async_ocspd_t async_ocspd;
+
+    /* Pointer to the builder for this revocation check. */
+    SecPathBuilderRef builder;
+
+    /* Pointer to the generic rvc for this revocation check */
+    SecRVCRef rvc;
+
+    /* The current CRL status from ocspd. */
+    OSStatus status;
+
+    /* Index of cert in builder that this RVC is for 0 = leaf, etc. */
+    CFIndex certIX;
+
+    /* Index in array returned by SecCertificateGetCRLDistributionPoints() for
+     current distribution point. */
+    CFIndex distributionPointIX;
+
+    /* URL of current distribution point. */
+    CFURLRef distributionPoint;
+
+    /* Date until which this revocation status is valid. */
+    CFAbsoluteTime nextUpdate;
+
+    bool done;
+};
+
+static void SecCRVCFinish(SecCRVCRef crvc) {
+    // nothing yet
+}
+
+#define MAX_CRL_DPS 3
+#define CRL_REQUEST_THRESHOLD 10
+
+static CFURLRef SecCRVCGetNextDistributionPoint(SecCRVCRef rvc) {
+    SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+    CFArrayRef crlDPs = SecCertificateGetCRLDistributionPoints(cert);
+    if (crlDPs) {
+        CFIndex crlDPCount = CFArrayGetCount(crlDPs);
+        if (crlDPCount >= CRL_REQUEST_THRESHOLD) {
+            secnotice("rvc", "too many CRL DP entries (%ld)", (long)crlDPCount);
+            return NULL;
+        }
+        while (rvc->distributionPointIX < crlDPCount && rvc->distributionPointIX < MAX_CRL_DPS) {
+            CFURLRef distributionPoint = CFArrayGetValueAtIndex(crlDPs, rvc->distributionPointIX);
+            rvc->distributionPointIX++;
+            CFStringRef scheme = CFURLCopyScheme(distributionPoint);
+            if (scheme) {
+                /* We only support http and https responders currently. */
+                bool valid_DP = (CFEqual(CFSTR("http"), scheme) ||
+                                 CFEqual(CFSTR("https"), scheme) ||
+                                 CFEqual(CFSTR("ldap"), scheme));
+                CFRelease(scheme);
+                if (valid_DP)
+                    return distributionPoint;
+            }
+        }
+    }
+    return NULL;
+}
+
+static void SecCRVCGetCRLStatus(SecCRVCRef rvc) {
+    SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+    SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
+    SecCertificatePathRef nonVCpath = SecCertificatePathVCCopyCertificatePath(path);
+    CFArrayRef serializedCertPath = SecCertificatePathCreateSerialized(nonVCpath, NULL);
+    CFReleaseNull(nonVCpath);
+    secdebug("rvc", "searching CRL cache for cert: %ld", rvc->certIX);
+    rvc->status = SecTrustLegacyCRLStatus(cert, serializedCertPath, rvc->distributionPoint);
+    CFReleaseNull(serializedCertPath);
+    /* we got a response indicating that the CRL was checked */
+    if (rvc->status == errSecSuccess || rvc->status == errSecCertificateRevoked) {
+        rvc->done = true;
+        /* ocspd doesn't give us the nextUpdate time, so set to default */
+        rvc->nextUpdate = SecPathBuilderGetVerifyTime(rvc->builder) + kSecDefaultCRLTTL;
+    }
+}
+
+static void SecCRVCCheckRevocationCache(SecCRVCRef rvc) {
+    while ((rvc->distributionPoint = SecCRVCGetNextDistributionPoint(rvc))) {
+        SecCRVCGetCRLStatus(rvc);
+        if (rvc->status == errSecCertificateRevoked) {
+            return;
+        }
+    }
+}
+
+/* Fire off an async http request for this certs revocation status, return
+ false if request was queued, true if we're done. */
+static bool SecCRVCFetchNext(SecCRVCRef rvc) {
+    while ((rvc->distributionPoint = SecCRVCGetNextDistributionPoint(rvc))) {
+        SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+        SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
+        SecCertificatePathRef nonVCpath = SecCertificatePathVCCopyCertificatePath(path);
+        CFArrayRef serializedCertPath = SecCertificatePathCreateSerialized(nonVCpath, NULL);
+        CFReleaseNull(nonVCpath);
+        secinfo("rvc", "fetching CRL for cert: %ld", rvc->certIX);
+        if (!SecTrustLegacyCRLFetch(&rvc->async_ocspd, rvc->distributionPoint,
+                                    CFAbsoluteTimeGetCurrent(), cert, serializedCertPath)) {
+            CFDataRef clientAuditToken = NULL;
+            SecTaskRef task = NULL;
+            audit_token_t auditToken = {};
+            clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder);
+            require(clientAuditToken, out);
+            require(sizeof(auditToken) == CFDataGetLength(clientAuditToken), out);
+            CFDataGetBytes(clientAuditToken, CFRangeMake(0, sizeof(auditToken)), (uint8_t *)&auditToken);
+            require(task = SecTaskCreateWithAuditToken(NULL, auditToken), out);
+            secnotice("rvc", "asynchronously fetching CRL (%@) for client (%@)",
+                      rvc->distributionPoint, task);
+
+        out:
+            CFReleaseNull(clientAuditToken);
+            CFReleaseNull(task);
+            /* Async request was posted, wait for reply. */
+            return false;
+        }
+    }
+    rvc->done = true;
+    return true;
+}
+
+static void SecCRVCUpdatePVC(SecCRVCRef rvc) {
+    if (rvc->status == errSecCertificateRevoked) {
+        secdebug("rvc", "CRL revoked cert %" PRIdCFIndex, rvc->certIX);
+        SInt32 reason = 0; // unspecified, since ocspd didn't tell us
+        CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
+        SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
+                              cfreason, true, kSecTrustResultFatalTrustFailure);
+        if (rvc->builder) {
+            CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
+            if (info) {
+                /* make the revocation reason available in the trust result */
+                CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason);
+            }
+        }
+        CFReleaseNull(cfreason);
+    }
+}
+
+static void SecCRVCFetchCompleted(async_ocspd_t *ocspd) {
+    SecCRVCRef rvc = ocspd->info;
+    SecPathBuilderRef builder = rvc->builder;
+    /* we got a response indicating that the CRL was checked */
+    if (ocspd->response == errSecSuccess || ocspd->response == errSecCertificateRevoked) {
+        rvc->status = ocspd->response;
+        rvc->done = true;
+        /* ocspd doesn't give us the nextUpdate time, so set to default */
+        rvc->nextUpdate = SecPathBuilderGetVerifyTime(rvc->builder) + kSecDefaultCRLTTL;
+        secdebug("rvc", "got CRL response for cert: %ld", rvc->certIX);
+        SecCRVCUpdatePVC(rvc);
+        if (!SecPathBuilderDecrementAsyncJobCount(builder)) {
+            secdebug("rvc", "done with all async jobs");
+            SecPathBuilderStep(builder);
+        }
+    } else {
+        if(SecCRVCFetchNext(rvc)) {
+            if (!SecPathBuilderDecrementAsyncJobCount(builder)) {
+                secdebug("rvc", "done with all async jobs");
+                SecPathBuilderStep(builder);
+            }
+        }
+    }
+}
+
+static SecCRVCRef SecCRVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
+    SecCRVCRef crvc = NULL;
+    crvc = malloc(sizeof(struct OpaqueSecCRVC));
+    if (crvc) {
+        memset(crvc, 0, sizeof(struct OpaqueSecCRVC));
+        crvc->builder = builder;
+        crvc->rvc = rvc;
+        crvc->certIX = certIX;
+        crvc->status = errSecInternal;
+        crvc->distributionPointIX = 0;
+        crvc->distributionPoint = NULL;
+        crvc->nextUpdate = NULL_TIME;
+        crvc->async_ocspd.queue = SecPathBuilderGetQueue(builder);
+        crvc->async_ocspd.completed = SecCRVCFetchCompleted;
+        crvc->async_ocspd.response = errSecInternal;
+        crvc->async_ocspd.info = crvc;
+        crvc->done = false;
+    }
+    return crvc;
+}
+
+static bool SecRVCShouldCheckCRL(SecRVCRef rvc) {
+    CFStringRef revocation_method = SecPathBuilderGetRevocationMethod(rvc->builder);
+    if (revocation_method &&
+        CFEqual(kSecPolicyCheckRevocationCRL, revocation_method)) {
+        /* Our client insists on CRLs */
+        secinfo("rvc", "client told us to check CRL");
+        return true;
+    }
+    SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+    CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert);
+    if ((!ocspResponders || CFArrayGetCount(ocspResponders) == 0) &&
+        (revocation_method && !CFEqual(kSecPolicyCheckRevocationOCSP, revocation_method))) {
+        /* The cert doesn't have OCSP responders and the client didn't specifically ask for OCSP.
+         * This logic will skip the CRL cache check if the client didn't ask for revocation checking */
+        secinfo("rvc", "client told us to check revocation and CRL is only option for cert: %ld", rvc->certIX);
+        return true;
+    }
+    return false;
+}
+#endif /* ENABLE_CRLS */
+
+void SecRVCDelete(SecRVCRef rvc) {
+    if (rvc->orvc) {
+        SecORVCFinish(rvc->orvc);
+        free(rvc->orvc);
+    }
+#if ENABLE_CRLS
+    if (rvc->crvc) {
+        SecCRVCFinish(rvc->crvc);
+        free(rvc->crvc);
+    }
+#endif
+    if (rvc->valid_info) {
+        SecValidInfoRelease(rvc->valid_info);
+    }
+}
+
+static void SecRVCInit(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
+    secdebug("alloc", "%p", rvc);
+    rvc->builder = builder;
+    rvc->certIX = certIX;
+    rvc->orvc = SecORVCCreate(rvc, builder, certIX);
+#if ENABLE_CRLS
+    rvc->crvc = SecCRVCCreate(rvc, builder, certIX);
+#endif
+    rvc->done = false;
+}
+
+#if ENABLE_CRLS
+static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) {
+    CFStringRef revocation_method = SecPathBuilderGetRevocationMethod(rvc->builder);
+    if (!revocation_method
+        || !CFEqual(revocation_method, kSecPolicyCheckRevocationCRL)) {
+        return true;
+    }
+    return false;
+}
+#else
+static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) {
+    return true;
+}
+#endif
+
+static void SecRVCProcessValidInfoResults(SecRVCRef rvc) {
+    if (!rvc || !rvc->valid_info || !rvc->builder) {
+        return;
+    }
+    SecValidInfoFormat format = rvc->valid_info->format;
+    bool valid = rvc->valid_info->valid;
+    bool noCACheck = rvc->valid_info->noCACheck;
+    bool checkOCSP = rvc->valid_info->checkOCSP;
+    bool complete = rvc->valid_info->complete;
+    bool isOnList = rvc->valid_info->isOnList;
+    bool definitive = false;
+
+    if (format == kSecValidInfoFormatSerial || format == kSecValidInfoFormatSHA256) {
+        /* serial or hash list: could be blocked or allowed; could be incomplete */
+        if (((!valid && complete && isOnList) || (valid && complete && !isOnList)) && noCACheck) {
+            /* definitely revoked */
+            SInt32 reason = 0; /* unspecified, since the Valid db doesn't tell us */
+            CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
+            SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
+                                          cfreason, true, kSecTrustResultFatalTrustFailure);
+            CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
+            if (info) {
+                /* make the revocation reason available in the trust result */
+                CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason);
+            }
+            CFReleaseNull(cfreason);
+            definitive = true;
+        }
+        else if (valid && complete && isOnList && noCACheck) {
+            /* definitely not revoked (allowlisted) */
+            SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
+            if (path) {
+                SecCertificatePathVCSetIsAllowlisted(path, true);
+            } else {
+                secdebug("validupdate", "rvc: no certificate path for builder");
+            }
+            definitive = true;
+        }
+        if (definitive) {
+            /* either definitely revoked or allowed; no need to check further. */
+            secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex,
+                     (valid && complete && isOnList) ? "allowed" : "revoked", rvc->certIX);
+            rvc->done = true;
+            return;
+        }
+        /* verify our info with the OCSP server */
+        checkOCSP = true;
+    }
+
+    /* Handle non-definitive information.
+       We set rvc->done = true above ONLY if the result was definitive;
+       otherwise we require a revocation check for SSL usage.
+     */
+    if (format == kSecValidInfoFormatNto1) {
+        /* matched the filter */
+        checkOCSP = true;
+    }
+
+    if (checkOCSP) {
+        CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
+        CFIndex issuerIX = rvc->certIX + 1;
+        if (issuerIX >= count) {
+            /* cannot perform a revocation check on the last cert in the
+               chain, since we don't have its issuer. */
+            return;
+        }
+        CFIndex pvcIX;
+        for (pvcIX = 0; pvcIX < SecPathBuilderGetPVCCount(rvc->builder); pvcIX++) {
+            SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, pvcIX);
+            if (!pvc) { continue; }
+            SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, 0);
+            CFStringRef policyName = (policy) ? SecPolicyGetName(policy) : NULL;
+            if (policyName && CFEqual(CFSTR("sslServer"), policyName)) {
+                /* perform revocation check for SSL policy;
+                 require for leaf if an OCSP responder is present. */
+                if (0 == rvc->certIX) {
+                    SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+                    CFArrayRef resps = (cert) ? SecCertificateGetOCSPResponders(cert) : NULL;
+                    CFIndex rcount = (resps) ? CFArrayGetCount(resps) : 0;
+                    if (rcount > 0) {
+                        // %%% rdar://31279923
+                        // This currently requires a valid revocation response for each cert,
+                        // but we only want to require a leaf check. For now, do not require.
+                        //SecPathBuilderSetRevocationResponseRequired(rvc->builder);
+                    }
+                }
+                secdebug("validupdate", "rvc: %s%s cert %" PRIdCFIndex " (will check OCSP)",
+                         (complete) ? "" : "possibly ", (valid) ? "allowed" : "revoked",
+                         rvc->certIX);
+                SecPathBuilderSetRevocationMethod(rvc->builder, kSecPolicyCheckRevocationAny);
+            }
+        }
+    }
+}
+
+static bool SecRVCCheckValidInfoDatabase(SecRVCRef rvc) {
+    /* Skip checking for OCSP Signer verification */
+    if (SecPathBuilderGetPVCCount(rvc->builder) == 1) {
+        SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, 0);
+        if (!pvc) { return false; }
+        SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, 0);
+        CFStringRef policyName = (policy) ? SecPolicyGetName(policy) : NULL;
+        if (policyName && CFEqual(policyName, CFSTR("OCSPSigner"))) {
+            return false;
+        }
+    }
+
+    /* Make sure revocation db info is up-to-date.
+     * We don't care if the builder is allowed to access the network because
+     * the network fetching does not block the trust evaluation. */
+    SecRevocationDbCheckNextUpdate();
+
+    /* Check whether we have valid db info for this cert,
+     given the cert and its issuer */
+    SecValidInfoRef info = NULL;
+    CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
+    if (count) {
+        bool isSelfSigned = false;
+        SecCertificateRef cert = NULL;
+        SecCertificateRef issuer = NULL;
+        CFIndex issuerIX = rvc->certIX + 1;
+        if (count > issuerIX) {
+            issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, issuerIX);
+        } else if (count == issuerIX) {
+            CFIndex rootIX = SecCertificatePathVCSelfSignedIndex(SecPathBuilderGetPath(rvc->builder));
+            if (rootIX == rvc->certIX) {
+                issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, rootIX);
+                isSelfSigned = true;
+            }
+        }
+        cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
+        if (!isSelfSigned) {
+            /* skip revocation db check for self-signed certificates [33137065] */
+            info = SecRevocationDbCopyMatching(cert, issuer);
+        }
+        SecValidInfoSetAnchor(info, SecPathBuilderGetCertificateAtIndex(rvc->builder, count-1));
+    }
+    if (info) {
+        SecValidInfoRef old_info = rvc->valid_info;
+        rvc->valid_info = info;
+        if (old_info) {
+            SecValidInfoRelease(old_info);
+        }
+        return true;
+    }
+    return false;
+}
+
+static void SecRVCCheckRevocationCaches(SecRVCRef rvc) {
+    /* Don't check OCSP cache if CRLs enabled and policy requested CRL only */
+    if (SecRVCShouldCheckOCSP(rvc) && (rvc->orvc->ocspRequest)) {
+        secdebug("ocsp", "Checking cached responses for cert %ld", rvc->certIX);
+        SecOCSPResponseRef response = NULL;
+        if (SecPathBuilderGetCheckRevocationOnline(rvc->builder)) {
+            CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
+            response = SecOCSPCacheCopyMatchingWithMinInsertTime(rvc->orvc->ocspRequest, NULL, now - kSecOCSPResponseOnlineTTL);
+        } else {
+            response = SecOCSPCacheCopyMatching(rvc->orvc->ocspRequest, NULL);
+        }
+        SecORVCConsumeOCSPResponse(rvc->orvc,
+                                   response,
+                                   NULL_TIME, false);
+    }
+#if ENABLE_CRLS
+    /* Don't check CRL cache if policy requested OCSP only */
+    if (SecRVCShouldCheckCRL(rvc)) {
+        SecCRVCCheckRevocationCache(rvc->crvc);
+    }
+#endif
+}
+
+static void SecRVCUpdatePVC(SecRVCRef rvc) {
+    SecRVCProcessValidInfoResults(rvc); /* restore the results we got from Valid */
+    SecORVCUpdatePVC(rvc->orvc);
+#if ENABLE_CRLS
+    SecCRVCUpdatePVC(rvc->crvc);
+#endif
+}
+
+static bool SecRVCFetchNext(SecRVCRef rvc) {
+    bool OCSP_fetch_finished = true;
+    /* Don't send OCSP request only if CRLs enabled and policy requested CRL only */
+    if (SecRVCShouldCheckOCSP(rvc)) {
+        OCSP_fetch_finished &= SecORVCFetchNext(rvc->orvc);
+    }
+    if (OCSP_fetch_finished) {
+        /* we didn't start an OCSP background job for this cert */
+        (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
+    }
+
+#if ENABLE_CRLS
+    bool CRL_fetch_finished = true;
+    /* Don't check CRL cache if policy requested OCSP only */
+    if (SecRVCShouldCheckCRL(rvc)) {
+        /* reset the distributionPointIX because we already iterated through the CRLDPs
+         * in SecCRVCCheckRevocationCache */
+        rvc->crvc->distributionPointIX = 0;
+        CRL_fetch_finished &= SecCRVCFetchNext(rvc->crvc);
+    }
+    if (CRL_fetch_finished) {
+        /* we didn't start a CRL background job for this cert */
+        (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
+    }
+    OCSP_fetch_finished &= CRL_fetch_finished;
+#endif
+
+    return OCSP_fetch_finished;
+}
+
+bool SecPathBuilderCheckRevocation(SecPathBuilderRef builder) {
+    secdebug("rvc", "checking revocation");
+    CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder);
+    SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
+    bool completed = true;
+    if (certCount <= 1) {
+        /* Can't verify without an issuer; we're done */
+        return completed;
+    }
+
+    /*
+     * Don't need to call SecPVCIsAnchored; having an issuer is sufficient here.
+     *
+     * Note: we can't check revocation for the last certificate in the chain
+     * via OCSP or CRL methods, since there isn't a separate issuer cert to
+     * sign those responses. However, since a self-signed root has an implied
+     * issuer of itself, we can check for it in the valid database.
+     */
+
+    if (SecCertificatePathVCIsRevocationDone(path)) {
+        /* We have done revocation checking already, set PVCs with results. */
+        for (certIX = 0; certIX < certCount; ++certIX) {
+            SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
+            if (rvc) { SecRVCUpdatePVC(rvc); }
+        }
+        secdebug("rvc", "Not rechecking revocation");
+        return completed;
+    }
+
+    /* Setup things so we check revocation status of all certs. */
+    SecCertificatePathVCAllocateRVCs(path, certCount);
+
+    /* Note that if we are multi threaded and a job completes after it
+     is started but before we return from this function, we don't want
+     a callback to decrement asyncJobCount to zero before we finish issuing
+     all the jobs. To avoid this we pretend we issued certCount-1 async jobs,
+     and decrement pvc->asyncJobCount for each cert that we don't start a
+     background fetch for. (We will never start an async job for the final
+     cert in the chain.) */
+#if !ENABLE_CRLS
+    SecPathBuilderSetAsyncJobCount(builder, (unsigned int)(certCount-1));
+#else
+    /* If we enable CRLS, we may end up with two async jobs per cert: one
+     * for OCSP and one for fetching the CRL */
+    SecPathBuilderSetAsyncJobCount(builder, 2 * (unsigned int)(certCount-1));
+#endif
+
+    /* Loop though certificates again and issue an ocsp fetch if the
+     revocation status checking isn't done yet (and we have an issuer!) */
+    for (certIX = 0; certIX < certCount; ++certIX) {
+        secdebug("rvc", "checking revocation for cert: %ld", certIX);
+        SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
+        if (!rvc) {
+            continue;
+        }
+
+        SecRVCInit(rvc, builder, certIX);
+        if (rvc->done){
+            continue;
+        }
+
+#if !TARGET_OS_BRIDGE
+        /* Check valid database first (separate from OCSP response cache) */
+        if (SecRVCCheckValidInfoDatabase(rvc)) {
+            SecRVCProcessValidInfoResults(rvc);
+        }
+#endif
+        /* Any other revocation method requires an issuer certificate;
+         * skip the last cert in the chain since it doesn't have one. */
+        if (certIX+1 >= certCount) {
+            continue;
+        }
+
+        /* Ignore stapled OCSP responses only if CRLs are enabled and the
+         * policy specifically requested CRLs only. */
+        if (SecRVCShouldCheckOCSP(rvc)) {
+            /*  If we have any OCSP stapled responses, check those first */
+            SecORVCProcessStapledResponses(rvc->orvc);
+        }
+
+#if TARGET_OS_BRIDGE
+        /* The bridge has no writeable storage and no network. Nothing else we can
+         * do here. */
+        rvc->done = true;
+        return completed;
+#endif
+
+        /* Then check the caches for revocation results. */
+        SecRVCCheckRevocationCaches(rvc);
+
+        /* The check is done if we found cached responses from either method. */
+        if (rvc->orvc->done
+#if ENABLE_CRLS
+            || rvc->orvc->done
+#endif
+            ) {
+            secdebug("rvc", "found cached response for cert: %ld", certIX);
+            rvc->done = true;
+        }
+
+        /* If we got a cached response that is no longer valid (which can only be true for
+         * revoked responses), let's try to get a fresher response even if no one asked.
+         * This check resolves unrevocation events after the nextUpdate time. */
+        bool old_cached_response = (!rvc->done && rvc->orvc->ocspResponse);
+
+        /* If the cert is EV or if revocation checking was explicitly enabled, attempt to fire off an
+         async http request for this cert's revocation status, unless we already successfully checked
+         the revocation status of this cert based on the cache or stapled responses.  */
+        bool allow_fetch = SecPathBuilderCanAccessNetwork(builder) &&
+            (SecCertificatePathVCIsEV(path) || SecCertificatePathVCIsOptionallyEV(path) ||
+             SecPathBuilderGetRevocationMethod(builder) || old_cached_response);
+        bool fetch_done = true;
+        if (rvc->done || !allow_fetch) {
+            /* We got a cache hit or we aren't allowed to access the network */
+            SecRVCUpdatePVC(rvc);
+            /* We didn't really start any background jobs for this cert. */
+            (void)SecPathBuilderDecrementAsyncJobCount(builder);
+#if ENABLE_CRLS
+            (void)SecPathBuilderDecrementAsyncJobCount(builder);
+#endif
+        } else {
+            fetch_done = SecRVCFetchNext(rvc);
+        }
+        if (!fetch_done) {
+            /* We started at least one background fetch. */
+            secdebug("rvc", "waiting on background fetch for cert %ld", certIX);
+            completed = false;
+        }
+    }
+
+    /* Return false if we started any background jobs. */
+    /* We can't just return !builder->asyncJobCount here, since if we started any
+     jobs the completion callback will be called eventually and it will call
+     SecPathBuilderStep(). If for some reason everything completed before we
+     get here we still want the outer SecPathBuilderStep() to terminate so we
+     keep track of whether we started any jobs and return false if so. */
+    return completed;
+}
+
+CFAbsoluteTime SecRVCGetEarliestNextUpdate(SecRVCRef rvc) {
+    CFAbsoluteTime enu = NULL_TIME;
+    if (!rvc || !rvc->orvc) { return enu; }
+    enu = rvc->orvc->nextUpdate;
+#if ENABLE_CRLS
+    CFAbsoluteTime crlNextUpdate = rvc->crvc->nextUpdate;
+    if (enu == NULL_TIME ||
+        ((crlNextUpdate > NULL_TIME) && (enu > crlNextUpdate))) {
+        /* We didn't check OCSP or CRL next update time was sooner */
+        enu = crlNextUpdate;
+    }
+#endif
+    return enu;
+}