source: trunk/src/org/expeditee/auth/mail/Mail.java@ 1389

Last change on this file since 1389 was 1389, checked in by bln4, 5 years ago

Moved things out of the old NGIKM package and deleted it.

File size: 17.0 KB
Line 
1package org.expeditee.auth.mail;
2
3import java.io.File;
4import java.io.FileNotFoundException;
5import java.io.IOException;
6import java.nio.file.Path;
7import java.nio.file.Paths;
8import java.security.InvalidKeyException;
9import java.security.KeyStoreException;
10import java.security.NoSuchAlgorithmException;
11import java.security.PrivateKey;
12import java.security.PublicKey;
13import java.security.cert.CertificateException;
14import java.security.spec.InvalidKeySpecException;
15import java.sql.Connection;
16import java.sql.DriverManager;
17import java.sql.PreparedStatement;
18import java.sql.SQLException;
19import java.sql.Statement;
20import java.text.ParseException;
21import java.text.SimpleDateFormat;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Base64;
25import java.util.Date;
26import java.util.HashMap;
27import java.util.List;
28import java.util.Map;
29import java.util.Scanner;
30
31import javax.crypto.BadPaddingException;
32import javax.crypto.Cipher;
33import javax.crypto.IllegalBlockSizeException;
34import javax.crypto.NoSuchPaddingException;
35import javax.crypto.SecretKey;
36import javax.crypto.spec.SecretKeySpec;
37
38import org.expeditee.auth.AuthenticatorBrowser;
39import org.expeditee.auth.mail.gui.MailBay;
40import org.expeditee.encryption.Actions;
41import org.expeditee.encryption.CryptographyConstants;
42import org.expeditee.gui.FrameIO;
43import org.expeditee.settings.UserSettings;
44
45public class Mail implements CryptographyConstants {
46
47 private static List<MailEntry> messages = new ArrayList<MailEntry>();
48
49 /**
50 * Add a piece of mail, used during initialisation.
51 */
52 public static void addEntry(MailEntry mail) {
53 messages.add(mail);
54 }
55
56 public static void clear() {
57 messages.clear();
58 }
59
60 public static void sendOneOffMail(MailEntry mail, String colleagueName, byte[] key) {
61 // Ensure dead drop area is set up.
62 Path databaseFileDirPath = ensureDeadDrops(colleagueName, mail.sender);
63
64 // Ensure the database file exists.
65 Path databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath);
66
67 // Create secret key.
68 SecretKey secretKey = new SecretKeySpec(key, SymmetricAlgorithm);
69
70 // Send message
71 sendMail(mail, secretKey, databaseFilePath);
72 }
73
74 public static void sendMail(MailEntry mail, String colleagueName) {
75 // Ensure dead drop area is set up.
76 Path databaseFileDirPath = ensureDeadDrops(colleagueName);
77
78 // Ensure the database file exists.
79 Path databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath);
80
81 // Obtain public key
82 PublicKey publicKey = null;
83 try {
84 publicKey = AuthenticatorBrowser.getInstance().getPublicKey(colleagueName);
85 } catch (InvalidKeySpecException | NoSuchAlgorithmException | KeyStoreException | CertificateException
86 | ClassNotFoundException | IOException | SQLException e) {
87 System.err.println("Error while sending message. Unable to obtain public key for colleague " +
88 colleagueName + ". Exception message: " + e.getMessage());
89 return;
90 }
91
92 // Check we got public key
93 if (publicKey == null) {
94 System.err.println("Error while sending message. Unable to obtain public key for colleague. Have you exchanged contact details?");
95 return;
96 }
97
98 // Send message
99 sendMail(mail, publicKey, databaseFilePath);
100 }
101
102 private static Path ensureDatabaseFile(String colleagueName, Path databaseFileDirPath) {
103 Path databaseFilePath = databaseFileDirPath.resolve(colleagueName + ".db");
104 File databaseFile = databaseFilePath.toFile();
105 if (!databaseFile.exists()) {
106 databaseFileDirPath.toFile().mkdirs();
107 String sql =
108 "CREATE TABLE EXPMAIL (" +
109 "TIME TEXT NOT NULL, " +
110 "SND TEXT NOT NULL, " +
111 "REC TEXT NOT NULL, " +
112 "MSG TEXT NOT NULL, " +
113 "MSG2 TEXT NOT NULL, " +
114 "OPTS ARRAY NOT NULL, " +
115 "OPTSVAL ARRAY NOT NULL)";
116 try {
117 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
118 Statement createTable = c.createStatement();
119 createTable.executeUpdate(sql);
120 createTable.close();
121 c.close();
122 } catch (SQLException e) {
123 System.err.println("Error while creating database file.");
124 e.printStackTrace();
125 }
126 }
127 return databaseFilePath;
128 }
129
130 private static Path ensureDeadDrops(String colleagueName) {
131 String me = UserSettings.UserName.get().toLowerCase();
132 String them = colleagueName.toLowerCase();
133 Path databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(me + "+" + them);
134 if (!databaseFileDirPath.toFile().exists()) {
135 databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(them + "+" + me);
136 }
137 return databaseFileDirPath;
138 }
139
140 private static Path ensureDeadDrops(String colleagueName, String sender) {
141 String me = sender.toLowerCase();
142 String them = colleagueName.toLowerCase();
143 Path parent = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + sender).resolve("deaddrops");
144 Path databaseFileDirPath = parent.resolve(me + "+" + them);
145 if (!databaseFileDirPath.toFile().exists()) {
146 databaseFileDirPath = parent.resolve(them + "+" + me);
147 }
148 return databaseFileDirPath;
149 }
150
151 private static void sendMail(MailEntry mail, PublicKey key, Path databaseFile) {
152 try {
153 Cipher cipher = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters);
154 cipher.init(Cipher.ENCRYPT_MODE, key);
155 String sender = Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes()));
156 cipher.init(Cipher.ENCRYPT_MODE, key);
157 String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes()));
158 cipher.init(Cipher.ENCRYPT_MODE, key);
159 String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes()));
160 cipher.init(Cipher.ENCRYPT_MODE, key);
161 String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes()));
162
163 Map<String, String> options = new HashMap<String, String>();
164 for (String label: mail.options.keySet()) {
165 cipher.init(Cipher.ENCRYPT_MODE, key);
166 String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes()));
167 cipher.init(Cipher.ENCRYPT_MODE, key);
168 String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes()));
169 options.put(labelEncrypted, actionNameEncrypted);
170 }
171
172 // write to mail database
173 writeToMailDatabase(mail, databaseFile, sender, rec, message, message2, options);
174 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) {
175 e.printStackTrace();
176 }
177 }
178
179 private static void sendMail(MailEntry mail, SecretKey key, Path databaseFile) {
180 try {
181 // Encrypt message.
182 Cipher cipher = Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters);
183 cipher.init(Cipher.ENCRYPT_MODE, key);
184 String sender = "=" + Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes()));
185 cipher.init(Cipher.ENCRYPT_MODE, key);
186 String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes()));
187 cipher.init(Cipher.ENCRYPT_MODE, key);
188 String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes()));
189 cipher.init(Cipher.ENCRYPT_MODE, key);
190 String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes()));
191 Map<String, String> options = new HashMap<String, String>();
192 for (String label: mail.options.keySet()) {
193 cipher.init(Cipher.ENCRYPT_MODE, key);
194 String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes()));
195 cipher.init(Cipher.ENCRYPT_MODE, key);
196 String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes()));
197 options.put(labelEncrypted, actionNameEncrypted);
198 }
199
200 // Write to mail database.
201 writeToMailDatabase(mail, databaseFile, sender, rec, message, message2, options);
202 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) {
203 e.printStackTrace();
204 }
205 }
206
207 private static void writeToMailDatabase(MailEntry mail, Path databaseFile, String sender, String rec,
208 String message, String message2, Map<String, String> options) throws SQLException {
209 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile);
210 String sql = "INSERT INTO EXPMAIL (TIME,SND,REC,MSG,MSG2,OPTS,OPTSVAL) VALUES (?, ?, ?, ?, ?, ?, ?);";
211 PreparedStatement statement = c.prepareStatement(sql);
212 statement.setString(1, mail.timestamp);
213 statement.setString(2, sender);
214 statement.setString(3, rec);
215 statement.setString(4, message);
216 statement.setString(5, message2);
217 String opts = Arrays.toString(options.keySet().toArray());
218 statement.setString(6, opts);
219 String optsval = Arrays.toString(options.values().toArray());
220 statement.setString(7, optsval);
221 statement.execute();
222 statement.close();
223 c.close();
224 System.err.println("Message written to database: " + databaseFile.toString());
225 }
226
227 /**
228 * Gets the mail messages that the specified user is able to read.
229 * The caller supplies their username and private key.
230 * If the private key can decrypt a message, then it was encrypted using the users public key, and is therefore for them.
231 */
232 private static List<MailEntry> getEntries(String name, PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
233 List<MailEntry> filtered = new ArrayList<MailEntry>();
234
235 for (MailEntry mail: messages) {
236 if (isEncryptedOneOffMessage(mail)) {
237 getOneOffSecureMail(name, filtered, mail);
238 } else {
239 getStandardSecureMail(name, key, filtered, mail);
240 }
241 }
242
243 return filtered;
244 }
245
246 private static void getOneOffSecureMail(String name, List<MailEntry> filtered, MailEntry mail) {
247 StringBuilder sb = new StringBuilder();
248 String sep = ":::";
249 sb.append("Read one-off secure message." + sep);
250 sb.append(mail.timestamp + sep);
251 sb.append(mail.sender.substring(1) + sep);
252 sb.append(mail.receiver + sep);
253 sb.append(mail.message + sep);
254 sb.append(mail.message2 + sep);
255 for (String k: mail.options.keySet()) {
256 sb.append(k + sep + mail.options.get(k) + sep);
257 }
258 sb.reverse().delete(0, 3).reverse();
259 Map<String, String> options = new HashMap<String, String>();
260 options.put(sb.toString(), "AuthOneOffSecureMessage");
261 String currentTime = org.expeditee.stats.Formatter.getDateTime();
262 MailEntry mailOuter = new MailEntry(currentTime, "Unknown", name, "You have received a one-off secure message.", "Check your email for more details.", options);
263
264 if (mail.deadDropSource != null) {
265 Path lastAccessedFile = mail.deadDropSource.getParent().resolve(name + ".last-accessed");
266 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]");
267 try (Scanner in = new Scanner(lastAccessedFile.toFile())) {
268 Date lastAccessedTimestamp = format.parse(in.nextLine());
269 Date mailTimestamp = format.parse(mail.timestamp);
270 if (mailTimestamp.after(lastAccessedTimestamp)) {
271 filtered.add(mailOuter);
272 }
273 } catch (FileNotFoundException e) {
274 // It may not have been created yet, then err on the safe side and add it in.
275 filtered.add(mailOuter);
276 } catch (ParseException e) {
277 // If we fail to parse, then err on the safe side and add it in.
278 filtered.add(mailOuter);
279 }
280 }
281 }
282
283 private static void getStandardSecureMail(String name, PrivateKey key, List<MailEntry> filtered, MailEntry mail)
284 throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeyException,
285 BadPaddingException {
286 // confirm this is a message for the requester of entries
287 String receiver = mail.receiver;
288 byte[] receiverBytes = Base64.getDecoder().decode(receiver);
289 String receiverDecrypted = null;
290 Cipher c = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters);
291 try {
292 c.init(Cipher.DECRYPT_MODE, key);
293 receiverDecrypted = new String(c.doFinal(receiverBytes));
294 } catch (InvalidKeyException | BadPaddingException e) {
295 return;
296 }
297
298 // add an unencrypted version of the message to the return list
299 if (receiverDecrypted.compareToIgnoreCase(name) == 0) {
300 c.init(Cipher.DECRYPT_MODE, key);
301 String sender = new String(c.doFinal(Base64.getDecoder().decode(mail.sender)));
302 c.init(Cipher.DECRYPT_MODE, key);
303 String message = new String(c.doFinal(Base64.getDecoder().decode(mail.message)));
304 c.init(Cipher.DECRYPT_MODE, key);
305 String message2 = new String(c.doFinal(Base64.getDecoder().decode(mail.message2)));
306
307 Map<String, String> options = new HashMap<String, String>();
308 for (String label: mail.options.keySet()) {
309 c.init(Cipher.DECRYPT_MODE, key);
310 String labelDecrypted = new String(c.doFinal(Base64.getDecoder().decode(label)));
311 c.init(Cipher.DECRYPT_MODE, key);
312 String actionNameDecrypted = new String(c.doFinal(Base64.getDecoder().decode(mail.options.get(label))));
313 options.put(labelDecrypted, actionNameDecrypted);
314 }
315
316 Path lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(UserSettings.UserName.get() + "+" + sender).resolve(name + ".last-accessed");
317 if (!lastAccessedFile.toFile().exists()) {
318 lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(sender + "+" + UserSettings.UserName.get()).resolve(name + ".last-accessed");
319 }
320 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]");
321 MailEntry mailEntry = new MailEntry(mail.timestamp, sender, receiverDecrypted, message, message2, options);
322 try (Scanner in = new Scanner(lastAccessedFile.toFile())) {
323 Date lastAccessedTimestamp = format.parse(in.nextLine());
324 Date mailTimestamp = format.parse(mail.timestamp);
325 if (mailTimestamp.after(lastAccessedTimestamp)) {
326 filtered.add(mailEntry);
327 }
328 } catch (FileNotFoundException e) {
329 // It may not have been created yet, then err on the safe side and add it in.
330 filtered.add(mailEntry);
331 } catch (ParseException e) {
332 // If we fail to parse, then err on the safe side and add it in.
333 filtered.add(mailEntry);
334 }
335
336 }
337 }
338
339 private static boolean isEncryptedOneOffMessage(MailEntry mail) {
340 return mail.sender.charAt(0) == '=';
341 }
342
343 /**
344 * Describes a piece of mail, either encrypted or decrypted.
345 */
346 public static class MailEntry {
347 public String timestamp;
348 public String sender;
349 public String receiver;
350 public String message;
351 public String message2;
352 public Map<String, String> options;
353 public Path deadDropSource;
354
355 public MailEntry(String timestamp, String sender, String rec, String message, String message2, Map<String, String> options) {
356 this.timestamp = timestamp;
357 this.sender = sender;
358 this.receiver = rec;
359 this.message = message;
360 this.message2 = message2;
361 this.options = options;
362 }
363 }
364
365 public static void checkMail(PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException,
366 IllegalBlockSizeException, BadPaddingException, InvalidKeyException, KeyStoreException,
367 FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException, ParseException {
368 MailBay.clear();
369 AuthenticatorBrowser.getInstance().loadMailDatabase();
370 List<MailEntry> mailForLoggingInUser = Mail.getEntries(UserSettings.UserName.get(), key);
371 for (MailEntry mail: mailForLoggingInUser) {
372 MailBay.addMessage(mail.timestamp, mail.sender, mail.message, mail.message2, mail.options);
373 }
374
375 // Update last read files.
376 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH);
377 for (File connectionDir: deadDropPath.toFile().listFiles()) {
378 if (connectionDir.isDirectory()) {
379 Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath());
380 AuthenticatorBrowser.getInstance().updateLastReadMailTime(deaddropforcontactPath);
381 }
382 }
383 }
384
385 public static void decryptOneOffSecureMessage(SecretKey key, List<String> data) {
386 byte[] topicBytes = Base64.getDecoder().decode(data.get(3));
387 String topic = new String(org.expeditee.encryption.Actions.DecryptSymmetric(topicBytes, key));
388 byte[] messageBytes = Base64.getDecoder().decode(data.get(4));
389 String message = new String(org.expeditee.encryption.Actions.DecryptSymmetric(messageBytes, key));
390 Map<String, String> options = new HashMap<String, String>();
391 for (int i = 5; i < data.size(); i+=2) {
392 byte[] optionKeyBytes = Base64.getDecoder().decode(data.get(i));
393 String k = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionKeyBytes, key));
394 byte[] optionValueBytes = Base64.getDecoder().decode(data.get(i + 1));
395 String v = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionValueBytes, key));
396 options.put(k, v);
397 }
398 MailBay.addMessage(data.get(0), "Single-use Secure (encrypted sender)", topic, message, options);
399 }
400}
Note: See TracBrowser for help on using the repository browser.