/*
* Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "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: 
*
*/


#include "pkixcertchainao.h"
#include "pkixCons.h"
#include <x509keys.h>
#include <pkixcertchain.h>
#include <x509constraintext.h>

CPKIXCertChainAO* CPKIXCertChainAO::NewL(MCertStore& aCertStore,
										 CPKIXCertChainBase &aPKIXCertChain,
										 const RPointerArray<CX509Certificate>& aRootCerts)
	{
	CPKIXCertChainAO* self = new(ELeave) CPKIXCertChainAO(aCertStore, aPKIXCertChain);
	CleanupStack::PushL(self);
	self->ConstructL(aRootCerts);
	CleanupStack::Pop(self);
	return self;
	}

CPKIXCertChainAO* CPKIXCertChainAO::NewL(MCertStore& aCertStore, 
										 CPKIXCertChainBase &aPKIXCertChain,
										 const TUid aClient)
	{
	return new(ELeave) CPKIXCertChainAO(aCertStore, aPKIXCertChain, aClient);
	}

CPKIXCertChainAO::~CPKIXCertChainAO()
	{
	Cancel();

	delete iRoots;
	delete iBuilder;
	delete iCertsFromStoreRoots;
	}

CPKIXCertChainAO::CPKIXCertChainAO(MCertStore& aCertStore,
								   CPKIXCertChainBase &aPKIXCertChain)
	: CActive(EPriorityNormal), iCertStore(&aCertStore), iPKIXCertChain(aPKIXCertChain)
	{
	CActiveScheduler::Add(this);
	}

CPKIXCertChainAO::CPKIXCertChainAO(MCertStore& aCertStore, 
								   CPKIXCertChainBase &aPKIXCertChain,
								   const TUid aClient)
	: CActive(EPriorityNormal), iCertStore(&aCertStore),
	  iPKIXCertChain(aPKIXCertChain), iClient(aClient)
	{
	CActiveScheduler::Add(this);
	}

void CPKIXCertChainAO::ConstructL(const RPointerArray<CX509Certificate>& aRootCerts)
	{
	CPKIXCertsFromClient* roots = CPKIXCertsFromClient::NewLC(aRootCerts);
	iRoots = CPKIXChainBuilder::NewL();
	iRoots->AddSourceL(roots);
	CleanupStack::Pop(roots);
	}

void CPKIXCertChainAO::RunL()
	{
	User::LeaveIfError(iStatus.Int());

	switch (iState)
		{
		case EAddRoots:
			HandleEAddRootsL();
			break;

		case ERootsInitialized:
			HandleERootsInitializedL();
			break;

		case EBuildChainStart:
			HandleEBuildChainStartL();
			break;

		case EBuildChainAddCandidateEnd:
			HandleEBuildChainAddCandidateEndL();
			break;

		case EBuildChainCertsFromStoreBegin:
			HandleEBuildChainCertsFromStoreBeginL();
			break;

		case EBuildChainCertsFromStoreEnd:
			HandleEBuildChainCertsFromStoreEndL();
			break;

		case EAddCandidateIntermediateCertsEnd:
			HandleEAddCandidateIntermediateCertsEndL();
			break;

		case EValidateEnd:
			HandleEValidateEndL();
			break;

		default:
			User::Panic(_L("CPKIXCertChainAO"), 1);
			break;
		}
	}

TInt CPKIXCertChainAO::RunError(TInt aError)
{
	iPKIXCertChain.RemoveLastCerts(iNumberOfAddedCertificates);
	iNumberOfAddedCertificates = 0;

	delete iRoots;
	iRoots = 0;
		
	delete iBuilder;
	iBuilder = 0;
	delete iCertsFromStoreRoots;
	iCertsFromStoreRoots = 0;

	iValidationResult->RemovePolicies();

	User::RequestComplete(iOriginalRequestStatus, aError);
	return KErrNone;
}

/**
 * Creates a list of all the certificates retrieved from the store based on the filter passed.  
 */

void CPKIXCertChainAO::HandleEAddRootsL()
	{
	__ASSERT_DEBUG(!iCertsFromStoreRoots, User::Panic(_L("CPKICCertChainAO"), 1));
	iCertsFromStoreRoots = CPKIXCertsFromStore::NewL(*iCertStore, iClient);
	iCertsFromStoreRoots->Initialize(iStatus);
	iState = ERootsInitialized;	
	SetActive();
	}

