summaryrefslogtreecommitdiff
path: root/src/org/noreply/fancydress/directory/ServerDescriptor.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/noreply/fancydress/directory/ServerDescriptor.java')
-rw-r--r--src/org/noreply/fancydress/directory/ServerDescriptor.java319
1 files changed, 319 insertions, 0 deletions
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;
+ }
+}