/*
* 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);
}
}
}