/**
 * Adds the list of certificates retrieved from the store, iRoots (CPKIXChainBuilder)
 * maintains a templatized list of all the certificates in MPKIXCertSource format.
 */

void CPKIXCertChainAO::HandleERootsInitializedL()
	{
	iRoots->AddSourceL(iCertsFromStoreRoots);
	// Ownership has been passed to iRoots
	iCertsFromStoreRoots = 0;
	iState = EBuildChainStart;
	TRequestStatus* status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();
	}

void CPKIXCertChainAO::HandleEBuildChainStartL()
	{
	if ( false == iPKIXCertChain.ChainHasRoot())
		{
		if (iPKIXCertChain.Chain().Count() == 0)
			{
			iState = EValidateEnd;
			TRequestStatus* status = &iStatus;
			User::RequestComplete(status, KErrNone);
			}
		else
			{
			//1) look for an issuer that's a root
			iRoots->AddIssuer(iNumberOfAddedCertificates, iAddIssuerResult, iPKIXCertChain.Chain(), iStatus);
			iState = EBuildChainAddCandidateEnd;
			}
		}
	else
		{
		// This is the correct state as at this point the chain of certificate has been build upto a
		// root certificate.
		iState = EValidateEnd;
		TRequestStatus* status = &iStatus;
		User::RequestComplete(status, KErrNone);
		}
	SetActive();
	}

void CPKIXCertChainAO::HandleEBuildChainAddCandidateEndL()
	{
	if (iAddIssuerResult)
		{
		iPKIXCertChain.SetChainHasRoot(ETrue);
		iState = EValidateEnd;
		}
	else
		{
		//2) look for a non-root issuer in intermediate certs
		iBuilder = CPKIXChainBuilder::NewL();
		
		CPKIXCertsFromClient* serverCerts = CPKIXCertsFromClient::NewLC(iPKIXCertChain.IntermediateCerts());
		iBuilder->AddSourceL(serverCerts);
		CleanupStack::Pop(serverCerts);

		iState = EBuildChainCertsFromStoreBegin;
		}

	TRequestStatus* status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();
	}

void CPKIXCertChainAO::HandleEBuildChainCertsFromStoreBeginL()
	{
	//3) look for a non-root issuer in the store
	iCertsFromStoreRoots = CPKIXCertsFromStore::NewL(*iCertStore);
	iCertsFromStoreRoots->Initialize(iStatus);
	iState = EBuildChainCertsFromStoreEnd;
	SetActive();
	}

void CPKIXCertChainAO::HandleEBuildChainCertsFromStoreEndL()
	{
	iBuilder->AddSourceL(iCertsFromStoreRoots);
	iCertsFromStoreRoots = 0;

	iBuilder->AddIssuer(iNumberOfAddedCertificates, iAddIssuerResult, iPKIXCertChain.Chain(), iStatus);
	iState = EAddCandidateIntermediateCertsEnd;
	SetActive();
	}

void CPKIXCertChainAO::HandleEAddCandidateIntermediateCertsEndL()
	{
	if (iAddIssuerResult)
		{
		// cert is a pointer to something we don't own
		CX509Certificate* cert = iPKIXCertChain.Chain().At(iPKIXCertChain.Chain().Count() - 1);
		
		/* If the issuer is not a self signed certificate then it cannot be trusted anchor for the chain 
		 * validation process, this means that we restart the certification validation process.
		 */ 
		
		if (!(cert->IsSelfSignedL()))
			{
			iState = EBuildChainStart;	
			}
		else
			{
			iState = EValidateEnd;			
			}
		}
	else
		{
		iState = EValidateEnd;	
		}

	delete iBuilder;
	iBuilder = 0;

	TRequestStatus* status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();
	}

void CPKIXCertChainAO::HandleEValidateEndL()
	{
	InitParamsL();
	
	__ASSERT_DEBUG(iValidationResult, User::Panic(_L("CPKICCertChainAO"), 1));
	DoValidateL(*iValidationResult, iValidationTime, iInitialPolicies);

	User::RequestComplete(iOriginalRequestStatus, KErrNone);
	}

