Changeset 1504 for trunk/src/org/expeditee/auth/mail/Mail.java
- Timestamp:
- 01/29/20 13:20:24 (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
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 }
Note:
See TracChangeset
for help on using the changeset viewer.