summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPeter Palfrader <peter@palfrader.org>2003-10-09 11:41:45 +0000
committerPeter Palfrader <peter@palfrader.org>2003-10-09 11:41:45 +0000
commit566d17f731637df6828bdf32502a0fb123882dbe (patch)
treefc09fdfb90953134fa1d25f73367307502348a22 /src
parent018eea460ee32df1b70c40c2eca05f06c06daca5 (diff)
Initial import
Diffstat (limited to 'src')
-rw-r--r--src/org/noreply/fancydress/crypto/CryptoPrimitives.java299
-rw-r--r--src/org/noreply/fancydress/crypto/RSAPublicKey.java125
-rw-r--r--src/org/noreply/fancydress/directory/DeliveryMBOXSMTPSection.java97
-rw-r--r--src/org/noreply/fancydress/directory/DeliveryMBOXSection.java27
-rw-r--r--src/org/noreply/fancydress/directory/DeliverySMTPSection.java27
-rw-r--r--src/org/noreply/fancydress/directory/Directory.java156
-rw-r--r--src/org/noreply/fancydress/directory/IncomingMMTPSection.java102
-rw-r--r--src/org/noreply/fancydress/directory/Server.java86
-rw-r--r--src/org/noreply/fancydress/directory/ServerDescriptor.java319
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectoryEntry.java82
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectoryLexer.flex40
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectoryLexer.java546
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectoryMessage.java130
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectoryParser.cup141
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectoryParser.java477
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectorySection.java129
-rw-r--r--src/org/noreply/fancydress/directory/parser/DirectorySymbols.java22
-rw-r--r--src/org/noreply/fancydress/misc/Util.java227
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadArgumentsChainTooLongException.java14
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadArgumentsException.java13
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java14
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadFormatException.java13
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadServerFormatException.java13
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadServerSignatureException.java14
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadServerUnrecognizedVersionException.java14
-rw-r--r--src/org/noreply/fancydress/status/Mix3BadSignatureException.java13
-rw-r--r--src/org/noreply/fancydress/status/Mix3Exception.java13
-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
-rw-r--r--src/tests/org/noreply/fancydress/crypto/CryptoPrimitivesTest.java212
-rw-r--r--src/tests/org/noreply/fancydress/crypto/RSAPublicKeyTest.java111
-rw-r--r--src/tests/org/noreply/fancydress/crypto/TestVectors134
-rw-r--r--src/tests/org/noreply/fancydress/directory/ServerDescriptorTest.java44
-rw-r--r--src/tests/org/noreply/fancydress/misc/UtilTest.java202
48 files changed, 4741 insertions, 0 deletions
diff --git a/src/org/noreply/fancydress/crypto/CryptoPrimitives.java b/src/org/noreply/fancydress/crypto/CryptoPrimitives.java
new file mode 100644
index 0000000..2f4fbd1
--- /dev/null
+++ b/src/org/noreply/fancydress/crypto/CryptoPrimitives.java
@@ -0,0 +1,299 @@
+package org.noreply.fancydress.crypto;
+
+import java.security.SecureRandom;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.noreply.fancydress.misc.Util;
+
+/**
+ * This class implements all cryptographic primitives that are needed for a
+ * type III implementation.
+ */
+public class CryptoPrimitives {
+ /**
+ * length (in octets) of a symmetric AES-128 key.
+ */
+ public static final int KEY_LEN = 16;
+ /**
+ * length (in octets) of our SPRP (LIONESS) key.
+ */
+ public static final int SPRP_KEY_LEN = 20;
+ /**
+ * length (in octets) of our hash function's (SHA-1) output.
+ */
+ public static final int HASH_LEN = 20;
+
+ /**
+ * A secure pseudo random number generator.
+ */
+ public static SecureRandom secureRandom = new SecureRandom();
+ /**
+ * Our SHA-1 Digest.
+ */
+ public static SHA1Digest digest = new SHA1Digest();
+ /**
+ * The AES Engine used for symmetric operations.
+ */
+ public static AESEngine aes_cipher = new AESEngine();
+
+ /**
+ * XOR two octet arrays.
+ *
+ * @param a octet array 1
+ * @param b octet array 2
+ * @return an octet array of equal length where x[i] = a[i] XOR b[i] for all i
+ * @throws Error if a and b do not have the same length
+ */
+ public static byte[] xor(byte[] a, byte[] b) {
+ if (a.length != b.length)
+ throw new Error("Arguments to xor must have the same length");
+ byte[] result = new byte[a.length];
+ for (int i=0; i<a.length; i++)
+ result[i] = (byte) (a[i] ^ b[i]);
+ return(result);
+ }
+ /**
+ * Get <code>n</code> random octets from our secure pseudo random number generator.
+ *
+ * @param n number of octets
+ * @return an octet array of n octets of your fines randomness
+ */
+ public static byte[] rand(int n) {
+ byte[] result = new byte[n];
+ secureRandom.nextBytes(result);
+ return(result);
+ }
+ /**
+ * Get <code>n</code> octets of zeroes.
+ *
+ * @param n number of octets
+ * @return an octet array of n octets of zeroes
+ */
+ public static byte[] zero(int n) {
+ byte[] result = new byte[n];
+ return(result);
+ }
+ /**
+ * Hash the message provided in <code>m</code>.
+ *
+ * @param m a message
+ * @return the SHA-1 hash of <code>m</code>
+ */
+ public static byte[] hash(byte[] m) {
+ digest.update(m, 0, m.length);
+ byte[] result = new byte[HASH_LEN];
+ digest.doFinal(result, 0);
+ return(result);
+ }
+ /**
+ * Hash the result of the concatenation of <code>m1</code> and <code>m2</code>.
+ *
+ * @param m1 message 1
+ * @param m2 message 2
+ * @return the SHA-1 hash of <code>m1 + m2</code>
+ */
+ public static byte[] hash(byte[] m1, byte[] m2) {
+ digest.update(m1, 0, m1.length);
+ digest.update(m2, 0, m2.length);
+ byte[] result = new byte[HASH_LEN];
+ digest.doFinal(result, 0);
+ return(result);
+ }
+ /**
+ * Hash the result of the concatenation of <code>m1</code>, <code>m2</code> and <code>m3</code>.
+ *
+ * @param m1 message 1
+ * @param m2 message 2
+ * @param m3 message 3
+ * @return the SHA-1 hash of <code>m1 + m2 + m3</code>
+ */
+ public static byte[] hash(byte[] m1, byte[] m2, byte[] m3) {
+ digest.update(m1, 0, m1.length);
+ digest.update(m2, 0, m2.length);
+ digest.update(m3, 0, m3.length);
+ byte[] result = new byte[HASH_LEN];
+ digest.doFinal(result, 0);
+ return(result);
+ }
+ /**
+ * Return <code>n</code> "random" octets generated based on key <code>k</code>.
+ *
+ * Generates <code>n</code> "random" octets or a key stream based on
+ * <code>k</code>. The stream is generated by using AES in counter mode
+ * with key <code>k</code>.
+ *
+ * @param k the key for AES counter mode
+ * @param n number of octets requested
+ * @return a keystream of n octets
+ * @throws Error if <code>k</code> is not KEY_LEN (=16) octets long.
+ */
+ public static byte[] prng(byte[] k, int n) {
+ byte[] result = new byte[ n ];
+ byte[] block = new byte[ KEY_LEN ];
+ byte[] encrypted = new byte[ KEY_LEN ];
+ if (k.length != KEY_LEN)
+ throw new Error("Argument k to encrypt must be KEY_LEN bytes");
+ KeyParameter params = new KeyParameter(k);
+ aes_cipher.init(true, params);
+ for (int i=0, p=0; i<n; p++) {
+ block[block.length - 1] = (byte) (p & 0xff);
+ block[block.length - 2] = (byte) ((p >> 8) & 0xff);
+ block[block.length - 3] = (byte) ((p >> 16) & 0xff);
+ block[block.length - 4] = (byte) ((p >> 24) & 0xff);
+
+ aes_cipher.processBlock(block, 0, encrypted, 0);
+ for (int j=0; j<encrypted.length && i<n; j++, i++)
+ result[i] = encrypted[j];
+ };
+ return(result);
+ }
+ /**
+ * Encrypt a message <code>m</code> using AES counter mode with key <code>k</code>.
+ *
+ * Returns the result of xor(m, prng(k, len(m))), i.e. the message <code>m</code>
+ * encrypted using AES in counter mode with key <code>k</code>.
+ *
+ * @param k the key for AES counter mode
+ * @param m the message to encrypt
+ * @return the encrypted message. (len(e) == len(m))
+ * @throws Error if <code>k</code> is not KEY_LEN (=16) octets long.
+ */
+ public static byte[] encrypt(byte[] k, byte[] m) {
+ byte[] result;
+ if (k.length != KEY_LEN)
+ throw new Error("Argument k to encrypt must be KEY_LEN bytes");
+ result = xor(m, prng(k, m.length));
+ return(result);
+ }
+ /**
+ * Encrypt a message using our super pseudorandom permutation.
+ *
+ * Encrypt message <code>m</code> using an instance of the LIONESS
+ * super pseudorandom permutation (SPRP).
+ *
+ * This SPRP has the property that any change in the encrypted value
+ * will make the decryption look like random bits
+ *
+ * @param k the key for our SPRP function
+ * @param m the message to encrypt
+ * @return the encrypted message. (len(e) == len(m))
+ * @throws Error if <code>k</code> is not SPRP_KEY_LEN (=20) octets long
+ * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets
+ */
+ public static byte[] sprpEncrypt(byte[] k, byte[] m) {
+ if (m.length < HASH_LEN)
+ throw new Error("Argument m to sprp_encrypt must be of at least HASH_LEN bytes");
+ if (k.length != SPRP_KEY_LEN)
+ throw new Error("Argument k to sprp_encrypt must be SPRP_KEY_LEN bytes");
+ byte[] result;
+ byte[] k1 = new byte[HASH_LEN];
+ byte[] k2 = new byte[HASH_LEN];
+ byte[] k3 = new byte[HASH_LEN];
+ byte[] k4 = new byte[HASH_LEN];
+
+ System.arraycopy(k, 0, k1, 0, k.length);
+ System.arraycopy(k, 0, k2, 0, k.length);
+ System.arraycopy(k, 0, k3, 0, k.length);
+ System.arraycopy(k, 0, k4, 0, k.length);
+ k2[19] = (byte) (k1[19] ^ 0x01);
+ k3[19] = (byte) (k1[19] ^ 0x02);
+ k4[19] = (byte) (k1[19] ^ 0x03);
+ byte[] l = Util.slice(m, 0, HASH_LEN);
+ byte[] r = Util.slice(m, HASH_LEN, m.length-HASH_LEN);
+ r = encrypt( Util.slice(hash( k1, l, k1), 0, KEY_LEN), r);
+ l = xor( l, hash(k2, r, k2 ));
+ r = encrypt( Util.slice(hash( k3, l, k3), 0, KEY_LEN), r);
+ l = xor( l, hash(k4, r, k4 ));
+ result = Util.concat(l, r);
+ return(result);
+ }
+ /**
+ * Encrypt a message using our super pseudorandom permutation.
+ *
+ * The message will be encrypted using a subkey constructed from
+ * <code>k</code> and <code>p</code> (=HASH(k | p)).
+ *
+ * @param k a master key for the SPRP encryption.
+ * @param p a string to build a subkey with.
+ * @param m the message to encrypt
+ * @return the encrypted message. (len(e) == len(m))
+ * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets
+ * @see #sprpEncrypt(byte[] k, byte[] m)
+ */
+ public static byte[] sprpEncrypt(byte[] k, String p, byte[] m) {
+ return sprpEncrypt(hash(k, Util.toOctets(p)) ,m);
+ }
+ /**
+ * Decrypt a message using our super pseudorandom permutation.
+ *
+ * Decrypt message <code>m</code> using our instance of the LIONESS
+ * super pseudorandom permutation (SPRP).
+ *
+ * @param k the key for our SPRP function
+ * @param m the message to decrypt
+ * @return the encrypted message. (len(e) == len(m))
+ * @throws Error if <code>k</code> is not SPRP_KEY_LEN (=20) octets long
+ * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets
+ * @see #sprpEncrypt(byte[] k, byte[] m)
+ */
+ public static byte[] sprpDecrypt(byte[] k, byte[] m) {
+ if (m.length < HASH_LEN)
+ throw new Error("Argument m to sprp_decrypt must be of at least HASH_LEN bytes");
+ if (k.length != SPRP_KEY_LEN)
+ throw new Error("Argument k to sprp_decrypt must be SPRP_KEY_LEN bytes");
+ byte[] result;
+ byte[] k1 = new byte[HASH_LEN];
+ byte[] k2 = new byte[HASH_LEN];
+ byte[] k3 = new byte[HASH_LEN];
+ byte[] k4 = new byte[HASH_LEN];
+
+ System.arraycopy(k, 0, k1, 0, k.length);
+ System.arraycopy(k, 0, k2, 0, k.length);
+ System.arraycopy(k, 0, k3, 0, k.length);
+ System.arraycopy(k, 0, k4, 0, k.length);
+ k2[19] = (byte) (k1[19] ^ 0x01);
+ k3[19] = (byte) (k1[19] ^ 0x02);
+ k4[19] = (byte) (k1[19] ^ 0x03);
+ byte[] l = Util.slice(m, 0, HASH_LEN);
+ byte[] r = Util.slice(m, HASH_LEN, m.length-HASH_LEN);
+ l = xor( l, hash(k4, r, k4 ));
+ r = encrypt( Util.slice(hash( k3, l, k3), 0, KEY_LEN), r);
+ l = xor( l, hash(k2, r, k2 ));
+ r = encrypt( Util.slice(hash( k1, l, k1), 0, KEY_LEN), r);
+ result = Util.concat(l, r);
+ return(result);
+ }
+ /**
+ * Decrypt a message using our super pseudorandom permutation.
+ *
+ * The message will be decrypted using a subkey constructed from
+ * <code>k</code> and <code>p</code> (=HASH(k | p)).
+ *
+ * @param k a master key for the SPRP decryption.
+ * @param p a string to build a subkey with.
+ * @param m the message to decrypt
+ * @return the encrypted message. (len(e) == len(m))
+ * @throws Error if <code>m</code> is shorter than SPRP_KEY_LEN (=20) octets
+ * @see #sprpEncrypt(byte[] k, byte[] m)
+ * @see #sprpDecrypt(byte[] k, byte[] m)
+ */
+ public static byte[] sprpDecrypt(byte[] k, String p, byte[] m) {
+ return sprpDecrypt(hash(k, Util.toOctets(p)) ,m);
+ }
+ /**
+ * Build a subkey from <code>k</code> and <code>p</code>.
+ *
+ * The subkey is generated using HASH(k | p).
+ *
+ * @param k a master key
+ * @param p a string to build a subkey with
+ * @return the subkey
+ * @throws Error if <code>k</code> is not KEY_LEN (=16) octets long
+ */
+ public static byte[] subKey(byte[] k, String p) {
+ if (k.length != KEY_LEN)
+ throw new Error("Argument k to subKey must be KEY_LEN bytes");
+ return Util.slice(hash(k, Util.toOctets(p)), 0, 16);
+ }
+}
diff --git a/src/org/noreply/fancydress/crypto/RSAPublicKey.java b/src/org/noreply/fancydress/crypto/RSAPublicKey.java
new file mode 100644
index 0000000..13f8b53
--- /dev/null
+++ b/src/org/noreply/fancydress/crypto/RSAPublicKey.java
@@ -0,0 +1,125 @@
+package org.noreply.fancydress.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERInputStream;
+import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
+import org.bouncycastle.crypto.AsymmetricBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.encodings.OAEPEncoding;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.noreply.fancydress.misc.Util;
+
+/**
+ * This class is a wrapper for the bouncycastle RSA functionality.
+ */
+public class RSAPublicKey {
+ /**
+ * The overhead (in octets) added by OAEP padding.
+ */
+ public static final int PK_OVERHEAD_LEN = 42;
+ /**
+ * The length of encrypted data. (we use 2048 bit keys)
+ */
+ public static final int PK_ENC_LEN = 256;
+ /**
+ * The maximum amount of octets that can be encoded in an RSA message.
+ */
+ public static final int PK_MAX_DATA_LEN = PK_ENC_LEN - PK_OVERHEAD_LEN;
+
+ /**
+ * the RSA cipher from bouncycastle.
+ */
+ private AsymmetricBlockCipher rsa_cipher;
+ /**
+ * a key structure
+ */
+ private RSAPublicKeyStructure key;
+ /**
+ * key parameters
+ */
+ private RSAKeyParameters keyParams;
+
+ /**
+ * Construct an instance of an RSAPublicKey from the ASN1 encoded key.
+ *
+ * @param asnEncoded ASN1 encoded RSA key.
+ */
+ public RSAPublicKey(byte[] asnEncoded) {
+ String p = new String("He who would make his own liberty secure, must guard even his enemy from oppression.");
+ rsa_cipher = new OAEPEncoding(new RSAEngine(), new SHA1Digest(), CryptoPrimitives.hash(Util.toOctets(p)));
+
+ ByteArrayInputStream bIn = new ByteArrayInputStream(asnEncoded);
+ DERInputStream dIn = new DERInputStream(bIn);
+ try {
+ key = new RSAPublicKeyStructure((ASN1Sequence)dIn.readObject());
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+ keyParams = new RSAKeyParameters(false, key.getModulus(), key.getPublicExponent());
+ };
+ /**
+ * Encrypt a message.
+ *
+ * Encrypts message m to this key.
+ *
+ * @param m the message to encrypt.
+ * @return the encrypted message (PK_ENC_LEN octets long)
+ */
+ public byte[] encrypt(byte[] m) {
+ rsa_cipher.init(true, keyParams);
+ byte[] result;
+ try {
+ result = rsa_cipher.processBlock(m, 0, m.length);
+ } catch (InvalidCipherTextException e) {
+ throw new Error(e);
+ }
+ return(result);
+ }
+ /**
+ * Verify a signature.
+ *
+ * Verify that the information signed in signature <code>sig</code>
+ * matches the value in <code>m</code>.
+ *
+ * @param sig the signature
+ * @param m message to compare the signed information to
+ * @return true if the signature is valid
+ * @throws InvalidCipherTextException
+ */
+ public boolean verify(byte[] sig, byte[] m) throws InvalidCipherTextException {
+ rsa_cipher.init(false, keyParams);
+ byte[] signedDigest = rsa_cipher.processBlock(sig, 0, sig.length);
+ byte[] digest = CryptoPrimitives.hash(m);
+ boolean verifies = Util.equal(digest, signedDigest);
+ return(verifies);
+ }
+ /**
+ * Return the ASN 1 encoding of this key.
+ *
+ * @return this key, asn1 encoded
+ */
+ public byte[] encode() {
+ DEROctetString asn = new DEROctetString(key);
+ byte[] result = asn.getOctets();
+ return(result);
+ }
+ /**
+ * Get this key's fingerprint.
+ *
+ * The fingerprint (or keyid) is the hash of the asn1 representation of this key.
+ *
+ * @return the fingerprint.
+ */
+ public byte[] getFingerprint() {
+ DEROctetString asn = new DEROctetString(key);
+ byte[] encoded = asn.getOctets();
+ byte[] result = CryptoPrimitives.hash(encoded);
+ return(result);
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/DeliveryMBOXSMTPSection.java b/src/org/noreply/fancydress/directory/DeliveryMBOXSMTPSection.java
new file mode 100644
index 0000000..7eaf2bd
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/DeliveryMBOXSMTPSection.java
@@ -0,0 +1,97 @@
+package org.noreply.fancydress.directory;
+
+import org.noreply.fancydress.directory.parser.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.status.*;
+import java.util.*;
+import java.text.ParseException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Base class for both DeliveryMBOXSection and DeliverySMTP Section.
+ *
+ * This is a base class for the DeliveryMBOXSection and the DeliverySMTP class.
+ * Since both are very similar it makes sense to pool their functionality into
+ * a single class from which both inheritate.
+ *
+ * @see ServerDescriptor
+ */
+public class DeliveryMBOXSMTPSection {
+ private String name;
+
+ /* Required */
+ /**
+ * The version number of this section.
+ */
+ private String version;
+ /**
+ * The maximum message size (in Kb) that is accepted at this node.
+ */
+ private int maximumSize;
+ /**
+ * Whether this exit node allows user supplied From: lines.
+ */
+ private boolean allowFrom;
+
+ /**
+ * Construct a Delivery/MBOX or Delivery/SMTP section.
+ *
+ * @param section the section to parse.
+ * @throws Mix3BadServerUnrecognizedVersionException if the Version in the section is not recognized.
+ * @throws Mix3BadServerFormatException if the Section is syntactially invalid.
+ */
+ public DeliveryMBOXSMTPSection(DirectorySection section)
+ throws Mix3BadServerFormatException
+ {
+ name = section.getName();
+ parseDeliveryMBOXSMTPSection(section);
+ }
+
+ /**
+ * Parse a Delivery/MBOX or the Delivery/SMTP section.
+ *
+ * @param section the section to parse.
+ * @throws Mix3BadServerUnrecognizedVersionException if the Version in the section is not recognized.
+ * @throws Mix3BadServerFormatException if the Section is syntactially invalid.
+ */
+ private void parseDeliveryMBOXSMTPSection(DirectorySection section) throws Mix3BadServerFormatException {
+ /* Check Version */
+ DirectoryEntry entryVersion = section.getEntry("Version");
+ if (entryVersion == null)
+ throw new Mix3BadServerFormatException("Version not in " + name + " section");
+ version = entryVersion.getValue();
+ if (! version.equals("0.1"))
+ /* We have to ignore unknown Versions */
+ throw new Mix3BadServerUnrecognizedVersionException("Unrecognized " + name + " Version "+version);
+
+ /* mandatory entries */
+ DirectoryEntry entryMaximumSize = section.getEntry("Maximum-size");
+ DirectoryEntry entryAllowFrom = section.getEntry("Allow-From");
+
+ /* FIXME
+ if (entryMaximumSize == null)
+ throw new Mix3BadServerFormatException("Maximum-size not in " + name + " section");
+ */
+ if (entryAllowFrom == null)
+ throw new Mix3BadServerFormatException("Allow-From not in " + name + " section");
+
+
+ /* FIXME
+ try {
+ Integer p = new Integer(entryMaximumSize.getValue());
+ maximumSize = p.intValue();
+ } catch (NumberFormatException e) {
+ throw new Mix3BadServerFormatException("Maximum-size is not a valid integer in " + name + " section", e);
+ };
+ if (maximumSize < 32)
+ throw new Mix3BadServerFormatException("Maximum-size is smaller than 32 in " + name + " section");
+ */
+
+ try {
+ allowFrom = Util.parseBoolean(entryAllowFrom.getValue());
+ } catch (ParseException e) {
+ throw new Mix3BadServerFormatException("Allow-From is not a valid boolean in " + name + " section", e);
+ };
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/DeliveryMBOXSection.java b/src/org/noreply/fancydress/directory/DeliveryMBOXSection.java
new file mode 100644
index 0000000..9a913e8
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/DeliveryMBOXSection.java
@@ -0,0 +1,27 @@
+package org.noreply.fancydress.directory;
+
+import org.noreply.fancydress.directory.parser.*;
+import org.noreply.fancydress.status.*;
+
+/**
+ * This class holds the information supplied in a Delivery/MBOX section of a
+ * server descriptor.
+ *
+ * @see ServerDescriptor
+ */
+public class DeliveryMBOXSection extends DeliveryMBOXSMTPSection {
+
+ /**
+ * Construct a Delivery/MBOX section
+ *
+ * @param section the section to parse.
+ * @throws Mix3BadServerUnrecognizedVersionException if the Version in the section is not recognized.
+ * @throws Mix3BadServerFormatException if the Section is syntactially invalid.
+ */
+ public DeliveryMBOXSection(DirectorySection section)
+ throws Mix3BadServerFormatException
+ {
+ // FIXME: assert that the section's name is Delivery/MBOX
+ super(section);
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/DeliverySMTPSection.java b/src/org/noreply/fancydress/directory/DeliverySMTPSection.java
new file mode 100644
index 0000000..e09c884
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/DeliverySMTPSection.java
@@ -0,0 +1,27 @@
+package org.noreply.fancydress.directory;
+
+import org.noreply.fancydress.directory.parser.*;
+import org.noreply.fancydress.status.*;
+
+/**
+ * This class holds the information supplied in a Delivery/SMTP section of a
+ * server descriptor.
+ *
+ * @see ServerDescriptor
+ */
+public class DeliverySMTPSection extends DeliveryMBOXSMTPSection {
+
+ /**
+ * Construct a Delivery/SMTP section
+ *
+ * @param section the section to parse.
+ * @throws Mix3BadServerUnrecognizedVersionException if the Version in the section is not recognized.
+ * @throws Mix3BadServerFormatException if the Section is syntactially invalid.
+ */
+ public DeliverySMTPSection(DirectorySection section)
+ throws Mix3BadServerFormatException
+ {
+ // FIXME: assert that the section's name is Delivery/SMTP
+ super(section);
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/Directory.java b/src/org/noreply/fancydress/directory/Directory.java
new file mode 100644
index 0000000..d5f940b
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/Directory.java
@@ -0,0 +1,156 @@
+package org.noreply.fancydress.directory;
+
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.noreply.fancydress.directory.parser.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.status.*;
+import java.util.*;
+
+/**
+ * This class represents a type III Directory.
+ *
+ * A directory holds information about the state of a type III network, like
+ * recommended servers, which servers exist, their keys, etc.
+ *
+ * FIXME:
+ * This whole flex and cup thing was just a nice way to try these tools.
+ * Eventually this should be rewritten to a simple parser in Java itself.
+ * Writing it should be pretty straight forward and cut down the
+ * dependencies.
+ *
+ * @see Server
+ */
+public class Directory {
+ /**
+ * Hash holding all Servers.
+ *
+ * This hash holds all Servers. Since nickname are to be treaded case
+ * insensitive the hash's keys are the lowercased nicknames of the Servers.
+ *
+ * All values in this hash are instances of Server.
+ *
+ * @see Server
+ */
+ private Hashtable byName;
+
+ /**
+ * Possibly add a new server descriptor to the directory.
+ *
+ * This method adds a server desccriptor to the directory if it
+ * - conforms to the syntax specified.
+ * - the server is not yet known or it's identity key matches the key of the already known ServerDescriptor.
+ *
+ * @param server a DirectoryMessage consisting of one of more sections. At 'Server' section needs to be present.
+ * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored.
+ * @throws Mix3BadServerSignatureException if the server descriptor's signature is invalid.
+ */
+ private void addServerDescriptor(DirectoryMessage server) throws Mix3BadServerFormatException, Mix3BadServerSignatureException {
+ try {
+ ServerDescriptor sd = new ServerDescriptor(server);
+ String key = sd.getNickname().toLowerCase();
+ if (byName.containsKey(key)) {
+ Server s = (Server) byName.get(key);
+ try {
+ s.addDescriptor(sd);
+ } catch (Mix3BadServerFormatException e) {
+ System.err.println("Ignoring Descriptor with different identity for "+key);
+ }
+ } else {
+ Server s = new Server(sd);
+ byName.put(key, s);
+ }
+ } catch (Mix3BadServerUnrecognizedVersionException e) {
+ System.err.println("Ignoring unregonized version");
+ }
+ }
+
+ /**
+ * Create a directory from an entire DirectoryMessage.
+ *
+ * This constructor builds up the directory from the message
+ * <code>m</code>. It optionally checks the directory's signature and
+ * splits the message into single server descriptors to load them.
+ *
+ * @param m the directory message
+ * @param checkDirectorySignature whether or not to check the directory's signature
+ * @throws Mix3BadDirectorySignatureException if the directory's signature is invalid.
+ * @throws Mix3BadServerSignatureException if a server descriptor's signature is invalid.
+ * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored.
+ */
+ public Directory(DirectoryMessage m, boolean checkDirectorySignature) throws
+ Mix3BadDirectorySignatureException,
+ Mix3BadServerFormatException,
+ Mix3BadServerSignatureException
+ {
+ byName = new Hashtable();
+
+ if (checkDirectorySignature)
+ checkSignature(m);
+ DirectoryMessage server = null;
+ for (Iterator i = m.getSectionsIterator(); i.hasNext(); ) {
+ DirectorySection section = (DirectorySection) i.next();
+ if (section.getName().equals("Server")) {
+ if (server != null)
+ addServerDescriptor(server);
+ server = new DirectoryMessage();
+ }
+ if (server != null)
+ server.addSection(section);
+ }
+ if (server != null)
+ addServerDescriptor(server);
+
+ System.out.println(byName);
+ }
+
+ /* FIXME: known directory servers should be passed as arguments */
+ /**
+ * Check the signature of a type III directory.
+ */
+ private void checkSignature(DirectoryMessage m) throws Mix3BadDirectorySignatureException {
+ /* FIXME: handle more than one signature block,
+ */
+
+ DirectorySection signatureSection = m.getSection("Signature");
+ if (signatureSection == null)
+ throw new Mix3BadDirectorySignatureException("No Signature section found.");
+
+ DirectoryEntry signatureEntry = signatureSection.getEntry("DirectorySignature");
+ if (signatureEntry == null)
+ throw new Mix3BadDirectorySignatureException("No DirectorySignature in Signature section found.");
+
+ DirectoryEntry identityEntry = signatureSection.getEntry("DirectoryIdentity");
+ if (identityEntry == null)
+ throw new Mix3BadDirectorySignatureException("No DirectoryIdentity in Signature section found.");
+
+ byte[] signature = Base64.decode(signatureEntry.getValue());
+ byte[] identity = Base64.decode(identityEntry.getValue());
+
+ DirectoryMessage cleanedDirectory = new DirectoryMessage(m);
+ cleanedDirectory.blindValue("Signature","DirectoryDigest");
+ cleanedDirectory.blindValue("Signature","DirectorySignature");
+
+ /* FIXME: what if identity is no valid key? */
+ RSAPublicKey identityKey = new RSAPublicKey(identity);
+ boolean verifies;
+ try {
+ verifies = identityKey.verify(signature, Util.toOctets(cleanedDirectory.toString()));
+ } catch (InvalidCipherTextException e) {
+ throw new Mix3BadDirectorySignatureException("Directory signature has invalid format.", e);
+ };
+ if (!verifies)
+ throw new Mix3BadDirectorySignatureException("Directory signature does not verify.");
+ }
+
+ /**
+ * Get a server by nickname.
+ */
+ public Server getServer(String name) {
+ String key = name.toLowerCase();
+ if (byName.containsKey(key))
+ return (Server) byName.get(key);
+ return null;
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/IncomingMMTPSection.java b/src/org/noreply/fancydress/directory/IncomingMMTPSection.java
new file mode 100644
index 0000000..28511b2
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/IncomingMMTPSection.java
@@ -0,0 +1,102 @@
+package org.noreply.fancydress.directory;
+
+import org.bouncycastle.util.encoders.Base64;
+import org.noreply.fancydress.directory.parser.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.status.*;
+import java.util.*;
+import java.text.ParseException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/* FIXME: parse allow/deny ACLs */
+
+/**
+ * Represent an Incoming/MMTP section of a server descriptor.
+ *
+ * @see ServerDescriptor
+ */
+public class IncomingMMTPSection {
+ private String name;
+
+ /* Required */
+ private String version;
+ private InetAddress ip;
+ private String hostname;
+ private int port;
+ private String protocols;
+
+ /**
+ * Construct an Incoming/MMTP section.
+ *
+ * @param section the section to parse.
+ * @throws Mix3BadServerFormatException if the Section is syntactially invalid.
+ */
+ public IncomingMMTPSection(DirectorySection section)
+ throws Mix3BadServerFormatException
+ {
+ name = section.getName();
+ parseIncomingMMTPSection(section);
+ }
+
+ /**
+ * Parse an Incoming/MMTP section.
+ *
+ * @param section the section to parse.
+ * @throws Mix3BadServerFormatException if the Section is syntactially invalid.
+ */
+ private void parseIncomingMMTPSection(DirectorySection section) throws Mix3BadServerFormatException {
+ /* Check Version */
+ DirectoryEntry entryVersion = section.getEntry("Version");
+ if (entryVersion == null)
+ throw new Mix3BadServerFormatException("Version not in " + name + " section");
+ version = entryVersion.getValue();
+ if (! version.equals("0.1"))
+ /* We have to ignore unknown Versions */
+ throw new Mix3BadServerUnrecognizedVersionException("Unrecognized " + name + " Version "+version);
+
+ /* mandatory entries */
+ DirectoryEntry entryIP = section.getEntry("IP");
+ DirectoryEntry entryHostname = section.getEntry("Hostname");
+ DirectoryEntry entryPort = section.getEntry("Port");
+ DirectoryEntry entryProtocols = section.getEntry("Protocols");
+
+ /* check if mandatory entries are available */
+ if (entryIP == null)
+ throw new Mix3BadServerFormatException("IP not in " + name + " section");
+ /* FIXME: spec is changing from IP to hostname, for now, hostname is not used */
+ /*
+ if (entryHostname == null)
+ throw new Mix3BadServerFormatException("Hostname not in " + name + " section");
+ */
+ if (entryPort == null)
+ throw new Mix3BadServerFormatException("Port not in " + name + " section");
+ if (entryProtocols == null)
+ throw new Mix3BadServerFormatException("Protocols not in " + name + " section");
+
+
+ try {
+ ip = InetAddress.getByName(entryIP.getValue());
+ } catch (UnknownHostException e) {
+ throw new Mix3BadServerFormatException("IP is not a valid IP adress in " + name + " section", e);
+ }
+ if (!ip.getHostAddress().equals(entryIP.getValue()))
+ throw new Mix3BadServerFormatException(
+ "parsed IP to string does not match its directory representation in " + name + " section: "+
+ ip.getHostAddress() + " vs. " + entryIP.getValue());
+
+ hostname = entryHostname == null ? null : entryHostname.getValue(); /* FIXME */
+
+ try {
+ Integer p = new Integer(entryPort.getValue());
+ port = p.intValue();
+ } catch (NumberFormatException e) {
+ throw new Mix3BadServerFormatException("Port is not a valid integer in " + name + " section", e);
+ };
+ if (port < 0 || port > 65535)
+ throw new Mix3BadServerFormatException("Port is not in TCP/IP port range in " + name + " section");
+
+ protocols = entryProtocols.getValue();
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/Server.java b/src/org/noreply/fancydress/directory/Server.java
new file mode 100644
index 0000000..e3bb355
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/Server.java
@@ -0,0 +1,86 @@
+package org.noreply.fancydress.directory;
+
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.status.*;
+import org.noreply.fancydress.crypto.*;
+import java.util.*;
+
+/**
+ * This class represents a server, pooling one or more server descriptors into one class.
+ *
+ * @see ServerDescriptor
+ */
+public class Server {
+ /**
+ * Nickname of the server.
+ *
+ * Note that all nickname comparisons need to be done in a case insensitive way.
+ */
+ String nickname;
+
+ /**
+ * The asn1 representation of the server's identity key.
+ */
+ byte[] identity;
+
+ /**
+ * The hash of <code>identity</code>, which is the node's keyid
+ */
+ byte[] keyid;
+
+ /**
+ * A list of one of more server descriptors.
+ */
+ ArrayList descriptors;
+
+ /**
+ * Construct a Server from a given ServerDescriptor.
+ *
+ * @param descriptor a ServerDescriptor.
+ */
+ public Server(ServerDescriptor descriptor) {
+ nickname = descriptor.getNickname();
+ identity = descriptor.getIdentity();
+ keyid = CryptoPrimitives.hash(identity);
+
+ descriptors = new ArrayList();
+ descriptors.add(descriptor);
+ }
+
+ /**
+ * Add a ServerDescriptor to this Server.
+ *
+ * @param descriptor a ServerDescriptor to add.
+ * @throws Mix3BadServerFormatException if the nickname or the identity key do not match this server's nickname or identity key.
+ */
+ public void addDescriptor(ServerDescriptor descriptor) throws Mix3BadServerFormatException {
+
+ if (!nickname.equalsIgnoreCase(descriptor.getNickname()))
+ /* This indicates an error in whoever called us */
+ throw new Error("Nickname does not match.");
+
+ if (!Util.equal(identity, descriptor.getIdentity()))
+ /* While this might happen. In this case this ServerDesc should be ignored */
+ throw new Mix3BadServerFormatException("Identity does not match.");
+
+ descriptors.add(descriptor);
+ }
+
+ /**
+ * Get the keyid (hash of the identity key) of this Server.
+ *
+ * @return keyid
+ */
+ public byte[] getKeyID() {
+ return keyid;
+ }
+
+ /**
+ * get the first server descriptor (needs fixing).
+ *
+ * @return first server descriptor
+ */
+ public ServerDescriptor getDescriptor() { /* FIXME */
+ return (ServerDescriptor) descriptors.get(0);
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/ServerDescriptor.java b/src/org/noreply/fancydress/directory/ServerDescriptor.java
new file mode 100644
index 0000000..06cf2d6
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/ServerDescriptor.java
@@ -0,0 +1,319 @@
+package org.noreply.fancydress.directory;
+
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.noreply.fancydress.directory.parser.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.crypto.*;
+import org.noreply.fancydress.status.*;
+import org.noreply.fancydress.type3.*;
+import java.util.*;
+import java.text.ParseException;
+
+/**
+ * A single Server Descriptor of a type III node.
+ *
+ * This class holds all information of a single server descriptor. It may also
+ * hold subsections like Incoming/MMTP or Delivery/MBOX if such sections exist
+ * in this server descriptor.
+ *
+ * An instance of Server pools ServerDescriptors of a single node.
+ *
+ * @see Server
+ */
+public class ServerDescriptor {
+ /* Required */
+ private String descriptorVersion;
+ private String nickname;
+ private byte[] identity;
+ private RSAPublicKey identityKey;
+ private byte[] identityDigest;
+ private byte[] digest;
+ private byte[] signature;
+ private Date published;
+ private Date validAfter;
+ private Date validUntil;
+ private RSAPublicKey packetKey;
+ private String[] packetVersions;
+
+ /* Optional */
+ private String contact;
+ private String contactFingerprint;
+ private String comments;
+ private String software;
+ private Boolean secureConfiguration;
+ private String whyInsecure;
+
+ private IncomingMMTPSection IncomingMMTPSection;
+ private Object OutgoingMMTPSection;
+ private DeliveryMBOXSection DeliveryMBOXSection;
+ private DeliverySMTPSection DeliverySMTPSection;
+ private Object DeliveryFragmentedSection;
+
+
+ /**
+ * Construct a ServerDescriptor instance from the message in m.
+ *
+ * This constructor builds up a ServerDescriptor from the message
+ * <code>m</code> which contains a single server descriptor.
+ *
+ * The DirectoryMessage needs to have a 'Server' section.
+ *
+ * @param server a DirectoryMessage consisting of one of more sections.
+ * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored.
+ * @throws Mix3BadServerSignatureException if the server descriptor's signature is invalid.
+ */
+ public ServerDescriptor(DirectoryMessage m)
+ throws Mix3BadServerFormatException, Mix3BadServerSignatureException
+ {
+ DirectorySection serverSection = m.getSection("Server");
+ if (serverSection == null)
+ throw new Mix3BadServerFormatException("No Server section found.");
+
+ parseServerSection(serverSection);
+ identityKey = new RSAPublicKey(identity);
+ identityDigest = CryptoPrimitives.hash(identity);
+ checkSignature(m);
+
+ DirectorySection section;
+ section = m.getSection("Incoming/MMTP");
+ try {
+ IncomingMMTPSection = section == null ? null : new IncomingMMTPSection(section);
+ } catch (Mix3BadServerFormatException e) { System.err.println(e); };
+
+ section = m.getSection("Delivery/MBOX");
+ try {
+ DeliveryMBOXSection = section == null ? null : new DeliveryMBOXSection(section);
+ } catch (Mix3BadServerFormatException e) { System.err.println(e); };
+
+ section = m.getSection("Delivery/SMTP");
+ try {
+ DeliverySMTPSection = section == null ? null : new DeliverySMTPSection(section);
+ } catch (Mix3BadServerFormatException e) { System.err.println(e); };
+ }
+
+
+ /**
+ * Check if <code>s</code> is a valid nickname.
+ *
+ * @throws Mix3BadServerFormatException if <code>s</code> is not a valid nickame
+ */
+ private static void checkSyntaxNickname(String s) throws Mix3BadServerFormatException {
+ for (int i=0; i<s.length(); i++) {
+ char c = s.charAt(i);
+ if (!((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') ||
+ c == '_' || c == '@' || c == '-'))
+ throw new Mix3BadServerFormatException("Illegal characters in nickname: "+c);
+ }
+ }
+
+ /**
+ * Tokenize a Packet-Versions string into single Packet-Versions.
+ *
+ * The Packet-Versions is a comma separated list of Packet-Versions.
+ * This method parses a Packet-Versions value and returns an array of
+ * Strings, holding one Packet-Version each.
+ *
+ * Whitespace is trimmed off each Packet-Version.
+ *
+ * @param s the comma separated list of Packet-Versions
+ * @param onlySupported only include Packet-Versions that we can handle
+ * @return array of Packet-Versions.
+ */
+ public static String[] parsePacketVersions(String s, boolean onlySupported) {
+ ArrayList versions = new ArrayList();
+ int indexFrom = 0;
+ int indexOf;
+
+ while ((indexOf = s.indexOf(',', indexFrom)) != -1) {
+ String v = s.substring(indexFrom, indexOf).trim();
+ if (Packet.isPacketVersionSupported(v) || !onlySupported )
+ versions.add( v );
+ indexFrom = indexOf + 1;
+ }
+ String v = s.substring(indexFrom).trim();
+ if (Packet.isPacketVersionSupported(v) || !onlySupported )
+ versions.add( v );
+
+ String[] result = new String[versions.size()];
+ for (int i=0; i<result.length; i++)
+ result[i] = (String) versions.get(i);
+
+ return result;
+ }
+ /**
+ * Tokenize a Packet-Versions string into single Packet-Versions.
+ *
+ * The Packet-Versions is a comma separated list of Packet-Versions.
+ * This method parses a Packet-Versions value and returns an array of
+ * Strings, holding one Packet-Version each.
+ *
+ * Whitespace is trimmed off each Packet-Version.
+ *
+ * Only Packet-Versions that we know to handle are included n the result.
+ *
+ * @param s the comma separated list of Packet-Versions
+ * @return array of Packet-Versions.
+ */
+ public static String[] parsePacketVersions(String s) {
+ return parsePacketVersions(s, true);
+ }
+
+ /**
+ * Parse the Server section.
+ *
+ * @throws Mix3BadServerFormatException if the section is syntactially invalid.
+ */
+ private void parseServerSection(DirectorySection serverSection) throws Mix3BadServerFormatException {
+ /* Check Descriptor-Version */
+ DirectoryEntry entryDescriptorVersion = serverSection.getEntry("Descriptor-Version");
+ if (entryDescriptorVersion == null)
+ throw new Mix3BadServerFormatException("Descriptor-Version not in Server Descriptor");
+ descriptorVersion = entryDescriptorVersion.getValue();
+ if (! descriptorVersion.equals("0.2"))
+ /* We have to ignore unknown Descriptor-Versions */
+ throw new Mix3BadServerUnrecognizedVersionException("Unrecognized Descriptor-Version "+descriptorVersion);
+
+ /* Do nickname now so we can refer to it in exceptions */
+ DirectoryEntry entryNickname = serverSection.getEntry("Nickname");
+ if (entryNickname == null) throw new Mix3BadServerFormatException("Nickname not in Server Descriptor");
+ nickname = entryNickname.getValue();
+ checkSyntaxNickname(nickname); /* Throws Mix3BadServerFormatException unless valid */
+
+ /* mandatory entries */
+ DirectoryEntry entryIdentity = serverSection.getEntry("Identity");
+ DirectoryEntry entryDigest = serverSection.getEntry("Digest");
+ DirectoryEntry entrySignature = serverSection.getEntry("Signature");
+ DirectoryEntry entryPublished = serverSection.getEntry("Published");
+ DirectoryEntry entryValidAfter = serverSection.getEntry("Valid-After");
+ DirectoryEntry entryValidUntil = serverSection.getEntry("Valid-Until");
+ DirectoryEntry entryPacketKey = serverSection.getEntry("Packet-Key");
+ DirectoryEntry entryPacketFormats = serverSection.getEntry("Packet-Formats"); /* FIXME: will go away */
+ DirectoryEntry entryPacketVersions = serverSection.getEntry("Packet-Versions");
+
+ /* optional entries */
+ DirectoryEntry entryContact = serverSection.getEntry("Contact");
+ DirectoryEntry entryContactFingerprint = serverSection.getEntry("Contact-Fingerprint");
+ DirectoryEntry entryComments = serverSection.getEntry("Comments");
+ DirectoryEntry entrySoftware = serverSection.getEntry("Software");
+ DirectoryEntry entrySecureConfiguration = serverSection.getEntry("Secure-Configuration");
+ DirectoryEntry entryWhyInsecure = serverSection.getEntry("Why-Insecure");
+
+ /* check if mandatory entries are available */
+ if (entryIdentity == null)
+ throw new Mix3BadServerFormatException("Identity not in Server Descriptor for "+nickname);
+ if (entryDigest == null)
+ throw new Mix3BadServerFormatException("Digest not in Server Descriptor for "+nickname);
+ if (entrySignature == null)
+ throw new Mix3BadServerFormatException("Signature not in Server Descriptor for "+nickname);
+ if (entryPublished == null)
+ throw new Mix3BadServerFormatException("Published not in Server Descriptor for "+nickname);
+ if (entryValidAfter == null)
+ throw new Mix3BadServerFormatException("Valid-After not in Server Descriptor for "+nickname);
+ if (entryValidUntil == null)
+ throw new Mix3BadServerFormatException("Valid-Until not in Server Descriptor for "+nickname);
+ if (entryPacketKey == null)
+ throw new Mix3BadServerFormatException("Packet-Key not in Server Descriptor for "+nickname);
+ if (entryPacketVersions == null && entryPacketFormats == null) /* FIXME */
+ throw new Mix3BadServerFormatException("Packet-Versions not in Server Descriptor for "+nickname);
+
+ identity = Base64.decode(entryIdentity.getValue());
+ digest = Base64.decode(entryDigest.getValue());
+ signature = Base64.decode(entrySignature.getValue());
+ published = Util.parseDate(entryPublished.getValue());
+ validAfter = Util.parseDate(entryValidAfter.getValue());
+ validUntil = Util.parseDate(entryValidUntil.getValue());
+ packetKey = new RSAPublicKey(Base64.decode(entryPacketKey.getValue()));
+ packetVersions = parsePacketVersions( entryPacketVersions == null ? entryPacketFormats.getValue()
+ : entryPacketVersions.getValue() /* FIXME */ );
+
+ if (published == null)
+ throw new Mix3BadServerFormatException("Cannot parse Published: "+entryPublished.getValue()+" for "+nickname);
+ if (validAfter == null)
+ throw new Mix3BadServerFormatException("Cannot parse Valid-After: "+entryValidAfter.getValue()+" for "+nickname);
+ if (validUntil == null)
+ throw new Mix3BadServerFormatException("Cannot parse Valid-Until: "+entryValidUntil.getValue()+" for "+nickname);
+
+
+ /* optional values */
+ contact = entryContact == null ? null : entryContact.getValue();
+ if (contact != null && contact.length() > 128)
+ throw new Mix3BadServerFormatException("Contact is too long for "+nickname);
+
+ contactFingerprint = entryContactFingerprint == null ? null : entryContactFingerprint.getValue();
+ if (contactFingerprint != null && contactFingerprint.length() > 128)
+ throw new Mix3BadServerFormatException("Contact-Fingerprint is too long for "+nickname);
+
+ comments = entryComments == null ? null : entryComments.getValue();
+ if (comments != null && comments.length() >= 1024)
+ throw new Mix3BadServerFormatException("Comments is too long for "+nickname);
+
+ software = entrySoftware == null ? null : entrySoftware.getValue();
+ try {
+ secureConfiguration = entrySecureConfiguration == null ? null :
+ new Boolean(Util.parseBoolean(entrySecureConfiguration.getValue()));
+ } catch (ParseException e) {
+ throw new Mix3BadServerFormatException("Cannot parse SecureConfiguration for "+nickname, e);
+ };
+ whyInsecure = entryWhyInsecure == null ? null : entryWhyInsecure.getValue();
+ }
+
+ /**
+ * Check the signature of the server descriptor in <code>m</code>.
+ *
+ * @param m the ServerDescriptor to check
+ * @throws Mix3BadServerSignatureException if the signature is invalid.
+ */
+ private void checkSignature(DirectoryMessage m) throws Mix3BadServerSignatureException {
+ DirectoryMessage cleanedDescriptor = new DirectoryMessage(m);
+ cleanedDescriptor.blindValue("Server","Digest");
+ cleanedDescriptor.blindValue("Server","Signature");
+
+ boolean verifies;
+ try {
+ verifies = identityKey.verify(signature, Util.toOctets(cleanedDescriptor.toString()));
+ } catch (InvalidCipherTextException e) {
+ throw new Mix3BadServerSignatureException("Server signature has invalid format.", e);
+ };
+ if (!verifies)
+ throw new Mix3BadServerSignatureException("Server signature does not verify.");
+ }
+
+ /**
+ * Get this server descriptor's nickname.
+ *
+ * @return nickname of the server
+ */
+ public String getNickname() {
+ return nickname;
+ }
+
+ /**
+ * Get this server descriptor's identity key.
+ *
+ * @return asn1 encoded identity key of the server
+ */
+ public byte[] getIdentity() {
+ return identity;
+ }
+
+ /**
+ * Get this server descriptor's identity key.
+ *
+ * @return identity key of the server
+ */
+ public RSAPublicKey getIdentityKey() {
+ return identityKey;
+ }
+
+ /**
+ * Get this server descriptor's packet key.
+ *
+ * @return packet key of this server descriptor
+ */
+ public RSAPublicKey getPacketKey() {
+ return packetKey;
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/parser/DirectoryEntry.java b/src/org/noreply/fancydress/directory/parser/DirectoryEntry.java
new file mode 100644
index 0000000..c9e4e50
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectoryEntry.java
@@ -0,0 +1,82 @@
+package org.noreply.fancydress.directory.parser;
+
+/**
+ * Hold one key/value pair in a directory.
+ *
+ * This class holds one key/value pair in a directory. The
+ * values themselves are still Strings, not parsed into
+ * dates or octet strings.
+ *
+ * A <code>DirectoySection</code> is made up of entries like this.
+ *
+ * @see org.noreply.fancydress.directory.Directory
+ * @see DirectoryParser
+ * @see DirectoryMessage
+ * @see DirectorySection
+ */
+public class DirectoryEntry {
+ private String name;
+ private String value;
+
+ /**
+ * Default constructor.
+ *
+ * Creates a new DirectoryEntry.
+ *
+ * @param name name of the entry.
+ * @param value value of the entry.
+ */
+ public DirectoryEntry(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * Creates a new DirectoryEntry copying the data from <code>entry</code>
+ *
+ * @param entry entry to copy
+ */
+ public DirectoryEntry(DirectoryEntry entry) {
+ this.name = new String(entry.name);
+ this.value = new String(entry.value);
+ }
+
+ /**
+ * Blint this value in order to verify or create a signature.
+ */
+ public void blindValue() {
+ value = "";
+ };
+
+ /**
+ * Returns a string representation of the directory.
+ *
+ * @return a string representation of the object.
+ */
+ public String toString() {
+ if (value.equals(""))
+ return new String(name+":");
+ else
+ return new String(name+": "+value);
+ }
+
+ /**
+ * Returns the name of this section.
+ *
+ * @return name of this section.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the value of this section.
+ *
+ * @return value of this section.
+ */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/parser/DirectoryLexer.flex b/src/org/noreply/fancydress/directory/parser/DirectoryLexer.flex
new file mode 100644
index 0000000..91462a7
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectoryLexer.flex
@@ -0,0 +1,40 @@
+package org.noreply.fancydress.directory.parser;
+
+import java_cup.runtime.*;
+
+%%
+
+%class DirectoryLexer
+
+%public
+%line
+%column
+%cupsym DirectorySymbols
+%cup
+
+%{
+ private Symbol symbol(int type) {
+ return new Symbol(type, yyline, yycolumn);
+ }
+
+ private Symbol symbol(int type, Object value) {
+ return new Symbol(type, yyline, yycolumn, value);
+ }
+%}
+
+Space = [ \t]+
+Identifier = ([!-9] | [;-Z] | \\ | \^ | [_-~])+
+
+%%
+
+<YYINITIAL> {
+ {Space} { return symbol(DirectorySymbols.SPACE, new String(yytext())); }
+ "[" { return symbol(DirectorySymbols.LEFT_BRACKET, new String(yytext())); }
+ "]" { return symbol(DirectorySymbols.RIGHT_BRACKET, new String(yytext())); }
+ "\n" { return symbol(DirectorySymbols.NL); }
+ "\r" { return symbol(DirectorySymbols.CR); }
+ ":" { return symbol(DirectorySymbols.COLON, new String(yytext())); }
+ {Identifier} { return symbol(DirectorySymbols.IDENTIFIER, new String(yytext())); }
+}
+
+[^] { throw new Error("Illegal character <"+yytext()+">"); }
diff --git a/src/org/noreply/fancydress/directory/parser/DirectoryLexer.java b/src/org/noreply/fancydress/directory/parser/DirectoryLexer.java
new file mode 100644
index 0000000..8026ccd
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectoryLexer.java
@@ -0,0 +1,546 @@
+/* The following code was generated by JFlex 1.3.5 on 05.10.03 01:55 */
+
+package org.noreply.fancydress.directory.parser;
+
+import java_cup.runtime.*;
+
+
+/**
+ * This class is a scanner generated by
+ * <a href="http://www.jflex.de/">JFlex</a> 1.3.5
+ * on 05.10.03 01:55 from the specification file
+ * <tt>file:/home/weasel/projects/fancydress/src/org/noreply/fancydress/directory/parser/DirectoryLexer.flex</tt>
+ */
+public class DirectoryLexer implements java_cup.runtime.Scanner {
+
+ /** This character denotes the end of file */
+ final public static int YYEOF = -1;
+
+ /** initial size of the lookahead buffer */
+ final private static int YY_BUFFERSIZE = 16384;
+
+ /** lexical states */
+ final public static int YYINITIAL = 0;
+
+ /**
+ * Translates characters to character classes
+ */
+ final private static char [] yycmap = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 0, 0, 6, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 4, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0
+ };
+
+ /**
+ * Translates a state to a row index in the transition table
+ */
+ final private static int yy_rowMap [] = {
+ 0, 8, 16, 24, 8, 8, 8, 8, 8
+ };
+
+ /**
+ * The packed transition table of the DFA (part 0)
+ */
+ final private static String yy_packed0 =
+ "\1\2\1\3\1\4\1\5\1\6\1\7\1\10\1\11"+
+ "\11\0\1\3\10\0\1\4\5\0";
+
+ /**
+ * The transition table of the DFA
+ */
+ final private static int yytrans [] = yy_unpack();
+
+
+ /* error codes */
+ final private static int YY_UNKNOWN_ERROR = 0;
+ final private static int YY_ILLEGAL_STATE = 1;
+ final private static int YY_NO_MATCH = 2;
+ final private static int YY_PUSHBACK_2BIG = 3;
+
+ /* error messages for the codes above */
+ final private static String YY_ERROR_MSG[] = {
+ "Unkown internal scanner error",
+ "Internal error: unknown state",
+ "Error: could not match input",
+ "Error: pushback value was too large"
+ };
+
+ /**
+ * YY_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
+ */
+ private final static byte YY_ATTRIBUTE[] = {
+ 0, 9, 1, 1, 9, 9, 9, 9, 9
+ };
+
+ /** the input device */
+ private java.io.Reader yy_reader;
+
+ /** the current state of the DFA */
+ private int yy_state;
+
+ /** the current lexical state */
+ private int yy_lexical_state = YYINITIAL;
+
+ /** this buffer contains the current text to be matched and is
+ the source of the yytext() string */
+ private char yy_buffer[] = new char[YY_BUFFERSIZE];
+
+ /** the textposition at the last accepting state */
+ private int yy_markedPos;
+
+ /** the textposition at the last state to be included in yytext */
+ private int yy_pushbackPos;
+
+ /** the current text position in the buffer */
+ private int yy_currentPos;
+
+ /** startRead marks the beginning of the yytext() string in the buffer */
+ private int yy_startRead;
+
+ /** endRead marks the last character in the buffer, that has been read
+ from input */
+ private int yy_endRead;
+
+ /** number of newlines encountered up to the start of the matched text */
+ private int yyline;
+
+ /** the number of characters up to the start of the matched text */
+ private int yychar;
+
+ /**
+ * the number of characters from the last newline up to the start of the
+ * matched text
+ */
+ private int yycolumn;
+
+ /**
+ * yy_atBOL == true <=> the scanner is currently at the beginning of a line
+ */
+ private boolean yy_atBOL = true;
+
+ /** yy_atEOF == true <=> the scanner is at the EOF */
+ private boolean yy_atEOF;
+
+ /** denotes if the user-EOF-code has already been executed */
+ private boolean yy_eof_done;
+
+ /* user code: */
+ private Symbol symbol(int type) {
+ return new Symbol(type, yyline, yycolumn);
+ }
+
+ private Symbol symbol(int type, Object value) {
+ return new Symbol(type, yyline, yycolumn, value);
+ }
+
+
+ /**
+ * Creates a new scanner
+ * There is also a java.io.InputStream version of this constructor.
+ *
+ * @param in the java.io.Reader to read input from.
+ */
+ public DirectoryLexer(java.io.Reader in) {
+ this.yy_reader = in;
+ }
+
+ /**
+ * Creates a new scanner.
+ * There is also java.io.Reader version of this constructor.
+ *
+ * @param in the java.io.Inputstream to read input from.
+ */
+ public DirectoryLexer(java.io.InputStream in) {
+ this(new java.io.InputStreamReader(in));
+ }
+
+ /**
+ * Unpacks the split, compressed DFA transition table.
+ *
+ * @return the unpacked transition table
+ */
+ private static int [] yy_unpack() {
+ int [] trans = new int[32];
+ int offset = 0;
+ offset = yy_unpack(yy_packed0, offset, trans);
+ return trans;
+ }
+
+ /**
+ * Unpacks the compressed DFA transition table.
+ *
+ * @param packed the packed transition table
+ * @return the index of the last entry
+ */
+ private static int yy_unpack(String packed, int offset, int [] trans) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ value--;
+ do trans[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+
+ /**
+ * Refills the input buffer.
+ *
+ * @return <code>false</code>, iff there was new input.
+ *
+ * @exception IOException if any I/O-Error occurs
+ */
+ private boolean yy_refill() throws java.io.IOException {
+
+ /* first: make room (if you can) */
+ if (yy_startRead > 0) {
+ System.arraycopy(yy_buffer, yy_startRead,
+ yy_buffer, 0,
+ yy_endRead-yy_startRead);
+
+ /* translate stored positions */
+ yy_endRead-= yy_startRead;
+ yy_currentPos-= yy_startRead;
+ yy_markedPos-= yy_startRead;
+ yy_pushbackPos-= yy_startRead;
+ yy_startRead = 0;
+ }
+
+ /* is the buffer big enough? */
+ if (yy_currentPos >= yy_buffer.length) {
+ /* if not: blow it up */
+ char newBuffer[] = new char[yy_currentPos*2];
+ System.arraycopy(yy_buffer, 0, newBuffer, 0, yy_buffer.length);
+ yy_buffer = newBuffer;
+ }
+
+ /* finally: fill the buffer with new input */
+ int numRead = yy_reader.read(yy_buffer, yy_endRead,
+ yy_buffer.length-yy_endRead);
+
+ if (numRead < 0) {
+ return true;
+ }
+ else {
+ yy_endRead+= numRead;
+ return false;
+ }
+ }
+
+
+ /**
+ * Closes the input stream.
+ */
+ final public void yyclose() throws java.io.IOException {
+ yy_atEOF = true; /* indicate end of file */
+ yy_endRead = yy_startRead; /* invalidate buffer */
+
+ if (yy_reader != null)
+ yy_reader.close();
+ }
+
+
+ /**
+ * Closes the current stream, and resets the
+ * scanner to read from a new input stream.
+ *
+ * All internal variables are reset, the old input stream
+ * <b>cannot</b> be reused (internal buffer is discarded and lost).
+ * Lexical state is set to <tt>YY_INITIAL</tt>.
+ *
+ * @param reader the new input stream
+ */
+ final public void yyreset(java.io.Reader reader) throws java.io.IOException {
+ yyclose();
+ yy_reader = reader;
+ yy_atBOL = true;
+ yy_atEOF = false;
+ yy_endRead = yy_startRead = 0;
+ yy_currentPos = yy_markedPos = yy_pushbackPos = 0;
+ yyline = yychar = yycolumn = 0;
+ yy_lexical_state = YYINITIAL;
+ }
+
+
+ /**
+ * Returns the current lexical state.
+ */
+ final public int yystate() {
+ return yy_lexical_state;
+ }
+
+
+ /**
+ * Enters a new lexical state
+ *
+ * @param newState the new lexical state
+ */
+ final public void yybegin(int newState) {
+ yy_lexical_state = newState;
+ }
+
+
+ /**
+ * Returns the text matched by the current regular expression.
+ */
+ final public String yytext() {
+ return new String( yy_buffer, yy_startRead, yy_markedPos-yy_startRead );
+ }
+
+
+ /**
+ * Returns the character at position <tt>pos</tt> from the
+ * matched text.
+ *
+ * It is equivalent to yytext().charAt(pos), but faster
+ *
+ * @param pos the position of the character to fetch.
+ * A value from 0 to yylength()-1.
+ *
+ * @return the character at position pos
+ */
+ final public char yycharat(int pos) {
+ return yy_buffer[yy_startRead+pos];
+ }
+
+
+ /**
+ * Returns the length of the matched text region.
+ */
+ final public int yylength() {
+ return yy_markedPos-yy_startRead;
+ }
+
+
+ /**
+ * Reports an error that occured while scanning.
+ *
+ * In a wellformed scanner (no or only correct usage of
+ * yypushback(int) and a match-all fallback rule) this method
+ * will only be called with things that "Can't Possibly Happen".
+ * If this method is called, something is seriously wrong
+ * (e.g. a JFlex bug producing a faulty scanner etc.).
+ *
+ * Usual syntax/scanner level error handling should be done
+ * in error fallback rules.
+ *
+ * @param errorCode the code of the errormessage to display
+ */
+ private void yy_ScanError(int errorCode) {
+ String message;
+ try {
+ message = YY_ERROR_MSG[errorCode];
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ message = YY_ERROR_MSG[YY_UNKNOWN_ERROR];
+ }
+
+ throw new Error(message);
+ }
+
+
+ /**
+ * Pushes the specified amount of characters back into the input stream.
+ *
+ * They will be read again by then next call of the scanning method
+ *
+ * @param number the number of characters to be read again.
+ * This number must not be greater than yylength()!
+ */
+ private void yypushback(int number) {
+ if ( number > yylength() )
+ yy_ScanError(YY_PUSHBACK_2BIG);
+
+ yy_markedPos -= number;
+ }
+
+
+ /**
+ * Contains user EOF-code, which will be executed exactly once,
+ * when the end of file is reached
+ */
+ private void yy_do_eof() throws java.io.IOException {
+ if (!yy_eof_done) {
+ yy_eof_done = true;
+ yyclose();
+ }
+ }
+
+
+ /**
+ * Resumes scanning until the next regular expression is matched,
+ * the end of input is encountered or an I/O-Error occurs.
+ *
+ * @return the next token
+ * @exception IOException if any I/O-Error occurs
+ */
+ public java_cup.runtime.Symbol next_token() throws java.io.IOException {
+ int yy_input;
+ int yy_action;
+
+ // cached fields:
+ int yy_currentPos_l;
+ int yy_startRead_l;
+ int yy_markedPos_l;
+ int yy_endRead_l = yy_endRead;
+ char [] yy_buffer_l = yy_buffer;
+ char [] yycmap_l = yycmap;
+
+ int [] yytrans_l = yytrans;
+ int [] yy_rowMap_l = yy_rowMap;
+ byte [] yy_attr_l = YY_ATTRIBUTE;
+
+ while (true) {
+ yy_markedPos_l = yy_markedPos;
+
+ boolean yy_r = false;
+ for (yy_currentPos_l = yy_startRead; yy_currentPos_l < yy_markedPos_l;
+ yy_currentPos_l++) {
+ switch (yy_buffer_l[yy_currentPos_l]) {
+ case '\u000B':
+ case '\u000C':
+ case '\u0085':
+ case '\u2028':
+ case '\u2029':
+ yyline++;
+ yycolumn = 0;
+ yy_r = false;
+ break;
+ case '\r':
+ yyline++;
+ yycolumn = 0;
+ yy_r = true;
+ break;
+ case '\n':
+ if (yy_r)
+ yy_r = false;
+ else {
+ yyline++;
+ yycolumn = 0;
+ }
+ break;
+ default:
+ yy_r = false;
+ yycolumn++;
+ }
+ }
+
+ if (yy_r) {
+ // peek one character ahead if it is \n (if we have counted one line too much)
+ boolean yy_peek;
+ if (yy_markedPos_l < yy_endRead_l)
+ yy_peek = yy_buffer_l[yy_markedPos_l] == '\n';
+ else if (yy_atEOF)
+ yy_peek = false;
+ else {
+ boolean eof = yy_refill();
+ yy_markedPos_l = yy_markedPos;
+ yy_buffer_l = yy_buffer;
+ if (eof)
+ yy_peek = false;
+ else
+ yy_peek = yy_buffer_l[yy_markedPos_l] == '\n';
+ }
+ if (yy_peek) yyline--;
+ }
+ yy_action = -1;
+
+ yy_startRead_l = yy_currentPos_l = yy_currentPos =
+ yy_startRead = yy_markedPos_l;
+
+ yy_state = yy_lexical_state;
+
+
+ yy_forAction: {
+ while (true) {
+
+ if (yy_currentPos_l < yy_endRead_l)
+ yy_input = yy_buffer_l[yy_currentPos_l++];
+ else if (yy_atEOF) {
+ yy_input = YYEOF;
+ break yy_forAction;
+ }
+ else {
+ // store back cached positions
+ yy_currentPos = yy_currentPos_l;
+ yy_markedPos = yy_markedPos_l;
+ boolean eof = yy_refill();
+ // get translated positions and possibly new buffer
+ yy_currentPos_l = yy_currentPos;
+ yy_markedPos_l = yy_markedPos;
+ yy_buffer_l = yy_buffer;
+ yy_endRead_l = yy_endRead;
+ if (eof) {
+ yy_input = YYEOF;
+ break yy_forAction;
+ }
+ else {
+ yy_input = yy_buffer_l[yy_currentPos_l++];
+ }
+ }
+ int yy_next = yytrans_l[ yy_rowMap_l[yy_state] + yycmap_l[yy_input] ];
+ if (yy_next == -1) break yy_forAction;
+ yy_state = yy_next;
+
+ int yy_attributes = yy_attr_l[yy_state];
+ if ( (yy_attributes & 1) == 1 ) {
+ yy_action = yy_state;
+ yy_markedPos_l = yy_currentPos_l;
+ if ( (yy_attributes & 8) == 8 ) break yy_forAction;
+ }
+
+ }
+ }
+
+ // store back cached position
+ yy_markedPos = yy_markedPos_l;
+
+ switch (yy_action) {
+
+ case 5:
+ { return symbol(DirectorySymbols.RIGHT_BRACKET, new String(yytext())); }
+ case 10: break;
+ case 1:
+ { throw new Error("Illegal character <"+yytext()+">"); }
+ case 11: break;
+ case 4:
+ { return symbol(DirectorySymbols.LEFT_BRACKET, new String(yytext())); }
+ case 12: break;
+ case 8:
+ { return symbol(DirectorySymbols.COLON, new String(yytext())); }
+ case 13: break;
+ case 7:
+ { return symbol(DirectorySymbols.CR); }
+ case 14: break;
+ case 6:
+ { return symbol(DirectorySymbols.NL); }
+ case 15: break;
+ case 3:
+ { return symbol(DirectorySymbols.IDENTIFIER, new String(yytext())); }
+ case 16: break;
+ case 2:
+ { return symbol(DirectorySymbols.SPACE, new String(yytext())); }
+ case 17: break;
+ default:
+ if (yy_input == YYEOF && yy_startRead == yy_currentPos) {
+ yy_atEOF = true;
+ yy_do_eof();
+ { return new java_cup.runtime.Symbol(DirectorySymbols.EOF); }
+ }
+ else {
+ yy_ScanError(YY_NO_MATCH);
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/src/org/noreply/fancydress/directory/parser/DirectoryMessage.java b/src/org/noreply/fancydress/directory/parser/DirectoryMessage.java
new file mode 100644
index 0000000..81ce6e2
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectoryMessage.java
@@ -0,0 +1,130 @@
+package org.noreply.fancydress.directory.parser;
+
+import java.util.*;
+
+/**
+ * Hold all sections and entries that are in a directory.
+ *
+ * This class holds all sections and entries of a directory. All data
+ * are stored in sections, which in turn hold entries of key:value pairs.
+ *
+ * The values themselves are not yet parsed, nor is the content
+ * cryptographically verified.
+ *
+ * The <code>DirectoryParser</code> returns an object of this class.
+ *
+ * @see org.noreply.fancydress.directory.Directory
+ * @see DirectoryParser
+ * @see DirectorySection
+ * @see DirectoryEntry
+ */
+public class DirectoryMessage implements Cloneable {
+ /**
+ * List of directory sections
+ */
+ private ArrayList sections;
+
+ /**
+ * Default constructor.
+ *
+ * Creates a new DirectoryMessage, adding <code>section</code> to
+ * the section list.
+ *
+ * @param section First section of the directory. It is used directly, not copied.
+ */
+ public DirectoryMessage(DirectorySection section) {
+ this();
+ sections.add(section);
+ }
+
+ /**
+ * Emty constructor.
+ *
+ * Creates a new DirectoryMessage.
+ */
+ public DirectoryMessage() {
+ sections = new ArrayList();
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * Creates a new DirectoryMessage copying all values (recursively) from
+ * <code>message</code>.
+ *
+ * @param message message to copy from.
+ */
+ public DirectoryMessage(DirectoryMessage message) {
+ this();
+ for (Iterator i = message.sections.iterator(); i.hasNext(); )
+ sections.add(new DirectorySection((DirectorySection)i.next()));
+ }
+
+ /**
+ * Add a section to this message.
+ *
+ * @param section Section to add. It is used directly, not copied.
+ */
+ public void addSection(DirectorySection section) {
+ sections.add(section);
+ }
+
+ /**
+ * Blind a value in order to verify or create a signature.
+ *
+ * @param section Section in which to blind the entry named.
+ * @param entry Entry to blind.
+ * @return The number of entries blinded.
+ */
+ public int blindValue(String section, String entry) {
+ int count = 0;
+ for (Iterator i = sections.iterator(); i.hasNext(); ) {
+ DirectorySection s = (DirectorySection) i.next();
+ if (s.getName().equals(section))
+ count += s.blindValue(entry);
+ };
+ return count;
+ }
+
+ /**
+ * Return the first section with that name
+ *
+ * @param section Section to return.
+ * @return first DirectorySection that matches the name or null.
+ */
+ public DirectorySection getSection(String section) {
+ int count = 0;
+ for (Iterator i = sections.iterator(); i.hasNext(); ) {
+ DirectorySection s = (DirectorySection) i.next();
+ if (s.getName().equals(section))
+ return s;
+ };
+ return null;
+ }
+
+ /**
+ * Return an iterator over the sections.
+ *
+ * @return Iterator over sections.
+ */
+ public Iterator getSectionsIterator() {
+ return sections.iterator();
+ }
+
+ /**
+ * Returns a string representation of the directory.
+ *
+ * This representation can be parsed again using the directory parser.
+ *
+ * @return a string representation of the object.
+ */
+ public String toString() {
+ StringBuffer strbuf = new StringBuffer();
+
+ for (Iterator i = sections.iterator(); i.hasNext(); ) {
+ Object o = i.next();
+ strbuf.append(o.toString());
+ }
+ return strbuf.toString();
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/parser/DirectoryParser.cup b/src/org/noreply/fancydress/directory/parser/DirectoryParser.cup
new file mode 100644
index 0000000..1aed682
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectoryParser.cup
@@ -0,0 +1,141 @@
+package org.noreply.fancydress.directory.parser;
+
+import java_cup.runtime.*;
+
+parser code {:
+ /* FIXME */
+ /* Change the method report_error so it will display the line and
+ column of where the error occurred in the input as well as the
+ reason for the error which is passed into the method in the
+ String 'message'. */
+ public void report_error(String message, Object info) {
+
+ /* Create a StringBuffer called 'm' with the string 'Error' in it. */
+ StringBuffer m = new StringBuffer("Error");
+
+ /* Check if the information passed to the method is the same
+ type as the type java_cup.runtime.Symbol. */
+ if (info instanceof java_cup.runtime.Symbol) {
+ /* Declare a java_cup.runtime.Symbol object 's' with the
+ information in the object info that is being typecasted
+ as a java_cup.runtime.Symbol object. */
+ java_cup.runtime.Symbol s = ((java_cup.runtime.Symbol) info);
+
+ /* Check if the line number in the input is greater or
+ equal to zero. */
+ if (s.left >= 0) {
+ /* Add to the end of the StringBuffer error message
+ the line number of the error in the input. */
+ m.append(" in line "+(s.left+1));
+ /* Check if the column number in the input is greater
+ or equal to zero. */
+ if (s.right >= 0)
+ /* Add to the end of the StringBuffer error message
+ the column number of the error in the input. */
+ m.append(", column "+(s.right+1));
+ }
+ }
+
+ /* Add to the end of the StringBuffer error message created in
+ this method the message that was passed into this method. */
+ m.append(" : "+message);
+
+ /* Print the contents of the StringBuffer 'm', which contains
+ an error message, out on a line. */
+ System.err.println(m);
+ }
+
+ /* Change the method report_fatal_error so when it reports a fatal
+ error it will display the line and column number of where the
+ fatal error occurred in the input as well as the reason for the
+ fatal error which is passed into the method in the object
+ 'message' and then exit.*/
+ public void report_fatal_error(String message, Object info) {
+ report_error(message, info);
+ System.exit(1);
+ }
+:};
+
+terminal NL, CR;
+terminal String SPACE, IDENTIFIER, LEFT_BRACKET, RIGHT_BRACKET, COLON;
+
+non terminal DirectoryEntry Entry;
+non terminal DirectorySection Section;
+non terminal DirectoryMessage Message;
+non terminal String Header;
+non terminal String OptValue, Value, NonSpaceValueChar, ValueChar, NonSpaceValue;
+non terminal String OptSpace;
+non terminal String EndOfLine, OptSpaceEndOfLine;
+
+start with Message;
+
+ Message ::=
+ Section:s
+ {: RESULT = new DirectoryMessage(s); :}
+ |
+ Message:m Section:s
+ {: m.addSection(s); RESULT = m; :}
+ ;
+ Section ::=
+ Header:h
+ {: RESULT = new DirectorySection(h); :}
+ |
+ Section:s Entry:e
+ {: s.addEntry(e); RESULT = s; :}
+ ;
+ Header ::=
+ LEFT_BRACKET IDENTIFIER:i RIGHT_BRACKET OptSpace EndOfLine
+ {: RESULT = i; :}
+ ;
+ Entry ::=
+ IDENTIFIER:i COLON SPACE OptValue:v EndOfLine
+ {: RESULT = new DirectoryEntry(i, v.trim()); :}
+ ;
+
+ OptValue ::=
+ Value:v OptSpace
+ {: RESULT = v; :}
+ |
+ {: RESULT = new String(""); :}
+ ;
+ Value ::=
+ NonSpaceValue:v
+ {: RESULT = v; :}
+ |
+ Value:v1 SPACE:c NonSpaceValue:v2
+ {: RESULT = new String(v1+c+v2); :}
+ ;
+ NonSpaceValue ::=
+ NonSpaceValueChar:c
+ {: RESULT = c; :}
+ |
+ NonSpaceValue:v NonSpaceValueChar:c
+ {: RESULT = new String(v+c); :}
+ ;
+ NonSpaceValueChar ::=
+ IDENTIFIER:c
+ {: RESULT = c; :}
+ |
+ COLON:c
+ {: RESULT = c; :}
+ |
+ LEFT_BRACKET:c
+ {: RESULT = c; :}
+ |
+ RIGHT_BRACKET:c
+ {: RESULT = c; :}
+ ;
+
+
+ OptSpace ::=
+ SPACE
+ |
+ ;
+
+ EndOfLine ::=
+ NL
+ |
+ CR
+ |
+ CR NL
+ ;
diff --git a/src/org/noreply/fancydress/directory/parser/DirectoryParser.java b/src/org/noreply/fancydress/directory/parser/DirectoryParser.java
new file mode 100644
index 0000000..2fa66ea
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectoryParser.java
@@ -0,0 +1,477 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.10k
+// Sun Oct 05 01:55:08 CEST 2003
+//----------------------------------------------------
+
+package org.noreply.fancydress.directory.parser;
+
+import java_cup.runtime.*;
+
+/** CUP v0.10k generated parser.
+ * @version Sun Oct 05 01:55:08 CEST 2003
+ */
+public class DirectoryParser extends java_cup.runtime.lr_parser {
+
+ /** Default constructor. */
+ public DirectoryParser() {super();}
+
+ /** Constructor which sets the default scanner. */
+ public DirectoryParser(java_cup.runtime.Scanner s) {super(s);}
+
+ /** Production table. */
+ protected static final short _production_table[][] =
+ unpackFromStrings(new String[] {
+ "\000\026\000\002\002\004\000\002\005\003\000\002\005" +
+ "\004\000\002\004\003\000\002\004\004\000\002\006\007" +
+ "\000\002\003\007\000\002\007\004\000\002\007\002\000" +
+ "\002\010\003\000\002\010\005\000\002\013\003\000\002" +
+ "\013\004\000\002\011\003\000\002\011\003\000\002\011" +
+ "\003\000\002\011\003\000\002\014\003\000\002\014\002" +
+ "\000\002\015\003\000\002\015\003\000\002\015\004" });
+
+ /** Access to production table. */
+ public short[][] production_table() {return _production_table;}
+
+ /** Parse-action table. */
+ protected static final short[][] _action_table =
+ unpackFromStrings(new String[] {
+ "\000\040\000\004\010\005\001\002\000\006\002\042\010" +
+ "\005\001\002\000\004\007\034\001\002\000\010\002\000" +
+ "\007\011\010\000\001\002\000\010\002\ufffe\007\ufffe\010" +
+ "\ufffe\001\002\000\010\002\ufffd\007\ufffd\010\ufffd\001\002" +
+ "\000\004\012\012\001\002\000\004\006\013\001\002\000" +
+ "\016\004\ufff9\005\ufff9\007\021\010\020\011\015\012\022" +
+ "\001\002\000\020\004\ufff8\005\ufff8\006\ufff8\007\021\010" +
+ "\020\011\015\012\022\001\002\000\020\004\ufff1\005\ufff1" +
+ "\006\ufff1\007\ufff1\010\ufff1\011\ufff1\012\ufff1\001\002\000" +
+ "\010\004\uffef\005\uffef\006\030\001\002\000\020\004\ufff6" +
+ "\005\ufff6\006\ufff6\007\ufff6\010\ufff6\011\ufff6\012\ufff6\001" +
+ "\002\000\020\004\ufff2\005\ufff2\006\ufff2\007\ufff2\010\ufff2" +
+ "\011\ufff2\012\ufff2\001\002\000\020\004\ufff4\005\ufff4\006" +
+ "\ufff4\007\ufff4\010\ufff4\011\ufff4\012\ufff4\001\002\000\020" +
+ "\004\ufff3\005\ufff3\006\ufff3\007\ufff3\010\ufff3\011\ufff3\012" +
+ "\ufff3\001\002\000\006\004\025\005\026\001\002\000\010" +
+ "\002\ufffb\007\ufffb\010\ufffb\001\002\000\010\002\uffee\007" +
+ "\uffee\010\uffee\001\002\000\012\002\uffed\004\027\007\uffed" +
+ "\010\uffed\001\002\000\010\002\uffec\007\uffec\010\uffec\001" +
+ "\002\000\016\004\ufff0\005\ufff0\007\021\010\020\011\015" +
+ "\012\022\001\002\000\006\004\ufffa\005\ufffa\001\002\000" +
+ "\020\004\ufff7\005\ufff7\006\ufff7\007\021\010\020\011\015" +
+ "\012\022\001\002\000\020\004\ufff5\005\ufff5\006\ufff5\007" +
+ "\ufff5\010\ufff5\011\ufff5\012\ufff5\001\002\000\004\011\035" +
+ "\001\002\000\010\004\uffef\005\uffef\006\036\001\002\000" +
+ "\006\004\ufff0\005\ufff0\001\002\000\006\004\025\005\026" +
+ "\001\002\000\010\002\ufffc\007\ufffc\010\ufffc\001\002\000" +
+ "\010\002\uffff\007\011\010\uffff\001\002\000\004\002\001" +
+ "\001\002" });
+
+ /** Access to parse-action table. */
+ public short[][] action_table() {return _action_table;}
+
+ /** <code>reduce_goto</code> table. */
+ protected static final short[][] _reduce_table =
+ unpackFromStrings(new String[] {
+ "\000\040\000\010\004\005\005\003\006\006\001\001\000" +
+ "\006\004\040\006\006\001\001\000\002\001\001\000\004" +
+ "\003\007\001\001\000\002\001\001\000\002\001\001\000" +
+ "\002\001\001\000\002\001\001\000\012\007\022\010\015" +
+ "\011\016\013\013\001\001\000\004\011\032\001\001\000" +
+ "\002\001\001\000\004\014\030\001\001\000\002\001\001" +
+ "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+ "\004\015\023\001\001\000\002\001\001\000\002\001\001" +
+ "\000\002\001\001\000\002\001\001\000\006\011\016\013" +
+ "\031\001\001\000\002\001\001\000\004\011\032\001\001" +
+ "\000\002\001\001\000\002\001\001\000\004\014\036\001" +
+ "\001\000\002\001\001\000\004\015\037\001\001\000\002" +
+ "\001\001\000\004\003\007\001\001\000\002\001\001" });
+
+ /** Access to <code>reduce_goto</code> table. */
+ public short[][] reduce_table() {return _reduce_table;}
+
+ /** Instance of action encapsulation class. */
+ protected CUP$DirectoryParser$actions action_obj;
+
+ /** Action encapsulation object initializer. */
+ protected void init_actions()
+ {
+ action_obj = new CUP$DirectoryParser$actions(this);
+ }
+
+ /** Invoke a user supplied parse action. */
+ public java_cup.runtime.Symbol do_action(
+ int act_num,
+ java_cup.runtime.lr_parser parser,
+ java.util.Stack stack,
+ int top)
+ throws java.lang.Exception
+ {
+ /* call code in generated class */
+ return action_obj.CUP$DirectoryParser$do_action(act_num, parser, stack, top);
+ }
+
+ /** Indicates start state. */
+ public int start_state() {return 0;}
+ /** Indicates start production. */
+ public int start_production() {return 0;}
+
+ /** <code>EOF</code> Symbol index. */
+ public int EOF_sym() {return 0;}
+
+ /** <code>error</code> Symbol index. */
+ public int error_sym() {return 1;}
+
+
+
+ /* FIXME */
+ /* Change the method report_error so it will display the line and
+ column of where the error occurred in the input as well as the
+ reason for the error which is passed into the method in the
+ String 'message'. */
+ public void report_error(String message, Object info) {
+
+ /* Create a StringBuffer called 'm' with the string 'Error' in it. */
+ StringBuffer m = new StringBuffer("Error");
+
+ /* Check if the information passed to the method is the same
+ type as the type java_cup.runtime.Symbol. */
+ if (info instanceof java_cup.runtime.Symbol) {
+ /* Declare a java_cup.runtime.Symbol object 's' with the
+ information in the object info that is being typecasted
+ as a java_cup.runtime.Symbol object. */
+ java_cup.runtime.Symbol s = ((java_cup.runtime.Symbol) info);
+
+ /* Check if the line number in the input is greater or
+ equal to zero. */
+ if (s.left >= 0) {
+ /* Add to the end of the StringBuffer error message
+ the line number of the error in the input. */
+ m.append(" in line "+(s.left+1));
+ /* Check if the column number in the input is greater
+ or equal to zero. */
+ if (s.right >= 0)
+ /* Add to the end of the StringBuffer error message
+ the column number of the error in the input. */
+ m.append(", column "+(s.right+1));
+ }
+ }
+
+ /* Add to the end of the StringBuffer error message created in
+ this method the message that was passed into this method. */
+ m.append(" : "+message);
+
+ /* Print the contents of the StringBuffer 'm', which contains
+ an error message, out on a line. */
+ System.err.println(m);
+ }
+
+ /* Change the method report_fatal_error so when it reports a fatal
+ error it will display the line and column number of where the
+ fatal error occurred in the input as well as the reason for the
+ fatal error which is passed into the method in the object
+ 'message' and then exit.*/
+ public void report_fatal_error(String message, Object info) {
+ report_error(message, info);
+ System.exit(1);
+ }
+
+}
+
+/** Cup generated class to encapsulate user supplied action code.*/
+class CUP$DirectoryParser$actions {
+ private final DirectoryParser parser;
+
+ /** Constructor */
+ CUP$DirectoryParser$actions(DirectoryParser parser) {
+ this.parser = parser;
+ }
+
+ /** Method with the actual generated action code. */
+ public final java_cup.runtime.Symbol CUP$DirectoryParser$do_action(
+ int CUP$DirectoryParser$act_num,
+ java_cup.runtime.lr_parser CUP$DirectoryParser$parser,
+ java.util.Stack CUP$DirectoryParser$stack,
+ int CUP$DirectoryParser$top)
+ throws java.lang.Exception
+ {
+ /* Symbol object for return from actions */
+ java_cup.runtime.Symbol CUP$DirectoryParser$result;
+
+ /* select the action based on the action number */
+ switch (CUP$DirectoryParser$act_num)
+ {
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 21: // EndOfLine ::= CR NL
+ {
+ String RESULT = null;
+
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(11/*EndOfLine*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 20: // EndOfLine ::= CR
+ {
+ String RESULT = null;
+
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(11/*EndOfLine*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 19: // EndOfLine ::= NL
+ {
+ String RESULT = null;
+
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(11/*EndOfLine*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 18: // OptSpace ::=
+ {
+ String RESULT = null;
+
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(10/*OptSpace*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 17: // OptSpace ::= SPACE
+ {
+ String RESULT = null;
+
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(10/*OptSpace*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 16: // NonSpaceValueChar ::= RIGHT_BRACKET
+ {
+ String RESULT = null;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = c;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(7/*NonSpaceValueChar*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 15: // NonSpaceValueChar ::= LEFT_BRACKET
+ {
+ String RESULT = null;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = c;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(7/*NonSpaceValueChar*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 14: // NonSpaceValueChar ::= COLON
+ {
+ String RESULT = null;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = c;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(7/*NonSpaceValueChar*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 13: // NonSpaceValueChar ::= IDENTIFIER
+ {
+ String RESULT = null;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = c;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(7/*NonSpaceValueChar*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 12: // NonSpaceValue ::= NonSpaceValue NonSpaceValueChar
+ {
+ String RESULT = null;
+ int vleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int vright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ String v = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = new String(v+c);
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(9/*NonSpaceValue*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 11: // NonSpaceValue ::= NonSpaceValueChar
+ {
+ String RESULT = null;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = c;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(9/*NonSpaceValue*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 10: // Value ::= Value SPACE NonSpaceValue
+ {
+ String RESULT = null;
+ int v1left = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-2)).left;
+ int v1right = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-2)).right;
+ String v1 = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-2)).value;
+ int cleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int cright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ String c = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ int v2left = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int v2right = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String v2 = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = new String(v1+c+v2);
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(6/*Value*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-2)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 9: // Value ::= NonSpaceValue
+ {
+ String RESULT = null;
+ int vleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int vright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String v = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = v;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(6/*Value*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 8: // OptValue ::=
+ {
+ String RESULT = null;
+ RESULT = new String("");
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(5/*OptValue*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 7: // OptValue ::= Value OptSpace
+ {
+ String RESULT = null;
+ int vleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int vright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ String v = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ RESULT = v;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(5/*OptValue*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 6: // Entry ::= IDENTIFIER COLON SPACE OptValue EndOfLine
+ {
+ DirectoryEntry RESULT = null;
+ int ileft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-4)).left;
+ int iright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-4)).right;
+ String i = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-4)).value;
+ int vleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int vright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ String v = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ RESULT = new DirectoryEntry(i, v.trim());
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(1/*Entry*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-4)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 5: // Header ::= LEFT_BRACKET IDENTIFIER RIGHT_BRACKET OptSpace EndOfLine
+ {
+ String RESULT = null;
+ int ileft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-3)).left;
+ int iright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-3)).right;
+ String i = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-3)).value;
+ RESULT = i;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(4/*Header*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-4)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 4: // Section ::= Section Entry
+ {
+ DirectorySection RESULT = null;
+ int sleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int sright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ DirectorySection s = (DirectorySection)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ int eleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int eright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ DirectoryEntry e = (DirectoryEntry)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ s.addEntry(e); RESULT = s;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(2/*Section*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 3: // Section ::= Header
+ {
+ DirectorySection RESULT = null;
+ int hleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int hright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ String h = (String)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = new DirectorySection(h);
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(2/*Section*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 2: // Message ::= Message Section
+ {
+ DirectoryMessage RESULT = null;
+ int mleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int mright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ DirectoryMessage m = (DirectoryMessage)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ int sleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int sright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ DirectorySection s = (DirectorySection)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ m.addSection(s); RESULT = m;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(3/*Message*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 1: // Message ::= Section
+ {
+ DirectoryMessage RESULT = null;
+ int sleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left;
+ int sright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right;
+ DirectorySection s = (DirectorySection)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).value;
+ RESULT = new DirectoryMessage(s);
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(3/*Message*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ return CUP$DirectoryParser$result;
+
+ /*. . . . . . . . . . . . . . . . . . . .*/
+ case 0: // $START ::= Message EOF
+ {
+ Object RESULT = null;
+ int start_valleft = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left;
+ int start_valright = ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).right;
+ DirectoryMessage start_val = (DirectoryMessage)((java_cup.runtime.Symbol) CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).value;
+ RESULT = start_val;
+ CUP$DirectoryParser$result = new java_cup.runtime.Symbol(0/*$START*/, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-1)).left, ((java_cup.runtime.Symbol)CUP$DirectoryParser$stack.elementAt(CUP$DirectoryParser$top-0)).right, RESULT);
+ }
+ /* ACCEPT */
+ CUP$DirectoryParser$parser.done_parsing();
+ return CUP$DirectoryParser$result;
+
+ /* . . . . . .*/
+ default:
+ throw new Exception(
+ "Invalid action number found in internal parse table");
+
+ }
+ }
+}
+
diff --git a/src/org/noreply/fancydress/directory/parser/DirectorySection.java b/src/org/noreply/fancydress/directory/parser/DirectorySection.java
new file mode 100644
index 0000000..f27ee86
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectorySection.java
@@ -0,0 +1,129 @@
+package org.noreply.fancydress.directory.parser;
+
+import java.util.*;
+
+/**
+ * Hold entries of a directory section.
+ *
+ * This class holds all entries of a directory section. All entries are
+ * key:value pairs.
+ *
+ * The values themselves are not yet parsed, nor is the content
+ * cryptographically verified.
+ *
+ * A <code>DirectoryMessage</code> is made up of instances of this class.
+ *
+ * @see org.noreply.fancydress.directory.Directory
+ * @see DirectoryParser
+ * @see DirectoryMessage
+ * @see DirectoryEntry
+ */
+public class DirectorySection {
+ /**
+ * name of this section
+ */
+ private String name;
+ /**
+ * List of entries
+ */
+ private ArrayList entries;
+
+ /**
+ * Default constructor.
+ *
+ * Creates a new DirectorySection with the name passed as parameter.
+ *
+ * @param name name of this section
+ */
+ public DirectorySection(String name) {
+ this.name = name;
+ entries = new ArrayList();
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * Creates a new DirectorySection copying all values (recursively) from
+ * <code>section</code>.
+ *
+ * @param section section to copy from.
+ */
+ public DirectorySection(DirectorySection section) {
+ name = new String(section.name);
+ entries = new ArrayList();
+ for (Iterator i = section.entries.iterator(); i.hasNext(); )
+ entries.add(new DirectoryEntry((DirectoryEntry)i.next()));
+ }
+
+ /**
+ * Add an entry to this section.
+ *
+ * @param entry Entry to add. It is used directly, not copied.
+ */
+ public void addEntry(DirectoryEntry entry) {
+ entries.add(entry);
+ }
+
+ /**
+ * Blint a value in order to verify or create a signature.
+ *
+ * @param entry Entry to blind.
+ * @return The number of entries blinded.
+ */
+ public int blindValue(String entry) {
+ int count = 0;
+ for (Iterator i = entries.iterator(); i.hasNext(); ) {
+ DirectoryEntry e = (DirectoryEntry) i.next();
+ if (e.getName().equals(entry)) {
+ e.blindValue();
+ count ++;
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * Return the first entry with that name
+ *
+ * @param entry Entry to return.
+ * @return first DirectoryEntry that matches the name or null.
+ */
+ public DirectoryEntry getEntry(String entry) {
+ int count = 0;
+ for (Iterator i = entries.iterator(); i.hasNext(); ) {
+ DirectoryEntry e = (DirectoryEntry) i.next();
+ if (e.getName().equals(entry))
+ return e;
+ };
+ return null;
+ }
+
+
+ /**
+ * Returns a string representation of this section.
+ *
+ * @return a string representation of the object.
+ */
+ public String toString() {
+ StringBuffer strbuf = new StringBuffer();
+ strbuf.append("[");
+ strbuf.append(name);
+ strbuf.append("]\n");
+ for (Iterator i = entries.iterator(); i.hasNext(); ) {
+ Object o = i.next();
+ strbuf.append(o.toString());
+ strbuf.append("\n");
+ }
+ return strbuf.toString();
+ }
+
+ /**
+ * Returns the name of this section.
+ *
+ * @return name of this section.
+ */
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/org/noreply/fancydress/directory/parser/DirectorySymbols.java b/src/org/noreply/fancydress/directory/parser/DirectorySymbols.java
new file mode 100644
index 0000000..481ad2f
--- /dev/null
+++ b/src/org/noreply/fancydress/directory/parser/DirectorySymbols.java
@@ -0,0 +1,22 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.10k
+// Sun Oct 05 01:55:07 CEST 2003
+//----------------------------------------------------
+
+package org.noreply.fancydress.directory.parser;
+
+/** CUP generated class containing symbol constants. */
+public class DirectorySymbols {
+ /* terminals */
+ public static final int CR = 3;
+ public static final int COLON = 8;
+ public static final int EOF = 0;
+ public static final int RIGHT_BRACKET = 7;
+ public static final int NL = 2;
+ public static final int error = 1;
+ public static final int SPACE = 4;
+ public static final int IDENTIFIER = 5;
+ public static final int LEFT_BRACKET = 6;
+}
+
diff --git a/src/org/noreply/fancydress/misc/Util.java b/src/org/noreply/fancydress/misc/Util.java
new file mode 100644
index 0000000..e25ffe7
--- /dev/null
+++ b/src/org/noreply/fancydress/misc/Util.java
@@ -0,0 +1,227 @@
+package org.noreply.fancydress.misc;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * This class implements some functionality needed all over the place.
+ */
+public class Util {
+ /**
+ * return the octets of this String (US-ASCII encoding)
+ *
+ * @param m String to convert to the octet array
+ * @return octet array
+ */
+ public static byte[] toOctets (String m) {
+ byte[] result;
+ try {
+ result = m.getBytes("US-ASCII");
+ } catch (IOException e) {
+ throw new Error(e);
+ }
+ return(result);
+ }
+
+ /**
+ * return a base16 or hex representation of an octet array
+ *
+ * @param buf octet array
+ * @return the octet array in hex
+ */
+ public static String asHex(byte buf[]) {
+ StringBuffer strbuf = new StringBuffer(buf.length * 3);
+ int i;
+
+ for (i = 0; i < buf.length; i++) {
+ if (((int) buf[i] & 0xff) < 0x10)
+ strbuf.append("0");
+
+ strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
+ strbuf.append(" ");
+ }
+ return strbuf.toString();
+ }
+
+ /**
+ * Read an octet array from a hex encoding.
+ *
+ * Read an octet array from a hex encoding, ignoring all whitespace.
+ *
+ * @param s hex encoded octet array
+ * @param octet array
+ */
+ public static byte[] fromHex(String s) {
+ boolean gotone = false;
+ String r = "";
+ ArrayList a = new ArrayList();
+
+ for (int i=0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (Character.isWhitespace(c))
+ continue;
+ r = r + c;
+ if (gotone) {
+ a.add(Integer.valueOf(r, 16));
+ r = "";
+ }
+ gotone = !gotone;
+ }
+
+ byte[] result = new byte[a.size()];
+ for (int i=0; i<result.length; i++) {
+ Integer o = (Integer) a.get(i);
+ int val = o.intValue();
+ result[i] = (byte) val;
+ };
+ return(result);
+ }
+
+ /**
+ * Return the smaller one of a and b.
+ *
+ * @param a
+ * @param b
+ * @return a&lt;b ? a : b
+ */
+ public static int min(int a, int b) {
+ return a<b ? a : b;
+ }
+
+ /**
+ * Return the greater one of a and b.
+ *
+ * @param a
+ * @param b
+ * @return a&gt;b ? a : b
+ */
+ public static int max(int a, int b) {
+ return a>b ? a : b;
+ }
+
+ /**
+ * Check if two octet arrays are equal
+ *
+ * @param a
+ * @param b
+ * @return true if a is equal to b
+ */
+ public static boolean equal(byte[] a, byte[] b) {
+ boolean result = true;
+ if (a.length != b.length)
+ return(false);
+ for (int i=0; i<a.length; i++)
+ result = result && (a[i] == b[i]);
+ return(result);
+ };
+
+ /**
+ * Concatinate two octet arrays
+ *
+ * @param a a
+ * @param b b
+ * @return a + b
+ */
+ public static byte[] concat(byte[] a, byte[] b) {
+ byte[] result = new byte[ a.length + b.length ];
+ for (int i=0; i<a.length; i++)
+ result[i] = a[i];
+ for (int i=0; i<b.length; i++)
+ result[a.length + i] = b[i];
+ return(result);
+ }
+
+ /**
+ * Return a substring of an octet array
+ *
+ * @param a an octet array
+ * @param s starting position
+ * @param len length to copy
+ * @return the slice of <code>a</code> starting at <code>s</code> of <code>len</code> octets in length
+ public static byte[] slice(byte[] a, int s, int len) {
+ byte[] result = new byte[len];
+ System.arraycopy(a, s, result, 0, len);
+ return(result);
+ }
+
+ /* FIXME: switch to singleton so we need not generate the formats every time */
+ /**
+ * Parse an ISO date yyyy-MM-dd.
+ *
+ * @param s a string holding a date
+ * @return Date the date represented in <code>s</code>
+ * @throws ParseException if <code>s</code> is not a valid date of that format
+ */
+ public static Date parseDate(String s) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ format.setLenient(false);
+ try {
+ return format.parse(s);
+ } catch (ParseException e) {
+ /* FIXME: US date.. */
+ format = new SimpleDateFormat("yyyy/MM/dd");
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ format.setLenient(false);
+ try {
+ return format.parse(s);
+ } catch (ParseException e2) {
+ return null;
+ }
+ }
+ }
+ /**
+ * Parse an ISO date yyyy-MM-dd hh:mm:ss.
+ *
+ * @param s a string holding a date
+ * @return Date the date represented in <code>s</code>
+ * @throws ParseException if <code>s</code> is not a valid date of that format
+ */
+ public static Date parseDateTime(String s) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ format.setLenient(false);
+ try {
+ return format.parse(s);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+ /**
+ * Parse an ISO date yyyy-MM-dd hh:mm:ss.SSSS.
+ *
+ * @param s a string holding a date
+ * @return Date the date represented in <code>s</code>
+ * @throws ParseException if <code>s</code> is not a valid date of that format
+ */
+ public static Date parseDateTimeMS(String s) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSSS");
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ format.setLenient(false);
+ try {
+ return format.parse(s);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+ /**
+ * Parse a boolean (yes/no) value.
+ *
+ * @param s a string, either "yes" or "no"
+ * @return true if <code>s</code> is "yes", false if it is "no"
+ * @throws ParseException if <code>s</code> is neither "yes" or "no"
+ */
+ public static boolean parseBoolean(String s) throws ParseException {
+ if (s.equals("yes"))
+ return true;
+ else if (s.equals("no"))
+ return false;
+ else {
+ throw new ParseException("Cannot parse boolean expression "+s,0);
+ }
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadArgumentsChainTooLongException.java b/src/org/noreply/fancydress/status/Mix3BadArgumentsChainTooLongException.java
new file mode 100644
index 0000000..bf6bea2
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadArgumentsChainTooLongException.java
@@ -0,0 +1,14 @@
+package org.noreply.fancydress.status;
+
+/**
+ * The operation failed because the chain specified does not fit into a single
+ * leg.
+ */
+public class Mix3BadArgumentsChainTooLongException extends Mix3BadArgumentsException {
+ public Mix3BadArgumentsChainTooLongException(String s) {
+ super(s);
+ }
+ public Mix3BadArgumentsChainTooLongException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadArgumentsException.java b/src/org/noreply/fancydress/status/Mix3BadArgumentsException.java
new file mode 100644
index 0000000..6c34f36
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadArgumentsException.java
@@ -0,0 +1,13 @@
+package org.noreply.fancydress.status;
+
+/**
+ * Base class for other bad arguments exceptions.
+ */
+public abstract class Mix3BadArgumentsException extends Mix3Exception {
+ public Mix3BadArgumentsException(String s) {
+ super(s);
+ }
+ public Mix3BadArgumentsException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java b/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java
new file mode 100644
index 0000000..5528728
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java
@@ -0,0 +1,14 @@
+package org.noreply.fancydress.status;
+
+/**
+ * The operation failed because the directory was not correctly signed,
+ * or no signature was found.
+ */
+public class Mix3BadDirectorySignatureException extends Mix3BadSignatureException {
+ public Mix3BadDirectorySignatureException(String s) {
+ super(s);
+ }
+ public Mix3BadDirectorySignatureException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadFormatException.java b/src/org/noreply/fancydress/status/Mix3BadFormatException.java
new file mode 100644
index 0000000..bde6f6a
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadFormatException.java
@@ -0,0 +1,13 @@
+package org.noreply.fancydress.status;
+
+/**
+ * Base class for other bad format exceptions.
+ */
+public abstract class Mix3BadFormatException extends Mix3Exception {
+ public Mix3BadFormatException(String s) {
+ super(s);
+ }
+ public Mix3BadFormatException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadServerFormatException.java b/src/org/noreply/fancydress/status/Mix3BadServerFormatException.java
new file mode 100644
index 0000000..ab5590c
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadServerFormatException.java
@@ -0,0 +1,13 @@
+package org.noreply.fancydress.status;
+
+/**
+ * An entry in the server descriptor violates the syntax given in the specs.
+ */
+public class Mix3BadServerFormatException extends Mix3BadFormatException {
+ public Mix3BadServerFormatException(String s) {
+ super(s);
+ }
+ public Mix3BadServerFormatException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadServerSignatureException.java b/src/org/noreply/fancydress/status/Mix3BadServerSignatureException.java
new file mode 100644
index 0000000..5173a3e
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadServerSignatureException.java
@@ -0,0 +1,14 @@
+package org.noreply.fancydress.status;
+
+/**
+ * The operation failed because the server descriptor was not correctly signed,
+ * or no signature was found.
+ */
+public class Mix3BadServerSignatureException extends Mix3BadSignatureException {
+ public Mix3BadServerSignatureException(String s) {
+ super(s);
+ }
+ public Mix3BadServerSignatureException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadServerUnrecognizedVersionException.java b/src/org/noreply/fancydress/status/Mix3BadServerUnrecognizedVersionException.java
new file mode 100644
index 0000000..d2cc364
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadServerUnrecognizedVersionException.java
@@ -0,0 +1,14 @@
+package org.noreply.fancydress.status;
+
+/**
+ * The Server Descriptor had a Descriptor-Version that was not recognized by
+ * this implementation.
+ */
+public class Mix3BadServerUnrecognizedVersionException extends Mix3BadServerFormatException {
+ public Mix3BadServerUnrecognizedVersionException(String s) {
+ super(s);
+ }
+ public Mix3BadServerUnrecognizedVersionException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3BadSignatureException.java b/src/org/noreply/fancydress/status/Mix3BadSignatureException.java
new file mode 100644
index 0000000..5887c01
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3BadSignatureException.java
@@ -0,0 +1,13 @@
+package org.noreply.fancydress.status;
+
+/**
+ * Base class for other bad signature exceptions.
+ */
+public abstract class Mix3BadSignatureException extends Mix3BadFormatException {
+ public Mix3BadSignatureException(String s) {
+ super(s);
+ }
+ public Mix3BadSignatureException(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
diff --git a/src/org/noreply/fancydress/status/Mix3Exception.java b/src/org/noreply/fancydress/status/Mix3Exception.java
new file mode 100644
index 0000000..7ed439f
--- /dev/null
+++ b/src/org/noreply/fancydress/status/Mix3Exception.java
@@ -0,0 +1,13 @@
+package org.noreply.fancydress.status;
+
+/**
+ * Base class for other mix3 exceptions.
+ */
+public abstract class Mix3Exception extends Exception {
+ public Mix3Exception(String s) {
+ super(s);
+ }
+ public Mix3Exception(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
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;
+ }
+}
diff --git a/src/tests/org/noreply/fancydress/crypto/CryptoPrimitivesTest.java b/src/tests/org/noreply/fancydress/crypto/CryptoPrimitivesTest.java
new file mode 100644
index 0000000..4acc4db
--- /dev/null
+++ b/src/tests/org/noreply/fancydress/crypto/CryptoPrimitivesTest.java
@@ -0,0 +1,212 @@
+import java.io.*;
+import java.util.*;
+import junit.framework.*;
+import org.noreply.fancydress.crypto.CryptoPrimitives;
+import org.noreply.fancydress.misc.Util;
+
+public class CryptoPrimitivesTest extends TestCase {
+
+ public CryptoPrimitivesTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Test if xor returns an array of the same size as the input arrays.
+ */
+ public void testXorLengthMatchesInputs() {
+ byte[] a = new byte[30];
+ byte[] b = new byte[30];
+ byte[] r = CryptoPrimitives.xor(a,b);
+ assertEquals(30, r.length);
+ }
+
+ /**
+ * Test if rand returns n bytes
+ */
+ public void testRandLength() {
+ byte[] r;
+
+ r = CryptoPrimitives.rand(0);
+ assertEquals(0, r.length);
+ r = CryptoPrimitives.rand(30);
+ assertEquals(30, r.length);
+ r = CryptoPrimitives.rand(300);
+ assertEquals(300, r.length);
+ r = CryptoPrimitives.rand(30000);
+ assertEquals(30000, r.length);
+ }
+
+ /**
+ * Test if zero returns n bytes
+ */
+ public void testZeroLength() {
+ byte[] r;
+
+ r = CryptoPrimitives.zero(0);
+ assertEquals(0, r.length);
+ r = CryptoPrimitives.zero(30);
+ assertEquals(30, r.length);
+ r = CryptoPrimitives.zero(300);
+ assertEquals(300, r.length);
+ r = CryptoPrimitives.zero(30000);
+ assertEquals(30000, r.length);
+ }
+ /**
+ * Test if hash returns HASH_LEN bytes
+ */
+ public void testHashLength() {
+ byte[] r;
+ r = CryptoPrimitives.hash(new byte[0]);
+ assertEquals(CryptoPrimitives.HASH_LEN, r.length);
+
+ r = CryptoPrimitives.hash(new byte[2000]);
+ assertEquals(CryptoPrimitives.HASH_LEN, r.length);
+
+
+ r = CryptoPrimitives.hash(new byte[0], new byte[0]);
+ assertEquals(CryptoPrimitives.HASH_LEN, r.length);
+
+ r = CryptoPrimitives.hash(new byte[2000], new byte[2000]);
+ assertEquals(CryptoPrimitives.HASH_LEN, r.length);
+
+
+ r = CryptoPrimitives.hash(new byte[0], new byte[0], new byte[0]);
+ assertEquals(CryptoPrimitives.HASH_LEN, r.length);
+
+ r = CryptoPrimitives.hash(new byte[2000], new byte[2000], new byte[2000]);
+ assertEquals(CryptoPrimitives.HASH_LEN, r.length);
+ }
+
+ /**
+ * Test if hash is sane sha-1
+ */
+ public void testHash() {
+ String s1 = "";
+ byte[] b1 = Util.toOctets(s1);
+ byte[] h1 = Util.fromHex("da39a3ee5e6b4b0d3255bfef95601890afd80709");
+ String s2 = "I couldn't possibly fail to disagree with you less.";
+ byte[] b2 = Util.toOctets(s2);
+ byte[] h2 = Util.fromHex("90ecb7803dd428bd64ed296600d650a981268f1c");
+ String s3 = "Everyone writes on the walls except me. -Said to be graffiti seen in Pompeii";
+ byte[] b3 = Util.toOctets(s3);
+ byte[] h3 = Util.fromHex("fcdbe159e7c4c2d25f329a75c19c209fd27fc10d");
+ String s4 = "Freedom of the press is for those who happen to own one.";
+ byte[] b4 = Util.toOctets(s4);
+ byte[] h4 = Util.fromHex("12dbc37c1f71491a8fae55ae305ab888b852f70d");
+
+ /* Test hash with one arg. */
+ assertTrue(Util.equal(CryptoPrimitives.hash(b1) , h1));
+ assertTrue(Util.equal(CryptoPrimitives.hash(b2) , h2));
+ assertTrue(Util.equal(CryptoPrimitives.hash(b3) , h3));
+ assertTrue(Util.equal(CryptoPrimitives.hash(b4) , h4));
+
+ byte[] b;
+ /* Test hash with two args. */
+ b = Util.concat(b1,b2);
+ assertTrue(Util.equal( CryptoPrimitives.hash(b), CryptoPrimitives.hash(b1,b2)));
+
+ b = Util.concat(b2,b3);
+ assertTrue(Util.equal( CryptoPrimitives.hash(b), CryptoPrimitives.hash(b2,b3)));
+
+ b = Util.concat(b2,b3);
+ assertTrue(! Util.equal( CryptoPrimitives.hash(b), CryptoPrimitives.hash(b3,b2)));
+
+ /* Test hash with three args. */
+ b = Util.concat(b1,Util.concat(b2,b3));
+ assertTrue(Util.equal( CryptoPrimitives.hash(b), CryptoPrimitives.hash(b1,b2,b3)));
+
+ b = Util.concat(b4,Util.concat(b3,b2));
+ assertTrue(Util.equal( CryptoPrimitives.hash(b), CryptoPrimitives.hash(b4,b3,b2)));
+
+ b = Util.concat(b4,Util.concat(b3,b2));
+ assertTrue(! Util.equal( CryptoPrimitives.hash(b), CryptoPrimitives.hash(b2,b3,b4)));
+ }
+
+ /**
+ * Test the PRNG as defined in the mixminion spec.
+ */
+ public void testPRNG() {
+ byte[] key = Util.fromHex("02 13 24 35 46 57 68 79 8A 9B AC BD CE DF E0 F1");
+ byte[] keystream1 = Util.fromHex("CA F3 E8 F6 23 B9 87 20 D2 F7 A8 66 9C B6 DE 01 71" +
+ "31 CC 3E 74 20 80 99 62 2D 7D DF 98 59 D7 5B A6 77 78 FE 3C 22 C1 B5 AE 1F 8E" +
+ "79 78 72 3D 0F 51 B7 EA 19 F7 93 7F F6 DC 21 EC 2C 13 54 DD 98");
+ byte[] keystream2 = Util.fromHex("81 AE AE FB 58 E0 A2 FE 37 27 31 8E 5B C4 90 B9" +
+ "86 99 95 78 C0 F6 BC AC 9A A6 16 DF BA 0B 4E 6C 0A 10 C5 8F 7B 67 54 19 D7 EA" +
+ "8C 4A A7 0E C7 77 6B 25 51 68 88 1C 7C 4D EB 83 8C A0 3F 4A 85 32");
+
+ byte[] keystream0 = CryptoPrimitives.prng(key, 0x300);
+
+ assertTrue(Util.equal( Util.slice(keystream0, 0 , 0x40), keystream1));
+ assertTrue(Util.equal( Util.slice(keystream0, 0x2c0, 0x40), keystream2));
+ }
+
+ /**
+ * Test encrypt
+ */
+ public void testEncrypt() {
+ byte[] key = Util.fromHex("02 13 24 35 46 57 68 79 8A 9B AC BD CE DF E0 F1");
+ byte[] msg = Util.fromHex("48 65 6C 6C 6F 20 77 6F 72 6C 64 21");
+ byte[] ciphertext = Util.fromHex("82 96 84 9A 4C 99 F0 4F A0 9B CC 47");
+
+ assertTrue(Util.equal( CryptoPrimitives.encrypt(key, msg), ciphertext));
+ }
+
+ /**
+ * Test sprp_encrypt (LIONESS)
+ */
+ /* FIXME: test SPRPEncrypt with 3 args */
+ public void testSPRPEncrypt() {
+ byte[] key = Util.fromHex("AE BB 71 FA E1 F4 70 6C 0C 60 83 83 83 26 70 E3 60 63 37 EA");
+ byte[] msg = Util.fromHex("49 20 6E 65 76 65 72 20 62 65 6C 69 65 76 65 20 69 6E" +
+ "20 63 6F 64 65 20 75 6E 74 69 6C 20 69 74 27 73 20 72 75 6E 6E 69 6E 67 2C 20" +
+ "61 6E 64 20 49 20 6E 65 76 65 72 20 62 65 6C 69 65 76 65 20 69 6E 20 74 68 65" +
+ "20 6E 65 78 74 20 72 65 6C 65 61 73 65 20 75 6E 74 69 6C 20 69 74 27 73 20 6F" +
+ "75 74 2E");
+ byte[] ciphertext = Util.fromHex("1D 46 61 E1 CC 16 FA 17 5C B8 06 66 19 17 4C 09 44 B7" +
+ "BC BC 57 8B 2E EB 06 19 4C E1 F0 0F 67 1B 1B A2 76 E9 3E 77 BF 7C 00 3D A2 91" +
+ "3A 23 62 0A 4C DE 7A 52 E8 29 03 2A 93 B7 1F EC 5C A8 5C 84 7A 3F 45 A5 80 0A" +
+ "0B B6 B5 DF E1 25 B0 DE CC 10 4D 46 45 EF 11 F7 CF 44 01 66 9D EC 36 BF CE 46" +
+ "97 60 3D");
+
+ assertTrue(Util.equal( CryptoPrimitives.sprpEncrypt(key, msg), ciphertext));
+ }
+
+ /**
+ * Test sprp_decrypt (LIONESS)
+ */
+ /* FIXME: test SPRPDecrypt with 3 args */
+ public void testSPRPDecrypt() {
+ byte[] key = Util.fromHex("AE BB 71 FA E1 F4 70 6C 0C 60 83 83 83 26 70 E3 60 63 37 EA");
+ byte[] msg = Util.fromHex("49 20 6E 65 76 65 72 20 62 65 6C 69 65 76 65 20 69 6E" +
+ "20 63 6F 64 65 20 75 6E 74 69 6C 20 69 74 27 73 20 72 75 6E 6E 69 6E 67 2C 20" +
+ "61 6E 64 20 49 20 6E 65 76 65 72 20 62 65 6C 69 65 76 65 20 69 6E 20 74 68 65" +
+ "20 6E 65 78 74 20 72 65 6C 65 61 73 65 20 75 6E 74 69 6C 20 69 74 27 73 20 6F" +
+ "75 74 2E");
+ byte[] plaintext = Util.fromHex("F3 5E 91 70 2F 43 14 9F E0 A0 3B 18 8E A9 FC 17 0A 3A" +
+ "3F C0 EB A5 18 F6 03 4E E3 88 F7 B7 C3 2E 01 80 E1 D7 A1 86 B5 7D D8 38 B8 4D" +
+ "DD 3E 3D D2 D2 15 CA 31 71 DF F7 85 7C 1C 95 20 5A B6 19 22 16 54 F1 4E 09 8E" +
+ "BC 8E C1 02 F6 F5 CE EB 34 53 7F 52 A1 7B 3A 04 78 D8 4C 9C 18 34 FD 5D 63 DD" +
+ "F0 E4 FD");
+
+ assertTrue(Util.equal( CryptoPrimitives.sprpDecrypt(key, msg), plaintext));
+ }
+
+ public static Test suite() {
+ TestSuite suite= new TestSuite();
+ // suite.addTest(new CryptoPrimitivesTest("testXorLengthThrows"));
+ suite.addTest(new CryptoPrimitivesTest("testXorLengthMatchesInputs"));
+ suite.addTest(new CryptoPrimitivesTest("testRandLength"));
+ suite.addTest(new CryptoPrimitivesTest("testZeroLength"));
+ suite.addTest(new CryptoPrimitivesTest("testHashLength"));
+ suite.addTest(new CryptoPrimitivesTest("testHash"));
+ suite.addTest(new CryptoPrimitivesTest("testPRNG"));
+ suite.addTest(new CryptoPrimitivesTest("testEncrypt"));
+ suite.addTest(new CryptoPrimitivesTest("testSPRPEncrypt"));
+ suite.addTest(new CryptoPrimitivesTest("testSPRPDecrypt"));
+ return suite;
+ }
+
+ public static void main(String args[]) {
+ junit.textui.TestRunner.run(suite());
+ }
+}
diff --git a/src/tests/org/noreply/fancydress/crypto/RSAPublicKeyTest.java b/src/tests/org/noreply/fancydress/crypto/RSAPublicKeyTest.java
new file mode 100644
index 0000000..3a37de0
--- /dev/null
+++ b/src/tests/org/noreply/fancydress/crypto/RSAPublicKeyTest.java
@@ -0,0 +1,111 @@
+import java.io.*;
+import java.util.*;
+import junit.framework.*;
+import org.noreply.fancydress.crypto.CryptoPrimitives;
+import org.noreply.fancydress.crypto.RSAPublicKey;
+import org.noreply.fancydress.misc.Util;
+
+public class RSAPublicKeyTest extends TestCase {
+
+ public RSAPublicKeyTest(String name) {
+ super(name);
+ }
+
+ byte[] fpr, asn, modulus, pubE, privP, privQ, privD, padded, encrypted, seed;
+ /**
+ * Test RSAPublicKey
+ */
+ protected void setUp() {
+ fpr = Util.fromHex("4B1B3BA1FC035CAFAFBBFEF7E461DAF28AB9FAA8");
+ asn = Util.fromHex("30 82 01 0A 02 82 01 01 00 D1 A0 41 1E A0 32 7E 80 30 7B 2A" +
+ "B9 34 6A 33 1D EC D5 BA DD 76 94 61 DE 2A B7 E6 9D AC 7D 01 53 CC 7E E7 AA B2" +
+ "4A 9C 2A C8 CC 7B 74 73 36 10 EE 90 3D DE 6D 82 8F 2C A3 27 54 33 3E 9E 85 72" +
+ "1D 26 B0 9F 32 1C FB 5F B0 AB 52 C5 3F 94 C9 88 F9 0F 76 87 4A AB F4 8D 97 3D" +
+ "FA DE 49 25 B8 BC 21 A1 67 67 FF 5E A4 8E 5A 6B 9E F7 43 93 A6 71 B9 75 D3 B0" +
+ "01 AD 83 6D F9 A4 4B 45 B3 80 4B 38 B3 B1 8A 45 B6 A5 DB 76 4E 2A FB D6 9F 52" +
+ "63 4C C6 74 CF 26 E5 45 99 C3 8C F7 2A A6 05 7C F2 C8 A0 1B 0D FB 78 69 72 C8" +
+ "E3 3B 80 69 93 3A 4F DF 35 71 CB C5 4D DB E8 42 C6 36 DF A3 0D AC 3B A7 DA 0F" +
+ "6B BA AF C0 57 3C 1E D6 94 06 E6 C0 3F 19 A1 7E 9F F0 56 DA CB 4D 9A 8C 6D 26" +
+ "C6 09 44 F5 FD 18 42 BE BA 0C E3 44 40 FA 31 42 DF 02 D6 09 F7 28 02 10 D8 77" +
+ "70 D5 CC 69 41 FD E5 91 C7 81 CB 02 03 01 00 01");
+ pubE = Util.fromHex("01 00 01");
+ modulus = Util.fromHex("D1 A0 41 1E A0 32 7E 80 30 7B 2A B9 34 6A 33 1D EC D5 BA DD 76" +
+ "94 61 DE 2A B7 E6 9D AC 7D 01 53 CC 7E E7 AA B2 4A 9C 2A C8 CC 7B 74 73 36 10" +
+ "EE 90 3D DE 6D 82 8F 2C A3 27 54 33 3E 9E 85 72 1D 26 B0 9F 32 1C FB 5F B0 AB" +
+ "52 C5 3F 94 C9 88 F9 0F 76 87 4A AB F4 8D 97 3D FA DE 49 25 B8 BC 21 A1 67 67" +
+ "FF 5E A4 8E 5A 6B 9E F7 43 93 A6 71 B9 75 D3 B0 01 AD 83 6D F9 A4 4B 45 B3 80" +
+ "4B 38 B3 B1 8A 45 B6 A5 DB 76 4E 2A FB D6 9F 52 63 4C C6 74 CF 26 E5 45 99 C3" +
+ "8C F7 2A A6 05 7C F2 C8 A0 1B 0D FB 78 69 72 C8 E3 3B 80 69 93 3A 4F DF 35 71" +
+ "CB C5 4D DB E8 42 C6 36 DF A3 0D AC 3B A7 DA 0F 6B BA AF C0 57 3C 1E D6 94 06" +
+ "E6 C0 3F 19 A1 7E 9F F0 56 DA CB 4D 9A 8C 6D 26 C6 09 44 F5 FD 18 42 BE BA 0C" +
+ "E3 44 40 FA 31 42 DF 02 D6 09 F7 28 02 10 D8 77 70 D5 CC 69 41 FD E5 91 C7 81" +
+ "CB");
+ privP = Util.fromHex("F1 8C 45 B7 41 AB AA 5B 2D 2D 71 32 B4 0A 64 91 36 64 18" +
+ "37 F7 0F 54 EB CF 2B 36 57 6F AE 50 4C 6D 61 CA 05 AD 9C 48 18 B9 3C FB B0 4F" +
+ "1C 68 B1 26 32 C1 BC CA 8C 2C D6 81 30 C5 C7 FF D1 DF 4B 21 CD DD 01 D7 5A E9" +
+ "0F A8 D8 66 C2 69 C7 89 3D 34 8B 44 2C 36 C7 70 B0 62 44 C5 71 F4 1E CC 60 64" +
+ "22 83 90 B6 D8 93 39 B3 7A D7 D7 3E 86 8F E3 74 39 7D E5 52 CE 19 81 AD 9D DE" +
+ "A7 09 53 24 31");
+ privQ = Util.fromHex("DE 2B 0A B4 B8 88 4B 21 AE 6A 8E 1B 31 3F EC DE C4 1C 82" +
+ "A7 0B 02 BA 4D 88 F5 28 E4 44 A9 E6 A6 D7 7E 50 22 1F 6C 13 0B 56 4D 87 BC 14" +
+ "B6 7D 00 9B C4 DD 1B 52 67 28 55 1D C0 71 0B A6 24 4F F7 20 90 8F 66 C5 4B FF" +
+ "39 8E 4E 91 08 49 97 74 01 A9 63 30 7A 96 BA AD 62 11 4D 7B 92 F3 A6 12 C6 B4" +
+ "02 EC B9 53 AA E8 8E CB 41 B5 17 63 7D 35 F5 F9 1A C1 F8 85 BE 76 19 56 8B C2" +
+ "DC 32 82 B2 BB");
+ privD = Util.fromHex("44 D1 52 6F 86 71 ED 3B 92 2E ED 20 AE 07 6B 4E 98 B0 B5" +
+ "CE FC 9D CB DF 4E B0 DE E1 C6 7D A7 50 E6 62 87 15 6F C0 B4 B2 0B 07 AE 43 D5" +
+ "8A DB 56 26 3E 59 66 24 25 72 A7 01 43 50 2B 6F 89 29 A4 4E 4E 4F 84 F9 24 C1" +
+ "0E 53 C0 31 87 25 06 60 94 3B 32 53 49 FD 57 A4 A0 11 35 E9 81 A4 03 98 A7 85" +
+ "C0 57 D0 EB 36 24 91 A4 A1 24 55 A8 04 4D 73 70 2E 15 AB 07 56 8D 65 16 3D AB" +
+ "DD 38 F1 F8 E3 D4 DA 17 B2 F2 D5 63 A1 D3 69 58 87 9F C4 42 C6 EA 12 1D 3F F5" +
+ "2D 78 D2 B6 F4 7D B6 C2 C7 93 A9 65 0A DC DC 44 ED 5F A8 11 FB 81 1A 20 F2 18" +
+ "4B D6 0E 54 AD F5 09 9D 73 52 0B 70 B6 3F D6 D0 B7 2D 17 00 2A B4 99 AF 7C 03" +
+ "01 9F A3 3A CA DC 26 B8 D0 6B 65 A9 86 EC 36 C6 CA 1E 01 20 49 57 B5 DB 85 C3" +
+ "79 5C 1E 2B C6 F9 E8 9F CE 11 4B 16 17 98 70 75 99 C1 4D F9 89 3F C2 16 9A 25" +
+ "D2 D7 A1");
+ seed = Util.fromHex("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13");
+ padded = Util.fromHex("00 19 B0 7B B0 24 3D 14 F6 17 06 CE E6 81 26 83" +
+ "FA 7D D3 9B 38 92 0C 33 64 A9 34 8C 3D 87 9E 4D 61 CA F2 50 24 96 19 83 6B 99" +
+ "F3 72 63 A5 74 17 1D 60 78 80 91 7A EF CF 64 22 94 B0 8B 98 27 11 C0 FA 68 F6" +
+ "48 5F 62 C7 30 54 AB 50 A0 C7 56 69 5E 2A 53 BD 58 5A 81 94 85 27 EC F0 15 61" +
+ "4F 8B BE 3E 96 FE AA 86 89 4F D5 43 FB 37 CA 4C A3 58 CA 98 88 07 83 DD 07 A5" +
+ "6F DB E4 BE 7D D4 4F 45 C5 CB 11 F4 7F 60 53 64 5A 2D 45 B3 3A 0F B0 CB 4E EF" +
+ "E0 3A 23 20 8A BA 39 17 A4 5F 1C BE 4B 72 BA BB E8 6F 85 9F FC 5F C1 00 04 22" +
+ "FB E2 1F 3A AD BD 73 FE 45 D0 37 61 EE 30 CC 33 10 26 1C 71 70 F4 EB 0C F4 EA" +
+ "DE B4 4F CE 38 5C 8F F4 34 02 97 1D 43 0D C7 BD 31 18 2E EE 6D E1 CE 62 D8 D7" +
+ "61 0C D7 4A 5A 92 FA 88 AD 95 CB C8 40 F0 B8 5C 81 9E 70 CF 4D C6 DB 9D 1A 7A" +
+ "20 9C 71 FD 97 CB");
+ encrypted = Util.fromHex("A5 7A 58 F6 3A 15 1B 9F 30 C3 56 EA 5A F3 C8 4D 3E 25 36" +
+ "37 59 2D 1C A3 BF 1B A0 40 99 80 50 7E 06 48 73 41 A0 58 AF 40 8F DF 71 9C D9" +
+ "3F 3E CB 7D B6 7F F7 E8 46 81 16 10 CB B1 0A E0 5B 4A 68 68 98 1A EE 8A 68 FC" +
+ "63 B9 14 6D 2A 48 FD A3 28 1A 1C E2 19 2A 41 1B 04 C7 74 3F EA 43 50 C9 23 80" +
+ "0D 5F 0F EC 7B 74 AD 6B 4F E0 A6 95 6B BF 93 1A B1 7C 08 1F 0C 25 1D E1 5B 7F" +
+ "58 EC 60 C2 71 9D BB AF BD 7E BC DB 71 47 A8 B1 25 E6 1A 15 61 D0 59 09 75 A4" +
+ "69 71 4F 80 52 99 2D D9 59 F0 06 45 53 88 EA FA E0 32 D9 9C E3 C9 6C 36 BF 52" +
+ "6F 60 54 B5 B2 DA 55 1C F9 90 5D 14 DC EA 79 C3 CE 10 E5 E7 11 16 92 B0 1C C7" +
+ "9A 17 52 7E D3 6C 04 20 14 C8 9E 23 02 6B 23 27 A3 97 89 18 B1 6C 6F C5 11 DA" +
+ "D9 21 A5 1B 2C 13 80 01 1E DF 37 87 FC 37 C1 36 0A 2E AA 9C 85 75 1A D2 B5 2D" +
+ "27 1A A6");
+ }
+
+ public void testRSAPublicKey() {
+ RSAPublicKey key = new RSAPublicKey(asn);
+
+ assertTrue(CryptoPrimitives.equal(key.encode(), asn));
+ assertTrue(CryptoPrimitives.equal(key.getFingerprint(), fpr));
+
+ /* FIXME
+ * we cannot check if encryptiong works because BC does not allow to set the MGF seed
+ */
+ };
+
+ public static Test suite() {
+ TestSuite suite= new TestSuite();
+ suite.addTest(new RSAPublicKeyTest("testRSAPublicKey"));
+ return suite;
+ }
+
+ public static void main(String args[]) {
+ junit.textui.TestRunner.run(suite());
+ }
+}
diff --git a/src/tests/org/noreply/fancydress/crypto/TestVectors b/src/tests/org/noreply/fancydress/crypto/TestVectors
new file mode 100644
index 0000000..78ae9cd
--- /dev/null
+++ b/src/tests/org/noreply/fancydress/crypto/TestVectors
@@ -0,0 +1,134 @@
+Here's my first pass at test vectors. Once we both generate these, I'll
+feel better about my crypto, and publish them for real.
+
+======================================== RSA Example
+2048-bit Key K
+
+ exponent = [01 00 01]
+
+ modulus = [D1 A0 41 1E A0 32 7E 80 30 7B 2A B9 34 6A 33 1D EC D5 BA DD 76
+94 61 DE 2A B7 E6 9D AC 7D 01 53 CC 7E E7 AA B2 4A 9C 2A C8 CC 7B 74 73 36 10
+EE 90 3D DE 6D 82 8F 2C A3 27 54 33 3E 9E 85 72 1D 26 B0 9F 32 1C FB 5F B0 AB
+52 C5 3F 94 C9 88 F9 0F 76 87 4A AB F4 8D 97 3D FA DE 49 25 B8 BC 21 A1 67 67
+FF 5E A4 8E 5A 6B 9E F7 43 93 A6 71 B9 75 D3 B0 01 AD 83 6D F9 A4 4B 45 B3 80
+4B 38 B3 B1 8A 45 B6 A5 DB 76 4E 2A FB D6 9F 52 63 4C C6 74 CF 26 E5 45 99 C3
+8C F7 2A A6 05 7C F2 C8 A0 1B 0D FB 78 69 72 C8 E3 3B 80 69 93 3A 4F DF 35 71
+CB C5 4D DB E8 42 C6 36 DF A3 0D AC 3B A7 DA 0F 6B BA AF C0 57 3C 1E D6 94 06
+E6 C0 3F 19 A1 7E 9F F0 56 DA CB 4D 9A 8C 6D 26 C6 09 44 F5 FD 18 42 BE BA 0C
+E3 44 40 FA 31 42 DF 02 D6 09 F7 28 02 10 D8 77 70 D5 CC 69 41 FD E5 91 C7 81
+CB]
+
+ Private key (P)= [F1 8C 45 B7 41 AB AA 5B 2D 2D 71 32 B4 0A 64 91 36 64 18
+37 F7 0F 54 EB CF 2B 36 57 6F AE 50 4C 6D 61 CA 05 AD 9C 48 18 B9 3C FB B0 4F
+1C 68 B1 26 32 C1 BC CA 8C 2C D6 81 30 C5 C7 FF D1 DF 4B 21 CD DD 01 D7 5A E9
+0F A8 D8 66 C2 69 C7 89 3D 34 8B 44 2C 36 C7 70 B0 62 44 C5 71 F4 1E CC 60 64
+22 83 90 B6 D8 93 39 B3 7A D7 D7 3E 86 8F E3 74 39 7D E5 52 CE 19 81 AD 9D DE
+A7 09 53 24 31]
+
+ Private key (Q)= [DE 2B 0A B4 B8 88 4B 21 AE 6A 8E 1B 31 3F EC DE C4 1C 82
+A7 0B 02 BA 4D 88 F5 28 E4 44 A9 E6 A6 D7 7E 50 22 1F 6C 13 0B 56 4D 87 BC 14
+B6 7D 00 9B C4 DD 1B 52 67 28 55 1D C0 71 0B A6 24 4F F7 20 90 8F 66 C5 4B FF
+39 8E 4E 91 08 49 97 74 01 A9 63 30 7A 96 BA AD 62 11 4D 7B 92 F3 A6 12 C6 B4
+02 EC B9 53 AA E8 8E CB 41 B5 17 63 7D 35 F5 F9 1A C1 F8 85 BE 76 19 56 8B C2
+DC 32 82 B2 BB]
+
+ Private key (D)= [44 D1 52 6F 86 71 ED 3B 92 2E ED 20 AE 07 6B 4E 98 B0 B5
+CE FC 9D CB DF 4E B0 DE E1 C6 7D A7 50 E6 62 87 15 6F C0 B4 B2 0B 07 AE 43 D5
+8A DB 56 26 3E 59 66 24 25 72 A7 01 43 50 2B 6F 89 29 A4 4E 4E 4F 84 F9 24 C1
+0E 53 C0 31 87 25 06 60 94 3B 32 53 49 FD 57 A4 A0 11 35 E9 81 A4 03 98 A7 85
+C0 57 D0 EB 36 24 91 A4 A1 24 55 A8 04 4D 73 70 2E 15 AB 07 56 8D 65 16 3D AB
+DD 38 F1 F8 E3 D4 DA 17 B2 F2 D5 63 A1 D3 69 58 87 9F C4 42 C6 EA 12 1D 3F F5
+2D 78 D2 B6 F4 7D B6 C2 C7 93 A9 65 0A DC DC 44 ED 5F A8 11 FB 81 1A 20 F2 18
+4B D6 0E 54 AD F5 09 9D 73 52 0B 70 B6 3F D6 D0 B7 2D 17 00 2A B4 99 AF 7C 03
+01 9F A3 3A CA DC 26 B8 D0 6B 65 A9 86 EC 36 C6 CA 1E 01 20 49 57 B5 DB 85 C3
+79 5C 1E 2B C6 F9 E8 9F CE 11 4B 16 17 98 70 75 99 C1 4D F9 89 3F C2 16 9A 25
+D2 D7 A1]
+
+ PK_Encode(K) = [30 82 01 0A 02 82 01 01 00 D1 A0 41 1E A0 32 7E 80 30 7B 2A
+B9 34 6A 33 1D EC D5 BA DD 76 94 61 DE 2A B7 E6 9D AC 7D 01 53 CC 7E E7 AA B2
+4A 9C 2A C8 CC 7B 74 73 36 10 EE 90 3D DE 6D 82 8F 2C A3 27 54 33 3E 9E 85 72
+1D 26 B0 9F 32 1C FB 5F B0 AB 52 C5 3F 94 C9 88 F9 0F 76 87 4A AB F4 8D 97 3D
+FA DE 49 25 B8 BC 21 A1 67 67 FF 5E A4 8E 5A 6B 9E F7 43 93 A6 71 B9 75 D3 B0
+01 AD 83 6D F9 A4 4B 45 B3 80 4B 38 B3 B1 8A 45 B6 A5 DB 76 4E 2A FB D6 9F 52
+63 4C C6 74 CF 26 E5 45 99 C3 8C F7 2A A6 05 7C F2 C8 A0 1B 0D FB 78 69 72 C8
+E3 3B 80 69 93 3A 4F DF 35 71 CB C5 4D DB E8 42 C6 36 DF A3 0D AC 3B A7 DA 0F
+6B BA AF C0 57 3C 1E D6 94 06 E6 C0 3F 19 A1 7E 9F F0 56 DA CB 4D 9A 8C 6D 26
+C6 09 44 F5 FD 18 42 BE BA 0C E3 44 40 FA 31 42 DF 02 D6 09 F7 28 02 10 D8 77
+70 D5 CC 69 41 FD E5 91 C7 81 CB 02 03 01 00 01]
+
+ Fingerprint = 4B1B3BA1FC035CAFAFBBFEF7E461DAF28AB9FAA8
+
+OAEP Padding/PKCS encoding example: (Using MGF SEED [00 01 02 03 04 05 06 07
+08 09 0A 0B 0C 0D 0E 0F 10 11 12 13])
+
+ original string M: [48 65 6C 6C 6F 20 77 6F 72 6C 64]
+
+ Padded string (2048 bits): [00 19 B0 7B B0 24 3D 14 F6 17 06 CE E6 81 26 83
+FA 7D D3 9B 38 92 0C 33 64 A9 34 8C 3D 87 9E 4D 61 CA F2 50 24 96 19 83 6B 99
+F3 72 63 A5 74 17 1D 60 78 80 91 7A EF CF 64 22 94 B0 8B 98 27 11 C0 FA 68 F6
+48 5F 62 C7 30 54 AB 50 A0 C7 56 69 5E 2A 53 BD 58 5A 81 94 85 27 EC F0 15 61
+4F 8B BE 3E 96 FE AA 86 89 4F D5 43 FB 37 CA 4C A3 58 CA 98 88 07 83 DD 07 A5
+6F DB E4 BE 7D D4 4F 45 C5 CB 11 F4 7F 60 53 64 5A 2D 45 B3 3A 0F B0 CB 4E EF
+E0 3A 23 20 8A BA 39 17 A4 5F 1C BE 4B 72 BA BB E8 6F 85 9F FC 5F C1 00 04 22
+FB E2 1F 3A AD BD 73 FE 45 D0 37 61 EE 30 CC 33 10 26 1C 71 70 F4 EB 0C F4 EA
+DE B4 4F CE 38 5C 8F F4 34 02 97 1D 43 0D C7 BD 31 18 2E EE 6D E1 CE 62 D8 D7
+61 0C D7 4A 5A 92 FA 88 AD 95 CB C8 40 F0 B8 5C 81 9E 70 CF 4D C6 DB 9D 1A 7A
+20 9C 71 FD 97 CB]
+
+ PK_Encrypt(K,M): [A5 7A 58 F6 3A 15 1B 9F 30 C3 56 EA 5A F3 C8 4D 3E 25 36
+37 59 2D 1C A3 BF 1B A0 40 99 80 50 7E 06 48 73 41 A0 58 AF 40 8F DF 71 9C D9
+3F 3E CB 7D B6 7F F7 E8 46 81 16 10 CB B1 0A E0 5B 4A 68 68 98 1A EE 8A 68 FC
+63 B9 14 6D 2A 48 FD A3 28 1A 1C E2 19 2A 41 1B 04 C7 74 3F EA 43 50 C9 23 80
+0D 5F 0F EC 7B 74 AD 6B 4F E0 A6 95 6B BF 93 1A B1 7C 08 1F 0C 25 1D E1 5B 7F
+58 EC 60 C2 71 9D BB AF BD 7E BC DB 71 47 A8 B1 25 E6 1A 15 61 D0 59 09 75 A4
+69 71 4F 80 52 99 2D D9 59 F0 06 45 53 88 EA FA E0 32 D9 9C E3 C9 6C 36 BF 52
+6F 60 54 B5 B2 DA 55 1C F9 90 5D 14 DC EA 79 C3 CE 10 E5 E7 11 16 92 B0 1C C7
+9A 17 52 7E D3 6C 04 20 14 C8 9E 23 02 6B 23 27 A3 97 89 18 B1 6C 6F C5 11 DA
+D9 21 A5 1B 2C 13 80 01 1E DF 37 87 FC 37 C1 36 0A 2E AA 9C 85 75 1A D2 B5 2D
+27 1A A6]
+
+======================================== AES Single block encryption
+ Key: [00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF]
+ Plaintext block: [4D 69 78 6D 69 6E 69 6F 6E 54 79 70 65 49 49 49]
+ Encrypted block: [11 86 E8 6C 9D 55 16 33 6E 13 E1 71 69 A4 86 94]
+ Decrypted block: [7F 49 AA 5E 62 4A 12 B6 99 47 D7 52 47 11 27 6E]
+
+Counter mode encryption:
+ Key: [02 13 24 35 46 57 68 79 8A 9B AC BD CE DF E0 F1]
+
+ Keystream[0x0...0x3F]: [CA F3 E8 F6 23 B9 87 20 D2 F7 A8 66 9C B6 DE 01 71
+31 CC 3E 74 20 80 99 62 2D 7D DF 98 59 D7 5B A6 77 78 FE 3C 22 C1 B5 AE 1F 8E
+79 78 72 3D 0F 51 B7 EA 19 F7 93 7F F6 DC 21 EC 2C 13 54 DD 98]
+
+ Keystream[0x2C0...0x2FF]: [81 AE AE FB 58 E0 A2 FE 37 27 31 8E 5B C4 90 B9
+86 99 95 78 C0 F6 BC AC 9A A6 16 DF BA 0B 4E 6C 0A 10 C5 8F 7B 67 54 19 D7 EA
+8C 4A A7 0E C7 77 6B 25 51 68 88 1C 7C 4D EB 83 8C A0 3F 4A 85 32]
+
+ Example text M: [48 65 6C 6C 6F 20 77 6F 72 6C 64 21]
+
+ Encrypt(K,M): [82 96 84 9A 4C 99 F0 4F A0 9B CC 47]
+
+======================================== LIONESS SPRP_Encrypt:
+ Base key K: [AE BB 71 FA E1 F4 70 6C 0C 60 83 83 83 26 70 E3 60 63 37 EA]
+ K2: [AE BB 71 FA E1 F4 70 6C 0C 60 83 83 83 26 70 E3 60 63 37 EB]
+ K3: [AE BB 71 FA E1 F4 70 6C 0C 60 83 83 83 26 70 E3 60 63 37 E8]
+ K4: [AE BB 71 FA E1 F4 70 6C 0C 60 83 83 83 26 70 E3 60 63 37 E9]
+
+ Example text M: [49 20 6E 65 76 65 72 20 62 65 6C 69 65 76 65 20 69 6E
+20 63 6F 64 65 20 75 6E 74 69 6C 20 69 74 27 73 20 72 75 6E 6E 69 6E 67 2C 20
+61 6E 64 20 49 20 6E 65 76 65 72 20 62 65 6C 69 65 76 65 20 69 6E 20 74 68 65
+20 6E 65 78 74 20 72 65 6C 65 61 73 65 20 75 6E 74 69 6C 20 69 74 27 73 20 6F
+75 74 2E]
+
+ SPRP_Encrypt(K,M): [1D 46 61 E1 CC 16 FA 17 5C B8 06 66 19 17 4C 09 44 B7
+BC BC 57 8B 2E EB 06 19 4C E1 F0 0F 67 1B 1B A2 76 E9 3E 77 BF 7C 00 3D A2 91
+3A 23 62 0A 4C DE 7A 52 E8 29 03 2A 93 B7 1F EC 5C A8 5C 84 7A 3F 45 A5 80 0A
+0B B6 B5 DF E1 25 B0 DE CC 10 4D 46 45 EF 11 F7 CF 44 01 66 9D EC 36 BF CE 46
+97 60 3D]
+
+ SPRP_Decrypt(K,M): [F3 5E 91 70 2F 43 14 9F E0 A0 3B 18 8E A9 FC 17 0A 3A
+3F C0 EB A5 18 F6 03 4E E3 88 F7 B7 C3 2E 01 80 E1 D7 A1 86 B5 7D D8 38 B8 4D
+DD 3E 3D D2 D2 15 CA 31 71 DF F7 85 7C 1C 95 20 5A B6 19 22 16 54 F1 4E 09 8E
+BC 8E C1 02 F6 F5 CE EB 34 53 7F 52 A1 7B 3A 04 78 D8 4C 9C 18 34 FD 5D 63 DD
+F0 E4 FD]
+
diff --git a/src/tests/org/noreply/fancydress/directory/ServerDescriptorTest.java b/src/tests/org/noreply/fancydress/directory/ServerDescriptorTest.java
new file mode 100644
index 0000000..adbe46e
--- /dev/null
+++ b/src/tests/org/noreply/fancydress/directory/ServerDescriptorTest.java
@@ -0,0 +1,44 @@
+import java.io.*;
+import java.util.*;
+import junit.framework.*;
+import org.noreply.fancydress.misc.Util;
+import org.noreply.fancydress.directory.ServerDescriptor;
+
+public class ServerDescriptorTest extends TestCase {
+
+ public ServerDescriptorTest(String name) {
+ super(name);
+ }
+
+ private boolean equals(String[] s1, String[] s2) {
+ boolean equals = s1.length == s2.length;
+ if (equals)
+ for (int i=0; i<s1.length; i++) {
+ equals = equals && s1[i].equals(s2[i]);
+ }
+ return equals;
+ }
+
+ public void testParsePacketVersions() {
+ String versions = "0.1 , 0.3, 0.6";
+ String[] expected = {"0.1", "0.3", "0.6"};
+ String[] parsed = ServerDescriptor.parsePacketVersions(versions, false);
+ String[] expected2 = {"0.3"};
+ String[] parsed2 = ServerDescriptor.parsePacketVersions(versions);
+ String[] parsed2_1 = ServerDescriptor.parsePacketVersions("0.3");
+
+ assertTrue(equals(expected, parsed));
+ assertTrue(equals(expected2, parsed2));
+ assertTrue(equals(expected2, parsed2_1));
+ };
+
+ public static Test suite() {
+ TestSuite suite= new TestSuite();
+ suite.addTest(new ServerDescriptorTest("testParsePacketVersions"));
+ return suite;
+ }
+
+ public static void main(String args[]) {
+ junit.textui.TestRunner.run(suite());
+ }
+}
diff --git a/src/tests/org/noreply/fancydress/misc/UtilTest.java b/src/tests/org/noreply/fancydress/misc/UtilTest.java
new file mode 100644
index 0000000..d967b99
--- /dev/null
+++ b/src/tests/org/noreply/fancydress/misc/UtilTest.java
@@ -0,0 +1,202 @@
+import java.io.*;
+import java.util.*;
+import junit.framework.*;
+import org.noreply.fancydress.misc.Util;
+
+public class UtilTest extends TestCase {
+
+ public UtilTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Test if equal returns an array of the same size as the input arrays.
+ */
+ public void testEqual() {
+ byte[] a0 = new byte[30];
+ byte[] a1 = { };
+ byte[] a2 = { (byte)0x00, (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04 };
+ byte[] a3 = { (byte)0x10, (byte)0x20, (byte)0xf0, (byte)0xF0, (byte)0xab };
+ assertTrue(Util.equal(a0,a0));
+ assertTrue(Util.equal(a1,a1));
+ assertTrue(Util.equal(a2,a2));
+ assertTrue(Util.equal(a3,a3));
+
+ assertTrue(!Util.equal(a0,a1));
+ assertTrue(!Util.equal(a0,a2));
+ assertTrue(!Util.equal(a0,a3));
+ assertTrue(!Util.equal(a1,a0));
+ assertTrue(!Util.equal(a1,a2));
+ assertTrue(!Util.equal(a1,a3));
+ assertTrue(!Util.equal(a2,a0));
+ assertTrue(!Util.equal(a2,a1));
+ assertTrue(!Util.equal(a2,a3));
+ assertTrue(!Util.equal(a3,a0));
+ assertTrue(!Util.equal(a3,a1));
+ assertTrue(!Util.equal(a3,a2));
+ }
+
+ /**
+ * Test if concat returns n+m bytes
+ */
+ public void testConcatLength() {
+ byte[] a;
+ byte[] b;
+ byte[] r;
+
+ a = new byte[0];
+ b = new byte[30];
+ r = Util.concat(a,b);
+ assertEquals(a.length + b.length, r.length);
+ a = new byte[30];
+ b = new byte[0];
+ r = Util.concat(a,b);
+ assertEquals(a.length + b.length, r.length);
+ a = new byte[30];
+ b = new byte[30];
+ r = Util.concat(a,b);
+ assertEquals(a.length + b.length, r.length);
+ }
+
+ /**
+ * Test that slice returns the correct length
+ */
+ public void testSliceLength() {
+ byte[] b = new byte[300];
+ byte[] r;
+
+ r = Util.slice(b, 0, 0);
+ assertEquals(0, r.length);
+ r = Util.slice(b, 30, 0);
+ assertEquals(0, r.length);
+ r = Util.slice(b, 0, 30);
+ assertEquals(30, r.length);
+ r = Util.slice(b, 30, 30);
+ assertEquals(30, r.length);
+ }
+
+ /**
+ * Test that slice returns the correct length
+ */
+ public void testSlice() {
+ byte[] b0 = Util.fromHex("00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF");
+ byte[] b1 = Util.fromHex("00 11 22 33 44 55 66 77 ");
+ byte[] b2 = Util.fromHex(" 55 66 77 88 99 ");
+ byte[] b3 = Util.fromHex(" DD EE FF");
+ byte[] b4 = Util.fromHex(" ");
+
+ assertTrue(Util.equal(Util.slice(b0, 0, b0.length), b0));
+ assertTrue(Util.equal(Util.slice(b0, 0, 8), b1));
+ assertTrue(Util.equal(Util.slice(b0, 5, 5), b2));
+ assertTrue(Util.equal(Util.slice(b0, 13, 3), b3));
+
+ assertTrue(Util.equal(Util.slice(b0, 0, 0), b4));
+ assertTrue(Util.equal(Util.slice(b0, 5, 0), b4));
+ assertTrue(Util.equal(Util.slice(b0, 15, 0), b4));
+ }
+
+ /**
+ * Test if fromHex returns the right amount if bytes.
+ */
+ public void testFromHexLength() {
+ String s;
+ byte[] r;
+
+ s = "";
+ r = Util.fromHex(s);
+ assertEquals(0, r.length);
+
+ s = " ";
+ r = Util.fromHex(s);
+ assertEquals(0, r.length);
+
+ s = "00 01 02 03 04";
+ r = Util.fromHex(s);
+ assertEquals(5, r.length);
+
+ s = "00 01 02 0 3 0 4";
+ r = Util.fromHex(s);
+ assertEquals(5, r.length);
+ }
+
+ /**
+ * Test if fromHex is sane.
+ */
+ public void testFromHexSaneness() {
+ String s1 = "";
+ String s2 = "00 01 02 03 04";
+ String s2_2 = "00 01 02 0 3 04";
+ String s3 = "10 20 f0 F0 Ab";
+ byte[] a1 = { };
+ byte[] a2 = { (byte)0x00, (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04 };
+ byte[] a3 = { (byte)0x10, (byte)0x20, (byte)0xf0, (byte)0xF0, (byte)0xab };
+ byte[] r;
+
+ r = Util.fromHex(s1);
+ assertTrue(Util.equal(a1, r));
+
+ r = Util.fromHex(s2);
+ assertTrue(Util.equal(a2, r));
+
+ r = Util.fromHex(s2_2);
+ assertTrue(Util.equal(a2, r));
+
+ r = Util.fromHex(s3);
+ assertTrue(Util.equal(a3, r));
+ }
+
+ /**
+ * Test if fromHex(asHex()) is sane.
+ */
+ public void testAsHexFromHex() {
+ String s = "CA F3 E8 F6 23 B9 87 20 D2 F7 A8 66 9C B6 DE 01 71" +
+ "31 CC 3E 74 20 80 99 62 2D 7D DF 98 59 D7 5B A6 77 78 FE 3C 22 C1 B5 AE 1F 8E" +
+ "79 78 72 3D 0F 51 B7 EA 19 F7 93 7F F6 DC 21 EC 2C 13 54 DD 98";
+ byte[] r, r1;
+ String s1;
+
+ r = Util.fromHex(s);
+ s1 = Util.asHex(r);
+ r1 = Util.fromHex(s1);
+
+ assertTrue(Util.equal(r, r1));
+ }
+
+ /**
+ * Test if toOctets() works.
+ */
+ public void testToOctets() {
+ String s0 = "Don't you wish you had more energy... or less ambition?";
+ byte[] o0 = Util.fromHex("44 6f 6e 27 74 20 79 6f 75 20 77 69 73 68 20 79" +
+ "6f 75 20 68 61 64 20 6d 6f 72 65 20 65 6e 65 72" +
+ "67 79 2e 2e 2e 20 6f 72 20 6c 65 73 73 20 61 6d" +
+ "62 69 74 69 6f 6e 3f" );
+ String s1 = "";
+ byte[] o1 = {};
+ byte[] r;
+
+ r = Util.toOctets(s0);
+ assertTrue(Util.equal(r, o0));
+
+ r = Util.toOctets(s1);
+ assertTrue(Util.equal(r, o1));
+ }
+
+
+ public static Test suite() {
+ TestSuite suite= new TestSuite();
+ suite.addTest(new UtilTest("testEqual"));
+ suite.addTest(new UtilTest("testConcatLength"));
+ suite.addTest(new UtilTest("testSlice"));
+ suite.addTest(new UtilTest("testSliceLength"));
+ suite.addTest(new UtilTest("testFromHexLength"));
+ suite.addTest(new UtilTest("testFromHexSaneness"));
+ suite.addTest(new UtilTest("testAsHexFromHex"));
+ suite.addTest(new UtilTest("testToOctets"));
+ return suite;
+ }
+
+ public static void main(String args[]) {
+ junit.textui.TestRunner.run(suite());
+ }
+}