void CPKIXCertChainAO::DoCancel()
	{
	delete iRoots;
	iRoots = 0;

	delete iBuilder;
	iBuilder = 0;

	delete iCertsFromStoreRoots;
	iCertsFromStoreRoots = 0;

	User::RequestComplete(iOriginalRequestStatus, KErrCancel);
	}

void CPKIXCertChainAO::ValidateL(CPKIXValidationResultBase& aValidationResult,
								 const TTime& aValidationTime,								 
								 const CArrayPtr<HBufC>* aInitialPolicies,
								 TRequestStatus& aStatus)
	{
	aValidationResult.Reset();
	iValidationResult = &aValidationResult;
	iValidationTime = aValidationTime;
	iInitialPolicies = aInitialPolicies;
	iOriginalRequestStatus = &aStatus;
	iNumberOfAddedCertificates = 0;

	__ASSERT_ALWAYS(!IsActive(), User::Panic(_L("CPKICCertChainAO"), 1));

	if (!iRoots)
		{
		// If iRoots is 0, it means that the caller gave a uid and that
		// we must retrieve the trusted certificates from the different
		// stores
		iRoots = CPKIXChainBuilder::NewL();
		iState = EAddRoots;
		}
	else
		{
		// The caller gave a set of certificates it trusts,
		// so we don't have to retrieve anything from the stores
		iState = EBuildChainStart;
		}

	aStatus = KRequestPending;
	TRequestStatus *status = &iStatus;
	User::RequestComplete(status, KErrNone);
	SetActive();
	}

void CPKIXCertChainAO::CancelValidate()
	{
	Cancel();
	}

void CPKIXCertChainAO::InitParamsL()
/*
this function initialises signing key parameters for the certificates
-only DSA needs these at present
-we get the signing key, from the spki of the issuer
-if it's dsa, we look for params here
	-if we find them we initialise the cert with them
	-otherwise, we look in the issuer's issuer
		-if we don't find them there we give up.
*/
	{
	
	// If the root is DSA signed, set its parameters

	TInt count = iPKIXCertChain.Chain().Count();
	
	CX509Certificate* current = iPKIXCertChain.Chain().At(count-1);
	TAlgorithmId signingAlgorithm = current->SigningAlgorithm().AsymmetricAlgorithm().Algorithm();
	
	if (signingAlgorithm == EDSA)
		{
		
		const CSubjectPublicKeyInfo& key = current->PublicKey();
		SetParamsL(*current, key.EncodedParams());
		
		}
	
	// Also the rest of the chain
	
	for (TInt i = count - 2; i >= 0; i--)
		{
		
		current = iPKIXCertChain.Chain().At(i);
		TAlgorithmId signingAlgorithm = current->SigningAlgorithm().AsymmetricAlgorithm().Algorithm();
		
		if (signingAlgorithm == EDSA)
			{
			
			// Look down the chain for parameters
			
			for (TInt j = i+1; j < count; j++)
				{
				
				CX509Certificate* issuer = iPKIXCertChain.Chain().At(j);
				const CSubjectPublicKeyInfo& key = issuer->PublicKey();
				if (key.EncodedParams() != KNullDesC8 && key.AlgorithmId() == EDSA)
					{
					SetParamsL(*current, key.EncodedParams());
					break;
					}
				
				}
			
			}
		
		}
	}

void CPKIXCertChainAO::SetParamsL(CX509Certificate& aCert, const TPtrC8& aEncodedParams)
	{
	TX509KeyFactory factory;
	CDSAParameters* theDSAParams = factory.DSAParametersL(aEncodedParams);
	CleanupStack::PushL(theDSAParams);
	
	CSigningKeyParameters* params = CSigningKeyParameters::NewLC();
	params->SetDSAParamsL(*theDSAParams);
	
	aCert.SetParametersL(*params);

	CleanupStack::PopAndDestroy(2, theDSAParams);
	}

