com.gemstone.org.jgroups.protocols.ENCRYPT Maven / Gradle / Ivy
Show all versions of gemfire-jgroups Show documentation
/** Notice of modification as required by the LGPL
* This file was modified by Gemstone Systems Inc. on
* $Date$
**/
// $Id: ENCRYPT.java,v 1.21 2005/12/21 10:16:14 belaban Exp $
package com.gemstone.org.jgroups.protocols;
import com.gemstone.org.jgroups.oswego.concurrent.LinkedQueue;
import com.gemstone.org.jgroups.*;
import com.gemstone.org.jgroups.stack.Protocol;
import com.gemstone.org.jgroups.util.ExternalStrings;
import com.gemstone.org.jgroups.util.QueueClosedException;
import com.gemstone.org.jgroups.util.Streamable;
import com.gemstone.org.jgroups.util.Util;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.Properties;
import java.util.WeakHashMap;
/**
* ENCRYPT layer. Encrypt and decrypt the group communication in JGroups
*
* The file can be used in two ways:
*
* - Option 1. Configured with a secretKey in a keystore so it can be used at any
* layer in JGroups without the need for a coordinator, or if you want protection against passive
* monitoring but do not want the key exchange overhead and complexity. In this mode all nodes must be distributed with
* the same keystore file.
*
- Option 2. Configured with algorithms and key sizes. The Encrypt Layer in this mode sould be used between the
* FRAG and PBCast layers in the stack. The coordinator then chooses the
* secretkey which it distributes amongst all the peers. In this form no keystore exists as the keys are
* distributed using a public/private key exchange. View changes that identify a new controller will result in a new session key
* being generated and then distributed to all peers. This overhead can be substantial in a an application with
* a reasonable peer churn.
*
*
*
* Each message is identified as encrypted with a specific encryption header which identifies
* the type of encrypt header and an MD5 digest that identifies the version of the key being used
* to encrypt/decrypt the messages.
*
*
*
Option 1
* This is the simplest option and can be used by simply inserting the Encryption layer
* at any point in the JGroup stack - it will encrypt all Events of a type MSG that
* have a non-null message buffer. The format of the entry in this form is:
* <ENCRYPT key_store_name="defaultStore.keystore" store_password="changeit" alias="myKey"/>
* An example bare-bones.xml file showing the keystore version can be found in the conf
* ina file called EncryptKeyStore.xml - along with a defaultStore.keystore file.
* In order to use the Encrypt layer in this manner it is necessary to have the secretKey already generated
* in a keystore file. The directory containing the keystore file must be on the application's classpath.
* You cannot create a SecretKey keystore file using the keytool application shipped with the JDK.
* A java file called KeyStoreGenerator is included in the demo
* package that can be used from the command line (or IDE) to generate a suitable keystore.
*
*
*
Option 2
* This option is suited to an application that does not ship with a known key but instead it is generated
* and distributed by the controller. The secret key is first generated by the Controller (in JGroup terms). When a view change
* occurs a peer will request the secret key by sending a key request with its own public key. The controller
* encrypts the secret key with this key and sends it back to the peer who then decrypts it and installs the
* key as its own secret key.
*
All encryption and decryption of Messages is done using this key. When a peer receives
* a view change that shows a different keyserver it will repeat this process - the view change event
* also trigger the encrypt layer to queue up and down messages until the new key is installed.
* The previous keys are retained so that messages sent before the view change that are queued can be decrypted
* if the key is different.
*
* An example EncryptNoKeyStore.xml is included in the conf file as a guide.
*
*
Note: the current version does not support the concept of perfect forward encryption (PFE)
* which means that if a peer leaves the group the keys are re-generated preventing the departed peer from
* decrypting future messages if it chooses to listen in on the group. This is not included as it really requires
* a suitable authentication scheme as well to make this feature useful as there is nothing to stop the peer rejoining and receiving the new
* key. A future release will address this issue.
*
* @author Steve Woodcock
*/
public class ENCRYPT extends Protocol {
public static class EncryptHeader extends com.gemstone.org.jgroups.Header implements Streamable {
short type;
public static final short ENCRYPT = 0;
public static final short KEY_REQUEST = 1;
public static final short SERVER_PUBKEY = 2;
public static final short SECRETKEY = 3;
public static final short SECRETKEY_READY = 4;
// adding key for Message object purpose
static final String KEY = "encrypt";
String version;
public EncryptHeader()
{
}
public EncryptHeader(short type)
{
//this(type, 0l);
this.type = type;
this.version = "";
}
public EncryptHeader(short type, String version)
{
this.type = type;
this.version = version;
}
public void writeExternal(java.io.ObjectOutput out) throws IOException
{
out.writeShort(type);
out.writeObject(version);
}
public void readExternal(java.io.ObjectInput in) throws IOException,
ClassNotFoundException
{
type = in.readShort();
version = (String)in.readObject();
}
public void writeTo(DataOutputStream out) throws IOException {
out.writeShort(type);
Util.writeString(version, out);
}
public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException {
type=in.readShort();
version=Util.readString(in);
}
@Override // GemStoneAddition
public String toString()
{
return "ENCRYPT [type=" + type + " version=\"" + version + "\"]";
}
@Override // GemStoneAddition
public long size(short v) {
long retval=Global.SHORT_SIZE + Global.BYTE_SIZE;
if(version != null)
retval+=version.length() +2;
return retval;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override // GemStoneAddition
public boolean equals(Object obj)
{
if (obj instanceof EncryptHeader)
{
boolean res = ((((EncryptHeader) obj).getType() == type) && ((((EncryptHeader) obj)
.getVersion().equals(version))));
return res;
}
return false;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*
* Note that we just need to make sure that equal objects return equal
* hashcodes; nothing really elaborate is done here.
*/
@Override // GemStoneAddition
public int hashCode() { // GemStoneAddition
return 0; // TODO more efficient implementation :-)
}
/**
* @return Returns the type.
*/
protected short getType()
{
return type;
}
/**
* @return Returns the version.
*/
protected String getVersion()
{
return version;
}
}
static final String DEFAULT_SYM_ALGO = "Blowfish";
// address info
Address local_addr = null;
// keyserver address
Address keyServerAddr = null;
//used to see whether we are the key server
// boolean keyServer = false; GemStoneAddition
// encryption properties in no supplied key mode
String asymProvider = null;
final String symProvider = null;
String asymAlgorithm = "RSA";
String symAlgorithm = DEFAULT_SYM_ALGO;
int asymInit = 512; // initial public/private key length
int symInit = 56; // initial shared key length
// properties for functioning in supplied key mode
private boolean suppliedKey = false;
private String keyStoreName;
private String storePassword ="changeit"; //JDK default
private String keyPassword="changeit"; //JDK default
private String alias="mykey"; // JDK default
// public/private Key
KeyPair Kpair; // to store own's public/private Key
// for client to store server's public Key
final static/*GemStoneAddition for findbugs*/ PublicKey serverPubKey = null;
// needed because we do simultaneous encode/decode with these ciphers - which
// would be a threading issue
Cipher symEncodingCipher;
Cipher symDecodingCipher;
// version filed for secret key
private String symVersion = null;
// dhared secret key to encrypt/decrypt messages
SecretKey secretKey = null;
// map to hold previous keys so we can decrypt some earlier messages if we need to
final Map keyMap = new WeakHashMap();
// queues to buffer data while we are swapping shared key
// or obtsining key for first time
private boolean queue_up = true;
private boolean queue_down = false;
// queue to hold upcoming messages while key negotiation is happening
private LinkedQueue upMessageQueue = new LinkedQueue();
// queue to hold downcoming messages while key negotiation is happening
private LinkedQueue downMessageQueue = new LinkedQueue();
// decrypting cypher for secret key requests
private Cipher asymCipher;
public ENCRYPT()
{
}
@Override // GemStoneAddition
public String getName()
{
return "ENCRYPT";
}
/*
* GetAlgorithm: Get the algorithm name from "algorithm/mode/padding"
* taken from original ENCRYPT file
*/
private String getAlgorithm(String s)
{
int index = s.indexOf("/");
if (index == -1)
return s;
return s.substring(0, index);
}
@Override // GemStoneAddition
public boolean setProperties(Properties props)
{
String str;
super.setProperties(props);
// asymmetric key length
str = props.getProperty("asym_init");
if (str != null)
{
asymInit = Integer.parseInt(str);
props.remove("asym_init");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_ASYM_ALGO_BITS_USED_IS__0, asymInit);
}
// symmetric key length
str = props.getProperty("sym_init");
if (str != null)
{
symInit = Integer.parseInt(str);
props.remove("sym_init");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_SYM_ALGO_BITS_USED_IS__0, symInit);
}
// asymmetric algorithm name
str = props.getProperty("asym_algorithm");
if (str != null)
{
asymAlgorithm = str;
props.remove("asym_algorithm");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_ASYM_ALGO_USED_IS__0, asymAlgorithm);
}
// symmetric algorithm name
str = props.getProperty("sym_algorithm");
if (str != null)
{
symAlgorithm = str;
props.remove("sym_algorithm");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_SYM_ALGO_USED_IS__0, symAlgorithm);
}
// symmetric algorithm name
str = props.getProperty("asym_provider");
if (str != null)
{
asymProvider = str;
props.remove("asym_provider");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_ASYMPROVIDER_USED_IS__0, asymProvider);
}
//symmetric algorithm name
str = props.getProperty("key_store_name");
if (str != null)
{
keyStoreName = str;
props.remove("key_store_name");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_KEY_STORE_NAME_USED_IS__0, keyStoreName);
}
// symmetric algorithm name
str = props.getProperty("store_password");
if (str != null)
{
storePassword = str;
props.remove("store_password");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_STORE_PASSWORD_USED_IS_NOT_NULL);
}
// symmetric algorithm name
str = props.getProperty("key_password");
if (str != null)
{
keyPassword = str;
props.remove("key_password");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_KEY_PASSWORD_USED_IS_NOT_NULL);
} else if (storePassword != null)
{
keyPassword = storePassword;
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_KEY_PASSWORD_USED_IS_SAME_AS_STORE_PASSWORD);
}
// symmetric algorithm name
str = props.getProperty("alias");
if (str != null)
{
alias = str;
props.remove("alias");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_ALIAS_USED_IS__0, alias);
}
if (props.size() > 0)
{
if (log.isErrorEnabled())
log.error(ExternalStrings.ENCRYPT_THESE_PROPERTIES_ARE_NOT_RECOGNIZED_0, props);
return false;
}
return true;
}
@Override // GemStoneAddition
public void init() throws Exception
{
if (keyStoreName == null)
{
initSymKey();
initKeyPair();
} else
{
initConfiguredKey();
}
initSymCiphers(symAlgorithm, getSecretKey());
}
/**
* Initialisation if a supplied key is defined in the properties. This
* supplied key must be in a keystore which can be generated using the
* keystoreGenerator file in demos. The keystore must be on the classpath
* to find it.
*
* @throws KeyStoreException
* @throws Exception
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws UnrecoverableKeyException
*/
private void initConfiguredKey() throws KeyStoreException, Exception,
IOException, NoSuchAlgorithmException, CertificateException,
UnrecoverableKeyException
{
InputStream inputStream = null;
// must not use default keystore type - as does not support secret keys
KeyStore store = KeyStore.getInstance("JCEKS");
SecretKey tempKey = null;
try
{
// load in keystore using this thread's classloader
inputStream = ClassLoader.getSystemResourceAsStream(keyStoreName);
// we can't find a keystore here -
if (inputStream == null)
{
throw new Exception("Unable to load keystore " + keyStoreName +
" ensure file is on classpath");
}
// we have located a file lets load the keystore
try{
store.load(inputStream, storePassword.toCharArray());
// loaded keystore - get the key
tempKey = (SecretKey) store
.getKey(alias, keyPassword.toCharArray());
} catch (IOException e){
throw new Exception("Unable to load keystore "+ keyStoreName + ": " + e);
}catch (NoSuchAlgorithmException e){
throw new Exception("No Such algorithm "+ keyStoreName + ": " + e);
}catch(CertificateException e){
throw new Exception("Certificate exception "+ keyStoreName + ": " + e);
}
if (tempKey == null)
{
throw new Exception("Unable to retrieve key '" + alias
+ "' from keystore " + keyStoreName);
}
//set the key here
setSecretKey(tempKey);
if (symAlgorithm.equals(DEFAULT_SYM_ALGO)) {
symAlgorithm = tempKey.getAlgorithm();
}
// set the fact we are using a supplied key
suppliedKey = true;
queue_down =false;
queue_up =false;
} finally
{
Util.closeInputStream(inputStream);
}
}
/**
* Used to initialise the symmetric key if none is supplied in a keystore.
* @throws Exception
*/
public void initSymKey() throws Exception
{
KeyGenerator keyGen = null;
// see if we have a provider specified
if (symProvider != null && symProvider.trim().length() > 0)
{
keyGen = KeyGenerator.getInstance(getAlgorithm(symAlgorithm),
symProvider);
} else
{
keyGen = KeyGenerator.getInstance(getAlgorithm(symAlgorithm));
}
// generate the key using the defined init properties
keyGen.init(symInit);
secretKey = keyGen.generateKey();
setSecretKey(secretKey);
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_SYMMETRIC_KEY_GENERATED);
}
/**
* Initialises the Ciphers for both encryption and decryption using the
* generated or supplied secret key.
*
* @param algorithm
* @param secret
* @throws Exception
*/
private void initSymCiphers(String algorithm, SecretKey secret) throws Exception
{
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_INITIALIZING_SYMMETRIC_CIPHERS);
symEncodingCipher = Cipher.getInstance(algorithm);
symDecodingCipher = Cipher.getInstance(algorithm);
symEncodingCipher.init(Cipher.ENCRYPT_MODE, secret);
symDecodingCipher.init(Cipher.DECRYPT_MODE, secret);
//set the version
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(secret.getEncoded());
symVersion = new String(digest.digest(), "UTF-8");
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_INITIALIZED_SYMMETRIC_CIPHERS_WITH_SECRET_KEY_VERSION__0, symVersion);
}
/**
* Generates the public/private key pair from the init params
* @throws Exception
*/
public void initKeyPair() throws Exception
{
// generate keys according to the specified algorithms
// generate publicKey and Private Key
KeyPairGenerator KpairGen = null;
if (asymProvider != null && asymProvider.trim().length() > 0)
{
KpairGen = KeyPairGenerator.getInstance(
getAlgorithm(asymAlgorithm), asymProvider);
} else
{
KpairGen = KeyPairGenerator
.getInstance(getAlgorithm(asymAlgorithm));
}
KpairGen.initialize(asymInit, new SecureRandom());
Kpair = KpairGen.generateKeyPair();
// set up the Cipher to decrypt secret key responses encrypted with our key
asymCipher = Cipher.getInstance(asymAlgorithm);
asymCipher.init(Cipher.DECRYPT_MODE,Kpair.getPrivate());
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_ASYM_ALGO_INITIALIZED);
}
/** Just remove if you don't need to reset any state */
public void reset()
{
}
/* (non-Javadoc)
* @see org.jgroups.stack.Protocol#up(org.jgroups.Event)
*/
@Override // GemStoneAddition
public void up(Event evt)
{
switch (evt.getType()) {
// we need to know what our address is
case Event.SET_LOCAL_ADDRESS :
local_addr = (Address) evt.getArg();
if (log.isDebugEnabled())
log.debug("set local address to " + local_addr);
break;
case Event.VIEW_CHANGE:
View view=(View)evt.getArg();
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_HANDLING_VIEW__0, view);
if (!suppliedKey){
handleViewChange(view);
}
break;
// we try and decrypt all messages
case Event.MSG :
// if empty just pass up
if (evt.getArg() == null || ((Message)evt.getArg()).getBuffer() == null){
if (trace)
log.trace("passing up message as it has an empty buffer ");
break;
}
// try and handle message
try
{
handleUpMessage(evt);
} catch (Exception e)
{
log.warn("Exception occurred decrypting message", e);
}
return;
default :
break;
}
passUp(evt);
return;
}
private synchronized void handleViewChange(View view){
// if view is a bit broken set me as keyserver
if (view.getMembers() == null || view.getMembers().get(0) == null){
becomeKeyServer(local_addr);
return;
}
// otherwise get keyserver from view controller
Address tmpKeyServer = (Address)view.getMembers().get(0);
//I am new keyserver - either first member of group or old key server is no more and
// I have been voted new controller
if (tmpKeyServer.equals(local_addr) && (keyServerAddr == null || (! tmpKeyServer.equals(keyServerAddr)))){
becomeKeyServer(tmpKeyServer);
// a new keyserver has been set and it is not me
}else if (keyServerAddr == null || (! tmpKeyServer.equals(keyServerAddr))){
handleNewKeyServer(tmpKeyServer);
} else{
if (log.isDebugEnabled())
log.debug("Membership has changed but I do not care");
}
}
/**
* Handles becoming server - resetting queue settings
* and setting keyserver address to be local address.
*
* @param tmpKeyServer
*/
private void becomeKeyServer(Address tmpKeyServer){
keyServerAddr = tmpKeyServer;
// keyServer =true; GemStoneAddition
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_I_HAVE_BECOME_KEY_SERVER__0, keyServerAddr);
queue_down = false;
queue_up = false;
}
/**
* Sets up the peer for a new keyserver - this is
* setting queueing to buffer messages until we have a new
* secret key from the key server and sending a key request
* to the new keyserver.
*
* @param newKeyServer
*/
private void handleNewKeyServer(Address newKeyServer){
// start queueing until we have new key
// to make sure we are not sending with old key
queue_up =true;
queue_down = true;
// set new keyserver address
keyServerAddr = newKeyServer;
// keyServer =false; GemStoneAddition
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_SENDING_KEY_REQUEST);
// create a key request message
sendKeyRequest();
}
/**
* @param evt
*/
private void handleUpMessage(Event evt) throws Exception
{
Message msg = (Message) evt.getArg();
if (msg == null)
{
if (trace)
log.trace("null message - passing straight up");
passUp(evt);
return;
}
Object obj = msg.getHeader(EncryptHeader.KEY);
// try and get the encryption header
if (obj == null || !(obj instanceof EncryptHeader))
{
if (log.isTraceEnabled())
log.trace("dropping message as ENCRYPT header is null or has not been recognized, msg will not be passed up, " +
"headers are " + msg.getHeaders());
return;
}
EncryptHeader hdr = (EncryptHeader) obj;
if (trace)
log.trace("header received " + hdr);
// if a normal message try and decrypt it
if (hdr.getType() == EncryptHeader.ENCRYPT)
{
// if queueing then pass into queue to be dealt with later
if (queue_up){
if (trace)
log.trace("queueing up message as no session key established: " + evt.getArg());
upMessageQueue.put(evt);
} else{
// make sure we pass up any queued messages first
// could be more optimised but this can wait
// we only need this if not using supplied key
if (!suppliedKey) {
drainUpQueue();
}
// try and decrypt the message
Message tmpMsg =decryptMessage(symDecodingCipher, msg);
if (tmpMsg != null){
if(trace)
log.trace("decrypted message " + tmpMsg);
passUp(evt);
} else {
log.warn("Unrecognised cipher discarding message");
}
}
} else
{
// check if we had some sort of encrypt control
// header if using supplied key we should not
// process it
if (suppliedKey)
{
if (log.isWarnEnabled())
{
log.warn("We received an encrypt header of "
+ hdr.getType() + " while in configured mode");
}
} else{
// see what sort of encrypt control message we
// have received
switch (hdr.getType()){
// if a key request
case EncryptHeader.KEY_REQUEST:
if (log.isInfoEnabled()) {
log.info(ExternalStrings.ENCRYPT_RECEIVED_A_KEY_REQUEST_FROM_PEER);
}
//if a key request send response key back
try {
// extract peer's public key
PublicKey tmpKey = generatePubKey(msg.getBuffer());
// send back the secret key we have
sendSecretKey(getSecretKey(), tmpKey, msg.getSrc());
} catch (Exception e){
log.warn("unable to reconstitute peer's public key");
}
break;
case EncryptHeader.SECRETKEY:
if (log.isInfoEnabled()) {
log.info(ExternalStrings.ENCRYPT_RECEIVED_A_SECRETKEY_RESPONSE_FROM_KEYSERVER);
}
try {
SecretKey tmp = decodeKey(msg.getBuffer());
if (tmp == null) {
// unable to understand response
// lets try again
sendKeyRequest();
}else{
// otherwise lets set the reurned key
// as the shared key
setKeys(tmp, hdr.getVersion());
if (log.isInfoEnabled()) {
log.info(ExternalStrings.ENCRYPT_DECODED_SECRETKEY_RESPONSE);
}
}
} catch (Exception e){
log.warn("unable to process received public key");
}
break;
default:
log.warn("Received ignored encrypt header of "+hdr.getType());
break;
}
}
}
}
/**
* used to drain the up queue - synchronized so we
* can call it safely despite access from potentially two threads at once
* @throws QueueClosedException
* @throws Exception
*/
private void drainUpQueue() throws QueueClosedException, Exception
{
//we do not synchronize here as we only have one up thread so we should never get an issue
//synchronized(upLock){
Event tmp =null;
while ((tmp = (Event)upMessageQueue.poll(0L)) != null){
// if (tmp != null) GemStoneAddition (cannot be null)
{
Message msg = decryptMessage(symDecodingCipher, (Message)tmp.getArg());
if (msg != null){
if (trace){
log.trace("passing up message from drain " + msg);
}
passUp(tmp);
}else{
log.warn("discarding message in queue up drain as cannot decode it");
}
}
}
}
/**
* Sets the keys for the app. and drains the queues - the drains could
* be called att he same time as the up/down messages calling in to
* the class so we may have an extra call to the drain methods but this slight expense
* is better than the alternative of waiting until the next message to
* trigger the drains which may never happen.
*
* @param key
* @param version
* @throws Exception
*/
private void setKeys(SecretKey key, String version) throws Exception{
// put the previous key into the map
// if the keys are already there then they will overwrite
keyMap.put(getSymVersion(), getSymDecodingCipher());
setSecretKey(key);
initSymCiphers(key.getAlgorithm(),key );
setSymVersion(version);
// drain the up queue
log.info(ExternalStrings.ENCRYPT_SETTING_QUEUE_UP_TO_FALSE_IN_SETKEYS);
queue_up =false;
drainUpQueue();
queue_down =false;
drainDownQueue();
}
/**
* Does the actual work for decrypting - if version does not match current cipher
* then tries to use previous cipher
* @param cipher
* @param msg
* @return Message
* @throws Exception
*/
private Message decryptMessage(Cipher cipher, Message msg) throws Exception
{
EncryptHeader hdr = (EncryptHeader)msg.getHeader(EncryptHeader.KEY);
//System.out.println("my version = " + getSymVersion());
//System.out.println("header version = " + hdr.getVersion());
if (!hdr.getVersion().equals(getSymVersion())){
log.warn("attempting to use stored cipher as message does not uses current encryption version ");
Cipher temp = (Cipher)keyMap.get(hdr.getVersion());
if (temp == null) {
log.warn("Unable to find a matching cipher in previous key map");
return null;
} else{
if(trace)
log.trace("decrypting using previous cipher version "+ hdr.getVersion());
msg.setBuffer(temp.doFinal(msg.getBuffer()));
return msg;
}
} else {
// reset buffer with decrypted message
msg.setBuffer(cipher.doFinal(msg.getBuffer()));
return msg;
}
}
/**
* @param secret
* @param pubKey
* @throws InvalidKeyException
* @throws IllegalStateException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private void sendSecretKey(SecretKey secret, PublicKey pubKey, Address source)
throws InvalidKeyException, IllegalStateException,
IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException,
NoSuchAlgorithmException
{
Message newMsg;
if (log.isDebugEnabled())
log.debug("encoding shared key ");
// create a cipher with peer's public key
Cipher tmp = Cipher.getInstance(asymAlgorithm);
tmp.init(Cipher.ENCRYPT_MODE, pubKey);
//encrypt current secret key
byte[] encryptedKey = tmp.doFinal(secret.getEncoded());
//SW logout encrypted bytes we are sending so we
// can match the clients log to see if they match
if (log.isDebugEnabled())
log.debug(" Generated encoded key which only client can decode:"
+ formatArray(encryptedKey));
newMsg = new Message(source, local_addr, encryptedKey);
newMsg.putHeader(EncryptHeader.KEY, new EncryptHeader(
EncryptHeader.SECRETKEY, getSymVersion()));
if (log.isDebugEnabled())
log.debug(" Sending version " + getSymVersion()
+ " encoded key to client");
passDown(new Event(Event.MSG, newMsg));
}
/**
* @param msg
* @return
*/
// private PublicKey handleKeyRequest(Message msg)
// {
// Message newMsg;
// if (log.isDebugEnabled())
// log.debug("Request for key received"); // GemStoneAddition
//
// //SW log the clients encoded public key so we can
// // see if they match
// if (log.isDebugEnabled())
// log.debug("Got peer's encoded public key:"
// + formatArray(msg.getBuffer()));
//
// PublicKey pubKey = generatePubKey(msg.getBuffer());
//
// //SW log the clients resulting public key so we can
// // see if it is created correctly
// if (log.isDebugEnabled())
// log.debug("Generated requestors public key" + pubKey);
//
// /*
// * SW why do we send this as the client does not use it ? - although we
// * could make use to provide some authentication later on rahter than
// * just encryption send server's publicKey
// */
// newMsg = new Message(msg.getSrc(), local_addr, Kpair.getPublic()
// .getEncoded());
//
// //SW Log out our public key in encoded format so we
// // can match with the client debugging to
// // see if they match
// if (log.isInfoEnabled())
// log.debug("encoded key is "
// + formatArray(Kpair.getPublic().getEncoded()));
//
//
// newMsg.putHeader(EncryptHeader.KEY, new EncryptHeader(
// EncryptHeader.SERVER_PUBKEY, getSymVersion()));
//
//
// passDown(new Event(Event.MSG, newMsg));
// return pubKey;
// }
/**
* @return Message
*/
private Message sendKeyRequest()
{
// send client's public key to server and request
// server's public key
Message newMsg = new Message(keyServerAddr, local_addr, Kpair.getPublic()
.getEncoded());
newMsg.putHeader(EncryptHeader.KEY, new EncryptHeader(
EncryptHeader.KEY_REQUEST, getSymVersion()));
passDown(new Event(Event.MSG, newMsg));
return newMsg;
}
/* (non-Javadoc)
* @see org.jgroups.stack.Protocol#down(org.jgroups.Event)
*/
@Override // GemStoneAddition
public void down(Event evt)
{
switch (evt.getType()) {
case Event.MSG :
try
{
if (queue_down){
if(trace)
log.trace("queueing down message as no session key established" + evt.getArg());
downMessageQueue.put(evt); // queue messages if we are waiting for a new key
} else{
handleDownEvent(evt);
}
} catch (Exception e)
{
log.warn("unable to send down event " + e);
}
return;
case Event.VIEW_CHANGE:
View view=(View)evt.getArg();
if (log.isInfoEnabled())
log.info(ExternalStrings.ENCRYPT_HANDLING_VIEW__0, view);
if (!suppliedKey){
handleViewChange(view);
}
break;
default :
break;
}
passDown(evt);
}
/**
* handle method for down mesages
* @param evt
*/
private void handleDownEvent(Event evt) throws Exception
{
// make sure the down queue is drained first to keep ordering
if (!suppliedKey){
drainDownQueue();
}
sendDown(evt);
}
/**
* @throws Exception
* @throws QueueClosedException
*/
private void drainDownQueue() throws Exception, QueueClosedException
{
// we do not synchronize here as we only have one down thread so we should never get an issue
// first lets replay any oustanding events
Event tmp =null;
while((tmp = (Event)downMessageQueue.poll(0L) )!= null){
sendDown(tmp);
}
}
/**
* @param evt
* @throws Exception
*/
private void sendDown(Event evt) throws Exception {
if (evt.getType() != Event.MSG) {
return;
}
final Message msg = (Message) evt.getArg();
// put our encrypt header on the message
msg.putHeader(EncryptHeader.KEY, new EncryptHeader(EncryptHeader.ENCRYPT, getSymVersion()));
if (msg.getBuffer() != null) {
// copy neeeded because same message (object) may be retransimted -> no double encryption
Message msgEncrypted = msg.copy(false);
msgEncrypted.setBuffer(encryptMessage(symEncodingCipher, msg.getBuffer()));
passDown(new Event(Event.MSG, msgEncrypted));
return;
}
if (trace)
log.trace("buffer is null not encrypting ");
passDown(evt);
}
/**
* @param cipher
* @param plain
*/
private byte[] encryptMessage(Cipher cipher, byte[] plain) throws Exception
{
byte[] cypherText = cipher.doFinal(plain);
return cypherText;
}
private SecretKeySpec decodeKey(byte[] encodedKey) throws Exception
{
// try and decode secrey key sent from keyserver
byte[] keyBytes = asymCipher.doFinal(encodedKey);
SecretKeySpec keySpec = null;
try
{
keySpec = new SecretKeySpec(keyBytes, getAlgorithm(symAlgorithm));
// test reconstituted key to see if valid
Cipher temp = Cipher.getInstance(symAlgorithm);
temp.init(Cipher.SECRET_KEY, keySpec);
} catch (Exception e)
{
log.fatal(e);
keySpec = null;
}
return keySpec;
}
/**
* used to reconstitute public key sent in byte form from peer
* @param encodedKey
* @return PublicKey
*/
private PublicKey generatePubKey(byte[] encodedKey)
{
PublicKey pubKey = null;
try
{
KeyFactory KeyFac = KeyFactory
.getInstance(getAlgorithm(asymAlgorithm));
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(encodedKey);
pubKey = KeyFac.generatePublic(x509KeySpec);
} catch (Exception e)
{
e.printStackTrace();
}
return pubKey;
}
/*
* simple helper method so we can see the format of the byte arrays in a
* readable form could be better to use Base64 but will do for now
*/
private String formatArray(byte[] array)
{
StringBuffer buf = new StringBuffer();
for (int i = 0; i < array.length; i++)
{
buf.append(Integer.toHexString(array[i]));
}
return buf.toString();
}
/**
* @return Returns the asymInit.
*/
protected int getAsymInit()
{
return asymInit;
}
/**
* @return Returns the asymProvider.
*/
protected String getAsymProvider()
{
return asymProvider;
}
/**
* @return Returns the desKey.
*/
protected SecretKey getDesKey()
{
return secretKey;
}
/**
* @return Returns the kpair.
*/
protected KeyPair getKpair()
{
return Kpair;
}
/**
* @return Returns the asymCipher.
*/
protected Cipher getAsymCipher()
{
return asymCipher;
}
/**
* @return Returns the serverPubKey.
*/
protected PublicKey getServerPubKey()
{
return serverPubKey;
}
/**
* @return Returns the symAlgorithm.
*/
protected String getSymAlgorithm()
{
return symAlgorithm;
}
/**
* @return Returns the symInit.
*/
protected int getSymInit()
{
return symInit;
}
/**
* @return Returns the symProvider.
*/
protected String getSymProvider()
{
return symProvider;
}
/**
* @return Returns the asymAlgorithm.
*/
protected String getAsymAlgorithm()
{
return asymAlgorithm;
}
/**
* @return Returns the symVersion.
*/
private String getSymVersion()
{
return symVersion;
}
/**
* @param symVersion
* The symVersion to set.
*/
private void setSymVersion(String symVersion)
{
this.symVersion = symVersion;
}
/**
* @return Returns the secretKey.
*/
private SecretKey getSecretKey()
{
return secretKey;
}
/**
* @param secretKey The secretKey to set.
*/
private void setSecretKey(SecretKey secretKey)
{
this.secretKey = secretKey;
}
/**
* @return Returns the keyStoreName.
*/
protected String getKeyStoreName()
{
return keyStoreName;
}
/**
* @return Returns the symDecodingCipher.
*/
protected Cipher getSymDecodingCipher()
{
return symDecodingCipher;
}
/**
* @return Returns the symEncodingCipher.
*/
protected Cipher getSymEncodingCipher()
{
return symEncodingCipher;
}
/**
* @return Returns the local_addr.
*/
protected Address getLocal_addr()
{
return local_addr;
}
/**
* @param local_addr The local_addr to set.
*/
protected void setLocal_addr(Address local_addr)
{
this.local_addr = local_addr;
}
/**
* @return Returns the keyServerAddr.
*/
protected Address getKeyServerAddr()
{
return keyServerAddr;
}
/**
* @param keyServerAddr The keyServerAddr to set.
*/
protected void setKeyServerAddr(Address keyServerAddr)
{
this.keyServerAddr = keyServerAddr;
}
}