summaryrefslogtreecommitdiff
path: root/src/org/noreply/fancydress/directory/ServerDescriptor.java
blob: 020d31b11fc90b8d76427e17b0223ce6115e347c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/* $Id$ */
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 (!v.equals("") && (Packet.isPacketVersionSupported(v) || !onlySupported ))
				versions.add( v );
			indexFrom = indexOf + 1;
		}
		String v = s.substring(indexFrom).trim();
		if (!v.equals("") && (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.parseDateTime(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;
	}

	/**
	 * Get the date after which this server descriptor is valid.
	 *
	 * @return date after which this server descriptor is valid
	 */
	public Date getValidAfter() {
		return validAfter;
	}

	/**
	 * Get the date until which this server descriptor is valid.
	 *
	 * @return date until which this server descriptor is valid
	 */
	public Date getValidUntil() {
		return validUntil;
	}

	/**
	 * Get the date when this server descriptor was published.
	 *
	 * @return date when this server descriptor was published
	 */
	public Date getPublished() {
		return published;
	}

	/**
	 * Get the Incoming/MMTP section.
	 *
	 * @return this server descriptor's Incoming/MMTP section
	 */
	public IncomingMMTPSection getIncomingMMTPSection() {
		return incomingMMTPSection;
	}

}