Changeset 1504


Ignore:
Timestamp:
01/29/20 13:20:24 (4 years ago)
Author:
bnemhaus
Message:

Revised implementation of authenticated Expeditee mail. Motivated by bugs relating to messages not being marked as read and incorrect counting of new messages for users, the Expeditee mail system has been rewritten. The new code not only does not exhibit the previous bugs but is also better engineered. Whilst the MailBay is static (which is in line with the MessageBay), the Mail class is no longer static and must be initialised for each user as they log in.

Location:
trunk/src/org/expeditee
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/expeditee/auth/Actions.java

    r1494 r1504  
    99import java.nio.file.StandardCopyOption;
    1010import java.security.InvalidKeyException;
    11 import java.security.KeyFactory;
    1211import java.security.KeyStoreException;
    1312import java.security.NoSuchAlgorithmException;
    14 import java.security.PrivateKey;
    1513import java.security.PublicKey;
    1614import java.security.SecureRandom;
    1715import java.security.cert.CertificateException;
    1816import java.security.spec.InvalidKeySpecException;
    19 import java.security.spec.PKCS8EncodedKeySpec;
    2017import java.sql.SQLException;
    2118import java.text.ParseException;
    2219import java.util.Base64;
    2320import java.util.Collection;
     21import java.util.Date;
    2422import java.util.HashMap;
    2523import java.util.List;
     
    5755import org.expeditee.items.Text;
    5856import org.expeditee.settings.UserSettings;
    59 import org.expeditee.settings.identity.secrets.KeyList;
    60 import org.expeditee.stats.Formatter;
    6157
    6258public class Actions implements CryptographyConstants {
    63        
    64         //      Start Debug Actions
    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());
    6763                String sender = UserSettings.UserName.get();
    6864                String topic = "Test Message";
     
    7066                Map<String, String> options = new HashMap<String, String>();
    7167                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);
    7471                MessageBay.displayMessage("Test message sent.");
    7572        }
    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());
    9284                String sender = UserSettings.UserName.get();
    9385                String topic = "Test Message";
     
    9587                Map<String, String> options = new HashMap<String, String>();
    9688                options.put("Neat", "Beep");
    97                 MailEntry mail = new MailEntry(time, sender, colleagueName, topic, message, options);
     89               
    9890                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);
    10399                MessageBay.displayMessage("Test message sent.");
    104100        }
     
    114110       
    115111        //      Start Misc Auth Actions
     112        public static void CheckForNewMail() {
     113                MailBay.checkMail();
     114        }
     115       
    116116        /**
    117117         * Action ran by user to read a message using a single use distributed Symmetric key
     
    124124                SecretKey key = new SecretKeySpec(keyBytes, SymmetricAlgorithm);
    125125                List<String> data = actionItem.getData();
    126                 Mail.decryptOneOffSecureMessage(key, data);
     126                MailBay.decryptOneOffSecureMessage(key, data);
    127127                StandardGestureActions.Refresh();
    128128        }
     
    146146        public static void ToggleBay() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, SQLException, IOException, ParseException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
    147147                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                //}
    156151                DisplayController.ToggleMailMode();
    157152        }
     
    558553        @SuppressWarnings("unused")
    559554        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);
    564559                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);
    566561                        return false;
    567562                } 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);
    569564                        return false;
    570565                } else {
    571                         String time = org.expeditee.stats.Formatter.getDateTime();
     566                        String time = Mail.FORMAT.format(new Date());
    572567                        String sender = userData.get(AuthenticationTag.Username);
    573568                        String topic = "You have received a request for cooperation from your colleague " + sender;
     
    576571                        arguments.put("I agree to assist " + sender + " if they loose access to their account.", "AuthConfirmPasswordColleagueRelationship " + sender);
    577572                        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);
    582578                        AuthenticatorBrowser.getInstance().markRequestedColleagues(UserSettings.UserName.get());
    583579                        return true;
  • trunk/src/org/expeditee/auth/AuthenticatorBrowser.java

    r1453 r1504  
    22
    33import java.io.File;
    4 import java.io.FileFilter;
    54import java.io.FileInputStream;
    65import java.io.FileNotFoundException;
    76import java.io.FileOutputStream;
    8 import java.io.FileWriter;
    97import java.io.IOException;
    108import java.io.InputStream;
    11 import java.nio.file.Path;
    129import java.nio.file.Paths;
    1310import java.security.KeyFactory;
     
    2219import java.security.spec.InvalidKeySpecException;
    2320import java.security.spec.X509EncodedKeySpec;
    24 import java.sql.Connection;
    25 import java.sql.DriverManager;
    26 import java.sql.PreparedStatement;
    27 import java.sql.ResultSet;
    2821import java.sql.SQLException;
    29 import java.text.ParseException;
    30 import java.text.SimpleDateFormat;
    3122import java.util.Arrays;
    3223import java.util.Base64;
    3324import java.util.Collection;
    34 import java.util.Date;
    35 import java.util.HashMap;
    36 import java.util.HashSet;
    37 import java.util.Map;
    3825import java.util.Scanner;
    39 import java.util.Set;
    4026import java.util.stream.Stream;
    4127
     
    4430
    4531import org.expeditee.actions.Actions;
    46 import org.expeditee.auth.mail.Mail;
    4732import org.expeditee.core.Dimension;
    4833import org.expeditee.core.Point;
     
    6752import org.expeditee.settings.UserSettings;
    6853import org.expeditee.settings.identity.secrets.KeyList;
    69 import org.expeditee.stats.Formatter;
    7054
    7155public final class AuthenticatorBrowser extends Browser implements CryptographyConstants {
     
    195179                        }
    196180                }
    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                         @Override
    264                         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                 }               
    315181        }
    316182         
  • trunk/src/org/expeditee/auth/account/Authenticate.java

    r1478 r1504  
    11package org.expeditee.auth.account;
    22
    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;
    143import java.util.ArrayList;
    154import java.util.Base64;
     
    187import java.util.Map;
    198
    20 import javax.crypto.BadPaddingException;
    21 import javax.crypto.IllegalBlockSizeException;
    22 import javax.crypto.NoSuchPaddingException;
    239import javax.crypto.SecretKey;
    2410
     
    9682                        MessageBay.clear();
    9783                        MessageBay.updateFramesetLocation();
    98                         MailBay.disconnect();
     84                        MailBay.reconnectToUser(UserSettings.UserName.get());
    9985                       
    10086                        // Parse the users profile to refresh settings.
     
    10894                        AuthenticationResult res = AuthenticationResult.SuccessLogin;
    10995                       
    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();
    13298                       
    13399                        Collection<Item> usernameFields = Actions.getByData(FrameIO.LoadFrame("multiuser1"), "txtUsername");
     
    155121                UserSettings.setupDefaultFolders();
    156122                MessageBay.updateFramesetLocation();
    157                 MailBay.disconnect();
    158123               
    159124                // Reset all of the settings.
  • trunk/src/org/expeditee/auth/account/Create.java

    r1474 r1504  
    4646import org.expeditee.setting.TextSetting;
    4747import org.expeditee.settings.UserSettings;
    48 import org.expeditee.settings.folders.FolderSettings;
    4948import org.expeditee.settings.identity.secrets.KeyList;
    5049
  • trunk/src/org/expeditee/auth/account/Password.java

    r1500 r1504  
    1313import java.util.Base64;
    1414import java.util.Collection;
     15import java.util.Date;
    1516import java.util.HashMap;
    1617import java.util.Iterator;
     
    3334import org.expeditee.auth.mail.Mail;
    3435import org.expeditee.auth.mail.Mail.MailEntry;
     36import org.expeditee.auth.mail.gui.MailBay;
    3537import org.expeditee.auth.tags.AuthenticationTag;
    3638import org.expeditee.encryption.CryptographyConstants;
     
    4446import org.expeditee.settings.identity.passwordrecovery.Colleagues;
    4547import org.expeditee.settings.identity.secrets.KeyList;
    46 import org.expeditee.stats.Formatter;
    4748
    4849import com.codahale.shamir.Scheme;
     
    113114               
    114115                // Get colleagues to distribute messages too.
    115                 String[] colleagues = getPasswordColleaguesFromUsername(username);
     116                String[] trustedUsers = getPasswordColleaguesFromUsername(username);
    116117                //      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());
    119120                String topic = "Password Recovery for " + username;
    120121                String message = "Your colleague " + username + " would like you to help them recover access to their account.";
    121122                Map<String, String> options = new HashMap<String, String>();
    122123                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);
    125128               
    126129                //      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);
    129132               
    130133                String nl = System.getProperty("line.separator");
     
    136139                try {
    137140                        //      Send email with key to colleague one
    138                         String colleagueOneEmail = colleagues[2];
     141                        String colleagueOneEmail = trustedUsers[2];
    139142                        sendEmail(colleagueOneEmail, sb);
    140143                        //      Send email with key to colleague two
    141                         String colleagueTwoEmail = colleagues[3];
     144                        String colleagueTwoEmail = trustedUsers[3];
    142145                        sendEmail(colleagueTwoEmail, sb);
    143146                       
     
    250253        }
    251254
    252         public static void setPWColleagues(String colleagueOne, String colleagueTwo) {
     255        public static void setPWColleagues(String trustedUserOne, String trustedUserTwo) {
    253256                // Get needed text items.
    254257                Frame pwRecoveryFrame = FrameIO.LoadFrame(UserSettings.UserName.get() + AuthenticatorBrowser.PASSWORD_RECOVERY_FRAME);
     
    261264                        Text text = it.next();
    262265                        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);
    265268                        } 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);
    268271                        }
    269272                }
     
    281284                // Create shares
    282285                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));
    285288               
    286289                // Distribute share zero to colleague one
    287                 String time = org.expeditee.stats.Formatter.getDateTime();
     290                String time = Mail.FORMAT.format(new Date());
    288291                String sender = UserSettings.UserName.get();
    289292                String topic = "Please help me secure my Expeditee account.";
    290293                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.";
    291294                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);
    295299               
    296300                // Distribute share one to colleague two
    297301                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 + ".  "
    303307                                + "They have been sent a Expeditee mail that they can use to store a share of your secret key.");
    304308        }
  • trunk/src/org/expeditee/auth/mail/Mail.java

    r1483 r1504  
    33import java.io.File;
    44import java.io.FileNotFoundException;
     5import java.io.FileWriter;
    56import java.io.IOException;
    67import java.nio.file.Path;
    78import java.nio.file.Paths;
    89import java.security.InvalidKeyException;
     10import java.security.Key;
     11import java.security.KeyFactory;
    912import java.security.KeyStoreException;
    1013import java.security.NoSuchAlgorithmException;
     
    1316import java.security.cert.CertificateException;
    1417import java.security.spec.InvalidKeySpecException;
     18import java.security.spec.PKCS8EncodedKeySpec;
    1519import java.sql.Connection;
    1620import java.sql.DriverManager;
    1721import java.sql.PreparedStatement;
     22import java.sql.ResultSet;
    1823import java.sql.SQLException;
    1924import java.sql.Statement;
     
    2833import java.util.Map;
    2934import java.util.Scanner;
     35import java.util.function.Supplier;
    3036
    3137import javax.crypto.BadPaddingException;
     
    3440import javax.crypto.NoSuchPaddingException;
    3541import javax.crypto.SecretKey;
    36 import javax.crypto.spec.SecretKeySpec;
    3742
    3843import org.expeditee.auth.AuthenticatorBrowser;
    39 import org.expeditee.auth.mail.gui.MailBay;
    4044import org.expeditee.encryption.CryptographyConstants;
    4145import org.expeditee.gui.FrameIO;
    42 import org.expeditee.settings.UserSettings;
     46import org.expeditee.gui.MessageBay;
     47import org.expeditee.items.Text;
     48import org.expeditee.settings.identity.secrets.KeyList;
    4349
    4450public 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        }
    4557       
    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
    8863                PublicKey publicKey = null;
    8964                try {
     
    9166                } catch (InvalidKeySpecException | NoSuchAlgorithmException | KeyStoreException | CertificateException
    9267                                | 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());
    9570                        return;
    9671                }
    97                
     72
    9873                // Check we got public key
    9974                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?");
    10177                        return;
    10278                }
     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;
    103282               
    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//              }
    106288               
    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));
    135376                        } 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");
    197394                       
    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;
    205408                        }
    206409                       
    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();
    258434                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                                }
    273448                        } 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) {
    282478                StringBuilder sb = new StringBuilder();
    283479                String sep = ":::";
    284480                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);
    292488                }
    293489                sb.reverse().delete(0, 3).reverse();
    294490                Map<String, String> options = new HashMap<String, String>();
    295491                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;
    434499        }
    435500}
  • trunk/src/org/expeditee/auth/mail/gui/MailBay.java

    r1376 r1504  
    44import java.nio.file.Path;
    55import java.nio.file.Paths;
    6 import java.util.LinkedList;
     6import java.util.ArrayList;
     7import java.util.Base64;
     8import java.util.HashMap;
    79import java.util.List;
    810import java.util.Map;
    911import java.util.Scanner;
    1012
     13import javax.crypto.SecretKey;
     14
    1115import org.expeditee.auth.AuthenticatorBrowser;
     16import org.expeditee.auth.mail.Mail;
     17import org.expeditee.auth.mail.Mail.MailEntry;
    1218import org.expeditee.core.Clip;
    1319import org.expeditee.core.Colour;
     
    1521import org.expeditee.core.Font;
    1622import org.expeditee.core.Image;
     23import org.expeditee.encryption.Actions;
    1724import org.expeditee.gio.EcosystemManager;
    1825import org.expeditee.gio.GraphicsManager;
     
    2431import org.expeditee.items.Item;
    2532import org.expeditee.items.Text;
    26 import org.expeditee.settings.UserSettings;
    2733
    2834public class MailBay {
     35        // Frameset name for mail
    2936        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.
    3542        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;
    3944        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() {
    101109                // Invalidate whole area
    102110                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);
    123177                Path credentialsFilePath = Paths.get(FrameIO.CONTACTS_PATH).resolve(sender + "-credentials").resolve("credentials.inf");
    124178                if (credentialsFilePath.toFile().exists()) {
     
    131185                        }
    132186                }
    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       
    184189        private synchronized static void updateBuffer(Colour background, Clip clip, Dimension size) {
    185190                // 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);
    188193                        clip = null; // Need to recreate the entire image;
    189194                        updateSize();
     
    192197                // Prepare graphics
    193198                GraphicsManager g = EcosystemManager.getGraphicsManager();
    194                 g.pushDrawingSurface(_mailBuffer);
     199                g.pushDrawingSurface(mailBufferImage);
    195200                if (clip != null) {
    196201                        g.pushClip(clip);
     
    198203                g.setAntialiasing(true);
    199204                g.clear(background);
    200                 g.setFont(_messageFont);
     205                g.setFont(MESSAGE_FONT);
    201206               
    202207                // 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);
    211216                        }
    212217                }
     
    219224               
    220225                // 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);
    223233                }
    224234               
     
    226236        }
    227237       
    228         /** Syncs message bay size according to FrameGraphics max size. */
    229238        private static void updateSize() {
    230                 for (Item i : _previewMessages) {
     239                for (Item i: previewMessages) {
    231240                        if (i != null) {
    232241                                i.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
     
    234243                }
    235244               
    236                 _mailLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
    237245                updateLink();
    238246        }
    239247       
    240248        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";
    309279                        }
    310280                       
    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;
    330290        }
    331291}
  • trunk/src/org/expeditee/gui/FrameIO.java

    r1491 r1504  
    5252import org.expeditee.agents.InvalidFramesetNameException;
    5353import org.expeditee.auth.AuthenticatorBrowser;
    54 import org.expeditee.auth.mail.gui.MailBay;
    5554import org.expeditee.encryption.io.EncryptedExpReader;
    5655import org.expeditee.encryption.io.EncryptedExpWriter;
  • trunk/src/org/expeditee/gui/FrameUtils.java

    r1490 r1504  
    12861286
    12871287                        // 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                                }
    12921294                        }
    12931295
Note: See TracChangeset for help on using the changeset viewer.