diff --git a/README.md b/README.md index beb06f5..c3162f0 100644 --- a/README.md +++ b/README.md @@ -51,23 +51,9 @@ reportPort=1338 // "ixuz (ict-0)" or "testIT (ict-0)". name= -// Insert the IP or domain of your neighbor A. -neighborAHost= -// Insert the additional port of the Report.ixi application of your neighbor A (report port). -// If your neighbor didn't install Report.ixi, ignore this setting. -neighborAPort=1338 - -// Insert the IP or domain of your neighbor B. -neighborBHost= -// Insert the additional port of the Report.ixi application of your neighbor B (report port). -// If your neighbor didn't install Report.ixi, ignore this setting. -neighborBPort=1338 - -// Insert the IP or domain of your neighbor C. -neighborCHost= -// Insert the additional port of the Report.ixi application of your neighbor C (report port). -// If your neighbor didn't install Report.ixi, ignore this setting. -neighborCPort=1338 +// Add host and port of your neighbors Report.ixi (comma separated, no whitespace). +// Please note that the port numbers should refer to your neighbor's Report.ixi operational port. +neighbors=10.10.10.10:1338,20.20.20.20:1338,public.address.to.neighbor:1338 // A unique identifier that has been generated upon initial start of // the Report.ixi application. Don't change the uuid by yourself. diff --git a/build.gradle b/build.gradle index 2071cc2..5d732c8 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'com.ictreport' -version '0.1' +version '0.2' sourceCompatibility = 1.7 repositories { @@ -15,6 +15,7 @@ repositories { dependencies { compile 'com.github.iotaledger:ict:master-SNAPSHOT' compile 'com.google.code.gson:gson:2.8.5' + compile group: 'org.danilopianini', name: 'gson-extras', version: '0.2.1' compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' testCompile group: 'junit', name: 'junit', version: '4.12' diff --git a/src/main/java/com/ictreport/ixi/ReportIxi.java b/src/main/java/com/ictreport/ixi/ReportIxi.java index df1c8c7..91feb57 100644 --- a/src/main/java/com/ictreport/ixi/ReportIxi.java +++ b/src/main/java/com/ictreport/ixi/ReportIxi.java @@ -1,8 +1,10 @@ package com.ictreport.ixi; import com.ictreport.ixi.api.Api; +import com.ictreport.ixi.exchange.*; import com.ictreport.ixi.model.Neighbor; - +import com.ictreport.ixi.utils.Constants; +import com.ictreport.ixi.utils.Cryptography; import org.iota.ict.ixi.IxiModule; import org.iota.ict.network.event.GossipFilter; import org.iota.ict.network.event.GossipReceiveEvent; @@ -12,22 +14,25 @@ import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; import java.util.LinkedList; import java.util.List; import com.ictreport.ixi.utils.Properties; public class ReportIxi extends IxiModule { - public static final String VERSION = "0.1"; - public final static Logger LOGGER = LogManager.getLogger(ReportIxi.class); private Properties properties; private final List neighbors = new LinkedList<>(); private static Api api = null; + private KeyPair keyPair = null; public static void main(String[] args) { + LOGGER.info(String.format("Report.ixi %s started.", Constants.VERSION)); + final String propertiesFilePath = (args.length == 0 ? "report.ixi.cfg" : args[0]); final Properties properties = new Properties(propertiesFilePath); properties.store(propertiesFilePath); @@ -47,12 +52,15 @@ public ReportIxi(Properties properties) { this.properties = properties; - InetSocketAddress neighborASocketAddress = new InetSocketAddress(properties.getNeighborAHost(), properties.getNeighborAPort()); - InetSocketAddress neighborBSocketAddress = new InetSocketAddress(properties.getNeighborBHost(), properties.getNeighborBPort()); - InetSocketAddress neighborCSocketAddress = new InetSocketAddress(properties.getNeighborCHost(), properties.getNeighborCPort()); - neighbors.add(new Neighbor(neighborASocketAddress.getAddress(), properties.getNeighborAPort())); - neighbors.add(new Neighbor(neighborBSocketAddress.getAddress(), properties.getNeighborBPort())); - neighbors.add(new Neighbor(neighborCSocketAddress.getAddress(), properties.getNeighborCPort())); + try { + keyPair = Cryptography.generateKeyPair(1024); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + for (InetSocketAddress neighborAddress : properties.getNeighborAddresses()) { + neighbors.add(new Neighbor(neighborAddress.getAddress(), neighborAddress.getPort())); + } GossipFilter filter = new GossipFilter(); filter.watchTag("REPORT9IXI99999999999999999"); @@ -79,19 +87,24 @@ public List getNeighbors() { return this.neighbors; } + public KeyPair getKeyPair() { + return keyPair; + } + + public Api getApi() { + return api; + } + @Override public void onTransactionReceived(GossipReceiveEvent event) { - LOGGER.info(String.format("message received '%s'", - event.getTransaction().tag )); if (api != null) { - api.getSender().reportTransactionReceived(event.getTransaction().decodedSignatureFragments); + Payload payload = Payload.deserialize(event.getTransaction().decodedSignatureFragments); + api.getReceiver().processPayload(null, payload); } } @Override public void onTransactionSubmitted(GossipSubmitEvent event) { - LOGGER.info(String.format("message submitted '%s'", - event.getTransaction().tag )); } diff --git a/src/main/java/com/ictreport/ixi/api/Api.java b/src/main/java/com/ictreport/ixi/api/Api.java index 75a60c9..c9e7ab3 100644 --- a/src/main/java/com/ictreport/ixi/api/Api.java +++ b/src/main/java/com/ictreport/ixi/api/Api.java @@ -1,7 +1,6 @@ package com.ictreport.ixi.api; import com.ictreport.ixi.ReportIxi; -import com.ictreport.ixi.utils.Properties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/com/ictreport/ixi/api/Receiver.java b/src/main/java/com/ictreport/ixi/api/Receiver.java index 230bf2a..1a0c396 100644 --- a/src/main/java/com/ictreport/ixi/api/Receiver.java +++ b/src/main/java/com/ictreport/ixi/api/Receiver.java @@ -1,13 +1,17 @@ package com.ictreport.ixi.api; +import com.ictreport.ixi.exchange.MetadataPayload; +import com.ictreport.ixi.exchange.Payload; +import com.ictreport.ixi.exchange.PingPayload; +import com.ictreport.ixi.exchange.ReceivedPingPayload; +import com.ictreport.ixi.exchange.SignedPayload; +import com.ictreport.ixi.utils.Constants; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.util.ArrayList; -import java.util.List; import com.ictreport.ixi.ReportIxi; import com.ictreport.ixi.model.Neighbor; @@ -19,29 +23,10 @@ public class Receiver extends Thread { private final DatagramSocket socket; private boolean isReceiving = false; - private List onIncomingPacketListeners = new ArrayList<>(); - public Receiver(ReportIxi reportIxi, DatagramSocket socket) { super("Receiver"); this.reportIxi = reportIxi; this.socket = socket; - - // Default onIncomingPacketListener - addOnIncomingPacketListener(new IIncomingPacketListener() { - @Override - public void OnIncomingPacket(DatagramPacket packet) { - for (final Neighbor neighbor : getReportIxi().getNeighbors()) { - if (packet.getAddress().equals(neighbor.getAddress()) && packet.getPort() == neighbor.getReportPort()) { - String data = new String(packet.getData(), 0, packet.getLength()); - - if (data.contains("uuid:")) { - neighbor.setUuid(data.substring(5)); - } - - } - } - } - }); } @Override @@ -51,12 +36,12 @@ public void run() { while (isReceiving) { - byte[] buf = new byte[256]; + byte[] buf = new byte[1024]; DatagramPacket packet = new DatagramPacket(buf, buf.length); try { - this.socket.receive(packet); - notifyOnIncomingPacketListeners(packet); + socket.receive(packet); + processPacket(packet); } catch (IOException e) { if (isReceiving) e.printStackTrace(); @@ -68,26 +53,88 @@ public void shutDown() { isReceiving = false; } - public void addOnIncomingPacketListener(IIncomingPacketListener onIncomingPacketListener) { - if (!onIncomingPacketListeners.contains(onIncomingPacketListener)) { - onIncomingPacketListeners.add(onIncomingPacketListener); + public void processPacket(final DatagramPacket packet) { + + for (final Neighbor neighbor : getReportIxi().getNeighbors()) { + if (packet.getAddress().equals(neighbor.getAddress()) && packet.getPort() == neighbor.getReportPort()) { + String data = new String(packet.getData(), 0, packet.getLength()); + + try { + final Payload payload = Payload.deserialize(data); + processPayload(neighbor, payload); + } catch (Exception e) { + LOGGER.info(String.format("Received invalid packet from Neighbor[%s:%s]", + neighbor.getAddress(), + neighbor.getReportPort())); + } + } } } - public void removeOnIncomingPacketListener(IIncomingPacketListener onIncomingPacketListener) { - if (onIncomingPacketListeners.contains(onIncomingPacketListener)) { - onIncomingPacketListeners.remove(onIncomingPacketListener); + public void processPayload(final Neighbor neighbor, final Payload payload) { + + if (payload instanceof MetadataPayload) { + processMetadataPacket(neighbor, (MetadataPayload) payload); + } + if (payload instanceof SignedPayload) { + processSignedPayload((SignedPayload) payload); } } - private void notifyOnIncomingPacketListeners(DatagramPacket packet) { - for (IIncomingPacketListener listener : onIncomingPacketListeners) { - listener.OnIncomingPacket(packet); + public void processMetadataPacket(final Neighbor neighbor, final MetadataPayload metadataPayload) { + + if (neighbor.getReportIxiVersion() == null || + !neighbor.getReportIxiVersion().equals(metadataPayload.getReportIxiVersion())) { + neighbor.setReportIxiVersion(metadataPayload.getReportIxiVersion()); + LOGGER.info(String.format("Neighbor[%s:%s] operates Report.ixi version: %s", + neighbor.getAddress(), + neighbor.getReportPort(), + neighbor.getReportIxiVersion())); + } + + if (neighbor.getUuid() == null || + !neighbor.getUuid().equals(metadataPayload.getUuid())) { + neighbor.setUuid(metadataPayload.getUuid()); + LOGGER.info(String.format("Received new uuid from neighbor[%s:%s]", + neighbor.getAddress(), + neighbor.getReportPort())); + } + + if (neighbor.getPublicKey() == null || + !neighbor.getPublicKey().equals(metadataPayload.getPublicKey())) { + neighbor.setPublicKey(metadataPayload.getPublicKey()); + LOGGER.info(String.format("Received new publicKey from neighbor[%s:%s]", + neighbor.getAddress(), + neighbor.getReportPort())); } } - public interface IIncomingPacketListener { - void OnIncomingPacket(DatagramPacket packet); + public void processSignedPayload(final SignedPayload signedPayload) { + + Neighbor signee = null; + for (Neighbor neighbor : reportIxi.getNeighbors()) { + + if (neighbor.getPublicKey() != null && signedPayload.verify(neighbor.getPublicKey())) { + + signee = neighbor; + break; + } + } + + if (signedPayload.getPayload() instanceof PingPayload) { + PingPayload pingPayload = (PingPayload) signedPayload.getPayload(); + ReceivedPingPayload receivedPingPayload; + if (signee != null) { + LOGGER.info(String.format("Received signed ping from neighbor [%s:%s]", + signee.getAddress(), + signee.getReportPort())); + + receivedPingPayload = new ReceivedPingPayload(reportIxi.getProperties().getUuid(), pingPayload, true); + } else { + receivedPingPayload = new ReceivedPingPayload(reportIxi.getProperties().getUuid(), pingPayload, false); + } + reportIxi.getApi().getSender().send(receivedPingPayload, Constants.RCS_HOST, Constants.RCS_PORT); + } } /** diff --git a/src/main/java/com/ictreport/ixi/api/Sender.java b/src/main/java/com/ictreport/ixi/api/Sender.java index c3a1f12..777c5b6 100644 --- a/src/main/java/com/ictreport/ixi/api/Sender.java +++ b/src/main/java/com/ictreport/ixi/api/Sender.java @@ -1,5 +1,6 @@ package com.ictreport.ixi.api; +import com.ictreport.ixi.exchange.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.iota.ict.model.TransactionBuilder; @@ -7,15 +8,16 @@ import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.LinkedList; +import java.util.List; import java.util.Timer; import java.util.TimerTask; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; import com.ictreport.ixi.ReportIxi; import com.ictreport.ixi.model.Neighbor; +import com.ictreport.ixi.utils.Constants; import com.ictreport.ixi.utils.RandomStringGenerator; public class Sender { @@ -28,9 +30,6 @@ public class Sender { private Timer submitRandomTransactionTimer = new Timer(); private RandomStringGenerator randomStringGenerator = new RandomStringGenerator(); - private final static String reportServerHost = "api.ictreport.com"; - private final static int reportServerPort = 14265; - public Sender(final ReportIxi reportIxi, DatagramSocket socket) { this.reportIxi = reportIxi; this.socket = socket; @@ -38,18 +37,13 @@ public Sender(final ReportIxi reportIxi, DatagramSocket socket) { public void start() { uuidSenderTimer.schedule(new TimerTask() { - @Override public void run() { for (final Neighbor neighbor : reportIxi.getNeighbors()) { - try { - byte[] messageByteArray = new String("uuid:" + reportIxi.getProperties().getUuid()).getBytes(); - DatagramPacket packet = new DatagramPacket(messageByteArray, messageByteArray.length); - packet.setSocketAddress(new InetSocketAddress(neighbor.getAddress(), neighbor.getReportPort())); - socket.send(packet); - } catch (IOException | RuntimeException e) { - e.printStackTrace(); - } + MetadataPayload metadataPayload = new MetadataPayload(reportIxi.getProperties().getUuid(), + reportIxi.getKeyPair().getPublic(), + Constants.VERSION); + send (metadataPayload, neighbor.getAddress(), neighbor.getReportPort()); } } }, 0, 60000); @@ -57,89 +51,53 @@ public void run() { reportTimer.schedule(new TimerTask() { @Override public void run() { - try { - Gson gson = new Gson(); - - JsonObject nodeInfo = new JsonObject(); - nodeInfo.addProperty("uuid", reportIxi.getProperties().getUuid()); - nodeInfo.addProperty("name", reportIxi.getProperties().getName()); - nodeInfo.addProperty("version", ReportIxi.VERSION); - - JsonArray neighborJSONArray = new JsonArray(); - for (final Neighbor neighbor : reportIxi.getNeighbors()) { - JsonObject neighborInfo = new JsonObject(); - neighborInfo.addProperty("uuid", neighbor.getUuid() != null ? neighbor.getUuid() : ""); - - neighborJSONArray.add(neighborInfo); - } - nodeInfo.add("neighbors", neighborJSONArray); - - JsonObject action = new JsonObject(); - action.addProperty("action", "setNodeStatus"); - action.add("value", nodeInfo); - - byte[] messageByteArray = gson.toJson(action).getBytes(); - DatagramPacket packet = new DatagramPacket(messageByteArray, messageByteArray.length); - InetSocketAddress reportServerAddress = new InetSocketAddress(reportServerHost, reportServerPort); - packet.setSocketAddress(reportServerAddress); - socket.send(packet); - - } catch (IOException | RuntimeException e) { - e.printStackTrace(); + List neighborUuids = new LinkedList<>(); + for (final Neighbor neighbor : reportIxi.getNeighbors()) { + neighborUuids.add(neighbor.getUuid() != null ? neighbor.getUuid() : ""); } + StatusPayload statusPayload = new StatusPayload( + reportIxi.getProperties().getUuid(), + reportIxi.getProperties().getName(), + Constants.VERSION, + neighborUuids); + send(statusPayload, Constants.RCS_HOST, Constants.RCS_PORT); } }, 0, 60000); submitRandomTransactionTimer.schedule(new TimerTask() { @Override public void run() { - try { - String randomString = randomStringGenerator.nextString(); - - TransactionBuilder t = new TransactionBuilder(); - t.tag = "REPORT9IXI99999999999999999"; - t.asciiMessage(randomString); - reportIxi.submit(t.build()); - - Gson gson = new Gson(); - - JsonObject transaction = new JsonObject(); - transaction.addProperty("uuid", reportIxi.getProperties().getUuid()); - transaction.addProperty("message", randomString); - - JsonObject action = new JsonObject(); - action.addProperty("action", "onTransactionSubmitted"); - action.add("value", transaction); - - byte[] messageByteArray = gson.toJson(action).getBytes(); - DatagramPacket packet = new DatagramPacket(messageByteArray, messageByteArray.length); - InetSocketAddress reportServerAddress = new InetSocketAddress(reportServerHost, reportServerPort); - packet.setSocketAddress(reportServerAddress); - socket.send(packet); - } catch (IOException | RuntimeException e) { - e.printStackTrace(); - } + // Prepare a signed ping payload + PingPayload pingPayload = new PingPayload(randomStringGenerator.nextString()); + SignedPayload signedPayload = new SignedPayload(pingPayload, reportIxi.getKeyPair().getPrivate()); + + String json = Payload.serialize(signedPayload); + + // Broadcast to neighbors + TransactionBuilder t = new TransactionBuilder(); + t.tag = "REPORT9IXI99999999999999999"; + t.asciiMessage(json); + reportIxi.submit(t.build()); + + // Send to RCS + SubmittedPingPayload submittedPingPayload = new SubmittedPingPayload(reportIxi.getProperties().getUuid(), pingPayload); + send(submittedPingPayload, Constants.RCS_HOST, Constants.RCS_PORT); } }, 0, 60000); } - public void reportTransactionReceived(String message) { + public void send(Payload payload, InetAddress address, int port) { + send(payload, new InetSocketAddress(address, port)); + } + + public void send(Payload payload, String host, int port) { + send(payload, new InetSocketAddress(host, port)); + } + + public void send(Payload payload, InetSocketAddress address) { try { - Gson gson = new Gson(); - - JsonObject transaction = new JsonObject(); - transaction.addProperty("uuid", reportIxi.getProperties().getUuid()); - transaction.addProperty("message", message); - - JsonObject action = new JsonObject(); - action.addProperty("action", "onTransactionReceived"); - action.add("value", transaction); - - byte[] messageByteArray = gson.toJson(action).getBytes(); - DatagramPacket packet = new DatagramPacket(messageByteArray, messageByteArray.length); - InetSocketAddress reportServerAddress = new InetSocketAddress(reportServerHost, reportServerPort); - packet.setSocketAddress(reportServerAddress); - socket.send(packet); + byte[] messageByteArray = Payload.serialize(payload).getBytes(); + socket.send(new DatagramPacket(messageByteArray, messageByteArray.length, address)); } catch (IOException | RuntimeException e) { e.printStackTrace(); } diff --git a/src/main/java/com/ictreport/ixi/exchange/MetadataPayload.java b/src/main/java/com/ictreport/ixi/exchange/MetadataPayload.java new file mode 100644 index 0000000..ec0cce4 --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/MetadataPayload.java @@ -0,0 +1,37 @@ +package com.ictreport.ixi.exchange; + +import com.ictreport.ixi.utils.Cryptography; +import org.apache.commons.codec.binary.Base64; + +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; + +public class MetadataPayload extends Payload { + + public final String uuid; + public final String publicKey; + public final String reportIxiVersion; + + public MetadataPayload(final String uuid, final PublicKey publicKey, final String reportIxiVersion) { + + this.uuid = uuid; + this.publicKey = Base64.encodeBase64String(publicKey.getEncoded()); + this.reportIxiVersion = reportIxiVersion; + } + + public String getUuid() { + return uuid; + } + + public PublicKey getPublicKey() { + try { + return Cryptography.getPublicKeyFromBytes(Base64.decodeBase64(publicKey.getBytes())); + } catch (InvalidKeySpecException e) { + return null; + } + } + + public String getReportIxiVersion() { + return reportIxiVersion; + } +} diff --git a/src/main/java/com/ictreport/ixi/exchange/Payload.java b/src/main/java/com/ictreport/ixi/exchange/Payload.java new file mode 100644 index 0000000..87b192a --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/Payload.java @@ -0,0 +1,44 @@ +package com.ictreport.ixi.exchange; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; + +public class Payload { + + public static String serialize(final Payload payload) { + + RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory + .of(Payload.class, "type") + .registerSubtype(MetadataPayload.class, "MetadataPayload") + .registerSubtype(PingPayload.class, "PingPayload") + .registerSubtype(SignedPayload.class, "SignedPayload") + .registerSubtype(StatusPayload.class, "StatusPayload") + .registerSubtype(ReceivedPingPayload.class, "ReceivedPingPayload") + .registerSubtype(SubmittedPingPayload.class, "SubmittedPingPayload"); + + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + + final String json = gson.toJson(payload, Payload.class); + + return json; + } + + public static Payload deserialize(final String json) { + + RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory + .of(Payload.class, "type") + .registerSubtype(MetadataPayload.class, "MetadataPayload") + .registerSubtype(PingPayload.class, "PingPayload") + .registerSubtype(SignedPayload.class, "SignedPayload") + .registerSubtype(StatusPayload.class, "StatusPayload") + .registerSubtype(ReceivedPingPayload.class, "ReceivedPingPayload") + .registerSubtype(SubmittedPingPayload.class, "SubmittedPingPayload"); + + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + + final Payload payload = gson.fromJson(json, Payload.class); + + return payload; + } +} diff --git a/src/main/java/com/ictreport/ixi/exchange/PingPayload.java b/src/main/java/com/ictreport/ixi/exchange/PingPayload.java new file mode 100644 index 0000000..f1d4df6 --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/PingPayload.java @@ -0,0 +1,14 @@ +package com.ictreport.ixi.exchange; + +public class PingPayload extends Payload { + + private final String message; + + public PingPayload(final String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/ictreport/ixi/exchange/ReceivedPingPayload.java b/src/main/java/com/ictreport/ixi/exchange/ReceivedPingPayload.java new file mode 100644 index 0000000..5268c1a --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/ReceivedPingPayload.java @@ -0,0 +1,26 @@ +package com.ictreport.ixi.exchange; + +public class ReceivedPingPayload extends Payload { + + private final String uuid; + private final PingPayload pingPayload; + private final boolean isSenderDirectNeighbor; + + public ReceivedPingPayload(final String uuid, final PingPayload pingPayload, final boolean isSenderDirectNeighbor) { + this.uuid = uuid; + this.pingPayload = pingPayload; + this.isSenderDirectNeighbor = isSenderDirectNeighbor; + } + + public String getUuid() { + return uuid; + } + + public PingPayload getPingPayload() { + return pingPayload; + } + + public boolean isSenderDirectNeighbor() { + return isSenderDirectNeighbor; + } +} diff --git a/src/main/java/com/ictreport/ixi/exchange/SignedPayload.java b/src/main/java/com/ictreport/ixi/exchange/SignedPayload.java new file mode 100644 index 0000000..bd3b6af --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/SignedPayload.java @@ -0,0 +1,34 @@ +package com.ictreport.ixi.exchange; + +import com.ictreport.ixi.utils.Cryptography; +import org.apache.commons.codec.binary.Base64; + +import java.security.PrivateKey; +import java.security.PublicKey; + +public class SignedPayload extends Payload { + + private final Payload payload; + private final String signature; + + public SignedPayload(final Payload payload, final PrivateKey privateKey) { + this.payload = payload; + + final String serializedPayload = Payload.serialize(payload); + this.signature = Base64.encodeBase64String(Cryptography.sign(serializedPayload.getBytes(), privateKey)); + } + + public boolean verify(final PublicKey publicKey) { + + final String serializedPayload = Payload.serialize(payload); + return Cryptography.verify(serializedPayload.getBytes(), Base64.decodeBase64(signature), publicKey); + } + + public Payload getPayload() { + return payload; + } + + public String getSignature() { + return signature; + } +} diff --git a/src/main/java/com/ictreport/ixi/exchange/StatusPayload.java b/src/main/java/com/ictreport/ixi/exchange/StatusPayload.java new file mode 100644 index 0000000..92a1741 --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/StatusPayload.java @@ -0,0 +1,34 @@ +package com.ictreport.ixi.exchange; + +import java.util.List; + +public class StatusPayload extends Payload { + + public final String uuid; + public final String name; + public final String reportIxiVersion; + public List neighborUuids; + + public StatusPayload(final String uuid, final String name, final String reportIxiVersion, final List neighborUuids) { + this.uuid = uuid; + this.name = name; + this.reportIxiVersion = reportIxiVersion; + this.neighborUuids = neighborUuids; + } + + public String getUuid() { + return uuid; + } + + public String getName() { + return name; + } + + public String getReportIxiVersion() { + return reportIxiVersion; + } + + public List getNeighborUuids() { + return neighborUuids; + } +} diff --git a/src/main/java/com/ictreport/ixi/exchange/SubmittedPingPayload.java b/src/main/java/com/ictreport/ixi/exchange/SubmittedPingPayload.java new file mode 100644 index 0000000..2048d2d --- /dev/null +++ b/src/main/java/com/ictreport/ixi/exchange/SubmittedPingPayload.java @@ -0,0 +1,20 @@ +package com.ictreport.ixi.exchange; + +public class SubmittedPingPayload extends Payload { + + private final String uuid; + private final PingPayload pingPayload; + + public SubmittedPingPayload(final String uuid, final PingPayload pingPayload) { + this.uuid = uuid; + this.pingPayload = pingPayload; + } + + public String getUuid() { + return uuid; + } + + public PingPayload getPingPayload() { + return pingPayload; + } +} diff --git a/src/main/java/com/ictreport/ixi/model/Neighbor.java b/src/main/java/com/ictreport/ixi/model/Neighbor.java index e4cd89b..13943bd 100644 --- a/src/main/java/com/ictreport/ixi/model/Neighbor.java +++ b/src/main/java/com/ictreport/ixi/model/Neighbor.java @@ -9,6 +9,7 @@ public class Neighbor { private int reportPort = -1; private String uuid = null; private PublicKey publicKey = null; + private String reportIxiVersion = null; public Neighbor(InetAddress address, int reportPort) { this.address = address; @@ -71,4 +72,11 @@ public void setAddress(InetAddress address) { this.address = address; } + public String getReportIxiVersion() { + return reportIxiVersion; + } + + public void setReportIxiVersion(String reportIxiVersion) { + this.reportIxiVersion = reportIxiVersion; + } } \ No newline at end of file diff --git a/src/main/java/com/ictreport/ixi/utils/Constants.java b/src/main/java/com/ictreport/ixi/utils/Constants.java new file mode 100644 index 0000000..68615ec --- /dev/null +++ b/src/main/java/com/ictreport/ixi/utils/Constants.java @@ -0,0 +1,7 @@ +package com.ictreport.ixi.utils; + +public class Constants { + public static final String VERSION = "0.2"; + public static final String RCS_HOST = "api.ictreport.com"; + public static final int RCS_PORT = 14265; +} \ No newline at end of file diff --git a/src/main/java/com/ictreport/ixi/utils/Cryptography.java b/src/main/java/com/ictreport/ixi/utils/Cryptography.java index c5f437b..8a5c2c7 100644 --- a/src/main/java/com/ictreport/ixi/utils/Cryptography.java +++ b/src/main/java/com/ictreport/ixi/utils/Cryptography.java @@ -1,6 +1,8 @@ package com.ictreport.ixi.utils; import org.apache.commons.codec.binary.Base64; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -8,9 +10,14 @@ import javax.crypto.NoSuchPaddingException; import java.nio.charset.StandardCharsets; import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; public class Cryptography { + private final static Logger LOGGER = LogManager.getLogger(Cryptography.class); + /** * Generates a new key pair * @param length @@ -53,4 +60,58 @@ public static String decryptText(String text, Key key) cipher.init(Cipher.DECRYPT_MODE, key); return new String(cipher.doFinal(Base64.decodeBase64(text)), StandardCharsets.UTF_8); } + + public static PrivateKey getPrivateKeyFromBytes(final byte[] bytes) throws InvalidKeySpecException { + + try { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return null; + } + + public static PublicKey getPublicKeyFromBytes(final byte[] bytes) throws InvalidKeySpecException { + + try { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return null; + } + + public static byte[] sign(byte[] bytes, PrivateKey key) { + + try { + Signature sig = Signature.getInstance("SHA1WithRSA"); + sig.initSign(key); + sig.update(bytes); + return sig.sign(); + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + e.printStackTrace(); + } + + return null; + } + + public static boolean verify(byte[] bytes, byte[] signature, PublicKey key) { + + try { + Signature sig = Signature.getInstance("SHA1WithRSA"); + sig.initVerify(key); + sig.update(bytes); + return sig.verify(signature); + } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { + e.printStackTrace(); + } + + return false; + } } diff --git a/src/main/java/com/ictreport/ixi/utils/Properties.java b/src/main/java/com/ictreport/ixi/utils/Properties.java index aca07ab..6ed002c 100644 --- a/src/main/java/com/ictreport/ixi/utils/Properties.java +++ b/src/main/java/com/ictreport/ixi/utils/Properties.java @@ -7,14 +7,14 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Collections; -import java.util.Enumeration; -import java.util.InvalidPropertiesFormatException; -import java.util.Vector; +import java.net.InetSocketAddress; +import java.util.*; public class Properties extends java.util.Properties { private final static Logger LOGGER = LogManager.getLogger(Properties.class); + private static final String LIST_DELIMITER = ","; + // Property names private final static String MODULE_NAME = "moduleName"; private final static String ICT_NAME = "ictName"; @@ -22,6 +22,7 @@ public class Properties extends java.util.Properties { private final static String UUID = "uuid"; private final static String HOST = "host"; private final static String REPORT_PORT = "reportPort"; + private final static String NEIGHBORS = "neighbors"; private final static String NEIGHBOR_A_HOST = "neighborAHost"; private final static String NEIGHBOR_A_PORT = "neighborAPort"; private final static String NEIGHBOR_B_HOST = "neighborBHost"; @@ -36,6 +37,7 @@ public class Properties extends java.util.Properties { private final static String DEFAULT_UUID = java.util.UUID.randomUUID().toString(); private final static String DEFAULT_HOST = "0.0.0.0"; private final static int DEFAULT_REPORT_PORT = 1338; + private final static String DEFAULT_NEIGHBORS = ""; private final static String DEFAULT_NEIGHBOR_A_HOST = ""; private final static int DEFAULT_NEIGHBOR_A_PORT = 1338; private final static String DEFAULT_NEIGHBOR_B_HOST = ""; @@ -56,6 +58,8 @@ public Properties(final String propertiesFilePath) { // Add required properties setRequiredProps(); + + LOGGER.info("Neighbors: " + getNeighborAddresses()); } /** @@ -160,64 +164,9 @@ public void setName(final String name) throws InvalidPropertiesFormatException { put(NAME, name); } - public String getNeighborAHost() { - - return getProperty(NEIGHBOR_A_HOST, DEFAULT_NEIGHBOR_A_HOST).trim(); - } - - public void setNeighborAHost(final String host) { - - put(NEIGHBOR_A_HOST, host); - } - - public int getNeighborAPort() { - - return Integer.parseInt(getProperty(NEIGHBOR_A_PORT, Integer.toString(DEFAULT_NEIGHBOR_A_PORT)).trim()); - } - - public void setNeighborAPort(final int port) { - - put(NEIGHBOR_A_PORT, Integer.toString(port)); - } - - public String getNeighborBHost() { - - return getProperty(NEIGHBOR_B_HOST, DEFAULT_NEIGHBOR_B_HOST).trim(); - } - - public void setNeighborBHost(final String host) { - - put(NEIGHBOR_B_HOST, host); - } - - public int getNeighborBPort() { - - return Integer.parseInt(getProperty(NEIGHBOR_B_PORT, Integer.toString(DEFAULT_NEIGHBOR_B_PORT)).trim()); - } - - public void setNeighborBPort(final int port) { + public List getNeighborAddresses() { - put(NEIGHBOR_B_PORT, Integer.toString(port)); - } - - public String getNeighborCHost() { - - return getProperty(NEIGHBOR_C_HOST, DEFAULT_NEIGHBOR_C_HOST).trim(); - } - - public void setNeighborCHost(final String host) { - - put(NEIGHBOR_C_HOST, host); - } - - public int getNeighborCPort() { - - return Integer.parseInt(getProperty(NEIGHBOR_C_PORT, Integer.toString(DEFAULT_NEIGHBOR_C_PORT)).trim()); - } - - public void setNeighborCPort(final int port) { - - put(NEIGHBOR_C_PORT, Integer.toString(port)); + return neighborsFromString(getProperty(NEIGHBORS)); } public void load(String propertiesFilePath) { @@ -289,6 +238,9 @@ private void setRequiredProps() { if (get(REPORT_PORT) == null) { put(REPORT_PORT, Integer.toString(DEFAULT_REPORT_PORT)); } + if (get(NEIGHBORS) == null) { + put(NEIGHBORS, DEFAULT_NEIGHBORS); + } if (get(NEIGHBOR_A_HOST) == null) { put(NEIGHBOR_A_HOST, DEFAULT_NEIGHBOR_A_HOST); } @@ -319,4 +271,36 @@ public Enumeration keys() { Collections.sort(keyList); return keyList.elements(); } + + private static List neighborsFromString(String string) { + List addresses = stringListFromString(string); + + List neighbors = new LinkedList<>(); + for (String address : addresses) { + try { + neighbors.add(inetSocketAddressFromString(address)); + } catch (Throwable t) { + LOGGER.error(String.format("Invalid neighbor address: '%s'", address)); + } + } + return neighbors; + } + + private static List stringListFromString(String string) { + List stringList = new LinkedList<>(); + for (String element : string.split(LIST_DELIMITER)) { + if (element.length() == 0) + continue; + stringList.add(element); + } + return stringList; + } + + private static InetSocketAddress inetSocketAddressFromString(String address) { + int portColonIndex; + for (portColonIndex = address.length() - 1; address.charAt(portColonIndex) != ':'; portColonIndex--) ; + String hostString = address.substring(0, portColonIndex); + int port = Integer.parseInt(address.substring(portColonIndex + 1, address.length())); + return new InetSocketAddress(hostString, port); + } } \ No newline at end of file diff --git a/src/test/java/com.ictreport.ixi/utils/ApiTest.java b/src/test/java/com.ictreport.ixi/utils/ApiTest.java deleted file mode 100644 index b574167..0000000 --- a/src/test/java/com.ictreport.ixi/utils/ApiTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.ictreport.ixi.utils; - -import com.ictreport.ixi.api.Api; -import com.ictreport.ixi.api.Receiver; -import com.sun.org.apache.xpath.internal.operations.Bool; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.SocketException; - -public class ApiTest { - - @Test - public void testReceivePacket() { - - final Object waitObject = new Object(); - final boolean[] isPacketReceived = {false}; - - ReportIxi reportIxi = new ReportIxi(); - Api api = new Api(reportIxi); - api.init(); - - api.getReceiver().addOnIncomingPacketListener(new Receiver.IIncomingPacketListener() { - @Override - public void OnIncomingPacket(DatagramPacket packet) { - synchronized(waitObject) { - waitObject.notify(); - isPacketReceived[0] = true; - } - } - }); - - DatagramSocket socket = null; - try { - socket = new DatagramSocket(); - } catch (SocketException e) { - e.printStackTrace(); - } - - byte[] payload = "API test payload".getBytes(); - DatagramPacket packet = new DatagramPacket(payload, payload.length, - api.getAddress().getAddress(), api.getAddress().getPort()); - - try { - socket.send(packet); - } catch (IOException e) { - e.printStackTrace(); - Assert.fail("Failed to send packet"); - } - - System.out.println("Waiting for packet..."); - synchronized(waitObject) { - try { - waitObject.wait(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (!isPacketReceived[0]) { - Assert.fail("No packet received"); - } - } - - api.shutDown(); - } -} diff --git a/src/test/java/com.ictreport.ixi/utils/CryptographyTest.java b/src/test/java/com.ictreport.ixi/utils/CryptographyTest.java index 63b661f..16615d5 100644 --- a/src/test/java/com.ictreport.ixi/utils/CryptographyTest.java +++ b/src/test/java/com.ictreport.ixi/utils/CryptographyTest.java @@ -1,14 +1,16 @@ package com.ictreport.ixi.utils; +import org.apache.commons.codec.binary.Base64; import org.junit.Assert; import org.junit.Test; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; public class CryptographyTest { @@ -45,4 +47,54 @@ public void testConfidentiality() { e.printStackTrace(); } } + + @Test + public void testPrivateKeySerialization() { + try { + KeyPair keyPair = Cryptography.generateKeyPair(1024); + byte[] encodedPrivateKey = keyPair.getPrivate().getEncoded(); + byte[] encodedPublicKey = keyPair.getPublic().getEncoded(); + + PrivateKey decodedPrivateKey = null; + PublicKey decodedPublicKey = null; + + try { + decodedPrivateKey = Cryptography.getPrivateKeyFromBytes(encodedPrivateKey); + + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + + try { + decodedPublicKey = Cryptography.getPublicKeyFromBytes(encodedPublicKey); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + + Assert.assertEquals(keyPair.getPrivate(), decodedPrivateKey); + Assert.assertEquals(keyPair.getPublic(), decodedPublicKey); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + @Test + public void testSignAndVerify() { + + try { + KeyPair keyPair = Cryptography.generateKeyPair(1024); + final byte[] data = "test".getBytes(StandardCharsets.UTF_8); + + final byte[] signature = Cryptography.sign(data, keyPair.getPrivate()); + System.out.println("Signature:" + Base64.encodeBase64String(signature)); + + final boolean verifyResult = Cryptography.verify(data, signature, keyPair.getPublic()); + + Assert.assertTrue(verifyResult); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } } diff --git a/src/test/java/com/ictreport/ixi/exchange/PayloadTest.java b/src/test/java/com/ictreport/ixi/exchange/PayloadTest.java new file mode 100644 index 0000000..d4632a6 --- /dev/null +++ b/src/test/java/com/ictreport/ixi/exchange/PayloadTest.java @@ -0,0 +1,102 @@ +package com.ictreport.ixi.exchange; + +import com.ictreport.ixi.ReportIxi; +import com.ictreport.ixi.utils.Constants; +import com.ictreport.ixi.utils.Cryptography; +import org.junit.Assert; +import org.junit.Test; + +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; + +public class PayloadTest { + + @Test + public void testMetadataPayload() { + + try { + + final KeyPair keyPair = Cryptography.generateKeyPair(1024); + + MetadataPayload metadataPayload = new MetadataPayload("abc", keyPair.getPublic(), Constants.VERSION); + + final String json = Payload.serialize(metadataPayload); + + final Payload deserializedPayload = Payload.deserialize(json); + + if (deserializedPayload instanceof MetadataPayload) { + MetadataPayload deserializedMetadataPayload = (MetadataPayload) deserializedPayload; + Assert.assertEquals(deserializedMetadataPayload.getUuid(), metadataPayload.getUuid()); + Assert.assertEquals(deserializedMetadataPayload.getPublicKey(), metadataPayload.getPublicKey()); + } else { + Assert.fail("Deserialization of polymorphism object failed."); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + Assert.fail("KeyPair generation failed."); + } + } + + @Test + public void testPingPayload() { + + try { + + final KeyPair keyPair = Cryptography.generateKeyPair(1024); + + PingPayload pingPayload = new PingPayload("abc"); + + final String json = Payload.serialize(pingPayload); + + + final Payload deserializedPayload = Payload.deserialize(json); + + if (deserializedPayload instanceof PingPayload) { + PingPayload deserializedPingPayload = (PingPayload) deserializedPayload; + Assert.assertEquals(deserializedPingPayload.getMessage(), pingPayload.getMessage()); + } else { + Assert.fail("Deserialization of polymorphism object failed."); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + Assert.fail("KeyPair generation failed."); + } + } + + @Test + public void testSignedPingPayload() { + + try { + + final KeyPair keyPair = Cryptography.generateKeyPair(1024); + + PingPayload pingPayload = new PingPayload("abc"); + + SignedPayload signedPayload = new SignedPayload(pingPayload, keyPair.getPrivate()); + + final String json = Payload.serialize(signedPayload); + + final Payload deserializedPayload = Payload.deserialize(json); + + // Determine what kind of payload it is. + if (deserializedPayload instanceof SignedPayload) { + SignedPayload deserializedSignedPayload = (SignedPayload) deserializedPayload; + + Assert.assertNotNull(deserializedSignedPayload.getPayload()); + Assert.assertNotNull(deserializedSignedPayload.getSignature()); + Assert.assertTrue(deserializedSignedPayload.verify(keyPair.getPublic())); + + if (deserializedSignedPayload.getPayload() instanceof PingPayload) { + PingPayload deserializedPingPayload = (PingPayload) deserializedSignedPayload.getPayload(); + + Assert.assertEquals(deserializedPingPayload.getMessage(), pingPayload.getMessage()); + } + } else { + Assert.fail("Deserialization of polymorphism object failed."); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + Assert.fail("KeyPair generation failed."); + } + } +}