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

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

NOTE. The previous revision 1357 accidentally included many more files than it should have.
The commit message only relates to the exp file and none of the java files. Commit messages for the java files will follow at some point.

File size: 13.2 KB
Line 
1package org.expeditee.auth;
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.gui.MailBay;
39import org.expeditee.gui.FrameIO;
40import org.expeditee.settings.UserSettings;
41import org.ngikm.cryptography.CryptographyConstants;
42
43public class Mail implements CryptographyConstants {
44
45 private static List<MailEntry> messages = new ArrayList<MailEntry>();
46
47 /**
48 * Add a piece of mail, used during initialisation.
49 */
50 public static void addEntry(MailEntry mail) {
51 messages.add(mail);
52 }
53
54 public static void clear() {
55 messages.clear();
56 }
57
58 public static void sendOneOffMail(MailEntry mail, String colleagueName, byte[] key) {
59 // Ensure dead drop area is set up.
60 Path databaseFileDirPath = ensureDeadDrops(colleagueName);
61
62 // Ensure the database file exists.
63 Path databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath);
64
65 // Create secret key.
66 SecretKey secretKey = new SecretKeySpec(key, SymmetricAlgorithm);
67
68 // Send message
69 sendMail(mail, secretKey, databaseFilePath);
70 }
71
72 public static void sendMail(MailEntry mail, String colleagueName) {
73 // Ensure dead drop area is set up.
74 Path databaseFileDirPath = ensureDeadDrops(colleagueName);
75
76 // Ensure the database file exists.
77 Path databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath);
78
79 // Obtain public key
80 PublicKey publicKey = null;
81 try {
82 publicKey = AuthenticatorBrowser.getInstance().getPublicKey(colleagueName);
83 } catch (InvalidKeySpecException | NoSuchAlgorithmException | KeyStoreException | CertificateException
84 | ClassNotFoundException | IOException | SQLException e) {
85 System.err.println("Error while sending message. Unable to obtain public key for colleague " +
86 colleagueName + ". Exception message: " + e.getMessage());
87 return;
88 }
89
90 // Check we got public key
91 if (publicKey == null) {
92 System.err.println("Error while sending message. Unable to obtain public key for colleague. Have you exchanged contact details?");
93 return;
94 }
95
96 // Send message
97 sendMail(mail, publicKey, databaseFilePath);
98 }
99
100 private static Path ensureDatabaseFile(String colleagueName, Path databaseFileDirPath) {
101 Path databaseFilePath = databaseFileDirPath.resolve(colleagueName + ".db");
102 File databaseFile = databaseFilePath.toFile();
103 if (!databaseFile.exists()) {
104 databaseFileDirPath.toFile().mkdirs();
105 String sql =
106 "CREATE TABLE EXPMAIL (" +
107 "TIME TEXT NOT NULL, " +
108 "SND TEXT NOT NULL, " +
109 "REC TEXT NOT NULL, " +
110 "MSG TEXT NOT NULL, " +
111 "MSG2 TEXT NOT NULL, " +
112 "OPTS ARRAY NOT NULL, " +
113 "OPTSVAL ARRAY NOT NULL)";
114 try {
115 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
116 Statement createTable = c.createStatement();
117 createTable.executeUpdate(sql);
118 createTable.close();
119 c.close();
120 } catch (SQLException e) {
121 System.err.println("Error while creating database file.");
122 e.printStackTrace();
123 }
124 }
125 return databaseFilePath;
126 }
127
128 private static Path ensureDeadDrops(String colleagueName) {
129 String me = UserSettings.UserName.get().toLowerCase();
130 String them = colleagueName.toLowerCase();
131 Path databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(me + "+" + them);
132 if (!databaseFileDirPath.toFile().exists()) {
133 databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(them + "+" + me);
134 }
135 return databaseFileDirPath;
136 }
137
138 private static void sendMail(MailEntry mail, PublicKey key, Path databaseFile) {
139 try {
140 Cipher cipher = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters);
141 cipher.init(Cipher.ENCRYPT_MODE, key);
142 String sender = Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes()));
143 cipher.init(Cipher.ENCRYPT_MODE, key);
144 String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes()));
145 cipher.init(Cipher.ENCRYPT_MODE, key);
146 String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes()));
147 cipher.init(Cipher.ENCRYPT_MODE, key);
148 String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes()));
149
150 Map<String, String> options = new HashMap<String, String>();
151 for (String label: mail.options.keySet()) {
152 cipher.init(Cipher.ENCRYPT_MODE, key);
153 String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes()));
154 cipher.init(Cipher.ENCRYPT_MODE, key);
155 String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes()));
156 options.put(labelEncrypted, actionNameEncrypted);
157 }
158
159 // write to mail database
160 writeToMailDatabase(mail, databaseFile, sender, rec, message, message2, options);
161 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) {
162 e.printStackTrace();
163 }
164 }
165
166 private static void sendMail(MailEntry mail, SecretKey key, Path databaseFile) {
167 try {
168 // Encrypt message.
169 Cipher cipher = Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters);
170 cipher.init(Cipher.ENCRYPT_MODE, key);
171 String sender = "=" + Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes()));
172 cipher.init(Cipher.ENCRYPT_MODE, key);
173 String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes()));
174 cipher.init(Cipher.ENCRYPT_MODE, key);
175 String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes()));
176 cipher.init(Cipher.ENCRYPT_MODE, key);
177 String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes()));
178 Map<String, String> options = new HashMap<String, String>();
179 for (String label: mail.options.keySet()) {
180 cipher.init(Cipher.ENCRYPT_MODE, key);
181 String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes()));
182 cipher.init(Cipher.ENCRYPT_MODE, key);
183 String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes()));
184 options.put(labelEncrypted, actionNameEncrypted);
185 }
186
187 // Write to mail database.
188 writeToMailDatabase(mail, databaseFile, sender, rec, message, message2, options);
189 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) {
190 e.printStackTrace();
191 }
192 }
193
194 private static void writeToMailDatabase(MailEntry mail, Path databaseFile, String sender, String rec,
195 String message, String message2, Map<String, String> options) throws SQLException {
196 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile);
197 String sql = "INSERT INTO EXPMAIL (TIME,SND,REC,MSG,MSG2,OPTS,OPTSVAL) VALUES (?, ?, ?, ?, ?, ?, ?);";
198 PreparedStatement statement = c.prepareStatement(sql);
199 statement.setString(1, mail.timestamp);
200 statement.setString(2, sender);
201 statement.setString(3, rec);
202 statement.setString(4, message);
203 statement.setString(5, message2);
204 String opts = Arrays.toString(options.keySet().toArray());
205 statement.setString(6, opts);
206 String optsval = Arrays.toString(options.values().toArray());
207 statement.setString(7, optsval);
208 statement.execute();
209 statement.close();
210 c.close();
211 }
212
213 /**
214 * Gets the mail messages that the specified user is able to read.
215 * The caller supplies their username and private key.
216 * If the private key can decrypt a message, then it was encrypted using the users public key, and is therefore for them.
217 */
218 public static List<MailEntry> getEntries(String name, PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
219 List<MailEntry> filtered = new ArrayList<MailEntry>();
220
221 for (MailEntry mail: messages) {
222 // confirm this is a message for the requester of entries
223 String receiver = mail.receiver;
224 byte[] receiverBytes = Base64.getDecoder().decode(receiver);
225 String receiverDecrypted = null;
226 Cipher c = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters);
227 try {
228 c.init(Cipher.DECRYPT_MODE, key);
229 receiverDecrypted = new String(c.doFinal(receiverBytes));
230 } catch (InvalidKeyException | BadPaddingException e) {
231 // this is not a message for 'us'
232 continue;
233 }
234
235 // add an unencrypted version of the message to the return list
236 if (receiverDecrypted.compareToIgnoreCase(name) == 0) {
237 c.init(Cipher.DECRYPT_MODE, key);
238 String sender = new String(c.doFinal(Base64.getDecoder().decode(mail.sender)));
239 c.init(Cipher.DECRYPT_MODE, key);
240 String message = new String(c.doFinal(Base64.getDecoder().decode(mail.message)));
241 c.init(Cipher.DECRYPT_MODE, key);
242 String message2 = new String(c.doFinal(Base64.getDecoder().decode(mail.message2)));
243
244 Map<String, String> options = new HashMap<String, String>(); //mail.options;
245 for (String label: mail.options.keySet()) {
246 c.init(Cipher.DECRYPT_MODE, key);
247 String labelDecrypted = new String(c.doFinal(Base64.getDecoder().decode(label)));
248 c.init(Cipher.DECRYPT_MODE, key);
249 String actionNameDecrypted = new String(c.doFinal(Base64.getDecoder().decode(mail.options.get(label))));
250 options.put(labelDecrypted, actionNameDecrypted);
251 }
252
253 Path lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(UserSettings.UserName.get() + "+" + sender).resolve(name + ".last-accessed");
254 if (!lastAccessedFile.toFile().exists()) {
255 lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(sender + "+" + UserSettings.UserName.get()).resolve(name + ".last-accessed");
256 }
257 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]");
258 MailEntry mailEntry = new MailEntry(mail.timestamp, sender, receiverDecrypted, message, message2, options);
259 try (Scanner in = new Scanner(lastAccessedFile.toFile())) {
260 Date lastAccessedTimestamp = format.parse(in.nextLine());
261 Date mailTimestamp = format.parse(mail.timestamp);
262 if (mailTimestamp.after(lastAccessedTimestamp)) {
263 filtered.add(mailEntry);
264 }
265 } catch (FileNotFoundException e) {
266 // It may not have been created yet, then err on the safe side and add it in.
267 filtered.add(mailEntry);
268 } catch (ParseException e) {
269 // If we fail to parse, then err on the safe side and add it in.
270 filtered.add(mailEntry);
271 }
272
273 }
274 }
275
276 return filtered;
277 }
278
279 /**
280 * Describes a piece of mail, either encrypted or decrypted.
281 */
282 public static class MailEntry {
283 public String timestamp;;
284 public String sender;
285 public String receiver;
286 public String message;
287 public String message2;
288 public Map<String, String> options;
289 public MailEntry subEntry;
290
291 public MailEntry(String timestamp, String sender, String rec, String message, String message2, Map<String, String> options) {
292 this.timestamp = timestamp;
293 this.sender = sender;
294 this.receiver = rec;
295 this.message = message;
296 this.message2 = message2;
297 this.options = options;
298 }
299 }
300
301 public static void checkMail(PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException,
302 IllegalBlockSizeException, BadPaddingException, InvalidKeyException, KeyStoreException,
303 FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException, ParseException {
304 MailBay.clear();
305 AuthenticatorBrowser.getInstance().loadMailDatabase();
306 List<MailEntry> mailForLoggingInUser = Mail.getEntries(UserSettings.UserName.get(), key);
307 for (MailEntry mail: mailForLoggingInUser) {
308 MailBay.addMessage(mail.timestamp, mail.message, mail.message2, mail.options);
309 }
310
311 // Update last read files.
312 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH);
313 for (File connectionDir: deadDropPath.toFile().listFiles()) {
314 if (connectionDir.isDirectory()) {
315 Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath());
316 AuthenticatorBrowser.getInstance().updateLastReadMailTime(deaddropforcontactPath);
317 }
318 }
319 }
320}
Note: See TracBrowser for help on using the repository browser.