package org.expeditee.auth; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.expeditee.auth.gui.MailBay; import org.expeditee.gui.FrameIO; import org.expeditee.settings.UserSettings; import org.ngikm.cryptography.CryptographyConstants; public class Mail implements CryptographyConstants { private static List messages = new ArrayList(); /** * Add a piece of mail, used during initialisation. */ public static void addEntry(MailEntry mail) { messages.add(mail); } public static void clear() { messages.clear(); } public static void sendMail(MailEntry mail, String colleagueName) { // Ensure dead drop area is set up. String me = UserSettings.UserName.get().toLowerCase(); String them = colleagueName.toLowerCase(); Path databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(me + "+" + them); if (!databaseFileDirPath.toFile().exists()) { databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(them + "+" + me); } Path databaseFilePath = databaseFileDirPath.resolve(colleagueName + ".db"); File databaseFile = databaseFilePath.toFile(); if (!databaseFile.exists()) { databaseFileDirPath.toFile().mkdirs(); String sql = "CREATE TABLE EXPMAIL (" + "TIME TEXT NOT NULL, " + "SND TEXT NOT NULL, " + "REC TEXT NOT NULL, " + "MSG TEXT NOT NULL, " + "MSG2 TEXT NOT NULL, " + "OPTS ARRAY NOT NULL, " + "OPTSVAL ARRAY NOT NULL)"; try { Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath()); Statement createTable = c.createStatement(); createTable.executeUpdate(sql); createTable.close(); c.close(); } catch (SQLException e) { System.err.println("Error while creating database file."); e.printStackTrace(); } } // Obtain public key PublicKey publicKey = null; try { publicKey = AuthenticatorBrowser.getInstance().getPublicKey(colleagueName); } catch (InvalidKeySpecException | NoSuchAlgorithmException | KeyStoreException | CertificateException | ClassNotFoundException | IOException | SQLException e) { System.err.println("Error while sending message. Unable to obtain public key for colleague " + colleagueName + ". Exception message: " + e.getMessage()); return; } // Check we got public key if (publicKey == null) { System.err.println("Error while sending message. Unable to obtain public key for colleague. Have you exchanged contact details?"); return; } // Send message sendMail(mail, publicKey, databaseFilePath); } private static void sendMail(MailEntry mail, PublicKey key, Path databaseFile) { try { Cipher cipher = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters); // encrypt the necessary parts of the message //cipher.init(Cipher.ENCRYPT_MODE, key); //String time = Base64.getEncoder().encodeToString(cipher.doFinal(mail.timestamp.getBytes())); cipher.init(Cipher.ENCRYPT_MODE, key); String sender = Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes())); cipher.init(Cipher.ENCRYPT_MODE, key); String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes())); cipher.init(Cipher.ENCRYPT_MODE, key); String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes())); cipher.init(Cipher.ENCRYPT_MODE, key); String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes())); Map options = new HashMap(); for (String label: mail.options.keySet()) { cipher.init(Cipher.ENCRYPT_MODE, key); String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes())); cipher.init(Cipher.ENCRYPT_MODE, key); String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes())); options.put(labelEncrypted, actionNameEncrypted); } // write to mail database Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile); String sql = "INSERT INTO EXPMAIL (TIME,SND,REC,MSG,MSG2,OPTS,OPTSVAL) VALUES (?, ?, ?, ?, ?, ?, ?);"; PreparedStatement statement = c.prepareStatement(sql); statement.setString(1, mail.timestamp); statement.setString(2, sender); statement.setString(3, rec); statement.setString(4, message); statement.setString(5, message2); String opts = Arrays.toString(options.keySet().toArray()); statement.setString(6, opts); String optsval = Arrays.toString(options.values().toArray()); statement.setString(7, optsval); statement.execute(); statement.close(); c.close(); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) { e.printStackTrace(); } } /** * Gets the mail messages that the specified user is able to read. * The caller supplies their username and private key. * If the private key can decrypt a message, then it was encrypted using the users public key, and is therefore for them. */ public static List getEntries(String name, PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { List filtered = new ArrayList(); for (MailEntry mail: messages) { // confirm this is a message for the requester of entries String receiver = mail.receiver; byte[] receiverBytes = Base64.getDecoder().decode(receiver); String receiverDecrypted = null; Cipher c = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters); try { c.init(Cipher.DECRYPT_MODE, key); receiverDecrypted = new String(c.doFinal(receiverBytes)); } catch (InvalidKeyException | BadPaddingException e) { // this is not a message for 'us' continue; } // add an unencrypted version of the message to the return list if (receiverDecrypted.compareToIgnoreCase(name) == 0) { c.init(Cipher.DECRYPT_MODE, key); String sender = new String(c.doFinal(Base64.getDecoder().decode(mail.sender))); c.init(Cipher.DECRYPT_MODE, key); String message = new String(c.doFinal(Base64.getDecoder().decode(mail.message))); c.init(Cipher.DECRYPT_MODE, key); String message2 = new String(c.doFinal(Base64.getDecoder().decode(mail.message2))); Map options = new HashMap(); //mail.options; for (String label: mail.options.keySet()) { c.init(Cipher.DECRYPT_MODE, key); String labelDecrypted = new String(c.doFinal(Base64.getDecoder().decode(label))); c.init(Cipher.DECRYPT_MODE, key); String actionNameDecrypted = new String(c.doFinal(Base64.getDecoder().decode(mail.options.get(label)))); options.put(labelDecrypted, actionNameDecrypted); } Path lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(UserSettings.UserName.get() + "+" + sender).resolve(name + ".last-accessed"); if (!lastAccessedFile.toFile().exists()) { lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(sender + "+" + UserSettings.UserName.get()).resolve(name + ".last-accessed"); } SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]"); MailEntry mailEntry = new MailEntry(mail.timestamp, sender, receiverDecrypted, message, message2, options); try (Scanner in = new Scanner(lastAccessedFile.toFile())) { Date lastAccessedTimestamp = format.parse(in.nextLine()); Date mailTimestamp = format.parse(mail.timestamp); if (mailTimestamp.after(lastAccessedTimestamp)) { filtered.add(mailEntry); } } catch (FileNotFoundException e) { // It may not have been created yet, then err on the safe side and add it in. filtered.add(mailEntry); } catch (ParseException e) { // If we fail to parse, then err on the safe side and add it in. filtered.add(mailEntry); } } } return filtered; } /** * Describes a piece of mail, either encrypted or decrypted. */ public static class MailEntry { public String timestamp; public String sender; public String receiver; public String message; public String message2; public Map options; public MailEntry(String timestamp, String sender, String rec, String message, String message2, Map options) { this.timestamp = timestamp; this.sender = sender; this.receiver = rec; this.message = message; this.message2 = message2; this.options = options; } } public static void checkMail(PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, KeyStoreException, FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException, ParseException { MailBay.clear(); AuthenticatorBrowser.getInstance().loadMailDatabase(); List mailForLoggingInUser = Mail.getEntries(UserSettings.UserName.get(), key); for (MailEntry mail: mailForLoggingInUser) { MailBay.addMessage(mail.timestamp, mail.message, mail.message2, mail.options); } // Update last read files. Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH); for (File connectionDir: deadDropPath.toFile().listFiles()) { if (connectionDir.isDirectory()) { Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath()); AuthenticatorBrowser.getInstance().updateLastReadMailTime(deaddropforcontactPath); } } } }