cryptoservices/filebasedcertificateandkeystores/source/certstore/server/filecertstore.cpp
/*
* 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 "filecertstore.h"
#include "CCertStoreConduit.h"
#include "CCertStoreSession.h"
#include "CCertStoreEntry.h"
#include "CCertStoreEntryList.h"
#include "fstokencliserv.h"
#include "fstokenutil.h"
#include "fsdatatypes.h"
#include <certstorepatchdata.h>
#include <ccertattributefilter.h>
#include <signed.h>
#include <x509cert.h>
#include <x509certext.h>
#include <x509keys.h>
#include <wtlscert.h>
#include <u32hal.h>
_LIT(KCertStoreFilename,"CACerts.dat");
_LIT(KCertStoreFilenamePattern,"cacerts*.dat");
/////////////////////////////////////////////////////////////////////////////////////////
//CFSCertStoreServer
/////////////////////////////////////////////////////////////////////////////////////////
CFSCertStoreServer* CFSCertStoreServer::NewL()
{
CFSCertStoreServer* self = new (ELeave) CFSCertStoreServer();
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}
CFSCertStoreServer::CFSCertStoreServer()
{
}
void CFSCertStoreServer::ConstructL()
{
iConduit = CCertStoreConduit::NewL(*this);
User::LeaveIfError(iFs.Connect());
iPatchableConst = KAggregateCertStore;
#ifdef __WINS__
// For the emulator allow the constant to be patched via epoc.ini
UserSvr::HalFunction(EHalGroupEmulator, EEmulatorHalIntProperty,
(TAny*)"KAggregateCertStore", &iPatchableConst); // read emulator property (if present)
#endif
OpenStoreL();
}
CFSCertStoreServer::~CFSCertStoreServer()
{
delete iStore;
delete iEntryList;
delete iConduit;
iFs.Close();
}
CCertStoreSession* CFSCertStoreServer::CreateSessionL()
{
return CCertStoreSession::NewL(*iConduit);
}
// API policing ////////////////////////////////////////////////////////////////
_LIT_SECURITY_POLICY_PASS(KPolicyAlwaysPass);
_LIT_SECURITY_POLICY_FAIL(KPolicyAlwaysFail);
_LIT_SECURITY_POLICY_C1(KPolicyRequireReadUserData, ECapabilityReadUserData);
_LIT_SECURITY_POLICY_C1(KPolicyRequireWriteUserData, ECapabilityWriteUserData);
_LIT_SECURITY_POLICY_C1(KPolicyRequireWriteDeviceData, ECapabilityWriteDeviceData);
const TSecurityPolicy& CFSCertStoreServer::AddRemovePolicy(TCertificateOwnerType aOwnerType) const
{
switch (aOwnerType)
{
case ECACertificate:
return KPolicyRequireWriteDeviceData;
case EUserCertificate:
case EPeerCertificate:
return KPolicyRequireWriteUserData;
default:
return KPolicyAlwaysFail;
}
}
const TSecurityPolicy& CFSCertStoreServer::RetrievePolicy(TCertificateOwnerType aOwnerType) const
{
switch (aOwnerType)
{
case ECACertificate:
return KPolicyAlwaysPass;
case EUserCertificate:
case EPeerCertificate:
return KPolicyRequireReadUserData;
default:
return KPolicyAlwaysFail;
}
}
const TSecurityPolicy& CFSCertStoreServer::WriteTrustSettingsPolicy() const
{
return KPolicyRequireWriteDeviceData;
}
// Read-only interface /////////////////////////////////////////////////////////
void CFSCertStoreServer::ListL(const CCertAttributeFilter& aFilter,
RPointerArray<CCertInfo>& aCertsOut) const
{
// check that if KeyUsage is set, only User certificates are requested
if (aFilter.iKeyUsage != EX509UsageAll &&
(!aFilter.iOwnerTypeIsSet || aFilter.iOwnerType != EUserCertificate))
{
User::Leave(KErrArgument);
}
TInt count = iEntryList->Count();
for (TInt index = 0; index < count; index++)
{
const CCertStoreEntry& entry = iEntryList->GetByIndex(index);
if (CertEntryMatchesFilter(aFilter, entry))
{
User::LeaveIfError(aCertsOut.Append(&entry.CertInfo()));
}
}
}
TBool CFSCertStoreServer::CertEntryMatchesFilter(const CCertAttributeFilter& aFilter,
const CCertStoreEntry& aEntry) const
{
if (aFilter.iUidIsSet && !aEntry.IsApplicable(aFilter.iUid))
{
return EFalse;
}
const CCertInfo& certInfo = aEntry.CertInfo();
if (aFilter.iFormatIsSet && aFilter.iFormat != certInfo.CertificateFormat())
{
return EFalse;
}
if (aFilter.iOwnerTypeIsSet && aFilter.iOwnerType != certInfo.CertificateOwnerType())
{
return EFalse;
}
if (aFilter.iSubjectKeyIdIsSet && aFilter.iSubjectKeyId != certInfo.SubjectKeyId())
{
return EFalse;
}
if (aFilter.iLabelIsSet && aFilter.iLabel != certInfo.Label())
{
return EFalse;
}
if (aFilter.iIssuerKeyIdIsSet && aFilter.iIssuerKeyId != certInfo.IssuerKeyId())
{
return EFalse;
}
return ETrue;
}
const CCertInfo& CFSCertStoreServer::GetCertL(TInt aHandle) const
{
// Leaves if not found
return iEntryList->GetByHandleL(aHandle).CertInfo();
}
const RArray<TUid>& CFSCertStoreServer::ApplicationsL(TInt aHandle) const
{
return iEntryList->GetByHandleL(aHandle).CertificateApps();
}
TBool CFSCertStoreServer::IsApplicableL(TInt aHandle, TUid aApplication) const
{
return iEntryList->GetByHandleL(aHandle).IsApplicable(aApplication);
}
TBool CFSCertStoreServer::TrustedL(TInt aHandle) const
{
return iEntryList->GetByHandleL(aHandle).Trusted();
}
HBufC8* CFSCertStoreServer::RetrieveLC(TInt aHandle, const RMessage2& aMessage) const
{
const CCertStoreEntry& entry = iEntryList->GetByHandleL(aHandle);
// API policing
if (!RetrievePolicy(entry.CertInfo().CertificateOwnerType()).CheckPolicy(aMessage))
{
User::Leave(KErrPermissionDenied);
}
TInt size = entry.CertInfo().Size();
HBufC8* buf = HBufC8::NewMaxLC(size);
TPtr8 ptr = buf->Des();
ptr.FillZ();
RStoreReadStream stream;
stream.OpenLC(*iStore, entry.DataStreamId());
stream.ReadL(ptr, size);
CleanupStack::PopAndDestroy(&stream);
return buf;
}
// Writable interface //////////////////////////////////////////////////////////
void CFSCertStoreServer::AddL(const TAddCertDataStruct& aInfo,
const TDesC8& aCert,
const RMessage2& aMessage)
{
// Check if a certificate with this name already exists
if (iEntryList->LabelExists(aInfo.iLabel))
{
User::Leave(KErrBadName);
}
// Check subject key id and cert data are supplied, issuer key id is optional
if (aInfo.iSubjectKeyId == KNullDesC8 || aCert == KNullDesC8)
{
User::Leave(KErrArgument);
}
// Create cert entry (this sanity checks the rest of the arguments)
CCertInfo* certInfo = CCertInfo::NewLC(aInfo.iLabel,
aInfo.iFormat,
aInfo.iCertificateOwnerType,
aCert.Length(),
&aInfo.iSubjectKeyId,
&aInfo.iIssuerKeyId,
iEntryList->NextFreeHandle(),
aInfo.iDeletable);
// API policing
if (!AddRemovePolicy(aInfo.iCertificateOwnerType).CheckPolicy(aMessage))
{
User::Leave(KErrPermissionDenied);
}
CompactStoreL();
TRAPD(err, DoAddL(*certInfo, aCert));
CleanupStack::PopAndDestroy(certInfo);
if (err != KErrNone)
{
iStore->Revert();
User::Leave(err);
}
}
void CFSCertStoreServer::DoAddL(const CCertInfo& aCertInfo, const TDesC8& aCertData)
{
TStreamId dataStreamId = WriteCertDataStreamL(aCertData);
RArray<TUid> initialApps;
CleanupClosePushL(initialApps);
CCertStoreEntry* entry = CCertStoreEntry::NewL(aCertInfo,
initialApps,
EFalse,
dataStreamId);
CleanupStack::PopAndDestroy(&initialApps);
CleanupStack::PushL(entry);
TInt index = iEntryList->AppendL(entry);
CleanupStack::Pop(entry); // iEntryList has taken ownership
TRAPD(err, UpdateStoreL());
if (err != KErrNone)
{
iEntryList->Remove(index);
delete entry;
User::Leave(err);
}
}
TStreamId CFSCertStoreServer::WriteCertDataStreamL(const TDesC8& aData)
{
RStoreWriteStream stream;
TStreamId streamId = stream.CreateLC(*iStore);
stream.WriteL(aData);
stream.CommitL();
CleanupStack::PopAndDestroy(&stream);
return streamId;
}
void CFSCertStoreServer::RemoveL(TInt aHandle, const RMessage2& aMessage)
{
TInt index = iEntryList->IndexForHandle(aHandle);
User::LeaveIfError(index);
// API policing
const CCertStoreEntry& entry = iEntryList->GetByIndex(index);
//Check whether the certificate is deleteable and that the necessary capabilities are
//present
if (!AddRemovePolicy(entry.CertInfo().CertificateOwnerType()).CheckPolicy(aMessage) ||
!entry.CertInfo().IsDeletable())
{
User::Leave(KErrPermissionDenied);
}
TCleanupItem cleanupStore(RevertStore, iStore);
CleanupStack::PushL(cleanupStore);
TStreamId datastreamid = entry.DataStreamId();
// Data stream needs to be deleted which was written while adding the certificate.
iStore->DeleteL(datastreamid);
CCertStoreEntry* oldEntry = iEntryList->Remove(index);
TRAPD(err, UpdateStoreL());
if (err == KErrNone)
{
delete oldEntry;
}
else
{
// This will always succeed because we just did a remove
iEntryList->AppendL(oldEntry);
User::Leave(err);
}
CleanupStack::Pop(); // cleanupStore
CompactStoreL();
}
void CFSCertStoreServer::SetApplicabilityL(TInt aHandle,
const RArray<TUid>& aApps,
const RMessage2& aMessage)
{
TInt index = iEntryList->IndexForHandle(aHandle);
User::LeaveIfError(index);
const CCertStoreEntry& entry = iEntryList->GetByIndex(index);
if (entry.CertInfo().CertificateOwnerType() != ECACertificate)
{
User::Leave(KErrArgument);
}
// API policing
if (!WriteTrustSettingsPolicy().CheckPolicy(aMessage))
{
User::Leave(KErrPermissionDenied);
}
CompactStoreL();
CCertStoreEntry* newEntry = CCertStoreEntry::NewL(entry.CertInfo(),
aApps,
entry.Trusted(),
entry.DataStreamId());
ReplaceCertEntryL(index, newEntry);
}
void CFSCertStoreServer::SetTrustL(TInt aHandle,
TBool aTrusted,
const RMessage2& aMessage)
{
TInt index = iEntryList->IndexForHandle(aHandle);
User::LeaveIfError(index);
const CCertStoreEntry& entry = iEntryList->GetByIndex(index);
if (entry.CertInfo().CertificateOwnerType() != ECACertificate)
{
User::Leave(KErrArgument);
}
// API policing
if (!WriteTrustSettingsPolicy().CheckPolicy(aMessage))
{
User::Leave(KErrPermissionDenied);
}
CompactStoreL();
CCertStoreEntry* newEntry = CCertStoreEntry::NewL(entry.CertInfo(),
entry.CertificateApps(),
aTrusted,
entry.DataStreamId());
ReplaceCertEntryL(index, newEntry);
}
void CFSCertStoreServer::ReplaceCertEntryL(TInt aIndex, CCertStoreEntry* aNewEntry)
{
assert(aIndex >= 0 && aNewEntry, EPanicCertStoreReplaceArguments);
CCertStoreEntry* oldEntry = iEntryList->Replace(aIndex, aNewEntry);
TRAPD(err, UpdateStoreL());
if (err == KErrNone)
{
delete oldEntry;
}
else
{
iStore->Revert();
iEntryList->Replace(aIndex, oldEntry);
delete aNewEntry;
User::Leave(err);
}
}
// This is a cleanup item that reverts the store
void CFSCertStoreServer::RevertStore(TAny* aStore)
{
CPermanentFileStore* store = reinterpret_cast<CPermanentFileStore*>(aStore);
// We're ignoring the leave code from this becuase there's no way we can
// handle this sensibly. This shouldn't be a problem in practice - this
// will leave if for example the file store is on removable which is
// unexpectedly remove, and this is never the case for us.
TRAP_IGNORE(store->RevertL());
}
void CFSCertStoreServer::UpdateStoreL()
{
RStoreWriteStream stream;
stream.ReplaceLC(*iStore, iStreamId);
stream << *iEntryList;
stream.CommitL();
CleanupStack::PopAndDestroy(&stream);
iStore->CommitL();
}
void CFSCertStoreServer::CompactStoreL()
{
iStore->ReclaimL();
iStore->CompactL();
iStore->CommitL();
}
void CFSCertStoreServer::RestoreL(const TDesC& aFilename)
{
// Make sure the store is not read-only
User::LeaveIfError(iFs.SetAtt(aFilename, KEntryAttNormal, KEntryAttReadOnly));
// Open the store
RFile file;
User::LeaveIfError(file.Open(iFs, aFilename, EFileRead | EFileWrite));
CleanupClosePushL(file);
CPermanentFileStore* store = CPermanentFileStore::FromL(file);
CleanupStack::Pop(&file); // now owned by store
CleanupStack::PushL(store);
// Read id of cert list stream
TStreamId caCertEntryStreamId;
RStoreReadStream stream;
stream.OpenLC(*store, store->Root());
stream >> iStreamId;
CleanupStack::PopAndDestroy(&stream);
// Read the certificate list
RStoreReadStream caCertEntryStream;
caCertEntryStream.OpenLC(*store, iStreamId);
iEntryList = CCertStoreEntryList::NewL(caCertEntryStream);
CleanupStack::PopAndDestroy(&caCertEntryStream);
assert(!iStore, EPanicCertStoreRestoreState);
iStore = store;
CleanupStack::Pop(store);
}
void CFSCertStoreServer::AggregateStoreFileL(const TDesC& aFile)
{
ASSERT(iPatchableConst);
// if patchable constant is enabled
// 1. open read-only permanent file store on each file.
// 2. open certificate client entry list of each store.
// 3. aggregate the entries.
RFile file;
User::LeaveIfError(file.Open(iFs, aFile, EFileRead));
CleanupClosePushL(file);
CPermanentFileStore* store = CPermanentFileStore::FromL(file);
// now owned by store
CleanupStack::Pop(&file);
CleanupStack::PushL(store);
// Read id of cert list stream
TStreamId streamId;
RStoreReadStream stream;
stream.OpenLC(*store, store->Root());
stream >> streamId;
CleanupStack::PopAndDestroy(&stream);
// Read the certificate entry list
stream.OpenLC(*store, streamId);
CCertStoreEntryList* entryList = CCertStoreEntryList::NewL(stream);
CleanupStack::PushL(entryList);
MergeCertificateEntryListL(*store,*entryList);
// cleanup entrylist, stream and store instances
CleanupStack::PopAndDestroy(3, store);
}
void CFSCertStoreServer::MergeCertificateEntryListL(const CPermanentFileStore& aStore, const CCertStoreEntryList& aSourceList)
{
ASSERT(iPatchableConst);
// if patchable constant is enabled
TInt sourceCount = aSourceList.Count();
for(TInt i = 0; i < sourceCount; i++)
{
if (!iEntryList->LabelExists(aSourceList.GetByIndex(i).CertInfo().Label()))
{
// Aggregation:
// 1. write the certificate data of this new entry to composite store i.e. 'iStore'.
// 2. append this new entry to composite entry list i.e. 'iEntryList'
const CCertStoreEntry& entry = aSourceList.GetByIndex(i);
TInt size = entry.CertInfo().Size();
HBufC8* buf = HBufC8::NewMaxLC(size);
TPtr8 ptr = buf->Des();
RStoreReadStream stream;
stream.OpenLC(aStore, entry.DataStreamId());
stream.ReadL(ptr, size);
CleanupStack::PopAndDestroy(&stream);
TStreamId dataStreamId = WriteCertDataStreamL(*buf);
CleanupStack::PopAndDestroy(buf);
// create a new entry to be appended to the composite list
// the new entry must have unique certificate id in this list.
const CCertInfo& certInfo = entry.CertInfo();
CCertInfo* info = CCertInfo::NewLC(
certInfo.Label(),
certInfo.CertificateFormat(),
certInfo.CertificateOwnerType(),
certInfo.Size(),
&certInfo.SubjectKeyId(),
&certInfo.IssuerKeyId(),
iEntryList->NextFreeHandle(),
certInfo.IsDeletable()
);
CCertStoreEntry* newEntry = CCertStoreEntry::NewL(
*info,
entry.CertificateApps(),
entry.Trusted(),
dataStreamId
);
CleanupStack::PopAndDestroy(info);
CleanupStack::PushL(newEntry);
iEntryList->AppendL(newEntry);
// iEntryList has taken ownership
CleanupStack::Pop(newEntry);
}
// Eclipsing: do not add this certificate in composite filecertstore.
// Higher order store certificates with same labels take precedence over lower order
// store certificates therefore the later are not included in the composite filecertstore.
// Higher order store certificates are ones which are aggregated prior to other certificates.
}
}
void CFSCertStoreServer::OpenCompositeStoreL(const TDesC& aFilename)
{
ASSERT(iPatchableConst);
// 1. create a new empty certstore file under system drive with the name 'CAcerts.dat'.
// 2. this will be the composite store and the instances 'iEntryList' and 'iStore' will be initialized with this.
// 3. make private rom drive path where certstore files are located.
// 4. collect the certstore file names in a list.
// 5. make private rom drive path on each file.
// 6. populate the composite store with certificate client entries present in rom drive certstores.
// create a new empty certstore file 'CAcerts.dat' under system drive.
CreateStoreFileL(aFilename);
// restore permanent store on it
// this will be the composite store after complete aggregation.
RestoreL(aFilename);
RBuf romFilename;
romFilename.CreateL(KMaxFileName);
CleanupClosePushL(romFilename);
FileUtils::MakePrivateROMFilenameL(iFs, KCertStoreFilenamePattern, romFilename);
CDir* filenameList = NULL;
User::LeaveIfError(iFs.GetDir(romFilename, KEntryAttNormal, ESortByName|EDescending, filenameList));
CleanupStack::PopAndDestroy(&romFilename);
CleanupStack::PushL(filenameList);
TInt count = filenameList->Count();
// aggregate ROM stores iteratively
for(TInt index = 0; index < count; index++)
{
RBuf fileName;
fileName.CreateL(KMaxFileName);
CleanupClosePushL(fileName);
FileUtils::MakePrivateROMFilenameL(iFs, ((*filenameList)[index]).iName, fileName);
// if there is any corrupt certstore present then we will simply ignore its
// aggregation and proceed with aggregating remaining stores.
TRAP_IGNORE(AggregateStoreFileL(fileName));
CleanupStack::PopAndDestroy(&fileName);
}
// write the 'iEntryList' to the composite store.
UpdateStoreL();
CompactStoreL();
CleanupStack::PopAndDestroy(filenameList);
}
void CFSCertStoreServer::OpenStoreL()
{
RBuf filename;
filename.CreateL(KMaxFilenameLength);
CleanupClosePushL(filename);
FileUtils::MakePrivateFilenameL(iFs, KCertStoreFilename, filename);
// Attempt to open the store
// need to test opening corrupt store
TRAPD(err, RestoreL(filename));
if (err == KErrNoMemory || err == KErrInUse)
{
User::Leave(err);
}
if (err != KErrNone)
{
// Couldn't open RAM based store, copy from ROM
FileUtils::EnsurePathL(iFs, filename);
// if patchable constant is enabled
if(iPatchableConst)
{
OpenCompositeStoreL(filename);
}
else
{
RBuf romFilename;
romFilename.CreateL(KMaxFilenameLength);
CleanupClosePushL(romFilename);
FileUtils::MakePrivateROMFilenameL(iFs, KCertStoreFilename, romFilename);
if (FileUtils::ExistsL(iFs, romFilename))
{
FileUtils::CopyL(iFs, romFilename, filename);
}
else
{
// if none of the certstore files are present then create an empty one.
CreateStoreFileL(filename);
}
CleanupStack::PopAndDestroy(&romFilename);
//Retry open, and leave on failure
RestoreL(filename);
}
}
CleanupStack::PopAndDestroy(&filename);
assert(iStore, EPanicCertStoreOpenState);
}
void CFSCertStoreServer::CreateStoreFileL(const TDesC& aFile)
{
TRAPD(err, DoCreateStoreFileL(aFile));
if (err != KErrNone)
{
// Attempt to delete file, but don't complain if it fails
iFs.Delete(aFile);
User::Leave(err);
}
}
void CFSCertStoreServer::DoCreateStoreFileL(const TDesC& aFile)
{
CPermanentFileStore* fileStore = CPermanentFileStore::ReplaceLC(iFs, aFile, EFileRead | EFileWrite | EFileShareExclusive);
fileStore->SetTypeL(KPermanentFileStoreLayoutUid);
// Create info stream
CCertStoreEntryList* emptyCertList = CCertStoreEntryList::NewLC();
RStoreWriteStream infoStream;
TStreamId streamId = infoStream.CreateLC(*fileStore);
infoStream << *emptyCertList;
infoStream.CommitL();
CleanupStack::PopAndDestroy(2, emptyCertList);
// Create root stream - just contains id of info stream
RStoreWriteStream rootStream;
TStreamId rootStreamId = rootStream.CreateLC(*fileStore);
fileStore->SetRootL(rootStreamId);
rootStream << streamId;
rootStream.CommitL();
CleanupStack::PopAndDestroy(&rootStream);
fileStore->CommitL();
CleanupStack::PopAndDestroy(fileStore);
}