pkiutilities/ocsp/src/validator.cpp
changeset 0 164170e6151a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkiutilities/ocsp/src/validator.cpp	Tue Jan 26 15:20:08 2010 +0200
@@ -0,0 +1,556 @@
+// Copyright (c) 2001-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// Define methods for validating a response.
+// 
+//
+
+#include "validator.h"
+#include "ocsp.h"
+#include "panic.h"
+#include "transaction.h"
+#include <x509cert.h>
+
+// We allow a certain amount of leeway when checking times.  This specifies the
+// default value.
+const TInt KDefaultLeewaySeconds = 5 * 60; // 5 minutes
+
+// The spec says we must check that the thisUpdate field is "sufficiently
+// recent".  This specifies the default value for the maximum age we tolerate
+// (in seconds).
+const TInt KDefaultMaxStatusAge = 30 * 24 * 60 * 60; // 30 days
+
+COCSPValidator* COCSPValidator::NewL( const COCSPParameters& aParameters)
+	{
+	COCSPValidator* self = new (ELeave) COCSPValidator(aParameters);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	CActiveScheduler::Add(self);
+	return self;
+	}
+
+
+COCSPValidator::~COCSPValidator()
+	{
+	Cancel();
+
+	iAuthorisationScheme.Close();
+	iRequestIndex.Close();
+	
+	delete iValidationTime;
+	delete iResponderCertRequest;
+	delete iResponderCertResponse;
+	delete iTransaction;
+	}
+
+
+COCSPValidator::COCSPValidator( const COCSPParameters& aParameters) :
+	CActive(CActive::EPriorityStandard),
+	iMaxStatusAge(KDefaultMaxStatusAge),
+	iLeewaySeconds(KDefaultLeewaySeconds),
+	iResponderCertCheck(EFalse),
+	iUseNonce(ETrue),
+	iParameters(&aParameters)
+	{}
+
+void COCSPValidator::ConstructL() 
+	{
+	for (TUint j = 0 ; j < iParameters->AuthSchemeCount() ; ++j)
+		{
+		User::LeaveIfError(iAuthorisationScheme.Append(&iParameters->AuthScheme(j)));
+		}
+	if (iParameters->ValidationTime())
+		{
+		iValidationTime = new (ELeave) TTime(*iParameters->ValidationTime());
+		}
+	if (iParameters->MaxStatusAge())
+		{
+		iMaxStatusAge = *iParameters->MaxStatusAge();
+		}
+	if (iParameters->TimeLeeway())
+		{
+		iLeewaySeconds = *iParameters->TimeLeeway();
+		}
+	iResponderCertCheck = iParameters->ReponderCertCheck();
+	iUseNonce = iParameters->UseNonce();
+	}
+
+void COCSPValidator::Validate(const COCSPRequest& aRequest, COCSPResponse& aResponse,
+									   TOCSPOutcome& aOutcome, TRequestStatus& aStatus)
+	{
+    TRAPD(err, DoValidateL(aRequest, aResponse, aOutcome, aStatus));
+    
+    if (err != KErrNone)
+        {
+        TRequestStatus* status = &aStatus;
+        User::RequestComplete(status, err);
+        }
+	}
+
+void COCSPValidator::DoValidateL(const COCSPRequest& aRequest, COCSPResponse& aResponse,
+								 TOCSPOutcome& aOutcome, TRequestStatus& aStatus)
+	{
+	iRequest = &aRequest;
+	iResponse = &aResponse;
+
+	iValidationStatus = &aStatus;
+	aStatus = KRequestPending;
+
+	iOutcome = &aOutcome;
+	iOutcome->iStatus = OCSP::EClientInternalError;
+	// this has been set to EUnknown at client side, but still making sure that this 
+	// value is being used.
+	iOutcome->iResult = OCSP::EUnknown;
+	
+	if ( !IsResponseWellFormed())
+		{
+		User::RequestComplete(iValidationStatus, KErrNone);
+		return;
+		}
+	
+	// points to the current scheme being used for validation of the certificate
+	// in question.
+	iIndexScheme = -1;
+	ProcessSchemeValidationL();
+	}
+
+TBool COCSPValidator::IsResponseWellFormed()
+	{
+	// Check the certificates in the response were indeed those we asked for
+	// Make lookup table indexing request/response while we're at it
+
+	TInt numResponseCerts = iResponse->CertCount();
+	TInt numRequestCerts = iRequest->CertCount();
+
+	if (numRequestCerts < numResponseCerts)
+		{
+		iOutcome->iStatus = OCSP::EMalformedResponse;
+		return EFalse;
+		}
+	else if (numRequestCerts > numResponseCerts)
+		{
+		iOutcome->iStatus = OCSP::EMissingCertificates;
+		return EFalse;
+		}
+	
+	// Check each cert to verify that each request has a corresponding response present.
+	// In process, set up array giving the position in the request of each cert in the response
+	iRequestIndex.Reset();
+		
+	for (TInt requestIndex = 0; requestIndex < numRequestCerts; ++requestIndex)
+		{
+		// This is what we're after
+		const COCSPCertID& requestCertID = iRequest->CertInfo(requestIndex).CertID();
+
+		// This is where it is in the response
+		TInt responseIndex = iResponse->Find(requestCertID);
+
+		if (responseIndex < 0)
+			{
+			iOutcome->iStatus = OCSP::EMissingCertificates;
+			return EFalse;
+			}
+		iRequestIndex.Append(responseIndex);
+		}
+	// All found
+	return ETrue;
+	}
+
+void COCSPValidator::ProcessSchemeValidationL()
+	{
+	TInt count = iAuthorisationScheme.Count();
+	__ASSERT_ALWAYS(count, Panic(KErrNoAuthorisationSchemes));
+	if (++iIndexScheme < count)
+		{
+		iSchemeInUse = iAuthorisationScheme[iIndexScheme];
+		TTime validationTime = ValidationTime();
+		iSchemeInUse->ValidateL(iOutcome->iStatus, *iResponse, validationTime, iStatus, *iRequest);
+		iState = EWaitingResponse;
+		SetActive();
+		}
+	else
+		{
+		User::RequestComplete(iValidationStatus, KErrNone);
+		}
+	}
+
+// Get status of least trusted cert
+void COCSPValidator::FinalResponseValidationL()
+	{
+	// Do nonce last so can still trust rest of validation if nonce is missing.
+	if(ValidateTimeL())
+		{
+		ValidateNonce();
+		}
+	
+	if (iOutcome->iStatus == OCSP::EMissingNonce || 
+		iOutcome->iStatus == OCSP::EValid )
+		{
+		iOutcome->iResult = CheckOCSPStatus(iResponse);
+		}
+	else
+		{
+		// If the response is not valid, result is always unknown
+		iOutcome->iResult = OCSP::EUnknown;
+		}
+	
+	if(iResponderCertCheck)
+		{
+		iResponderCert = iSchemeInUse->ResponderCert();
+	 	
+	 	if(iResponderCert != NULL)
+	 		{
+	 		iIssuerCert = &iRequest->CertInfo(0).Issuer();
+	 		SendResponderCertL();
+	 		}
+	 	else
+			{
+			User::RequestComplete(iValidationStatus, KErrNone);
+			}
+		}
+	else
+		{
+		User::RequestComplete(iValidationStatus, KErrNone);
+		}
+	}
+
+TBool COCSPValidator::ValidateTimeL()
+	{
+	const TTime validationTime = ValidationTime();
+	const TTime producedAt = iResponse->ProducedAt();
+	
+	// For each certificate request, do the following:
+	// 1. Check thisUpdate
+	// 2. Check producedAt 
+	TInt numCerts = iRequest->CertCount();
+	for (TInt requestIndex = 0; requestIndex < numCerts; ++requestIndex)
+		{
+		const COCSPResponseCertInfo& responseCertInfo = iResponse->CertInfo(iRequestIndex[requestIndex]);
+		const TTime thisUpdate = responseCertInfo.ThisUpdate();
+		const TTime* nextUpdate = responseCertInfo.NextUpdate();
+
+		// Check validity interval of response includes validation time
+		// and producedAt time (if different).  Give iLeewaySeconds second's lee-way.
+
+		// 4.2.2.1 "Responses whose thisUpdate time is later than the local
+		// system time SHOULD be considered unreliable"
+		if (TimeIsBeforeL(validationTime, thisUpdate))
+			{
+			iOutcome->iStatus = OCSP::EThisUpdateTooLate;
+			return EFalse;
+			}
+
+		// Check producedAt later than thisUpdate.  This is not mandated by the spec.
+		if (TimeIsBeforeL(producedAt, thisUpdate))
+			{
+			iOutcome->iStatus = OCSP::EThisUpdateTooLate;
+			return EFalse;
+			}
+		
+		if (nextUpdate)
+			{
+			// 4.2.2.1 "Responses whose nextUpdate value is earlier than the
+			// local system time value SHOULD be considered unreliable"
+			// 3.2.6 "OCSP clients shall confirm that ... nextUpdate is greater
+			// than the current time."
+			if (TimeIsBeforeL(*nextUpdate, validationTime))
+				{
+				iOutcome->iStatus = OCSP::ENextUpdateTooEarly;
+				return EFalse;
+				}
+
+			// Check nextUpdate later than producedAt.  This is not mandated by the spec.
+			if (TimeIsBeforeL(*nextUpdate, producedAt))
+				{
+				iOutcome->iStatus = OCSP::ENextUpdateTooEarly;
+				return EFalse;
+				}
+			}
+		
+		// 3.2.5 "OCSP clients SHALL confirm that ... thisUpdate is sufficiently
+		// recent"
+		if (iMaxStatusAge)
+			{
+			TTimeIntervalSeconds difference;
+
+			User::LeaveIfError(validationTime.SecondsFrom(thisUpdate, difference));
+			const TTimeIntervalSeconds maxUpdateAge(iMaxStatusAge + iLeewaySeconds);	
+			if (difference > maxUpdateAge)
+				{
+				iOutcome->iStatus = OCSP::EThisUpdateTooEarly;
+				return EFalse;
+				}	
+			}
+
+		// Check certificate validity period against validation time.
+		// 
+		// Strictly speaking, the OCSP protcol is about checking revocation
+		// rather then checking whether a certificate has just expired.
+		// However, it's difficult to check this on a device when you don't have
+		// an accurate value for the current time.  We do the check here for
+		// completeness, and trust the time given to us by the ocsp server.  If
+		// we are using a nonce, as we will be most of the time, we can
+		// guarantee that the producedAt time is current.
+
+		const CX509Certificate& cert = iRequest->CertInfo(requestIndex).Subject();
+		const CValidityPeriod& validityPeriod = cert.ValidityPeriod();
+
+		if (!validityPeriod.Valid(validationTime))
+			{
+			iOutcome->iStatus = OCSP::ECertificateNotValidAtValidationTime;
+			return EFalse;
+			}
+
+		} // Continue with next cert
+
+	// If we've got this far, we're fine
+	return ETrue;
+	}
+
+TBool COCSPValidator::ValidateNonce()
+	{
+	const TDesC8* requestNonce = iRequest->Nonce();
+	const TPtrC8* responseNonce = iResponse->DataElementEncoding(COCSPResponse::ENonce);
+	
+	if (requestNonce)
+		{
+		if (responseNonce)
+			{
+			if (*requestNonce == *responseNonce)
+				{
+				return ETrue;
+				}
+			else
+				{
+				iOutcome->iStatus = OCSP::ENonceMismatch;
+				return EFalse;
+				}
+			}
+		else
+			{
+			iOutcome->iStatus = OCSP::EMissingNonce;
+			return EFalse;
+			}
+		}
+	else
+		{
+		if (responseNonce)
+			{
+			// Shouldn't have a nonce!
+			iOutcome->iStatus = OCSP::EMalformedResponse;
+			return EFalse;
+			}
+		else
+			{
+			// No nonces - fine
+			return ETrue;
+			}
+		}
+	}
+
+// Return true if first argument is iLeewaySeconds or more before the second
+// argument.  Hence it is conservative, and should be always used "positively"
+// to check for error conditions.
+TBool COCSPValidator::TimeIsBeforeL(const TTime& aBefore, const TTime& aAfter)
+	{
+	TTimeIntervalSeconds difference;
+	const TTimeIntervalSeconds leeway(iLeewaySeconds);	
+
+	User::LeaveIfError(aAfter.SecondsFrom(aBefore, difference));
+	return (difference > leeway);
+	}
+
+void COCSPValidator::RunL()
+	{
+	User::LeaveIfError(iStatus.Int());
+
+	switch (iState)
+		{
+	case EWaitingResponse:
+		CheckSchemeValidationL();
+		break;
+	case EValidating:
+		ProcessSchemeValidationL();
+		break;
+	case EValidateResponderCert:
+		ValidateResponderCertL();
+		break;
+	default:
+		ASSERT(FALSE);
+		}
+	}
+
+void COCSPValidator::DoCancel()
+	{
+	TInt count = iAuthorisationScheme.Count();
+	__ASSERT_ALWAYS(count, Panic(KErrNoAuthorisationSchemes));
+	if (iState == EWaitingResponse)
+		{
+		ASSERT(iSchemeInUse != NULL);
+		iSchemeInUse->CancelValidate();
+		}
+	User::RequestComplete(iValidationStatus, KErrCancel);
+	}
+
+TInt COCSPValidator::RunError(TInt aError)
+	{
+	User::RequestComplete(iValidationStatus, aError);
+	return KErrNone;
+	}
+
+void COCSPValidator::CheckSchemeValidationL()
+	{
+	// If any scheme says it's OK, we're happy, otherwise we'll return
+	// with whatever the last scheme said.
+	if (iOutcome->iStatus == OCSP::EValid)
+		{
+		FinalResponseValidationL();
+		}
+	else
+		{
+		iState = EValidating;
+		// Fire off AO
+		TRequestStatus* status = &iStatus;
+		User::RequestComplete(status, KErrNone);
+		SetActive();
+		}
+	}
+
+TTime COCSPValidator::ValidationTime() const
+	{
+	__ASSERT_ALWAYS(iResponse, Panic(KErrNotReady));
+	if (iValidationTime)
+		{
+		return *iValidationTime;
+		}
+	else
+		{
+		TTime gmt;
+		
+		// if secure time is not available then fall back to the insecure version.
+		if(gmt.UniversalTimeSecure() == KErrNoSecureTime)
+			{
+			gmt.UniversalTime();
+			}
+		return gmt;
+		}
+	}
+
+/**
+ * For the response in question there can be more than one authentication scheme initialized.
+ * We need to find out whether the schemes initialized contain at least delegate or direct auth scheme,
+ * if any of them is present we can send the request for validation for responder certificate, as 
+ * validation of responder certificate should only work for these 2 schemes.
+ * 
+ * If we get a valid scheme, following would be the sequence of operation:
+ * 1. Retrieve the responder certificate and the issuer(should be the CA who issued the certificate
+ * in question) who has issued the responder certificate.
+ * 2. Check whether the responder certificate contains the id-pkix-ocsp-nocheck, if present there is no need for 
+ * 	 sending it for OCSP check, if not present send it for OCSP check.
+ 
+ * Send the responder certificate for OCSP checking. Here we would use the existing parameters
+ * for creating the responder certificate request, as this check is an extension of the original 
+ * certificate OCSP check.
+ */
+ void COCSPValidator::SendResponderCertL()
+ 	{
+ 	if( OCSPUtils::DoesCertHaveOCSPNoCheckExt(*iResponderCert))
+ 		{
+ 		User::RequestComplete(iValidationStatus, KErrNone);
+ 		return;
+ 		}
+ 	
+ 	iResponderCertRequest = COCSPRequest::NewL(iUseNonce);
+ 	iResponderCertRequest->AddCertificateL(*iResponderCert, *iIssuerCert);
+ 	 	
+ 	// Only add further requests if there is:
+ 	// a URI (either AIA URI or default URI) 
+ 	TDesC8* uri = NULL;
+ 	TRAPD(error, uri = OCSPUtils::ServerUriL(iResponderCertRequest->CertInfo(0).Subject(),iParameters));
+ 
+	if(error == KErrArgument)
+ 		{
+		iOutcome->iStatus = OCSP::ENoServerSpecified;
+		TRequestStatus* status = &iStatus;
+		User::RequestComplete(status, OCSP::ENoServerSpecified);
+		iState = EValidateResponderCert;
+	 	SetActive();
+	 	return;
+ 		}
+	
+	User::LeaveIfError(error);
+	CleanupStack::PushL(uri);
+ 	
+ 	// if state is valid it means that uri has been retrieved.
+	__ASSERT_ALWAYS(uri != NULL, Panic(OCSP::EInvalidURI));
+	MOCSPTransport& transport = *iParameters->Transport();
+	delete iTransaction;
+	iTransaction = NULL;
+	iTransaction = COCSPTransaction::NewL(*uri, transport, iParameters->RetryCount(), iParameters->Timeout());
+	iTransaction->SendRequest(*iResponderCertRequest, iStatus);
+	CleanupStack::PopAndDestroy(uri);
+	iState = EValidateResponderCert;
+ 	SetActive();
+ 	}
+
+ /**
+  * Receive the response for responder certificate OCSP check.
+  * Leave if there is any problem with the received response.
+  * If the response is well formed then send it for further validation.
+  */
+ void COCSPValidator::ValidateResponderCertL()
+ 	{
+ 	TInt status = iStatus.Int();
+ 	
+ 	if (status == KErrNone)
+ 		{
+ 		iResponderCertResponse = iTransaction->TakeResponse();
+ 		}
+ 	else if (status == OCSP::KErrTransportFailure)
+ 		{
+ 		User::Leave(OCSP::ETransportError);
+ 		}
+ 	else if (status == OCSP::KErrInvalidURI)
+ 		{
+ 		User::Leave(OCSP::EInvalidURI);
+ 		}
+ 	else
+ 		{
+ 		User::Leave(status);		   
+ 		}
+	
+ 	iOutcome->iResult = CheckOCSPStatus(iResponderCertResponse);
+ 	if(iOutcome->iResult != OCSP::EGood )
+ 		{
+ 		// as the responder certificate is either revoked or unknown the final status returned
+ 		// should be unknown.
+ 		iOutcome->iResult = OCSP::EUnknown;
+ 		}
+ 	User::RequestComplete(iValidationStatus, KErrNone);
+ 	
+ 	}
+
+ OCSP::TResult COCSPValidator::CheckOCSPStatus(const COCSPResponse* aResponse) const
+	{
+	OCSP::TResult result = OCSP::EGood;
+	TInt numCerts = aResponse->CertCount();
+	for (TInt index = 0; index < numCerts; ++index)
+		{
+		const COCSPResponseCertInfo& info = aResponse->CertInfo(index);
+	
+		OCSP::TResult certStatus = info.Status();
+		result = certStatus > result? certStatus : result;
+		}
+	return result;
+	}