/*
* Copyright (c) 2004-2010 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 "CKeyDataManager.h"
#include "fsdatatypes.h"
#include "fstokencliserv.h"
#include "fstokenutil.h"
#include "keystorepassphrase.h"

_LIT(KKeyStoreFilename,"keys.dat");

const TInt KDefaultPassphraseTimeout = 30;

//	*********************************************************************
//	Key store data manager - maintains array of objects representing keys
//	*********************************************************************

/*static*/ CFileKeyDataManager* CFileKeyDataManager::NewL()
	{
	CFileKeyDataManager* self = new (ELeave) CFileKeyDataManager();
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

CFileKeyDataManager::~CFileKeyDataManager()
	{
	if (iFileStore)
		{
		CompactStore();
		delete iFileStore;
		}

	iFile.Close(); // May already have been closed by store
	iFs.Close();
		
	iKeys.ResetAndDestroy();
	iKeys.Close();
	}

CFileKeyDataManager::CFileKeyDataManager() :
	iRootStreamId(KNullStreamId),
	iInfoStreamId(KNullStreamId)
	,iPassStreamId(KNullStreamId),
	iTimeoutStreamId(KNullStreamId)
	{
	}

void CFileKeyDataManager::ConstructL()
	{
	
	User::LeaveIfError(iFs.Connect());
	OpenStoreL();

	RStoreReadStream lookupStream;
	lookupStream.OpenLC(*iFileStore, iInfoStreamId);

	iPassStreamId = (TStreamId) lookupStream.ReadUint32L();
	iTimeoutStreamId = (TStreamId) lookupStream.ReadUint32L();

	TInt count = lookupStream.ReadInt32L();
	for (TInt index = 0; index < count; index++)
		{
		CFileKeyData* keyData = CFileKeyData::NewL(lookupStream);
		CleanupStack::PushL(keyData);

		if (keyData->Handle() > iKeyIdentifier)
			iKeyIdentifier = keyData->Handle();

		iKeys.AppendL(keyData);
		CleanupStack::Pop(keyData);
		}
	
	CleanupStack::PopAndDestroy(&lookupStream);
	ReadPassphraseTimeoutL();
	}

CPassphraseManager* CFileKeyDataManager::CreatePassphraseManagerLC()
	{
	CPassphraseManager* result = CPassphraseManager::NewL(*iFileStore);
	CleanupStack::PushL(result);
	return result;
	}

void CFileKeyDataManager::OpenStoreL()
	{
	//	Tries to locate a key store file on the default drive and then from ROM
	//	If it cannot find one, tries to create a file with permanent file store
	//	inside it In all cases, should initialise iFileStore unless it cannot
	//	create the file/store/streams
	
	__ASSERT_DEBUG(!iFileStore, PanicServer(EPanicStoreInitialised));

	TFileName fullPath;
	FileUtils::MakePrivateFilenameL(iFs, KKeyStoreFilename, fullPath);

	FileUtils::EnsurePathL(iFs, fullPath);
	TRAPD(result, OpenStoreInFileL(fullPath));

	if (result == KErrInUse) 
		{		
		// Cannot access the file now. Abort server startup rather than wiping the keystore.
		User::Leave(result); 
		}

	if (result != KErrNone)
		{		
		// Not yet opened a valid store, either no file to be found, or no valid
		// store in it. Copy the original one stored in the ROM.
		delete iFileStore;
		iFileStore = NULL;
		
		TFileName romPath;
		FileUtils::MakePrivateROMFilenameL(iFs, KKeyStoreFilename, romPath);

		if (result != KErrNotFound)
			{
			// Wipe the keystore if we can't open it (it's corrupt anyway)
			User::LeaveIfError(iFs.Delete(fullPath));
			}

		// Copy data from rom and open it	
		TRAPD(err,
			  FileUtils::CopyL(iFs, romPath, fullPath);
			  OpenStoreInFileL(fullPath)
			);
		
		if (KErrNone != err)
			{
			// We tried to copy the keystore from ROM. For some reason this
			// failed and we still cannot open the file. Create a new one from
			// scratch.
			CreateStoreInFileL(fullPath);
			}
		}

	__ASSERT_DEBUG(iFileStore, PanicServer(EPanicStoreInitialised));
	__ASSERT_DEBUG((KNullStreamId!=iRootStreamId), PanicServer(EPanicRootStreamNotReady));
	__ASSERT_DEBUG((KNullStreamId!=iInfoStreamId), PanicServer(EPanicManagerStreamNotReady));
	}

void CFileKeyDataManager::CreateStoreInFileL(const TDesC& aFile)
	{
	TInt r = iFs.MkDirAll(aFile);
	if ( (r!=KErrNone) && (r!=KErrAlreadyExists) )
		User::Leave(r);

	iFileStore = CPermanentFileStore::ReplaceL(iFs, aFile, EFileRead | EFileWrite | EFileShareExclusive);
	iFileStore->SetTypeL(KPermanentFileStoreLayoutUid);

	TCleanupItem cleanupStore(RevertStore, iFileStore);
	CleanupStack::PushL(cleanupStore);
	
	// Create timeout stream with default timeout
	RStoreWriteStream timeoutStream;
	iTimeoutStreamId = timeoutStream.CreateLC(*iFileStore);
	timeoutStream.WriteUint32L(KDefaultPassphraseTimeout);
	timeoutStream.CommitL();
	CleanupStack::PopAndDestroy(&timeoutStream);
	
	// Create info stream - Currently no passphrase created, and no keys
	RStoreWriteStream infoStream;
	iInfoStreamId = infoStream.CreateLC(*iFileStore);
	
	infoStream.WriteUint32L(KNullStreamId.Value());
	infoStream.WriteUint32L(iTimeoutStreamId.Value());
	
	infoStream.WriteUint32L(0); // Write key count of zero
	infoStream.CommitL();
	CleanupStack::PopAndDestroy(&infoStream);

	// Create root stream - just contains id of info stream
	RStoreWriteStream rootStream;
	iRootStreamId = rootStream.CreateLC(*iFileStore);
	iFileStore->SetRootL(iRootStreamId);

	rootStream.WriteUint32L(iInfoStreamId.Value());		
	rootStream.CommitL();
	CleanupStack::PopAndDestroy(&rootStream);

	WriteKeysToStoreL();
	
	iFileStore->CommitL();
	CleanupStack::Pop(); // cleanupStore
	}

void CFileKeyDataManager::OpenStoreInFileL(const TDesC& aFile)
	{
	// Make sure the file isn't write protected
	User::LeaveIfError(iFs.SetAtt(aFile, 0, KEntryAttReadOnly));
	
	User::LeaveIfError(iFile.Open(iFs, aFile, EFileRead | EFileWrite | EFileShareExclusive));
	
	iFileStore = CPermanentFileStore::FromL(iFile);		

	// Get the salt, root and manager TStreamIds
	iRootStreamId = iFileStore->Root();
	if (iRootStreamId == KNullStreamId)
		{
		User::Leave(KErrCorrupt);
		}
	
	RStoreReadStream rootStream;
	rootStream.OpenLC(*iFileStore, iRootStreamId);
	iInfoStreamId = (TStreamId)(rootStream.ReadUint32L());
	CleanupStack::PopAndDestroy(&rootStream);
	}

// Methods dealing with atomic updates to key data file ////////////////////////

// This is a cleanup item that reverts the store
void CFileKeyDataManager::RevertStore(TAny* aStore)
{
	CPermanentFileStore* store = reinterpret_cast<CPermanentFileStore*>(aStore);
	TRAP_IGNORE(store->RevertL());
	// 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.
}

// Rewrites the info stream (ie the array of key data info) to the store
void CFileKeyDataManager::WriteKeysToStoreL()
	{
	RStoreWriteStream lookupStream;
	lookupStream.ReplaceLC(*iFileStore, iInfoStreamId);

	lookupStream.WriteUint32L(iPassStreamId.Value());
	lookupStream.WriteUint32L(iTimeoutStreamId.Value());

	
	TInt keyCount = iKeys.Count();
	lookupStream.WriteInt32L(keyCount);

	for (TInt index = 0; index < keyCount; index++)
		{
		const CFileKeyData* key = iKeys[index];
		key->ExternalizeL(lookupStream);
		}

	lookupStream.CommitL();
	CleanupStack::PopAndDestroy(&lookupStream);
	}

/**
 * Add a key to the store.  Assumes that the key data streams (info, public key
 * and private key) have already been written.
 */
void CFileKeyDataManager::AddL(const CFileKeyData* aKeyData)
	{
	ASSERT(aKeyData);
	ASSERT(aKeyData->PassphraseStreamId() != KNullStreamId);

	// Add the key to to the array, rewrite the infostream and 
	// ONLY THEN commit the store
	User::LeaveIfError(iKeys.Append(aKeyData));
	TStreamId oldDefaultPassphraseId;

	// Set the default passphrase id if this is the first key
	oldDefaultPassphraseId = iPassStreamId;
	if (iKeys.Count() == 1)
		{
		iPassStreamId = aKeyData->PassphraseStreamId();
		}
	
	TRAPD(err,UpdateStoreL());
	
	if (err != KErrNone)
		{
		iKeys.Remove(iKeys.Count() - 1);
		iPassStreamId = oldDefaultPassphraseId;
		User::Leave(err);
		}
	}

void CFileKeyDataManager::UpdateStoreL()
	{
	WriteKeysToStoreL();

	// Release ownership of key data and reset default passphrase id if store
	// can't be written
	TCleanupItem cleanupStore(RevertStore, iFileStore);
	CleanupStack::PushL(cleanupStore);

	iFileStore->CommitL();
	
	CleanupStack::Pop(); // cleanupStore
	}

/**
 * "Transaction safe" key removal - only removes the key in memory and file if
 * all operations are successful.
 */
void CFileKeyDataManager::RemoveL(TInt aObjectId)
	{
	TInt index;
	const CFileKeyData* key = NULL;
	for (index = 0 ; index < iKeys.Count() ; ++index)
		{
		if (iKeys[index]->Handle() == aObjectId)
			{
			key = iKeys[index];
			break;
			}
		}
	
	if (!key)
		{
		User::Leave(KErrNotFound);
		}

	TCleanupItem cleanupStore(RevertStore, iFileStore);
	CleanupStack::PushL(cleanupStore);	

	iFileStore->DeleteL(key->PrivateDataStreamId());
	iFileStore->DeleteL(key->PublicDataStreamId());
	iFileStore->DeleteL(key->InfoDataStreamId());

	// Remove the passphrase if it's the last key
	TStreamId oldPassphraseId = iPassStreamId;
	if (Count() == 1)
		{
		iFileStore->DeleteL(iPassStreamId);
		iPassStreamId = KNullStreamId;
		}
	
	// Remove the key
	iKeys.Remove(index);
	
	TRAPD(res, WriteKeysToStoreL());

	if (res != KErrNone)
		{
		iPassStreamId = oldPassphraseId;

		User::LeaveIfError(iKeys.Append(key)); // Put it back, shouldn't leave
		User::Leave(res);
		}
	else 
		{
		delete key;   // Cannot leave from the point it's removed to here, so no cleanup stack!
		}		
	iFileStore->CommitL();

	CleanupStack::Pop(); // cleanupStore
	CompactStore();
}

TBool CFileKeyDataManager::IsKeyAlreadyInStore(const TDesC& aKeyLabel) const
{//	Check each key in the store to determine if aKeyLabel already exists
	TInt keyCount = iKeys.Count();
	TBool isInStore = EFalse;
	for (TInt index = 0; index < keyCount; index++)
	{
		const TDesC& keyLabel = iKeys[index]->Label();
		if (keyLabel.Compare(aKeyLabel)==0)
		{
			isInStore = ETrue;
			break;
		}
	}

	return (isInStore);
}


TInt CFileKeyDataManager::Count() const
	{
	return iKeys.Count();
	}	

const CFileKeyData* CFileKeyDataManager::operator[](TInt aIndex) const
	{
	return iKeys[aIndex];
	}

const CFileKeyData* CFileKeyDataManager::Lookup(TInt aObjectId) const
	{
	TInt count = Count();
	for (TInt i = 0; i < count; ++i)
		{
		if ((*this)[i]->Handle() == aObjectId)
			{
			return (*this)[i];
			}
		}
	return NULL;
	}

//	*********************************************************************
//	Management of file and store therein
//	*********************************************************************

const CFileKeyData* CFileKeyDataManager::CreateKeyDataLC(const TDesC& aLabel, TStreamId aPassStreamId)
	{
	ASSERT(aPassStreamId != KNullStreamId);
	TInt objectId = ++iKeyIdentifier;
	TStreamId infoData = CreateWriteStreamL();
	TStreamId publicKeyData = CreateWriteStreamL();
	TStreamId privateKeyData = CreateWriteStreamL();
	return CFileKeyData::NewLC(objectId, aLabel, infoData, aPassStreamId, publicKeyData, privateKeyData);
	}


//	Creates a new write stream in the store (which it then closes)
//	Returns the TStreamId associated with it
TStreamId CFileKeyDataManager::CreateWriteStreamL()
	{
	__ASSERT_DEBUG(iFileStore, PanicServer(EPanicStoreInitialised));
	if (!iFileStore)
		User::Leave(KErrNotReady);

	RStoreWriteStream newStream;
	TStreamId result = newStream.CreateLC(*iFileStore);
	if (KNullStreamId == result)
		User::Leave(KErrBadHandle);

	newStream.CommitL();
	CleanupStack::PopAndDestroy(&newStream);

	return result;
	}

CKeyInfo* CFileKeyDataManager::ReadKeyInfoLC(const CFileKeyData& aKeyData) const
	{
	__ASSERT_ALWAYS(iFileStore, PanicServer(EPanicStoreInitialised));
	RStoreReadStream stream;
	stream.OpenLC(*iFileStore, aKeyData.InfoDataStreamId());
	CKeyInfo* info = CKeyInfo::NewL(stream);
	
	CleanupStack::PopAndDestroy(&stream);
	info->CleanupPushL();
	if (info->Handle() != aKeyData.Handle())
		{
		User::Leave(KErrCorrupt); // is this appropriate?
		}
	return info;
	}

void CFileKeyDataManager::WriteKeyInfoL(const CFileKeyData& aKeyData, const CKeyInfo& aKeyInfo)
	{
	RStoreWriteStream infoStream;
	OpenInfoDataStreamLC(aKeyData, infoStream);
	infoStream << aKeyInfo;
	infoStream.CommitL();
	CleanupStack::PopAndDestroy(&infoStream);
	}

void CFileKeyDataManager::SafeWriteKeyInfoL(const CFileKeyData& aKeyData, const CKeyInfo& aKeyInfo)
	{
	TCleanupItem cleanupStore(RevertStore, iFileStore);
	CleanupStack::PushL(cleanupStore);	

	WriteKeyInfoL(aKeyData, aKeyInfo);
	iFileStore->CommitL();
	
	CleanupStack::Pop(); // cleanupStore	
	}

void CFileKeyDataManager::OpenInfoDataStreamLC(const CFileKeyData& aKeyData, RStoreWriteStream& aStream)
	{
	__ASSERT_ALWAYS(iFileStore, PanicServer(EPanicStoreInitialised));
	aStream.ReplaceLC(*iFileStore, aKeyData.InfoDataStreamId());
	}

void CFileKeyDataManager::OpenPublicDataStreamLC(const CFileKeyData& aKeyData, RStoreWriteStream& aStream)
	{
	__ASSERT_ALWAYS(iFileStore, PanicServer(EPanicStoreInitialised));
	aStream.ReplaceLC(*iFileStore, aKeyData.PublicDataStreamId());
	}

void CFileKeyDataManager::OpenPublicDataStreamLC(const CFileKeyData& aKeyData, RStoreReadStream& aStream) const
	{
	__ASSERT_ALWAYS(iFileStore, PanicServer(EPanicStoreInitialised));
	aStream.OpenLC(*iFileStore, aKeyData.PublicDataStreamId());
	}

void CFileKeyDataManager::OpenPrivateDataStreamLC(const CFileKeyData& aKeyData, CPassphrase& aPassphrase,
												  RStoreReadStream& aStream) 
	{
	__ASSERT_DEBUG(iFileStore, PanicServer(EPanicStoreInitialised));
	aStream.OpenLC(aPassphrase.Store(), aKeyData.PrivateDataStreamId());
	}

void CFileKeyDataManager::OpenPrivateDataStreamLC(const CFileKeyData& aKeyData, CPassphrase& aPassphrase,
												  RStoreWriteStream& aStream)
	{
	__ASSERT_DEBUG(iFileStore, PanicServer(EPanicStoreInitialised));
	aStream.ReplaceLC(aPassphrase.Store(), aKeyData.PrivateDataStreamId());
	}


TInt CFileKeyDataManager::GetPassphraseTimeout() const
	{
	return iTimeout;
	}

void CFileKeyDataManager::SetPassphraseTimeoutL(TInt aTimeout)
	{
	TInt oldTimeout = iTimeout;

	iTimeout = aTimeout;
	TRAPD(err, WritePassphraseTimeoutL(); iFileStore->CommitL());

	if (err != KErrNone)
		{
		iTimeout = oldTimeout;
		iFileStore->RevertL(); // shouldn't leave
		User::Leave(err);
		}
	}

void CFileKeyDataManager::ReadPassphraseTimeoutL()
	{
	ASSERT(iTimeout == 0); // Only called from ConstructL()
	
	RStoreReadStream stream;
	stream.OpenLC(*iFileStore, iTimeoutStreamId);
	iTimeout = stream.ReadInt32L();
	CleanupStack::PopAndDestroy(&stream);
	}

void CFileKeyDataManager::WritePassphraseTimeoutL()
	{
	RStoreWriteStream stream;
	stream.ReplaceLC(*iFileStore, iTimeoutStreamId);
	stream.WriteUint32L(iTimeout);	
	stream.CommitL();
	CleanupStack::PopAndDestroy(&stream);
	}

TStreamId CFileKeyDataManager::DefaultPassphraseId() const
	{
	ASSERT((iPassStreamId == KNullStreamId) == (Count() == 0));
	return iPassStreamId;
	}


/**
 * Attempt to compact the store - it doesn't matter if these calls leave, it
 * will only mean that the store takes up more space than necessary.
 */
void CFileKeyDataManager::CompactStore()
	{
	ASSERT(iFileStore);
	TRAP_IGNORE(iFileStore->ReclaimL(); iFileStore->CompactL());
	}

// CFileKeyData ////////////////////////////////////////////////////////////////

CFileKeyData* CFileKeyData::NewLC(TInt aObjectId, const TDesC& aLabel, TStreamId aInfoData,
								  TStreamId aPassphraseId, TStreamId aPublicData, TStreamId aPrivateData)
	{
	CFileKeyData* self = new (ELeave) CFileKeyData(aObjectId, aInfoData, aPassphraseId, aPublicData, aPrivateData);
	CleanupStack::PushL(self);
	self->ConstructL(aLabel);
	return self;
	}


CFileKeyData* CFileKeyData::NewL(RStoreReadStream& aReadStream)
	{
	CFileKeyData* self = new (ELeave) CFileKeyData();
	CleanupStack::PushL(self);
	self->InternalizeL(aReadStream);
	CleanupStack::Pop(self);
	return (self);
	}

CFileKeyData::~CFileKeyData()
	{
	delete iLabel;
	}

#ifdef KEYTOOL
CFileKeyData::CFileKeyData(TInt aObjectId, TStreamId aInfoData,  
						   TStreamId aPublicData, TStreamId aPrivateData,
						   AuthServer::TIdentityId aIdentityId) :
	iObjectId(aObjectId), iInfoData(aInfoData), 
	iPublicKeyData(aPublicData), iPrivateKeyData(aPrivateData),
	iIdentityId(aIdentityId)
	{
	ASSERT(iObjectId);
	ASSERT(iInfoData != KNullStreamId);
	ASSERT(iPublicKeyData != KNullStreamId);
	ASSERT(iPrivateKeyData != KNullStreamId);
	ASSERT(iIdentityId);
	}
#endif // KEYTOOL

CFileKeyData::CFileKeyData(TInt aObjectId, TStreamId aInfoData, TStreamId aPassphraseId, 
						   TStreamId aPublicData, TStreamId aPrivateData) :
	iObjectId(aObjectId), iInfoData(aInfoData), iPassphraseId(aPassphraseId),
	iPublicKeyData(aPublicData), iPrivateKeyData(aPrivateData)
	{
	ASSERT(iObjectId);
	ASSERT(iInfoData != KNullStreamId);
	ASSERT(iPassphraseId != KNullStreamId);
	ASSERT(iPublicKeyData != KNullStreamId);
	ASSERT(iPrivateKeyData != KNullStreamId);
	}

CFileKeyData::CFileKeyData()
	{
	}

void CFileKeyData::ConstructL(const TDesC& aLabel)
	{
	TInt labelLen = aLabel.Length();
	iLabel = HBufC::NewMaxL(labelLen);
	TPtr theLabel(iLabel->Des());
	theLabel.FillZ();
	theLabel.Copy(aLabel);
	}

void CFileKeyData::InternalizeL(RReadStream& aReadStream)
{
	iObjectId = aReadStream.ReadInt32L();
	iInfoData.InternalizeL(aReadStream);
	iPassphraseId.InternalizeL(aReadStream);
	iPublicKeyData.InternalizeL(aReadStream);
	iPrivateKeyData.InternalizeL(aReadStream);
	
	TInt labelLen = aReadStream.ReadInt32L();
	iLabel = HBufC::NewMaxL(labelLen);
	TPtr theLabel((TUint16*)iLabel->Ptr(), labelLen, labelLen);
	theLabel.FillZ(labelLen);
	aReadStream.ReadL(theLabel);
}

void CFileKeyData::ExternalizeL(RWriteStream& aWriteStream) const
{
	aWriteStream.WriteInt32L(iObjectId);
	iInfoData.ExternalizeL(aWriteStream);
	iPassphraseId.ExternalizeL(aWriteStream);
	iPublicKeyData.ExternalizeL(aWriteStream);
	iPrivateKeyData.ExternalizeL(aWriteStream);

	TInt labelLen = iLabel->Length();
	aWriteStream.WriteInt32L(labelLen);
	TPtr theLabel(iLabel->Des());
	theLabel.SetLength(labelLen);
	aWriteStream.WriteL(theLabel);
}

#ifdef KEYTOOL

CFileKeyData* CFileKeyData::CreateOldKeyL(RStoreReadStream& aReadStream)
	{
	CFileKeyData* self = new (ELeave) CFileKeyData();
	CleanupStack::PushL(self);
	self->InternalizeOldKeyL(aReadStream);
	CleanupStack::Pop(self);
	return (self);
	}

void CFileKeyData::InternalizeOldKeyL(RReadStream& aReadStream)
	{
	iObjectId = aReadStream.ReadInt32L();
	iInfoData.InternalizeL(aReadStream);
	iPassphraseId.InternalizeL(aReadStream);
	iPublicKeyData.InternalizeL(aReadStream);
	iPrivateKeyData.InternalizeL(aReadStream);
	
	TInt labelLen = aReadStream.ReadInt32L();
	iLabel = HBufC::NewMaxL(labelLen);
	TPtr theLabel((TUint16*)iLabel->Ptr(), labelLen, labelLen);
	theLabel.FillZ(labelLen);
	aReadStream.ReadL(theLabel);
	}

void CFileKeyData::ExternalizeWithAuthL(RWriteStream& aWriteStream) 
{
	aWriteStream.WriteInt32L(iObjectId);
	iInfoData.ExternalizeL(aWriteStream);
	iPublicKeyData.ExternalizeL(aWriteStream);
	iPrivateKeyData.ExternalizeL(aWriteStream);

	TInt labelLen = iLabel->Length();
	aWriteStream.WriteInt32L(labelLen);
	TPtr theLabel(iLabel->Des());
	theLabel.SetLength(labelLen);
	aWriteStream.WriteL(theLabel);
	aWriteStream.WriteInt32L(iIdentityId);
}

#endif // KEYTOOL
