|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 /* |
|
5 * pkix_ocspchecker.c |
|
6 * |
|
7 * OcspChecker Object Functions |
|
8 * |
|
9 */ |
|
10 |
|
11 #include "pkix_ocspchecker.h" |
|
12 #include "pkix_pl_ocspcertid.h" |
|
13 #include "pkix_error.h" |
|
14 |
|
15 |
|
16 /* --Private-Data-and-Types--------------------------------------- */ |
|
17 |
|
18 typedef struct pkix_OcspCheckerStruct { |
|
19 /* RevocationMethod is the super class of OcspChecker. */ |
|
20 pkix_RevocationMethod method; |
|
21 PKIX_PL_VerifyCallback certVerifyFcn; |
|
22 } pkix_OcspChecker; |
|
23 |
|
24 /* --Private-Functions-------------------------------------------- */ |
|
25 |
|
26 /* |
|
27 * FUNCTION: pkix_OcspChecker_Destroy |
|
28 * (see comments for PKIX_PL_DestructorCallback in pkix_pl_system.h) |
|
29 */ |
|
30 static PKIX_Error * |
|
31 pkix_OcspChecker_Destroy( |
|
32 PKIX_PL_Object *object, |
|
33 void *plContext) |
|
34 { |
|
35 return NULL; |
|
36 } |
|
37 |
|
38 /* |
|
39 * FUNCTION: pkix_OcspChecker_RegisterSelf |
|
40 * DESCRIPTION: |
|
41 * Registers PKIX_OCSPCHECKER_TYPE and its related functions with |
|
42 * systemClasses[] |
|
43 * THREAD SAFETY: |
|
44 * Not Thread Safe - for performance and complexity reasons |
|
45 * |
|
46 * Since this function is only called by PKIX_PL_Initialize, which should |
|
47 * only be called once, it is acceptable that this function is not |
|
48 * thread-safe. |
|
49 */ |
|
50 PKIX_Error * |
|
51 pkix_OcspChecker_RegisterSelf(void *plContext) |
|
52 { |
|
53 extern pkix_ClassTable_Entry systemClasses[PKIX_NUMTYPES]; |
|
54 pkix_ClassTable_Entry* entry = &systemClasses[PKIX_OCSPCHECKER_TYPE]; |
|
55 |
|
56 PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_RegisterSelf"); |
|
57 |
|
58 entry->description = "OcspChecker"; |
|
59 entry->typeObjectSize = sizeof(pkix_OcspChecker); |
|
60 entry->destructor = pkix_OcspChecker_Destroy; |
|
61 |
|
62 PKIX_RETURN(OCSPCHECKER); |
|
63 } |
|
64 |
|
65 |
|
66 /* |
|
67 * FUNCTION: pkix_OcspChecker_Create |
|
68 */ |
|
69 PKIX_Error * |
|
70 pkix_OcspChecker_Create(PKIX_RevocationMethodType methodType, |
|
71 PKIX_UInt32 flags, |
|
72 PKIX_UInt32 priority, |
|
73 pkix_LocalRevocationCheckFn localRevChecker, |
|
74 pkix_ExternalRevocationCheckFn externalRevChecker, |
|
75 PKIX_PL_VerifyCallback verifyFn, |
|
76 pkix_RevocationMethod **pChecker, |
|
77 void *plContext) |
|
78 { |
|
79 pkix_OcspChecker *method = NULL; |
|
80 |
|
81 PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_Create"); |
|
82 PKIX_NULLCHECK_ONE(pChecker); |
|
83 |
|
84 PKIX_CHECK(PKIX_PL_Object_Alloc |
|
85 (PKIX_OCSPCHECKER_TYPE, |
|
86 sizeof (pkix_OcspChecker), |
|
87 (PKIX_PL_Object **)&method, |
|
88 plContext), |
|
89 PKIX_COULDNOTCREATECERTCHAINCHECKEROBJECT); |
|
90 |
|
91 pkixErrorResult = pkix_RevocationMethod_Init( |
|
92 (pkix_RevocationMethod*)method, methodType, flags, priority, |
|
93 localRevChecker, externalRevChecker, plContext); |
|
94 if (pkixErrorResult) { |
|
95 goto cleanup; |
|
96 } |
|
97 method->certVerifyFcn = (PKIX_PL_VerifyCallback)verifyFn; |
|
98 |
|
99 *pChecker = (pkix_RevocationMethod*)method; |
|
100 method = NULL; |
|
101 |
|
102 cleanup: |
|
103 PKIX_DECREF(method); |
|
104 |
|
105 PKIX_RETURN(OCSPCHECKER); |
|
106 } |
|
107 |
|
108 /* |
|
109 * FUNCTION: pkix_OcspChecker_MapResultCodeToRevStatus |
|
110 */ |
|
111 PKIX_RevocationStatus |
|
112 pkix_OcspChecker_MapResultCodeToRevStatus(SECErrorCodes resultCode) |
|
113 { |
|
114 switch (resultCode) { |
|
115 case SEC_ERROR_REVOKED_CERTIFICATE: |
|
116 return PKIX_RevStatus_Revoked; |
|
117 default: |
|
118 return PKIX_RevStatus_NoInfo; |
|
119 } |
|
120 } |
|
121 |
|
122 /* --Public-Functions--------------------------------------------- */ |
|
123 |
|
124 /* |
|
125 * FUNCTION: pkix_OcspChecker_Check (see comments in pkix_checker.h) |
|
126 */ |
|
127 |
|
128 /* |
|
129 * The OCSPChecker is created in an idle state, and remains in this state until |
|
130 * either (a) the default Responder has been set and enabled, and a Check |
|
131 * request is received with no responder specified, or (b) a Check request is |
|
132 * received with a specified responder. A request message is constructed and |
|
133 * given to the HttpClient. If non-blocking I/O is used the client may return |
|
134 * with WOULDBLOCK, in which case the OCSPChecker returns the WOULDBLOCK |
|
135 * condition to its caller in turn. On a subsequent call the I/O is resumed. |
|
136 * When a response is received it is decoded and the results provided to the |
|
137 * caller. |
|
138 * |
|
139 */ |
|
140 PKIX_Error * |
|
141 pkix_OcspChecker_CheckLocal( |
|
142 PKIX_PL_Cert *cert, |
|
143 PKIX_PL_Cert *issuer, |
|
144 PKIX_PL_Date *date, |
|
145 pkix_RevocationMethod *checkerObject, |
|
146 PKIX_ProcessingParams *procParams, |
|
147 PKIX_UInt32 methodFlags, |
|
148 PKIX_Boolean chainVerificationState, |
|
149 PKIX_RevocationStatus *pRevStatus, |
|
150 PKIX_UInt32 *pReasonCode, |
|
151 void *plContext) |
|
152 { |
|
153 PKIX_PL_OcspCertID *cid = NULL; |
|
154 PKIX_Boolean hasFreshStatus = PKIX_FALSE; |
|
155 PKIX_Boolean statusIsGood = PKIX_FALSE; |
|
156 SECErrorCodes resultCode = SEC_ERROR_REVOKED_CERTIFICATE_OCSP; |
|
157 PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo; |
|
158 |
|
159 PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_CheckLocal"); |
|
160 |
|
161 PKIX_CHECK( |
|
162 PKIX_PL_OcspCertID_Create(cert, NULL, &cid, |
|
163 plContext), |
|
164 PKIX_OCSPCERTIDCREATEFAILED); |
|
165 if (!cid) { |
|
166 goto cleanup; |
|
167 } |
|
168 |
|
169 PKIX_CHECK( |
|
170 PKIX_PL_OcspCertID_GetFreshCacheStatus(cid, date, |
|
171 &hasFreshStatus, |
|
172 &statusIsGood, |
|
173 &resultCode, |
|
174 plContext), |
|
175 PKIX_OCSPCERTIDGETFRESHCACHESTATUSFAILED); |
|
176 if (hasFreshStatus) { |
|
177 if (statusIsGood) { |
|
178 revStatus = PKIX_RevStatus_Success; |
|
179 resultCode = 0; |
|
180 } else { |
|
181 revStatus = pkix_OcspChecker_MapResultCodeToRevStatus(resultCode); |
|
182 } |
|
183 } |
|
184 |
|
185 cleanup: |
|
186 *pRevStatus = revStatus; |
|
187 |
|
188 /* ocsp carries only tree statuses: good, bad, and unknown. |
|
189 * revStatus is used to pass them. reasonCode is always set |
|
190 * to be unknown. */ |
|
191 *pReasonCode = crlEntryReasonUnspecified; |
|
192 PKIX_DECREF(cid); |
|
193 |
|
194 PKIX_RETURN(OCSPCHECKER); |
|
195 } |
|
196 |
|
197 |
|
198 /* |
|
199 * The OCSPChecker is created in an idle state, and remains in this state until |
|
200 * either (a) the default Responder has been set and enabled, and a Check |
|
201 * request is received with no responder specified, or (b) a Check request is |
|
202 * received with a specified responder. A request message is constructed and |
|
203 * given to the HttpClient. When a response is received it is decoded and the |
|
204 * results provided to the caller. |
|
205 * |
|
206 * During the most recent enhancement of this function, it has been found that |
|
207 * it doesn't correctly implement non-blocking I/O. |
|
208 * |
|
209 * The nbioContext is used in two places, for "response-obtaining" and |
|
210 * for "response-verification". |
|
211 * |
|
212 * However, if this function gets called to resume, it always |
|
213 * repeats the "request creation" and "response fetching" steps! |
|
214 * As a result, the earlier operation is never resumed. |
|
215 */ |
|
216 PKIX_Error * |
|
217 pkix_OcspChecker_CheckExternal( |
|
218 PKIX_PL_Cert *cert, |
|
219 PKIX_PL_Cert *issuer, |
|
220 PKIX_PL_Date *date, |
|
221 pkix_RevocationMethod *checkerObject, |
|
222 PKIX_ProcessingParams *procParams, |
|
223 PKIX_UInt32 methodFlags, |
|
224 PKIX_RevocationStatus *pRevStatus, |
|
225 PKIX_UInt32 *pReasonCode, |
|
226 void **pNBIOContext, |
|
227 void *plContext) |
|
228 { |
|
229 SECErrorCodes resultCode = SEC_ERROR_REVOKED_CERTIFICATE_OCSP; |
|
230 PKIX_Boolean uriFound = PKIX_FALSE; |
|
231 PKIX_Boolean passed = PKIX_TRUE; |
|
232 pkix_OcspChecker *checker = NULL; |
|
233 PKIX_PL_OcspCertID *cid = NULL; |
|
234 PKIX_PL_OcspRequest *request = NULL; |
|
235 PKIX_PL_OcspResponse *response = NULL; |
|
236 PKIX_PL_Date *validity = NULL; |
|
237 PKIX_RevocationStatus revStatus = PKIX_RevStatus_NoInfo; |
|
238 void *nbioContext = NULL; |
|
239 enum { stageGET, stagePOST } currentStage; |
|
240 PRBool retry = PR_FALSE; |
|
241 |
|
242 PKIX_ENTER(OCSPCHECKER, "pkix_OcspChecker_CheckExternal"); |
|
243 |
|
244 PKIX_CHECK( |
|
245 pkix_CheckType((PKIX_PL_Object*)checkerObject, |
|
246 PKIX_OCSPCHECKER_TYPE, plContext), |
|
247 PKIX_OBJECTNOTOCSPCHECKER); |
|
248 |
|
249 checker = (pkix_OcspChecker *)checkerObject; |
|
250 |
|
251 PKIX_CHECK( |
|
252 PKIX_PL_OcspCertID_Create(cert, NULL, &cid, |
|
253 plContext), |
|
254 PKIX_OCSPCERTIDCREATEFAILED); |
|
255 |
|
256 /* create request */ |
|
257 PKIX_CHECK( |
|
258 pkix_pl_OcspRequest_Create(cert, cid, validity, NULL, |
|
259 methodFlags, &uriFound, &request, |
|
260 plContext), |
|
261 PKIX_OCSPREQUESTCREATEFAILED); |
|
262 |
|
263 if (uriFound == PKIX_FALSE) { |
|
264 /* no caching for certs lacking URI */ |
|
265 resultCode = 0; |
|
266 goto cleanup; |
|
267 } |
|
268 |
|
269 if (methodFlags & CERT_REV_M_FORCE_POST_METHOD_FOR_OCSP) { |
|
270 /* Do not try HTTP GET, only HTTP POST */ |
|
271 currentStage = stagePOST; |
|
272 } else { |
|
273 /* Try HTTP GET first, falling back to POST */ |
|
274 currentStage = stageGET; |
|
275 } |
|
276 |
|
277 do { |
|
278 const char *method; |
|
279 passed = PKIX_TRUE; |
|
280 |
|
281 retry = PR_FALSE; |
|
282 if (currentStage == stageGET) { |
|
283 method = "GET"; |
|
284 } else { |
|
285 PORT_Assert(currentStage == stagePOST); |
|
286 method = "POST"; |
|
287 } |
|
288 |
|
289 /* send request and create a response object */ |
|
290 PKIX_CHECK_NO_GOTO( |
|
291 pkix_pl_OcspResponse_Create(request, method, NULL, |
|
292 checker->certVerifyFcn, |
|
293 &nbioContext, |
|
294 &response, |
|
295 plContext), |
|
296 PKIX_OCSPRESPONSECREATEFAILED); |
|
297 |
|
298 if (pkixErrorResult) { |
|
299 passed = PKIX_FALSE; |
|
300 } |
|
301 |
|
302 if (passed && nbioContext != 0) { |
|
303 *pNBIOContext = nbioContext; |
|
304 goto cleanup; |
|
305 } |
|
306 |
|
307 if (passed){ |
|
308 PKIX_CHECK_NO_GOTO( |
|
309 pkix_pl_OcspResponse_Decode(response, &passed, |
|
310 &resultCode, plContext), |
|
311 PKIX_OCSPRESPONSEDECODEFAILED); |
|
312 if (pkixErrorResult) { |
|
313 passed = PKIX_FALSE; |
|
314 } |
|
315 } |
|
316 |
|
317 if (passed){ |
|
318 PKIX_CHECK_NO_GOTO( |
|
319 pkix_pl_OcspResponse_GetStatus(response, &passed, |
|
320 &resultCode, plContext), |
|
321 PKIX_OCSPRESPONSEGETSTATUSRETURNEDANERROR); |
|
322 if (pkixErrorResult) { |
|
323 passed = PKIX_FALSE; |
|
324 } |
|
325 } |
|
326 |
|
327 if (passed){ |
|
328 PKIX_CHECK_NO_GOTO( |
|
329 pkix_pl_OcspResponse_VerifySignature(response, cert, |
|
330 procParams, &passed, |
|
331 &nbioContext, plContext), |
|
332 PKIX_OCSPRESPONSEVERIFYSIGNATUREFAILED); |
|
333 if (pkixErrorResult) { |
|
334 passed = PKIX_FALSE; |
|
335 } else { |
|
336 if (nbioContext != 0) { |
|
337 *pNBIOContext = nbioContext; |
|
338 goto cleanup; |
|
339 } |
|
340 } |
|
341 } |
|
342 |
|
343 if (!passed && currentStage == stagePOST) { |
|
344 /* We won't retry a POST failure, so it's final. |
|
345 * Because the following block with its call to |
|
346 * pkix_pl_OcspResponse_GetStatusForCert |
|
347 * will take care of caching good or bad state, |
|
348 * but we only execute that next block if there hasn't |
|
349 * been a failure yet, we must cache the POST |
|
350 * failure now. |
|
351 */ |
|
352 |
|
353 if (cid && cid->certID) { |
|
354 /* Caching MIGHT consume the cid. */ |
|
355 PKIX_Error *err; |
|
356 err = PKIX_PL_OcspCertID_RememberOCSPProcessingFailure( |
|
357 cid, plContext); |
|
358 if (err) { |
|
359 PKIX_PL_Object_DecRef((PKIX_PL_Object*)err, plContext); |
|
360 } |
|
361 } |
|
362 } |
|
363 |
|
364 if (passed){ |
|
365 PKIX_Boolean allowCachingOfFailures = |
|
366 (currentStage == stagePOST) ? PKIX_TRUE : PKIX_FALSE; |
|
367 |
|
368 PKIX_CHECK_NO_GOTO( |
|
369 pkix_pl_OcspResponse_GetStatusForCert(cid, response, |
|
370 allowCachingOfFailures, |
|
371 date, |
|
372 &passed, &resultCode, |
|
373 plContext), |
|
374 PKIX_OCSPRESPONSEGETSTATUSFORCERTFAILED); |
|
375 if (pkixErrorResult) { |
|
376 passed = PKIX_FALSE; |
|
377 } else if (passed == PKIX_FALSE) { |
|
378 revStatus = pkix_OcspChecker_MapResultCodeToRevStatus(resultCode); |
|
379 } else { |
|
380 revStatus = PKIX_RevStatus_Success; |
|
381 } |
|
382 } |
|
383 |
|
384 if (currentStage == stageGET && revStatus != PKIX_RevStatus_Success && |
|
385 revStatus != PKIX_RevStatus_Revoked) { |
|
386 /* we'll retry */ |
|
387 PKIX_DECREF(response); |
|
388 retry = PR_TRUE; |
|
389 currentStage = stagePOST; |
|
390 revStatus = PKIX_RevStatus_NoInfo; |
|
391 if (pkixErrorResult) { |
|
392 PKIX_PL_Object_DecRef((PKIX_PL_Object*)pkixErrorResult, |
|
393 plContext); |
|
394 pkixErrorResult = NULL; |
|
395 } |
|
396 } |
|
397 } while (retry); |
|
398 |
|
399 cleanup: |
|
400 if (revStatus == PKIX_RevStatus_NoInfo && (uriFound || |
|
401 methodFlags & PKIX_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE) && |
|
402 methodFlags & PKIX_REV_M_FAIL_ON_MISSING_FRESH_INFO) { |
|
403 revStatus = PKIX_RevStatus_Revoked; |
|
404 } |
|
405 *pRevStatus = revStatus; |
|
406 |
|
407 /* ocsp carries only three statuses: good, bad, and unknown. |
|
408 * revStatus is used to pass them. reasonCode is always set |
|
409 * to be unknown. */ |
|
410 *pReasonCode = crlEntryReasonUnspecified; |
|
411 |
|
412 PKIX_DECREF(cid); |
|
413 PKIX_DECREF(request); |
|
414 PKIX_DECREF(response); |
|
415 |
|
416 PKIX_RETURN(OCSPCHECKER); |
|
417 } |
|
418 |
|
419 |