Changeset 1504
- Timestamp:
- 01/29/20 13:20:24 (4 years ago)
- Location:
- trunk/src/org/expeditee
- Files:
-
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/expeditee/auth/Actions.java
r1494 r1504 9 9 import java.nio.file.StandardCopyOption; 10 10 import java.security.InvalidKeyException; 11 import java.security.KeyFactory;12 11 import java.security.KeyStoreException; 13 12 import java.security.NoSuchAlgorithmException; 14 import java.security.PrivateKey;15 13 import java.security.PublicKey; 16 14 import java.security.SecureRandom; 17 15 import java.security.cert.CertificateException; 18 16 import java.security.spec.InvalidKeySpecException; 19 import java.security.spec.PKCS8EncodedKeySpec;20 17 import java.sql.SQLException; 21 18 import java.text.ParseException; 22 19 import java.util.Base64; 23 20 import java.util.Collection; 21 import java.util.Date; 24 22 import java.util.HashMap; 25 23 import java.util.List; … … 57 55 import org.expeditee.items.Text; 58 56 import org.expeditee.settings.UserSettings; 59 import org.expeditee.settings.identity.secrets.KeyList;60 import org.expeditee.stats.Formatter;61 57 62 58 public class Actions implements CryptographyConstants { 63 64 // 65 public static void SendTestMessage(String colleagueName) throws InvalidKeySpecException, NoSuchAlgorithmException, FileNotFoundException, KeyStoreException, CertificateException, ClassNotFoundException, IOException, SQLException{66 String time = org.expeditee.stats.Formatter.getDateTime();59 60 // Start Debug Actions 61 public static void SendTestMessage(String recipient) { 62 String time = Mail.FORMAT.format(new Date()); 67 63 String sender = UserSettings.UserName.get(); 68 64 String topic = "Test Message"; … … 70 66 Map<String, String> options = new HashMap<String, String>(); 71 67 options.put("Neat", "Beep"); 72 MailEntry mail = new MailEntry(time, sender, colleagueName, topic, message, options); 73 Mail.sendMail(mail, colleagueName); 68 Mail mailClient = MailBay.getMailClient(); 69 Mail.MailEntry mail = mailClient.new MailEntry(time, sender, recipient, topic, message, options); 70 mailClient.sendMail(mail, recipient); 74 71 MessageBay.displayMessage("Test message sent."); 75 72 } 76 77 public static void SendTestMessageHemi(String param) { 78 String time = Formatter.getDateTime(); 79 String sender = UserSettings.UserName.get(); 80 String recipient = param.split(" ")[0]; 81 String message = param.split(" ")[1]; 82 Map<String, String> options = new HashMap<String, String>(); 83 options.put("Accept", "beep"); 84 options.put("Reject", "beep"); 85 MailEntry mail = new MailEntry(time, sender, recipient, "Have a key", message, options); 86 Mail.sendMail(mail, recipient); 87 MessageBay.displayMessage("Test message sent."); 88 } 89 90 public static void SendTestOneOffMessage(String colleagueName) { 91 String time = Formatter.getDateTime(); 73 74 public static void CheckMailTest() { 75 List<Mail.MailEntry> mail = MailBay.getMailClient().checkMail(); 76 for (Mail.MailEntry entry: mail) { 77 MessageBay.displayMessage(entry.toString()); 78 } 79 MessageBay.displayMessage(mail.size() + " new mail since last run."); 80 } 81 82 public static void SendTestOneOffMessage(String recipient) { 83 String time = Mail.FORMAT.format(new Date()); 92 84 String sender = UserSettings.UserName.get(); 93 85 String topic = "Test Message"; … … 95 87 Map<String, String> options = new HashMap<String, String>(); 96 88 options.put("Neat", "Beep"); 97 MailEntry mail = new MailEntry(time, sender, colleagueName, topic, message, options);89 98 90 Random rand = new SecureRandom(); 99 byte[] key = new byte[16]; 100 rand.nextBytes(key); 101 System.out.println(Base64.getEncoder().encodeToString(key)); 102 Mail.sendOneOffMail(mail, colleagueName, key); 91 byte[] keyBytes = new byte[16]; 92 rand.nextBytes(keyBytes); 93 SecretKey key = new SecretKeySpec(keyBytes, SymmetricAlgorithm); 94 System.out.println(Base64.getEncoder().encodeToString(keyBytes)); 95 96 Mail mailClient = MailBay.getMailClient(); 97 Mail.MailEntry mail = mailClient.new MailEntry(time, sender, recipient, topic, message, options); 98 mailClient.sendOneOffMail(mail, recipient, key); 103 99 MessageBay.displayMessage("Test message sent."); 104 100 } … … 114 110 115 111 // Start Misc Auth Actions 112 public static void CheckForNewMail() { 113 MailBay.checkMail(); 114 } 115 116 116 /** 117 117 * Action ran by user to read a message using a single use distributed Symmetric key … … 124 124 SecretKey key = new SecretKeySpec(keyBytes, SymmetricAlgorithm); 125 125 List<String> data = actionItem.getData(); 126 Mail .decryptOneOffSecureMessage(key, data);126 MailBay.decryptOneOffSecureMessage(key, data); 127 127 StandardGestureActions.Refresh(); 128 128 } … … 146 146 public static void ToggleBay() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, SQLException, IOException, ParseException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { 147 147 if (!AuthenticatorBrowser.isAuthenticated()) return; 148 if (!DisplayController.isMailMode()) { 149 MailBay.ensureLink(); 150 Mail.clear(); 151 String keyEncoded = KeyList.PrivateKey.get().getData().get(0); 152 byte[] keyBytes = Base64.getDecoder().decode(keyEncoded); 153 PrivateKey key = KeyFactory.getInstance(AsymmetricAlgorithm).generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); 154 Mail.checkMail(key); 155 } 148 //if (!DisplayController.isMailMode()) { 149 // MailBayV2.checkMail(); 150 //} 156 151 DisplayController.ToggleMailMode(); 157 152 } … … 558 553 @SuppressWarnings("unused") 559 554 private static boolean submitTrustedUsersPasswordRecovery(Map<AuthenticationTag, String> userData) throws InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, ClassNotFoundException, IOException, SQLException { 560 String colleagueOne = userData.get(AuthenticationTag.TrustedUserOne);561 String colleagueTwo = userData.get(AuthenticationTag.TrustedUserTwo);562 PublicKey colleagueOneKey = AuthenticatorBrowser.getInstance().getPublicKey( colleagueOne);563 PublicKey colleagueTwoKey = AuthenticatorBrowser.getInstance().getPublicKey( colleagueTwo);555 String trustedUserOne = userData.get(AuthenticationTag.TrustedUserOne); 556 String trustedUserTwo = userData.get(AuthenticationTag.TrustedUserTwo); 557 PublicKey colleagueOneKey = AuthenticatorBrowser.getInstance().getPublicKey(trustedUserOne); 558 PublicKey colleagueTwoKey = AuthenticatorBrowser.getInstance().getPublicKey(trustedUserTwo); 564 559 if (colleagueOneKey == null) { 565 MessageBay.errorMessage("Unable to get public key for colleague: " + colleagueOne);560 MessageBay.errorMessage("Unable to get public key for colleague: " + trustedUserOne); 566 561 return false; 567 562 } else if (colleagueTwoKey == null) { 568 MessageBay.errorMessage("Unable to get public key for colleague: " + colleagueTwo);563 MessageBay.errorMessage("Unable to get public key for colleague: " + trustedUserTwo); 569 564 return false; 570 565 } else { 571 String time = org.expeditee.stats.Formatter.getDateTime();566 String time = Mail.FORMAT.format(new Date()); 572 567 String sender = userData.get(AuthenticationTag.Username); 573 568 String topic = "You have received a request for cooperation from your colleague " + sender; … … 576 571 arguments.put("I agree to assist " + sender + " if they loose access to their account.", "AuthConfirmPasswordColleagueRelationship " + sender); 577 572 arguments.put("I wish to excuse myself from this responsibility.", "AuthDenyPasswordColleagueRelationship " + sender); 578 MailEntry mail = new MailEntry(time, sender, colleagueOne, topic, message, arguments); 579 Mail.sendMail(mail, colleagueOne); 580 mail = new MailEntry(time, sender, colleagueTwo, topic, message, arguments); 581 Mail.sendMail(mail, colleagueTwo); 573 Mail outbox = MailBay.getMailClient(); 574 MailEntry mail = outbox.new MailEntry(time, sender, trustedUserOne, topic, message, arguments); 575 outbox.sendMail(mail, trustedUserOne); 576 mail = outbox.new MailEntry(time, sender, trustedUserTwo, topic, message, arguments); 577 outbox.sendMail(mail, trustedUserTwo); 582 578 AuthenticatorBrowser.getInstance().markRequestedColleagues(UserSettings.UserName.get()); 583 579 return true; -
trunk/src/org/expeditee/auth/AuthenticatorBrowser.java
r1453 r1504 2 2 3 3 import java.io.File; 4 import java.io.FileFilter;5 4 import java.io.FileInputStream; 6 5 import java.io.FileNotFoundException; 7 6 import java.io.FileOutputStream; 8 import java.io.FileWriter;9 7 import java.io.IOException; 10 8 import java.io.InputStream; 11 import java.nio.file.Path;12 9 import java.nio.file.Paths; 13 10 import java.security.KeyFactory; … … 22 19 import java.security.spec.InvalidKeySpecException; 23 20 import java.security.spec.X509EncodedKeySpec; 24 import java.sql.Connection;25 import java.sql.DriverManager;26 import java.sql.PreparedStatement;27 import java.sql.ResultSet;28 21 import java.sql.SQLException; 29 import java.text.ParseException;30 import java.text.SimpleDateFormat;31 22 import java.util.Arrays; 32 23 import java.util.Base64; 33 24 import java.util.Collection; 34 import java.util.Date;35 import java.util.HashMap;36 import java.util.HashSet;37 import java.util.Map;38 25 import java.util.Scanner; 39 import java.util.Set;40 26 import java.util.stream.Stream; 41 27 … … 44 30 45 31 import org.expeditee.actions.Actions; 46 import org.expeditee.auth.mail.Mail;47 32 import org.expeditee.core.Dimension; 48 33 import org.expeditee.core.Point; … … 67 52 import org.expeditee.settings.UserSettings; 68 53 import org.expeditee.settings.identity.secrets.KeyList; 69 import org.expeditee.stats.Formatter;70 54 71 55 public final class AuthenticatorBrowser extends Browser implements CryptographyConstants { … … 195 179 } 196 180 } 197 }198 199 final void loadMailFromFile(Path dbFile) throws SQLException {200 // Load in all mail.201 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.toAbsolutePath().toString());202 String sql = "SELECT * FROM EXPMAIL";203 PreparedStatement query = c.prepareStatement(sql);204 ResultSet allMail = query.executeQuery();205 206 // Construct all mail objects using content from database.207 while(allMail.next()) {208 String timestamp = allMail.getString("time");209 String from = allMail.getString("snd");210 String to = allMail.getString("rec");211 String msg = allMail.getString("msg");212 String msg2 = allMail.getString("msg2");213 String[] opts = allMail.getString("opts").split(",");214 opts[0] = opts[0].replace("[", "");215 opts[opts.length - 1] = opts[opts.length - 1].replace("]", "");216 String[] optsVal = allMail.getString("optsval").split(",");217 optsVal[0] = optsVal[0].replace("[", "");218 optsVal[optsVal.length - 1] = optsVal[optsVal.length - 1].replace("]", "");219 220 Map<String, String> options = new HashMap<String, String>();221 for (int i = 0, o = 0; i < opts.length && o < optsVal.length; i++, o++) {222 String key = opts[i].trim();223 String val = optsVal[o].trim();224 options.put(key, val);225 }226 227 Mail.MailEntry mail = new Mail.MailEntry(timestamp, from, to, msg, msg2, options);228 mail.deadDropSource = dbFile;229 Mail.addEntry(mail);230 }231 232 // Disconnect from database.233 allMail.close();234 query.close();235 c.close();236 }237 238 public final void loadMailDatabase() throws SQLException, FileNotFoundException, ParseException {239 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH);240 for (File connectionDir: deadDropPath.toFile().listFiles()) {241 if (connectionDir.isDirectory()) {242 Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath());243 Path dbFile = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".db");244 if (dbFile.toFile().exists()) {245 loadMailFromFile(dbFile);246 }247 clearOldMailFromDatabase(deaddropforcontactPath);248 }249 }250 }251 252 public final void updateLastReadMailTime(Path deaddropforcontactPath) {253 Path timestamp = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".last-accessed");254 try(FileWriter out = new FileWriter(timestamp.toFile())) {255 out.write(Formatter.getDateTime() + System.getProperty("line.separator"));256 } catch (IOException e) {257 e.printStackTrace();258 }259 }260 261 private void clearOldMailFromDatabase(Path directory) throws FileNotFoundException, ParseException, SQLException {262 File[] files = directory.toFile().listFiles(new FileFilter() {263 @Override264 public boolean accept(File file) {265 return !file.getName().startsWith(UserSettings.UserName.get());266 }267 });268 269 File dbFile = null;270 File lastAccessedFile = null;271 for (File file: files) {272 if (file.getName().endsWith(".db")) {273 dbFile = file;274 } else {275 lastAccessedFile = file;276 }277 }278 279 if (dbFile == null || lastAccessedFile == null) {280 return; // Not the end of the world if we cannot clear out old messages, these files may not be present yet if the others are recently new.281 }282 283 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]");284 Date timestamp = null;285 try(Scanner in = new Scanner(lastAccessedFile)) {286 timestamp = format.parse(in.nextLine());287 } catch (ParseException e) {288 return; // Not the end of the world if we cannot clear out old messages, the database might be empty.289 }290 291 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());292 String sql = "SELECT * FROM EXPMAIL";293 PreparedStatement query = c.prepareStatement(sql);294 ResultSet allMail = query.executeQuery();295 Set<String> oldTimestamps = new HashSet<String>();296 297 while (allMail.next()) {298 String time = allMail.getString("time");299 Date messageTimestamp = format.parse(time);300 if (timestamp.after(messageTimestamp)) {301 oldTimestamps.add(time);302 }303 }304 305 if (oldTimestamps.isEmpty()) {306 return;307 }308 309 for(String oldTimestamp: oldTimestamps) {310 System.out.println("Deleting message with timestamp: " + oldTimestamp);311 sql = "DELETE FROM EXPMAIL WHERE time='" + oldTimestamp + "'";312 query = c.prepareStatement(sql);313 query.executeUpdate();314 }315 181 } 316 182 -
trunk/src/org/expeditee/auth/account/Authenticate.java
r1478 r1504 1 1 package org.expeditee.auth.account; 2 2 3 import java.io.IOException;4 import java.security.InvalidKeyException;5 import java.security.KeyFactory;6 import java.security.KeyStoreException;7 import java.security.NoSuchAlgorithmException;8 import java.security.PrivateKey;9 import java.security.cert.CertificateException;10 import java.security.spec.InvalidKeySpecException;11 import java.security.spec.PKCS8EncodedKeySpec;12 import java.sql.SQLException;13 import java.text.ParseException;14 3 import java.util.ArrayList; 15 4 import java.util.Base64; … … 18 7 import java.util.Map; 19 8 20 import javax.crypto.BadPaddingException;21 import javax.crypto.IllegalBlockSizeException;22 import javax.crypto.NoSuchPaddingException;23 9 import javax.crypto.SecretKey; 24 10 … … 96 82 MessageBay.clear(); 97 83 MessageBay.updateFramesetLocation(); 98 MailBay. disconnect();84 MailBay.reconnectToUser(UserSettings.UserName.get()); 99 85 100 86 // Parse the users profile to refresh settings. … … 108 94 AuthenticationResult res = AuthenticationResult.SuccessLogin; 109 95 110 // Check mail and update last read files. 111 MailBay.clear(); 112 try { 113 Text keyItem = KeyList.PrivateKey.get(); 114 if (keyItem.getData() != null) { 115 // Check mail. 116 String keyEncoded = keyItem.getData().get(0); 117 byte[] keyBytes = Base64.getDecoder().decode(keyEncoded); 118 PrivateKey key = KeyFactory.getInstance(AsymmetricAlgorithm).generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); 119 org.expeditee.auth.mail.Mail.checkMail(key); 120 } else { 121 res.additionalInfo.add("No private key present: your communication with other Expeditee users will be limited until this is resolved."); 122 } 123 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | ClassNotFoundException 124 | SQLException | ParseException | IOException | InvalidKeyException | NoSuchPaddingException | 125 IllegalBlockSizeException | BadPaddingException e) { 126 res.additionalInfo.add("An error occured while attempting to load in mail sent to you by other Expeditee users. See the exception for more information."); 127 e.printStackTrace(); 128 } catch (InvalidKeySpecException e) { 129 res.additionalInfo.add("Stored data cannot be used to create a private key. See exception for more information."); 130 e.printStackTrace(); 131 } 96 // Check mail for new user. 97 MailBay.checkMail(); 132 98 133 99 Collection<Item> usernameFields = Actions.getByData(FrameIO.LoadFrame("multiuser1"), "txtUsername"); … … 155 121 UserSettings.setupDefaultFolders(); 156 122 MessageBay.updateFramesetLocation(); 157 MailBay.disconnect();158 123 159 124 // Reset all of the settings. -
trunk/src/org/expeditee/auth/account/Create.java
r1474 r1504 46 46 import org.expeditee.setting.TextSetting; 47 47 import org.expeditee.settings.UserSettings; 48 import org.expeditee.settings.folders.FolderSettings;49 48 import org.expeditee.settings.identity.secrets.KeyList; 50 49 -
trunk/src/org/expeditee/auth/account/Password.java
r1500 r1504 13 13 import java.util.Base64; 14 14 import java.util.Collection; 15 import java.util.Date; 15 16 import java.util.HashMap; 16 17 import java.util.Iterator; … … 33 34 import org.expeditee.auth.mail.Mail; 34 35 import org.expeditee.auth.mail.Mail.MailEntry; 36 import org.expeditee.auth.mail.gui.MailBay; 35 37 import org.expeditee.auth.tags.AuthenticationTag; 36 38 import org.expeditee.encryption.CryptographyConstants; … … 44 46 import org.expeditee.settings.identity.passwordrecovery.Colleagues; 45 47 import org.expeditee.settings.identity.secrets.KeyList; 46 import org.expeditee.stats.Formatter;47 48 48 49 import com.codahale.shamir.Scheme; … … 113 114 114 115 // Get colleagues to distribute messages too. 115 String[] colleagues = getPasswordColleaguesFromUsername(username);116 String[] trustedUsers = getPasswordColleaguesFromUsername(username); 116 117 // Send secure message to colleague one 117 String colleagueOne = colleagues[0];118 String time = Formatter.getDateTime();118 String trustedUserOne = trustedUsers[0]; 119 String time = Mail.FORMAT.format(new Date()); 119 120 String topic = "Password Recovery for " + username; 120 121 String message = "Your colleague " + username + " would like you to help them recover access to their account."; 121 122 Map<String, String> options = new HashMap<String, String>(); 122 123 options.put("Provide assistance", "AuthEmailPasswordShare " + username); 123 MailEntry mail = new MailEntry(time, username, colleagueOne, topic, message, options); 124 Mail.sendOneOffMail(mail, colleagueOne, Base64.getDecoder().decode(intergalacticNumber)); 124 Mail outbox = MailBay.getMailClient(username); 125 MailEntry mail = outbox.new MailEntry(time, username, trustedUserOne, topic, message, options); 126 SecretKeySpec key = new SecretKeySpec(Base64.getDecoder().decode(intergalacticNumber), SymmetricAlgorithm); 127 outbox.sendOneOffMail(mail, trustedUserOne, key); 125 128 126 129 // Send secure message to colleague two 127 String colleagueTwo = colleagues[1];128 Mail.sendOneOffMail(mail, colleagueTwo, Base64.getDecoder().decode(intergalacticNumber));130 String trustedUserTwo = trustedUsers[1]; 131 outbox.sendOneOffMail(mail, trustedUserTwo, key); 129 132 130 133 String nl = System.getProperty("line.separator"); … … 136 139 try { 137 140 // Send email with key to colleague one 138 String colleagueOneEmail = colleagues[2];141 String colleagueOneEmail = trustedUsers[2]; 139 142 sendEmail(colleagueOneEmail, sb); 140 143 // Send email with key to colleague two 141 String colleagueTwoEmail = colleagues[3];144 String colleagueTwoEmail = trustedUsers[3]; 142 145 sendEmail(colleagueTwoEmail, sb); 143 146 … … 250 253 } 251 254 252 public static void setPWColleagues(String colleagueOne, String colleagueTwo) {255 public static void setPWColleagues(String trustedUserOne, String trustedUserTwo) { 253 256 // Get needed text items. 254 257 Frame pwRecoveryFrame = FrameIO.LoadFrame(UserSettings.UserName.get() + AuthenticatorBrowser.PASSWORD_RECOVERY_FRAME); … … 261 264 Text text = it.next(); 262 265 if (text.getText().toLowerCase().startsWith("user_one:")) { 263 text.setText("User_one: " + colleagueOne);264 Colleagues.User_One.set( colleagueOne);266 text.setText("User_one: " + trustedUserOne); 267 Colleagues.User_One.set(trustedUserOne); 265 268 } else if (text.getText().toLowerCase().startsWith("user_two:")) { 266 text.setText("User_two: " + colleagueTwo);267 Colleagues.User_Two.set( colleagueTwo);269 text.setText("User_two: " + trustedUserTwo); 270 Colleagues.User_Two.set(trustedUserTwo); 268 271 } 269 272 } … … 281 284 // Create shares 282 285 Map<Integer, byte[]> shares = scheme.split(keyBytes); 283 String colleagueOneShare = Base64.getEncoder().encodeToString(shares.get(1));284 String colleagueTwoShare = Base64.getEncoder().encodeToString(shares.get(2));286 String trustedUserOneShare = Base64.getEncoder().encodeToString(shares.get(1)); 287 String trustedUserTwoShare = Base64.getEncoder().encodeToString(shares.get(2)); 285 288 286 289 // Distribute share zero to colleague one 287 String time = org.expeditee.stats.Formatter.getDateTime();290 String time = Mail.FORMAT.format(new Date()); 288 291 String sender = UserSettings.UserName.get(); 289 292 String topic = "Please help me secure my Expeditee account."; 290 293 String message = "Run the below action to store a secret key that will help me recover access to my account should I ever loose it."; 291 294 Map<String, String> options = new HashMap<String, String>(); 292 options.put("Store Secret Key for " + sender, "AuthAddSecretKey " + sender + "PersonalKeyShare " + colleagueOneShare); 293 MailEntry mail = new MailEntry(time, sender, colleagueOne, topic, message, options); 294 Mail.sendMail(mail, colleagueOne); 295 options.put("Store Secret Key for " + sender, "AuthAddSecretKey " + sender + "PersonalKeyShare " + trustedUserOneShare); 296 Mail outbox = MailBay.getMailClient(); 297 MailEntry mail = outbox.new MailEntry(time, sender, trustedUserOne, topic, message, options); 298 outbox.sendMail(mail, trustedUserOne); 295 299 296 300 // Distribute share one to colleague two 297 301 options = new HashMap<String, String>(); 298 options.put("Store Secret Key for " + sender, "AuthAddSecretKey " + sender + "PersonalKeyShare " + colleagueTwoShare);299 mail = new MailEntry(time, sender, colleagueTwo, topic, message, options);300 Mail.sendMail(mail, colleagueTwo);301 302 MessageBay.displayMessage("You PW Colleagues have been set to " + colleagueOne + " and " + colleagueTwo + ". "302 options.put("Store Secret Key for " + sender, "AuthAddSecretKey " + sender + "PersonalKeyShare " + trustedUserTwoShare); 303 mail = outbox.new MailEntry(time, sender, trustedUserTwo, topic, message, options); 304 outbox.sendMail(mail, trustedUserTwo); 305 306 MessageBay.displayMessage("You Trusted Users have been set to " + trustedUserOne + " and " + trustedUserTwo + ". " 303 307 + "They have been sent a Expeditee mail that they can use to store a share of your secret key."); 304 308 } -
trunk/src/org/expeditee/auth/mail/Mail.java
r1483 r1504 3 3 import java.io.File; 4 4 import java.io.FileNotFoundException; 5 import java.io.FileWriter; 5 6 import java.io.IOException; 6 7 import java.nio.file.Path; 7 8 import java.nio.file.Paths; 8 9 import java.security.InvalidKeyException; 10 import java.security.Key; 11 import java.security.KeyFactory; 9 12 import java.security.KeyStoreException; 10 13 import java.security.NoSuchAlgorithmException; … … 13 16 import java.security.cert.CertificateException; 14 17 import java.security.spec.InvalidKeySpecException; 18 import java.security.spec.PKCS8EncodedKeySpec; 15 19 import java.sql.Connection; 16 20 import java.sql.DriverManager; 17 21 import java.sql.PreparedStatement; 22 import java.sql.ResultSet; 18 23 import java.sql.SQLException; 19 24 import java.sql.Statement; … … 28 33 import java.util.Map; 29 34 import java.util.Scanner; 35 import java.util.function.Supplier; 30 36 31 37 import javax.crypto.BadPaddingException; … … 34 40 import javax.crypto.NoSuchPaddingException; 35 41 import javax.crypto.SecretKey; 36 import javax.crypto.spec.SecretKeySpec;37 42 38 43 import org.expeditee.auth.AuthenticatorBrowser; 39 import org.expeditee.auth.mail.gui.MailBay;40 44 import org.expeditee.encryption.CryptographyConstants; 41 45 import org.expeditee.gui.FrameIO; 42 import org.expeditee.settings.UserSettings; 46 import org.expeditee.gui.MessageBay; 47 import org.expeditee.items.Text; 48 import org.expeditee.settings.identity.secrets.KeyList; 43 49 44 50 public class Mail implements CryptographyConstants { 51 private String user; 52 public static final SimpleDateFormat FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z"); 53 54 public Mail(String user) { 55 this.user = user.toLowerCase(); 56 } 45 57 46 private static List<MailEntry> messages = new ArrayList<MailEntry>(); 47 48 /** 49 * Add a piece of mail, used during initialisation. 50 */ 51 public static void addEntry(MailEntry mail) { 52 messages.add(mail); 53 } 54 55 public static void clear() { 56 messages.clear(); 57 } 58 59 public static void sendOneOffMail(MailEntry mail, String colleagueName, byte[] key) { 60 // Ensure dead drop area is set up. 61 Path databaseFileDirPath = ensureDeadDrops(colleagueName, mail.sender); 62 63 // Ensure the database file exists. 64 Path databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath); 65 66 // Create secret key. 67 SecretKey secretKey = new SecretKeySpec(key, SymmetricAlgorithm); 68 69 // Send message 70 sendMail(mail, secretKey, databaseFilePath); 71 72 // Do it all again if demo mode is on (ensures sync without having to move db files) 73 if (Boolean.getBoolean("expeditee.demo-mode")) { 74 databaseFileDirPath = ensureDeadDropsDemoMode(colleagueName, mail.sender); 75 databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath); 76 sendMail(mail, secretKey, databaseFilePath); 77 } 78 } 79 80 public static void sendMail(MailEntry mail, String colleagueName) { 81 // Ensure dead drop area is set up. 82 Path databaseFileDirPath = ensureDeadDrops(colleagueName); 83 84 // Ensure the database file exists. 85 Path databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath); 86 87 // Obtain public key 58 public void sendMail(MailEntry mail, String colleagueName) { 59 // Get database file to place mail into 60 Path databaseFilePath = getDatabaseFilePath(colleagueName); 61 62 // Obtain colleagues public key 88 63 PublicKey publicKey = null; 89 64 try { … … 91 66 } catch (InvalidKeySpecException | NoSuchAlgorithmException | KeyStoreException | CertificateException 92 67 | ClassNotFoundException | IOException | SQLException e) { 93 System.err.println("Error while sending message. Unable to obtain public key for colleague " +94 colleagueName + ". Exception message: " + e.getMessage());68 System.err.println("Error while sending message. Unable to obtain public key for colleague " 69 + colleagueName + ". Exception message: " + e.getMessage()); 95 70 return; 96 71 } 97 72 98 73 // Check we got public key 99 74 if (publicKey == null) { 100 System.err.println("Error while sending message. Unable to obtain public key for colleague. Have you exchanged contact details?"); 75 System.err.println("Error while sending message. " + "Unable to obtain public key for colleague. " 76 + "Have you exchanged contact details?"); 101 77 return; 102 78 } 79 80 // Send message 81 MailEntry encryptedMailEntryForSending = mail.encryptMailEntry(publicKey, () -> { 82 try { 83 return Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters); 84 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { 85 e.printStackTrace(); 86 return null; 87 } 88 }, ""); 89 encryptedMailEntryForSending.writeToMailDatabase(databaseFilePath); 90 91 // Do it all again if demo mode is on (ensures sync without having to move db 92 // files) 93 if (Boolean.getBoolean("expeditee.demo-mode")) { 94 databaseFilePath = getDatabaseFilePath(colleagueName, true); 95 encryptedMailEntryForSending.writeToMailDatabase(databaseFilePath); 96 } 97 } 98 99 public void sendOneOffMail(MailEntry mail, String colleagueName, SecretKey key) { 100 // Get database file to place mail into 101 Path databaseFilePath = getDatabaseFilePath(colleagueName); 102 103 // Send message 104 MailEntry encryptedMailEntryForSending = mail.encryptMailEntry(key, () -> { 105 try { 106 return Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters); 107 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { 108 e.printStackTrace(); 109 return null; 110 } 111 }, MailEntry.SINGLE_USE_SENDER_TAG); 112 encryptedMailEntryForSending.writeToMailDatabase(databaseFilePath); 113 114 // Do it all again if demo mode is on (ensures sync without having to move db 115 // files) 116 if (Boolean.getBoolean("expeditee.demo-mode")) { 117 databaseFilePath = getDatabaseFilePath(colleagueName, true); 118 encryptedMailEntryForSending.writeToMailDatabase(databaseFilePath); 119 } 120 } 121 122 public List<MailEntry> checkMail() { 123 PrivateKey key = getPrivateKey(); 124 List<Path> databasePaths = getDatabaseInboxFiles(); 125 List<MailEntry> receivedMailEncrypted = loadMailFromDatabases(databasePaths); 126 List<MailEntry> receivedMail = decryptMailEntries(receivedMailEncrypted, key); 127 // TODO: delete processed messages from database files 128 touchLastAccessedFiles(databasePaths); 129 return receivedMail; 130 } 131 132 public class MailEntry { 133 private static final String SINGLE_USE_SENDER_TAG = "="; 134 private String timestamp; 135 private String sender; 136 private String receiver; 137 private String subject; 138 private String message; 139 private Map<String, String> options; 140 private Path source; 141 142 public MailEntry(String timestamp, String sender, String receiver, String subject, String message, 143 Map<String, String> options) { 144 this.timestamp = timestamp; 145 this.sender = sender; 146 this.receiver = receiver; 147 this.subject = subject; 148 this.message = message; 149 this.options = options; 150 } 151 152 //@formatter:off 153 public String getTimestamp() { return timestamp; } 154 public String getSender() { return sender; } 155 public String getReceiver() { return receiver; } 156 public String getSubject() { return subject; } 157 public String getMessage() { return message; } 158 public Map<String, String> getOptionsTextActionMap() { return options; } 159 public Path getDatabaseSource() { return source; } 160 public void setDatabaseSource(Path source) { this.source = source; } 161 public boolean isSingleUseEncryption() { 162 return sender.startsWith(MailEntry.SINGLE_USE_SENDER_TAG); 163 } 164 //@formatter:on 165 166 private MailEntry encryptMailEntry(Key key, Supplier<Cipher> generateCipher, String markSender) { 167 if (markSender == null) 168 markSender = ""; 169 try { 170 // Encrypt sender, receiver, subject and message 171 Cipher cipher = generateCipher.get(); 172 cipher.init(Cipher.ENCRYPT_MODE, key); 173 String sender = markSender + Base64.getEncoder().encodeToString(cipher.doFinal(getSender().toLowerCase().getBytes())); 174 cipher.init(Cipher.ENCRYPT_MODE, key); 175 String receiver = Base64.getEncoder().encodeToString(cipher.doFinal(getReceiver().getBytes())); 176 cipher.init(Cipher.ENCRYPT_MODE, key); 177 String subject = Base64.getEncoder().encodeToString(cipher.doFinal(getSubject().getBytes())); 178 cipher.init(Cipher.ENCRYPT_MODE, key); 179 String message = Base64.getEncoder().encodeToString(cipher.doFinal(getMessage().getBytes())); 180 181 // Encrypt reply options to be provided to receiver 182 Map<String, String> optionsTextActionMap = new HashMap<String, String>(); 183 Map<String, String> toEncOptionsTextActionMap = getOptionsTextActionMap(); 184 for (String text : toEncOptionsTextActionMap.keySet()) { 185 cipher.init(Cipher.ENCRYPT_MODE, key); 186 String textEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(text.getBytes())); 187 cipher.init(Cipher.ENCRYPT_MODE, key); 188 String toEncAction = toEncOptionsTextActionMap.get(text); 189 String actionEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(toEncAction.getBytes())); 190 optionsTextActionMap.put(textEncrypted, actionEncrypted); 191 } 192 193 return new MailEntry(getTimestamp(), sender, receiver, subject, message, optionsTextActionMap); 194 } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { 195 e.printStackTrace(); 196 return null; 197 } 198 } 199 200 private void writeToMailDatabase(Path databaseFile) { 201 try { 202 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile); 203 String sql = "INSERT INTO EXPMAIL (TIME,SND,REC,SUB,MSG,OPTS,OPTSVAL) VALUES (?, ?, ?, ?, ?, ?, ?);"; 204 PreparedStatement statement = c.prepareStatement(sql); 205 statement.setString(1, getTimestamp()); 206 statement.setString(2, getSender()); 207 statement.setString(3, getReceiver()); 208 statement.setString(4, getSubject()); 209 statement.setString(5, message); 210 String opts = Arrays.toString(getOptionsTextActionMap().keySet().toArray()); 211 statement.setString(6, opts); 212 String optsVal = Arrays.toString(getOptionsTextActionMap().values().toArray()); 213 statement.setString(7, optsVal); 214 statement.execute(); 215 statement.close(); 216 } catch (SQLException e) { 217 e.printStackTrace(); 218 } 219 } 220 221 private MailEntry decryptMailEntry(Key key, Supplier<Cipher> generateCipher, String senderMarkFilter) { 222 Cipher cipher = generateCipher.get(); 223 try { 224 // Confirm this is a message is intended for this recipient 225 cipher.init(Cipher.DECRYPT_MODE, key); 226 byte[] receiverBytes = Base64.getDecoder().decode(getReceiver()); 227 String receiver = new String(cipher.doFinal(receiverBytes)); 228 if (!receiver.toLowerCase().equals(user)) { 229 return null; 230 } 231 232 // Decrypt remainder of message 233 cipher.init(Cipher.DECRYPT_MODE, key); 234 byte[] senderBytes = Base64.getDecoder().decode(getSender()); 235 String sender = new String(cipher.doFinal(senderBytes)); 236 cipher.init(Cipher.DECRYPT_MODE, key); 237 byte[] subjectBytes = Base64.getDecoder().decode(getSubject()); 238 String subject = new String(cipher.doFinal(subjectBytes)); 239 240 cipher.init(Cipher.DECRYPT_MODE, key); 241 byte[] messageBytes = Base64.getDecoder().decode(getMessage()); 242 String message = new String(cipher.doFinal(messageBytes)); 243 244 Map<String, String> options = new HashMap<String, String>(); 245 for (String text : getOptionsTextActionMap().keySet()) { 246 cipher.init(Cipher.DECRYPT_MODE, key); 247 byte[] textBytes = Base64.getDecoder().decode(text); 248 String k = new String(cipher.doFinal(textBytes)); 249 cipher.init(Cipher.DECRYPT_MODE, key); 250 byte[] actionBytes = Base64.getDecoder().decode(getOptionsTextActionMap().get(text)); 251 String v = new String(cipher.doFinal(actionBytes)); 252 options.put(k, v); 253 } 254 255 return new MailEntry(getTimestamp(), sender, receiver, subject, message, options); 256 } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) { 257 e.printStackTrace(); 258 return null; 259 } 260 } 261 262 public String toString() { 263 StringBuilder sb = new StringBuilder("MailEntry { "); 264 265 sb.append("[timestamp: " + getTimestamp() + "], "); 266 sb.append("[sender: " + getSender() + "], "); 267 sb.append("[receiver: " + getReceiver() + "], "); 268 sb.append("[subject: " + getSubject() + "], "); 269 sb.append("[message: " + getMessage() + "], "); 270 271 sb.append(" }"); 272 return sb.toString(); 273 } 274 } 275 276 private Path getDatabaseFilePath(String colleagueName) { 277 return getDatabaseFilePath(colleagueName, false); 278 } 279 280 private Path getDatabaseFilePath(String colleagueName, boolean demoMode) { 281 String deadDropsPath = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + user).resolve("deaddrops").toAbsolutePath().toString();//FrameIO.DEAD_DROPS_PATH; 103 282 104 // Send message 105 sendMail(mail, publicKey, databaseFilePath); 283 // if (deadDropsPath == null) { 284 // deadDropsPath = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + colleagueName).resolve("deadrops").toAbsolutePath().toString(); 285 // } else if (demoMode) { 286 // deadDropsPath = deadDropsPath.replace(user, colleagueName); 287 // } 106 288 107 // Do it all again if demo mode is on (ensures sync without having to move db files) 108 if (Boolean.getBoolean("expeditee.demo-mode")) { 109 databaseFileDirPath = ensureDeadDropsDemoMode(colleagueName); 110 databaseFilePath = ensureDatabaseFile(colleagueName, databaseFileDirPath); 111 sendMail(mail, publicKey, databaseFilePath); 112 } 113 } 114 115 private static Path ensureDatabaseFile(String colleagueName, Path databaseFileDirPath) { 116 Path databaseFilePath = databaseFileDirPath.resolve(colleagueName + ".db"); 117 File databaseFile = databaseFilePath.toFile(); 118 if (!databaseFile.exists()) { 119 databaseFileDirPath.toFile().mkdirs(); 120 String sql = 121 "CREATE TABLE EXPMAIL (" + 122 "TIME TEXT NOT NULL, " + 123 "SND TEXT NOT NULL, " + 124 "REC TEXT NOT NULL, " + 125 "MSG TEXT NOT NULL, " + 126 "MSG2 TEXT NOT NULL, " + 127 "OPTS ARRAY NOT NULL, " + 128 "OPTSVAL ARRAY NOT NULL)"; 129 try { 130 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath()); 131 Statement createTable = c.createStatement(); 132 createTable.executeUpdate(sql); 133 createTable.close(); 134 c.close(); 289 if (demoMode) { 290 deadDropsPath = deadDropsPath.replace(user, colleagueName); 291 } 292 293 Path databaseFileDirectoryPath = Paths.get(deadDropsPath).resolve(user + "+" + colleagueName); 294 if (!databaseFileDirectoryPath.toFile().exists()) { 295 databaseFileDirectoryPath = Paths.get(deadDropsPath).resolve(colleagueName + "+" + user); 296 } 297 298 if (!databaseFileDirectoryPath.toFile().exists()) { 299 databaseFileDirectoryPath.toFile().mkdirs(); 300 } 301 302 Path databaseFilePath = databaseFileDirectoryPath.resolve(colleagueName + ".db"); 303 if (!databaseFilePath.toFile().exists()) { 304 createMailDatabaseFile(databaseFilePath); 305 } 306 return databaseFilePath; 307 } 308 309 private void createMailDatabaseFile(Path databaseFilePath) { 310 databaseFilePath.getParent().toFile().mkdir(); 311 //@formatter:off 312 String sql = 313 "CREATE TABLE EXPMAIL (" + 314 "TIME TEXT NOT NULL, " + 315 "SND TEXT NOT NULL, " + 316 "REC TEXT NOT NULL, " + 317 "SUB TEXT NOT NULL, " + 318 "MSG TEXT NOT NULL, " + 319 "OPTS ARRAY NOT NULL, " + 320 "OPTSVAL ARRAY NOT NULL)"; 321 //@formatter:on 322 try (Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFilePath.toAbsolutePath())) { 323 Statement createTable = c.createStatement(); 324 createTable.executeUpdate(sql); 325 createTable.close(); 326 } catch (SQLException e) { 327 System.err.println("Error while creating database file."); 328 e.printStackTrace(); 329 } 330 } 331 332 private PrivateKey getPrivateKey() { 333 Text keyItem = KeyList.PrivateKey.get(); 334 String noKeyMessage = "No private key present: your communication with other Expeditee users will be limited until this is resolved."; 335 if (keyItem.getData() != null) { 336 // Check mail. 337 String keyEncoded = keyItem.getData().get(0); 338 byte[] keyBytes = Base64.getDecoder().decode(keyEncoded); 339 try { 340 return KeyFactory.getInstance(AsymmetricAlgorithm).generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); 341 } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { 342 MessageBay.errorMessage(noKeyMessage); 343 MessageBay.errorMessage("See exception stack trace for more information."); 344 e.printStackTrace(); 345 return null; 346 } 347 } else { 348 MessageBay.errorMessage(noKeyMessage); 349 return null; 350 } 351 } 352 353 private List<Path> getDatabaseInboxFiles() { 354 List<Path> databaseFiles = new ArrayList<Path>(); 355 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH); 356 File deadDropDirectory = deadDropPath.toFile(); 357 if (!deadDropDirectory.exists()) { return databaseFiles; } 358 for (File partnershipDirectoryCanditate : deadDropDirectory.listFiles()) { 359 if (partnershipDirectoryCanditate.isDirectory()) { 360 Path partnershipDirectory = Paths.get(partnershipDirectoryCanditate.getAbsolutePath()); 361 Path dbFile = partnershipDirectory.resolve(user + ".db"); 362 if (dbFile.toFile().exists()) { 363 databaseFiles.add(dbFile); 364 } 365 } 366 } 367 return databaseFiles; 368 } 369 370 private List<MailEntry> loadMailFromDatabases(List<Path> databasePaths) { 371 List<MailEntry> mail = new ArrayList<MailEntry>(); 372 373 for (Path dbPath : databasePaths) { 374 try { 375 mail.addAll(loadMailFromDatabase(dbPath)); 135 376 } catch (SQLException e) { 136 System.err.println("Error while creating database file."); 137 e.printStackTrace(); 138 } 139 } 140 return databaseFilePath; 141 } 142 143 private static Path ensureDeadDrops(String colleagueName) { 144 String me = UserSettings.UserName.get().toLowerCase(); 145 String them = colleagueName.toLowerCase(); 146 Path databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(me + "+" + them); 147 if (!databaseFileDirPath.toFile().exists()) { 148 databaseFileDirPath = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(them + "+" + me); 149 } 150 return databaseFileDirPath; 151 } 152 153 private static Path ensureDeadDropsDemoMode(String colleagueName) { 154 String me = UserSettings.UserName.get().toLowerCase(); 155 String them = colleagueName.toLowerCase(); 156 String demoModeDeadDropsPath = FrameIO.DEAD_DROPS_PATH.replace(me, them); 157 Path databaseFileDirPath = Paths.get(demoModeDeadDropsPath).resolve(me + "+" + them); 158 if (!databaseFileDirPath.toFile().exists()) { 159 databaseFileDirPath = Paths.get(demoModeDeadDropsPath).resolve(them + "+" + me); 160 } 161 return databaseFileDirPath; 162 } 163 164 private static Path ensureDeadDrops(String colleagueName, String sender) { 165 String me = sender.toLowerCase(); 166 String them = colleagueName.toLowerCase(); 167 Path parent = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + sender).resolve("deaddrops"); 168 Path databaseFileDirPath = parent.resolve(me + "+" + them); 169 if (!databaseFileDirPath.toFile().exists()) { 170 databaseFileDirPath = parent.resolve(them + "+" + me); 171 } 172 return databaseFileDirPath; 173 } 174 175 private static Path ensureDeadDropsDemoMode(String colleagueName, String sender) { 176 String me = sender.toLowerCase(); 177 String them = colleagueName.toLowerCase(); 178 Path parent = Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + them).resolve("deaddrops"); 179 Path databaseFileDirPath = parent.resolve(me + "+" + them); 180 if (!databaseFileDirPath.toFile().exists()) { 181 databaseFileDirPath = parent.resolve(them + "+" + me); 182 } 183 return databaseFileDirPath; 184 } 185 186 private static void sendMail(MailEntry mail, PublicKey key, Path databaseFile) { 187 try { 188 Cipher cipher = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters); 189 cipher.init(Cipher.ENCRYPT_MODE, key); 190 String sender = Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes())); 191 cipher.init(Cipher.ENCRYPT_MODE, key); 192 String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes())); 193 cipher.init(Cipher.ENCRYPT_MODE, key); 194 String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes())); 195 cipher.init(Cipher.ENCRYPT_MODE, key); 196 String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes())); 377 e.printStackTrace(); 378 } 379 } 380 381 return mail; 382 } 383 384 private List<MailEntry> loadMailFromDatabase(Path dbPath) throws SQLException { 385 List<MailEntry> mail = new ArrayList<MailEntry>(); 386 387 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toAbsolutePath()); 388 String sql = "SELECT * FROM EXPMAIL"; 389 PreparedStatement query = c.prepareStatement(sql); 390 ResultSet results = query.executeQuery(); 391 392 while (results.next()) { 393 String timestamp = results.getString("TIME"); 197 394 198 Map<String, String> options = new HashMap<String, String>(); 199 for (String label: mail.options.keySet()) { 200 cipher.init(Cipher.ENCRYPT_MODE, key); 201 String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes())); 202 cipher.init(Cipher.ENCRYPT_MODE, key); 203 String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes())); 204 options.put(labelEncrypted, actionNameEncrypted); 395 Path lastAccessedPath = dbPath.getParent().resolve(user + ".last-accessed"); 396 try (Scanner in = new Scanner(lastAccessedPath.toFile())) { 397 Date lastAccessedTimestamp = FORMAT.parse(in.nextLine()); 398 Date mailTimestamp = FORMAT.parse(timestamp); 399 if (mailTimestamp.before(lastAccessedTimestamp)) { 400 continue; 401 } 402 } catch (FileNotFoundException | ParseException e) { 403 // It may not have been created yet, then err on the safe side and add it in. 404 // If it fails to parse then we again err on the safe side. 405 } catch (RuntimeException e) { 406 e.printStackTrace(); 407 continue; 205 408 } 206 409 207 // write to mail database 208 writeToMailDatabase(mail, databaseFile, sender, rec, message, message2, options); 209 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) { 210 e.printStackTrace(); 211 } 212 } 213 214 private static void sendMail(MailEntry mail, SecretKey key, Path databaseFile) { 215 try { 216 // Encrypt message. 217 Cipher cipher = Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters); 218 cipher.init(Cipher.ENCRYPT_MODE, key); 219 String sender = "=" + Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes())); 220 cipher.init(Cipher.ENCRYPT_MODE, key); 221 String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes())); 222 cipher.init(Cipher.ENCRYPT_MODE, key); 223 String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes())); 224 cipher.init(Cipher.ENCRYPT_MODE, key); 225 String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes())); 226 Map<String, String> options = new HashMap<String, String>(); 227 for (String label: mail.options.keySet()) { 228 cipher.init(Cipher.ENCRYPT_MODE, key); 229 String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes())); 230 cipher.init(Cipher.ENCRYPT_MODE, key); 231 String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes())); 232 options.put(labelEncrypted, actionNameEncrypted); 233 } 234 235 // Write to mail database. 236 writeToMailDatabase(mail, databaseFile, sender, rec, message, message2, options); 237 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) { 238 e.printStackTrace(); 239 } 240 } 241 242 private static void writeToMailDatabase(MailEntry mail, Path databaseFile, String sender, String rec, 243 String message, String message2, Map<String, String> options) throws SQLException { 244 Connection c = DriverManager.getConnection("jdbc:sqlite:" + databaseFile); 245 String sql = "INSERT INTO EXPMAIL (TIME,SND,REC,MSG,MSG2,OPTS,OPTSVAL) VALUES (?, ?, ?, ?, ?, ?, ?);"; 246 PreparedStatement statement = c.prepareStatement(sql); 247 statement.setString(1, mail.timestamp); 248 statement.setString(2, sender); 249 statement.setString(3, rec); 250 statement.setString(4, message); 251 statement.setString(5, message2); 252 String opts = Arrays.toString(options.keySet().toArray()); 253 statement.setString(6, opts); 254 String optsval = Arrays.toString(options.values().toArray()); 255 statement.setString(7, optsval); 256 statement.execute(); 257 statement.close(); 410 String from = results.getString("SND"); 411 String to = results.getString("REC"); 412 String subject = results.getString("SUB"); 413 String message = results.getString("MSG"); 414 String[] opts = results.getString("opts").split(","); 415 opts[0] = opts[0].replace("[", ""); 416 opts[opts.length - 1] = opts[opts.length - 1].replace("]", ""); 417 String[] optsVal = results.getString("optsval").split(","); 418 optsVal[0] = optsVal[0].replace("[", ""); 419 optsVal[optsVal.length - 1] = optsVal[optsVal.length - 1].replace("]", ""); 420 Map<String, String> options = new HashMap<String, String>(); 421 for (int i = 0; i < opts.length; i++) { 422 String key = opts[i].trim(); 423 String val = optsVal[i].trim(); 424 options.put(key, val); 425 } 426 427 MailEntry mailEntry = new MailEntry(timestamp, from, to, subject, message, options); 428 mailEntry.setDatabaseSource(dbPath); 429 mail.add(mailEntry); 430 } 431 432 results.close(); 433 query.close(); 258 434 c.close(); 259 System.err.println("Message written to database: " + databaseFile.toString()); 260 } 261 262 /** 263 * Gets the mail messages that the specified user is able to read. 264 * The caller supplies their username and private key. 265 * If the private key can decrypt a message, then it was encrypted using the users public key, and is therefore for them. 266 */ 267 private static List<MailEntry> getEntries(String name, PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { 268 List<MailEntry> filtered = new ArrayList<MailEntry>(); 269 270 for (MailEntry mail: messages) { 271 if (isEncryptedOneOffMessage(mail)) { 272 getOneOffSecureMail(name, filtered, mail); 435 436 return mail; 437 } 438 439 private List<MailEntry> decryptMailEntries(List<MailEntry> encryptedMailMessages, PrivateKey key) { 440 List<MailEntry> mail = new ArrayList<MailEntry>(); 441 442 for (MailEntry encMail : encryptedMailMessages) { 443 if (encMail.isSingleUseEncryption()) { 444 MailEntry oneOffSecureSubstitute = generateOneOffSecureSubstitute(encMail); 445 if (oneOffSecureSubstitute != null) { 446 mail.add(oneOffSecureSubstitute); 447 } 273 448 } else { 274 getStandardSecureMail(name, key, filtered, mail); 275 } 276 } 277 278 return filtered; 279 } 280 281 private static void getOneOffSecureMail(String name, List<MailEntry> filtered, MailEntry mail) { 449 MailEntry decryptMailEntry = encMail.decryptMailEntry(key, () -> { 450 try { 451 return Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters); 452 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { 453 e.printStackTrace(); 454 return null; 455 } 456 }, ""); 457 if (decryptMailEntry != null) { 458 mail.add(decryptMailEntry); 459 } 460 } 461 } 462 463 return mail; 464 } 465 466 private void touchLastAccessedFiles(List<Path> databasePaths) { 467 for (Path databasePath : databasePaths) { 468 Path lastAccessedFilePath = databasePath.getParent().resolve(user + ".last-accessed"); 469 try (FileWriter out = new FileWriter(lastAccessedFilePath.toFile())) { 470 out.write(Mail.FORMAT.format(new Date()) + System.getProperty("line.separator")); 471 } catch (IOException e) { 472 e.printStackTrace(); 473 } 474 } 475 } 476 477 private MailEntry generateOneOffSecureSubstitute(MailEntry toWrap) { 282 478 StringBuilder sb = new StringBuilder(); 283 479 String sep = ":::"; 284 480 sb.append("Read one-off secure message." + sep); 285 sb.append( mail.timestamp + sep);286 sb.append( mail.sender.substring(1) + sep);287 sb.append( mail.receiver + sep);288 sb.append( mail.message+ sep);289 sb.append( mail.message2+ sep);290 for (String k: mail.options.keySet()) {291 sb.append(k + sep + mail.options.get(k) + sep);481 sb.append(toWrap.timestamp + sep); 482 sb.append(toWrap.sender.substring(1) + sep); 483 sb.append(toWrap.receiver + sep); 484 sb.append(toWrap.subject + sep); 485 sb.append(toWrap.message + sep); 486 for (String k: toWrap.options.keySet()) { 487 sb.append(k + sep + toWrap.options.get(k) + sep); 292 488 } 293 489 sb.reverse().delete(0, 3).reverse(); 294 490 Map<String, String> options = new HashMap<String, String>(); 295 491 options.put(sb.toString(), "AuthOneOffSecureMessage"); 296 String currentTime = org.expeditee.stats.Formatter.getDateTime(); 297 MailEntry mailOuter = new MailEntry(currentTime, "Unknown", name, "You have received a one-off secure message.", "Check your email for more details.", options); 298 299 if (mail.deadDropSource != null) { 300 Path lastAccessedFile = mail.deadDropSource.getParent().resolve(name + ".last-accessed"); 301 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]"); 302 try (Scanner in = new Scanner(lastAccessedFile.toFile())) { 303 Date lastAccessedTimestamp = format.parse(in.nextLine()); 304 Date mailTimestamp = format.parse(mail.timestamp); 305 if (mailTimestamp.after(lastAccessedTimestamp)) { 306 filtered.add(mailOuter); 307 } 308 } catch (FileNotFoundException e) { 309 // It may not have been created yet, then err on the safe side and add it in. 310 filtered.add(mailOuter); 311 } catch (ParseException e) { 312 // If we fail to parse, then err on the safe side and add it in. 313 filtered.add(mailOuter); 314 } 315 } 316 } 317 318 private static void getStandardSecureMail(String name, PrivateKey key, List<MailEntry> filtered, MailEntry mail) 319 throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeyException, 320 BadPaddingException { 321 // confirm this is a message for the requester of entries 322 String receiver = mail.receiver; 323 byte[] receiverBytes = Base64.getDecoder().decode(receiver); 324 String receiverDecrypted = null; 325 Cipher c = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters); 326 try { 327 c.init(Cipher.DECRYPT_MODE, key); 328 receiverDecrypted = new String(c.doFinal(receiverBytes)); 329 } catch (InvalidKeyException | BadPaddingException e) { 330 return; 331 } 332 333 // add an unencrypted version of the message to the return list 334 if (receiverDecrypted.compareToIgnoreCase(name) == 0) { 335 c.init(Cipher.DECRYPT_MODE, key); 336 String sender = new String(c.doFinal(Base64.getDecoder().decode(mail.sender))); 337 c.init(Cipher.DECRYPT_MODE, key); 338 String message = new String(c.doFinal(Base64.getDecoder().decode(mail.message))); 339 c.init(Cipher.DECRYPT_MODE, key); 340 String message2 = new String(c.doFinal(Base64.getDecoder().decode(mail.message2))); 341 342 Map<String, String> options = new HashMap<String, String>(); 343 for (String label: mail.options.keySet()) { 344 c.init(Cipher.DECRYPT_MODE, key); 345 String labelDecrypted = new String(c.doFinal(Base64.getDecoder().decode(label))); 346 c.init(Cipher.DECRYPT_MODE, key); 347 String actionNameDecrypted = new String(c.doFinal(Base64.getDecoder().decode(mail.options.get(label)))); 348 options.put(labelDecrypted, actionNameDecrypted); 349 } 350 351 Path lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(UserSettings.UserName.get() + "+" + sender).resolve(name + ".last-accessed"); 352 if (!lastAccessedFile.toFile().exists()) { 353 lastAccessedFile = Paths.get(FrameIO.DEAD_DROPS_PATH).resolve(sender + "+" + UserSettings.UserName.get()).resolve(name + ".last-accessed"); 354 } 355 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]"); 356 MailEntry mailEntry = new MailEntry(mail.timestamp, sender, receiverDecrypted, message, message2, options); 357 try (Scanner in = new Scanner(lastAccessedFile.toFile())) { 358 Date lastAccessedTimestamp = format.parse(in.nextLine()); 359 Date mailTimestamp = format.parse(mail.timestamp); 360 if (mailTimestamp.after(lastAccessedTimestamp)) { 361 filtered.add(mailEntry); 362 } 363 } catch (FileNotFoundException e) { 364 // It may not have been created yet, then err on the safe side and add it in. 365 filtered.add(mailEntry); 366 } catch (ParseException e) { 367 // If we fail to parse, then err on the safe side and add it in. 368 filtered.add(mailEntry); 369 } 370 371 } 372 } 373 374 private static boolean isEncryptedOneOffMessage(MailEntry mail) { 375 return mail.sender.charAt(0) == '='; 376 } 377 378 /** 379 * Describes a piece of mail, either encrypted or decrypted. 380 */ 381 public static class MailEntry { 382 public String timestamp; 383 public String sender; 384 public String receiver; 385 public String message; 386 public String message2; 387 public Map<String, String> options; 388 public Path deadDropSource; 389 390 public MailEntry(String timestamp, String sender, String rec, String message, String message2, Map<String, String> options) { 391 this.timestamp = timestamp; 392 this.sender = sender; 393 this.receiver = rec; 394 this.message = message; 395 this.message2 = message2; 396 this.options = options; 397 } 398 } 399 400 public static void checkMail(PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, 401 IllegalBlockSizeException, BadPaddingException, InvalidKeyException, KeyStoreException, 402 FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException, ParseException { 403 MailBay.clear(); 404 AuthenticatorBrowser.getInstance().loadMailDatabase(); 405 List<MailEntry> mailForLoggingInUser = Mail.getEntries(UserSettings.UserName.get(), key); 406 for (MailEntry mail: mailForLoggingInUser) { 407 MailBay.addMessage(mail.timestamp, mail.sender, mail.message, mail.message2, mail.options); 408 } 409 410 // Update last read files. 411 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH); 412 for (File connectionDir: deadDropPath.toFile().listFiles()) { 413 if (connectionDir.isDirectory()) { 414 Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath()); 415 AuthenticatorBrowser.getInstance().updateLastReadMailTime(deaddropforcontactPath); 416 } 417 } 418 } 419 420 public static void decryptOneOffSecureMessage(SecretKey key, List<String> data) { 421 byte[] topicBytes = Base64.getDecoder().decode(data.get(3)); 422 String topic = new String(org.expeditee.encryption.Actions.DecryptSymmetric(topicBytes, key)); 423 byte[] messageBytes = Base64.getDecoder().decode(data.get(4)); 424 String message = new String(org.expeditee.encryption.Actions.DecryptSymmetric(messageBytes, key)); 425 Map<String, String> options = new HashMap<String, String>(); 426 for (int i = 5; i < data.size(); i+=2) { 427 byte[] optionKeyBytes = Base64.getDecoder().decode(data.get(i)); 428 String k = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionKeyBytes, key)); 429 byte[] optionValueBytes = Base64.getDecoder().decode(data.get(i + 1)); 430 String v = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionValueBytes, key)); 431 options.put(k, v); 432 } 433 MailBay.addMessage(data.get(0), "Single-use Secure (encrypted sender)", topic, message, options); 492 MailEntry mailOuter = new MailEntry(toWrap.timestamp, "Unknown", user, "You have received a one-off secure message.", 493 "Check your email for more details.", options); 494 return mailOuter; 495 } 496 497 public String getUser() { 498 return user; 434 499 } 435 500 } -
trunk/src/org/expeditee/auth/mail/gui/MailBay.java
r1376 r1504 4 4 import java.nio.file.Path; 5 5 import java.nio.file.Paths; 6 import java.util.LinkedList; 6 import java.util.ArrayList; 7 import java.util.Base64; 8 import java.util.HashMap; 7 9 import java.util.List; 8 10 import java.util.Map; 9 11 import java.util.Scanner; 10 12 13 import javax.crypto.SecretKey; 14 11 15 import org.expeditee.auth.AuthenticatorBrowser; 16 import org.expeditee.auth.mail.Mail; 17 import org.expeditee.auth.mail.Mail.MailEntry; 12 18 import org.expeditee.core.Clip; 13 19 import org.expeditee.core.Colour; … … 15 21 import org.expeditee.core.Font; 16 22 import org.expeditee.core.Image; 23 import org.expeditee.encryption.Actions; 17 24 import org.expeditee.gio.EcosystemManager; 18 25 import org.expeditee.gio.GraphicsManager; … … 24 31 import org.expeditee.items.Item; 25 32 import org.expeditee.items.Text; 26 import org.expeditee.settings.UserSettings;27 33 28 34 public class MailBay { 35 // Frameset name for mail 29 36 public static final String EXPEDITEE_MAIL_FRAMESET_NAME = "expediteemail"; 30 31 / ** The y position of the header to the mail bay. */32 private static final int HEADER_OFFSET_Y = 15;33 34 / ** Space between mail messages */37 38 // Font used for messages in the MailBay 39 private static final Font MESSAGE_FONT = new Font("Serif-Plain-16"); 40 41 // Bay preview area constants. 35 42 private static final int SPACING = 25; 36 37 /** The (x,y) of the top message. */ 38 private static final int MESSAGE_OFFSET_Y = 15 + SPACING; 43 private static final int OFFSET_Y = 15; 39 44 private static final int OFFSET_X = 20; 40 41 /** Buffer image of the mail window. */ 42 private static Image _mailBuffer; 43 44 /** The list of messages shown in the mail bay. */ 45 private static List<Item> _previewMessages = new LinkedList<Item>(); 46 private static List<Item> _messages = new LinkedList<Item>(); 47 48 /** Font used for mail messages. */ 49 private static Font _messageFont = new Font("Serif-Plain-16"); 50 51 /** Used to number messages. */ 52 private static int _messageCount; 53 54 /** Creator for creating the mail frames. */ 55 private static FrameCreator _creator = null; 56 57 /** The currently logged in user, consulted when deciding if a new FrameCreator is needed. */ 58 private static String _forUser = UserSettings.UserName.get(); 59 60 /** The link that the preview pane displays pointing towards unprocessed messages. */ 61 private static Text _mailLink = new Text(-2, "@Mail", Colour.BLACK, Colour.WHITE); 62 63 /** Wether the link has been drawn before. */ 64 private static boolean isLinkInitialized = false; 65 66 /** The position of the mail link. */ 67 private static int MAIL_LINK_Y_OFFSET = 100; 68 private static int MAIL_LINK_X = 50; 69 70 /** 71 * Obtain the two messages shown at the bottom of the screen as a preview to the entire collection of mail. 72 * @return 73 */ 74 public static List<Item> getPreviewMessages() { 75 return _previewMessages; 76 } 77 78 /** 79 * An item is a 'preview mail item' if it is currently being previewed at the bottom of the screen. 80 * @param i 81 * @return 82 */ 83 public static boolean isPreviewMailItem(Item i) { 84 return _previewMessages.contains(i) || i == _mailLink; 85 } 86 87 public static void disconnect() { 88 if (_forUser != UserSettings.UserName.get()) { 89 _creator = null; 90 _forUser = UserSettings.UserName.get(); 91 } 92 } 93 94 /** 95 * Adds a message to the MailBay. 96 * @param message The basic text of the message, what is displayed in the bay window down the bottom. 97 * @options A map describing the buttons to be provided as reply options (Button Text, Action to run) 98 * @return 99 */ 100 public synchronized static Text addMessage(String timestamp, String sender, String message, String message2, Map<String, String> options) { 45 46 private static final int MAIL_LINK_X = 50; 47 48 private static List<Item> previewMessages = new ArrayList<Item>(); 49 private static FrameCreator creator; 50 private static Mail mail; 51 private static Text mailLink; 52 private static Text checkMailAction; 53 54 private static Image mailBufferImage; 55 56 public static Mail getMailClient() { 57 return mail; 58 } 59 60 public static Mail getMailClient(String user) { 61 boolean restore = mail != null; 62 String oldUser = restore ? mail.getUser() : null; 63 MailBay.reconnectToUser(user); 64 Mail ret = mail; 65 if (restore) { 66 MailBay.reconnectToUser(oldUser); 67 } 68 return ret; 69 } 70 71 public static List<Item> getPreviewMessages() { 72 return previewMessages; 73 } 74 75 public static boolean isPreviewMailItem(Item i) { 76 return previewMessages.contains(i) || i == getMailLink() || i == checkMailAction; 77 } 78 79 public static Image getImage(Clip clip, Dimension size) { 80 // Can't get an image with an invalid size 81 if (size == null || size.width <= 0 || size.height <= 0) { 82 return null; 83 } 84 85 // Update the buffer 86 updateBuffer(Item.DEFAULT_BACKGROUND, clip, size); 87 88 // Return the image buffer 89 return mailBufferImage; 90 } 91 92 public static Item getMailLink() { 93 return mailLink; 94 } 95 96 public static Item getCheckMailAction() { 97 return checkMailAction; 98 } 99 100 public static void reconnectToUser(String user) { 101 mail = new Mail(user); 102 creator = new FrameCreator(EXPEDITEE_MAIL_FRAMESET_NAME, FrameIO.MAIL_PATH, 103 EXPEDITEE_MAIL_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.AppendAfterLastItem, 104 false, AuthenticatorBrowser.PROFILEENCRYPTIONLABEL); 105 updateLink(); 106 } 107 108 public static void checkMail() { 101 109 // Invalidate whole area 102 110 DisplayController.invalidateArea(DisplayController.getMessageBayPaintArea()); 103 104 // Ensure frame creator 105 if (_creator == null || _forUser != UserSettings.UserName.get()) { 106 _forUser = UserSettings.UserName.get(); 107 _creator = new FrameCreator(EXPEDITEE_MAIL_FRAMESET_NAME, FrameIO.MAIL_PATH, EXPEDITEE_MAIL_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.AppendAfterLastItem, false, AuthenticatorBrowser.PROFILEENCRYPTIONLABEL); 108 } 109 110 // We have enough space for the header + 2 preview messages 111 if (_previewMessages.size() >= 2) { 112 _previewMessages.remove(0); 113 for (Item i : _previewMessages) { 114 i.setY(i.getY() - SPACING); 115 } 116 } 117 118 // Add new message 119 Mail mail = new Mail(message, message2, options); 120 Text t = mail.getPreviewMessage(true); 121 _messages.add(t); 122 Text header = _creator.addText(timestamp + " (From: " + sender + ")", Colour.BLACK, null, null, false); 111 previewMessages.clear(); 112 113 // Get new mail since last time we checked for mail 114 List<MailEntry> newMail = mail.checkMail(); 115 116 // Place up to two previews 117 Map<String, Integer> countBySender = countBySender(newMail); 118 String[] senders = countBySender.keySet().toArray(new String[] {}); 119 for (int i = 0; i < senders.length && i < 3; i++) { 120 String sender = senders[i]; 121 Text text = new Text("You have received " + countBySender.get(sender) + " new messages from " + sender); 122 text.setFont(MESSAGE_FONT); 123 text.setPosition(MAIL_LINK_X, OFFSET_Y + (SPACING * (i + 1))); 124 previewMessages.add(text); 125 } 126 127 // Add new mail to creator 128 for (MailEntry mail: newMail) { 129 String timestamp = mail.getTimestamp(); 130 String sender = mail.getSender(); 131 generateMailHeader(timestamp, sender); 132 creator.addText("Subject: " + mail.getSubject(), Colour.BLACK, null, null, false).setFont(MESSAGE_FONT); 133 creator.addText(mail.getMessage(), Colour.BLACK, null, null, false).setFont(MESSAGE_FONT); 134 Map<String, String> optionsTextActionMap = mail.getOptionsTextActionMap(); 135 String[] optionsTextActionMapKeyset = optionsTextActionMap.keySet().toArray(new String[] {}); 136 for (int i = 0; i < optionsTextActionMapKeyset.length; i++) { 137 String text = optionsTextActionMapKeyset[i]; 138 String[] split = text.split(":::"); 139 String action = optionsTextActionMap.get(text); 140 Text t = new Text(split[0]); 141 for (int o = 1; o < split.length; o++) { 142 t.addToData(split[o]); 143 } 144 int y = OFFSET_Y + (previewMessages.size() + i) * SPACING; 145 t.setPosition(OFFSET_X, y); 146 t.setColor(Colour.BLUE); 147 t.setFont(MESSAGE_FONT); 148 t.setAction(action); 149 creator.addItem(t, false); 150 } 151 } 152 153 DisplayController.requestRefresh(true); 154 } 155 156 public static void decryptOneOffSecureMessage(SecretKey key, List<String> data) { 157 byte[] subjectBytes = Base64.getDecoder().decode(data.get(3)); 158 String subject = new String(Actions.DecryptSymmetric(subjectBytes, key)); 159 byte[] messageBytes = Base64.getDecoder().decode(data.get(4)); 160 String message = new String(Actions.DecryptSymmetric(messageBytes, key)); 161 162 creator.addText(data.get(0) + " (Single-use encrypted message)", Colour.BLACK, null, null, false); 163 creator.addText(subject, Colour.BLACK, null, null, false).setFont(MESSAGE_FONT); 164 creator.addText(message, Colour.BLACK, null, null, false).setFont(MESSAGE_FONT); 165 166 for (int i = 5; i < data.size(); i += 2) { 167 byte[] optionKeyBytes = Base64.getDecoder().decode(data.get(i)); 168 String k = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionKeyBytes, key)); 169 byte[] optionValueBytes = Base64.getDecoder().decode(data.get(i + 1)); 170 String v = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionValueBytes, key)); 171 creator.addText(k, Colour.BLACK, null, v, false).setFont(MESSAGE_FONT); 172 } 173 } 174 175 private static void generateMailHeader(String timestamp, String sender) { 176 Text header = creator.addText(timestamp + " (From: " + sender + ")", Colour.BLACK, null, null, false); 123 177 Path credentialsFilePath = Paths.get(FrameIO.CONTACTS_PATH).resolve(sender + "-credentials").resolve("credentials.inf"); 124 178 if (credentialsFilePath.toFile().exists()) { … … 131 185 } 132 186 } 133 for (Text line: mail.getMessage()) { 134 _creator.addItem(line.copy(), false); 135 } 136 t.setLink(_creator.getCurrentFrame().getName()); 137 _previewMessages.add(t); 138 _creator.addSpace(SPACING); 139 _creator.save(); 140 141 // Make sure the link points to the latest frame 142 _mailLink.setLink(_creator.getCurrent()); 143 144 DisplayController.requestRefresh(true); 145 146 return t; 147 } 148 149 /** 150 * Obtains the image item that is drawn to display the mail bay. 151 * @param clip 152 * @param size 153 * @return 154 */ 155 public static Image getImage(Clip clip, Dimension size) { 156 // Can't get an image with an invalid size 157 if (size == null || size.width <= 0 || size.height <= 0) { 158 return null; 159 } 160 161 // Update the buffer 162 updateBuffer(Item.DEFAULT_BACKGROUND, clip, size); 163 164 // Return the image buffer 165 return _mailBuffer; 166 } 167 168 public static void clear() { 169 getPreviewMessages().clear(); 170 _messageCount = 0; 171 } 172 173 public static Item getMailLink() { 174 return _mailLink; 175 } 176 177 public static void ensureLink() { 178 if (_mailLink.getLink() == null && FrameIO.canAccessFrame(MailBay.EXPEDITEE_MAIL_FRAMESET_NAME + 1)) { 179 _mailLink.setLink(MailBay.EXPEDITEE_MAIL_FRAMESET_NAME + 1); 180 } 181 } 182 183 /** Updates the image buffer to reflect the current state of the mail bay. */ 187 } 188 184 189 private synchronized static void updateBuffer(Colour background, Clip clip, Dimension size) { 185 190 // If the buffer doesn't exist or is the wrong size, recreate it 186 if ( _mailBuffer == null || !_mailBuffer.getSize().equals(size)) {187 _mailBuffer= Image.createImage(size, true);191 if (mailBufferImage == null || !mailBufferImage.getSize().equals(size)) { 192 mailBufferImage = Image.createImage(size, true); 188 193 clip = null; // Need to recreate the entire image; 189 194 updateSize(); … … 192 197 // Prepare graphics 193 198 GraphicsManager g = EcosystemManager.getGraphicsManager(); 194 g.pushDrawingSurface( _mailBuffer);199 g.pushDrawingSurface(mailBufferImage); 195 200 if (clip != null) { 196 201 g.pushClip(clip); … … 198 203 g.setAntialiasing(true); 199 204 g.clear(background); 200 g.setFont( _messageFont);205 g.setFont(MESSAGE_FONT); 201 206 202 207 // Paint header 203 FrameGraphics.PaintItem(getHeader( Colour.BLACK));204 205 // Paint the mail messages to the screen (preview)206 for (Item message : _previewMessages) {207 if (message != null) {208 if (clip == null || clip.isNotClipped() || message.isInDrawingArea(clip.getBounds())) {209 FrameGraphics.PaintItem(message);210 }208 FrameGraphics.PaintItem(getHeader()); 209 210 // Paint the mail messages to the bay 211 for (Item message: previewMessages) { 212 if (message == null) { continue; } 213 214 if (clip == null || clip.isNotClipped() || message.isInDrawingArea(clip.getBounds())) { 215 FrameGraphics.PaintItem(message); 211 216 } 212 217 } … … 219 224 220 225 // Paint the link to the mail frame 221 if (clip == null || clip.isNotClipped() || _mailLink.isInDrawingArea(clip.getBounds())) { 222 FrameGraphics.PaintItem(_mailLink); 226 if (clip == null || clip.isNotClipped() || mailLink.isInDrawingArea(clip.getBounds())) { 227 FrameGraphics.PaintItem(mailLink); 228 } 229 230 // Paint the action item to check for new mail 231 if (clip == null || clip.isNotClipped() || checkMailAction.isInDrawingArea(clip.getBounds())) { 232 FrameGraphics.PaintItem(checkMailAction); 223 233 } 224 234 … … 226 236 } 227 237 228 /** Syncs message bay size according to FrameGraphics max size. */229 238 private static void updateSize() { 230 for (Item i : _previewMessages) {239 for (Item i: previewMessages) { 231 240 if (i != null) { 232 241 i.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); … … 234 243 } 235 244 236 _mailLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());237 245 updateLink(); 238 246 } 239 247 240 248 private static void updateLink() { 241 if (!isLinkInitialized && DisplayController.getFramePaintArea() != null 242 && DisplayController.getFramePaintAreaWidth() > 0) { 243 // set up 'Messages' link on the right hand side 244 _mailLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MAIL_LINK_Y_OFFSET, 245 MAIL_LINK_X); 246 _mailLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); 247 isLinkInitialized = true; 248 } else { 249 _mailLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MAIL_LINK_Y_OFFSET, 250 MAIL_LINK_X); 251 } 252 } 253 254 private static Text getHeader(Colour fontColor) { 255 Text t = new Text("You have [" + _messages.size() + "] unprocessed messages waiting."); 256 if (_messages.size() >= 2) { 257 t.setText(t.getText() + " Two latest below:"); 258 } 259 t.setPosition(OFFSET_X, HEADER_OFFSET_Y); 260 t.setOffset(0, -DisplayController.getFramePaintAreaHeight()); 261 t.setColor(fontColor); 262 t.setFont(_messageFont.clone()); 263 return t; 264 } 265 266 private static String getMessagePrefix() { 267 _messageCount++; 268 return "@" + _messageCount + ": "; 269 } 270 271 private static class Mail { 272 private String message; 273 private Map<String, String> options; 274 private String message2; 275 276 private Mail(String message, String message2, Map<String, String> options) { 277 this.message = message; 278 this.message2 = message2; 279 this.options = options; 280 } 281 282 private Text getPreviewMessage(boolean usePrefix) { 283 Text t = usePrefix ? new Text(getMessagePrefix() + message) : new Text(message); 284 int y = MESSAGE_OFFSET_Y + _previewMessages.size() * SPACING; 285 t.setPosition(OFFSET_X, y); 286 t.setColor(Colour.BLACK); 287 t.setFont(_messageFont.clone()); 288 return t; 289 } 290 291 private Text getDetailLine() { 292 if (message2 == null || message2.isEmpty()) { 293 return null; 294 } 295 Text t = new Text(message2); 296 int y = MESSAGE_OFFSET_Y + _previewMessages.size() * SPACING; 297 t.setPosition(OFFSET_X, y); 298 t.setColor(Colour.BLACK); 299 t.setFont(_messageFont.clone()); 300 return t; 301 } 302 303 private Text[] getMessage() { 304 List<Text> items = new LinkedList<Text>(); 305 items.add(getPreviewMessage(false)); 306 Text detail = getDetailLine(); 307 if (detail != null) { 308 items.add(detail); 249 mailLink = new Text(-2, "See Mail", Colour.BLACK, Colour.WHITE); 250 mailLink.setFont(MESSAGE_FONT); 251 mailLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); 252 mailLink.setAnchorRight(OFFSET_X); 253 mailLink.setAnchorTop(OFFSET_Y); 254 mailLink.setLink(creator.getCurrent()); 255 256 checkMailAction = new Text(-2, "Get New Mail", Colour.BLACK, Colour.WHITE); 257 checkMailAction.setFont(MESSAGE_FONT); 258 checkMailAction.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); 259 checkMailAction.setAnchorRight(OFFSET_X); 260 checkMailAction.setAnchorTop(OFFSET_Y + SPACING); 261 checkMailAction.setAction("CheckForNewMail"); 262 } 263 264 private static Text getHeader() { 265 Text header = new Text("You have [" + previewMessages.size() + "] new messages since last you check."); 266 header.setPosition(OFFSET_X, OFFSET_Y); 267 header.setOffset(0, -DisplayController.getFramePaintAreaHeight()); 268 header.setFont(MESSAGE_FONT); 269 return header; 270 } 271 272 private static Map<String, Integer> countBySender(List<MailEntry> newMail) { 273 Map<String, Integer> bySenderCount = new HashMap<String, Integer>(); 274 275 for (MailEntry mail: newMail) { 276 String sender = mail.getSender(); 277 if (mail.isSingleUseEncryption()) { 278 sender = "Encrypted Sender"; 309 279 } 310 280 311 int i = items.size(); 312 for (String content: options.keySet()) { 313 String[] split = content.split(":::"); 314 String action = options.get(content); 315 Text t = new Text(split[0]); 316 for (int o = 1; o < split.length; o++) { 317 t.addToData(split[o]); 318 } 319 int y = MESSAGE_OFFSET_Y + (_previewMessages.size() + i) * SPACING; 320 t.setPosition(OFFSET_X, y); 321 t.setColor(Colour.BLUE); 322 t.setFont(_messageFont.clone()); 323 t.setAction(action); 324 items.add(t); 325 i++; 326 } 327 328 return items.toArray(new Text[] {}); 329 } 281 if (bySenderCount.containsKey(sender)) { 282 Integer newValue = bySenderCount.get(sender) + 1; 283 bySenderCount.put(sender, newValue); 284 } else { 285 bySenderCount.put(sender, 1); 286 } 287 } 288 289 return bySenderCount; 330 290 } 331 291 } -
trunk/src/org/expeditee/gui/FrameIO.java
r1491 r1504 52 52 import org.expeditee.agents.InvalidFramesetNameException; 53 53 import org.expeditee.auth.AuthenticatorBrowser; 54 import org.expeditee.auth.mail.gui.MailBay;55 54 import org.expeditee.encryption.io.EncryptedExpReader; 56 55 import org.expeditee.encryption.io.EncryptedExpWriter; -
trunk/src/org/expeditee/gui/FrameUtils.java
r1490 r1504 1286 1286 1287 1287 // check the link to the message/mail frame 1288 Item linkItem = DisplayController.isMailMode() ? MailBay.getMailLink() : MessageBay.getMessageLink(); 1289 if (linkItem != null && linkItem.contains(new Point(x, y))) { 1290 linkItem.setOverlayPermission(UserAppliedPermission.copy); 1291 possibles.add(linkItem); 1288 Item[] linkItems = DisplayController.isMailMode() ? new Item[] { MailBay.getMailLink(), MailBay.getCheckMailAction() } : new Item[] { MessageBay.getMessageLink() }; 1289 for (Item linkItem: linkItems) { 1290 if (linkItem != null && linkItem.contains(new Point(x, y))) { 1291 linkItem.setOverlayPermission(UserAppliedPermission.copy); 1292 possibles.add(linkItem); 1293 } 1292 1294 } 1293 1295
Note:
See TracChangeset
for help on using the changeset viewer.