]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
5c19dc3a | 2 | * Copyright (c) 2004,2011-2012,2014-2015 Apple Inc. All Rights Reserved. |
949d2ff0 | 3 | * |
b1ab9ed8 | 4 | * @APPLE_LICENSE_HEADER_START@ |
949d2ff0 | 5 | * |
b1ab9ed8 A |
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. | |
949d2ff0 | 12 | * |
b1ab9ed8 A |
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. | |
949d2ff0 | 20 | * |
b1ab9ed8 A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | ||
24 | /* | |
25 | * tpOcspVerify.cpp - top-level OCSP verification | |
26 | */ | |
949d2ff0 | 27 | |
b1ab9ed8 A |
28 | #include "tpOcspVerify.h" |
29 | #include "tpdebugging.h" | |
30 | #include "ocspRequest.h" | |
31 | #include "tpOcspCache.h" | |
32 | #include "tpOcspCertVfy.h" | |
33 | #include <security_ocspd/ocspResponse.h> | |
34 | #include "certGroupUtils.h" | |
35 | #include <Security/certextensions.h> | |
36 | #include <Security/oidsattr.h> | |
37 | #include <Security/oidscert.h> | |
38 | #include <security_asn1/SecNssCoder.h> | |
39 | #include <security_ocspd/ocspdClient.h> | |
40 | #include <security_ocspd/ocspdUtils.h> | |
41 | #include "tpTime.h" | |
42 | ||
43 | #pragma mark ---- private routines ---- | |
44 | ||
949d2ff0 | 45 | /* |
b1ab9ed8 A |
46 | * Get a smart CertID for specified cert and issuer |
47 | */ | |
48 | static CSSM_RETURN tpOcspGetCertId( | |
49 | TPCertInfo &subject, | |
50 | TPCertInfo &issuer, | |
51 | OCSPClientCertID *&certID) /* mallocd by coder and RETURNED */ | |
52 | { | |
53 | CSSM_RETURN crtn; | |
54 | CSSM_DATA_PTR issuerSubject = NULL; | |
55 | CSSM_DATA_PTR issuerPubKeyData = NULL; | |
56 | CSSM_KEY_PTR issuerPubKey; | |
949d2ff0 | 57 | CSSM_DATA issuerPubKeyBytes; |
b1ab9ed8 | 58 | CSSM_DATA_PTR subjectSerial = NULL; |
949d2ff0 | 59 | |
b1ab9ed8 A |
60 | crtn = subject.fetchField(&CSSMOID_X509V1SerialNumber, &subjectSerial); |
61 | if(crtn) { | |
62 | return crtn; | |
63 | } | |
64 | crtn = subject.fetchField(&CSSMOID_X509V1IssuerNameStd, &issuerSubject); | |
65 | if(crtn) { | |
66 | return crtn; | |
67 | } | |
68 | crtn = issuer.fetchField(&CSSMOID_CSSMKeyStruct, &issuerPubKeyData); | |
69 | if(crtn) { | |
70 | return crtn; | |
71 | } | |
72 | assert(issuerPubKeyData->Length == sizeof(CSSM_KEY)); | |
73 | issuerPubKey = (CSSM_KEY_PTR)issuerPubKeyData->Data; | |
949d2ff0 A |
74 | ocspdGetPublicKeyBytes(NULL, issuerPubKey, issuerPubKeyBytes); |
75 | certID = new OCSPClientCertID(*issuerSubject, issuerPubKeyBytes, *subjectSerial); | |
76 | ||
b1ab9ed8 A |
77 | subject.freeField(&CSSMOID_X509V1SerialNumber, subjectSerial); |
78 | issuer.freeField(&CSSMOID_X509V1IssuerNameStd, issuerSubject); | |
79 | issuer.freeField(&CSSMOID_CSSMKeyStruct, issuerPubKeyData); | |
80 | return CSSM_OK; | |
81 | } | |
82 | ||
949d2ff0 | 83 | /* |
b1ab9ed8 | 84 | * Examine cert, looking for AuthorityInfoAccess, with id-ad-ocsp URIs. Create |
949d2ff0 | 85 | * an NULL_terminated array of CSSM_DATAs containing the URIs if found. |
b1ab9ed8 A |
86 | */ |
87 | static CSSM_DATA **tpOcspUrlsFromCert( | |
949d2ff0 | 88 | TPCertInfo &subject, |
b1ab9ed8 A |
89 | SecNssCoder &coder) |
90 | { | |
91 | CSSM_DATA_PTR extField = NULL; | |
92 | CSSM_RETURN crtn; | |
949d2ff0 | 93 | |
b1ab9ed8 A |
94 | crtn = subject.fetchField(&CSSMOID_AuthorityInfoAccess, &extField); |
95 | if(crtn) { | |
96 | tpOcspDebug("tpOcspUrlsFromCert: no AIA extension"); | |
97 | return NULL; | |
98 | } | |
99 | if(extField->Length != sizeof(CSSM_X509_EXTENSION)) { | |
100 | tpErrorLog("tpOcspUrlsFromCert: malformed CSSM_FIELD"); | |
101 | return NULL; | |
102 | } | |
103 | CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)extField->Data; | |
104 | if(cssmExt->format != CSSM_X509_DATAFORMAT_PARSED) { | |
105 | tpErrorLog("tpOcspUrlsFromCert: malformed CSSM_X509_EXTENSION"); | |
106 | return NULL; | |
107 | } | |
949d2ff0 A |
108 | |
109 | CE_AuthorityInfoAccess *aia = | |
b1ab9ed8 A |
110 | (CE_AuthorityInfoAccess *)cssmExt->value.parsedValue; |
111 | CSSM_DATA **urls = NULL; | |
112 | unsigned numUrls = 0; | |
113 | for(unsigned dex=0; dex<aia->numAccessDescriptions; dex++) { | |
114 | CE_AccessDescription *ad = &aia->accessDescriptions[dex]; | |
115 | if(!tpCompareCssmData(&ad->accessMethod, &CSSMOID_AD_OCSP)) { | |
116 | continue; | |
117 | } | |
118 | CE_GeneralName *genName = &ad->accessLocation; | |
119 | if(genName->nameType != GNT_URI) { | |
120 | tpErrorLog("tpOcspUrlsFromCert: CSSMOID_AD_OCSP, but not type URI"); | |
121 | continue; | |
122 | } | |
949d2ff0 | 123 | |
b1ab9ed8 A |
124 | /* got one */ |
125 | if(urls == NULL) { | |
126 | urls = coder.mallocn<CSSM_DATA_PTR>(2); | |
127 | } | |
128 | else { | |
129 | /* realloc */ | |
130 | CSSM_DATA **oldUrls = urls; | |
131 | urls = coder.mallocn<CSSM_DATA_PTR>(numUrls + 2); | |
132 | for(unsigned i=0; i<numUrls; i++) { | |
133 | urls[i] = oldUrls[i]; | |
134 | } | |
135 | } | |
136 | urls[numUrls] = coder.mallocn<CSSM_DATA>(); | |
137 | coder.allocCopyItem(genName->name, *urls[numUrls++]); | |
138 | urls[numUrls] = NULL; | |
139 | #ifndef NDEBUG | |
140 | { | |
141 | CSSM_DATA urlStr; | |
142 | coder.allocItem(urlStr, genName->name.Length + 1); | |
143 | memmove(urlStr.Data, genName->name.Data, genName->name.Length); | |
144 | urlStr.Data[urlStr.Length-1] = '\0'; | |
145 | tpOcspDebug("tpOcspUrlsFromCert: found URI %s", urlStr.Data); | |
146 | } | |
147 | #endif | |
148 | } | |
149 | subject.freeField(&CSSMOID_AuthorityInfoAccess, extField); | |
150 | return urls; | |
151 | } | |
152 | ||
949d2ff0 | 153 | /* |
b1ab9ed8 A |
154 | * Create an SecAsn1OCSPDRequest for one cert. This consists of: |
155 | * | |
156 | * -- cooking up an OCSPRequest if net fetch is enabled or a local responder | |
157 | * is configured; | |
158 | * -- extracting URLs from subject cert if net fetch is enabled; | |
159 | * -- creating an SecAsn1OCSPDRequest, mallocd in coder's space; | |
160 | */ | |
161 | static SecAsn1OCSPDRequest *tpGenOcspdReq( | |
162 | TPVerifyContext &vfyCtx, | |
163 | SecNssCoder &coder, | |
164 | TPCertInfo &subject, | |
165 | TPCertInfo &issuer, | |
166 | OCSPClientCertID &certId, | |
167 | const CSSM_DATA **urls, // from subject's AuthorityInfoAccess | |
168 | CSSM_DATA &nonce) // possibly mallocd in coder's space and RETURNED | |
169 | { | |
170 | OCSPRequest *ocspReq = NULL; | |
171 | SecAsn1OCSPDRequest *ocspdReq = NULL; // to return | |
172 | OCSPClientCertID *certID = NULL; | |
173 | CSSM_RETURN crtn; | |
174 | bool deleteCertID = false; | |
949d2ff0 | 175 | |
b1ab9ed8 A |
176 | /* gather options or their defaults */ |
177 | CSSM_APPLE_TP_OCSP_OPT_FLAGS optFlags = 0; | |
178 | const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts; | |
179 | CSSM_DATA_PTR localResponder = NULL; | |
180 | CSSM_DATA_PTR localResponderCert = NULL; | |
181 | if(ocspOpts != NULL) { | |
182 | optFlags = vfyCtx.ocspOpts->Flags; | |
183 | localResponder = ocspOpts->LocalResponder; | |
184 | localResponderCert = ocspOpts->LocalResponderCert; | |
185 | } | |
186 | bool genNonce = optFlags & CSSM_TP_OCSP_GEN_NONCE ? true : false; | |
187 | bool requireRespNonce = optFlags & CSSM_TP_OCSP_REQUIRE_RESP_NONCE ? true : false; | |
949d2ff0 A |
188 | |
189 | /* | |
b1ab9ed8 A |
190 | * One degenerate case in case we can't really do anything. |
191 | * If no URI and no local responder, only proceed if cache is not disabled | |
192 | * and we're requiring full OCSP per cert. | |
193 | */ | |
194 | if( ( (optFlags & CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE) || | |
195 | !(optFlags & CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT) | |
949d2ff0 A |
196 | ) && |
197 | (localResponder == NULL) && | |
b1ab9ed8 A |
198 | (urls == NULL)) { |
199 | tpOcspDebug("tpGenOcspdReq: no route to OCSP; NULL return"); | |
200 | return NULL; | |
201 | } | |
202 | ||
203 | /* do we need an OCSP request? */ | |
204 | if(!(optFlags & CSSM_TP_ACTION_OCSP_DISABLE_NET) || (localResponder != NULL)) { | |
205 | try { | |
206 | ocspReq = new OCSPRequest(subject, issuer, genNonce); | |
207 | certID = ocspReq->certID(); | |
208 | } | |
209 | catch(...) { | |
210 | /* not sure how this could even happen but that was a fair amount of code */ | |
211 | tpErrorLog("tpGenOcspdReq: error cooking up OCSPRequest\n"); | |
212 | if(ocspReq != NULL) { | |
213 | delete ocspReq; | |
214 | return NULL; | |
215 | } | |
216 | } | |
217 | } | |
949d2ff0 | 218 | |
b1ab9ed8 A |
219 | /* certID needed one way or the other */ |
220 | if(certID == NULL) { | |
221 | crtn = tpOcspGetCertId(subject, issuer, certID); | |
222 | if(crtn) { | |
223 | goto errOut; | |
224 | } | |
225 | deleteCertID = true; | |
226 | } | |
949d2ff0 | 227 | |
b1ab9ed8 A |
228 | /* |
229 | * Create the SecAsn1OCSPDRequest. All fields optional. | |
230 | */ | |
231 | ocspdReq = coder.mallocn<SecAsn1OCSPDRequest>(); | |
232 | memset(ocspdReq, 0, sizeof(*ocspdReq)); | |
233 | if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_WRITE_DISABLE) { | |
234 | ocspdReq->cacheWriteDisable = coder.mallocn<CSSM_DATA>(); | |
235 | ocspdReq->cacheWriteDisable->Data = coder.mallocn<uint8>(); | |
236 | ocspdReq->cacheWriteDisable->Data[0] = 0xff; | |
237 | ocspdReq->cacheWriteDisable->Length = 1; | |
238 | } | |
949d2ff0 A |
239 | /* |
240 | * Note we're enforcing a not-so-obvious policy here: if nonce match is | |
241 | * required, disk cache reads by ocspd are disabled. In-core cache is | |
242 | * still enabled and hits in that cache do NOT require nonce matches. | |
b1ab9ed8 A |
243 | */ |
244 | if((optFlags & CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE) || requireRespNonce) { | |
245 | ocspdReq->cacheReadDisable = coder.mallocn<CSSM_DATA>(); | |
246 | ocspdReq->cacheReadDisable->Data = coder.mallocn<uint8>(); | |
247 | ocspdReq->cacheReadDisable->Data[0] = 0xff; | |
248 | ocspdReq->cacheReadDisable->Length = 1; | |
249 | } | |
250 | /* CertID, only required field */ | |
251 | coder.allocCopyItem(*certID->encode(), ocspdReq->certID); | |
252 | if(ocspReq != NULL) { | |
253 | ocspdReq->ocspReq = coder.mallocn<CSSM_DATA>(); | |
254 | coder.allocCopyItem(*ocspReq->encode(), *ocspdReq->ocspReq); | |
255 | if(genNonce) { | |
256 | /* nonce not available until encode() called */ | |
257 | coder.allocCopyItem(*ocspReq->nonce(), nonce); | |
258 | } | |
259 | } | |
260 | if(localResponder != NULL) { | |
261 | ocspdReq->localRespURI = localResponder; | |
262 | } | |
263 | if(!(optFlags & CSSM_TP_ACTION_OCSP_DISABLE_NET)) { | |
264 | ocspdReq->urls = const_cast<CSSM_DATA **>(urls); | |
265 | } | |
949d2ff0 | 266 | |
b1ab9ed8 A |
267 | errOut: |
268 | delete ocspReq; | |
269 | if(deleteCertID) { | |
270 | delete certID; | |
271 | } | |
272 | return ocspdReq; | |
273 | } | |
274 | ||
275 | static bool revocationTimeAfterVerificationTime(CFAbsoluteTime revokedTime, CSSM_TIMESTRING verifyTimeStr) | |
276 | { | |
277 | // Return true if the revocation time is after the specified verification time (i.e. "good") | |
278 | // If verifyTime not specified, use now for the verifyTime | |
949d2ff0 | 279 | |
b1ab9ed8 | 280 | CFAbsoluteTime verifyTime = 0; |
949d2ff0 | 281 | |
b1ab9ed8 A |
282 | if (verifyTimeStr) |
283 | { | |
284 | CFDateRef cfVerifyTime = NULL; // made with CFDateCreate | |
285 | int rtn = timeStringToCfDate((char *)verifyTimeStr, (unsigned)strlen(verifyTimeStr), &cfVerifyTime); | |
286 | if (!rtn) | |
287 | if (cfVerifyTime) | |
288 | { | |
289 | verifyTime = CFDateGetAbsoluteTime(cfVerifyTime); | |
290 | CFRelease(cfVerifyTime); | |
291 | } | |
292 | } | |
949d2ff0 | 293 | |
b1ab9ed8 A |
294 | if (verifyTime == 0) |
295 | verifyTime = CFAbsoluteTimeGetCurrent(); | |
296 | ||
297 | return verifyTime < revokedTime; | |
298 | } | |
299 | ||
300 | /* | |
301 | * Apply a verified OCSPSingleResponse to a TPCertInfo. | |
302 | */ | |
303 | static CSSM_RETURN tpApplySingleResp( | |
304 | OCSPSingleResponse &singleResp, | |
305 | TPCertInfo &cert, | |
306 | unsigned dex, // for debug | |
307 | CSSM_APPLE_TP_OCSP_OPT_FLAGS flags, // for OCSP_SUFFICIENT | |
308 | CSSM_TIMESTRING verifyTime, // Check revocation at specific time | |
309 | bool &processed) // set true iff CS_Good or CS_Revoked found | |
310 | { | |
311 | SecAsn1OCSPCertStatusTag certStatus = singleResp.certStatus(); | |
312 | CSSM_RETURN crtn = CSSM_OK; | |
949d2ff0 | 313 | if ((certStatus == CS_Revoked) && |
b1ab9ed8 A |
314 | revocationTimeAfterVerificationTime(singleResp.revokedTime(), verifyTime)) |
315 | { | |
316 | tpOcspDebug("tpApplySingleResp: CS_Revoked for cert %u, but revoked after verification time", dex); | |
317 | certStatus = CS_Good; | |
318 | } | |
949d2ff0 | 319 | |
b1ab9ed8 A |
320 | switch(certStatus) { |
321 | case CS_Good: | |
322 | tpOcspDebug("tpApplySingleResp: CS_Good for cert %u", dex); | |
323 | cert.revokeCheckGood(true); | |
324 | if(flags & CSSM_TP_ACTION_OCSP_SUFFICIENT) { | |
325 | /* no more revocation checking necessary for this cert */ | |
326 | cert.revokeCheckComplete(true); | |
327 | } | |
328 | processed = true; | |
329 | break; | |
330 | case CS_Revoked: | |
331 | tpOcspDebug("tpApplySingleResp: CS_Revoked for cert %u", dex); | |
332 | switch(singleResp.crlReason()) { | |
333 | case CE_CR_CertificateHold: | |
334 | crtn = CSSMERR_TP_CERT_SUSPENDED; | |
335 | break; | |
336 | default: | |
337 | /* FIXME - may want more detailed CRLReason-specific errors */ | |
338 | crtn = CSSMERR_TP_CERT_REVOKED; | |
5c19dc3a | 339 | cert.crlReason((sint32)singleResp.crlReason()); |
b1ab9ed8 A |
340 | break; |
341 | } | |
342 | if(!cert.addStatusCode(crtn)) { | |
343 | crtn = CSSM_OK; | |
344 | } | |
345 | processed = true; | |
346 | break; | |
347 | case CS_Unknown: | |
348 | /* not an error, no per-cert status, nothing here */ | |
349 | tpOcspDebug("tpApplySingleResp: CS_Unknown for cert %u", dex); | |
350 | break; | |
351 | default: | |
949d2ff0 | 352 | tpOcspDebug("tpApplySingleResp: BAD certStatus (%d) for cert %u", |
b1ab9ed8 A |
353 | (int)certStatus, dex); |
354 | if(cert.addStatusCode(CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED)) { | |
355 | crtn = CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED; | |
356 | } | |
357 | break; | |
358 | } | |
359 | return crtn; | |
360 | } | |
361 | ||
949d2ff0 | 362 | /* |
b1ab9ed8 | 363 | * An exceptional case: synchronously flush the OCSPD cache and send a new |
949d2ff0 | 364 | * resquest for just this one cert. |
b1ab9ed8 A |
365 | */ |
366 | static OCSPResponse *tpOcspFlushAndReFetch( | |
949d2ff0 A |
367 | TPVerifyContext &vfyCtx, |
368 | SecNssCoder &coder, | |
b1ab9ed8 | 369 | TPCertInfo &subject, |
949d2ff0 | 370 | TPCertInfo &issuer, |
b1ab9ed8 A |
371 | OCSPClientCertID &certID) |
372 | { | |
373 | const CSSM_DATA *derCertID = certID.encode(); | |
374 | CSSM_RETURN crtn; | |
949d2ff0 | 375 | |
b1ab9ed8 A |
376 | crtn = ocspdCacheFlush(*derCertID); |
377 | if(crtn) { | |
378 | #ifndef NDEBUG | |
379 | cssmPerror("ocspdCacheFlush", crtn); | |
380 | #endif | |
381 | return NULL; | |
382 | } | |
949d2ff0 | 383 | |
b1ab9ed8 A |
384 | /* Cook up an OCSPDRequests, one request, just for this */ |
385 | /* send it to ocsdp */ | |
386 | /* munge reply into an OCSPRsponse and return it */ | |
387 | tpOcspDebug("pOcspFlushAndReFetch: Code on demand"); | |
388 | return NULL; | |
389 | } | |
390 | ||
391 | class PendingRequest | |
392 | { | |
393 | public: | |
394 | PendingRequest( | |
395 | TPCertInfo &subject, | |
396 | TPCertInfo &issuer, | |
397 | OCSPClientCertID &cid, | |
398 | CSSM_DATA **u, | |
399 | unsigned dex); | |
400 | ~PendingRequest() {} | |
949d2ff0 | 401 | |
b1ab9ed8 A |
402 | TPCertInfo &subject; |
403 | TPCertInfo &issuer; | |
404 | OCSPClientCertID &certID; // owned by caller | |
405 | CSSM_DATA **urls; // owner-managed array of URLs obtained from subject's | |
949d2ff0 A |
406 | // AuthorityInfoAccess.id-ad-ocsp. |
407 | CSSM_DATA nonce; // owner-managed copy of this requests' nonce, if it | |
408 | // has one | |
b1ab9ed8 A |
409 | unsigned dex; // in inputCerts, for debug |
410 | bool processed; | |
411 | }; | |
412 | ||
413 | PendingRequest::PendingRequest( | |
414 | TPCertInfo &subj, | |
415 | TPCertInfo &iss, | |
416 | OCSPClientCertID &cid, | |
417 | CSSM_DATA **u, | |
418 | unsigned dx) | |
949d2ff0 | 419 | : subject(subj), issuer(iss), certID(cid), |
b1ab9ed8 A |
420 | urls(u), dex(dx), processed(false) |
421 | { | |
422 | nonce.Data = NULL; | |
423 | nonce.Length = 0; | |
424 | } | |
425 | ||
426 | #pragma mark ---- public API ---- | |
427 | ||
428 | CSSM_RETURN tpVerifyCertGroupWithOCSP( | |
429 | TPVerifyContext &vfyCtx, | |
949d2ff0 | 430 | TPCertGroup &certGroup) // to be verified |
b1ab9ed8 A |
431 | { |
432 | assert(vfyCtx.clHand != 0); | |
433 | assert(vfyCtx.policy == kRevokeOcsp); | |
949d2ff0 | 434 | |
b1ab9ed8 A |
435 | CSSM_RETURN ourRtn = CSSM_OK; |
436 | OcspRespStatus respStat; | |
437 | SecNssCoder coder; | |
438 | CSSM_RETURN crtn; | |
439 | SecAsn1OCSPDRequests ocspdReqs; | |
440 | SecAsn1OCSPReplies ocspdReplies; | |
441 | unsigned numRequests = 0; // in ocspdReqs | |
442 | CSSM_DATA derOcspdRequests = {0, NULL}; | |
443 | CSSM_DATA derOcspdReplies = {0, NULL}; | |
444 | uint8 version = OCSPD_REQUEST_VERS; | |
445 | unsigned numReplies; | |
446 | unsigned numCerts = certGroup.numCerts(); | |
447 | if(numCerts <= 1) { | |
448 | /* Can't verify without an issuer; we're done */ | |
449 | return CSSM_OK; | |
450 | } | |
451 | numCerts--; | |
949d2ff0 | 452 | |
b1ab9ed8 A |
453 | /* gather options or their defaults */ |
454 | CSSM_APPLE_TP_OCSP_OPT_FLAGS optFlags = 0; | |
455 | const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts; | |
456 | CSSM_DATA_PTR localResponder = NULL; | |
457 | CSSM_DATA_PTR localResponderCert = NULL; | |
458 | bool cacheReadDisable = false; | |
459 | bool cacheWriteDisable = false; | |
460 | bool genNonce = false; // in outgoing request | |
461 | bool requireRespNonce = false; // in incoming response | |
462 | PRErrorCode prtn; | |
949d2ff0 | 463 | |
b1ab9ed8 A |
464 | if(ocspOpts != NULL) { |
465 | optFlags = vfyCtx.ocspOpts->Flags; | |
466 | localResponder = ocspOpts->LocalResponder; | |
467 | localResponderCert = ocspOpts->LocalResponderCert; | |
468 | } | |
469 | if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE) { | |
470 | cacheReadDisable = true; | |
471 | } | |
472 | if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_WRITE_DISABLE) { | |
473 | cacheWriteDisable = true; | |
474 | } | |
475 | if(optFlags & CSSM_TP_OCSP_GEN_NONCE) { | |
476 | genNonce = true; | |
477 | } | |
478 | if(optFlags & CSSM_TP_OCSP_REQUIRE_RESP_NONCE) { | |
479 | requireRespNonce = true; | |
480 | } | |
481 | if(requireRespNonce & !genNonce) { | |
482 | /* no can do */ | |
483 | tpErrorLog("tpVerifyCertGroupWithOCSP: requireRespNonce, !genNonce\n"); | |
484 | return CSSMERR_TP_INVALID_REQUEST_INPUTS; | |
485 | } | |
949d2ff0 A |
486 | |
487 | tpOcspDebug("tpVerifyCertGroupWithOCSP numCerts %u optFlags 0x%lx", | |
b1ab9ed8 | 488 | numCerts, (unsigned long)optFlags); |
949d2ff0 | 489 | |
b1ab9ed8 A |
490 | /* |
491 | * create list of pendingRequests parallel to certGroup | |
492 | */ | |
493 | PendingRequest **pending = coder.mallocn<PendingRequest *>(numCerts); | |
494 | memset(pending, 0, (numCerts * sizeof(PendingRequest *))); | |
949d2ff0 | 495 | |
b1ab9ed8 A |
496 | for(unsigned dex=0; dex<numCerts; dex++) { |
497 | OCSPClientCertID *certID = NULL; | |
498 | TPCertInfo *subject = certGroup.certAtIndex(dex); | |
949d2ff0 | 499 | |
b1ab9ed8 A |
500 | if(subject->trustSettingsFound()) { |
501 | /* functionally equivalent to root - we're done */ | |
949d2ff0 | 502 | tpOcspDebug("...tpVerifyCertGroupWithOCSP: terminate per user trust at %u", |
b1ab9ed8 A |
503 | (unsigned)dex); |
504 | goto postOcspd; | |
505 | } | |
506 | TPCertInfo *issuer = certGroup.certAtIndex(dex + 1); | |
507 | crtn = tpOcspGetCertId(*subject, *issuer, certID); | |
508 | if(crtn) { | |
509 | tpErrorLog("tpVerifyCertGroupWithOCSP: error extracting cert fields; " | |
510 | "aborting\n"); | |
511 | goto errOut; | |
512 | } | |
949d2ff0 A |
513 | |
514 | /* | |
b1ab9ed8 A |
515 | * We use the URLs in the subject cert's AuthorityInfoAccess extension |
516 | * for two things - mainly to get the URL(s) for actual OCSP transactions, | |
517 | * but also for CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT processing. | |
518 | * So, we do the per-cert processing to get them right now even if we | |
949d2ff0 | 519 | * wind up using a local responder or getting verification from cache. |
b1ab9ed8 A |
520 | */ |
521 | CSSM_DATA **urls = tpOcspUrlsFromCert(*subject, coder); | |
522 | pending[dex] = new PendingRequest(*subject, *issuer, *certID, urls, dex); | |
523 | } | |
524 | /* subsequent errors to errOut: */ | |
949d2ff0 A |
525 | |
526 | /* | |
527 | * Create empty SecAsn1OCSPDRequests big enough for all certs | |
b1ab9ed8 A |
528 | */ |
529 | ocspdReqs.requests = coder.mallocn<SecAsn1OCSPDRequest *>(numCerts + 1); | |
530 | memset(ocspdReqs.requests, 0, (numCerts + 1) * sizeof(SecAsn1OCSPDRequest *)); | |
531 | ocspdReqs.version.Data = &version; | |
532 | ocspdReqs.version.Length = 1; | |
949d2ff0 A |
533 | |
534 | /* | |
b1ab9ed8 | 535 | * For each cert, either obtain a cached OCSPResponse, or create |
949d2ff0 | 536 | * a request to get one. |
b1ab9ed8 A |
537 | * |
538 | * NOTE: in-core cache reads (via tpOcspCacheLookup() do NOT involve a | |
539 | * nonce check, no matter what the app says. If nonce checking is required by the | |
540 | * app, responses don't get added to cache if the nonce doesn't match, but once | |
949d2ff0 | 541 | * a response is validated and added to cache it's fair game for that task. |
b1ab9ed8 A |
542 | */ |
543 | for(unsigned dex=0; dex<numCerts; dex++) { | |
544 | PendingRequest *pendReq = pending[dex]; | |
545 | OCSPSingleResponse *singleResp = NULL; | |
546 | if(!cacheReadDisable) { | |
547 | singleResp = tpOcspCacheLookup(pendReq->certID, localResponder); | |
548 | } | |
549 | if(singleResp) { | |
949d2ff0 | 550 | tpOcspDebug("...tpVerifyCertGroupWithOCSP: localCache hit (1) dex %u", |
b1ab9ed8 A |
551 | (unsigned)dex); |
552 | crtn = tpApplySingleResp(*singleResp, pendReq->subject, dex, optFlags, | |
553 | vfyCtx.verifyTime, pendReq->processed); | |
554 | delete singleResp; | |
555 | if(pendReq->processed) { | |
556 | /* definitely done with this cert one way or the other */ | |
557 | if(crtn && (ourRtn == CSSM_OK)) { | |
558 | /* real cert error: first error encountered; we'll keep going */ | |
559 | ourRtn = crtn; | |
560 | } | |
561 | continue; | |
562 | } | |
563 | if(crtn) { | |
949d2ff0 A |
564 | /* |
565 | * This indicates a bad cached response. Well that's kinda weird, let's | |
b1ab9ed8 A |
566 | * just flush this out and try a normal transaction. |
567 | */ | |
568 | tpOcspCacheFlush(pendReq->certID); | |
569 | } | |
570 | } | |
949d2ff0 A |
571 | |
572 | /* | |
b1ab9ed8 A |
573 | * Prepare a request for ocspd |
574 | */ | |
949d2ff0 A |
575 | SecAsn1OCSPDRequest *ocspdReq = tpGenOcspdReq(vfyCtx, coder, |
576 | pendReq->subject, pendReq->issuer, pendReq->certID, | |
b1ab9ed8 A |
577 | const_cast<const CSSM_DATA **>(pendReq->urls), |
578 | pendReq->nonce); | |
579 | if(ocspdReq == NULL) { | |
580 | /* tpGenOcspdReq determined there was no route to OCSP responder */ | |
581 | tpOcspDebug("tpVerifyCertGroupWithOCSP: no OCSP possible for cert %u", dex); | |
582 | continue; | |
583 | } | |
584 | ocspdReqs.requests[numRequests++] = ocspdReq; | |
585 | } | |
586 | if(numRequests == 0) { | |
587 | /* no candidates for OCSP: almost done */ | |
588 | goto postOcspd; | |
589 | } | |
949d2ff0 | 590 | |
b1ab9ed8 A |
591 | /* ship requests off to ocspd, get ocspReplies back */ |
592 | if(coder.encodeItem(&ocspdReqs, kSecAsn1OCSPDRequestsTemplate, derOcspdRequests)) { | |
593 | tpErrorLog("tpVerifyCertGroupWithOCSP: error encoding ocspdReqs\n"); | |
594 | ourRtn = CSSMERR_TP_INTERNAL_ERROR; | |
595 | goto errOut; | |
596 | } | |
597 | crtn = ocspdFetch(vfyCtx.alloc, derOcspdRequests, derOcspdReplies); | |
598 | if(crtn) { | |
599 | tpErrorLog("tpVerifyCertGroupWithOCSP: error during ocspd RPC\n"); | |
600 | #ifndef NDEBUG | |
601 | cssmPerror("ocspdFetch", crtn); | |
602 | #endif | |
949d2ff0 | 603 | /* But this is not necessarily fatal...update per-cert status and check |
b1ab9ed8 A |
604 | * caller requirements below */ |
605 | goto postOcspd; | |
606 | } | |
607 | memset(&ocspdReplies, 0, sizeof(ocspdReplies)); | |
949d2ff0 | 608 | prtn = coder.decodeItem(derOcspdReplies, kSecAsn1OCSPDRepliesTemplate, |
b1ab9ed8 A |
609 | &ocspdReplies); |
610 | /* we're done with this, mallocd in ocspdFetch() */ | |
611 | vfyCtx.alloc.free(derOcspdReplies.Data); | |
612 | if(prtn) { | |
949d2ff0 | 613 | /* |
b1ab9ed8 A |
614 | * This can happen when an OCSP server provides bad data...we cannot |
615 | * determine which cert is associated with this bad response; | |
616 | * just flag it with the first one and proceed to the loop that | |
617 | * handles CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT. | |
618 | */ | |
619 | tpErrorLog("tpVerifyCertGroupWithOCSP: error decoding ocspd reply\n"); | |
620 | pending[0]->subject.addStatusCode(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); | |
621 | goto postOcspd; | |
622 | } | |
623 | if((ocspdReplies.version.Length != 1) || | |
624 | (ocspdReplies.version.Data[0] != OCSPD_REPLY_VERS)) { | |
625 | tpErrorLog("tpVerifyCertGroupWithOCSP: ocspd reply version mismatch\n"); | |
626 | if(ourRtn == CSSM_OK) { | |
627 | ourRtn = CSSMERR_TP_INTERNAL_ERROR; // maybe something better? | |
628 | } | |
629 | goto errOut; | |
630 | } | |
949d2ff0 | 631 | |
b1ab9ed8 A |
632 | /* process each reply */ |
633 | numReplies = ocspdArraySize((const void **)ocspdReplies.replies); | |
634 | for(unsigned dex=0; dex<numReplies; dex++) { | |
635 | SecAsn1OCSPDReply *reply = ocspdReplies.replies[dex]; | |
949d2ff0 | 636 | |
b1ab9ed8 A |
637 | /* Cook up our version of an OCSPResponse from the encoded data */ |
638 | OCSPResponse *ocspResp = NULL; | |
639 | try { | |
640 | ocspResp = new OCSPResponse(reply->ocspResp, TP_OCSP_CACHE_TTL); | |
641 | } | |
642 | catch(...) { | |
643 | tpErrorLog("tpVerifyCertGroupWithOCSP: error decoding ocsp response\n"); | |
644 | /* what the heck, keep going */ | |
645 | continue; | |
646 | } | |
949d2ff0 A |
647 | |
648 | /* | |
649 | * Find matching subject cert if possible (it's technically optional for | |
b1ab9ed8 A |
650 | * verification of the response in some cases, e.g., local responder). |
651 | */ | |
652 | PendingRequest *pendReq = NULL; // fully qualified | |
653 | PendingRequest *reqWithIdMatch = NULL; // CertID match only, not nonce | |
654 | for(unsigned pdex=0; pdex<numCerts; pdex++) { | |
949d2ff0 | 655 | |
b1ab9ed8 A |
656 | /* first check ID match; that is required no matter what */ |
657 | if((pending[pdex])->certID.compareToExist(reply->certID)) { | |
658 | reqWithIdMatch = pending[pdex]; | |
659 | } | |
660 | if(reqWithIdMatch == NULL) { | |
661 | continue; | |
662 | } | |
663 | if(!genNonce) { | |
664 | /* that's good enough */ | |
665 | pendReq = reqWithIdMatch; | |
949d2ff0 | 666 | tpOcspDebug("OCSP process reply: CertID match, no nonce"); |
b1ab9ed8 A |
667 | break; |
668 | } | |
669 | if(tpCompareCssmData(&reqWithIdMatch->nonce, ocspResp->nonce())) { | |
949d2ff0 | 670 | tpOcspDebug("OCSP process reply: nonce MATCH"); |
b1ab9ed8 A |
671 | pendReq = reqWithIdMatch; |
672 | break; | |
673 | } | |
949d2ff0 | 674 | |
b1ab9ed8 | 675 | /* |
949d2ff0 | 676 | * In this case we keep going; if we never find a match, then we can |
b1ab9ed8 A |
677 | * use reqWithIdMatch if !requireRespNonce. |
678 | */ | |
949d2ff0 | 679 | tpOcspDebug("OCSP process reply: certID match, nonce MISMATCH"); |
b1ab9ed8 A |
680 | } |
681 | if(pendReq == NULL) { | |
682 | if(requireRespNonce) { | |
949d2ff0 | 683 | tpOcspDebug("OCSP process reply: tossing out response due to " |
b1ab9ed8 A |
684 | "requireRespNonce"); |
685 | delete ocspResp; | |
686 | if(ourRtn == CSSM_OK) { | |
687 | ourRtn = CSSMERR_APPLETP_OCSP_NONCE_MISMATCH; | |
688 | } | |
689 | continue; | |
690 | } | |
691 | if(reqWithIdMatch != NULL) { | |
692 | /* | |
693 | * Nonce mismatch but caller thinks that's OK. Log it and proceed. | |
694 | */ | |
695 | assert(genNonce); | |
949d2ff0 | 696 | tpOcspDebug("OCSP process reply: using bad nonce due to !requireRespNonce"); |
b1ab9ed8 A |
697 | pendReq = reqWithIdMatch; |
698 | pendReq->subject.addStatusCode(CSSMERR_APPLETP_OCSP_NONCE_MISMATCH); | |
699 | } | |
700 | } | |
701 | TPCertInfo *issuer = NULL; | |
702 | if(pendReq != NULL) { | |
703 | issuer = &pendReq->issuer; | |
704 | } | |
949d2ff0 | 705 | |
b1ab9ed8 A |
706 | /* verify response and either throw out or add to local cache */ |
707 | respStat = tpVerifyOcspResp(vfyCtx, coder, issuer, *ocspResp, crtn); | |
708 | switch(respStat) { | |
709 | case ORS_Good: | |
710 | break; | |
711 | case ORS_Unknown: | |
712 | /* not an error but we can't use it */ | |
713 | if((crtn != CSSM_OK) && (pendReq != NULL)) { | |
714 | /* pass this info back to caller here... */ | |
715 | pendReq->subject.addStatusCode(crtn); | |
716 | } | |
717 | delete ocspResp; | |
718 | continue; | |
719 | case ORS_Bad: | |
720 | delete ocspResp; | |
949d2ff0 A |
721 | /* |
722 | * An exceptional case: synchronously flush the OCSPD cache and send a | |
723 | * new request for just this one cert. | |
b1ab9ed8 A |
724 | * FIXME: does this really buy us anything? A DOS attacker who managed |
725 | * to get this bogus response into our cache is likely to be able | |
726 | * to do it again and again. | |
727 | */ | |
728 | tpOcspDebug("tpVerifyCertGroupWithOCSP: flush/refetch for cert %u", dex); | |
729 | ocspResp = tpOcspFlushAndReFetch(vfyCtx, coder, pendReq->subject, | |
730 | pendReq->issuer, pendReq->certID); | |
731 | if(ocspResp == NULL) { | |
732 | tpErrorLog("tpVerifyCertGroupWithOCSP: error on flush/refetch\n"); | |
733 | ourRtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE; | |
734 | goto errOut; | |
735 | } | |
736 | respStat = tpVerifyOcspResp(vfyCtx, coder, issuer, *ocspResp, crtn); | |
737 | if(respStat != ORS_Good) { | |
738 | tpErrorLog("tpVerifyCertGroupWithOCSP: verify error after " | |
739 | "flush/refetch\n"); | |
740 | if((crtn != CSSM_OK) && (pendReq != NULL)) { | |
741 | /* pass this info back to caller here... */ | |
742 | if(pendReq->subject.addStatusCode(crtn)) { | |
743 | ourRtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE; | |
744 | } | |
745 | } | |
746 | else { | |
747 | ourRtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE; | |
748 | } | |
749 | goto errOut; | |
750 | } | |
751 | /* Voila! Recovery. Proceed. */ | |
949d2ff0 | 752 | tpOcspDebug("tpVerifyCertGroupWithOCSP: refetch for cert %u SUCCEEDED", |
b1ab9ed8 A |
753 | dex); |
754 | break; | |
755 | } /* switch response status */ | |
949d2ff0 | 756 | |
b1ab9ed8 A |
757 | if(!cacheWriteDisable) { |
758 | tpOcspCacheAdd(reply->ocspResp, localResponder); | |
759 | } | |
949d2ff0 | 760 | |
b1ab9ed8 A |
761 | /* attempt to apply to pendReq */ |
762 | if(pendReq != NULL) { | |
949d2ff0 | 763 | OCSPSingleResponse *singleResp = |
b1ab9ed8 A |
764 | ocspResp->singleResponseFor(pendReq->certID); |
765 | if(singleResp) { | |
949d2ff0 | 766 | crtn = tpApplySingleResp(*singleResp, pendReq->subject, pendReq->dex, |
b1ab9ed8 A |
767 | optFlags, vfyCtx.verifyTime, pendReq->processed); |
768 | if(crtn && (ourRtn == CSSM_OK)) { | |
769 | ourRtn = crtn; | |
770 | } | |
771 | delete singleResp; | |
772 | } | |
773 | } /* a reply which matches a pending request */ | |
949d2ff0 A |
774 | |
775 | /* | |
b1ab9ed8 A |
776 | * Done with this - note local OCSP response cache doesn't store this |
777 | * object; it stores an encoded copy. | |
778 | */ | |
779 | delete ocspResp; | |
780 | } /* for each reply */ | |
781 | ||
782 | postOcspd: | |
783 | ||
949d2ff0 A |
784 | /* |
785 | * Now process each cert which hasn't had an OCSP response applied to it. | |
786 | * This can happen if we get back replies which are not strictly in 1-1 sync with | |
b1ab9ed8 A |
787 | * our requests but which nevertheless contain valid info for more than one |
788 | * cert each. | |
789 | */ | |
790 | for(unsigned dex=0; dex<numCerts; dex++) { | |
791 | PendingRequest *pendReq = pending[dex]; | |
792 | if(pendReq == NULL) { | |
793 | /* i.e. terminated due to user trust */ | |
949d2ff0 | 794 | tpOcspDebug("...tpVerifyCertGroupWithOCSP: NULL pendReq dex %u", |
b1ab9ed8 A |
795 | (unsigned)dex); |
796 | break; | |
797 | } | |
798 | if(pendReq->processed) { | |
799 | continue; | |
800 | } | |
801 | OCSPSingleResponse *singleResp = NULL; | |
802 | /* Note this corner case will not work if cache is disabled. */ | |
803 | if(!cacheReadDisable) { | |
804 | singleResp = tpOcspCacheLookup(pendReq->certID, localResponder); | |
805 | } | |
806 | if(singleResp) { | |
949d2ff0 | 807 | tpOcspDebug("...tpVerifyCertGroupWithOCSP: localCache (2) hit dex %u", |
b1ab9ed8 | 808 | (unsigned)dex); |
949d2ff0 | 809 | crtn = tpApplySingleResp(*singleResp, pendReq->subject, dex, optFlags, |
b1ab9ed8 A |
810 | vfyCtx.verifyTime, pendReq->processed); |
811 | if(crtn) { | |
812 | if(ourRtn == CSSM_OK) { | |
813 | ourRtn = crtn; | |
814 | } | |
815 | } | |
816 | delete singleResp; | |
817 | } | |
818 | if(!pendReq->processed) { | |
819 | /* Couldn't perform OCSP for this cert. */ | |
820 | tpOcspDebug("tpVerifyCertGroupWithOCSP: OCSP_UNAVAILABLE for cert %u", dex); | |
821 | bool required = false; | |
822 | CSSM_RETURN responseStatus = CSSM_OK; | |
823 | if(pendReq->subject.numStatusCodes() > 0) { | |
824 | /* | |
825 | * Check whether we got a response for this cert, but it was rejected | |
826 | * due to being improperly signed. That should result in an actual | |
827 | * error, even under Best Attempt processing. (10743149) | |
828 | */ | |
829 | if(pendReq->subject.hasStatusCode(CSSMERR_APPLETP_OCSP_BAD_RESPONSE)) { | |
830 | // responseStatus = CSSMERR_APPLETP_OCSP_BAD_RESPONSE; <rdar://problem/10831157> | |
831 | } else if(pendReq->subject.hasStatusCode(CSSMERR_APPLETP_OCSP_SIG_ERROR)) { | |
832 | responseStatus = CSSMERR_APPLETP_OCSP_SIG_ERROR; | |
833 | } else if(pendReq->subject.hasStatusCode(CSSMERR_APPLETP_OCSP_NO_SIGNER)) { | |
834 | responseStatus = CSSMERR_APPLETP_OCSP_NO_SIGNER; | |
835 | } | |
836 | } | |
837 | if(responseStatus == CSSM_OK) { | |
838 | /* no response available (as opposed to getting an invalid response) */ | |
839 | pendReq->subject.addStatusCode(CSSMERR_APPLETP_OCSP_UNAVAILABLE); | |
840 | } | |
841 | if(optFlags & CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT) { | |
842 | /* every cert needs OCSP */ | |
843 | tpOcspDebug("tpVerifyCertGroupWithOCSP: response required for all certs, missing for cert %u", dex); | |
844 | required = true; | |
845 | } | |
846 | else if(optFlags & CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT) { | |
847 | /* this cert needs OCSP if it had an AIA extension with an OCSP URI */ | |
848 | if(pendReq->urls) { | |
849 | tpOcspDebug("tpVerifyCertGroupWithOCSP: OCSP URI present but no valid response for cert %u", dex); | |
850 | required = true; | |
851 | } | |
852 | } | |
853 | if( (required && pendReq->subject.isStatusFatal(CSSMERR_APPLETP_OCSP_UNAVAILABLE)) || | |
854 | (responseStatus != CSSM_OK && pendReq->subject.isStatusFatal(responseStatus)) ) { | |
855 | /* fatal error, but we keep on processing */ | |
856 | if(ourRtn == CSSM_OK) { | |
857 | ourRtn = (responseStatus != CSSM_OK) ? responseStatus : CSSMERR_APPLETP_OCSP_UNAVAILABLE; | |
858 | } | |
859 | } | |
860 | } | |
861 | } | |
949d2ff0 | 862 | errOut: |
b1ab9ed8 A |
863 | for(unsigned dex=0; dex<numCerts; dex++) { |
864 | PendingRequest *pendReq = pending[dex]; | |
865 | if(pendReq == NULL) { | |
866 | /* i.e. terminated due to user trust */ | |
867 | break; | |
868 | } | |
869 | delete &pendReq->certID; | |
870 | delete pendReq; | |
871 | } | |
872 | return ourRtn; | |
873 | } |