package org.expeditee.auth; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.sql.SQLException; import java.text.ParseException; import java.util.Base64; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.Scanner; import java.util.stream.Collectors; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.expeditee.agents.ExistingFramesetException; import org.expeditee.agents.InvalidFramesetNameException; import org.expeditee.auth.account.Authenticate; import org.expeditee.auth.account.Authenticate.AuthenticationResult; import org.expeditee.auth.account.Contacts; import org.expeditee.auth.account.Create; import org.expeditee.auth.account.Create.CreateResult; import org.expeditee.auth.account.Password; import org.expeditee.auth.mail.Mail; import org.expeditee.auth.mail.Mail.MailEntry; import org.expeditee.auth.mail.gui.MailBay; import org.expeditee.auth.tags.AuthenticationTag; import org.expeditee.encryption.CryptographyConstants; import org.expeditee.gio.gesture.StandardGestureActions; import org.expeditee.gui.DisplayController; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameIO; import org.expeditee.gui.MessageBay; import org.expeditee.items.Item; import org.expeditee.items.Text; import org.expeditee.settings.UserSettings; import org.expeditee.settings.identity.secrets.KeyList; import org.expeditee.stats.Formatter; public class Actions implements CryptographyConstants { // Start Debug Actions public static void SendTestMessage(String colleagueName) throws InvalidKeySpecException, NoSuchAlgorithmException, FileNotFoundException, KeyStoreException, CertificateException, ClassNotFoundException, IOException, SQLException { String time = org.expeditee.stats.Formatter.getDateTime(); String sender = UserSettings.UserName.get(); String topic = "Test Message"; String message = "This is a test message."; Map options = new HashMap(); options.put("Neat", "Beep"); MailEntry mail = new MailEntry(time, sender, colleagueName, topic, message, options); Mail.sendMail(mail, colleagueName); MessageBay.displayMessage("Test message sent."); } public static void SendTestMessageHemi(String param) { String time = Formatter.getDateTime(); String sender = UserSettings.UserName.get(); String recipient = param.split(" ")[0]; String message = param.split(" ")[1]; Map options = new HashMap(); options.put("Accept", "beep"); options.put("Reject", "beep"); MailEntry mail = new MailEntry(time, sender, recipient, "Have a key", message, options); Mail.sendMail(mail, recipient); MessageBay.displayMessage("Test message sent."); } public static void SendTestOneOffMessage(String colleagueName) { String time = Formatter.getDateTime(); String sender = UserSettings.UserName.get(); String topic = "Test Message"; String message = "This is a test message."; Map options = new HashMap(); options.put("Neat", "Beep"); MailEntry mail = new MailEntry(time, sender, colleagueName, topic, message, options); Random rand = new SecureRandom(); byte[] key = new byte[16]; rand.nextBytes(key); System.out.println(Base64.getEncoder().encodeToString(key)); Mail.sendOneOffMail(mail, colleagueName, key); } private static String userbackup = "authadmin"; public static void ToggleAuth() { String backup = UserSettings.UserName.get(); System.setProperty("user.name", userbackup); UserSettings.UserName.set(userbackup); userbackup = backup; } // End Debug Actions // Start Misc Auth Actions /** * Action ran by user to read a message using a single use distributed Symmetric key * @param cursor The content on the cursor should be a text item whose content is the * Symmetric key to use, represented as a Base64 encoded string. * @param actionItem The action item will contain the encrypted message in its data. */ public static void AuthOneOffSecureMessage(Text cursor, Text actionItem) { byte[] keyBytes = Base64.getDecoder().decode(cursor.getText()); SecretKey key = new SecretKeySpec(keyBytes, SymmetricAlgorithm); List data = actionItem.getData(); Mail.decryptOneOffSecureMessage(key, data); StandardGestureActions.Refresh(); } /** * Display Expeditee Mail * @throws IOException * @throws SQLException * @throws ClassNotFoundException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws FileNotFoundException * @throws KeyStoreException * @throws ParseException * @throws InvalidKeySpecException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws NoSuchPaddingException * @throws InvalidKeyException */ public static void ToggleBay() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, SQLException, IOException, ParseException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { if (!AuthenticatorBrowser.isAuthenticated()) return; if (!DisplayController.isMailMode()) { MailBay.ensureLink(); Mail.clear(); String keyEncoded = KeyList.PrivateKey.get().getData().get(0); byte[] keyBytes = Base64.getDecoder().decode(keyEncoded); PrivateKey key = KeyFactory.getInstance(AsymmetricAlgorithm).generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); Mail.checkMail(key); } DisplayController.ToggleMailMode(); } /** * Action used to navigate the authorised user back to their desktop. */ public static void AuthGoToDesktop() { if (AuthenticatorBrowser.Authenticated) { DisplayController.setCurrentFrame(FrameIO.LoadFrame(UserSettings.HomeFrame.get()), true); } else { MessageBay.displayMessage("Please Login to proceed to your home frame."); DisplayController.setCurrentFrame(FrameIO.LoadFrame("authentication1"), true); } } /** * Action used to navigate to multiuser1 (multiuser abilities) if authenticated and authentication1 (login) is not so. */ public static void AuthGotoAccountManagement() { if (AuthenticatorBrowser.Authenticated) { DisplayController.setCurrentFrame(FrameIO.LoadFrame("multiuser1"), true); } else { MessageBay.displayMessage("Please Login to proceed to account managment."); DisplayController.setCurrentFrame(FrameIO.LoadFrame("authentication1"), true); } } /** * Gets all items on a specified frame that contain the specified data. */ public static Collection getByData(Frame frame, String data) { Collection allItems = frame.getAllItems(); allItems.removeIf(i -> i.getData() == null || !i.hasData(data)); return allItems; } /** * Gets all items on a specified frame that contains the specified content. */ public static Collection getByContent(Frame frame, String content) { Collection allItems = frame.getAllItems(); allItems.removeIf(i -> i.getText().compareTo(content) != 0); return allItems; } // End Misc Auth Actions // Start Making Contacts Actions public static void AuthDistributeContactDetails() { MessageBay.displayMessage( "To receive directions on distributing your contact details to someone, attach their username to your cursor and run this action again." ); } public static String AuthDistributeContactDetails(String username) { return Contacts.distributeContactDetails(username); } public static void AuthAddContactDetails() { MessageBay.displayMessage("If a user has sent their contact details to you, running this action again with their username attached to your cursor will display instructions to adding those details to your contacts directory."); } public static String AuthAddContactDetails(String username) { return Contacts.addContactDetails(username); } // End Making Contacts Actions // Start Regain Account Access Actions /** * Action used to start the process of formalising the password recovery process. * @throws SQLException * @throws IOException * @throws ClassNotFoundException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws FileNotFoundException * @throws KeyStoreException * @throws InvalidKeySpecException */ public static void AuthSubmitTrustedUsersPasswordRecovery() throws InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, ClassNotFoundException, IOException, SQLException { Frame currentFrame = DisplayController.getCurrentFrame(); Collection textItems = currentFrame.getTextItems(); if (!AuthenticatorBrowser.Authenticated) { MessageBay.errorMessage("You must be logged in to perform this action."); return; } Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.TrustedUserOne, AuthenticationTag.TrustedUserTwo); if (userdata.isPresent()) { Map userData = userdata.get(); String colleagueOne = userData.get(AuthenticationTag.TrustedUserOne); String colleagueTwo = userData.get(AuthenticationTag.TrustedUserTwo); AuthSubmitTrustedUsersPasswordRecovery(colleagueOne, colleagueTwo); // Path colleagueOnePath = Paths.get(FrameIO.CONTACTS_PATH).resolve(colleagueOne + "-credentials"); // Path colleagueTwoPath = Paths.get(FrameIO.CONTACTS_PATH).resolve(colleagueTwo + "-credentials"); // if (!colleagueOnePath.toFile().exists()) { // MessageBay.errorMessage("Your nominated trusted user: " + colleagueOne + " must exist in your contacts."); // } else if (!colleagueTwoPath.toFile().exists()) { // MessageBay.errorMessage("Your nominated trusted user: " + colleagueTwo + " must exist in your contacts."); // } else { // userData.put(AuthenticationTag.Username, UserSettings.UserName.get()); // boolean success = submitTrustedUsersPasswordRecovery(userData); // if (success) { // Collection toShow = getByData(currentFrame, "ShowOnProgress"); // for (Item i: toShow) { // i.setVisible(true); // } // currentFrame.change(); // MessageBay.displayMessage("-------Messages sent-------"); // } // FrameIO.SaveFrame(currentFrame); // DisplayController.requestRefresh(false); // } } } /** * Action ran by user to specify who their password colleagues are. These are the * individuals who will be consulted if and when the user needs to regain access * to their account. * @param colleagueOne * @param colleagueTwo */ public static void AuthSubmitTrustedUsersPasswordRecovery(String colleagueOne, String colleagueTwo) { Password.setPWColleagues(colleagueOne, colleagueTwo); } /** * Action ran by user to oblige with a request from colleague who has nominated the * user as a pw colleague. Will email (not Expeditee mail) the colleague the password * share that the user has stored on their secrets frame. * @param colleagueName */ public static void AuthEmailPasswordShare(String colleagueName) { Password.emailPasswordShare(colleagueName); } /** * Action ran by user to regain access to their account by providing: * their username * two password shares obtained from pw colleagues * their desired new password */ public static void AuthRegainAccountAccess() { Collection textItems = DisplayController.getCurrentFrame().getTextItems(); Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Username, AuthenticationTag.NewPassword, AuthenticationTag.NewPasswordAgain, AuthenticationTag.PasswordSliceOne, AuthenticationTag.PasswordSliceTwo); if (userdata.isPresent()) { // Confirm new requested passwords match Map userData = userdata.get(); String username = userData.get(AuthenticationTag.Username).trim(); if (username.length() == 0) { MessageBay.errorMessage("Please fill out the username box."); } else if (!userData.get(AuthenticationTag.NewPassword).equals(userData.get(AuthenticationTag.NewPasswordAgain))) { MessageBay.errorMessage("The passwords you have provided do not match."); } else { Password.regainAccountAccess(userData); } } } /** * Actions used to generate and deliver an intergalactic number to a users public email * address after they have began the password recovery process. */ public static void AuthDistributeIntergalacticNumber() { Collection textItems = DisplayController.getCurrentFrame().getTextItems(); Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Username); if (userdata.isPresent()) { Map userData = userdata.get(); String username = userData.get(AuthenticationTag.Username); String email = getEmailFromUsername(username); userData.put(AuthenticationTag.Email, email); Password.generateAndDeliverIntergalacticNumber(userData); MessageBay.displayMessage("A identity number has been sent to the email " + "associated with your account. Enter it below to proceed."); } } /** * Action used by user to submit their intergalactic number along with their username * in order to confirm that they own the public email address registered to their account. * This is part of the process of recoverying access to an account. */ public static void AuthSubmitIntergalacticNumber() { Collection textItems = DisplayController.getCurrentFrame().getTextItems(); Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Username, AuthenticationTag.IntergalacticNumber); if (userdata.isPresent()) { Password.confirmIntergalacticNumberAndAlertTrustedUsers(userdata.get()); } } // End Regain Account Access Actions // Start Create Account Actions /** * Action used to created a new user account. * Attempts to use content from text items on frame, will default to java properties if they cannot be found. * Will fail if it cannot find content from text items on frame and all required java properties are not present. * @throws SQLException * @throws IOException * @throws ExistingFramesetException * @throws InvalidFramesetNameException * @throws ClassNotFoundException * @throws FileNotFoundException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws KeyStoreException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws NoSuchPaddingException * @throws InvalidKeySpecException * @throws InvalidKeyException * @throws ParseException * @throws Exception */ public static void AuthCreateAccount() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, ClassNotFoundException, InvalidFramesetNameException, ExistingFramesetException, IOException, SQLException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, ParseException { Collection textItems = DisplayController.getCurrentFrame().getTextItems(); Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Username, AuthenticationTag.Password, AuthenticationTag.PasswordAgain, AuthenticationTag.Email, AuthenticationTag.EmailAgain); if (userdata.isPresent()) { Map userData = userdata.get(); // A profile already existing with 'username' means an account cannot be created with that username. if (FrameIO.getProfilesList().contains(userData.get(AuthenticationTag.Username))) { MessageBay.errorMessage("A Expeditee profile with this username already exists, please choose another."); return; } // The chosen username must be a valid frameset name. if (!FrameIO.isValidFramesetName(userData.get(AuthenticationTag.Username))) { MessageBay.errorMessage("The provided username must begin and end with a letter and contain only letters and numbers inbetween, please choose another."); return; } // The passwords provided must match if (userData.get(AuthenticationTag.Password).compareTo(userData.get(AuthenticationTag.PasswordAgain)) != 0) { MessageBay.errorMessage("The provided passwords do not match, please fix this and try again."); return; } // The emails provided must match if (userData.get(AuthenticationTag.Email).compareTo(userData.get(AuthenticationTag.EmailAgain)) != 0) { MessageBay.errorMessage("The provided emails do not match, please fix this and try again."); return; } CreateResult result = Create.createAccount(userData); if (result == CreateResult.SuccessCreateAccount) { Authenticate.login(userData); AuthenticatorBrowser.Authenticated = true; } else { MessageBay.errorMessage(result.toString()); } } else { MessageBay.errorMessage("Please fill out all the supplied text boxes."); } } // End Create Account Actions // Start Account Login Actions /** * Action used to start authentication as a specified user. * Attempts to use content from text items on frame, will default to java properties if they cannot be found. * Will fail if it cannot find content from text items on frame and all required java properties are not present. * @throws Exception */ public static void AuthLogin() { Collection textItems = DisplayController.getCurrentFrame().getTextItems(); Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Username, AuthenticationTag.Password); if (userdata.isPresent()) { AuthenticationResult result = Authenticate.login(userdata.get()); if (result == AuthenticationResult.SuccessLogin) { MessageBay.displayMessage(result.toString()); AuthenticatorBrowser.Authenticated = true; } else { MessageBay.errorMessage(result.toString()); } } else { MessageBay.errorMessage("Please fill out all the supplied text boxes."); } } /** * Action used by the user to log out of their account. */ public static void AuthLogout() { MessageBay.displayMessage(Authenticate.logout().toString()); } // End Account Login Actions // Start Change Access Actions /** * Action used to change the currently authenticated users password. * Attempts to use content from text items on frame, will default to java properties if they cannot be found. * Will fail if it cannot find content from text items on frame and all required java properties are not present. * Will fail if no user is currently logged in. * @throws IOException * @throws CertificateException * @throws FileNotFoundException * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws SQLException * @throws ClassNotFoundException */ public static void AuthChangePassword() throws NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException { final Collection textItems = DisplayController.getCurrentFrame().getTextItems(); if (!AuthenticatorBrowser.Authenticated) { MessageBay.errorMessage("You must be logged in to perform this action."); } else { final Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Password, AuthenticationTag.NewPassword, AuthenticationTag.NewPasswordAgain); if (userdata.isPresent()) { final Map userData = userdata.get(); if (userData.get(AuthenticationTag.NewPassword).compareTo(userData.get(AuthenticationTag.NewPasswordAgain)) != 0) { MessageBay.errorMessage("The provided passwords do not match, please fix this and try again."); } else { userData.put(AuthenticationTag.Username, UserSettings.UserName.get()); Password.changePassword(userData); } } else { MessageBay.errorMessage("Please fill out all the supplied text boxes."); } } } // End Change Access Actions // Start Private Helper Functions. /** * Gets the public email address associated with the specified username. * @param username * @return */ private static String getEmailFromUsername(String username) { Path credentialsDirPath = Paths.get(FrameIO.PROFILE_PATH).resolve(username).resolve(username + "-credentials"); Path credentialsFilePath = credentialsDirPath.resolve("credentials.inf"); String fileName = null; if (credentialsFilePath.toFile().exists()) { try (Scanner in = new Scanner(credentialsFilePath)) { fileName = in.nextLine(); } catch (IOException e) { MessageBay.errorMessage("Unable to locate public email for specified user, are they registered on this computer?"); return null; } } else { MessageBay.errorMessage("Unable to locate public email for specified user, are they registered on this computer?"); return null; } int number = Integer.parseInt(fileName.replace(".exp", "")); Frame credentialsFrame = FrameIO.LoadFrame(username + number, FrameIO.PROFILE_PATH); Collection textItems = credentialsFrame.getTextItems(); textItems.removeIf(text -> !text.getText().startsWith("Email: ")); if (textItems.isEmpty()) { MessageBay.errorMessage("Unable to locate public email for specified user, are they registered on this computer?"); return null; } else { Text emailText = textItems.iterator().next(); String email = emailText.getText().replace("Email: ", ""); return email; } } // End Private Helper Functions. // Start Future Functionality public static void AuthShareFrameset() throws IOException { Collection textItems = DisplayController.getCurrentFrame().getTextItems(); Optional> userdata = AuthenticationTag.fetchUserData(textItems, false, AuthenticationTag.Frameset); if (userdata.isPresent()) { Map userData = userdata.get(); FrameIO.SuspendCache(); Frame toShare = FrameIO.LoadFrame(userData.get(AuthenticationTag.Frameset) + 1); FrameIO.ResumeCache(); if (toShare == null) { MessageBay.errorMessage("Insufficient information provided to complete this action."); return; } shareFrameset(toShare); } } /* * Function to share a specified frameset. * Currently, this moves the frameset to the 'Shared By Me' directory and then relies on the user to use Google Drive functionality to share it appropriately. */ private static void shareFrameset(Frame toShare) throws IOException { File destinationDir = new File(FrameIO.SHARED_FRAMESETS_PATH + File.separator + toShare.getFramesetName()); File sourceDir = new File(toShare.getFramesetPath()); if (destinationDir.exists()) { MessageBay.errorMessage("A frameset by this name already exists."); return; } destinationDir.mkdir(); List files = Files.walk(sourceDir.toPath()).collect(Collectors.toList()); Files.move(files.get(0), destinationDir.toPath(), StandardCopyOption.ATOMIC_MOVE); MessageBay.displayMessage("The frameset " + toShare.getFramesetName() + " has been moved to " + destinationDir + ". Google Drive functionality can now be used to share it with colleagues."); } /* * Function to submit a request to specified contacts to be the current users pw colleagues. */ @SuppressWarnings("unused") private static boolean submitTrustedUsersPasswordRecovery(Map userData) throws InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, ClassNotFoundException, IOException, SQLException { String colleagueOne = userData.get(AuthenticationTag.TrustedUserOne); String colleagueTwo = userData.get(AuthenticationTag.TrustedUserTwo); PublicKey colleagueOneKey = AuthenticatorBrowser.getInstance().getPublicKey(colleagueOne); PublicKey colleagueTwoKey = AuthenticatorBrowser.getInstance().getPublicKey(colleagueTwo); if (colleagueOneKey == null) { MessageBay.errorMessage("Unable to get public key for colleague: " + colleagueOne); return false; } else if (colleagueTwoKey == null) { MessageBay.errorMessage("Unable to get public key for colleague: " + colleagueTwo); return false; } else { String time = org.expeditee.stats.Formatter.getDateTime(); String sender = userData.get(AuthenticationTag.Username); String topic = "You have received a request for cooperation from your colleague " + sender; String message = "Should " + sender + " forget their password, they would like your help recoverying it."; Map arguments = new HashMap(); arguments.put("I agree to assist " + sender + " if they loose access to their account.", "AuthConfirmPasswordColleagueRelationship " + sender); arguments.put("I wish to excuse myself from this responsibility.", "AuthDenyPasswordColleagueRelationship " + sender); MailEntry mail = new MailEntry(time, sender, colleagueOne, topic, message, arguments); Mail.sendMail(mail, colleagueOne); mail = new MailEntry(time, sender, colleagueTwo, topic, message, arguments); Mail.sendMail(mail, colleagueTwo); AuthenticatorBrowser.getInstance().markRequestedColleagues(UserSettings.UserName.get()); return true; } } // End Future Functionality }