|
1 // Copyright (c) 2001-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 // All rights reserved. |
|
3 // This component and the accompanying materials are made available |
|
4 // under the terms of "Eclipse Public License v1.0" |
|
5 // which accompanies this distribution, and is available |
|
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 // |
|
8 // Initial Contributors: |
|
9 // Nokia Corporation - initial contribution. |
|
10 // |
|
11 // Contributors: |
|
12 // |
|
13 // Description: |
|
14 // Define methods for validating a response. |
|
15 // |
|
16 // |
|
17 |
|
18 #include "validator.h" |
|
19 #include "ocsp.h" |
|
20 #include "panic.h" |
|
21 #include "transaction.h" |
|
22 #include <x509cert.h> |
|
23 |
|
24 // We allow a certain amount of leeway when checking times. This specifies the |
|
25 // default value. |
|
26 const TInt KDefaultLeewaySeconds = 5 * 60; // 5 minutes |
|
27 |
|
28 // The spec says we must check that the thisUpdate field is "sufficiently |
|
29 // recent". This specifies the default value for the maximum age we tolerate |
|
30 // (in seconds). |
|
31 const TInt KDefaultMaxStatusAge = 30 * 24 * 60 * 60; // 30 days |
|
32 |
|
33 COCSPValidator* COCSPValidator::NewL( const COCSPParameters& aParameters) |
|
34 { |
|
35 COCSPValidator* self = new (ELeave) COCSPValidator(aParameters); |
|
36 CleanupStack::PushL(self); |
|
37 self->ConstructL(); |
|
38 CleanupStack::Pop(self); |
|
39 CActiveScheduler::Add(self); |
|
40 return self; |
|
41 } |
|
42 |
|
43 |
|
44 COCSPValidator::~COCSPValidator() |
|
45 { |
|
46 Cancel(); |
|
47 |
|
48 iAuthorisationScheme.Close(); |
|
49 iRequestIndex.Close(); |
|
50 |
|
51 delete iValidationTime; |
|
52 delete iResponderCertRequest; |
|
53 delete iResponderCertResponse; |
|
54 delete iTransaction; |
|
55 } |
|
56 |
|
57 |
|
58 COCSPValidator::COCSPValidator( const COCSPParameters& aParameters) : |
|
59 CActive(CActive::EPriorityStandard), |
|
60 iMaxStatusAge(KDefaultMaxStatusAge), |
|
61 iLeewaySeconds(KDefaultLeewaySeconds), |
|
62 iResponderCertCheck(EFalse), |
|
63 iUseNonce(ETrue), |
|
64 iParameters(&aParameters) |
|
65 {} |
|
66 |
|
67 void COCSPValidator::ConstructL() |
|
68 { |
|
69 for (TUint j = 0 ; j < iParameters->AuthSchemeCount() ; ++j) |
|
70 { |
|
71 User::LeaveIfError(iAuthorisationScheme.Append(&iParameters->AuthScheme(j))); |
|
72 } |
|
73 if (iParameters->ValidationTime()) |
|
74 { |
|
75 iValidationTime = new (ELeave) TTime(*iParameters->ValidationTime()); |
|
76 } |
|
77 if (iParameters->MaxStatusAge()) |
|
78 { |
|
79 iMaxStatusAge = *iParameters->MaxStatusAge(); |
|
80 } |
|
81 if (iParameters->TimeLeeway()) |
|
82 { |
|
83 iLeewaySeconds = *iParameters->TimeLeeway(); |
|
84 } |
|
85 iResponderCertCheck = iParameters->ReponderCertCheck(); |
|
86 iUseNonce = iParameters->UseNonce(); |
|
87 } |
|
88 |
|
89 void COCSPValidator::Validate(const COCSPRequest& aRequest, COCSPResponse& aResponse, |
|
90 TOCSPOutcome& aOutcome, TRequestStatus& aStatus) |
|
91 { |
|
92 TRAPD(err, DoValidateL(aRequest, aResponse, aOutcome, aStatus)); |
|
93 |
|
94 if (err != KErrNone) |
|
95 { |
|
96 TRequestStatus* status = &aStatus; |
|
97 User::RequestComplete(status, err); |
|
98 } |
|
99 } |
|
100 |
|
101 void COCSPValidator::DoValidateL(const COCSPRequest& aRequest, COCSPResponse& aResponse, |
|
102 TOCSPOutcome& aOutcome, TRequestStatus& aStatus) |
|
103 { |
|
104 iRequest = &aRequest; |
|
105 iResponse = &aResponse; |
|
106 |
|
107 iValidationStatus = &aStatus; |
|
108 aStatus = KRequestPending; |
|
109 |
|
110 iOutcome = &aOutcome; |
|
111 iOutcome->iStatus = OCSP::EClientInternalError; |
|
112 // this has been set to EUnknown at client side, but still making sure that this |
|
113 // value is being used. |
|
114 iOutcome->iResult = OCSP::EUnknown; |
|
115 |
|
116 if ( !IsResponseWellFormed()) |
|
117 { |
|
118 User::RequestComplete(iValidationStatus, KErrNone); |
|
119 return; |
|
120 } |
|
121 |
|
122 // points to the current scheme being used for validation of the certificate |
|
123 // in question. |
|
124 iIndexScheme = -1; |
|
125 ProcessSchemeValidationL(); |
|
126 } |
|
127 |
|
128 TBool COCSPValidator::IsResponseWellFormed() |
|
129 { |
|
130 // Check the certificates in the response were indeed those we asked for |
|
131 // Make lookup table indexing request/response while we're at it |
|
132 |
|
133 TInt numResponseCerts = iResponse->CertCount(); |
|
134 TInt numRequestCerts = iRequest->CertCount(); |
|
135 |
|
136 if (numRequestCerts < numResponseCerts) |
|
137 { |
|
138 iOutcome->iStatus = OCSP::EMalformedResponse; |
|
139 return EFalse; |
|
140 } |
|
141 else if (numRequestCerts > numResponseCerts) |
|
142 { |
|
143 iOutcome->iStatus = OCSP::EMissingCertificates; |
|
144 return EFalse; |
|
145 } |
|
146 |
|
147 // Check each cert to verify that each request has a corresponding response present. |
|
148 // In process, set up array giving the position in the request of each cert in the response |
|
149 iRequestIndex.Reset(); |
|
150 |
|
151 for (TInt requestIndex = 0; requestIndex < numRequestCerts; ++requestIndex) |
|
152 { |
|
153 // This is what we're after |
|
154 const COCSPCertID& requestCertID = iRequest->CertInfo(requestIndex).CertID(); |
|
155 |
|
156 // This is where it is in the response |
|
157 TInt responseIndex = iResponse->Find(requestCertID); |
|
158 |
|
159 if (responseIndex < 0) |
|
160 { |
|
161 iOutcome->iStatus = OCSP::EMissingCertificates; |
|
162 return EFalse; |
|
163 } |
|
164 iRequestIndex.Append(responseIndex); |
|
165 } |
|
166 // All found |
|
167 return ETrue; |
|
168 } |
|
169 |
|
170 void COCSPValidator::ProcessSchemeValidationL() |
|
171 { |
|
172 TInt count = iAuthorisationScheme.Count(); |
|
173 __ASSERT_ALWAYS(count, Panic(KErrNoAuthorisationSchemes)); |
|
174 if (++iIndexScheme < count) |
|
175 { |
|
176 iSchemeInUse = iAuthorisationScheme[iIndexScheme]; |
|
177 TTime validationTime = ValidationTime(); |
|
178 iSchemeInUse->ValidateL(iOutcome->iStatus, *iResponse, validationTime, iStatus, *iRequest); |
|
179 iState = EWaitingResponse; |
|
180 SetActive(); |
|
181 } |
|
182 else |
|
183 { |
|
184 User::RequestComplete(iValidationStatus, KErrNone); |
|
185 } |
|
186 } |
|
187 |
|
188 // Get status of least trusted cert |
|
189 void COCSPValidator::FinalResponseValidationL() |
|
190 { |
|
191 // Do nonce last so can still trust rest of validation if nonce is missing. |
|
192 if(ValidateTimeL()) |
|
193 { |
|
194 ValidateNonce(); |
|
195 } |
|
196 |
|
197 if (iOutcome->iStatus == OCSP::EMissingNonce || |
|
198 iOutcome->iStatus == OCSP::EValid ) |
|
199 { |
|
200 iOutcome->iResult = CheckOCSPStatus(iResponse); |
|
201 } |
|
202 else |
|
203 { |
|
204 // If the response is not valid, result is always unknown |
|
205 iOutcome->iResult = OCSP::EUnknown; |
|
206 } |
|
207 |
|
208 if(iResponderCertCheck) |
|
209 { |
|
210 iResponderCert = iSchemeInUse->ResponderCert(); |
|
211 |
|
212 if(iResponderCert != NULL) |
|
213 { |
|
214 iIssuerCert = &iRequest->CertInfo(0).Issuer(); |
|
215 SendResponderCertL(); |
|
216 } |
|
217 else |
|
218 { |
|
219 User::RequestComplete(iValidationStatus, KErrNone); |
|
220 } |
|
221 } |
|
222 else |
|
223 { |
|
224 User::RequestComplete(iValidationStatus, KErrNone); |
|
225 } |
|
226 } |
|
227 |
|
228 TBool COCSPValidator::ValidateTimeL() |
|
229 { |
|
230 const TTime validationTime = ValidationTime(); |
|
231 const TTime producedAt = iResponse->ProducedAt(); |
|
232 |
|
233 // For each certificate request, do the following: |
|
234 // 1. Check thisUpdate |
|
235 // 2. Check producedAt |
|
236 TInt numCerts = iRequest->CertCount(); |
|
237 for (TInt requestIndex = 0; requestIndex < numCerts; ++requestIndex) |
|
238 { |
|
239 const COCSPResponseCertInfo& responseCertInfo = iResponse->CertInfo(iRequestIndex[requestIndex]); |
|
240 const TTime thisUpdate = responseCertInfo.ThisUpdate(); |
|
241 const TTime* nextUpdate = responseCertInfo.NextUpdate(); |
|
242 |
|
243 // Check validity interval of response includes validation time |
|
244 // and producedAt time (if different). Give iLeewaySeconds second's lee-way. |
|
245 |
|
246 // 4.2.2.1 "Responses whose thisUpdate time is later than the local |
|
247 // system time SHOULD be considered unreliable" |
|
248 if (TimeIsBeforeL(validationTime, thisUpdate)) |
|
249 { |
|
250 iOutcome->iStatus = OCSP::EThisUpdateTooLate; |
|
251 return EFalse; |
|
252 } |
|
253 |
|
254 // Check producedAt later than thisUpdate. This is not mandated by the spec. |
|
255 if (TimeIsBeforeL(producedAt, thisUpdate)) |
|
256 { |
|
257 iOutcome->iStatus = OCSP::EThisUpdateTooLate; |
|
258 return EFalse; |
|
259 } |
|
260 |
|
261 if (nextUpdate) |
|
262 { |
|
263 // 4.2.2.1 "Responses whose nextUpdate value is earlier than the |
|
264 // local system time value SHOULD be considered unreliable" |
|
265 // 3.2.6 "OCSP clients shall confirm that ... nextUpdate is greater |
|
266 // than the current time." |
|
267 if (TimeIsBeforeL(*nextUpdate, validationTime)) |
|
268 { |
|
269 iOutcome->iStatus = OCSP::ENextUpdateTooEarly; |
|
270 return EFalse; |
|
271 } |
|
272 |
|
273 // Check nextUpdate later than producedAt. This is not mandated by the spec. |
|
274 if (TimeIsBeforeL(*nextUpdate, producedAt)) |
|
275 { |
|
276 iOutcome->iStatus = OCSP::ENextUpdateTooEarly; |
|
277 return EFalse; |
|
278 } |
|
279 } |
|
280 |
|
281 // 3.2.5 "OCSP clients SHALL confirm that ... thisUpdate is sufficiently |
|
282 // recent" |
|
283 if (iMaxStatusAge) |
|
284 { |
|
285 TTimeIntervalSeconds difference; |
|
286 |
|
287 User::LeaveIfError(validationTime.SecondsFrom(thisUpdate, difference)); |
|
288 const TTimeIntervalSeconds maxUpdateAge(iMaxStatusAge + iLeewaySeconds); |
|
289 if (difference > maxUpdateAge) |
|
290 { |
|
291 iOutcome->iStatus = OCSP::EThisUpdateTooEarly; |
|
292 return EFalse; |
|
293 } |
|
294 } |
|
295 |
|
296 // Check certificate validity period against validation time. |
|
297 // |
|
298 // Strictly speaking, the OCSP protcol is about checking revocation |
|
299 // rather then checking whether a certificate has just expired. |
|
300 // However, it's difficult to check this on a device when you don't have |
|
301 // an accurate value for the current time. We do the check here for |
|
302 // completeness, and trust the time given to us by the ocsp server. If |
|
303 // we are using a nonce, as we will be most of the time, we can |
|
304 // guarantee that the producedAt time is current. |
|
305 |
|
306 const CX509Certificate& cert = iRequest->CertInfo(requestIndex).Subject(); |
|
307 const CValidityPeriod& validityPeriod = cert.ValidityPeriod(); |
|
308 |
|
309 if (!validityPeriod.Valid(validationTime)) |
|
310 { |
|
311 iOutcome->iStatus = OCSP::ECertificateNotValidAtValidationTime; |
|
312 return EFalse; |
|
313 } |
|
314 |
|
315 } // Continue with next cert |
|
316 |
|
317 // If we've got this far, we're fine |
|
318 return ETrue; |
|
319 } |
|
320 |
|
321 TBool COCSPValidator::ValidateNonce() |
|
322 { |
|
323 const TDesC8* requestNonce = iRequest->Nonce(); |
|
324 const TPtrC8* responseNonce = iResponse->DataElementEncoding(COCSPResponse::ENonce); |
|
325 |
|
326 if (requestNonce) |
|
327 { |
|
328 if (responseNonce) |
|
329 { |
|
330 if (*requestNonce == *responseNonce) |
|
331 { |
|
332 return ETrue; |
|
333 } |
|
334 else |
|
335 { |
|
336 iOutcome->iStatus = OCSP::ENonceMismatch; |
|
337 return EFalse; |
|
338 } |
|
339 } |
|
340 else |
|
341 { |
|
342 iOutcome->iStatus = OCSP::EMissingNonce; |
|
343 return EFalse; |
|
344 } |
|
345 } |
|
346 else |
|
347 { |
|
348 if (responseNonce) |
|
349 { |
|
350 // Shouldn't have a nonce! |
|
351 iOutcome->iStatus = OCSP::EMalformedResponse; |
|
352 return EFalse; |
|
353 } |
|
354 else |
|
355 { |
|
356 // No nonces - fine |
|
357 return ETrue; |
|
358 } |
|
359 } |
|
360 } |
|
361 |
|
362 // Return true if first argument is iLeewaySeconds or more before the second |
|
363 // argument. Hence it is conservative, and should be always used "positively" |
|
364 // to check for error conditions. |
|
365 TBool COCSPValidator::TimeIsBeforeL(const TTime& aBefore, const TTime& aAfter) |
|
366 { |
|
367 TTimeIntervalSeconds difference; |
|
368 const TTimeIntervalSeconds leeway(iLeewaySeconds); |
|
369 |
|
370 User::LeaveIfError(aAfter.SecondsFrom(aBefore, difference)); |
|
371 return (difference > leeway); |
|
372 } |
|
373 |
|
374 void COCSPValidator::RunL() |
|
375 { |
|
376 User::LeaveIfError(iStatus.Int()); |
|
377 |
|
378 switch (iState) |
|
379 { |
|
380 case EWaitingResponse: |
|
381 CheckSchemeValidationL(); |
|
382 break; |
|
383 case EValidating: |
|
384 ProcessSchemeValidationL(); |
|
385 break; |
|
386 case EValidateResponderCert: |
|
387 ValidateResponderCertL(); |
|
388 break; |
|
389 default: |
|
390 ASSERT(FALSE); |
|
391 } |
|
392 } |
|
393 |
|
394 void COCSPValidator::DoCancel() |
|
395 { |
|
396 TInt count = iAuthorisationScheme.Count(); |
|
397 __ASSERT_ALWAYS(count, Panic(KErrNoAuthorisationSchemes)); |
|
398 if (iState == EWaitingResponse) |
|
399 { |
|
400 ASSERT(iSchemeInUse != NULL); |
|
401 iSchemeInUse->CancelValidate(); |
|
402 } |
|
403 User::RequestComplete(iValidationStatus, KErrCancel); |
|
404 } |
|
405 |
|
406 TInt COCSPValidator::RunError(TInt aError) |
|
407 { |
|
408 User::RequestComplete(iValidationStatus, aError); |
|
409 return KErrNone; |
|
410 } |
|
411 |
|
412 void COCSPValidator::CheckSchemeValidationL() |
|
413 { |
|
414 // If any scheme says it's OK, we're happy, otherwise we'll return |
|
415 // with whatever the last scheme said. |
|
416 if (iOutcome->iStatus == OCSP::EValid) |
|
417 { |
|
418 FinalResponseValidationL(); |
|
419 } |
|
420 else |
|
421 { |
|
422 iState = EValidating; |
|
423 // Fire off AO |
|
424 TRequestStatus* status = &iStatus; |
|
425 User::RequestComplete(status, KErrNone); |
|
426 SetActive(); |
|
427 } |
|
428 } |
|
429 |
|
430 TTime COCSPValidator::ValidationTime() const |
|
431 { |
|
432 __ASSERT_ALWAYS(iResponse, Panic(KErrNotReady)); |
|
433 if (iValidationTime) |
|
434 { |
|
435 return *iValidationTime; |
|
436 } |
|
437 else |
|
438 { |
|
439 TTime gmt; |
|
440 |
|
441 // if secure time is not available then fall back to the insecure version. |
|
442 if(gmt.UniversalTimeSecure() == KErrNoSecureTime) |
|
443 { |
|
444 gmt.UniversalTime(); |
|
445 } |
|
446 return gmt; |
|
447 } |
|
448 } |
|
449 |
|
450 /** |
|
451 * For the response in question there can be more than one authentication scheme initialized. |
|
452 * We need to find out whether the schemes initialized contain at least delegate or direct auth scheme, |
|
453 * if any of them is present we can send the request for validation for responder certificate, as |
|
454 * validation of responder certificate should only work for these 2 schemes. |
|
455 * |
|
456 * If we get a valid scheme, following would be the sequence of operation: |
|
457 * 1. Retrieve the responder certificate and the issuer(should be the CA who issued the certificate |
|
458 * in question) who has issued the responder certificate. |
|
459 * 2. Check whether the responder certificate contains the id-pkix-ocsp-nocheck, if present there is no need for |
|
460 * sending it for OCSP check, if not present send it for OCSP check. |
|
461 |
|
462 * Send the responder certificate for OCSP checking. Here we would use the existing parameters |
|
463 * for creating the responder certificate request, as this check is an extension of the original |
|
464 * certificate OCSP check. |
|
465 */ |
|
466 void COCSPValidator::SendResponderCertL() |
|
467 { |
|
468 if( OCSPUtils::DoesCertHaveOCSPNoCheckExt(*iResponderCert)) |
|
469 { |
|
470 User::RequestComplete(iValidationStatus, KErrNone); |
|
471 return; |
|
472 } |
|
473 |
|
474 iResponderCertRequest = COCSPRequest::NewL(iUseNonce); |
|
475 iResponderCertRequest->AddCertificateL(*iResponderCert, *iIssuerCert); |
|
476 |
|
477 // Only add further requests if there is: |
|
478 // a URI (either AIA URI or default URI) |
|
479 TDesC8* uri = NULL; |
|
480 TRAPD(error, uri = OCSPUtils::ServerUriL(iResponderCertRequest->CertInfo(0).Subject(),iParameters)); |
|
481 |
|
482 if(error == KErrArgument) |
|
483 { |
|
484 iOutcome->iStatus = OCSP::ENoServerSpecified; |
|
485 TRequestStatus* status = &iStatus; |
|
486 User::RequestComplete(status, OCSP::ENoServerSpecified); |
|
487 iState = EValidateResponderCert; |
|
488 SetActive(); |
|
489 return; |
|
490 } |
|
491 |
|
492 User::LeaveIfError(error); |
|
493 CleanupStack::PushL(uri); |
|
494 |
|
495 // if state is valid it means that uri has been retrieved. |
|
496 __ASSERT_ALWAYS(uri != NULL, Panic(OCSP::EInvalidURI)); |
|
497 MOCSPTransport& transport = *iParameters->Transport(); |
|
498 delete iTransaction; |
|
499 iTransaction = NULL; |
|
500 iTransaction = COCSPTransaction::NewL(*uri, transport, iParameters->RetryCount(), iParameters->Timeout()); |
|
501 iTransaction->SendRequest(*iResponderCertRequest, iStatus); |
|
502 CleanupStack::PopAndDestroy(uri); |
|
503 iState = EValidateResponderCert; |
|
504 SetActive(); |
|
505 } |
|
506 |
|
507 /** |
|
508 * Receive the response for responder certificate OCSP check. |
|
509 * Leave if there is any problem with the received response. |
|
510 * If the response is well formed then send it for further validation. |
|
511 */ |
|
512 void COCSPValidator::ValidateResponderCertL() |
|
513 { |
|
514 TInt status = iStatus.Int(); |
|
515 |
|
516 if (status == KErrNone) |
|
517 { |
|
518 iResponderCertResponse = iTransaction->TakeResponse(); |
|
519 } |
|
520 else if (status == OCSP::KErrTransportFailure) |
|
521 { |
|
522 User::Leave(OCSP::ETransportError); |
|
523 } |
|
524 else if (status == OCSP::KErrInvalidURI) |
|
525 { |
|
526 User::Leave(OCSP::EInvalidURI); |
|
527 } |
|
528 else |
|
529 { |
|
530 User::Leave(status); |
|
531 } |
|
532 |
|
533 iOutcome->iResult = CheckOCSPStatus(iResponderCertResponse); |
|
534 if(iOutcome->iResult != OCSP::EGood ) |
|
535 { |
|
536 // as the responder certificate is either revoked or unknown the final status returned |
|
537 // should be unknown. |
|
538 iOutcome->iResult = OCSP::EUnknown; |
|
539 } |
|
540 User::RequestComplete(iValidationStatus, KErrNone); |
|
541 |
|
542 } |
|
543 |
|
544 OCSP::TResult COCSPValidator::CheckOCSPStatus(const COCSPResponse* aResponse) const |
|
545 { |
|
546 OCSP::TResult result = OCSP::EGood; |
|
547 TInt numCerts = aResponse->CertCount(); |
|
548 for (TInt index = 0; index < numCerts; ++index) |
|
549 { |
|
550 const COCSPResponseCertInfo& info = aResponse->CertInfo(index); |
|
551 |
|
552 OCSP::TResult certStatus = info.Status(); |
|
553 result = certStatus > result? certStatus : result; |
|
554 } |
|
555 return result; |
|
556 } |