]> git.saurik.com Git - apple/security.git/blob - trust/trustd/SecRevocationServer.c
Security-59754.41.1.tar.gz
[apple/security.git] / trust / trustd / SecRevocationServer.c
1 /*
2 * Copyright (c) 2008-2019 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * SecRevocationServer.c - Engine for evaluating certificate revocation.
26 */
27
28 #include <AssertMacros.h>
29
30 #include <mach/mach_time.h>
31
32 #include <Security/SecCertificatePriv.h>
33 #include <Security/SecCertificateInternal.h>
34 #include <Security/SecPolicyPriv.h>
35 #include <Security/SecTrustPriv.h>
36 #include <Security/SecInternal.h>
37
38 #include <utilities/debugging.h>
39 #include <utilities/SecCFWrappers.h>
40 #include <utilities/SecIOFormat.h>
41
42 #include "trust/trustd/SecTrustServer.h"
43 #include "trust/trustd/SecOCSPRequest.h"
44 #include "trust/trustd/SecOCSPResponse.h"
45 #include "trust/trustd/SecOCSPCache.h"
46 #include "trust/trustd/SecRevocationDb.h"
47 #include "trust/trustd/SecCertificateServer.h"
48 #include "trust/trustd/SecPolicyServer.h"
49 #include "trust/trustd/SecRevocationNetworking.h"
50
51 #include "trust/trustd/SecRevocationServer.h"
52
53 // MARK: SecORVCRef
54 /********************************************************
55 ****************** OCSP RVC Functions ******************
56 ********************************************************/
57 const CFAbsoluteTime kSecDefaultOCSPResponseTTL = 24.0 * 60.0 * 60.0;
58 const CFAbsoluteTime kSecOCSPResponseOnlineTTL = 5.0 * 60.0;
59 #define OCSP_RESPONSE_TIMEOUT (3 * NSEC_PER_SEC)
60
61 static void SecORVCFinish(SecORVCRef orvc) {
62 secdebug("alloc", "finish orvc %p", orvc);
63 if (orvc->ocspRequest) {
64 SecOCSPRequestFinalize(orvc->ocspRequest);
65 orvc->ocspRequest = NULL;
66 }
67 if (orvc->ocspResponse) {
68 SecOCSPResponseFinalize(orvc->ocspResponse);
69 orvc->ocspResponse = NULL;
70 if (orvc->ocspSingleResponse) {
71 SecOCSPSingleResponseDestroy(orvc->ocspSingleResponse);
72 orvc->ocspSingleResponse = NULL;
73 }
74 }
75 memset(orvc, 0, sizeof(struct OpaqueSecORVC));
76 }
77
78 /* Process a verified ocsp response for a given cert. Return true if the
79 certificate status was obtained. */
80 static bool SecOCSPSingleResponseProcess(SecOCSPSingleResponseRef this,
81 SecORVCRef rvc) {
82 bool processed;
83 switch (this->certStatus) {
84 case CS_Good:
85 secdebug("ocsp", "CS_Good for cert %" PRIdCFIndex, rvc->certIX);
86 /* @@@ Mark cert as valid until a given date (nextUpdate if we have one)
87 in the info dictionary. */
88 //cert.revokeCheckGood(true);
89 rvc->nextUpdate = this->nextUpdate == NULL_TIME ? this->thisUpdate + kSecDefaultOCSPResponseTTL : this->nextUpdate;
90 processed = true;
91 break;
92 case CS_Revoked:
93 secdebug("ocsp", "CS_Revoked for cert %" PRIdCFIndex, rvc->certIX);
94 /* @@@ Mark cert as revoked (with reason) at revocation date in
95 the info dictionary, or perhaps we should use a different key per
96 reason? That way a client using exceptions can ignore some but
97 not all reasons. */
98 SInt32 reason = this->crlReason;
99 CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
100 SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
101 cfreason, true);
102 SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
103 if (path) {
104 SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(path, rvc->certIX, cfreason);
105 }
106 CFRelease(cfreason);
107 processed = true;
108 break;
109 case CS_Unknown:
110 /* not an error, no per-cert status, nothing here */
111 secdebug("ocsp", "CS_Unknown for cert %" PRIdCFIndex, rvc->certIX);
112 processed = false;
113 break;
114 default:
115 secnotice("ocsp", "BAD certStatus (%d) for cert %" PRIdCFIndex,
116 (int)this->certStatus, rvc->certIX);
117 processed = false;
118 break;
119 }
120
121 return processed;
122 }
123
124 void SecORVCUpdatePVC(SecORVCRef rvc) {
125 if (rvc->ocspSingleResponse) {
126 SecOCSPSingleResponseProcess(rvc->ocspSingleResponse, rvc);
127 }
128 if (rvc->ocspResponse) {
129 rvc->nextUpdate = SecOCSPResponseGetExpirationTime(rvc->ocspResponse);
130 }
131 }
132
133 typedef void (^SecOCSPEvaluationCompleted)(SecTrustResultType tr);
134
135 static void
136 SecOCSPEvaluateCompleted(const void *userData,
137 CFArrayRef chain, CFArrayRef details, CFDictionaryRef info,
138 SecTrustResultType result) {
139 SecOCSPEvaluationCompleted evaluated = (SecOCSPEvaluationCompleted)userData;
140 evaluated(result);
141 Block_release(evaluated);
142
143 }
144
145 static bool SecOCSPResponseEvaluateSigner(SecORVCRef rvc, CFArrayRef signers, CFArrayRef issuers, CFAbsoluteTime verifyTime) {
146 __block bool evaluated = false;
147 bool trusted = false;
148 if (!signers || !issuers) {
149 return trusted;
150 }
151
152 /* Verify the signer chain against the OCSPSigner policy, using the issuer chain as anchors. */
153 const void *ocspSigner = SecPolicyCreateOCSPSigner();
154 CFArrayRef policies = CFArrayCreate(kCFAllocatorDefault,
155 &ocspSigner, 1, &kCFTypeArrayCallBacks);
156 CFRelease(ocspSigner);
157
158 SecOCSPEvaluationCompleted completed = Block_copy(^(SecTrustResultType result) {
159 if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
160 evaluated = true;
161 }
162 });
163
164 CFDataRef clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder);
165 SecPathBuilderRef oBuilder = SecPathBuilderCreate(NULL, clientAuditToken,
166 signers, issuers, true, false,
167 policies, NULL, NULL, NULL,
168 verifyTime, NULL, NULL,
169 SecOCSPEvaluateCompleted, completed);
170 /* disable network access to avoid recursion */
171 SecPathBuilderSetCanAccessNetwork(oBuilder, false);
172
173 /* Build the chain(s), evaluate them, call the completed block, free the block and builder */
174 SecPathBuilderStep(oBuilder);
175 CFReleaseNull(clientAuditToken);
176 CFReleaseNull(policies);
177
178 /* verify the public key of the issuer signed the OCSP signer */
179 if (evaluated) {
180 SecCertificateRef issuer = NULL, signer = NULL;
181 SecKeyRef issuerPubKey = NULL;
182
183 issuer = (SecCertificateRef)CFArrayGetValueAtIndex(issuers, 0);
184 signer = (SecCertificateRef)CFArrayGetValueAtIndex(signers, 0);
185
186 if (issuer) {
187 issuerPubKey = SecCertificateCopyKey(issuer);
188 }
189 if (signer && issuerPubKey && (errSecSuccess == SecCertificateIsSignedBy(signer, issuerPubKey))) {
190 trusted = true;
191 } else {
192 secnotice("ocsp", "ocsp signer cert not signed by issuer");
193 }
194 CFReleaseNull(issuerPubKey);
195 }
196
197 return trusted;
198 }
199
200 static bool SecOCSPResponseVerify(SecOCSPResponseRef ocspResponse, SecORVCRef rvc, CFAbsoluteTime verifyTime) {
201 bool trusted;
202 SecCertificatePathVCRef issuers = SecCertificatePathVCCopyFromParent(SecPathBuilderGetPath(rvc->builder), rvc->certIX + 1);
203 SecCertificateRef issuer = issuers ? CFRetainSafe(SecCertificatePathVCGetCertificateAtIndex(issuers, 0)) : NULL;
204 CFArrayRef signers = SecOCSPResponseCopySigners(ocspResponse);
205 SecCertificateRef signer = SecOCSPResponseCopySigner(ocspResponse, issuer);
206
207 if (signer && signers) {
208 if (issuer && CFEqual(signer, issuer)) {
209 /* We already know we trust issuer since it's the issuer of the
210 * cert we are verifying. */
211 secinfo("ocsp", "ocsp responder: %@ response signed by issuer",
212 rvc->responder);
213 trusted = true;
214 } else {
215 secinfo("ocsp", "ocsp responder: %@ response signed by cert issued by issuer",
216 rvc->responder);
217 CFMutableArrayRef signerCerts = NULL;
218 CFArrayRef issuerCerts = NULL;
219
220 /* Ensure the signer cert is the 0th cert for trust evaluation */
221 signerCerts = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
222 CFArrayAppendValue(signerCerts, signer);
223 CFArrayAppendArray(signerCerts, signers, CFRangeMake(0, CFArrayGetCount(signers)));
224
225 if (issuers) {
226 issuerCerts = SecCertificatePathVCCopyCertificates(issuers);
227 }
228
229 if (SecOCSPResponseEvaluateSigner(rvc, signerCerts, issuerCerts, verifyTime)) {
230 secdebug("ocsp", "response satisfies ocspSigner policy (%@)",
231 rvc->responder);
232 trusted = true;
233 } else {
234 /* @@@ We don't trust the cert so don't use this response. */
235 secnotice("ocsp", "ocsp response signed by certificate which "
236 "does not satisfy ocspSigner policy");
237 trusted = false;
238 }
239 CFReleaseNull(signerCerts);
240 CFReleaseNull(issuerCerts);
241 }
242 } else {
243 /* @@@ No signer found for this ocsp response, discard it. */
244 secnotice("ocsp", "ocsp responder: %@ no signer found for response",
245 rvc->responder);
246 trusted = false;
247 }
248
249 #if DUMP_OCSPRESPONSES
250 char buf[40];
251 snprintf(buf, 40, "/tmp/ocspresponse%ld%s.der",
252 rvc->certIX, (trusted ? "t" : "u"));
253 secdumpdata(ocspResponse->data, buf);
254 #endif
255 CFReleaseNull(issuers);
256 CFReleaseNull(issuer);
257 CFReleaseNull(signers);
258 CFReleaseNull(signer);
259 return trusted;
260 }
261
262 void SecORVCConsumeOCSPResponse(SecORVCRef rvc, SecOCSPResponseRef ocspResponse /*CF_CONSUMED*/,
263 CFTimeInterval maxAge, bool updateCache, bool fromCache) {
264 SecOCSPSingleResponseRef sr = NULL;
265 require_quiet(ocspResponse, errOut);
266 SecOCSPResponseStatus orStatus = SecOCSPGetResponseStatus(ocspResponse);
267 require_action_quiet(orStatus == kSecOCSPSuccess, errOut,
268 secnotice("ocsp", "responder: %@ returned status: %d", rvc->responder, orStatus));
269 require_action_quiet(sr = SecOCSPResponseCopySingleResponse(ocspResponse, rvc->ocspRequest), errOut,
270 secnotice("ocsp", "ocsp responder: %@ did not include status of requested cert", rvc->responder));
271 // Check if this response is fresher than any (cached) response we might still have in the rvc.
272 require_quiet(!rvc->ocspSingleResponse || rvc->ocspSingleResponse->thisUpdate < sr->thisUpdate, errOut);
273
274 CFAbsoluteTime verifyTime = CFAbsoluteTimeGetCurrent();
275 #if TARGET_OS_IPHONE
276 /* Check the OCSP response signature and verify the response if not pulled from the cache.
277 * Performance optimization since we don't write invalid responses to the cache. */
278 if (!fromCache) {
279 require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
280 sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
281 }
282 #else
283 /* Always check the OCSP response signature and verify the response (since the cache is user-modifiable). */
284 require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
285 sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
286 #endif
287
288 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
289 if (analytics && SecOCSPResponseIsWeakHash(ocspResponse)) {
290 analytics->ocsp_weak_hash = true;
291 }
292
293 // If we get here, we have a properly signed ocsp response
294 // but we haven't checked dates yet.
295
296 bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime);
297 if (sr_valid) {
298 rvc->rvc->revocation_checked = true;
299 }
300 if (sr->certStatus == CS_Good) {
301 // Side effect of SecOCSPResponseCalculateValidity sets ocspResponse->expireTime
302 require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut);
303 } else if (sr->certStatus == CS_Revoked) {
304 // Expire revoked responses when the subject certificate itself expires.
305 ocspResponse->expireTime = SecCertificateNotValidAfter(SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX));
306 }
307
308 // Ok we like the new response, let's toss the old one.
309 if (updateCache)
310 SecOCSPCacheReplaceResponse(rvc->ocspResponse, ocspResponse, rvc->responder, verifyTime);
311
312 if (rvc->ocspResponse) SecOCSPResponseFinalize(rvc->ocspResponse);
313 rvc->ocspResponse = ocspResponse;
314 ocspResponse = NULL;
315
316 if (rvc->ocspSingleResponse) SecOCSPSingleResponseDestroy(rvc->ocspSingleResponse);
317 rvc->ocspSingleResponse = sr;
318 sr = NULL;
319
320 rvc->done = sr_valid;
321
322 errOut:
323 if (sr) SecOCSPSingleResponseDestroy(sr);
324 if (ocspResponse) SecOCSPResponseFinalize(ocspResponse);
325 }
326
327 static SecORVCRef SecORVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
328 SecORVCRef orvc = NULL;
329 orvc = malloc(sizeof(struct OpaqueSecORVC));
330 secdebug("alloc", "orvc %p", orvc);
331 if (orvc) {
332 memset(orvc, 0, sizeof(struct OpaqueSecORVC));
333 orvc->builder = builder;
334 orvc->rvc = rvc;
335 orvc->certIX = certIX;
336
337 SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(builder, certIX);
338 if (SecPathBuilderGetCertificateCount(builder) > (certIX + 1)) {
339 SecCertificateRef issuer = SecPathBuilderGetCertificateAtIndex(builder, certIX + 1);
340 orvc->ocspRequest = SecOCSPRequestCreate(cert, issuer);
341 }
342 }
343 return orvc;
344 }
345
346 static void SecORVCProcessStapledResponses(SecORVCRef rvc) {
347 /* Get stapled OCSP responses */
348 CFArrayRef ocspResponsesData = SecPathBuilderCopyOCSPResponses(rvc->builder);
349
350 if(ocspResponsesData) {
351 secdebug("rvc", "Checking stapled responses for cert %ld", rvc->certIX);
352 CFArrayForEach(ocspResponsesData, ^(const void *value) {
353 SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value);
354 SecORVCConsumeOCSPResponse(rvc, ocspResponse, NULL_TIME, false, false);
355 });
356 CFRelease(ocspResponsesData);
357 }
358 }
359
360 void SecRVCDelete(SecRVCRef rvc) {
361 secdebug("alloc", "delete rvc %p", rvc);
362 if (rvc->orvc) {
363 SecORVCFinish(rvc->orvc);
364 free(rvc->orvc);
365 rvc->orvc = NULL;
366 }
367 if (rvc->valid_info) {
368 CFReleaseNull(rvc->valid_info);
369 }
370 }
371
372 // Forward declaration
373 static void SecRVCSetFinishedWithoutNetwork(SecRVCRef rvc);
374
375 static void SecRVCInit(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
376 secdebug("alloc", "rvc %p", rvc);
377 rvc->builder = builder;
378 rvc->certIX = certIX;
379 rvc->orvc = SecORVCCreate(rvc, builder, certIX);
380 if (!rvc->orvc) {
381 SecRVCDelete(rvc);
382 SecRVCSetFinishedWithoutNetwork(rvc);
383 } else {
384 rvc->done = false;
385 }
386 }
387
388 static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) {
389 return true;
390 }
391
392 static bool SecRVCPolicyConstraintsPermitPolicy(SecValidPolicy *constraints, CFIndex count, SecPolicyRef policy) {
393 if (!constraints || !policy) {
394 return true; /* nothing to constrain */
395 }
396 SecValidPolicy policyType = kSecValidPolicyAny;
397 CFStringRef policyName = SecPolicyGetName(policy);
398 /* determine if the policy is a candidate for being constrained */
399 if (CFEqualSafe(policyName, kSecPolicyNameSSLServer) ||
400 CFEqualSafe(policyName, kSecPolicyNameEAPServer) ||
401 CFEqualSafe(policyName, kSecPolicyNameIPSecServer)) {
402 policyType = kSecValidPolicyServerAuthentication;
403 } else if (CFEqualSafe(policyName, kSecPolicyNameSSLClient) ||
404 CFEqualSafe(policyName, kSecPolicyNameEAPClient) ||
405 CFEqualSafe(policyName, kSecPolicyNameIPSecClient)) {
406 policyType = kSecValidPolicyClientAuthentication;
407 } else if (CFEqualSafe(policyName, kSecPolicyNameSMIME)) {
408 policyType = kSecValidPolicyEmailProtection;
409 } else if (CFEqualSafe(policyName, kSecPolicyNameCodeSigning)) {
410 policyType = kSecValidPolicyCodeSigning;
411 } else if (CFEqualSafe(policyName, kSecPolicyNameTimeStamping)) {
412 policyType = kSecValidPolicyTimeStamping;
413 }
414 if (policyType == kSecValidPolicyAny) {
415 return true; /* policy not subject to constraint */
416 }
417 /* policy is subject to constraint; do the constraints allow it? */
418 bool result = false;
419 for (CFIndex ix = 0; ix < count; ix++) {
420 SecValidPolicy allowedPolicy = constraints[ix];
421 if (allowedPolicy == kSecValidPolicyAny ||
422 allowedPolicy == policyType) {
423 result = true;
424 break;
425 }
426 }
427 if (!result) {
428 secnotice("rvc", "%@ not allowed by policy constraints on issuing CA", policyName);
429 }
430 return result;
431 }
432
433 static bool SecRVCGetPolicyConstraints(CFDataRef data, SecValidPolicy **constraints, CFIndex *count) {
434 /* Sanity-check the input policy constraints data, returning pointer and
435 * count values in output arguments. Function result is true if successful.
436 *
437 * The first byte of the policy constraints data contains the number of entries,
438 * followed by an array of 0..n policy constraint values of type SecValidPolicy.
439 * The maximum number of defined policies is not expected to approach 127, i.e.
440 * the largest value which can be expressed in a signed byte.
441 */
442 bool result = false;
443 CFIndex length = 0;
444 SecValidPolicy *p = NULL;
445 if (data) {
446 length = CFDataGetLength(data);
447 p = (SecValidPolicy *)CFDataGetBytePtr(data);
448 }
449 /* Verify that count is 0 or greater, and equal to remaining number of bytes */
450 CFIndex c = (length > 0) ? *p++ : -1;
451 if (c < 0 || c != (length - 1)) {
452 secerror("invalid policy constraints array");
453 } else {
454 if (constraints) {
455 *constraints = p;
456 }
457 if (count) {
458 *count = c;
459 }
460 result = true;
461 }
462 return result;
463 }
464
465 static void SecRVCProcessValidPolicyConstraints(SecRVCRef rvc) {
466 if (!rvc || !rvc->valid_info || !rvc->builder) {
467 return;
468 }
469 if (!rvc->valid_info->hasPolicyConstraints) {
470 return;
471 }
472 CFIndex count = 0;
473 SecValidPolicy *constraints = NULL;
474 if (!SecRVCGetPolicyConstraints(rvc->valid_info->policyConstraints, &constraints, &count)) {
475 return;
476 }
477 secdebug("rvc", "found policy constraints for cert at index %ld", rvc->certIX);
478
479 /* check that policies being verified are permitted by the policy constraints */
480 bool policyDeniedByConstraints = false;
481 CFIndex ix, initialPVCCount = SecPathBuilderGetPVCCount(rvc->builder);
482 for (ix = 0; ix < initialPVCCount; ix++) {
483 SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, ix);
484 CFArrayRef policies = CFRetainSafe(pvc->policies);
485 CFIndex policyCount = (policies) ? CFArrayGetCount(policies) : 0;
486 for (CFIndex policyIX = 0; policyIX < policyCount; policyIX++) {
487 SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX);
488 if (!SecRVCPolicyConstraintsPermitPolicy(constraints, count, policy)) {
489 policyDeniedByConstraints = true;
490 if (rvc->valid_info->overridable) {
491 SecPVCSetResultForcedWithTrustResult(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX,
492 kCFBooleanFalse, true, kSecTrustResultRecoverableTrustFailure);
493 } else {
494 SecPVCSetResultForced(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX, kCFBooleanFalse, true);
495 }
496 }
497 }
498 CFReleaseSafe(policies);
499 }
500 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
501 if (analytics) {
502 TAValidStatus status = (policyDeniedByConstraints) ? TAValidPolicyConstrainedDenied : TAValidPolicyConstrainedOK;
503 analytics->valid_status |= status;
504 }
505 }
506
507 static void SecRVCProcessValidDateConstraints(SecRVCRef rvc) {
508 if (!rvc || !rvc->valid_info || !rvc->builder) {
509 return;
510 }
511 if (!rvc->valid_info->hasDateConstraints) {
512 return;
513 }
514 SecCertificateRef certificate = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
515 if (!certificate) {
516 return;
517 }
518 CFAbsoluteTime certIssued = SecCertificateNotValidBefore(certificate);
519 CFAbsoluteTime caNotBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
520 CFAbsoluteTime caNotAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
521 if (rvc->valid_info->notBeforeDate) {
522 caNotBefore = CFDateGetAbsoluteTime(rvc->valid_info->notBeforeDate);
523 }
524 if (rvc->valid_info->notAfterDate) {
525 caNotAfter = CFDateGetAbsoluteTime(rvc->valid_info->notAfterDate);
526 /* per the Valid specification, if this date is in the past, we need to check CT. */
527 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
528 if (caNotAfter < now) {
529 rvc->valid_info->requireCT = true;
530 }
531 }
532 if ((certIssued < caNotBefore) && (rvc->certIX > 0)) {
533 /* not-before constraint is only applied to leaf certificate, for now. */
534 return;
535 }
536
537 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
538 if ((certIssued < caNotBefore) || (certIssued > caNotAfter)) {
539 /* We are outside the constrained validity period. */
540 secnotice("rvc", "certificate issuance date not within the allowed range for this CA%s",
541 (rvc->valid_info->overridable) ? "" : " (non-recoverable error)");
542 if (analytics) {
543 analytics->valid_status |= TAValidDateConstrainedRevoked;
544 }
545 if (rvc->valid_info->overridable) {
546 /* error is recoverable, treat certificate as untrusted
547 (note this date check is different from kSecPolicyCheckTemporalValidity) */
548 SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedKey, rvc->certIX,
549 kCFBooleanFalse, true);
550 } else {
551 /* error is non-overridable, treat certificate as revoked */
552 SInt32 reason = 0; /* unspecified reason code */
553 CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
554 SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
555 cfreason, true);
556 SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
557 if (path) {
558 SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(path, rvc->certIX, cfreason);
559 }
560 CFReleaseNull(cfreason);
561 }
562 } else if (analytics) {
563 analytics->valid_status |= TAValidDateConstrainedOK;
564 }
565 }
566
567 bool SecRVCHasDefinitiveValidInfo(SecRVCRef rvc) {
568 if (!rvc || !rvc->valid_info) {
569 return false;
570 }
571 SecValidInfoRef info = rvc->valid_info;
572 /* outcomes as defined in Valid server specification */
573 if (info->format == kSecValidInfoFormatSerial ||
574 info->format == kSecValidInfoFormatSHA256) {
575 if (info->noCACheck || info->complete || info->isOnList) {
576 return true;
577 }
578 } else { /* info->format == kSecValidInfoFormatNto1 */
579 if (info->noCACheck || (info->complete && !info->isOnList)) {
580 return true;
581 }
582 }
583 return false;
584 }
585
586 bool SecRVCHasRevokedValidInfo(SecRVCRef rvc) {
587 if (!rvc || !rvc->valid_info) {
588 return false;
589 }
590 SecValidInfoRef info = rvc->valid_info;
591 /* either not present on an allowlist, or present on a blocklist */
592 return (!info->isOnList && info->valid) || (info->isOnList && !info->valid);
593 }
594
595 void SecRVCSetValidDeterminedErrorResult(SecRVCRef rvc) {
596 if (!rvc || !rvc->valid_info || !rvc->builder) {
597 return;
598 }
599 if (rvc->valid_info->overridable) {
600 /* error is recoverable, treat certificate as untrusted */
601 SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedLeaf, rvc->certIX,
602 kCFBooleanFalse, true);
603 return;
604 }
605 /* error is fatal at this point */
606 if (!SecRVCHasRevokedValidInfo(rvc) || rvc->valid_info->noCACheck) {
607 /* result key should indicate blocked instead of revoked,
608 * but result must be non-recoverable */
609 SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckBlackListedLeaf, rvc->certIX,
610 kCFBooleanFalse, true);
611 return;
612 }
613 SInt32 reason = 0; /* unspecified, since the Valid db doesn't tell us */
614 CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
615 SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
616 cfreason, true);
617 SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
618 if (path) {
619 SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(path, rvc->certIX, cfreason);
620 }
621 CFReleaseNull(cfreason);
622 }
623
624 bool SecRVCRevocationChecked(SecRVCRef rvc) {
625 return rvc->revocation_checked;
626 }
627
628 static void SecRVCProcessValidInfoResults(SecRVCRef rvc) {
629 if (!rvc || !rvc->valid_info || !rvc->builder) {
630 return;
631 }
632 SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
633 SecValidInfoRef info = rvc->valid_info;
634
635 bool definitive = SecRVCHasDefinitiveValidInfo(rvc);
636 bool revoked = SecRVCHasRevokedValidInfo(rvc);
637
638 /* set analytics */
639 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
640 if (analytics) {
641 if (revoked) {
642 analytics->valid_status |= definitive ? TAValidDefinitelyRevoked : TAValidProbablyRevoked;
643 } else {
644 analytics->valid_status |= definitive ? TAValidDefinitelyOK : TAValidProbablyOK;
645 }
646 analytics->valid_require_ct |= info->requireCT;
647 analytics->valid_known_intermediates_only |= info->knownOnly;
648 }
649
650 /* Handle no-ca cases */
651 if (info->noCACheck) {
652 bool allowed = (info->valid && info->complete && info->isOnList);
653 if (revoked) {
654 /* definitely revoked */
655 SecRVCSetValidDeterminedErrorResult(rvc);
656 } else if (allowed) {
657 /* definitely not revoked (allowlisted) */
658 SecCertificatePathVCSetIsAllowlisted(path, true);
659 rvc->revocation_checked = true;
660 }
661 /* no-ca is definitive; no need to check further. */
662 secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex,
663 (allowed) ? "allowed" : "revoked", rvc->certIX);
664 rvc->done = true;
665 return;
666 }
667
668 /* Handle policy constraints, if present. */
669 SecRVCProcessValidPolicyConstraints(rvc);
670
671 /* Handle date constraints, if present.
672 * Note: a not-after date may set the CT requirement,
673 * so check requireCT after this function is called. */
674 SecRVCProcessValidDateConstraints(rvc);
675
676 /* Set CT requirement on path, if present. */
677 if (info->requireCT) {
678 SecPathCTPolicy ctp = kSecPathCTRequired;
679 if (info->overridable) {
680 ctp = kSecPathCTRequiredOverridable;
681 }
682 SecCertificatePathVCSetRequiresCT(path, ctp);
683 }
684
685 /* Trigger OCSP for any non-definitive or revoked cases */
686 if (!definitive || revoked) {
687 info->checkOCSP = true;
688 }
689
690 if (info->checkOCSP) {
691 CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
692 CFIndex issuerIX = rvc->certIX + 1;
693 if (issuerIX >= count) {
694 /* cannot perform a revocation check on the last cert in the
695 chain, since we don't have its issuer. */
696 return;
697 }
698 secdebug("validupdate", "rvc: %s%s cert %" PRIdCFIndex " (will check OCSP)",
699 (info->complete) ? "" : "possibly ", (info->valid) ? "allowed" : "revoked",
700 rvc->certIX);
701 SecPathBuilderSetRevocationMethod(rvc->builder, kSecPolicyCheckRevocationAny);
702 if (analytics) {
703 /* Valid DB results caused us to do OCSP */
704 analytics->valid_trigger_ocsp = true;
705 }
706 }
707 }
708
709 static bool SecRVCCheckValidInfoDatabase(SecRVCRef rvc) {
710 /* Skip checking for OCSP Signer verification */
711 if (SecPathBuilderGetPVCCount(rvc->builder) == 1) {
712 SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, 0);
713 if (!pvc) { return false; }
714 SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, 0);
715 CFStringRef policyName = (policy) ? SecPolicyGetName(policy) : NULL;
716 if (policyName && CFEqual(policyName, CFSTR("OCSPSigner"))) {
717 return false;
718 }
719 }
720
721 /* Make sure revocation db info is up-to-date.
722 * We don't care if the builder is allowed to access the network because
723 * the network fetching does not block the trust evaluation. */
724 SecRevocationDbCheckNextUpdate();
725
726 /* Check whether we have valid db info for this cert,
727 given the cert and its issuer */
728 SecValidInfoRef info = NULL;
729 CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
730 if (count) {
731 bool isSelfSigned = false;
732 SecCertificateRef cert = NULL;
733 SecCertificateRef issuer = NULL;
734 CFIndex issuerIX = rvc->certIX + 1;
735 if (count > issuerIX) {
736 issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, issuerIX);
737 } else if (count == issuerIX) {
738 CFIndex rootIX = SecCertificatePathVCSelfSignedIndex(SecPathBuilderGetPath(rvc->builder));
739 if (rootIX == rvc->certIX) {
740 issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, rootIX);
741 isSelfSigned = true;
742 }
743 }
744 cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
745 if (!isSelfSigned) {
746 /* skip revocation db check for self-signed certificates [33137065] */
747 info = SecRevocationDbCopyMatching(cert, issuer);
748 }
749 SecValidInfoSetAnchor(info, SecPathBuilderGetCertificateAtIndex(rvc->builder, count-1));
750 }
751 if (info) {
752 SecValidInfoRef old_info = rvc->valid_info;
753 rvc->valid_info = info;
754 if (old_info) {
755 CFReleaseNull(old_info);
756 }
757 return true;
758 }
759 return false;
760 }
761
762 static void SecRVCCheckRevocationCaches(SecRVCRef rvc) {
763 /* Don't check OCSP cache if CRLs enabled and policy requested CRL only */
764 if (SecRVCShouldCheckOCSP(rvc) && (rvc->orvc->ocspRequest)) {
765 secdebug("ocsp", "Checking cached responses for cert %ld", rvc->certIX);
766 SecOCSPResponseRef response = NULL;
767 if (SecPathBuilderGetCheckRevocationOnline(rvc->builder)) {
768 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
769 response = SecOCSPCacheCopyMatchingWithMinInsertTime(rvc->orvc->ocspRequest, NULL, now - kSecOCSPResponseOnlineTTL);
770 } else {
771 response = SecOCSPCacheCopyMatching(rvc->orvc->ocspRequest, NULL);
772 }
773 SecORVCConsumeOCSPResponse(rvc->orvc, response, NULL_TIME, false, true);
774 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
775 if (rvc->orvc->done && analytics) {
776 /* We found a valid OCSP response in the cache */
777 analytics->ocsp_cache_hit = true;
778 }
779 }
780 }
781
782 static void SecRVCUpdatePVC(SecRVCRef rvc) {
783 SecRVCProcessValidInfoResults(rvc); /* restore the results we got from Valid */
784 if (rvc->orvc) { SecORVCUpdatePVC(rvc->orvc); }
785 }
786
787 static void SecRVCSetFinishedWithoutNetwork(SecRVCRef rvc) {
788 rvc->done = true;
789 SecRVCUpdatePVC(rvc);
790 (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
791 }
792
793 static bool SecRVCFetchNext(SecRVCRef rvc) {
794 bool OCSP_fetch_finished = true;
795 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
796 /* Don't send OCSP request only if CRLs enabled and policy requested CRL only */
797 if (SecRVCShouldCheckOCSP(rvc)) {
798 SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
799 SecCertificateRef cert = SecCertificatePathVCGetCertificateAtIndex(path, rvc->certIX);
800 OCSP_fetch_finished = SecORVCBeginFetches(rvc->orvc, cert);
801 if (analytics && !OCSP_fetch_finished) {
802 /* We did a network OCSP fetch, set report appropriately */
803 analytics->ocsp_network = true;
804 }
805 }
806 if (OCSP_fetch_finished) {
807 /* we didn't start an OCSP background job for this cert */
808 (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
809 }
810 return OCSP_fetch_finished;
811 }
812
813 /* The SecPathBuilder state machine calls SecPathBuilderCheckRevocation twice --
814 * once in the ValidatePath state, and again in the ComputeDetails state. In the
815 * ValidatePath state we've not yet run the path checks, so for callers who set
816 * kSecRevocationCheckIfTrusted, we don't do any networking on that first call.
817 * Here, if we've already done revocation before (so we're in ComputeDetails now),
818 * we need to recheck (and enable networking) for trusted chains and
819 * kSecRevocationCheckIfTrusted. Otherwise, we skip the checks to save on the processing
820 * but update the PVCs with the revocation results from the last check. */
821 static bool SecRevocationDidCheckRevocation(SecPathBuilderRef builder, bool *first_check_done) {
822 SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
823 if (!SecCertificatePathVCIsRevocationDone(path)) {
824 return false;
825 }
826 if (first_check_done) {
827 *first_check_done = true;
828 }
829
830 SecPVCRef resultPVC = SecPathBuilderGetResultPVC(builder);
831 bool recheck = false;
832 if (SecPathBuilderGetCheckRevocationIfTrusted(builder) && SecPVCIsOkResult(resultPVC)) {
833 recheck = true;
834 secdebug("rvc", "Rechecking revocation because network now allowed");
835 } else {
836 secdebug("rvc", "Not rechecking revocation");
837 }
838
839 if (recheck) {
840 // reset the RVCs for the second pass
841 SecCertificatePathVCDeleteRVCs(path);
842 } else {
843 CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder);
844 for (certIX = 0; certIX < certCount; ++certIX) {
845 SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
846 if (rvc) {
847 SecRVCUpdatePVC(rvc);
848 }
849 }
850 }
851
852 return !recheck;
853 }
854
855 static bool SecRevocationCanAccessNetwork(SecPathBuilderRef builder, bool first_check_done) {
856 /* CheckRevocationIfTrusted overrides NoNetworkAccess for revocation */
857 if (SecPathBuilderGetCheckRevocationIfTrusted(builder)) {
858 if (first_check_done) {
859 /* We're on the second pass. We need to now allow networking for revocation.
860 * SecRevocationDidCheckRevocation takes care of not running a second pass
861 * if the chain isn't trusted. */
862 return true;
863 } else {
864 /* We're on the first pass of the revocation checks, where we aren't
865 * supposed to do networking because we don't know if the chain
866 * is trusted yet. */
867 return false;
868 }
869 }
870 return SecPathBuilderCanAccessNetwork(builder);
871 }
872
873 void SecPathBuilderCheckKnownIntermediateConstraints(SecPathBuilderRef builder) {
874 SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
875 if (!path) {
876 return;
877 }
878 /* only perform this check once per path! */
879 CFIndex certIX = kCFNotFound;
880 if (SecCertificatePathVCCheckedIssuers(path)) {
881 certIX = SecCertificatePathVCUnknownCAIndex(path);
882 goto checkedIssuers;
883 }
884 /* check full path: start with anchor and decrement to leaf */
885 bool parentConstrained = false;
886 CFIndex certCount = SecPathBuilderGetCertificateCount(builder);
887 for (certIX = certCount - 1; certIX >= 0; --certIX) {
888 SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
889 if (!rvc) {
890 continue;
891 }
892 if (parentConstrained && !rvc->valid_info) {
893 /* Parent had the known-only constraint, but our issuer is unknown.
894 Bump index to point back at the issuer since it fails the constraint. */
895 certIX++;
896 break;
897 }
898 parentConstrained = (rvc->valid_info && rvc->valid_info->knownOnly);
899 if (parentConstrained) {
900 secdebug("validupdate", "Valid db found a known-intermediate constraint on %@ (index=%ld)",
901 rvc->valid_info->issuerHash, certIX+1);
902 if (certIX == 0) {
903 /* check special case: unknown constrained CA in leaf position */
904 SecCertificateRef cert = SecCertificatePathVCGetCertificateAtIndex(path, certIX);
905 if (cert && SecCertificateIsCA(cert) && !SecRevocationDbContainsIssuer(cert)) {
906 /* leaf is a CA which violates the constraint */
907 break;
908 }
909 }
910 }
911 }
912 /* At this point, certIX will either be -1, indicating no CA was found
913 which failed a known-intermediates-only constraint on its parent, or it
914 will be the index of the first unknown CA which fails the constraint. */
915 if (certIX >= 0) {
916 secnotice("validupdate", "CA at index %ld violates known-intermediate constraint", certIX);
917 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
918 if (analytics) {
919 analytics->valid_unknown_intermediate = true;
920 }
921 }
922 SecCertificatePathVCSetUnknownCAIndex(path, certIX);
923 SecCertificatePathVCSetCheckedIssuers(path, true);
924
925 checkedIssuers:
926 if (certIX >= 0) {
927 /* Error is set on CA certificate which failed the constraint. */
928 SecRVCSetValidDeterminedErrorResult(SecCertificatePathVCGetRVCAtIndex(path, certIX));
929 }
930 }
931
932 bool SecPathBuilderCheckRevocation(SecPathBuilderRef builder) {
933 secdebug("rvc", "checking revocation");
934 CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder);
935 SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
936 if (certCount <= 1) {
937 /* Can't verify without an issuer; we're done */
938 return true;
939 }
940
941 bool first_check_done = false;
942 if (SecRevocationDidCheckRevocation(builder, &first_check_done)) {
943 return true;
944 }
945
946 /* Setup things so we check revocation status of all certs. */
947 SecCertificatePathVCAllocateRVCs(path, certCount);
948
949 /* Note that if we are multi threaded and a job completes after it
950 is started but before we return from this function, we don't want
951 a callback to decrement asyncJobCount to zero before we finish issuing
952 all the jobs. To avoid this we pretend we issued certCount async jobs,
953 and decrement pvc->asyncJobCount for each cert that we don't start a
954 background fetch for. We include the root, even though we'll never start
955 an async job for it so that we count all active threads for this eval. */
956 SecPathBuilderSetAsyncJobCount(builder, (unsigned int)(certCount));
957
958 /* Loop though certificates again and issue an ocsp fetch if the
959 revocation status checking isn't done yet (and we have an issuer!) */
960 for (certIX = 0; certIX < certCount; ++certIX) {
961 secdebug("rvc", "checking revocation for cert: %ld", certIX);
962 SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
963 if (!rvc) {
964 continue;
965 }
966
967 SecRVCInit(rvc, builder, certIX);
968
969 /* RFC 6960: id-pkix-ocsp-nocheck extension says that we shouldn't check revocation. */
970 if (SecCertificateHasOCSPNoCheckMarkerExtension(SecCertificatePathVCGetCertificateAtIndex(path, certIX)))
971 {
972 secdebug("rvc", "skipping revocation checks for no-check cert: %ld", certIX);
973 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
974 if (analytics) {
975 /* This certificate has OCSP No-Check, so add to reporting analytics */
976 analytics->ocsp_no_check = true;
977 }
978 SecRVCSetFinishedWithoutNetwork(rvc);
979 }
980
981 if (rvc->done) {
982 continue;
983 }
984
985 #if !TARGET_OS_BRIDGE
986 /* Check valid database first (separate from OCSP response cache) */
987 if (SecRVCCheckValidInfoDatabase(rvc)) {
988 SecRVCProcessValidInfoResults(rvc);
989 }
990 #endif
991 /* Any other revocation method requires an issuer certificate to verify the response;
992 * skip the last cert in the chain since it doesn't have one. */
993 if (certIX + 1 >= certCount) {
994 continue;
995 }
996
997 /* Ignore stapled OCSP responses only if CRLs are enabled and the
998 * policy specifically requested CRLs only. */
999 if (SecRVCShouldCheckOCSP(rvc)) {
1000 /* If we have any OCSP stapled responses, check those first */
1001 SecORVCProcessStapledResponses(rvc->orvc);
1002 }
1003
1004 #if TARGET_OS_BRIDGE
1005 /* The bridge has no writeable storage and no network. Nothing else we can
1006 * do here. */
1007 SecRVCSetFinishedWithoutNetwork(rvc);
1008 continue;
1009 #else // !TARGET_OS_BRIDGE
1010 /* Then check the caches for revocation results. */
1011 SecRVCCheckRevocationCaches(rvc);
1012
1013 /* The check is done if we found cached responses from either method. */
1014 if (rvc->done || rvc->orvc->done) {
1015 secdebug("rvc", "found cached response for cert: %ld", certIX);
1016 SecRVCSetFinishedWithoutNetwork(rvc);
1017 continue;
1018 }
1019
1020 /* If we got a cached response that is no longer valid (which can only be true for
1021 * revoked responses), let's try to get a fresher response even if no one asked.
1022 * This check resolves unrevocation events after the nextUpdate time. */
1023 bool old_cached_response = (!rvc->done && rvc->orvc->ocspResponse);
1024
1025 /* Check whether CA revocation additions match an issuer above us in this path.
1026 * If so, we will attempt revocation checking below. */
1027 bool has_ca_revocation = (certIX < SecCertificatePathVCIndexOfCAWithRevocationAdditions(path));
1028
1029 /* If the cert is EV or if revocation checking was explicitly enabled, attempt to fire off an
1030 async http request for this cert's revocation status, unless we already successfully checked
1031 the revocation status of this cert based on the cache or stapled responses. */
1032 bool allow_fetch = SecRevocationCanAccessNetwork(builder, first_check_done) &&
1033 (SecCertificatePathVCIsEV(path) || SecCertificatePathVCIsOptionallyEV(path) ||
1034 SecPathBuilderGetRevocationMethod(builder) || old_cached_response || has_ca_revocation);
1035 if (rvc->done || !allow_fetch) {
1036 /* We got a cache hit or we aren't allowed to access the network */
1037 SecRVCUpdatePVC(rvc);
1038 /* We didn't really start any background jobs for this cert. */
1039 (void)SecPathBuilderDecrementAsyncJobCount(builder);
1040 } else {
1041 (void)SecRVCFetchNext(rvc);
1042 }
1043 #endif // !TARGET_OS_BRIDGE
1044 }
1045
1046 /* Return false if there are still async jobs running. */
1047 /* builder->asyncJobCount is atomic, so we know that if the job count is 0, all other
1048 * threads are finished. If the job count is > 0, other threads will decrement the job
1049 * count and SecPathBuilderStep to crank the state machine when the job count is 0. */
1050 return (SecPathBuilderDecrementAsyncJobCount(builder) == 0);
1051 }
1052
1053 CFAbsoluteTime SecRVCGetEarliestNextUpdate(SecRVCRef rvc) {
1054 CFAbsoluteTime enu = NULL_TIME;
1055 if (!rvc || !rvc->orvc) { return enu; }
1056 enu = rvc->orvc->nextUpdate;
1057 return enu;
1058 }