//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// MicroSafe source file
// Copyright (c) 2006 Elmar Sonnenschein / esoco GmbH
// Last Change: 17.10.2006 by eso
//
// MicroSafe is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// MicroSafe is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// MicroSafe; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA or use the contact information
// from the GNU website http://www.gnu.org
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package de.esoco.microsafe.model;
import de.esoco.j2me.J2meLib;
import de.esoco.j2me.storage.HierarchicalStorage;
import de.esoco.j2me.storage.StorageException;
import de.esoco.j2me.util.Assert;
import de.esoco.j2me.util.ProgressMonitor;
import de.esoco.microsafe.MicroSafe;
import de.esoco.microsafe.crypto.CryptoHandler;
import de.esoco.microsafe.crypto.InvalidKeyException;
import java.io.IOException;
import org.bouncycastle.crypto.CryptoException;
/********************************************************************
* Data model class for the MicroSafe app.
*
* @author eso
*/
public class MicroSafeModel extends MicroSafeNode
{
//~ Static fields/initializers ---------------------------------------------
/** The major version number of the model */
public static final int MAJOR_VERSION = 1;
/** The minor version number of the model */
public static final int MINOR_VERSION = 0;
/** The number of iterations to use to generate the password verification */
public static final int KEY_VERIFY_ITERATIONS = 10;
//~ Instance fields --------------------------------------------------------
/** The handler for encrypting and decrypting of nodes */
private CryptoHandler aCryptoHandler = null;
/** The encrypted key and value for password verification */
private byte[] aKeyVerificationData = null;
/**
* If set this monitor will be notified of encryption and other long tasks
*/
private ProgressMonitor rProgressMonitor = null;
/** The storage to use for random access persistence */
private HierarchicalStorage rStorage;
//~ Constructors -----------------------------------------------------------
/***************************************
* Constructor that associates the node hierarchy with a particular storage.
*
* @param rStorage The storage to use for the model
*/
public MicroSafeModel(HierarchicalStorage rStorage)
{
super(null, "MicroSafe", null);
Assert.notFalse(rStorage.hasRandomAccess(),
"Storage must provide random accesss");
this.rStorage = rStorage;
}
//~ Methods ----------------------------------------------------------------
/***************************************
* To close the model and the underlying storage after saving all unsaved
* data.
*
* @throws StorageException If closing the storage fails
*/
public void close() throws StorageException
{
rStorage.close();
}
/***************************************
* To return the cryptography handler of this model. If this has not been
* created already the return value may be NULL. An instance will be created
* when a key (password) is set through the method setEncryptionKey().
*
* @return The internal cryptography handler or NULL
*
* @see #setEncryptionKey(byte[])
*/
public CryptoHandler getCryptoHandler()
{
return aCryptoHandler;
}
/***************************************
* Overloaded to return the current progress monitor instance used by the
* model.
*
* @return The current monitor (NULL if none)
*/
public ProgressMonitor getProgressMonitor()
{
return rProgressMonitor;
}
/***************************************
* To return the storage where each change to the model will be stored
* automatically.
*
* @return The HierarchicalStorage instance for this model
*
* @see de.esoco.j2me.model.BasicHierarchyNode#getStorage()
*/
public HierarchicalStorage getStorage()
{
return rStorage;
}
/***************************************
* Initializes the model from the underlying storage. This method either
* reads the node hierarchy from the storage or, if the storage is empty,
* writes a new node record structure to the storage.
*
* @throws StorageException If accessing the storage fails
* @throws IOException If reading from or writing to the storage fails
*/
public void initFromStorage() throws StorageException, IOException
{
if (rStorage.nextNode())
{
rStorage.reset();
load();
}
else
{
store();
}
}
/***************************************
* Loads the node hierarchy from the underlying HierarchicalStorage
* implementation. Invokes the method <code>readFrom()</code> from the base
* class.
*
* @throws StorageException If accessing the storage fails
* @throws IOException If reading from the storage fails
*
* @see de.esoco.j2me.model.BasicHierarchyNode#readFrom(HierarchicalStorage)
*/
public void load() throws StorageException, IOException
{
initProgressMonitor(rStorage.getNodeCountEstimate(),
MicroSafe.getString("RsLoad"));
try
{
if (rStorage.nextNode())
{
readFrom(rStorage);
rStorage.closeNode();
}
}
finally
{
resetProgressMonitor();
}
}
/***************************************
* To set the cryptography key (password) for this model. If the model is
* already encrypted and the new key differs from the previous one all
* encrypted nodes will be decrypted and then re-encrypted with the new key.
* If the new key is NULL encryption will be removed and all encrypted nodes
* will be decrypted.
*
* <p>If a key had been associated with the model before password
* verification data has been stored in the model. This data will be
* compared to the new key and if it doesn't match, an InvalidKeyException
* will be thrown.</p>
*
* <p>The application is responsible for setting the correct key before it
* tries to access encrypted data.</p>
*
* @param rKey The data of the new key or NULL to remove encryption
*
* @throws InvalidKeyException If the new key doesn't match the original one
* @throws If a general cryptography error occurs
* @throws StorageException If updating the model data in the storage
* fails
*
* @see de.esoco.j2me.crypto.CryptoHandler
*/
public void setEncryptionKey(byte[] rKey) throws InvalidKeyException,
CryptoException,
StorageException
{
CryptoHandler aNewCrypto = null;
if (rKey != null)
{
aNewCrypto = new CryptoHandler(rKey);
aNewCrypto.setProgressMonitor(rProgressMonitor);
}
if (((aNewCrypto == null) && (aCryptoHandler != null)) ||
((aNewCrypto != null) && !aNewCrypto.equals(aCryptoHandler)))
{
if ((aCryptoHandler == null) && (aKeyVerificationData != null))
{
verifyKey(aNewCrypto);
return;
}
changeEncryption(aNewCrypto);
// generate key verification data for the new key or reset it
if (aNewCrypto != null)
{
aKeyVerificationData = aNewCrypto.generateKeyVerification(KEY_VERIFY_ITERATIONS);
}
else
{
aKeyVerificationData = null;
}
aCryptoHandler = aNewCrypto;
updateStorage(rStorage);
}
}
/***************************************
* Sets the current progress monitor instance used by the model. If set this
* monitor will be notified of encryption and other long running tasks.
*
* @param aMonitor The progress monitor to notify or NULL to disable
*/
public void setProgressMonitor(ProgressMonitor aMonitor)
{
rProgressMonitor = aMonitor;
}
/***************************************
* Stores the node hierarchy in the underlying HierarchicalStorage
* implementation. Invokes the method <code>writeTo()</code> from the base
* class.
*
* @throws StorageException If accessing the storage fails
* @throws IOException If writing to the storage fails
*
* @see de.esoco.j2me.model.BasicHierarchyNode#writeTo(de.esoco.j2me.storage.HierarchicalStorage)
*/
public void store() throws StorageException, IOException
{
initProgressMonitor(getTotalChildCount() + 1,
MicroSafe.getString("RsStore"));
try
{
rStorage.createNode();
writeTo(rStorage);
rStorage.closeNode();
}
finally
{
resetProgressMonitor();
}
}
/***************************************
* To check whether the model uses encryption in any of it's nodes.
*
* @return TRUE if encryption is used in any node of the model
*/
public boolean usesEncryption()
{
// check if key verification data exists; this will even work if the
// crypto handler (i.e., the password) hasn't been set yet
return (aKeyVerificationData != null);
}
/***************************************
* Overloaded to load additional model fields (currently a version number).
*
* @see de.esoco.j2me.model.BasicHierarchyNode#readFields(de.esoco.j2me.storage.HierarchicalStorage)
*/
protected int readFields(HierarchicalStorage rStorage)
throws StorageException, IOException
{
int nResult = super.readFields(rStorage);
// Read version number
int nMajor = rStorage.readByte();
int nMinor = rStorage.readByte();
// And check
if (J2meLib.DEBUG)
{
Assert.notFalse((nMajor == MAJOR_VERSION) &&
(nMinor == MINOR_VERSION),
"Wrong model version number (" + MAJOR_VERSION +
"." + MINOR_VERSION + " expected): " + nMajor +
"." + nMinor);
}
if (rStorage.readByte() == 1)
{
aKeyVerificationData = rStorage.readData();
}
return nResult;
}
/***************************************
* Overloaded to write additional model fields: the model version number and
* the encryption data.
*
* @see de.esoco.j2me.model.BasicHierarchyNode#writeFields(HierarchicalStorage)
*/
protected void writeFields(HierarchicalStorage rStorage)
throws StorageException, IOException
{
boolean bEncryptionUsed = (aCryptoHandler != null);
super.writeFields(rStorage);
// Version 1.0
rStorage.writeByte((byte) MAJOR_VERSION);
rStorage.writeByte((byte) MINOR_VERSION);
rStorage.writeByte((byte) (bEncryptionUsed ? 1 : 0));
if (bEncryptionUsed)
{
rStorage.writeData(aKeyVerificationData);
}
}
/***************************************
* Verifies a new key that is already stored in a CryptoHandler instance.
* This is done by comparing the key with the key verification data stored
* in the model which therefore must exist already.
*
* @param rNewCrypto The CryptoHander containing the new key
*
* @throws CryptoException If performing the key verification fails
* @throws InvalidKeyException If the key doesn't match the verification
* data
*/
private void verifyKey(CryptoHandler rNewCrypto) throws CryptoException,
InvalidKeyException
{
// Will throw an Exception if the verification fails
rNewCrypto.verifyKey(aKeyVerificationData);
aCryptoHandler = rNewCrypto;
}
}