]>
Commit | Line | Data |
---|---|---|
866f8763 | 1 | /* |
ecaf5866 | 2 | * Copyright (c) 2008-2018 Apple Inc. All Rights Reserved. |
866f8763 A |
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 | ||
ecaf5866 A |
30 | #include <mach/mach_time.h> |
31 | ||
866f8763 A |
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 <securityd/SecTrustServer.h> | |
43 | #include <securityd/SecOCSPRequest.h> | |
44 | #include <securityd/SecOCSPResponse.h> | |
45 | #include <securityd/asynchttp.h> | |
46 | #include <securityd/SecOCSPCache.h> | |
47 | #include <securityd/SecRevocationDb.h> | |
48 | #include <securityd/SecCertificateServer.h> | |
29734401 | 49 | #include <securityd/SecPolicyServer.h> |
866f8763 A |
50 | |
51 | #include <securityd/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 | /* OCSP Revocation verification context. */ | |
62 | struct OpaqueSecORVC { | |
63 | /* Will contain the response data. */ | |
64 | asynchttp_t http; | |
65 | ||
66 | /* Pointer to the builder for this revocation check. */ | |
67 | SecPathBuilderRef builder; | |
68 | ||
69 | /* Pointer to the generic rvc for this revocation check */ | |
70 | SecRVCRef rvc; | |
71 | ||
72 | /* The ocsp request we send to each responder. */ | |
73 | SecOCSPRequestRef ocspRequest; | |
74 | ||
75 | /* The freshest response we received so far, from stapling or cache or responder. */ | |
76 | SecOCSPResponseRef ocspResponse; | |
77 | ||
78 | /* The best validated candidate single response we received so far, from stapling or cache or responder. */ | |
79 | SecOCSPSingleResponseRef ocspSingleResponse; | |
80 | ||
81 | /* Index of cert in builder that this RVC is for 0 = leaf, etc. */ | |
82 | CFIndex certIX; | |
83 | ||
84 | /* Index in array returned by SecCertificateGetOCSPResponders() for current | |
85 | responder. */ | |
86 | CFIndex responderIX; | |
87 | ||
88 | /* URL of current responder. */ | |
89 | CFURLRef responder; | |
90 | ||
91 | /* Date until which this revocation status is valid. */ | |
92 | CFAbsoluteTime nextUpdate; | |
93 | ||
94 | bool done; | |
95 | }; | |
96 | ||
97 | static void SecORVCFinish(SecORVCRef orvc) { | |
98 | secdebug("alloc", "%p", orvc); | |
99 | asynchttp_free(&orvc->http); | |
100 | if (orvc->ocspRequest) { | |
101 | SecOCSPRequestFinalize(orvc->ocspRequest); | |
102 | orvc->ocspRequest = NULL; | |
103 | } | |
104 | if (orvc->ocspResponse) { | |
105 | SecOCSPResponseFinalize(orvc->ocspResponse); | |
106 | orvc->ocspResponse = NULL; | |
107 | if (orvc->ocspSingleResponse) { | |
108 | SecOCSPSingleResponseDestroy(orvc->ocspSingleResponse); | |
109 | orvc->ocspSingleResponse = NULL; | |
110 | } | |
111 | } | |
112 | } | |
113 | ||
114 | #define MAX_OCSP_RESPONDERS 3 | |
115 | #define OCSP_REQUEST_THRESHOLD 10 | |
116 | ||
117 | /* Return the next responder we should contact for this rvc or NULL if we | |
118 | exhausted them all. */ | |
119 | static CFURLRef SecORVCGetNextResponder(SecORVCRef rvc) { | |
120 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
121 | CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert); | |
122 | if (ocspResponders) { | |
123 | CFIndex responderCount = CFArrayGetCount(ocspResponders); | |
124 | if (responderCount >= OCSP_REQUEST_THRESHOLD) { | |
125 | secnotice("rvc", "too many ocsp responders (%ld)", (long)responderCount); | |
126 | return NULL; | |
127 | } | |
128 | while (rvc->responderIX < responderCount && rvc->responderIX < MAX_OCSP_RESPONDERS) { | |
129 | CFURLRef responder = CFArrayGetValueAtIndex(ocspResponders, rvc->responderIX); | |
130 | rvc->responderIX++; | |
131 | CFStringRef scheme = CFURLCopyScheme(responder); | |
132 | if (scheme) { | |
133 | /* We only support http and https responders currently. */ | |
134 | bool valid_responder = (CFEqual(CFSTR("http"), scheme) || | |
135 | CFEqual(CFSTR("https"), scheme)); | |
136 | CFRelease(scheme); | |
137 | if (valid_responder) | |
138 | return responder; | |
139 | } | |
140 | } | |
141 | } | |
142 | return NULL; | |
143 | } | |
144 | ||
145 | /* Fire off an async http request for this certs revocation status, return | |
146 | false if request was queued, true if we're done. */ | |
147 | static bool SecORVCFetchNext(SecORVCRef rvc) { | |
148 | while ((rvc->responder = SecORVCGetNextResponder(rvc))) { | |
149 | CFDataRef request = SecOCSPRequestGetDER(rvc->ocspRequest); | |
150 | if (!request) | |
151 | goto errOut; | |
152 | ||
153 | secinfo("rvc", "Sending http ocsp request for cert %ld", rvc->certIX); | |
154 | if (!asyncHttpPost(rvc->responder, request, OCSP_RESPONSE_TIMEOUT, &rvc->http)) { | |
155 | /* Async request was posted, wait for reply. */ | |
156 | return false; | |
157 | } | |
158 | } | |
159 | ||
160 | errOut: | |
161 | rvc->done = true; | |
162 | return true; | |
163 | } | |
164 | ||
165 | /* Process a verified ocsp response for a given cert. Return true if the | |
166 | certificate status was obtained. */ | |
167 | static bool SecOCSPSingleResponseProcess(SecOCSPSingleResponseRef this, | |
168 | SecORVCRef rvc) { | |
169 | bool processed; | |
170 | switch (this->certStatus) { | |
171 | case CS_Good: | |
172 | secdebug("ocsp", "CS_Good for cert %" PRIdCFIndex, rvc->certIX); | |
173 | /* @@@ Mark cert as valid until a given date (nextUpdate if we have one) | |
174 | in the info dictionary. */ | |
175 | //cert.revokeCheckGood(true); | |
176 | rvc->nextUpdate = this->nextUpdate == NULL_TIME ? this->thisUpdate + kSecDefaultOCSPResponseTTL : this->nextUpdate; | |
177 | processed = true; | |
178 | break; | |
179 | case CS_Revoked: | |
180 | secdebug("ocsp", "CS_Revoked for cert %" PRIdCFIndex, rvc->certIX); | |
181 | /* @@@ Mark cert as revoked (with reason) at revocation date in | |
182 | the info dictionary, or perhaps we should use a different key per | |
183 | reason? That way a client using exceptions can ignore some but | |
184 | not all reasons. */ | |
185 | SInt32 reason = this->crlReason; | |
186 | CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason); | |
187 | SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX, | |
ecaf5866 | 188 | cfreason, true); |
866f8763 A |
189 | if (rvc->builder) { |
190 | CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder); | |
191 | if (info) { | |
192 | /* make the revocation reason available in the trust result */ | |
193 | CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason); | |
194 | } | |
195 | } | |
196 | CFRelease(cfreason); | |
197 | processed = true; | |
198 | break; | |
199 | case CS_Unknown: | |
200 | /* not an error, no per-cert status, nothing here */ | |
201 | secdebug("ocsp", "CS_Unknown for cert %" PRIdCFIndex, rvc->certIX); | |
202 | processed = false; | |
203 | break; | |
204 | default: | |
205 | secnotice("ocsp", "BAD certStatus (%d) for cert %" PRIdCFIndex, | |
206 | (int)this->certStatus, rvc->certIX); | |
207 | processed = false; | |
208 | break; | |
209 | } | |
210 | ||
211 | return processed; | |
212 | } | |
213 | ||
214 | static void SecORVCUpdatePVC(SecORVCRef rvc) { | |
215 | if (rvc->ocspSingleResponse) { | |
216 | SecOCSPSingleResponseProcess(rvc->ocspSingleResponse, rvc); | |
217 | } | |
218 | if (rvc->ocspResponse) { | |
219 | rvc->nextUpdate = SecOCSPResponseGetExpirationTime(rvc->ocspResponse); | |
220 | } | |
221 | } | |
222 | ||
223 | typedef void (^SecOCSPEvaluationCompleted)(SecTrustResultType tr); | |
224 | ||
225 | static void | |
226 | SecOCSPEvaluateCompleted(const void *userData, | |
ecaf5866 | 227 | CFArrayRef chain, CFArrayRef details, CFDictionaryRef info, |
866f8763 A |
228 | SecTrustResultType result) { |
229 | SecOCSPEvaluationCompleted evaluated = (SecOCSPEvaluationCompleted)userData; | |
230 | evaluated(result); | |
231 | Block_release(evaluated); | |
232 | ||
233 | } | |
234 | ||
235 | static bool SecOCSPResponseEvaluateSigner(SecORVCRef rvc, CFArrayRef signers, CFArrayRef issuers, CFAbsoluteTime verifyTime) { | |
236 | __block bool evaluated = false; | |
237 | bool trusted = false; | |
238 | if (!signers || !issuers) { | |
239 | return trusted; | |
240 | } | |
241 | ||
242 | /* Verify the signer chain against the OCSPSigner policy, using the issuer chain as anchors. */ | |
243 | const void *ocspSigner = SecPolicyCreateOCSPSigner(); | |
244 | CFArrayRef policies = CFArrayCreate(kCFAllocatorDefault, | |
245 | &ocspSigner, 1, &kCFTypeArrayCallBacks); | |
246 | CFRelease(ocspSigner); | |
247 | ||
248 | SecOCSPEvaluationCompleted completed = Block_copy(^(SecTrustResultType result) { | |
249 | if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) { | |
250 | evaluated = true; | |
251 | } | |
252 | }); | |
253 | ||
254 | CFDataRef clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder); | |
255 | SecPathBuilderRef oBuilder = SecPathBuilderCreate(clientAuditToken, | |
256 | signers, issuers, true, false, | |
257 | policies, NULL, NULL, NULL, | |
258 | verifyTime, NULL, NULL, | |
259 | SecOCSPEvaluateCompleted, completed); | |
260 | /* Build the chain(s), evaluate them, call the completed block, free the block and builder */ | |
261 | SecPathBuilderStep(oBuilder); | |
262 | CFReleaseNull(clientAuditToken); | |
263 | CFReleaseNull(policies); | |
264 | ||
265 | /* verify the public key of the issuer signed the OCSP signer */ | |
266 | if (evaluated) { | |
267 | SecCertificateRef issuer = NULL, signer = NULL; | |
268 | SecKeyRef issuerPubKey = NULL; | |
269 | ||
270 | issuer = (SecCertificateRef)CFArrayGetValueAtIndex(issuers, 0); | |
271 | signer = (SecCertificateRef)CFArrayGetValueAtIndex(signers, 0); | |
272 | ||
273 | if (issuer) { | |
274 | #if TARGET_OS_IPHONE | |
275 | issuerPubKey = SecCertificateCopyPublicKey(issuer); | |
276 | #else | |
277 | issuerPubKey = SecCertificateCopyPublicKey_ios(issuer); | |
278 | #endif | |
279 | } | |
280 | if (signer && issuerPubKey && (errSecSuccess == SecCertificateIsSignedBy(signer, issuerPubKey))) { | |
281 | trusted = true; | |
282 | } else { | |
283 | secnotice("ocsp", "ocsp signer cert not signed by issuer"); | |
284 | } | |
285 | CFReleaseNull(issuerPubKey); | |
286 | } | |
287 | ||
288 | return trusted; | |
289 | } | |
290 | ||
291 | static bool SecOCSPResponseVerify(SecOCSPResponseRef ocspResponse, SecORVCRef rvc, CFAbsoluteTime verifyTime) { | |
292 | bool trusted; | |
293 | SecCertificatePathVCRef issuers = SecCertificatePathVCCopyFromParent(SecPathBuilderGetPath(rvc->builder), rvc->certIX + 1); | |
294 | SecCertificateRef issuer = issuers ? CFRetainSafe(SecCertificatePathVCGetCertificateAtIndex(issuers, 0)) : NULL; | |
295 | CFArrayRef signers = SecOCSPResponseCopySigners(ocspResponse); | |
296 | SecCertificateRef signer = SecOCSPResponseCopySigner(ocspResponse, issuer); | |
297 | ||
298 | if (signer && signers) { | |
299 | if (issuer && CFEqual(signer, issuer)) { | |
300 | /* We already know we trust issuer since it's the issuer of the | |
301 | * cert we are verifying. */ | |
302 | secinfo("ocsp", "ocsp responder: %@ response signed by issuer", | |
303 | rvc->responder); | |
304 | trusted = true; | |
305 | } else { | |
306 | secinfo("ocsp", "ocsp responder: %@ response signed by cert issued by issuer", | |
307 | rvc->responder); | |
308 | CFMutableArrayRef signerCerts = NULL; | |
309 | CFArrayRef issuerCerts = NULL; | |
310 | ||
311 | /* Ensure the signer cert is the 0th cert for trust evaluation */ | |
312 | signerCerts = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
313 | CFArrayAppendValue(signerCerts, signer); | |
314 | CFArrayAppendArray(signerCerts, signers, CFRangeMake(0, CFArrayGetCount(signers))); | |
315 | ||
316 | if (issuers) { | |
317 | issuerCerts = SecCertificatePathVCCopyCertificates(issuers); | |
318 | } | |
319 | ||
320 | if (SecOCSPResponseEvaluateSigner(rvc, signerCerts, issuerCerts, verifyTime)) { | |
321 | secdebug("ocsp", "response satisfies ocspSigner policy (%@)", | |
322 | rvc->responder); | |
323 | trusted = true; | |
324 | } else { | |
325 | /* @@@ We don't trust the cert so don't use this response. */ | |
326 | secnotice("ocsp", "ocsp response signed by certificate which " | |
327 | "does not satisfy ocspSigner policy"); | |
328 | trusted = false; | |
329 | } | |
330 | CFReleaseNull(signerCerts); | |
331 | CFReleaseNull(issuerCerts); | |
332 | } | |
333 | } else { | |
334 | /* @@@ No signer found for this ocsp response, discard it. */ | |
335 | secnotice("ocsp", "ocsp responder: %@ no signer found for response", | |
336 | rvc->responder); | |
337 | trusted = false; | |
338 | } | |
339 | ||
340 | #if DUMP_OCSPRESPONSES | |
341 | char buf[40]; | |
342 | snprintf(buf, 40, "/tmp/ocspresponse%ld%s.der", | |
343 | rvc->certIX, (trusted ? "t" : "u")); | |
344 | secdumpdata(ocspResponse->data, buf); | |
345 | #endif | |
346 | CFReleaseNull(issuers); | |
347 | CFReleaseNull(issuer); | |
348 | CFReleaseNull(signers); | |
349 | CFReleaseNull(signer); | |
350 | return trusted; | |
351 | } | |
352 | ||
353 | static void SecORVCConsumeOCSPResponse(SecORVCRef rvc, SecOCSPResponseRef ocspResponse /*CF_CONSUMED*/, CFTimeInterval maxAge, bool updateCache) { | |
354 | SecOCSPSingleResponseRef sr = NULL; | |
355 | require_quiet(ocspResponse, errOut); | |
356 | SecOCSPResponseStatus orStatus = SecOCSPGetResponseStatus(ocspResponse); | |
357 | require_action_quiet(orStatus == kSecOCSPSuccess, errOut, | |
358 | secnotice("ocsp", "responder: %@ returned status: %d", rvc->responder, orStatus)); | |
359 | require_action_quiet(sr = SecOCSPResponseCopySingleResponse(ocspResponse, rvc->ocspRequest), errOut, | |
360 | secnotice("ocsp", "ocsp responder: %@ did not include status of requested cert", rvc->responder)); | |
361 | // Check if this response is fresher than any (cached) response we might still have in the rvc. | |
362 | require_quiet(!rvc->ocspSingleResponse || rvc->ocspSingleResponse->thisUpdate < sr->thisUpdate, errOut); | |
363 | ||
364 | CFAbsoluteTime verifyTime = CFAbsoluteTimeGetCurrent(); | |
866f8763 A |
365 | /* Check the OCSP response signature and verify the response. */ |
366 | require_quiet(SecOCSPResponseVerify(ocspResponse, rvc, | |
367 | sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut); | |
368 | ||
369 | // If we get here, we have a properly signed ocsp response | |
370 | // but we haven't checked dates yet. | |
371 | ||
372 | bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime); | |
373 | if (sr->certStatus == CS_Good) { | |
374 | // Side effect of SecOCSPResponseCalculateValidity sets ocspResponse->expireTime | |
375 | require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut); | |
376 | } else if (sr->certStatus == CS_Revoked) { | |
377 | // Expire revoked responses when the subject certificate itself expires. | |
378 | ocspResponse->expireTime = SecCertificateNotValidAfter(SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX)); | |
379 | } | |
380 | ||
381 | // Ok we like the new response, let's toss the old one. | |
382 | if (updateCache) | |
383 | SecOCSPCacheReplaceResponse(rvc->ocspResponse, ocspResponse, rvc->responder, verifyTime); | |
384 | ||
385 | if (rvc->ocspResponse) SecOCSPResponseFinalize(rvc->ocspResponse); | |
386 | rvc->ocspResponse = ocspResponse; | |
387 | ocspResponse = NULL; | |
388 | ||
389 | if (rvc->ocspSingleResponse) SecOCSPSingleResponseDestroy(rvc->ocspSingleResponse); | |
390 | rvc->ocspSingleResponse = sr; | |
391 | sr = NULL; | |
392 | ||
393 | rvc->done = sr_valid; | |
394 | ||
395 | errOut: | |
396 | if (sr) SecOCSPSingleResponseDestroy(sr); | |
397 | if (ocspResponse) SecOCSPResponseFinalize(ocspResponse); | |
398 | } | |
399 | ||
400 | /* Callback from async http code after an ocsp response has been received. */ | |
401 | static void SecOCSPFetchCompleted(asynchttp_t *http, CFTimeInterval maxAge) { | |
402 | SecORVCRef rvc = (SecORVCRef)http->info; | |
403 | SecPathBuilderRef builder = rvc->builder; | |
ecaf5866 A |
404 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder); |
405 | if (analytics) { | |
406 | /* Add the time this fetch took to complete to the total time */ | |
407 | analytics->ocsp_fetch_time += (mach_absolute_time() - http->start_time); | |
408 | } | |
866f8763 A |
409 | SecOCSPResponseRef ocspResponse = NULL; |
410 | if (http->response) { | |
411 | CFDataRef data = CFHTTPMessageCopyBody(http->response); | |
412 | if (data) { | |
413 | /* Parse the returned data as if it's an ocspResponse. */ | |
414 | ocspResponse = SecOCSPResponseCreate(data); | |
415 | CFRelease(data); | |
416 | } | |
417 | } | |
418 | ||
ecaf5866 A |
419 | if ((!http->response || !ocspResponse) && analytics) { |
420 | /* We didn't get any data back, so the fetch failed */ | |
421 | analytics->ocsp_fetch_failed++; | |
422 | } | |
423 | ||
866f8763 A |
424 | SecORVCConsumeOCSPResponse(rvc, ocspResponse, maxAge, true); |
425 | // TODO: maybe we should set the cache-control: false in the http header and try again if the response is stale | |
426 | ||
427 | if (!rvc->done) { | |
ecaf5866 A |
428 | if (analytics && ocspResponse) { |
429 | /* We got an OCSP response that didn't pass validation */ | |
430 | analytics-> ocsp_validation_failed = true; | |
431 | } | |
866f8763 A |
432 | /* Clear the data for the next response. */ |
433 | asynchttp_free(http); | |
434 | SecORVCFetchNext(rvc); | |
435 | } | |
436 | ||
437 | if (rvc->done) { | |
438 | secdebug("rvc", "got OCSP response for cert: %ld", rvc->certIX); | |
439 | SecORVCUpdatePVC(rvc); | |
440 | if (!SecPathBuilderDecrementAsyncJobCount(builder)) { | |
441 | secdebug("rvc", "done with all async jobs"); | |
442 | SecPathBuilderStep(builder); | |
443 | } | |
444 | } | |
445 | } | |
446 | ||
447 | static SecORVCRef SecORVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) { | |
448 | SecORVCRef orvc = NULL; | |
449 | orvc = malloc(sizeof(struct OpaqueSecORVC)); | |
450 | if (orvc) { | |
451 | memset(orvc, 0, sizeof(struct OpaqueSecORVC)); | |
452 | orvc->builder = builder; | |
453 | orvc->rvc = rvc; | |
454 | orvc->certIX = certIX; | |
455 | orvc->http.queue = SecPathBuilderGetQueue(builder); | |
456 | orvc->http.token = SecPathBuilderCopyClientAuditToken(builder); | |
457 | orvc->http.completed = SecOCSPFetchCompleted; | |
458 | orvc->http.info = orvc; | |
459 | orvc->ocspRequest = NULL; | |
460 | orvc->responderIX = 0; | |
461 | orvc->responder = NULL; | |
462 | orvc->nextUpdate = NULL_TIME; | |
463 | orvc->ocspResponse = NULL; | |
464 | orvc->ocspSingleResponse = NULL; | |
465 | orvc->done = false; | |
466 | ||
467 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(builder, certIX); | |
468 | if (SecPathBuilderGetCertificateCount(builder) > (certIX + 1)) { | |
469 | SecCertificateRef issuer = SecPathBuilderGetCertificateAtIndex(builder, certIX + 1); | |
470 | orvc->ocspRequest = SecOCSPRequestCreate(cert, issuer); | |
471 | } | |
472 | } | |
473 | return orvc; | |
474 | } | |
475 | ||
476 | static void SecORVCProcessStapledResponses(SecORVCRef rvc) { | |
477 | /* Get stapled OCSP responses */ | |
478 | CFArrayRef ocspResponsesData = SecPathBuilderCopyOCSPResponses(rvc->builder); | |
479 | ||
480 | if(ocspResponsesData) { | |
481 | secdebug("rvc", "Checking stapled responses for cert %ld", rvc->certIX); | |
482 | CFArrayForEach(ocspResponsesData, ^(const void *value) { | |
483 | SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value); | |
484 | SecORVCConsumeOCSPResponse(rvc, ocspResponse, NULL_TIME, false); | |
485 | }); | |
486 | CFRelease(ocspResponsesData); | |
487 | } | |
488 | } | |
489 | ||
490 | // MARK: SecCRVCRef | |
491 | /******************************************************** | |
492 | ******************* CRL RVC Functions ****************** | |
493 | ********************************************************/ | |
494 | #if ENABLE_CRLS | |
495 | #include <../trustd/macOS/SecTrustOSXEntryPoints.h> | |
496 | #define kSecDefaultCRLTTL kSecDefaultOCSPResponseTTL | |
497 | ||
498 | /* CRL Revocation verification context. */ | |
499 | struct OpaqueSecCRVC { | |
500 | /* Response data from ocspd. Yes, ocspd does CRLs, but not OCSP... */ | |
501 | async_ocspd_t async_ocspd; | |
502 | ||
503 | /* Pointer to the builder for this revocation check. */ | |
504 | SecPathBuilderRef builder; | |
505 | ||
506 | /* Pointer to the generic rvc for this revocation check */ | |
507 | SecRVCRef rvc; | |
508 | ||
509 | /* The current CRL status from ocspd. */ | |
510 | OSStatus status; | |
511 | ||
512 | /* Index of cert in builder that this RVC is for 0 = leaf, etc. */ | |
513 | CFIndex certIX; | |
514 | ||
515 | /* Index in array returned by SecCertificateGetCRLDistributionPoints() for | |
516 | current distribution point. */ | |
517 | CFIndex distributionPointIX; | |
518 | ||
519 | /* URL of current distribution point. */ | |
520 | CFURLRef distributionPoint; | |
521 | ||
522 | /* Date until which this revocation status is valid. */ | |
523 | CFAbsoluteTime nextUpdate; | |
524 | ||
525 | bool done; | |
526 | }; | |
527 | ||
528 | static void SecCRVCFinish(SecCRVCRef crvc) { | |
529 | // nothing yet | |
530 | } | |
531 | ||
532 | #define MAX_CRL_DPS 3 | |
533 | #define CRL_REQUEST_THRESHOLD 10 | |
534 | ||
535 | static CFURLRef SecCRVCGetNextDistributionPoint(SecCRVCRef rvc) { | |
536 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
537 | CFArrayRef crlDPs = SecCertificateGetCRLDistributionPoints(cert); | |
538 | if (crlDPs) { | |
539 | CFIndex crlDPCount = CFArrayGetCount(crlDPs); | |
540 | if (crlDPCount >= CRL_REQUEST_THRESHOLD) { | |
541 | secnotice("rvc", "too many CRL DP entries (%ld)", (long)crlDPCount); | |
542 | return NULL; | |
543 | } | |
544 | while (rvc->distributionPointIX < crlDPCount && rvc->distributionPointIX < MAX_CRL_DPS) { | |
545 | CFURLRef distributionPoint = CFArrayGetValueAtIndex(crlDPs, rvc->distributionPointIX); | |
546 | rvc->distributionPointIX++; | |
547 | CFStringRef scheme = CFURLCopyScheme(distributionPoint); | |
548 | if (scheme) { | |
549 | /* We only support http and https responders currently. */ | |
550 | bool valid_DP = (CFEqual(CFSTR("http"), scheme) || | |
551 | CFEqual(CFSTR("https"), scheme) || | |
552 | CFEqual(CFSTR("ldap"), scheme)); | |
553 | CFRelease(scheme); | |
554 | if (valid_DP) | |
555 | return distributionPoint; | |
556 | } | |
557 | } | |
558 | } | |
559 | return NULL; | |
560 | } | |
561 | ||
562 | static void SecCRVCGetCRLStatus(SecCRVCRef rvc) { | |
563 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
564 | SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder); | |
ecaf5866 | 565 | CFArrayRef serializedCertPath = SecCertificatePathVCCreateSerialized(path); |
866f8763 A |
566 | secdebug("rvc", "searching CRL cache for cert: %ld", rvc->certIX); |
567 | rvc->status = SecTrustLegacyCRLStatus(cert, serializedCertPath, rvc->distributionPoint); | |
568 | CFReleaseNull(serializedCertPath); | |
569 | /* we got a response indicating that the CRL was checked */ | |
570 | if (rvc->status == errSecSuccess || rvc->status == errSecCertificateRevoked) { | |
571 | rvc->done = true; | |
572 | /* ocspd doesn't give us the nextUpdate time, so set to default */ | |
573 | rvc->nextUpdate = SecPathBuilderGetVerifyTime(rvc->builder) + kSecDefaultCRLTTL; | |
574 | } | |
575 | } | |
576 | ||
577 | static void SecCRVCCheckRevocationCache(SecCRVCRef rvc) { | |
578 | while ((rvc->distributionPoint = SecCRVCGetNextDistributionPoint(rvc))) { | |
579 | SecCRVCGetCRLStatus(rvc); | |
580 | if (rvc->status == errSecCertificateRevoked) { | |
581 | return; | |
582 | } | |
583 | } | |
584 | } | |
585 | ||
586 | /* Fire off an async http request for this certs revocation status, return | |
587 | false if request was queued, true if we're done. */ | |
588 | static bool SecCRVCFetchNext(SecCRVCRef rvc) { | |
589 | while ((rvc->distributionPoint = SecCRVCGetNextDistributionPoint(rvc))) { | |
590 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
591 | SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder); | |
ecaf5866 | 592 | CFArrayRef serializedCertPath = SecCertificatePathVCCreateSerialized(path); |
866f8763 A |
593 | secinfo("rvc", "fetching CRL for cert: %ld", rvc->certIX); |
594 | if (!SecTrustLegacyCRLFetch(&rvc->async_ocspd, rvc->distributionPoint, | |
595 | CFAbsoluteTimeGetCurrent(), cert, serializedCertPath)) { | |
596 | CFDataRef clientAuditToken = NULL; | |
597 | SecTaskRef task = NULL; | |
598 | audit_token_t auditToken = {}; | |
599 | clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder); | |
600 | require(clientAuditToken, out); | |
601 | require(sizeof(auditToken) == CFDataGetLength(clientAuditToken), out); | |
602 | CFDataGetBytes(clientAuditToken, CFRangeMake(0, sizeof(auditToken)), (uint8_t *)&auditToken); | |
603 | require(task = SecTaskCreateWithAuditToken(NULL, auditToken), out); | |
604 | secnotice("rvc", "asynchronously fetching CRL (%@) for client (%@)", | |
605 | rvc->distributionPoint, task); | |
606 | ||
607 | out: | |
608 | CFReleaseNull(clientAuditToken); | |
609 | CFReleaseNull(task); | |
610 | /* Async request was posted, wait for reply. */ | |
611 | return false; | |
612 | } | |
613 | } | |
614 | rvc->done = true; | |
615 | return true; | |
616 | } | |
617 | ||
618 | static void SecCRVCUpdatePVC(SecCRVCRef rvc) { | |
619 | if (rvc->status == errSecCertificateRevoked) { | |
620 | secdebug("rvc", "CRL revoked cert %" PRIdCFIndex, rvc->certIX); | |
621 | SInt32 reason = 0; // unspecified, since ocspd didn't tell us | |
622 | CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason); | |
623 | SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX, | |
ecaf5866 | 624 | cfreason, true); |
866f8763 A |
625 | if (rvc->builder) { |
626 | CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder); | |
627 | if (info) { | |
628 | /* make the revocation reason available in the trust result */ | |
629 | CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason); | |
630 | } | |
631 | } | |
632 | CFReleaseNull(cfreason); | |
633 | } | |
634 | } | |
635 | ||
636 | static void SecCRVCFetchCompleted(async_ocspd_t *ocspd) { | |
637 | SecCRVCRef rvc = ocspd->info; | |
638 | SecPathBuilderRef builder = rvc->builder; | |
ecaf5866 A |
639 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder); |
640 | if (analytics) { | |
641 | /* Add the time this fetch took to complete to the total time */ | |
642 | analytics->crl_fetch_time += (mach_absolute_time() - ocspd->start_time); | |
643 | } | |
866f8763 A |
644 | /* we got a response indicating that the CRL was checked */ |
645 | if (ocspd->response == errSecSuccess || ocspd->response == errSecCertificateRevoked) { | |
646 | rvc->status = ocspd->response; | |
647 | rvc->done = true; | |
648 | /* ocspd doesn't give us the nextUpdate time, so set to default */ | |
649 | rvc->nextUpdate = SecPathBuilderGetVerifyTime(rvc->builder) + kSecDefaultCRLTTL; | |
650 | secdebug("rvc", "got CRL response for cert: %ld", rvc->certIX); | |
651 | SecCRVCUpdatePVC(rvc); | |
652 | if (!SecPathBuilderDecrementAsyncJobCount(builder)) { | |
653 | secdebug("rvc", "done with all async jobs"); | |
654 | SecPathBuilderStep(builder); | |
655 | } | |
656 | } else { | |
ecaf5866 A |
657 | if (analytics) { |
658 | /* We didn't get any data back, so the fetch failed */ | |
659 | analytics->crl_fetch_failed++; | |
660 | } | |
866f8763 A |
661 | if(SecCRVCFetchNext(rvc)) { |
662 | if (!SecPathBuilderDecrementAsyncJobCount(builder)) { | |
663 | secdebug("rvc", "done with all async jobs"); | |
664 | SecPathBuilderStep(builder); | |
665 | } | |
666 | } | |
667 | } | |
668 | } | |
669 | ||
670 | static SecCRVCRef SecCRVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) { | |
671 | SecCRVCRef crvc = NULL; | |
672 | crvc = malloc(sizeof(struct OpaqueSecCRVC)); | |
673 | if (crvc) { | |
674 | memset(crvc, 0, sizeof(struct OpaqueSecCRVC)); | |
675 | crvc->builder = builder; | |
676 | crvc->rvc = rvc; | |
677 | crvc->certIX = certIX; | |
678 | crvc->status = errSecInternal; | |
679 | crvc->distributionPointIX = 0; | |
680 | crvc->distributionPoint = NULL; | |
681 | crvc->nextUpdate = NULL_TIME; | |
682 | crvc->async_ocspd.queue = SecPathBuilderGetQueue(builder); | |
683 | crvc->async_ocspd.completed = SecCRVCFetchCompleted; | |
684 | crvc->async_ocspd.response = errSecInternal; | |
685 | crvc->async_ocspd.info = crvc; | |
686 | crvc->done = false; | |
687 | } | |
688 | return crvc; | |
689 | } | |
690 | ||
691 | static bool SecRVCShouldCheckCRL(SecRVCRef rvc) { | |
692 | CFStringRef revocation_method = SecPathBuilderGetRevocationMethod(rvc->builder); | |
ecaf5866 | 693 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder); |
866f8763 A |
694 | if (revocation_method && |
695 | CFEqual(kSecPolicyCheckRevocationCRL, revocation_method)) { | |
696 | /* Our client insists on CRLs */ | |
697 | secinfo("rvc", "client told us to check CRL"); | |
ecaf5866 A |
698 | if (analytics) { |
699 | analytics->crl_client = true; | |
700 | } | |
866f8763 A |
701 | return true; |
702 | } | |
703 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
704 | CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert); | |
705 | if ((!ocspResponders || CFArrayGetCount(ocspResponders) == 0) && | |
706 | (revocation_method && !CFEqual(kSecPolicyCheckRevocationOCSP, revocation_method))) { | |
707 | /* The cert doesn't have OCSP responders and the client didn't specifically ask for OCSP. | |
708 | * This logic will skip the CRL cache check if the client didn't ask for revocation checking */ | |
709 | secinfo("rvc", "client told us to check revocation and CRL is only option for cert: %ld", rvc->certIX); | |
ecaf5866 A |
710 | if (analytics) { |
711 | analytics->crl_cert = true; | |
712 | } | |
866f8763 A |
713 | return true; |
714 | } | |
715 | return false; | |
716 | } | |
717 | #endif /* ENABLE_CRLS */ | |
718 | ||
719 | void SecRVCDelete(SecRVCRef rvc) { | |
720 | if (rvc->orvc) { | |
721 | SecORVCFinish(rvc->orvc); | |
722 | free(rvc->orvc); | |
dd5fb164 | 723 | rvc->orvc = NULL; |
866f8763 A |
724 | } |
725 | #if ENABLE_CRLS | |
726 | if (rvc->crvc) { | |
727 | SecCRVCFinish(rvc->crvc); | |
728 | free(rvc->crvc); | |
dd5fb164 | 729 | rvc->crvc = NULL; |
866f8763 A |
730 | } |
731 | #endif | |
732 | if (rvc->valid_info) { | |
733 | SecValidInfoRelease(rvc->valid_info); | |
dd5fb164 | 734 | rvc->valid_info = NULL; |
866f8763 A |
735 | } |
736 | } | |
737 | ||
738 | static void SecRVCInit(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) { | |
739 | secdebug("alloc", "%p", rvc); | |
740 | rvc->builder = builder; | |
741 | rvc->certIX = certIX; | |
742 | rvc->orvc = SecORVCCreate(rvc, builder, certIX); | |
743 | #if ENABLE_CRLS | |
744 | rvc->crvc = SecCRVCCreate(rvc, builder, certIX); | |
745 | #endif | |
dd5fb164 A |
746 | if (!rvc->orvc |
747 | #if ENABLE_CRLS | |
748 | || !rvc->crvc | |
749 | #endif | |
750 | ) { | |
751 | SecRVCDelete(rvc); | |
752 | rvc->done = true; | |
753 | } else { | |
754 | rvc->done = false; | |
755 | } | |
866f8763 A |
756 | } |
757 | ||
758 | #if ENABLE_CRLS | |
759 | static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) { | |
760 | CFStringRef revocation_method = SecPathBuilderGetRevocationMethod(rvc->builder); | |
761 | if (!revocation_method | |
762 | || !CFEqual(revocation_method, kSecPolicyCheckRevocationCRL)) { | |
763 | return true; | |
764 | } | |
765 | return false; | |
766 | } | |
767 | #else | |
768 | static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) { | |
769 | return true; | |
770 | } | |
771 | #endif | |
772 | ||
ecaf5866 | 773 | static void SecRVCProcessValidDateConstraints(SecRVCRef rvc) { |
866f8763 A |
774 | if (!rvc || !rvc->valid_info || !rvc->builder) { |
775 | return; | |
776 | } | |
ecaf5866 A |
777 | if (!rvc->valid_info->hasDateConstraints) { |
778 | return; | |
779 | } | |
780 | SecCertificateRef certificate = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
781 | if (!certificate) { | |
782 | return; | |
783 | } | |
784 | CFAbsoluteTime certIssued = SecCertificateNotValidBefore(certificate); | |
785 | CFAbsoluteTime caNotBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */ | |
786 | CFAbsoluteTime caNotAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */ | |
787 | if (rvc->valid_info->notBeforeDate) { | |
788 | caNotBefore = CFDateGetAbsoluteTime(rvc->valid_info->notBeforeDate); | |
789 | } | |
790 | if (rvc->valid_info->notAfterDate) { | |
791 | caNotAfter = CFDateGetAbsoluteTime(rvc->valid_info->notAfterDate); | |
792 | /* per the Valid specification, if this date is in the past, we need to check CT. */ | |
793 | CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); | |
794 | if (caNotAfter < now) { | |
795 | rvc->valid_info->requireCT = true; | |
796 | } | |
797 | } | |
798 | if ((certIssued < caNotBefore) && (rvc->certIX > 0)) { | |
799 | /* not-before constraint is only applied to leaf certificate, for now. */ | |
800 | return; | |
801 | } | |
802 | ||
803 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder); | |
804 | if ((certIssued < caNotBefore) || (certIssued > caNotAfter)) { | |
805 | /* We are outside the constrained validity period. */ | |
806 | secnotice("rvc", "certificate issuance date not within the allowed range for this CA%s", | |
807 | (rvc->valid_info->overridable) ? "" : " (non-recoverable error)"); | |
808 | if (analytics) { | |
809 | analytics->valid_status |= TAValidDateContrainedRevoked; | |
810 | } | |
811 | if (rvc->valid_info->overridable) { | |
812 | /* error is recoverable, treat certificate as untrusted | |
813 | (note this date check is different from kSecPolicyCheckTemporalValidity) */ | |
814 | SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedKey, rvc->certIX, | |
815 | kCFBooleanFalse, true); | |
816 | } else { | |
817 | /* error is non-overridable, treat certificate as revoked */ | |
818 | SInt32 reason = 0; /* unspecified reason code */ | |
866f8763 A |
819 | CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason); |
820 | SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX, | |
ecaf5866 | 821 | cfreason, true); |
866f8763 A |
822 | CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder); |
823 | if (info) { | |
824 | /* make the revocation reason available in the trust result */ | |
825 | CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason); | |
826 | } | |
827 | CFReleaseNull(cfreason); | |
866f8763 | 828 | } |
ecaf5866 A |
829 | } else if (analytics) { |
830 | analytics->valid_status |= TAValidDateConstrainedOK; | |
831 | } | |
832 | } | |
833 | ||
834 | bool SecRVCHasDefinitiveValidInfo(SecRVCRef rvc) { | |
835 | if (!rvc || !rvc->valid_info) { | |
836 | return false; | |
837 | } | |
838 | SecValidInfoRef info = rvc->valid_info; | |
839 | /* outcomes as defined in Valid server specification */ | |
840 | if (info->format == kSecValidInfoFormatSerial || | |
841 | info->format == kSecValidInfoFormatSHA256) { | |
842 | if (info->noCACheck || info->complete || info->isOnList) { | |
843 | return true; | |
844 | } | |
845 | } else { /* info->format == kSecValidInfoFormatNto1 */ | |
846 | if (info->noCACheck || (info->complete && !info->isOnList)) { | |
847 | return true; | |
848 | } | |
849 | } | |
850 | return false; | |
851 | } | |
852 | ||
853 | bool SecRVCHasRevokedValidInfo(SecRVCRef rvc) { | |
854 | if (!rvc || !rvc->valid_info) { | |
855 | return false; | |
856 | } | |
857 | SecValidInfoRef info = rvc->valid_info; | |
858 | /* either not present on an allowlist, or present on a blocklist */ | |
859 | return (!info->isOnList && info->valid) || (info->isOnList && !info->valid); | |
860 | } | |
861 | ||
862 | void SecRVCSetRevokedResult(SecRVCRef rvc) { | |
863 | if (!rvc || !rvc->valid_info || !rvc->builder) { | |
864 | return; | |
865 | } | |
866 | if (rvc->valid_info->overridable) { | |
867 | /* error is recoverable, treat certificate as untrusted */ | |
868 | SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedKey, rvc->certIX, | |
869 | kCFBooleanFalse, true); | |
870 | return; | |
871 | } | |
872 | /* error is fatal, treat certificate as revoked */ | |
873 | SInt32 reason = 0; /* unspecified, since the Valid db doesn't tell us */ | |
874 | CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason); | |
875 | SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX, | |
876 | cfreason, true); | |
877 | CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder); | |
878 | if (info) { | |
879 | /* make the revocation reason available in the trust result */ | |
880 | CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason); | |
881 | } | |
882 | CFReleaseNull(cfreason); | |
883 | } | |
884 | ||
885 | static void SecRVCProcessValidInfoResults(SecRVCRef rvc) { | |
886 | if (!rvc || !rvc->valid_info || !rvc->builder) { | |
887 | return; | |
888 | } | |
889 | SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder); | |
890 | SecValidInfoRef info = rvc->valid_info; | |
891 | ||
892 | bool definitive = SecRVCHasDefinitiveValidInfo(rvc); | |
893 | bool revoked = SecRVCHasRevokedValidInfo(rvc); | |
894 | ||
895 | /* set analytics */ | |
896 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder); | |
897 | if (analytics) { | |
898 | if (revoked) { | |
899 | analytics->valid_status |= definitive ? TAValidDefinitelyRevoked : TAValidProbablyRevoked; | |
900 | } else { | |
901 | analytics->valid_status |= definitive ? TAValidDefinitelyOK : TAValidProbablyOK; | |
902 | } | |
903 | } | |
904 | ||
905 | /* Handle no-ca cases */ | |
906 | if (info->noCACheck) { | |
907 | bool allowed = (info->valid && info->complete && info->isOnList); | |
908 | if (revoked) { | |
909 | /* definitely revoked */ | |
910 | SecRVCSetRevokedResult(rvc); | |
911 | } else if (allowed) { | |
866f8763 | 912 | /* definitely not revoked (allowlisted) */ |
ecaf5866 | 913 | SecCertificatePathVCSetIsAllowlisted(path, true); |
866f8763 | 914 | } |
ecaf5866 A |
915 | /* no-ca is definitive; no need to check further. */ |
916 | secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex, | |
917 | (allowed) ? "allowed" : "revoked", rvc->certIX); | |
918 | rvc->done = true; | |
919 | return; | |
920 | } | |
921 | ||
922 | /* Handle date constraints, if present. | |
923 | * Note: a not-after date may set the CT requirement, | |
924 | * so check requireCT after this function is called. */ | |
925 | SecRVCProcessValidDateConstraints(rvc); | |
926 | ||
927 | /* Set CT requirement on path, if present. */ | |
928 | if (info->requireCT) { | |
929 | if (analytics) { | |
930 | analytics->valid_require_ct |= info->requireCT; | |
866f8763 | 931 | } |
ecaf5866 A |
932 | SecPathCTPolicy ctp = kSecPathCTRequired; |
933 | if (info->overridable) { | |
934 | ctp = kSecPathCTRequiredOverridable; | |
935 | } | |
936 | SecCertificatePathVCSetRequiresCT(path, ctp); | |
866f8763 A |
937 | } |
938 | ||
ecaf5866 A |
939 | /* Trigger OCSP for any non-definitive or revoked cases */ |
940 | if (!definitive || revoked) { | |
941 | info->checkOCSP = true; | |
866f8763 A |
942 | } |
943 | ||
ecaf5866 A |
944 | if (info->checkOCSP) { |
945 | if (analytics) { | |
946 | /* Valid DB results caused us to do OCSP */ | |
947 | analytics->valid_trigger_ocsp = true; | |
948 | } | |
866f8763 A |
949 | CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder); |
950 | CFIndex issuerIX = rvc->certIX + 1; | |
951 | if (issuerIX >= count) { | |
952 | /* cannot perform a revocation check on the last cert in the | |
953 | chain, since we don't have its issuer. */ | |
954 | return; | |
955 | } | |
956 | CFIndex pvcIX; | |
957 | for (pvcIX = 0; pvcIX < SecPathBuilderGetPVCCount(rvc->builder); pvcIX++) { | |
958 | SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, pvcIX); | |
959 | if (!pvc) { continue; } | |
960 | SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, 0); | |
961 | CFStringRef policyName = (policy) ? SecPolicyGetName(policy) : NULL; | |
962 | if (policyName && CFEqual(CFSTR("sslServer"), policyName)) { | |
963 | /* perform revocation check for SSL policy; | |
964 | require for leaf if an OCSP responder is present. */ | |
965 | if (0 == rvc->certIX) { | |
966 | SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
967 | CFArrayRef resps = (cert) ? SecCertificateGetOCSPResponders(cert) : NULL; | |
968 | CFIndex rcount = (resps) ? CFArrayGetCount(resps) : 0; | |
969 | if (rcount > 0) { | |
970 | // %%% rdar://31279923 | |
971 | // This currently requires a valid revocation response for each cert, | |
972 | // but we only want to require a leaf check. For now, do not require. | |
973 | //SecPathBuilderSetRevocationResponseRequired(rvc->builder); | |
974 | } | |
975 | } | |
976 | secdebug("validupdate", "rvc: %s%s cert %" PRIdCFIndex " (will check OCSP)", | |
ecaf5866 | 977 | (info->complete) ? "" : "possibly ", (info->valid) ? "allowed" : "revoked", |
866f8763 A |
978 | rvc->certIX); |
979 | SecPathBuilderSetRevocationMethod(rvc->builder, kSecPolicyCheckRevocationAny); | |
980 | } | |
981 | } | |
982 | } | |
983 | } | |
984 | ||
985 | static bool SecRVCCheckValidInfoDatabase(SecRVCRef rvc) { | |
986 | /* Skip checking for OCSP Signer verification */ | |
987 | if (SecPathBuilderGetPVCCount(rvc->builder) == 1) { | |
988 | SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, 0); | |
989 | if (!pvc) { return false; } | |
990 | SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, 0); | |
991 | CFStringRef policyName = (policy) ? SecPolicyGetName(policy) : NULL; | |
992 | if (policyName && CFEqual(policyName, CFSTR("OCSPSigner"))) { | |
993 | return false; | |
994 | } | |
995 | } | |
996 | ||
997 | /* Make sure revocation db info is up-to-date. | |
998 | * We don't care if the builder is allowed to access the network because | |
999 | * the network fetching does not block the trust evaluation. */ | |
1000 | SecRevocationDbCheckNextUpdate(); | |
1001 | ||
1002 | /* Check whether we have valid db info for this cert, | |
1003 | given the cert and its issuer */ | |
1004 | SecValidInfoRef info = NULL; | |
1005 | CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder); | |
1006 | if (count) { | |
1007 | bool isSelfSigned = false; | |
1008 | SecCertificateRef cert = NULL; | |
1009 | SecCertificateRef issuer = NULL; | |
1010 | CFIndex issuerIX = rvc->certIX + 1; | |
1011 | if (count > issuerIX) { | |
1012 | issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, issuerIX); | |
1013 | } else if (count == issuerIX) { | |
1014 | CFIndex rootIX = SecCertificatePathVCSelfSignedIndex(SecPathBuilderGetPath(rvc->builder)); | |
1015 | if (rootIX == rvc->certIX) { | |
1016 | issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, rootIX); | |
1017 | isSelfSigned = true; | |
1018 | } | |
1019 | } | |
1020 | cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX); | |
1021 | if (!isSelfSigned) { | |
1022 | /* skip revocation db check for self-signed certificates [33137065] */ | |
1023 | info = SecRevocationDbCopyMatching(cert, issuer); | |
1024 | } | |
1025 | SecValidInfoSetAnchor(info, SecPathBuilderGetCertificateAtIndex(rvc->builder, count-1)); | |
1026 | } | |
1027 | if (info) { | |
1028 | SecValidInfoRef old_info = rvc->valid_info; | |
1029 | rvc->valid_info = info; | |
1030 | if (old_info) { | |
1031 | SecValidInfoRelease(old_info); | |
1032 | } | |
1033 | return true; | |
1034 | } | |
1035 | return false; | |
1036 | } | |
1037 | ||
1038 | static void SecRVCCheckRevocationCaches(SecRVCRef rvc) { | |
1039 | /* Don't check OCSP cache if CRLs enabled and policy requested CRL only */ | |
1040 | if (SecRVCShouldCheckOCSP(rvc) && (rvc->orvc->ocspRequest)) { | |
1041 | secdebug("ocsp", "Checking cached responses for cert %ld", rvc->certIX); | |
1042 | SecOCSPResponseRef response = NULL; | |
1043 | if (SecPathBuilderGetCheckRevocationOnline(rvc->builder)) { | |
1044 | CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); | |
1045 | response = SecOCSPCacheCopyMatchingWithMinInsertTime(rvc->orvc->ocspRequest, NULL, now - kSecOCSPResponseOnlineTTL); | |
1046 | } else { | |
1047 | response = SecOCSPCacheCopyMatching(rvc->orvc->ocspRequest, NULL); | |
1048 | } | |
1049 | SecORVCConsumeOCSPResponse(rvc->orvc, | |
1050 | response, | |
1051 | NULL_TIME, false); | |
ecaf5866 A |
1052 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder); |
1053 | if (rvc->orvc->done && analytics) { | |
1054 | /* We found a valid OCSP response in the cache */ | |
1055 | analytics->ocsp_cache_hit = true; | |
1056 | } | |
866f8763 A |
1057 | } |
1058 | #if ENABLE_CRLS | |
1059 | /* Don't check CRL cache if policy requested OCSP only */ | |
1060 | if (SecRVCShouldCheckCRL(rvc)) { | |
1061 | SecCRVCCheckRevocationCache(rvc->crvc); | |
1062 | } | |
1063 | #endif | |
1064 | } | |
1065 | ||
1066 | static void SecRVCUpdatePVC(SecRVCRef rvc) { | |
1067 | SecRVCProcessValidInfoResults(rvc); /* restore the results we got from Valid */ | |
dd5fb164 | 1068 | if (rvc->orvc) { SecORVCUpdatePVC(rvc->orvc); } |
866f8763 | 1069 | #if ENABLE_CRLS |
dd5fb164 | 1070 | if (rvc->crvc) { SecCRVCUpdatePVC(rvc->crvc); } |
866f8763 A |
1071 | #endif |
1072 | } | |
1073 | ||
1074 | static bool SecRVCFetchNext(SecRVCRef rvc) { | |
1075 | bool OCSP_fetch_finished = true; | |
ecaf5866 | 1076 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder); |
866f8763 A |
1077 | /* Don't send OCSP request only if CRLs enabled and policy requested CRL only */ |
1078 | if (SecRVCShouldCheckOCSP(rvc)) { | |
1079 | OCSP_fetch_finished &= SecORVCFetchNext(rvc->orvc); | |
1080 | } | |
1081 | if (OCSP_fetch_finished) { | |
1082 | /* we didn't start an OCSP background job for this cert */ | |
1083 | (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder); | |
ecaf5866 A |
1084 | } else if (analytics) { |
1085 | /* We did a network OCSP fetch, set report appropriately */ | |
1086 | analytics->ocsp_network = true; | |
1087 | analytics->ocsp_fetches++; | |
866f8763 A |
1088 | } |
1089 | ||
1090 | #if ENABLE_CRLS | |
1091 | bool CRL_fetch_finished = true; | |
1092 | /* Don't check CRL cache if policy requested OCSP only */ | |
1093 | if (SecRVCShouldCheckCRL(rvc)) { | |
1094 | /* reset the distributionPointIX because we already iterated through the CRLDPs | |
1095 | * in SecCRVCCheckRevocationCache */ | |
1096 | rvc->crvc->distributionPointIX = 0; | |
1097 | CRL_fetch_finished &= SecCRVCFetchNext(rvc->crvc); | |
1098 | } | |
1099 | if (CRL_fetch_finished) { | |
1100 | /* we didn't start a CRL background job for this cert */ | |
1101 | (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder); | |
ecaf5866 A |
1102 | } else if (analytics) { |
1103 | /* We did a CRL fetch */ | |
1104 | analytics->crl_fetches++; | |
866f8763 A |
1105 | } |
1106 | OCSP_fetch_finished &= CRL_fetch_finished; | |
1107 | #endif | |
1108 | ||
1109 | return OCSP_fetch_finished; | |
1110 | } | |
1111 | ||
29734401 A |
1112 | /* The SecPathBuilder state machine calls SecPathBuilderCheckRevocation twice -- |
1113 | * once in the ValidatePath state, and again in the ComputeDetails state. In the | |
1114 | * ValidatePath state we've not yet run the path checks, so for callers who set | |
1115 | * kSecRevocationCheckIfTrusted, we don't do any networking on that first call. | |
1116 | * Here, if we've already done revocation before (so we're in ComputeDetails now), | |
1117 | * we need to recheck (and enable networking) for trusted chains and | |
1118 | * kSecRevocationCheckIfTrusted. Otherwise, we skip the checks to save on the processing | |
1119 | * but update the PVCs with the revocation results from the last check. */ | |
1120 | static bool SecRevocationDidCheckRevocation(SecPathBuilderRef builder, bool *first_check_done) { | |
1121 | SecCertificatePathVCRef path = SecPathBuilderGetPath(builder); | |
1122 | if (!SecCertificatePathVCIsRevocationDone(path)) { | |
1123 | return false; | |
1124 | } | |
1125 | if (first_check_done) { | |
1126 | *first_check_done = true; | |
1127 | } | |
1128 | ||
1129 | SecPVCRef resultPVC = SecPathBuilderGetResultPVC(builder); | |
1130 | bool recheck = false; | |
1131 | if (SecPathBuilderGetCheckRevocationIfTrusted(builder) && SecPVCIsOkResult(resultPVC)) { | |
1132 | recheck = true; | |
1133 | secdebug("rvc", "Rechecking revocation because network now allowed"); | |
1134 | } else { | |
1135 | secdebug("rvc", "Not rechecking revocation"); | |
1136 | } | |
1137 | ||
1138 | CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder); | |
1139 | for (certIX = 0; certIX < certCount; ++certIX) { | |
1140 | SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX); | |
1141 | if (rvc && !recheck) { | |
1142 | SecRVCUpdatePVC(rvc); | |
1143 | } else if (rvc) { | |
1144 | SecRVCDelete(rvc); // reset the RVC for the second pass | |
1145 | } | |
1146 | } | |
1147 | return !recheck; | |
1148 | } | |
1149 | ||
1150 | static bool SecRevocationCanAccessNetwork(SecPathBuilderRef builder, bool first_check_done) { | |
1151 | /* CheckRevocationIfTrusted overrides NoNetworkAccess for revocation */ | |
1152 | if (SecPathBuilderGetCheckRevocationIfTrusted(builder)) { | |
1153 | if (first_check_done) { | |
1154 | /* We're on the second pass. We need to now allow networking for revocation. | |
1155 | * SecRevocationDidCheckRevocation takes care of not running a second pass | |
1156 | * if the chain isn't trusted. */ | |
1157 | return true; | |
1158 | } else { | |
1159 | /* We're on the first pass of the revocation checks, where we aren't | |
1160 | * supposed to do networking because we don't know if the chain | |
1161 | * is trusted yet. */ | |
1162 | return false; | |
1163 | } | |
1164 | } | |
1165 | return SecPathBuilderCanAccessNetwork(builder); | |
1166 | } | |
1167 | ||
866f8763 A |
1168 | bool SecPathBuilderCheckRevocation(SecPathBuilderRef builder) { |
1169 | secdebug("rvc", "checking revocation"); | |
1170 | CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder); | |
1171 | SecCertificatePathVCRef path = SecPathBuilderGetPath(builder); | |
1172 | bool completed = true; | |
1173 | if (certCount <= 1) { | |
1174 | /* Can't verify without an issuer; we're done */ | |
1175 | return completed; | |
1176 | } | |
1177 | ||
29734401 A |
1178 | bool first_check_done = false; |
1179 | if (SecRevocationDidCheckRevocation(builder, &first_check_done)) { | |
866f8763 A |
1180 | return completed; |
1181 | } | |
1182 | ||
1183 | /* Setup things so we check revocation status of all certs. */ | |
1184 | SecCertificatePathVCAllocateRVCs(path, certCount); | |
1185 | ||
1186 | /* Note that if we are multi threaded and a job completes after it | |
1187 | is started but before we return from this function, we don't want | |
1188 | a callback to decrement asyncJobCount to zero before we finish issuing | |
1189 | all the jobs. To avoid this we pretend we issued certCount-1 async jobs, | |
1190 | and decrement pvc->asyncJobCount for each cert that we don't start a | |
1191 | background fetch for. (We will never start an async job for the final | |
1192 | cert in the chain.) */ | |
1193 | #if !ENABLE_CRLS | |
1194 | SecPathBuilderSetAsyncJobCount(builder, (unsigned int)(certCount-1)); | |
1195 | #else | |
1196 | /* If we enable CRLS, we may end up with two async jobs per cert: one | |
1197 | * for OCSP and one for fetching the CRL */ | |
1198 | SecPathBuilderSetAsyncJobCount(builder, 2 * (unsigned int)(certCount-1)); | |
1199 | #endif | |
1200 | ||
1201 | /* Loop though certificates again and issue an ocsp fetch if the | |
1202 | revocation status checking isn't done yet (and we have an issuer!) */ | |
1203 | for (certIX = 0; certIX < certCount; ++certIX) { | |
1204 | secdebug("rvc", "checking revocation for cert: %ld", certIX); | |
1205 | SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX); | |
1206 | if (!rvc) { | |
1207 | continue; | |
1208 | } | |
1209 | ||
1210 | SecRVCInit(rvc, builder, certIX); | |
ecaf5866 A |
1211 | |
1212 | /* RFC 6960: OCSP No-Check extension says that we shouldn't check revocation. */ | |
1213 | if (SecCertificateHasMarkerExtension(SecCertificatePathVCGetCertificateAtIndex(path, certIX), | |
1214 | CFSTR("1.3.6.1.5.5.7.48.1.5"))) // id-pkix-ocsp-nocheck | |
1215 | { | |
1216 | secdebug("rvc", "skipping revocation checks for no-check cert: %ld", certIX); | |
1217 | TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder); | |
1218 | if (analytics) { | |
1219 | /* This certificate has OCSP No-Check, so add to reporting analytics */ | |
1220 | analytics->ocsp_no_check = true; | |
1221 | } | |
1222 | rvc->done = true; | |
1223 | } | |
1224 | ||
1225 | if (rvc->done) { | |
866f8763 A |
1226 | continue; |
1227 | } | |
1228 | ||
1229 | #if !TARGET_OS_BRIDGE | |
1230 | /* Check valid database first (separate from OCSP response cache) */ | |
1231 | if (SecRVCCheckValidInfoDatabase(rvc)) { | |
1232 | SecRVCProcessValidInfoResults(rvc); | |
1233 | } | |
1234 | #endif | |
29734401 | 1235 | /* Any other revocation method requires an issuer certificate to verify the response; |
866f8763 A |
1236 | * skip the last cert in the chain since it doesn't have one. */ |
1237 | if (certIX+1 >= certCount) { | |
1238 | continue; | |
1239 | } | |
1240 | ||
1241 | /* Ignore stapled OCSP responses only if CRLs are enabled and the | |
1242 | * policy specifically requested CRLs only. */ | |
1243 | if (SecRVCShouldCheckOCSP(rvc)) { | |
1244 | /* If we have any OCSP stapled responses, check those first */ | |
1245 | SecORVCProcessStapledResponses(rvc->orvc); | |
1246 | } | |
1247 | ||
1248 | #if TARGET_OS_BRIDGE | |
1249 | /* The bridge has no writeable storage and no network. Nothing else we can | |
1250 | * do here. */ | |
1251 | rvc->done = true; | |
1252 | return completed; | |
1253 | #endif | |
1254 | ||
1255 | /* Then check the caches for revocation results. */ | |
1256 | SecRVCCheckRevocationCaches(rvc); | |
1257 | ||
1258 | /* The check is done if we found cached responses from either method. */ | |
1259 | if (rvc->orvc->done | |
1260 | #if ENABLE_CRLS | |
dd5fb164 | 1261 | || rvc->crvc->done |
866f8763 A |
1262 | #endif |
1263 | ) { | |
1264 | secdebug("rvc", "found cached response for cert: %ld", certIX); | |
1265 | rvc->done = true; | |
1266 | } | |
1267 | ||
1268 | /* If we got a cached response that is no longer valid (which can only be true for | |
1269 | * revoked responses), let's try to get a fresher response even if no one asked. | |
1270 | * This check resolves unrevocation events after the nextUpdate time. */ | |
1271 | bool old_cached_response = (!rvc->done && rvc->orvc->ocspResponse); | |
1272 | ||
1273 | /* If the cert is EV or if revocation checking was explicitly enabled, attempt to fire off an | |
1274 | async http request for this cert's revocation status, unless we already successfully checked | |
1275 | the revocation status of this cert based on the cache or stapled responses. */ | |
29734401 | 1276 | bool allow_fetch = SecRevocationCanAccessNetwork(builder, first_check_done) && |
866f8763 A |
1277 | (SecCertificatePathVCIsEV(path) || SecCertificatePathVCIsOptionallyEV(path) || |
1278 | SecPathBuilderGetRevocationMethod(builder) || old_cached_response); | |
1279 | bool fetch_done = true; | |
1280 | if (rvc->done || !allow_fetch) { | |
1281 | /* We got a cache hit or we aren't allowed to access the network */ | |
1282 | SecRVCUpdatePVC(rvc); | |
1283 | /* We didn't really start any background jobs for this cert. */ | |
1284 | (void)SecPathBuilderDecrementAsyncJobCount(builder); | |
1285 | #if ENABLE_CRLS | |
1286 | (void)SecPathBuilderDecrementAsyncJobCount(builder); | |
1287 | #endif | |
1288 | } else { | |
1289 | fetch_done = SecRVCFetchNext(rvc); | |
1290 | } | |
1291 | if (!fetch_done) { | |
1292 | /* We started at least one background fetch. */ | |
1293 | secdebug("rvc", "waiting on background fetch for cert %ld", certIX); | |
1294 | completed = false; | |
1295 | } | |
1296 | } | |
1297 | ||
1298 | /* Return false if we started any background jobs. */ | |
1299 | /* We can't just return !builder->asyncJobCount here, since if we started any | |
1300 | jobs the completion callback will be called eventually and it will call | |
1301 | SecPathBuilderStep(). If for some reason everything completed before we | |
1302 | get here we still want the outer SecPathBuilderStep() to terminate so we | |
1303 | keep track of whether we started any jobs and return false if so. */ | |
1304 | return completed; | |
1305 | } | |
1306 | ||
1307 | CFAbsoluteTime SecRVCGetEarliestNextUpdate(SecRVCRef rvc) { | |
1308 | CFAbsoluteTime enu = NULL_TIME; | |
1309 | if (!rvc || !rvc->orvc) { return enu; } | |
1310 | enu = rvc->orvc->nextUpdate; | |
1311 | #if ENABLE_CRLS | |
1312 | CFAbsoluteTime crlNextUpdate = rvc->crvc->nextUpdate; | |
1313 | if (enu == NULL_TIME || | |
1314 | ((crlNextUpdate > NULL_TIME) && (enu > crlNextUpdate))) { | |
1315 | /* We didn't check OCSP or CRL next update time was sooner */ | |
1316 | enu = crlNextUpdate; | |
1317 | } | |
1318 | #endif | |
1319 | return enu; | |
1320 | } |