void CPKIXCertChainAO::DoValidateL(CPKIXValidationResultBase& aValidationResult,
								 const TTime& aValidationTime, 
								 const CArrayPtr<HBufC>* aInitialPolicies)
	{
	if (!iPKIXCertChain.ChainHasRoot())
		{
		aValidationResult.SetError(EChainHasNoRoot, 0);
		}
	else
		{
		CPKIXValidationState* state = CPKIXValidationState::NewLC(aValidationTime, iPKIXCertChain.Chain().Count(), aInitialPolicies);
		TRAPD(err, ProcessCertsL(*state, aValidationResult));
		//a leave here means either:
		//	-a validation error, in which case we've set the error field in result, or
		//	-some other error (e.g. OOM) in which case error is still EChainHasNoRoot
		if ((err != KErrNone) && ((aValidationResult.Error().iReason) == EChainHasNoRoot))
			//then we left with a non-validation-related error, so leave again...
			{
			User::Leave(err);
			}
		CleanupStack::PopAndDestroy(state);
		}
	}

// ProcessCertsL: This function validates a complete certificate 
//                chain. If an error occurs in this function the function
//                SetErrorAndLeaveL must be called. 
// 
// Note Do not use SetErrorAndLeaveL with EChainHasNoRoot (see TRAP code in 
// CPKIXCertChainAO::DoValidateL )             
void CPKIXCertChainAO::ProcessCertsL(CPKIXValidationState& aState,
									 CPKIXValidationResultBase& aResult) const
	{
	TPKIXPolicyConstraint policy(aState, aResult);
	TPKIXNameConstraint name(aState, aResult);
	TPKIXBasicConstraint basic(aState, aResult);
	TPKIXKeyUsageConstraint keyUsage(aState, aResult);
	for (; aState.iPos >= 0; aState.iPos--)
		{
		aState.iMaxPathLength--;
		if (aState.iMaxPathLength < aState.iPos)
			{
			aResult.SetErrorAndLeaveL(EPathTooLong, aState.iPos);
			}
		const CX509Certificate* current = iPKIXCertChain.Chain().At(aState.iPos);
		CCertificateValidationWarnings* certWarnings = CCertificateValidationWarnings::NewLC(aState.iPos);
		aResult.AppendCertificateValidationObjectL(*certWarnings);
		CleanupStack::Pop(certWarnings);
		CriticalExtsL(aState, *current);
		CheckCriticalExtsL(aState, aResult);
		CheckSignatureAndNameL(*current, aState, aResult);
		//!!!!NO!!checks for revocation at this time!!
		
		if (!(current->ValidityPeriod().Valid(aState.iValidationTime)))
			{
			//validity period invalid, now check how to report this
			if (iPKIXCertChain.ValidityPeriodCheckFatal())
				{
				aResult.SetErrorAndLeaveL(EDateOutOfRange, aState.iPos);
				}
			else
				{
				aResult.AppendWarningL(TValidationStatus(EDateOutOfRange, aState.iPos));
				}
			}
		
		policy.CheckCertPoliciesL(*current);
		name.CheckNameConstraintsL(*current);
		keyUsage.CheckKeyUsageL(*current);
		if (aState.iPos < (iPKIXCertChain.Chain().Count() - 1))
			{
			basic.CheckCertSubjectTypeL(*current);
			}
		basic.UpdatePathLengthConstraintsL(*current);
		name.UpdateNameConstraintsL(*current);
		policy.UpdatePolicyConstraintsL(*current);
		aState.iCriticalExts->Reset();
		}
	policy.FinishPolicyCheckL();
	//*copy* all policies from aState.iAuthorityConstrainedPolicies into aResult.iPolicies
	TInt policyCount = aState.iAuthorityConstrainedPolicies->Count();
	for (TInt i = 0; i < policyCount; i ++)
		{
		CX509CertPolicyInfo* policyInfo = CX509CertPolicyInfo::NewLC(*(aState.iAuthorityConstrainedPolicies->At(i)));
		aResult.AppendPolicyL(*policyInfo);
		CleanupStack::Pop(policyInfo);
		}

	aResult.SetError(EValidatedOK, 0);
	}

