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

org.jgroups.protocols.ASYM_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.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.util.AsciiString;
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.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * Encrypts and decrypts communication in JGroups by using a secret key distributed to all cluster members by the
 * key server (coordinator) using asymmetric (public/private key) encryption.
* * The secret key is identical for all cluster members and is used to encrypt messages when sending and decrypt them * when receiving messages. * * This protocol is typically placed under {@link org.jgroups.protocols.pbcast.NAKACK2}, so that most important * headers are encrypted as well, to prevent replay attacks.
* * The current keyserver (always the coordinator) generates a secret key. When a new member joins, it asks the keyserver * for the secret key. The keyserver encrypts the secret key with the joiner's public key and the joiner decrypts it with * its private key and then installs it and starts encrypting and decrypting messages with the secret key.
* * View changes that identify a new keyserver will result in a new secret key being generated and then distributed to * all cluster members. This overhead can be substantial in an application with a reasonable member churn.
* * This protocol is suited to an application that does not ship with a known key but instead it is generated and * distributed by the keyserver. * * Since messages can only get encrypted and decrypted when the secret key was received from the keyserver, messages * other then join and merge requests/responses are dropped when the secret key isn't yet available. Join and merge * requests / responses are handled by {@link AUTH}. * * @author Bela Ban * @author Steve Woodcock */ @MBean(description="Asymmetric encryption protocol. The secret key for encryption and decryption of messages is fetched " + "from a key server (the coordinator) via asymmetric encryption") public class ASYM_ENCRYPT extends Encrypt { protected static final short GMS_ID=ClassConfigurator.getProtocolId(GMS.class); @Property(description="When a member leaves the view, change the secret key, preventing old members from eavesdropping", writable=false) protected boolean change_key_on_leave=true; protected volatile Address key_server_addr; @ManagedAttribute(description="True if this member is the current key server, false otherwise") protected volatile boolean is_key_server; protected KeyPair key_pair; // to store own's public/private Key protected Cipher asym_cipher; // decrypting cypher for secret key requests // queue all up msgs until the secret key has been received/created @ManagedAttribute(description="whether or not to queue received messages (until the secret key was received)") protected volatile boolean queue_up_msgs=true; // queues a bounded number of messages received during a null secret key (or fetching the key from a new coord) protected final BlockingQueue up_queue=new ArrayBlockingQueue<>(100); protected volatile long last_key_request; public KeyPair keyPair() {return key_pair;} public Cipher asymCipher() {return asym_cipher;} public Address keyServerAddr() {return key_server_addr;} public ASYM_ENCRYPT keyServerAddr(Address key_srv) {this.key_server_addr=key_srv; return this;} @ManagedAttribute(description="Number of received messages currently queued") public int numQueuedMessages() {return up_queue.size();} @ManagedOperation(description="Triggers a request for the secret key to the current keyserver") public void sendKeyRequest() { if(key_server_addr == null) { log.debug("%s: key server is currently not set", local_addr); return; } sendKeyRequest(key_server_addr); } public void init() throws Exception { initKeyPair(); super.init(); } public void stop() { drainUpQueue(); super.stop(); } public Object down(Message msg) { if(skip(msg)) return down_prot.down(msg); return super.down(msg); } public Object up(Message msg) { if(skip(msg)) return up_prot.up(msg); return super.up(msg); } public void up(MessageBatch batch) { for(Message msg: batch) { if(skip(msg)) { try { up_prot.up(msg); batch.remove(msg); } catch(Throwable t) { log.error("failed passing up message from %s: %s, ex=%s", msg.src(), msg.printHeaders(), t); } } } if(!batch.isEmpty()) super.up(batch); // decrypt the rest of the messages in the batch (if any) } /** Checks if a message needs to be encrypted/decrypted. Join and merge requests/responses don't need to be * encrypted as they're authenticated by {@link AUTH} */ protected static boolean skip(Message msg) { GMS.GmsHeader hdr=msg.getHeader(GMS_ID); if(hdr == null) return false; switch(hdr.getType()) { case GMS.GmsHeader.JOIN_REQ: case GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER: case GMS.GmsHeader.JOIN_RSP: case GMS.GmsHeader.MERGE_REQ: case GMS.GmsHeader.MERGE_RSP: case GMS.GmsHeader.VIEW_ACK: case GMS.GmsHeader.INSTALL_MERGE_VIEW: return true; } return false; } @Override protected Object handleUpEvent(Message msg, EncryptHeader hdr) { switch(hdr.type()) { case EncryptHeader.SECRET_KEY_REQ: handleSecretKeyRequest(msg); break; case EncryptHeader.SECRET_KEY_RSP: handleSecretKeyResponse(msg, hdr.version()); break; default: log.warn("%s: received unknown encrypt header of type %d", local_addr, hdr.type()); break; } return null; } @Override protected boolean process(Message msg) { if(queue_up_msgs || secret_key == null) { up_queue.offer(msg); log.trace("%s: queuing %s message from %s as secret key hasn't been retrieved from keyserver %s yet, hdrs: %s", local_addr, msg.dest() == null? "mcast" : "unicast", msg.src(), key_server_addr, msg.printHeaders()); if(last_key_request == 0 || System.currentTimeMillis() - last_key_request > 2000) { last_key_request=System.currentTimeMillis(); sendKeyRequest(); } return false; } return true; } protected void handleSecretKeyRequest(final Message msg) { if(!inView(msg.src(), "key requester %s is not in current view %s; ignoring key request")) return; log.debug("%s: received key request from %s", local_addr, msg.getSrc()); try { PublicKey tmpKey=generatePubKey(msg.getBuffer()); sendSecretKey(secret_key, tmpKey, msg.getSrc()); } catch(Exception e) { log.warn("%s: unable to reconstitute peer's public key", local_addr); } } protected void handleSecretKeyResponse(final Message msg, final byte[] key_version) { if(!inView(msg.src(), "ignoring secret key sent by %s which is not in current view %s")) return; try { SecretKey tmp=decodeKey(msg.getBuffer()); if(tmp == null) sendKeyRequest(key_server_addr); // unable to understand response, let's try again else { // otherwise set the returned key as the shared key log.debug("%s: received secret key from keyserver %s", local_addr, msg.getSrc()); setKeys(tmp, key_version); } } catch(Exception e) { log.warn("%s: unable to process received public key", local_addr, e); } } /** Initialise the symmetric key if none is supplied in a keystore */ protected SecretKey createSecretKey() throws Exception { KeyGenerator keyGen=null; // see if we have a provider specified if(provider != null && !provider.trim().isEmpty()) keyGen=KeyGenerator.getInstance(getAlgorithm(sym_algorithm), provider); else keyGen=KeyGenerator.getInstance(getAlgorithm(sym_algorithm)); // generate the key using the defined init properties keyGen.init(sym_keylength); return keyGen.generateKey(); } /** Generates the public/private key pair from the init params */ protected void initKeyPair() throws Exception { // generate keys according to the specified algorithms // generate publicKey and Private Key KeyPairGenerator KpairGen=null; if(provider != null && !provider.trim().isEmpty()) KpairGen=KeyPairGenerator.getInstance(getAlgorithm(asym_algorithm), provider); else KpairGen=KeyPairGenerator.getInstance(getAlgorithm(asym_algorithm)); KpairGen.initialize(asym_keylength,new SecureRandom()); key_pair=KpairGen.generateKeyPair(); // set up the Cipher to decrypt secret key responses encrypted with our key if(provider != null && !provider.trim().isEmpty()) asym_cipher=Cipher.getInstance(asym_algorithm, provider); else asym_cipher=Cipher.getInstance(asym_algorithm); asym_cipher.init(Cipher.DECRYPT_MODE, key_pair.getPrivate()); } @Override protected synchronized void handleView(View v) { boolean left_mbrs=change_key_on_leave && this.view != null && !v.containsMembers(this.view.getMembersRaw()); super.handleView(v); Address tmpKeyServer=v.getCoord(); // the coordinator is the keyserver if(tmpKeyServer.equals(local_addr)) { if(!is_key_server || left_mbrs) becomeKeyServer(tmpKeyServer, left_mbrs); } else handleNewKeyServer(tmpKeyServer, v instanceof MergeView, left_mbrs); } protected void becomeKeyServer(Address tmpKeyServer, boolean left_mbrs) { if(log.isDebugEnabled()) { if(!is_key_server) log.debug("%s: I'm the new key server", local_addr); else if(left_mbrs) log.debug("%s: creating new secret key because members left", local_addr); } key_server_addr=tmpKeyServer; is_key_server=true; try { this.secret_key=createSecretKey(); initSymCiphers(sym_algorithm, secret_key); drainUpQueue(); } catch(Exception ex) { log.error("%s: failed creating secret key and initializing ciphers", local_addr, ex); } } /** If the keyserver changed, send a request for the secret key to the keyserver */ protected void handleNewKeyServer(Address newKeyServer, boolean merge_view, boolean left_mbrs) { if(keyServerChanged(newKeyServer) || merge_view || left_mbrs) { secret_key=null; sym_version=null; queue_up_msgs=true; key_server_addr=newKeyServer; is_key_server=false; log.debug("%s: sending request for secret key to the new keyserver %s", local_addr, key_server_addr); sendKeyRequest(key_server_addr); } } protected boolean keyServerChanged(Address newKeyServer) { return !Objects.equals(key_server_addr, newKeyServer); } protected void setKeys(SecretKey key, byte[] version) throws Exception { if(Arrays.equals(this.sym_version, version)) return; Cipher decoding_cipher=secret_key != null? decoding_ciphers.take() : null; // put the previous key into the map, keep the cipher: no leak, as we'll clear decoding_ciphers in initSymCiphers() if(decoding_cipher != null) key_map.put(new AsciiString(version), decoding_cipher); secret_key=key; initSymCiphers(key.getAlgorithm(), key); sym_version=version; drainUpQueue(); } protected void sendSecretKey(SecretKey secret_key, PublicKey public_key, Address source) throws Exception { byte[] encryptedKey=encryptSecretKey(secret_key, public_key); Message newMsg=new Message(source, encryptedKey).src(local_addr) .putHeader(this.id, new EncryptHeader(EncryptHeader.SECRET_KEY_RSP, symVersion())); log.debug("%s: sending secret key to %s", local_addr, source); down_prot.down(newMsg); } /** Encrypts the current secret key with the requester's public key (the requester will decrypt it with its private key) */ protected byte[] encryptSecretKey(SecretKey secret_key, PublicKey public_key) throws Exception { Cipher tmp; if (provider != null && !provider.trim().isEmpty()) tmp=Cipher.getInstance(asym_algorithm, provider); else tmp=Cipher.getInstance(asym_algorithm); tmp.init(Cipher.ENCRYPT_MODE, public_key); // encrypt current secret key return tmp.doFinal(secret_key.getEncoded()); } /** send client's public key to server and request server's public key */ protected void sendKeyRequest(Address key_server) { Message newMsg=new Message(key_server, key_pair.getPublic().getEncoded()).src(local_addr) .putHeader(this.id,new EncryptHeader(EncryptHeader.SECRET_KEY_REQ, sym_version)); down_prot.down(newMsg); } protected SecretKeySpec decodeKey(byte[] encodedKey) throws Exception { byte[] keyBytes; synchronized(this) { keyBytes=asym_cipher.doFinal(encodedKey); } try { SecretKeySpec keySpec=new SecretKeySpec(keyBytes, getAlgorithm(sym_algorithm)); Cipher temp; if (provider != null && !provider.trim().isEmpty()) temp=Cipher.getInstance(sym_algorithm, provider); else temp=Cipher.getInstance(sym_algorithm); temp.init(Cipher.SECRET_KEY, keySpec); return keySpec; } catch(Exception e) { log.error(Util.getMessage("FailedDecodingKey"), e); return null; } } // doesn't have to be 100% correct: leftover messages wll be delivered later and will be discarded as dupes, as // retransmission is likely to have kicked in before anyway protected void drainUpQueue() { queue_up_msgs=false; Message queued_msg; while((queued_msg=up_queue.poll()) != null) { try { Message decrypted_msg=decryptMessage(null, queued_msg.copy()); if(decrypted_msg != null) up_prot.up(decrypted_msg); } catch(Exception ex) { log.error("failed decrypting message from %s: %s", queued_msg.src(), ex); } } } @Override protected void handleUnknownVersion() { if(!is_key_server) sendKeyRequest(key_server_addr); } /** Used to reconstitute public key sent in byte form from peer */ protected PublicKey generatePubKey(byte[] encodedKey) { PublicKey pubKey=null; try { KeyFactory KeyFac=KeyFactory.getInstance(getAlgorithm(asym_algorithm)); X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(encodedKey); pubKey=KeyFac.generatePublic(x509KeySpec); } catch(Exception e) { e.printStackTrace(); } return pubKey; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy