package org.jgroups.protocols; import org.jgroups.*; import org.jgroups.auth.MD5Token; import org.jgroups.conf.ClassConfigurator; import org.jgroups.protocols.pbcast.DeltaView; import org.jgroups.protocols.pbcast.GMS; import org.jgroups.protocols.pbcast.JoinRsp; import org.jgroups.protocols.pbcast.NAKACK2; import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Buffer; import org.jgroups.util.ByteArrayDataOutputStream; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.crypto.SecretKey; import java.util.Arrays; import java.util.stream.Stream; /** * Tests use cases for {@link ASYM_ENCRYPT} described in https://issues.jboss.org/browse/JGRP-2021. * @author Bela Ban * @since 4.0 */ @Test(groups=Global.FUNCTIONAL,singleThreaded=true) public class ASYM_ENCRYPT_Test extends EncryptTest { protected static final short ASYM_ENCRYPT_ID; static { ASYM_ENCRYPT_ID=ClassConfigurator.getProtocolId(ASYM_ENCRYPT.class); } @BeforeMethod protected void init() throws Exception { super.init(getClass().getSimpleName()); } @AfterMethod protected void destroy() { super.destroy(); } /** Calling methods in superclass. Kludge because TestNG doesn't call methods in superclass correctly **/ public void testRegularMessageReception() throws Exception { super.testRegularMessageReception(); } public void testRegularMessageReceptionWithNullMessages() throws Exception { super.testRegularMessageReceptionWithNullMessages(); } /** Same as above, but don't encrypt entire message, but just payload */ public void testRegularMessageReceptionWithNullMessagesEncryptOnlyPayload() throws Exception { Stream.of(a,b,c).forEach(ch -> { Encrypt encr=ch.getProtocolStack().findProtocol(Encrypt.class); encr.encryptEntireMessage(false); }); super.testRegularMessageReceptionWithNullMessages(); } public void testRegularMessageReceptionWithEmptyMessages() throws Exception { super.testRegularMessageReceptionWithEmptyMessages(); } public void testRegularMessageReceptionWithEmptyMessagesEncryptOnlyPayload() throws Exception { Stream.of(a,b,c).forEach(ch -> { Encrypt encr=ch.getProtocolStack().findProtocol(Encrypt.class); encr.encryptEntireMessage(false); }); super.testRegularMessageReceptionWithEmptyMessages(); } public void testChecksum() throws Exception { super.testChecksum(); } public void testRogueMemberJoin() throws Exception { super.testRogueMemberJoin(); } public void testMessageSendingByRogue() throws Exception { super.testMessageSendingByRogue(); } public void testMessageSendingByRogueUsingEncryption() throws Exception { super.testMessageSendingByRogueUsingEncryption(); } public void testMessageReceptionByRogue() throws Exception { super.testMessageReceptionByRogue(); } public void testCapturingOfMessageByNonMemberAndResending() throws Exception { super.testCapturingOfMessageByNonMemberAndResending(); } public void testRogueViewInstallation() throws Exception { super.testRogueViewInstallation(); } /** * A non-member sends a {@link EncryptHeader#SECRET_KEY_REQ} request to the key server. Asserts that the rogue member * doesn't get the secret key. If it did, it would be able to decrypt all messages from cluster members! */ public void nonMemberGetsSecretKeyFromKeyServer() throws Exception { Util.close(rogue); rogue=new JChannel(Util.getTestStack()).name("rogue"); DISCARD discard=new DISCARD().setDiscardAll(true); rogue.getProtocolStack().insertProtocol(discard, ProtocolStack.Position.ABOVE, TP.class); CustomENCRYPT encrypt=new CustomENCRYPT(); encrypt.init(); rogue.getProtocolStack().insertProtocol(encrypt, ProtocolStack.Position.BELOW, NAKACK2.class); rogue.connect(cluster_name); // creates a singleton cluster assert rogue.getView().size() == 1; GMS gms=rogue.getProtocolStack().findProtocol(GMS.class); View rogue_view=new View(a.getAddress(), a.getView().getViewId().getId(), Arrays.asList(a.getAddress(),b.getAddress(),c.getAddress(),rogue.getAddress())); gms.installView(rogue_view); // now fabricate a KEY_REQUEST message and send it to the key server (A) Message newMsg=new Message(a.getAddress(), encrypt.keyPair().getPublic().getEncoded()).src(rogue.getAddress()) .putHeader(encrypt.getId(),new EncryptHeader(EncryptHeader.SECRET_KEY_REQ, encrypt.symVersion())); discard.setDiscardAll(false); System.out.printf("-- sending KEY_REQUEST to key server %s\n", a.getAddress()); encrypt.getDownProtocol().down(newMsg); for(int i=0; i < 10; i++) { SecretKey secret_key=encrypt.key; if(secret_key != null) break; Util.sleep(500); } discard.setDiscardAll(true); gms.installView(View.create(rogue.getAddress(), 20, rogue.getAddress())); System.out.printf("-- secret key is %s (should be null)\n", encrypt.key); assert encrypt.key == null : String.format("should not have received secret key %s", encrypt.key); } /** Verifies that a non-member (non-coord) cannot send a JOIN-RSP to a member */ public void nonMemberInjectingJoinResponse() throws Exception { Util.close(rogue); rogue=create("rogue"); ProtocolStack stack=rogue.getProtocolStack(); AUTH auth=stack.findProtocol(AUTH.class); auth.setAuthToken(new MD5Token("unknown_pwd")); GMS gms=stack.findProtocol(GMS.class); gms.setMaxJoinAttempts(1); DISCARD discard=new DISCARD().setDiscardAll(true); stack.insertProtocol(discard, ProtocolStack.Position.ABOVE, TP.class); rogue.connect(cluster_name); assert rogue.getView().size() == 1; discard.setDiscardAll(false); stack.removeProtocol(NAKACK2.class, UNICAST3.class); View rogue_view=View.create(a.getAddress(), a.getView().getViewId().getId() +5, a.getAddress(),b.getAddress(),c.getAddress(),rogue.getAddress()); JoinRsp join_rsp=new JoinRsp(rogue_view, null); GMS.GmsHeader gms_hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_RSP); Message rogue_join_rsp=new Message(b.getAddress(), rogue.getAddress()).putHeader(GMS_ID, gms_hdr) .setBuffer(GMS.marshal(join_rsp)).setFlag(Message.Flag.NO_RELIABILITY); // bypasses NAKACK2 / UNICAST3 rogue.down(rogue_join_rsp); for(int i=0; i < 10; i++) { if(b.getView().size() > 3) break; Util.sleep(500); } assert b.getView().size() == 3 : String.format("B's view is %s, but should be {A,B,C}", b.getView()); } /** The rogue node has an incorrect {@link AUTH} config (secret) and can thus not join */ public void rogueMemberCannotJoinDueToAuthRejection() throws Exception { Util.close(rogue); rogue=create("rogue"); AUTH auth=rogue.getProtocolStack().findProtocol(AUTH.class); auth.setAuthToken(new MD5Token("unknown_pwd")); GMS gms=rogue.getProtocolStack().findProtocol(GMS.class); gms.setMaxJoinAttempts(2); rogue.connect(cluster_name); System.out.printf("Rogue's view is %s\n", rogue.getView()); assert rogue.getView().size() == 1 : String.format("rogue should have a singleton view of itself, but doesn't: %s", rogue.getView()); } public void mergeViewInjectionByNonMember() throws Exception { Util.close(rogue); rogue=create("rogue"); AUTH auth=rogue.getProtocolStack().findProtocol(AUTH.class); auth.setAuthToken(new MD5Token("unknown_pwd")); GMS gms=rogue.getProtocolStack().findProtocol(GMS.class); gms.setMaxJoinAttempts(1); rogue.connect(cluster_name); MergeView merge_view=new MergeView(a.getAddress(), a.getView().getViewId().getId()+5, Arrays.asList(a.getAddress(), b.getAddress(), c.getAddress(), rogue.getAddress()), null); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW, a.getAddress()); Message merge_view_msg=new Message(null, marshalView(merge_view)).putHeader(GMS_ID, hdr) .setFlag(Message.Flag.NO_RELIABILITY); System.out.printf("** %s: trying to install MergeView %s in all members\n", rogue.getAddress(), merge_view); rogue.down(merge_view_msg); // check if A, B or C installed the MergeView sent by rogue: for(int i=0; i < 10; i++) { boolean rogue_views_installed=Stream.of(a,b,c).anyMatch(ch -> ch.getView().containsMember(rogue.getAddress())); if(rogue_views_installed) break; Util.sleep(500); } Stream.of(a,b,c).forEach(ch -> System.out.printf("%s: %s\n", ch.getAddress(), ch.getView())); assert Stream.of(a, b, c).noneMatch(ch -> ch.getView().containsMember(rogue.getAddress())); } /** Tests that when {ABC} -> {AB}, neither A nor B can receive a message from non-member C */ public void testMessagesByLeftMember() throws Exception { View view=View.create(a.getAddress(), a.getView().getViewId().getId()+1, a.getAddress(),b.getAddress()); Stream.of(a,b).forEach(ch -> { GMS gms=ch.getProtocolStack().findProtocol(GMS.class); gms.installView(view); }); Stream.of(a,b).forEach(ch -> System.out.printf("%s: %s\n", ch.getAddress(), ch.getView())); System.out.printf("%s: %s\n", c.getAddress(), c.getView()); c.getProtocolStack().removeProtocol(NAKACK2.class); // to prevent A and B from discarding C as non-member Util.sleep(1000); // give members time to handle the new view c.send(null, "hello world from left member C!"); for(int i=0; i < 10; i++) { if(ra.size() > 0 || rb.size() > 0) break; Util.sleep(500); } assert ra.size() == 0 : String.format("A: received msgs from non-member C: %s", print(ra.list())); assert rb.size() == 0 : String.format("B: received msgs from non-member C: %s", print(rb.list())); } /** Tests that a left member C cannot decrypt messages from the cluster */ public void testEavesdroppingByLeftMember() throws Exception { printSymVersion(a,b,c); View view=View.create(a.getAddress(), a.getView().getViewId().getId()+1, a.getAddress(),b.getAddress()); Stream.of(a,b).forEach(ch -> { GMS gms=ch.getProtocolStack().findProtocol(GMS.class); gms.installView(view); }); Stream.of(a,b).forEach(ch -> System.out.printf("%s: %s\n", ch.getAddress(), ch.getView())); System.out.printf("%s: %s\n", c.getAddress(), c.getView()); c.getProtocolStack().removeProtocol(NAKACK2.class); // to prevent A and B from discarding C as non-member Util.sleep(2000); // give members time to handle the new view printSymVersion(a,b,c); a.send(null, "hello from A"); b.send(null, "hello from B"); for(int i=0; i < 10; i++) { if(rc.size() > 0) break; Util.sleep(500); } assert rc.size() == 0 : String.format("C: received msgs from cluster: %s", print(rc.list())); } protected JChannel create(String name) throws Exception { JChannel ch=new JChannel(Util.getTestStack()).name(name); ProtocolStack stack=ch.getProtocolStack(); Encrypt encrypt=createENCRYPT(); stack.insertProtocol(encrypt, ProtocolStack.Position.BELOW, NAKACK2.class); AUTH auth=new AUTH().setAuthCoord(true).setAuthToken(new MD5Token("mysecret")); // .setAuthCoord(false); stack.insertProtocol(auth, ProtocolStack.Position.BELOW, GMS.class); stack.findProtocol(GMS.class).setValue("join_timeout", 2000); // .setValue("view_ack_collection_timeout", 10); return ch; } protected void printSymVersion(JChannel ... channels) { for(JChannel ch: channels) { ASYM_ENCRYPT encr=ch.getProtocolStack().findProtocol(ASYM_ENCRYPT.class); byte[] sym_version=encr.symVersion(); System.out.printf("sym-version %s: %s\n", ch.getAddress(), Util.byteArrayToHexString(sym_version)); } } // Note that setting encrypt_entire_message to true is critical here, or else some of the tests in this // unit test would fail! protected ASYM_ENCRYPT createENCRYPT() throws Exception { ASYM_ENCRYPT encrypt=new ASYM_ENCRYPT().encryptEntireMessage(true).signMessages(true); encrypt.init(); return encrypt; } protected static Buffer marshalView(final View view) throws Exception { final ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(Global.SHORT_SIZE + view.serializedSize()); out.writeShort(determineFlags(view)); view.writeTo(out); return out.getBuffer(); } protected static short determineFlags(final View view) { short retval=0; if(view != null) { retval|=GMS.VIEW_PRESENT; if(view instanceof MergeView) retval|=GMS.MERGE_VIEW; else if(view instanceof DeltaView) retval|=GMS.DELTA_VIEW; } return retval; } protected static class CustomENCRYPT extends ASYM_ENCRYPT { protected SecretKey key; public CustomENCRYPT() { this.id=ASYM_ENCRYPT_ID; } protected Object handleUpEvent(Message msg, EncryptHeader hdr) { if(hdr.type() == EncryptHeader.SECRET_KEY_RSP) { try { key=decodeKey(msg.getBuffer()); System.out.printf("received secret key %s !\n", key); } catch(Exception e) { e.printStackTrace(); } } return super.handleUpEvent(msg, hdr); } } }