All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgroups.protocols.ENCRYPT Maven / Gradle / Ivy

There is a newer version: 9.1.7.Final
Show newest version
package org.jgroups.protocols;

import org.jgroups.*;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.Property;
import org.jgroups.stack.Protocol;
import org.jgroups.util.AsciiString;
import org.jgroups.util.Buffer;
import org.jgroups.util.MessageBatch;
import org.jgroups.util.Util;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ENCRYPT layer. Encrypt and decrypt communication in JGroups
 * 
 * This class 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 placed above the GMS protocol in the configuration. 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 JGroups 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 showing the keystore version can be found in * the conf in a 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 JGroups 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 * @author Bela Ban */ @MBean(description="Protocol which encrypts and decrypts cluster traffic") public class ENCRYPT extends Protocol { private static final String DEFAULT_SYM_ALGO="AES"; Address local_addr; Address keyServerAddr; boolean keyServer=false; //used to see whether we are the key server /* ----------------------------------------- Properties -------------------------------------------------- */ // encryption properties in no supplied key mode @Property(name="asym_provider", description="Cryptographic Service Provider. Default is Bouncy Castle Provider") String asymProvider=null; @Property(name="sym_provider", description="Cryptographic Service Provider. Default is Bouncy Castle Provider") String symProvider=null; @Property(name="asym_algorithm", description="Cipher engine transformation for asymmetric algorithm. Default is RSA") protected String asymAlgorithm="RSA"; @Property(name="sym_algorithm", description="Cipher engine transformation for symmetric algorithm. Default is AES") String symAlgorithm=DEFAULT_SYM_ALGO; @Property(name="asym_init", description="Initial public/private key length. Default is 512") int asymInit=512; @Property(name="sym_init", description="Initial key length for matching symmetric algorithm. Default is 128") int symInit=128; @Property(name="change_keys", description="Generate new symmetric keys on every view change. Default is false") boolean changeKeysOnViewChange=false; // properties for functioning in supplied key mode private boolean suppliedKey=false; @Property(name="key_store_name", description="File on classpath that contains keystore repository") String keyStoreName; @Property(name="store_password", description="Password used to check the integrity/unlock the keystore. Change the default", exposeAsManagedAttribute=false) private String storePassword="changeit"; //JDK default @Property(name="key_password", description="Password for recovering the key. Change the default", exposeAsManagedAttribute=false) private String keyPassword=null; // allows to assign keypwd=storepwd if not set (https://issues.jboss.org/browse/JGRP-1375) @Property(name="alias", description="Alias used for recovering the key. Change the default",exposeAsManagedAttribute=false) private String alias="mykey"; // JDK default @Property(description="Number of ciphers in the pool to parallelize encrypt and decrypt requests",writable=false) protected int cipher_pool_size=8; // public/private Key KeyPair Kpair; // to store own's public/private Key // for client to store server's public Key PublicKey serverPubKey=null; // Cipher pools used for encryption and decryption. Size is cipher_pool_size protected Cipher[] encoding_ciphers, decoding_ciphers; // Locks to synchronize access to the cipher pools protected Lock[] encoding_locks, decoding_locks; protected final AtomicInteger cipher_index=new AtomicInteger(0); // the cipher and lock to select // version filed for secret key protected byte[] symVersion; // dhared secret key to encrypt/decrypt messages protected SecretKey secretKey; // 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 obtaining 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 BlockingQueue upMessageQueue=new LinkedBlockingQueue(); // queue to hold downcoming messages while key negotiation is happening private BlockingQueue downMessageQueue=new LinkedBlockingQueue(); // decrypting cypher for secret key requests private Cipher asymCipher; /** determines whether to encrypt the entire message, or just the buffer */ @Property private boolean encrypt_entire_message=false; protected int getNextIndex() { // same as mod, but (apparently, I'm told) more efficient. Size needs to be a power ot 2 int current_index=cipher_index.getAndIncrement(); return current_index & (cipher_pool_size-1); } public int getAsymInit() {return asymInit;} public SecretKey getDesKey() {return secretKey;} public KeyPair getKpair() {return Kpair;} public Cipher getAsymCipher() {return asymCipher;} public String getSymAlgorithm() {return symAlgorithm;} public int getSymInit() {return symInit;} public String getAsymAlgorithm() {return asymAlgorithm;} public byte[] getSymVersion() {return symVersion;} public SecretKey getSecretKey() {return secretKey;} public Cipher getSymDecodingCipher() {return decoding_ciphers[getNextIndex()];} public Cipher getSymEncodingCipher() {return encoding_ciphers[getNextIndex()];} public Address getKeyServerAddr() {return keyServerAddr;} private void setSymVersion(byte[] symVersion) {this.symVersion=Arrays.copyOf(symVersion, symVersion.length);} private void setSecretKey(SecretKey secretKey) {this.secretKey=secretKey;} protected void setLocalAddress(Address local_addr) {this.local_addr=local_addr;} protected void setKeyServerAddr(Address keyServerAddr) {this.keyServerAddr=keyServerAddr;} /* * GetAlgorithm: Get the algorithm name from "algorithm/mode/padding" * taken m original ENCRYPT file */ private static String getAlgorithm(String s) { int index=s.indexOf("/"); if(index == -1) return s; return s.substring(0, index); } public void init() throws Exception { if(keyPassword == null && storePassword != null) { keyPassword=storePassword; log.debug("key_password used is same as store_password"); } if(keyStoreName == null) { initSymKey(); initKeyPair(); } else initConfiguredKey(); if(cipher_pool_size <= 0) { log.warn("cipher_pool_size of %d is invalid; setting it to 1", cipher_pool_size); cipher_pool_size=1; } int tmp=Util.getNextHigherPowerOfTwo(cipher_pool_size); if(tmp != cipher_pool_size) { log.warn("setting cipher_pool_size (%d) to %d (power of 2) for faster modulo operation", cipher_pool_size, tmp); cipher_pool_size=tmp; } encoding_ciphers=new Cipher[cipher_pool_size]; encoding_locks=new Lock[cipher_pool_size]; decoding_ciphers=new Cipher[cipher_pool_size]; decoding_locks=new Lock[cipher_pool_size]; 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 Exception { 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=Thread.currentThread() .getContextClassLoader() .getResourceAsStream(keyStoreName); if(inputStream == null) inputStream=new FileInputStream(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=queue_up=false; } finally { Util.close(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().isEmpty()) 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); log.debug("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 { log.debug("initializing symmetric ciphers (pool size=%d)",cipher_pool_size); for(int i=0; i < cipher_pool_size; i++) { encoding_ciphers[i]=symProvider != null && !symProvider.trim().isEmpty()? Cipher.getInstance(algorithm, symProvider) : Cipher.getInstance(algorithm); encoding_ciphers[i].init(Cipher.ENCRYPT_MODE, secret); decoding_ciphers[i]=symProvider != null && !symProvider.trim().isEmpty()? Cipher.getInstance(algorithm, symProvider) : Cipher.getInstance(algorithm); decoding_ciphers[i].init(Cipher.DECRYPT_MODE, secret); encoding_locks[i]=new ReentrantLock(); decoding_locks[i]=new ReentrantLock(); } //set the version MessageDigest digest=MessageDigest.getInstance("MD5"); digest.reset(); digest.update(secret.getEncoded()); byte[] tmp=digest.digest(); symVersion=Arrays.copyOf(tmp, tmp.length); // symVersion = byteArrayToHexString(digest.digest()); log.debug("initialized symmetric ciphers with secret key (" + symVersion.length + " bytes)"); } /* public static String byteArrayToHexString(byte[] b){ StringBuilder sb = new StringBuilder(b.length * 2); for (int i = 0; i < b.length; i++){ int v = b[i] & 0xff; if (v < 16) { sb.append('0'); } sb.append(Integer.toHexString(v)); } return sb.toString().toUpperCase(); }*/ /** * 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().isEmpty()) 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 if(asymProvider != null && !asymProvider.trim().isEmpty()) asymCipher=Cipher.getInstance(asymAlgorithm, asymProvider); else asymCipher=Cipher.getInstance(asymAlgorithm); asymCipher.init(Cipher.DECRYPT_MODE,Kpair.getPrivate()); log.debug("asym algo initialized"); } public Object up(Event evt) { switch(evt.getType()) { case Event.VIEW_CHANGE: View view=(View)evt.getArg(); log.debug("new view: " + view); if(!suppliedKey) handleViewChange(view, false); break; case Event.TMP_VIEW: view=(View)evt.getArg(); if(!suppliedKey) handleViewChange(view, true); break; // we try and decrypt all messages case Event.MSG: try { return handleUpMessage(evt); } catch(Exception e) { log.warn("exception occurred decrypting message", e); } return null; } return up_prot.up(evt); } public void up(MessageBatch batch) { Decrypter decrypter=new Decrypter(); batch.map(decrypter); decrypter.unlock(); if(!batch.isEmpty()) up_prot.up(batch); } private synchronized void handleViewChange(View view, boolean makeServer) { if(makeServer) initializeNewSymmetricKey(); // if view is a bit broken set me as keyserver List

members = view.getMembers(); if (members == null || members.isEmpty() || members.get(0) == null) { becomeKeyServer(local_addr, false); return; } // otherwise get keyserver from view controller Address tmpKeyServer=view.getMembers().get(0); //I am keyserver - either first member of group or old key server is no more and // I have been voted new controller if(makeServer || (tmpKeyServer.equals(local_addr))) becomeKeyServer(tmpKeyServer, makeServer); else handleNewKeyServer(tmpKeyServer); } private void initializeNewSymmetricKey() { try { if ( changeKeysOnViewChange || !keyServer) { log.debug("initalizing new ciphers"); initSymKey(); initSymCiphers(getSymAlgorithm(), getSecretKey()); } } catch (Exception e) { log.error("could not initialize new ciphers", e); if ( e instanceof RuntimeException) { throw (RuntimeException)e; } else { throw new IllegalStateException(e); } } } /** * Handles becoming server - resetting queue settings and setting keyserver * address to be local address. * * @param tmpKeyServer */ private void becomeKeyServer(Address tmpKeyServer, boolean forced) { keyServerAddr=tmpKeyServer; keyServer=true; if(log.isDebugEnabled() && !forced) log.debug("%s: I have become the new key server", local_addr); 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) { if ( changeKeysOnViewChange || keyServerChanged(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; log.debug("%s: %s has become the new key server, sending key request to it", local_addr, keyServerAddr); sendKeyRequest(); } } private boolean keyServerChanged(Address newKeyServer) { return keyServerAddr == null || !keyServerAddr.equals(newKeyServer); } private Object handleUpMessage(Event evt) throws Exception { Message msg=(Message)evt.getArg(); EncryptHeader hdr; if(msg == null || (msg.getLength() == 0 && !encrypt_entire_message) || ((hdr=(EncryptHeader)msg.getHeader(this.id)) == null)) return up_prot.up(evt); if(log.isTraceEnabled()) log.trace("header received %s", hdr); switch(hdr.getType()) { case EncryptHeader.ENCRYPT: return handleEncryptedMessage(msg, evt, hdr); default: handleUpEvent(msg,hdr); return null; } } protected Object handleEncryptedMessage(Message msg, Event evt, EncryptHeader hdr) throws Exception { // if queueing then pass into queue to be dealt with later if(queue_up) { log.trace("queueing up message as no session key established: %s", msg); upMessageQueue.put(msg); return null; } // 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 - we need to copy msg as we modify its // buffer (http://jira.jboss.com/jira/browse/JGRP-538) Message tmpMsg=decryptMessage(null, msg.copy()); // need to copy for possible xmits if(tmpMsg != null) return up_prot.up(new Event(Event.MSG, tmpMsg)); log.warn("unrecognised cipher; discarding message"); return null; } protected void handleUpEvent(Message msg, EncryptHeader hdr) { // check if we had some sort of encrypt control header if using supplied key we should not process it if(suppliedKey) { log.warn("we received an encrypt header of %s while in configured mode",hdr.getType()); return; } // see what sort of encrypt control message we have received switch(hdr.getType()) { // if a key request case EncryptHeader.KEY_REQUEST: log.debug("received a key request from peer %s", msg.getSrc()); // 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: log.debug("received a secretkey response from keyserver %s", msg.getSrc()); try { SecretKey tmp=decodeKey(msg.getBuffer()); if(tmp == null) sendKeyRequest(); // unable to understand response, let's try again else { // otherwise lets set the returned key as the shared key setKeys(tmp, hdr.getVersion()); log.debug("decoded secretkey response"); } } catch(Exception e) { log.warn("unable to process received public key", e); } break; default: log.warn("received ignored encrypt header of %s", hdr.getType()); break; } } /** * used to drain the up queue - synchronized so we can call it safely * despite access from potentially two threads at once */ private void drainUpQueue() { if(log.isTraceEnabled()) { int size=upMessageQueue.size(); if(size > 0) log.trace("draining %d messages from the up queue", size); } while(true) { try { Message tmp=upMessageQueue.poll(0L, TimeUnit.MILLISECONDS); if(tmp == null) break; Message msg=decryptMessage(null, tmp.copy()); // need to copy for possible xmits if(msg != null) up_prot.up(new Event(Event.MSG, msg)); } catch(Throwable t) { log.error("failed decrypting and sending message up when draining queue", t); } } } private void drainDownQueue() { if(log.isTraceEnabled()) { int size=downMessageQueue.size(); if(size > 0) log.trace("draining %d messages from the down queue", size); } while(true) { try { Message tmp=downMessageQueue.poll(0L, TimeUnit.MILLISECONDS); if(tmp == null) break; encryptAndSend(tmp); } catch(Throwable t) { log.error("failed sending message down when draining queue", t); } } } /** * 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, byte[] version) throws Exception { // put the previous key into the map // if the keys are already there then they will overwrite keyMap.put(new AsciiString(getSymVersion()), getSymDecodingCipher()); setSecretKey(key); initSymCiphers(key.getAlgorithm(), key); setSymVersion(version); // drain the up queue log.debug("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 the previous cipher */ private Message decryptMessage(Cipher cipher, Message msg) throws Exception { EncryptHeader hdr=(EncryptHeader)msg.getHeader(this.id); if(!Arrays.equals(hdr.getVersion(),getSymVersion())) { log.warn("attempting to use stored cipher as message does not use current encryption version "); cipher=keyMap.get(new AsciiString(hdr.getVersion())); if(cipher == null) { log.warn("unable to find a matching cipher in previous key map"); return null; } log.trace("decrypting using previous cipher version"); synchronized(cipher) { return _decrypt(cipher, msg, hdr.encryptEntireMessage()); } } return _decrypt(cipher, msg, hdr.encryptEntireMessage()); } private Message _decrypt(final Cipher cipher, Message msg, boolean decrypt_entire_msg) throws Exception { byte[] decrypted_msg; if(cipher == null) decrypted_msg=code(msg.getRawBuffer(), msg.getOffset(), msg.getLength(), true); else decrypted_msg=cipher.doFinal(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); if(!decrypt_entire_msg) { msg.setBuffer(decrypted_msg); return msg; } Message ret=Util.streamableFromBuffer(Message.class,decrypted_msg,0,decrypted_msg.length); if(ret.getDest() == null) ret.setDest(msg.getDest()); if(ret.getSrc() == null) ret.setSrc(msg.getSrc()); return ret; } private void sendSecretKey(SecretKey secret, PublicKey pubKey, Address source) throws Exception { // create a cipher with peer's public key Cipher tmp; if (asymProvider != null && !asymProvider.trim().isEmpty()) tmp=Cipher.getInstance(asymAlgorithm, asymProvider); else tmp=Cipher.getInstance(asymAlgorithm); tmp.init(Cipher.ENCRYPT_MODE,pubKey); //encrypt current secret key byte[] encryptedKey=tmp.doFinal(secret.getEncoded()); Message newMsg=new Message(source, local_addr, encryptedKey) .putHeader(this.id, new EncryptHeader(EncryptHeader.SECRETKEY, getSymVersion())); log.debug("sending version %s encoded key to client", new String(getSymVersion())); down_prot.down(new Event(Event.MSG,newMsg)); } /** send client's public key to server and request server's public key */ private void sendKeyRequest() { Message newMsg=new Message(keyServerAddr, local_addr, Kpair.getPublic().getEncoded()) .putHeader(this.id,new EncryptHeader(EncryptHeader.KEY_REQUEST,getSymVersion())); down_prot.down(new Event(Event.MSG,newMsg)); } public Object down(Event evt) { switch(evt.getType()) { case Event.MSG: Message msg=(Message)evt.getArg(); if(msg.getLength() == 0 && !encrypt_entire_message) break; try { if(queue_down) { log.trace("queueing down message as no session key established: %s", msg); downMessageQueue.put(msg); // queue messages if we are waiting for a new key } else { // make sure the down queue is drained first to keep ordering if(!suppliedKey) drainDownQueue(); encryptAndSend(msg); } } catch(Exception e) { log.warn("unable to send message down", e); } return null; case Event.VIEW_CHANGE: View view=(View)evt.getArg(); log.debug("new view: " + view); if(!suppliedKey) handleViewChange(view, false); break; case Event.SET_LOCAL_ADDRESS: local_addr=(Address)evt.getArg(); log.debug("set local address to %s", local_addr); break; case Event.TMP_VIEW: view=(View)evt.getArg(); if(!suppliedKey) { // if a tmp_view then we are trying to become coordinator so // make us keyserver handleViewChange(view, true); } break; } return down_prot.down(evt); } private void encryptAndSend(Message msg) throws Exception { EncryptHeader hdr=new EncryptHeader(EncryptHeader.ENCRYPT, getSymVersion()); if(this.encrypt_entire_message) hdr.type|=EncryptHeader.ENCRYPT_ENTIRE_MSG; if(encrypt_entire_message) { if(msg.getSrc() == null) msg.setSrc(local_addr); Buffer serialized_msg=Util.streamableToBuffer(msg); byte[] encrypted_msg=code(serialized_msg.getBuf(),serialized_msg.getOffset(),serialized_msg.getLength(),false); // exclude existing headers, they will be seen again when we decrypt and unmarshal the msg at the receiver Message tmp=msg.copy(false, false).setBuffer(encrypted_msg).putHeader(this.id,hdr); down_prot.down(new Event(Event.MSG, tmp)); return; } // copy neeeded because same message (object) may be retransmitted -> no double encryption Message msgEncrypted=msg.copy(false).putHeader(this.id, hdr) .setBuffer(code(msg.getRawBuffer(),msg.getOffset(),msg.getLength(),false)); down_prot.down(new Event(Event.MSG,msgEncrypted)); } private byte[] code(byte[] buf, int offset, int length, boolean decode) throws Exception { int index=getNextIndex(); Lock lock=decode? decoding_locks[index] : encoding_locks[index]; Cipher cipher=decode? decoding_ciphers[index] : encoding_ciphers[index]; lock.lock(); try { return cipher.doFinal(buf, offset, length); } finally { lock.unlock(); } } // try and decode secrey key sent from keyserver private SecretKeySpec decodeKey(byte[] encodedKey) throws Exception { byte[] keyBytes; synchronized(this) { keyBytes=asymCipher.doFinal(encodedKey); } try { SecretKeySpec keySpec=new SecretKeySpec(keyBytes, getAlgorithm(symAlgorithm)); // test reconstituted key to see if valid Cipher temp; if (symProvider != null && !symProvider.trim().isEmpty()) temp=Cipher.getInstance(symAlgorithm, symProvider); else temp=Cipher.getInstance(symAlgorithm); temp.init(Cipher.SECRET_KEY, keySpec); return keySpec; } catch(Exception e) { log.error("failed decoding key", e); return null; } } /** * 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; } /** Decrypts all messages in a batch, replacing encrypted messages in-place with their decrypted versions */ protected class Decrypter implements MessageBatch.Visitor { protected Lock lock; protected Cipher cipher; public Message visit(Message msg, MessageBatch batch) { EncryptHeader hdr; if(msg == null || (msg.getLength() == 0 && !encrypt_entire_message) || ((hdr=(EncryptHeader)msg.getHeader(id)) == null)) return null; if(hdr.getType() == EncryptHeader.ENCRYPT) { // if queueing then pass into queue to be dealt with later if(queue_up) { queueUpMessage(msg, batch); return null; } // make sure we pass up any queued messages first if(!suppliedKey) drainUpQueue(); if(lock == null) { int index=getNextIndex(); lock=decoding_locks[index]; cipher=decoding_ciphers[index]; lock.lock(); } try { Message tmpMsg=decryptMessage(cipher, msg.copy()); // need to copy for possible xmits if(tmpMsg != null) batch.replace(msg, tmpMsg); } catch(Exception e) { log.error("failed decrypting message from %s (offset=%d, length=%d, buf.length=%d): %s, headers are %s", msg.getSrc(), msg.getOffset(), msg.getLength(), msg.getRawBuffer().length, e, msg.printHeaders()); } } else { batch.remove(msg); // a control message will get handled by ENCRYPT and should not be passed up handleUpEvent(msg, hdr); } return null; } protected void unlock() { if(lock != null) { lock.unlock(); lock=null; } } protected void queueUpMessage(Message msg, MessageBatch batch) { log.trace("queueing up message as no session key established: " + msg); try { upMessageQueue.put(msg); batch.remove(msg); } catch(InterruptedException e) { } } } public static class EncryptHeader extends org.jgroups.Header { public static final byte ENCRYPT = 1 << 0; public static final byte KEY_REQUEST = 1 << 1; public static final byte SECRETKEY = 1 << 2; public static final byte ENCRYPT_ENTIRE_MSG = 1 << 3; private byte type; protected byte[] version; public EncryptHeader() {} public EncryptHeader(byte type, byte[] version) { this.type=type; this.version=version; if(version == null) throw new IllegalArgumentException("version must be defined"); } public byte getType() { return (byte)(type & ~ENCRYPT_ENTIRE_MSG); // clear the ENCRYPT_ENTIRE_MSG flag } /** * @return Returns the version. */ protected byte[] getVersion() { return version; } public boolean encryptEntireMessage() { return Util.isFlagSet(type, ENCRYPT_ENTIRE_MSG); } public void writeTo(DataOutput out) throws Exception { out.writeByte(type); out.writeShort(version.length); out.write(version); } public void readFrom(DataInput in) throws Exception { type=in.readByte(); short len=in.readShort(); version=new byte[len]; in.readFully(version); } public String toString() { return "[type=" + type + " version=\"" + (version != null? version.length + " bytes" : "n/a") + "\"]"; } public int size() { int retval=Global.BYTE_SIZE + Global.SHORT_SIZE; retval+=version.length; return retval; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy