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

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

 package org.jgroups.protocols;

import org.jgroups.*;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.Property;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.util.*;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 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}.
* * 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 for 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 * are dropped when the secret key hasn't been installed yet. * * @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 node leaves, change the secret group key, preventing old members from eavesdropping") protected boolean change_key_on_leave; @Property(description="Change the secret group key when the coordinator changes. If enabled, this will take " + "place even if change_key_on_leave is disabled.") protected boolean change_key_on_coord_leave=true; @Property(description="If true, a separate KeyExchange protocol (somewhere in the stack) is used to" + " fetch the shared secret key. If false, the default (built-in) key exchange protocol will be used.") protected boolean use_external_key_exchange; protected KeyExchange key_exchange; protected volatile Address key_server_addr; protected volatile boolean send_group_keys; // set by handleView() protected KeyPair key_pair; // to store own's public/private Key protected Cipher asym_cipher; // decrypting cypher for secret key requests protected final Map pub_map=new ConcurrentHashMap<>(); // map of members and their public keys // cache server address between reception of INSTALL_MERGE_VIEW and sending of VIEW (MergeView) protected static final ThreadLocal
srv_addr=new ThreadLocal<>(); @Override public ASYM_ENCRYPT setKeyStoreEntry(KeyStore.PrivateKeyEntry entry) { this.key_pair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()); return this; } public boolean getChangeKeyOnLeave() {return change_key_on_leave;} public ASYM_ENCRYPT setChangeKeyOnLeave(boolean c) {change_key_on_leave=c; return this;} public boolean getChangeKeyOnCoordLeave() {return change_key_on_coord_leave;} public ASYM_ENCRYPT setChangeKeyOnCoordLeave(boolean c) {change_key_on_coord_leave=c; return this;} public boolean getUseExternalKeyExchange() {return use_external_key_exchange;} public ASYM_ENCRYPT setUseExternalKeyExchange(boolean u) {use_external_key_exchange=u; return this;} public KeyPair keyPair() {return key_pair;} public Cipher asymCipher() {return asym_cipher;} public Address keyServerAddr() {return key_server_addr;} public ASYM_ENCRYPT keyServerAddr(Address ks) {this.key_server_addr=ks; return this;} public List providedDownServices() { return Arrays.asList(Event.GET_SECRET_KEY, Event.SET_SECRET_KEY); } @ManagedAttribute(description="Keys in the public key map") public String getPublicKeys() { return pub_map.keySet().toString(); } @ManagedAttribute(description="The current key server") public String getKeyServerAddress() {return key_server_addr != null? key_server_addr.toString() : "null";} @ManagedAttribute(description="True if this member is the current key server, false otherwise") public boolean isKeyServer() { return Objects.equals(key_server_addr, local_addr); } public void init() throws Exception { SecretKey k=createSecretKey(); byte[] encoded=k.getEncoded(); if(encoded == null) throw new IllegalArgumentException(String.format("secret key %s/%s does not support encoding (FIPS enabled?)", k.getAlgorithm(), k.getFormat())); send_group_keys=false; initKeyPair(); super.init(); if(use_external_key_exchange) fetchAndSetKeyExchange(); } public void start() throws Exception { super.start(); pub_map.put(local_addr, key_pair.getPublic().getEncoded()); } public Object down(Event evt) { if(evt.type() == Event.INSTALL_MERGE_VIEW) { // only received by the merge *leader* // the new group key will be added to the next INSTALL_MERGE_VIEW message (if !use_external_key_exchange) createNewKey("because of an INSTALL_MERGE_VIEW event"); } return super.down(evt); } public Object down(Message msg) { Processing processing=skipDownMessage(msg); if(processing == Processing.PROCESS) return super.down(msg); if(processing == Processing.SKIP) return down_prot.down(msg); return null; // DROP } public Object up(Event evt) { switch(evt.type()) { case Event.GET_SECRET_KEY: return new Tuple<>(secret_key, sym_version); case Event.SET_SECRET_KEY: Tuple tuple=evt.arg(); try { installSharedGroupKey(null, tuple.getVal1(), tuple.getVal2()); } catch(Exception ex) { log.error("failed setting group key", ex); } return null; } return up_prot.up(evt); } public Object up(Message msg) { if(dropMulticastMessageFromNonMember(msg)) return null; if(skipUpMessage(msg)) return up_prot.up(msg); return super.up(msg); } public void up(MessageBatch batch) { Iterator it=batch.iterator(); while(it.hasNext()) { Message msg=it.next(); if(dropMulticastMessageFromNonMember(msg)) { it.remove(); continue; } if(skipUpMessage(msg)) { try { up_prot.up(msg); it.remove(); } 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) } protected boolean dropMulticastMessageFromNonMember(Message msg) { return msg.dest() == null && !inView(msg.src(), String.format("%s: dropped multicast message from non-member %s", local_addr, msg.getSrc())); } public ASYM_ENCRYPT fetchAndSetKeyExchange() { if((key_exchange=stack.findProtocol(KeyExchange.class)) == null) throw new IllegalStateException(KeyExchange.class.getSimpleName() + " not found in stack"); return this; } protected static void cacheServerAddress(Address srv) { srv_addr.set(srv); } protected static Address getCachedServerAddress() { Address retval=srv_addr.get(); srv_addr.remove(); return retval; } /** * Processes a message with a GMS header (e.g. by adding the secret key to a JOIN response) and returns true if * the message should be passed down (not encrypted) or false if the message needs to be encrypted * @return Processing {@link Processing#DROP} if the message needs to be dropped, {@link Processing#SKIP} if the * message needs to be skipped (not encrypted), or {@link Processing#PROCESS} if the message needs to be * processed (= encrypted) */ protected Processing skipDownMessage(Message msg) { GMS.GmsHeader hdr=msg.getHeader(GMS_ID); if(hdr == null) return Processing.PROCESS; switch(hdr.getType()) { case GMS.GmsHeader.JOIN_REQ: case GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER: case GMS.GmsHeader.MERGE_RSP: if(!use_external_key_exchange) { // attach our public key to the JOIN-REQ Message copy=addKeysToMessage(msg, true, false, null); down_prot.down(copy); return Processing.DROP; } return Processing.SKIP; case GMS.GmsHeader.JOIN_RSP: return addMetadata(msg,false, msg.getDest(), true); case GMS.GmsHeader.VIEW: boolean tmp=send_group_keys; send_group_keys=false; return addMetadata(msg, tmp, null, tmp); case GMS.GmsHeader.INSTALL_MERGE_VIEW: // a new group key was created in down(INSTALL_MERGE_VIEW) if(Objects.equals(local_addr, msg.dest())) break; return addMetadata(msg, true, null, true); case GMS.GmsHeader.MERGE_REQ: case GMS.GmsHeader.VIEW_ACK: case GMS.GmsHeader.GET_DIGEST_REQ: case GMS.GmsHeader.GET_DIGEST_RSP: return Processing.SKIP; } return Processing.PROCESS; } /** Checks if the message contains a public key (and adds it to pub_map if present) or an encrypted group key * (and installs it if present) */ protected boolean skipUpMessage(Message msg) { GMS.GmsHeader hdr=msg.getHeader(GMS_ID); if(hdr == null) return false; EncryptHeader h=msg.getHeader(id); switch(hdr.getType()) { case GMS.GmsHeader.JOIN_REQ: case GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER: case GMS.GmsHeader.MERGE_RSP: return processEncryptMessage(msg, h, true); case GMS.GmsHeader.JOIN_RSP: case GMS.GmsHeader.VIEW: case GMS.GmsHeader.INSTALL_MERGE_VIEW: if(hdr.getType() == GMS.GmsHeader.INSTALL_MERGE_VIEW) cacheServerAddress(h.server()); return processEncryptMessage(msg, h, false); case GMS.GmsHeader.MERGE_REQ: case GMS.GmsHeader.VIEW_ACK: case GMS.GmsHeader.GET_DIGEST_REQ: case GMS.GmsHeader.GET_DIGEST_RSP: return true; } return false; } protected boolean processEncryptMessage(Message msg, EncryptHeader hdr, boolean retval) { if(hdr == null) return retval; switch(hdr.type) { case EncryptHeader.INSTALL_KEYS: removeKeysFromMessageAndInstall(msg, hdr.version()); break; case EncryptHeader.FETCH_SHARED_KEY: if(!Objects.equals(local_addr, msg.getSrc())) { try { Address key_server=hdr.server() != null? hdr.server() : msg.src(); if(log.isTraceEnabled()) log.trace("%s: fetching group key from %s", local_addr, key_server); key_exchange.fetchSecretKeyFrom(key_server); } catch(Exception e) { log.warn("%s: failed fetching group key from %s: %s", local_addr, msg.src(), e); } } break; } return retval; } protected void installPublicKeys(Address sender, byte[] buf, int offset, int length) { ByteArrayDataInputStream in=new ByteArrayDataInputStream(buf, offset, length); try { int num_keys=in.readInt(); for(int i=0; i < num_keys; i++) { Address mbr=Util.readAddress(in); int len=in.readInt(); byte[] key=new byte[len]; in.readFully(key, 0, key.length); pub_map.put(mbr, key); } log.trace("%s: added %d public keys to local cache", local_addr, num_keys); } catch(Exception ex) { log.error("%s: failed reading public keys received from %s: %s", local_addr, sender, ex); } } protected Processing addMetadata(Message msg, boolean add_secret_keys, Address include_secret_key_only_for, boolean attach_fetch_key_header) { try { if(use_external_key_exchange && !attach_fetch_key_header) return Processing.PROCESS; Message encr_msg=encrypt(msg); // makes a copy if(use_external_key_exchange) { // attach a FETCH_SHARED_KEY to the message; this causes the recipient to fetch and install the // shared key *before* delivering the message (so it can be decrypted) Address srv=key_exchange.getServerLocation(); if(srv == null) srv=getCachedServerAddress(); log.trace("%s: asking %s to fetch the shared group key %s via an external key exchange protocol (srv=%s)", local_addr, encr_msg.getDest() == null? "all members" : encr_msg.getDest(), Util.byteArrayToHexString(sym_version), srv); encr_msg.putHeader(id, new EncryptHeader(EncryptHeader.FETCH_SHARED_KEY, symVersion(), getIv(encr_msg)).server(srv)); } else { encr_msg=addKeysToMessage(encr_msg, false, add_secret_keys, include_secret_key_only_for); if(add_secret_keys || include_secret_key_only_for != null) log.trace("%s: sending encrypted group key to %s (version: %s)", local_addr, encr_msg.getDest() == null? "all members" : encr_msg.getDest(), Util.byteArrayToHexString(sym_version)); } down_prot.down(encr_msg); return Processing.DROP; // the encrypted msg was already sent; no need to send the un-encrypted msg } catch(Exception ex) { log.warn("%s: unable to send message to %s: %s", msg.getDest() == null? "all" : msg.getDest(), local_addr, ex); return Processing.PROCESS; } } /** * Adds the public and/or encrypted shared keys to the payload of msg. If msg already has a payload, the message * will be copied and the new payload consists of the keys and the original payload * @param msg The original message * @return A copy of the message */ protected Message addKeysToMessage(Message msg, boolean copy, boolean add_secret_keys, Address serialize_only) { ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(pub_map.size() * 200 + msg.getLength()); try { serializeKeys(out, add_secret_keys, serialize_only); if(msg.getLength() > 0) // add the original buffer out.write(msg.getArray(), msg.getOffset(), msg.getLength()); return (copy? msg.copy(true, true) : msg).setArray(out.getBuffer()) .putHeader(id, new EncryptHeader(EncryptHeader.INSTALL_KEYS, symVersion(), getIv(msg))); } catch(Throwable t) { log.error("%s: failed adding keys to message: %s", local_addr, t); return null; } } /** * Removes the public and/or private keys from the payload of msg and installs them. If there is some payload left * (the original payload), the offset of the message will be changed. Otherwise, the payload will be nulled, to * re-create the original message */ protected void removeKeysFromMessageAndInstall(Message msg, byte[] version) { ByteArrayDataInputStream in=new ByteArrayDataInputStream(msg.getArray(), msg.getOffset(), msg.getLength()); unserializeAndInstallKeys(msg.getSrc(), version, in); int len=msg.getLength(), offset=msg.getOffset(), bytes_read=in.position(); // we can modify the original message as the sender sends a copy (even on retransmissions) if(offset + bytes_read == len) msg.setArray(null, 0, 0); // the original payload must have been null else msg.setArray(msg.getArray(), offset+bytes_read, len-bytes_read); } /** Serializes all public keys and their corresponding encrypted shared group keys into a buffer */ protected void serializeKeys(ByteArrayDataOutputStream out, boolean serialize_shared_keys, Address serialize_only) throws Exception { out.writeInt(pub_map.size()); // number of entries (actual value is written after serialization) int num=0; for(Map.Entry e: pub_map.entrySet()) { Address mbr=e.getKey(); byte[] public_key=e.getValue(); // the encoded public key Util.writeAddress(mbr, out); // public keys out.writeInt(public_key.length); out.write(public_key, 0, public_key.length); if(serialize_shared_keys || Objects.equals(mbr, serialize_only)) { PublicKey pk=makePublicKey(public_key); byte[] encrypted_shared_key=encryptSecretKey(secret_key, pk); // the encrypted shared group key out.writeInt(encrypted_shared_key.length); out.write(encrypted_shared_key, 0, encrypted_shared_key.length); } else out.writeInt(0); num++; } int curr_pos=out.position(); out.position(0).writeInt(num); out.position(curr_pos); } /** Unserializes public keys and installs them to pub_map, then reads encrypted shared keys and install our own */ protected void unserializeAndInstallKeys(Address sender, byte[] version, ByteArrayDataInputStream in) { try { int num_keys=in.readInt(); for(int i=0; i < num_keys; i++) { Address mbr=Util.readAddress(in); int len=in.readInt(); if(len > 0) { byte[] public_key=new byte[len]; in.readFully(public_key, 0, public_key.length); pub_map.put(mbr, public_key); } if((len=in.readInt()) > 0) { byte[] encrypted_shared_group_key=new byte[len]; in.readFully(encrypted_shared_group_key, 0, encrypted_shared_group_key.length); if(local_addr.equals(mbr)) { try { SecretKey tmp=decodeKey(encrypted_shared_group_key); if(tmp != null) installSharedGroupKey(sender, tmp, version); // otherwise set the received key as the shared key } catch(Exception e) { log.warn("%s: unable to process key received from %s: %s", local_addr, sender, e); } } } } } catch(Exception ex) { log.error("%s: failed reading keys received from %s: %s", local_addr, sender, ex); } } protected static ByteArray serializeKeys(Map keys) throws Exception { int num_keys=keys.size(); if(num_keys == 0) return null; ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(num_keys * 100); out.writeInt(num_keys); for(Map.Entry e: keys.entrySet()) { Util.writeAddress(e.getKey(), out); byte[] val=e.getValue(); out.writeInt(val.length); out.write(val, 0, val.length); } return out.getBuffer(); } protected Map unserializeKeys(Address sender, byte[] buf, int offset, int length) { Map map=new HashMap<>(); ByteArrayDataInputStream in=new ByteArrayDataInputStream(buf, offset, length); try { int num_keys=in.readInt(); for(int i=0; i < num_keys; i++) { Address mbr=Util.readAddress(in); int len=in.readInt(); byte[] key=new byte[len]; in.readFully(key, 0, key.length); map.put(mbr, key); } } catch(Exception ex) { log.error("%s: failed reading keys received from %s: %s", local_addr, sender, ex); } return map; } /** 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 { if (this.key_pair == null) { // 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 void handleView(View v) { boolean left_mbrs, create_new_key, key_server_changed; pub_map.keySet().retainAll(v.getMembers()); synchronized(this) { key_server_changed=!Objects.equals(v.getCoord(), key_server_addr); left_mbrs=this.view != null && !v.containsMembers(this.view.getMembersRaw()); super.handleView(v); key_server_addr=v.getCoord(); // the coordinator is the keyserver create_new_key=secret_key == null // always create a group key the first time (key is null) || change_key_on_leave && left_mbrs // && has higher precedence than || || change_key_on_coord_leave && key_server_changed; // if we get a MergeView, the secret key has already been created (in down(INSTALL_MERGE_VIEW) create_new_key&=!(v instanceof MergeView); // keys will be added to VIEW in down() send_group_keys=create_new_key || v instanceof MergeView; if(!Objects.equals(key_server_addr, local_addr)) return; // all non-coordinators are done at this point if(key_server_changed) log.debug("%s: I'm the new key server", local_addr); if(create_new_key) createNewKey("because of new view " + v); } } protected void createNewKey(String message) { try { this.secret_key=createSecretKey(); initSymCiphers(sym_algorithm, secret_key); log.debug("%s: created new group key (version: %s) %s", local_addr, Util.byteArrayToHexString(sym_version), message); cacheGroupKey(sym_version); } catch(Exception ex) { log.error("%s: failed creating group key and initializing ciphers", local_addr, ex); } } protected synchronized void installSharedGroupKey(Address sender, SecretKey key, byte[] version) throws Exception { if(Arrays.equals(this.sym_version, version)) { log.debug("%s: ignoring group key received from %s (version: %s); it has already been installed", local_addr, sender != null? sender : "key exchange protocol", Util.byteArrayToHexString(version)); return; } log.debug("%s: installing group key received from %s (version: %s)", local_addr, sender != null? sender : "key exchange protocol", Util.byteArrayToHexString(version)); secret_key=key; initSymCiphers(sym_algorithm, key); sym_version=version; cacheGroupKey(version); } /** Cache the current shared key to decrypt messages encrypted with the old shared group key */ protected void cacheGroupKey(byte[] version) throws Exception { // put the previous key into the map if(secret_key != null) key_map.putIfAbsent(new AsciiString(version), secret_key); } /** Encrypts the current secret key with the requester's public key (the requester will decrypt it with its private key) */ protected byte[] encryptSecretKey(Key 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()); } protected SecretKeySpec decodeKey(byte[] encodedKey) throws Exception { byte[] keyBytes; synchronized(this) { try { keyBytes=asym_cipher.doFinal(encodedKey); } catch (BadPaddingException | IllegalBlockSizeException e) { // if any exception is thrown, this cipher object may need to be reset before it can be used again. asym_cipher.init(Cipher.DECRYPT_MODE, key_pair.getPrivate()); throw e; } } 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; } } /** Used to reconstitute public key sent in byte form from peer */ protected PublicKey makePublicKey(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; } protected byte[] getIv(Message msg) { EncryptHeader h=msg.getHeader(id); if (h == null) return null; return h.iv(); } protected enum Processing {SKIP, PROCESS, DROP} }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy