//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// MicroSafe source file
// Copyright (c) 2006 Elmar Sonnenschein / esoco GmbH
// Last Change: 28.09.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.model.BasicHierarchyNode;
import de.esoco.j2me.model.HierarchyNode;
import de.esoco.j2me.model.NodeAccessException;
import de.esoco.j2me.resource.ResourceBundle;
import de.esoco.j2me.storage.HierarchicalStorage;
import de.esoco.j2me.storage.StorageException;
import de.esoco.j2me.util.Log;
import de.esoco.microsafe.crypto.CryptoHandler;
import java.io.IOException;
import org.bouncycastle.crypto.CryptoException;
/********************************************************************
* Subclass of BasicHierarchyNode with extension to handle encryption of nodes
* (and node hierarchies). A hierarchy of EncryptedNodes can only contain
* instances of this class (or subclasses) because of the requirements for the
* encryption handling.
*
* <p>There must also be a special instance as the root node that implements the
* management of the CryptoHandler instance needed for encryption through the
* methods set/getCryptoHander(). This is necessary because the default
* implementation simply returns the result of rParent.set/getCryptoHandler().
* </p>
*
* @author eso
* @see de.esoco.microsafe.crypto.CryptoHandler
*/
public abstract class EncryptableNode extends BasicHierarchyNode
{
//~ Static fields/initializers ---------------------------------------------
private static final char ENCRYPTION_NONE = 0;
//~ Instance fields --------------------------------------------------------
private char cEncryption = ENCRYPTION_NONE;
//~ Constructors -----------------------------------------------------------
/***************************************
* Constructor, simply calls the same super constructor.
*
* @see de.esoco.j2me.model.BasicHierarchyNode
*/
public EncryptableNode(EncryptableNode rParent, String sTitle, byte[] rData)
{
super(rParent, sTitle, rData);
}
//~ Methods ----------------------------------------------------------------
/***************************************
* To return the CryptoHandler for this node. It will be queried recursively
* from the EncryptableNode implementation which is the root of the node
* hierarchy (and which must therefore overload this method to return a
* CryptoHander instance).
*
* @return A CryptoHandler instance or NULL if none is available
*/
public CryptoHandler getCryptoHandler()
{
if (getParent() != null)
{
return ((EncryptableNode) getParent()).getCryptoHandler();
}
else
{
return null;
}
}
/***************************************
* Overloaded to decrypt the data before returning it if the node is
* encrypted. This requires that a CryptoHandler instance has been set
* before. Otherwise the data cannot be decrypted and a default value will
* be returned.
*
* @return The data of the node, decrypted if the node is encrypted
*/
public byte[] getData()
{
if (isEncrypted())
{
try
{
return getCryptoHandler().decrypt(aData);
}
catch (Exception e)
{
if (J2meLib.LOGGING)
{
Log.error(e);
}
return ResourceBundle.getCurrent().getString("MS_ErrDecrpt")
.getBytes();
}
}
else
{
return super.getData();
}
}
/***************************************
* Returns the total number of encrypted child nodes in the hierarchy of
* this node (not counting the starting node itself).
*
* @return The total number of children of the node
*/
public int getTotalEncryptedChildCount()
{
int nCount = 0;
for (int i = 0, n = getChildCount(); i < n; i++)
{
HierarchyNode c = getChild(i);
if (c instanceof EncryptableNode)
{
EncryptableNode rChild = (EncryptableNode) c;
if (rChild.isEncrypted())
{
nCount++;
}
nCount += rChild.getTotalEncryptedChildCount();
}
}
return nCount;
}
/***************************************
* Overloaded to handle encryption for the inserted nodes. If the child node
* is encrypted differently than the current node the child node will be
* re-encrypted with the new encryption. For this to work it is necessary
* that the inserted node has still a reference to it's previous parent
* node.
*
* <p>Regardless of the encryption state of the child node, if this node is
* encrypted the child node and all it's children will be encrypted too by
* this method.</p>
*
* @param rChild The root node of the node hierarchy to be inserted
* @param nIndex The position in the child list where to insert the node
*
* @throws StorageException If updating the storage with the child data
* fails
*
* @see de.esoco.j2me.model.MutableHierarchyNode#insertChild(de.esoco.j2me.model.HierarchyNode,
* int)
*/
public void insertChild(HierarchyNode rChild, int nIndex)
throws NodeAccessException, StorageException
{
super.insertChild(rChild, nIndex);
if (rChild instanceof EncryptableNode)
{
try
{
((EncryptableNode) rChild).changeEncryption(getCryptoHandler());
if (isEncrypted())
{
((EncryptableNode) rChild).setEncryption(true);
}
}
catch (CryptoException e)
{
throw new NodeAccessException(e);
}
}
}
/***************************************
* To check if a node is encrypted.
*
* @return TRUE, if the node is encrypted
*/
public boolean isEncrypted()
{
return (cEncryption != ENCRYPTION_NONE);
}
/***************************************
* To enable or disable encryption for the node and all of it's children. If
* no CryptoHandler is available through the method getCryptoHandler()
* nothing will be changed by this method. That means that the application
* is responsible to provide the cryptography setup.
*
* @param bEncrypt If TRUE the node hierarchy will be encrypted; if FALSE
* it will be decrypted
*
* @throws StorageException If updating the model data in the storage fails
* @throws CryptoException If enabling or disabling the encryption fails
*/
public void setEncryption(boolean bEncrypt) throws CryptoException,
StorageException
{
CryptoHandler aCrypto = getCryptoHandler();
if (aCrypto != null)
{
if (bEncrypt)
{
encrypt(aCrypto, false);
}
else
{
decrypt(aCrypto, false);
}
}
}
/***************************************
* Re-encrypts the node and it's (encrypted) children from one encryption
* handler to another. If the new encryption is same as the previous no
* change will be made. This method will only re-encrypt nodes that are
* already encrypted.
*
* @param rNewCrypto The new CryptoHandler to apply
*
* @throws StorageException If updating the model data in the storage fails
* @throws CryptoException If changing the encryption fails
*/
protected void changeEncryption(CryptoHandler rNewCrypto)
throws CryptoException, StorageException
{
CryptoHandler rCurrentCrypto = getCryptoHandler();
if (((rNewCrypto == null) && (rCurrentCrypto != null)) ||
((rNewCrypto != null) && !rNewCrypto.equals(rCurrentCrypto)))
{
// re-encryption is only necessary if nodes are already encrypted
if (rCurrentCrypto != null)
{
// first decrypt either all if handler removed (NULL) or only
// the already encrypted nodes if encryption handler has changed
decrypt(rCurrentCrypto, (rNewCrypto != null));
// then re-encrypt if handler changed
if (rNewCrypto != null)
{
encrypt(rNewCrypto, true);
}
}
}
}
/***************************************
* Overloaded to copy the additional data fields of EncryptableNode (i.e.
* the encryption flag).
*
* @param rTarget The target node for the copied data
*
* @see BasicHierarchyNode#copyFields(BasicHierarchyNode)
*/
protected void copyFields(BasicHierarchyNode rTarget)
{
super.copyFields(rTarget);
((EncryptableNode) rTarget).cEncryption = cEncryption;
}
/***************************************
* Internal method to decrypt a node and all it's children with a certain
* CryptoHandler instance. If the parameter bKeepFlag is TRUE, the node's
* encryption flag will not be reset. This is used internally to propagate
* key changes and must always be followed by invoking the method encrypt()
* on the same starting node with the parameter bOnlyIfFlagSet set to TRUE!
*
* <p>If bKeepFlag is FALSE, all encrypted nodes in the hierarchy starting
* with this node will be decrypted (assuming the CryptoHandler contains the
* correct key) and marked as such.</p>
*
* @param rCrypto The CryptoHander to use
* @param bKeepFlag If TRUE, the encryption flag of the node is not reset
*
* @throws CryptoException If decrypting the node fails
* @throws StorageException If updating the storage fails
*/
protected void decrypt(CryptoHandler rCrypto, boolean bKeepFlag)
throws CryptoException, StorageException
{
if (isEncrypted())
{
if (aData != null)
{
aData = rCrypto.decrypt(aData);
}
// if bKeepFlag is TRUE this is just the first loop of a key change;
// update of the storage will happen in the following call to encrypt()
if (!bKeepFlag)
{
cEncryption = ENCRYPTION_NONE;
updateStorage(getStorage());
}
}
for (int i = 0, cnt = getChildCount(); i < cnt; i++)
{
((EncryptableNode) getChild(i)).decrypt(rCrypto, bKeepFlag);
}
}
/***************************************
* Internal method to encrypt a node and all it's children with a certain
* CryptoHandler instance. If the parameter bOnlyIfFlagSet is TRUE, only
* nodes that have their encryption flag already set will be encrypted
* (again). This is used internally to propagate key changes and must have
* been preceded by a call to decrypt(CryptoHander, TRUE)!
*
* <p>If bOnlyIfFlagSet is FALSE, only nodes that don't have the flag set
* will be encrypted and marked as such.</p>
*
* @param rCrypto The CryptoHander to use
* @param bOnlyIfFlagSet If TRUE, only the nodes already marked as
* encrypted will be processed
*
* @throws CryptoException If encrypting the node fails
* @throws StorageException If updating the storage fails
*/
protected void encrypt(CryptoHandler rCrypto, boolean bOnlyIfFlagSet)
throws CryptoException, StorageException
{
boolean bEnc = isEncrypted();
if ((bOnlyIfFlagSet && bEnc) || (!bOnlyIfFlagSet && !bEnc))
{
// TODO: check if title should be encrypted too
if (aData != null)
{
aData = rCrypto.encrypt(aData);
}
cEncryption = CryptoHandler.getEncryptionType();
updateStorage(getStorage());
}
for (int i = 0, cnt = getChildCount(); i < cnt; i++)
{
// recursively encrypt all children
((EncryptableNode) getChild(i)).encrypt(rCrypto, bOnlyIfFlagSet);
}
}
/***************************************
* Overloaded to load additional node fields (currently three reserved byte
* values).
*
* @see BasicHierarchyNode#readFields(HierarchicalStorage)
*/
protected int readFields(HierarchicalStorage rStorage)
throws StorageException, IOException
{
int nResult = super.readFields(rStorage);
cEncryption = (char) rStorage.readByte();
return nResult;
}
/***************************************
* Overloaded to perform encryption before storing the data if the node is
* encrypted. This requires that a CryptoHandler instance has been set
* before. Otherwise the data cannot be encrypted and will remain unchanged.
*
* @param rNewData
*
* @see BasicHierarchyNode#setDataField(byte[])
*/
protected void setDataField(byte[] rNewData)
{
if (isEncrypted())
{
try
{
aData = getCryptoHandler().encrypt(rNewData);
}
catch (Exception e)
{
if (J2meLib.LOGGING)
{
Log.error(e);
}
}
}
else
{
super.setDataField(rNewData);
}
}
/***************************************
* Overloaded to write additional node fields (currently three reserved byte
* values).
*
* @see BasicHierarchyNode#writeFields(HierarchicalStorage)
*/
protected void writeFields(HierarchicalStorage rStorage)
throws StorageException, IOException
{
super.writeFields(rStorage);
rStorage.writeByte((byte) cEncryption);
}
}