void CPKIXCertChainAO::CriticalExtsL(CPKIXValidationState& aState, 
									 const CX509Certificate& aCert) const
	{
	const CArrayPtrFlat<CX509CertExtension>& exts = aCert.Extensions();
	TInt count = exts.Count();
	for (TInt i = 0; i < count; i++)
		{
		CX509CertExtension* ext = exts.At(i);
		if (ext->Critical())
			{
			aState.iCriticalExts->AppendL(ext);
			}
		}	 
	}

void CPKIXCertChainAO::CheckSignatureAndNameL(const CX509Certificate& aCert, CPKIXValidationState& aState, 
											CPKIXValidationResultBase& aResult) const
	{
	TInt issuerPos = aState.iPos + 1;
	if (issuerPos == iPKIXCertChain.Chain().Count())
		//then it's the root
		{
		if (aCert.IssuerName().ExactMatchL(aCert.SubjectName()))
			//then it claims to be self signed, sig must verify
			{
			if (!(aCert.VerifySignatureL(aCert.PublicKey().KeyData())))
				{
				aResult.SetErrorAndLeaveL(ESignatureInvalid, aState.iPos);
				}
			}
		else
			//we generate a warning
			{
			aResult.AppendWarningL(TValidationStatus(ERootCertNotSelfSigned, aState.iPos));
			}
		}
	else
		//then it isn't the root: so names must chain & sigs must verify
		{
		const CX509Certificate* issuer = iPKIXCertChain.Chain().At(issuerPos);
		if (!(aCert.IssuerName().ExactMatchL(issuer->SubjectName())))
			{
			aResult.SetErrorAndLeaveL(ENamesDontChain, aState.iPos);
			}
		if (!(aCert.VerifySignatureL(issuer->PublicKey().KeyData())))
			{
			aResult.SetErrorAndLeaveL(ESignatureInvalid, aState.iPos);
			}
		}
	}

void CPKIXCertChainAO::CheckCriticalExtsL(CPKIXValidationState& aState, CPKIXValidationResultBase& aResult) const
	{
	TBool foundUnrecognisedCritExt;
	
	// retrieve the supported list of critical extensions. If a critical extension is found whose OID matches an 
	// element in this set then certificate validation shall treat this as a warning instead of an error.
	const RPointerArray<TDesC>& supportedCritExt = iPKIXCertChain.SupportedCriticalExtensions();
	
	TInt count = aState.iCriticalExts->Count();
	TInt supportedCount = supportedCritExt.Count();
	for (TInt i = 0; i < count; i++)
		{
		foundUnrecognisedCritExt = ETrue;
		const CX509CertExtension* ext = aState.iCriticalExts->At(i);
		const TPtrC& extName = ext->Id();
		
		for (TInt j = 0; j < supportedCount; ++j)
			{
			if (extName == *supportedCritExt[j])
				{
				foundUnrecognisedCritExt = EFalse;
				HBufC* oid = extName.AllocLC();
				aResult.AppendCriticalExtensionWarningL(*oid);
				CleanupStack::Pop(oid);
				break;
				}
			}			
		
		if (extName == KExtendedKeyUsage)
			{
			aResult.AppendWarningL(TValidationStatus(ECriticalExtendedKeyUsage, aState.iPos));
			}
		else if (extName == KPolicyMapping)
			{
			aResult.AppendWarningL(TValidationStatus(ECriticalPolicyMapping, aState.iPos));
			}
		else if (extName == KInhibitAnyPolicy)
			{
			//ignore this in the same way
			}
		else if (extName == KDeviceIdListConstraint)
			{
			aResult.AppendWarningL(TValidationStatus(ECriticalDeviceId, aState.iPos));
			}
		else if(extName == KSidListConstraint)
			{
			aResult.AppendWarningL(TValidationStatus(ECriticalSid, aState.iPos));
			}
		else if(extName == KVidListConstraint)
			{
			aResult.AppendWarningL(TValidationStatus(ECriticalVid, aState.iPos));
			}
		else if(extName == KCapabilitiesConstraint)
			{
			aResult.AppendWarningL(TValidationStatus(ECriticalCapabilities, aState.iPos));
			}
		
		if (foundUnrecognisedCritExt)
			{
			aResult.SetErrorAndLeaveL(EUnrecognizedCriticalExtension, aState.iPos);
			}			
		
		}
	}
