summaryrefslogtreecommitdiff
path: root/src/org/noreply/fancydress/type3
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/noreply/fancydress/type3')
-rw-r--r--src/org/noreply/fancydress/type3/ForwardLeg.java30
-rw-r--r--src/org/noreply/fancydress/type3/HalfPath.java12
-rw-r--r--src/org/noreply/fancydress/type3/Hop.java22
-rw-r--r--src/org/noreply/fancydress/type3/Packet.java78
-rw-r--r--src/org/noreply/fancydress/type3/Path.java39
-rw-r--r--src/org/noreply/fancydress/type3/Payload.java93
-rw-r--r--src/org/noreply/fancydress/type3/SURB.java25
-rw-r--r--src/org/noreply/fancydress/type3/SingleLeg.java144
-rw-r--r--src/org/noreply/fancydress/type3/routing/Routing.java44
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingDROP.java39
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingDestination.java26
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingForward.java26
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingHOST.java102
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingIP4.java110
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingSMTP.java63
-rw-r--r--src/org/noreply/fancydress/type3/routing/RoutingType.java32
16 files changed, 885 insertions, 0 deletions
diff --git a/src/org/noreply/fancydress/type3/ForwardLeg.java b/src/org/noreply/fancydress/type3/ForwardLeg.java
new file mode 100644
index 0000000..4088d11
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/ForwardLeg.java
@@ -0,0 +1,30 @@
+package org.noreply.fancydress.type3;
+
+import org.noreply.fancydress.type3.routing.*;
+import org.noreply.fancydress.status.*;
+import org.noreply.fancydress.crypto.*;
+
+public class ForwardLeg extends SingleLeg {
+ private byte[][] sharedKeys;
+
+ public ForwardLeg (HalfPath path, Routing finalRouting)
+ throws Mix3BadArgumentsChainTooLongException
+ {
+ super();
+ Hop[] hops = path.getHops();
+
+ initSharedKeys(hops.length);
+ makeLeg(hops, sharedKeys, finalRouting);
+ };
+
+ private void initSharedKeys(int n) {
+ sharedKeys = new byte[n][];
+
+ for (int i=0; i < n; i++)
+ sharedKeys[i] = CryptoPrimitives.rand(CryptoPrimitives.KEY_LEN);
+ }
+
+ public byte[][] getSharedKeys() {
+ return sharedKeys;
+ }
+}
diff --git a/src/org/noreply/fancydress/type3/HalfPath.java b/src/org/noreply/fancydress/type3/HalfPath.java
new file mode 100644
index 0000000..66e712b
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/HalfPath.java
@@ -0,0 +1,12 @@
+package org.noreply.fancydress.type3;
+
+public class HalfPath {
+ private Hop[] hops;
+ public HalfPath(Hop[] hops) { /* FIXME */
+ this.hops = hops;
+ }
+ public Hop[] getHops() {
+ return hops;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/Hop.java b/src/org/noreply/fancydress/type3/Hop.java
new file mode 100644
index 0000000..a9d7dda
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/Hop.java
@@ -0,0 +1,22 @@
+package org.noreply.fancydress.type3;
+
+import org.noreply.fancydress.type3.routing.*;
+import org.noreply.fancydress.crypto.*;
+
+public class Hop {
+ private Routing routing;
+ private RSAPublicKey pubKey;
+
+ public Hop(Routing routing, RSAPublicKey pubKey) {
+ this.routing = routing;
+ this.pubKey = pubKey;
+ }
+
+ public Routing getRouting() {
+ return routing;
+ }
+
+ public RSAPublicKey getPubKey() {
+ return pubKey;
+ }
+}
diff --git a/src/org/noreply/fancydress/type3/Packet.java b/src/org/noreply/fancydress/type3/Packet.java
new file mode 100644
index 0000000..3d76cbe
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/Packet.java
@@ -0,0 +1,78 @@
+package org.noreply.fancydress.type3;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.type3.routing.*;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.status.*;
+import java.util.*;
+
+public class Packet {
+
+ private byte[] packet;
+ private Routing thisRoute;
+
+ public Packet(
+ Path path,
+ RoutingDestination address,
+ Payload payload)
+ throws Mix3BadArgumentsChainTooLongException
+ {
+ ForwardLeg leg2 = new ForwardLeg(path.getSecondHalf(), address);
+ if (!(leg2.getRoute() instanceof RoutingForward))
+ throw new Error("Routing Type of second leg is not a forward type.");
+ ForwardLeg leg1 = new ForwardLeg(path.getFirstHalf(), ((RoutingForward)leg2.getRoute()).asSwap());
+ makePacket(leg1, leg2, payload);
+ }
+
+ public Packet(
+ HalfPath path,
+ SURB surb,
+ Payload payload)
+ throws Mix3BadArgumentsChainTooLongException
+ {
+ ForwardLeg leg1 = new ForwardLeg(path, surb.getRoute());
+ makePacket(leg1, surb, payload);
+ }
+
+ public static boolean isPacketVersionSupported(String s) {
+ return s.equals(SingleLeg.MAJOR_VERSION + "." + SingleLeg.MINOR_VERSION);
+ }
+
+ private void makePacket(ForwardLeg leg1, SingleLeg leg2, Payload payload) {
+ byte p[] = payload.asOctets();
+ byte h1[] = leg1.asOctets();
+ byte h2[] = leg2.asOctets();
+
+ // Phase 1
+ if (leg2 instanceof SURB) {
+ byte[] k = ((SURB) leg2).getEncryptionKey();
+ p = CryptoPrimitives.sprpDecrypt(k, "PAYLOAD ENCRYPT", p);
+ } else {
+ byte[][] sk = ((ForwardLeg) leg2).getSharedKeys();
+ for (int i=sk.length-1; i>=0; i--)
+ p = CryptoPrimitives.sprpEncrypt(sk[i], "PAYLOAD ENCRYPT", p);
+ }
+
+ // Phase 2
+ h2 = CryptoPrimitives.sprpEncrypt(CryptoPrimitives.hash(p), "HIDE HEADER", h2);
+ p = CryptoPrimitives.sprpEncrypt(CryptoPrimitives.hash(h2), "HIDE PAYLOAD", p);
+
+ byte[][] sk = leg1.getSharedKeys();
+ for (int i=sk.length-1; i>=0; i--) {
+ h2 = CryptoPrimitives.sprpEncrypt(sk[i], "HEADER ENCRYPT", h2);
+ p = CryptoPrimitives.sprpEncrypt(sk[i], "PAYLOAD ENCRYPT", p);
+ }
+
+ packet = Util.concat(Util.concat(h1,h2),p);
+ thisRoute = leg1.getRoute();
+ }
+
+ public Routing getRoute() {
+ return thisRoute;
+ }
+
+ public byte[] asOctets() {
+ return packet;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/Path.java b/src/org/noreply/fancydress/type3/Path.java
new file mode 100644
index 0000000..11fab1f
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/Path.java
@@ -0,0 +1,39 @@
+package org.noreply.fancydress.type3;
+
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.directory.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.type3.routing.*;
+import java.net.InetAddress;
+
+public class Path {
+ HalfPath first;
+ HalfPath second;
+
+ public Path(Directory dir) throws Exception {
+ Hop[] hops1 = new Hop[4];
+ Hop[] hops2 = new Hop[2];
+
+ Server test = dir.getServer("test1");
+ ServerDescriptor desc = test.getDescriptor();
+
+ Routing route1 = new RoutingIP4(InetAddress.getByName("172.22.118.2"), 48099, test.getKeyID());
+ hops1[0] = new Hop(route1, desc.getPacketKey());
+ hops1[1] = new Hop(route1, desc.getPacketKey());
+ hops1[2] = new Hop(route1, desc.getPacketKey());
+ hops1[3] = new Hop(route1, desc.getPacketKey());
+ hops2[0] = new Hop(route1, desc.getPacketKey());
+ hops2[1] = new Hop(route1, desc.getPacketKey());
+
+ first = new HalfPath(hops1);
+ second = new HalfPath(hops2);
+ }
+
+ public HalfPath getFirstHalf() {
+ return first;
+ }
+ public HalfPath getSecondHalf() {
+ return second;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/Payload.java b/src/org/noreply/fancydress/type3/Payload.java
new file mode 100644
index 0000000..f0cf583
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/Payload.java
@@ -0,0 +1,93 @@
+package org.noreply.fancydress.type3;
+
+import java.io.*;
+import java.util.zip.*;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.type3.routing.*;
+
+public class Payload {
+ byte[] thisPayload;
+
+ public Payload() {
+ RoutingSMTP route = new RoutingSMTP("peter@palfrader.org");
+ String body = "FROM:Peter\n" +
+ "SUBJECT:test fancydress\n" +
+ "\n" +
+ "blubb\n";
+
+ /*
+ // Encode a String into bytes
+ String inputString = "blahblahblah??";
+ byte[] input = inputString.getBytes("UTF-8");
+
+ // Compress the bytes
+ byte[] output = new byte[100];
+ Deflater compresser = new Deflater();
+ compresser.setInput(input);
+ compresser.finish();
+ int compressedDataLength = compresser.deflate(output);
+
+ // Decompress the bytes
+ Inflater decompresser = new Inflater();
+ decompresser.setInput(output, 0, compressedDataLength);
+ byte[] result = new byte[100];
+ int resultLength = decompresser.inflate(result);
+ decompresser.end();
+ */
+ try {
+ PipedOutputStream poMessage = new PipedOutputStream();
+ PipedInputStream piMessage = new PipedInputStream( poMessage );
+
+ Deflater compresser = new Deflater(Deflater.BEST_COMPRESSION);
+ DeflaterOutputStream compresserStream = new DeflaterOutputStream(poMessage, compresser);
+
+ /* FIXME, do a more clever and robust way of reading/writing */
+
+ byte[] message = Util.toOctets(body);
+ compresserStream.write(message, 0, message.length);
+ compresserStream.close();
+
+ byte[] compressed = new byte[message.length]; // UGH
+ int read = piMessage.read(compressed,0,compressed.length);
+ compressed = Util.slice(compressed, 0, read);
+
+ byte[] padding = CryptoPrimitives.rand( 28*1024 - compressed.length - 22);
+
+ byte[] payload = new byte[28*1024];
+ int pos = 0;
+
+ int firstWord = compressed.length;
+
+ payload[pos] = (byte) ( (firstWord >> 8) & 0xff);
+ pos++;
+ payload[pos] = (byte) ( firstWord & 0xff);
+ pos++;
+
+ System.arraycopy(CryptoPrimitives.hash(compressed, padding), 0, payload, pos, CryptoPrimitives.HASH_LEN);
+ pos += CryptoPrimitives.HASH_LEN;
+
+ System.arraycopy(compressed, 0, payload, pos, compressed.length);
+ pos += compressed.length;
+
+ System.arraycopy(padding, 0, payload, pos, padding.length);
+ pos += padding.length;
+
+ if (pos != 28*1024)
+ throw new Error("did not fill in 28k bytes");
+
+ thisPayload = payload;
+ } catch (Exception e) {
+ throw new Error(e);
+ };
+
+ // Return a singleton payload containing:
+ // Flag 0 | Int(15,LEN(M_C)) | Hash(M_C | PADDING) | M_C | PADDING
+
+ }
+
+ public byte[] asOctets() {
+ return thisPayload;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/SURB.java b/src/org/noreply/fancydress/type3/SURB.java
new file mode 100644
index 0000000..914c3da
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/SURB.java
@@ -0,0 +1,25 @@
+package org.noreply.fancydress.type3;
+
+import org.noreply.fancydress.type3.routing.*;
+import org.noreply.fancydress.status.*;
+import org.noreply.fancydress.crypto.*;
+
+public class SURB extends SingleLeg {
+
+ public SURB (Path path, RoutingDestination address) throws Mix3BadArgumentsChainTooLongException {
+ super();
+
+ byte[][] sharedKeys = {};
+ Hop[] hops = new Hop[0]; // path.getHops();
+
+ /* FIXME */
+
+ makeLeg ( hops, sharedKeys, address);
+
+ };
+
+ public byte[] getEncryptionKey() {
+ return new byte[16];
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/SingleLeg.java b/src/org/noreply/fancydress/type3/SingleLeg.java
new file mode 100644
index 0000000..f4a68c6
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/SingleLeg.java
@@ -0,0 +1,144 @@
+package org.noreply.fancydress.type3;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.type3.routing.*;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.status.*;
+import java.util.*;
+
+public abstract class SingleLeg {
+ public static final int MIN_SH = 42; /* length of the invariant part of a subheader */
+ public static final int SINGLE_HEADER_LEN = 2048; /* length of the invariant part of a subheader */
+ private byte[] thisLeg = null;
+ private Routing thisRoute = null;
+
+ public static final int MAJOR_VERSION = 0;
+ public static final int MINOR_VERSION = 3;
+
+ public SingleLeg() {
+ }
+
+ public byte[] asOctets() {
+ if (thisLeg == null)
+ throw new Error("Leg not yet initialized");
+ return thisLeg;
+ }
+
+ protected void makeLeg(
+ Hop[] hops,
+ byte[][] sharedKeys,
+ Routing finalRouting)
+ throws Mix3BadArgumentsChainTooLongException
+ {
+ if (thisLeg != null)
+ throw new Error("Leg already initialized");
+
+ int n = hops.length;
+ int[] size = new int[n];
+ byte[][] junkKey = new byte[n][];
+ byte[][] key = new byte[n][];
+ byte[][] junk = new byte[n][];
+ byte[][] subHeader = new byte[n+1][];
+
+ // Calculate the sizes of the subheaders
+ int paddingLength = SINGLE_HEADER_LEN;
+ for (int i=0; i < n; i++) {
+ Routing routing = i == n-1 ? finalRouting : hops[i+1].getRouting();
+
+ size[i] = MIN_SH + RSAPublicKey.PK_OVERHEAD_LEN + routing.getRoutingInformationLength();
+ junkKey[i] = CryptoPrimitives.subKey(sharedKeys[i], "RANDOM JUNK");
+ key[i] = CryptoPrimitives.subKey(sharedKeys[i], "HEADER SECRET KEY");
+
+ paddingLength -= size[i];
+ }
+
+ if (paddingLength < 0)
+ throw new Mix3BadArgumentsChainTooLongException("Chain too long");
+
+ // Calculate the Junk that will be appended during processing.
+ // J_i is the junk that node i will append, and node i+1 will see.
+ for (int i=0; i < n; i++) {
+ byte[] tmp = CryptoPrimitives.prng(junkKey[i], size[i]);
+ junk[i] = i == 0 ? tmp : Util.concat(junk[i-1], tmp);
+ byte[] stream = CryptoPrimitives.prng(key[i], SINGLE_HEADER_LEN + size[i]);
+ // Before we encrypt the junk, we encrypt all the data, and all
+ // the initial padding, but not the RSA-encrypted part.
+ int offset = SINGLE_HEADER_LEN - RSAPublicKey.PK_ENC_LEN - (i==0 ? 0 : junk[i-1].length);
+ junk[i] = CryptoPrimitives.xor(junk[i], Util.slice(stream, offset, junk[i].length));
+ }
+
+ // Create the Header, starting with the padding
+ subHeader[n] = CryptoPrimitives.rand(paddingLength);
+ for (int i=n-1; i>=0; i--) {
+ Routing routing = i == n-1 ? finalRouting : hops[i+1].getRouting();
+
+ byte[] sh0 = makeSHS(sharedKeys[i], CryptoPrimitives.zero(CryptoPrimitives.HASH_LEN), routing);
+ int shLength = sh0.length;
+ byte[] h0 = Util.concat( sh0, subHeader[i+1] );
+
+ byte[] rest = Util.slice( h0, RSAPublicKey.PK_MAX_DATA_LEN, h0.length - RSAPublicKey.PK_MAX_DATA_LEN);
+ byte[] encryptedRest = CryptoPrimitives.encrypt(key[i], rest);
+
+ byte[] digest = CryptoPrimitives.hash( i == 0 ? encryptedRest : Util.concat(encryptedRest, junk[i-1]));
+
+ byte[] sh = makeSHS(sharedKeys[i], digest, routing);
+ int underflow = Util.max(RSAPublicKey.PK_MAX_DATA_LEN - shLength, 0);
+ byte[] rsaPart = Util.concat( sh, Util.slice(h0, RSAPublicKey.PK_MAX_DATA_LEN - underflow, underflow));
+
+ RSAPublicKey pk = hops[i].getPubKey();
+ byte[] esh = pk.encrypt(rsaPart);
+ subHeader[i] = Util.concat(esh, encryptedRest);
+ }
+ byte[] result = subHeader[0];
+
+ thisLeg = result;
+ thisRoute = hops[0].getRouting();
+ }
+
+
+ /**
+ * create a sub header structure.
+ */
+ static private byte[] makeSHS(
+ byte[] sharedSecret,
+ byte[] digest,
+ Routing routing)
+ {
+ if (sharedSecret.length != CryptoPrimitives.KEY_LEN)
+ throw new Error("sharedSecret must be KEY_LEN bytes long.");
+ if (digest.length != CryptoPrimitives.HASH_LEN)
+ throw new Error("digest must be HASH_LEN bytes long.");
+ byte[] fixedPart = new byte[MIN_SH];
+ byte[] dynamicPart = routing.getRoutingInformation();
+ int routingSize = dynamicPart.length;
+ int routingType = routing.getRoutingType();
+
+ int pos = 0;
+ fixedPart[pos] = MAJOR_VERSION;
+ pos++;
+ fixedPart[pos] = MINOR_VERSION;
+ pos++;
+ System.arraycopy(sharedSecret, 0, fixedPart, pos, CryptoPrimitives.KEY_LEN);
+ pos +=CryptoPrimitives.KEY_LEN;
+ System.arraycopy(digest, 0, fixedPart, pos, CryptoPrimitives.HASH_LEN);
+ pos +=CryptoPrimitives.HASH_LEN;
+ fixedPart[pos] = (byte) ( (routingSize >> 8) & 0xff);
+ pos++;
+ fixedPart[pos] = (byte) ( routingSize & 0xff);
+ pos++;
+ fixedPart[pos] = (byte) ( (routingType >> 8) & 0xff);
+ pos++;
+ fixedPart[pos] = (byte) ( routingType & 0xff);
+ pos++;
+
+ if (pos != MIN_SH)
+ throw new Error("Did not fill in MIN_SH bytes!");
+
+ return Util.concat(fixedPart, dynamicPart);
+ }
+
+ public Routing getRoute() {
+ return thisRoute;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/Routing.java b/src/org/noreply/fancydress/type3/routing/Routing.java
new file mode 100644
index 0000000..c87bb53
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/Routing.java
@@ -0,0 +1,44 @@
+package org.noreply.fancydress.type3.routing;
+
+/**
+ * Base class for all Routing classes.
+ *
+ * Routing Type and Routing Information are pooled into this one entity.
+ *
+ * @see RoutingType
+ */
+public abstract class Routing {
+ protected RoutingType type;
+
+ /**
+ * Default constructor.
+ *
+ * @param type The routing type as integer.
+ */
+ protected Routing(int type) {
+ this.type = new RoutingType(type);
+ }
+
+ /**
+ * Return the total length in octets of the routing information.
+ *
+ * @return total length in octets of the routing information
+ */
+ public abstract int getRoutingInformationLength();
+
+ /**
+ * Return the routing information.
+ *
+ * @return routing information
+ */
+ public abstract byte[] getRoutingInformation();
+
+ /**
+ * Get the routing type of this instance.
+ *
+ * @return routing type
+ */
+ public int getRoutingType() {
+ return type.getType();
+ }
+}
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingDROP.java b/src/org/noreply/fancydress/type3/routing/RoutingDROP.java
new file mode 100644
index 0000000..942a640
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingDROP.java
@@ -0,0 +1,39 @@
+package org.noreply.fancydress.type3.routing;
+
+/**
+ * The DROP routing as specified in the type III spec.
+ *
+ * DROP packets are dropped by the handling server. Their purpose
+ * is to generate dummy traffic.
+ *
+ * The payload of a DROP packet should be random garbage.
+ */
+public class RoutingDROP extends RoutingDestination {
+
+ /**
+ * Construct a DROP routing.
+ */
+ public RoutingDROP() {
+ super (RoutingType.DROP);
+ }
+
+ /**
+ * Return the total length in octets of the routing information.
+ *
+ * @return total length in octets of the routing information
+ */
+ public int getRoutingInformationLength() {
+ return 0;
+ }
+
+ /**
+ * Get the routing type of this instance.
+ *
+ * @return routing type
+ */
+ public byte[] getRoutingInformation() {
+ byte[] result = {};
+ return result;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingDestination.java b/src/org/noreply/fancydress/type3/routing/RoutingDestination.java
new file mode 100644
index 0000000..306a0bc
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingDestination.java
@@ -0,0 +1,26 @@
+package org.noreply.fancydress.type3.routing;
+
+/**
+ * Base class for all Routings that are final destinations.
+ *
+ * Routings of this type usually mean that the message leaves the Type III
+ * network at the server processing them (for instance to be delivered via
+ * SMTP). In case of a DROP type it means that the packet is dropped at this
+ * node.
+ */
+public abstract class RoutingDestination extends Routing {
+ /**
+ * The size of decoding handles for payloads
+ */
+ public static final int DECODINGHANDLE_LEN = 20;
+
+ /**
+ * Default constructor.
+ *
+ * @param type The routing type as integer.
+ */
+ protected RoutingDestination(int type) {
+ super (type);
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingForward.java b/src/org/noreply/fancydress/type3/routing/RoutingForward.java
new file mode 100644
index 0000000..0c5d567
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingForward.java
@@ -0,0 +1,26 @@
+package org.noreply.fancydress.type3.routing;
+
+/**
+ * Base class for all Routings that appear within a chain.
+ *
+ * Routings of this type mean that the packet is forwarded
+ * (possible after a SWAP operation) to a next node.
+ */
+public abstract class RoutingForward extends Routing {
+ /**
+ * Default constructor.
+ *
+ * @param type The routing type as integer.
+ */
+ protected RoutingForward(int type) {
+ super (type);
+ }
+
+ /**
+ * Get the same routing information as a SWAP type.
+ *
+ * @return A routing class with the same information, but with a SWAP routing type.
+ */
+ public abstract RoutingForward asSwap();
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingHOST.java b/src/org/noreply/fancydress/type3/routing/RoutingHOST.java
new file mode 100644
index 0000000..f950e76
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingHOST.java
@@ -0,0 +1,102 @@
+package org.noreply.fancydress.type3.routing;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+
+/**
+ * The FWD/HOST and SWAP-FWD/HOST routing as specified in the type III spec.
+ *
+ * FWD/HOST packets are forwarded to another type III node given by
+ * a hostname/port pair. The delivering node (i.e. the node parsing
+ * this routing type) is responsible for resolving the hostname to an
+ * IP address.
+ */
+public class RoutingHOST extends RoutingForward {
+ /**
+ * The hostname of the next hop
+ */
+ private String hostname;
+ /**
+ * Port at which the Type III server is listening
+ */
+ private int port;
+ /**
+ * Keyid of the Packet Key
+ */
+ private byte[] keyid;
+
+ /**
+ * Constructor that creates the routing as either FWD/HOST or SWAP-FWD/HOST
+ *
+ * @param hostname host name of the next hop
+ * @param port TCP port at which the next hop is listening
+ * @param keyid keyid of the packet key
+ * @param boolean if true, have a SWAP-FWD/HOST routing type, FWD/HOST otherwhise
+ */
+ private RoutingHOST(String hostname, int port, byte[] keyid, boolean asSwap) {
+ super (asSwap ? RoutingType.SWAP_FWD_HOST : RoutingType.FWD_HOST);
+
+ if (keyid.length != CryptoPrimitives.HASH_LEN)
+ throw new Error("keyid must be HASH_LEN bytes long.");
+
+ this.hostname = hostname;
+ this.port = port;
+ this.keyid = keyid;
+ }
+
+ /**
+ * Create a FWD/HOST routing.
+ *
+ * @param hostname host name of the next hop
+ * @param port TCP port at which the next hop is listening
+ * @param keyid keyid of the packet key
+ */
+ public RoutingHOST(String hostname, int port, byte[] keyid) {
+ this(hostname, port, keyid, false);
+ }
+
+ /**
+ * Return a routing with the same information but with a SWAP_FWD_HOST routing type.
+ *
+ * @return a SWAP_FWD_HOST routing.
+ */
+ public RoutingForward asSwap() {
+ RoutingHOST swap = new RoutingHOST(hostname, port, keyid, true);
+ return swap;
+ }
+
+ /**
+ * Return the total length in octets of the routing information.
+ *
+ * @return total length in octets of the routing information
+ */
+ public int getRoutingInformationLength() {
+ return 2 + keyid.length + hostname.length();
+ }
+
+ /**
+ * Get the routing type of this instance.
+ *
+ * @return routing type
+ */
+ public byte[] getRoutingInformation() {
+ int length = getRoutingInformationLength();
+ byte[] result = new byte[length];
+ int pos = 0;
+
+ result[pos] = (byte) ( (port >> 8) & 0xff);
+ pos++;
+ result[pos] = (byte) ( port & 0xff);
+ pos++;
+ System.arraycopy(keyid, 0, result, pos, keyid.length);
+ pos += keyid.length;
+ System.arraycopy(Util.toOctets(hostname), 0, result, pos, hostname.length());
+ pos += hostname.length();
+
+ if (pos != length)
+ throw new Error("Did not fill in expected amount of bytes!");
+
+ return result;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingIP4.java b/src/org/noreply/fancydress/type3/routing/RoutingIP4.java
new file mode 100644
index 0000000..05d65ee
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingIP4.java
@@ -0,0 +1,110 @@
+package org.noreply.fancydress.type3.routing;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+
+/**
+ * The FWD/IP4 and SWAP-FWD/IP4 routing as specified in the type III spec.
+ *
+ * FWD/IP4 packets are forwarded to another type III node given by
+ * an ip/port pair.
+ *
+ * This routing type is going to go away RSN. FIXME
+ */
+public class RoutingIP4 extends RoutingForward {
+ /**
+ * The IP address of the next hop
+ */
+ private InetAddress ip;
+ /**
+ * Port at which the Type III server is listening
+ */
+ private int port;
+ /**
+ * Keyid of the next hop's Packet Key
+ */
+ private byte[] keyid;
+
+ /**
+ * Constructor that creates the routing as either FWD/IP4 or SWAP-FWD/IP4
+ *
+ * @param ip IP address of the next hop
+ * @param port TCP port at which the next hop is listening
+ * @param keyid keyid of the packet key
+ * @param boolean if true, have a SWAP-FWD/IP4 routing type, FWD/IP4 otherwhise
+ */
+ private RoutingIP4(InetAddress ip, int port, byte[] keyid, boolean asSwap) {
+ super (asSwap ? RoutingType.SWAP_FWD_IP4 : RoutingType.FWD_IP4);
+
+ if (keyid.length != CryptoPrimitives.HASH_LEN)
+ throw new Error("keyid must be HASH_LEN bytes long.");
+ if (! (ip instanceof Inet4Address))
+ throw new Error("IP must be an instance of Inet4Address.");
+
+ this.ip = ip;
+ this.port = port;
+ this.keyid = keyid;
+ }
+
+ /**
+ * Create a FWD/IP4 routing.
+ *
+ * @param ip IP address of the next hop
+ * @param port TCP port at which the next hop is listening
+ * @param keyid keyid of the packet key
+ */
+ public RoutingIP4(InetAddress ip, int port, byte[] keyid) {
+ this(ip, port, keyid, false);
+ }
+
+ /**
+ * Return a routing with the same information but with a SWAP_FWD_IP4 routing type.
+ *
+ * @return a SWAP_FWD_IP4 routing.
+ */
+ public RoutingForward asSwap() {
+ RoutingIP4 swap = new RoutingIP4(ip, port, keyid, true);
+ return swap;
+ }
+
+ /**
+ * Return the total length in octets of the routing information.
+ *
+ * @return total length in octets of the routing information
+ */
+ public int getRoutingInformationLength() {
+ return 4 + 2 + keyid.length;
+ }
+
+ /**
+ * Get the routing type of this instance.
+ *
+ * @return routing type
+ */
+ public byte[] getRoutingInformation() {
+ int length = getRoutingInformationLength();
+ byte[] result = new byte[length];
+ byte[] address = ip.getAddress();
+ int pos = 0;
+
+ if (address.length != 4)
+ throw new Error("Expected 4 bytes from ip.getAddress().");
+
+ System.arraycopy(address, 0, result, pos, address.length);
+ pos += address.length;
+ result[pos] = (byte) ( (port >> 8) & 0xff);
+ pos++;
+ result[pos] = (byte) ( port & 0xff);
+ pos++;
+ System.arraycopy(keyid, 0, result, pos, keyid.length);
+ pos += keyid.length;
+
+ if (pos != length)
+ throw new Error("Did not fill in expected amount of bytes!");
+
+ return result;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingSMTP.java b/src/org/noreply/fancydress/type3/routing/RoutingSMTP.java
new file mode 100644
index 0000000..acfe435
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingSMTP.java
@@ -0,0 +1,63 @@
+package org.noreply.fancydress.type3.routing;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+
+/**
+ * The SMTP routing as specified in the type III spec.
+ *
+ * SMTP packets are delivered by email by the handling server.
+ *
+ * The payload of a SMTP packet should be constructed according
+ * to the E2E spec.
+ *
+ * @see org.noreply.fancydress.type3.Payload
+ */
+public class RoutingSMTP extends RoutingDestination {
+ /**
+ * Recipient mailbox.
+ */
+ String mailbox;
+
+ /**
+ * Construct an SMTP routing.
+ */
+ public RoutingSMTP(String mailbox) {
+ super (RoutingType.SMTP);
+
+ // FIXME: syntax check mailbox
+
+ this.mailbox = mailbox;
+ }
+
+ /**
+ * Return the total length in octets of the routing information.
+ *
+ * @return total length in octets of the routing information
+ */
+ public int getRoutingInformationLength() {
+ return DECODINGHANDLE_LEN + mailbox.length();
+ }
+
+ /**
+ * Get the routing type of this instance.
+ *
+ * @return routing type
+ */
+ public byte[] getRoutingInformation() {
+ int length = getRoutingInformationLength();
+ byte[] result = new byte[length];
+ int pos = 0;
+
+ System.arraycopy(CryptoPrimitives.rand(DECODINGHANDLE_LEN), 0, result, pos, DECODINGHANDLE_LEN);
+ pos += DECODINGHANDLE_LEN;
+ System.arraycopy(Util.toOctets(mailbox), 0, result, pos, mailbox.length());
+ pos += mailbox.length();
+
+ if (pos != length)
+ throw new Error("Did not fill in expected amount of bytes!");
+
+ return result;
+ }
+}
+
diff --git a/src/org/noreply/fancydress/type3/routing/RoutingType.java b/src/org/noreply/fancydress/type3/routing/RoutingType.java
new file mode 100644
index 0000000..daf3281
--- /dev/null
+++ b/src/org/noreply/fancydress/type3/routing/RoutingType.java
@@ -0,0 +1,32 @@
+package org.noreply.fancydress.type3.routing;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.status.*;
+import java.util.*;
+
+/**
+ * RoutingTypes as specified in the type III spec.
+ */
+public class RoutingType {
+ public static final int DROP = 0x0000;
+ public static final int FWD_IP4 = 0x0001;
+ public static final int SWAP_FWD_IP4 = 0x0002;
+ public static final int FWD_HOST = 0x0003;
+ public static final int SWAP_FWD_HOST = 0x0004;
+
+ public static final int SMTP = 0x0100;
+ public static final int MBOX = 0x0101;
+ public static final int MIX2 = 0x0102;
+ public static final int FRAGMENT = 0x0103;
+
+ private int type;
+
+ public RoutingType(int type) {
+ this.type = type;
+ }
+
+ public int getType() {
+ return type;
+ }
+}