package org.expeditee.auth; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStore.SecretKeyEntry; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Base64; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.stream.Stream; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.expeditee.actions.Actions; import org.expeditee.auth.mail.Mail; import org.expeditee.core.Dimension; import org.expeditee.core.Point; import org.expeditee.encryption.CryptographyConstants; import org.expeditee.gio.EcosystemManager; import org.expeditee.gio.GraphicsManager; import org.expeditee.gio.InputManager; import org.expeditee.gio.InputManager.WindowEventListener; import org.expeditee.gio.InputManager.WindowEventType; import org.expeditee.gio.gesture.StandardGestureActions; import org.expeditee.gui.Browser; import org.expeditee.gui.DisplayController; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameIO; import org.expeditee.gui.FrameUtils; import org.expeditee.gui.MessageBay; import org.expeditee.io.ExpReader; import org.expeditee.items.Item; import org.expeditee.items.ItemUtils; import org.expeditee.items.Text; import org.expeditee.settings.Settings; import org.expeditee.settings.UserSettings; import org.expeditee.settings.identity.secrets.KeyList; import org.expeditee.stats.Formatter; public final class AuthenticatorBrowser extends Browser implements CryptographyConstants { // The frame number of the frame containing the current authenticated users public key. public static int CREDENTIALS_FRAME = -1; public static int PASSWORD_RECOVERY_FRAME = -1; public static int SECRETS_FRAME = -1; public static final String ADMINACCOUNT = "authadmin"; public static final String PROFILEENCRYPTIONLABEL = "Profile"; public static boolean Authenticated = false; private KeyStore keyStore = KeyStore.getInstance(KeystoreType); public static String USER_NOBODY = "nobody"; private static final byte[] TRUE = "yes".getBytes(); private static final byte[] FALSE = "no".getBytes(); private static final String KEYSTOREFILENAME = "keystore.ks" + File.separator; private static AuthenticatorBrowser instance; public static AuthenticatorBrowser getInstance() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException, ClassNotFoundException, SQLException { if (instance == null) { instance = new AuthenticatorBrowser(); } return instance; } public static boolean isAuthenticationRequired() { return Boolean.getBoolean("expeditee.authentication"); } public static boolean isAuthenticated() { return isAuthenticationRequired() && !UserSettings.UserName.get().equals(AuthenticatorBrowser.USER_NOBODY); } private AuthenticatorBrowser() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, SQLException { super("Authentication"); UserSettings.setupDefaultFolders(); // initialise keystore and actions loadKeystore(); Actions.LoadMethods(org.expeditee.auth.Actions.class); Actions.LoadMethods(org.expeditee.encryption.Actions.class); // Does the account Authentication.ADMINACCOUNT exist? // If not then we have get the user to assign a password to it. if (!keyStore.containsAlias(AuthenticatorBrowser.ADMINACCOUNT)) { new File(FrameIO.PARENT_FOLDER).mkdirs(); protectAdmin(); } // draw the window GraphicsManager g = EcosystemManager.getGraphicsManager(); g.setWindowLocation(new Point(50, 50)); DisplayController.Init(); g.setWindowSize(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get())); setInputManagerWindowRoutines(); // Load documentation and start pages FrameUtils.extractResources(false); // Load fonts before loading any frames so the items on the frames will be able to access their fonts Text.InitFonts(); // initialing settings does not require a user profile established Settings.Init(); // navigate to authentication frame Frame authFrame = FrameIO.LoadFrame("authentication1"); DisplayController.setCurrentFrame(authFrame, true); // set initial values Stream usernameItemsStream = authFrame.getTextItems().stream().filter(t -> t.getData() != null && t.getData().contains("txtUsername")); Stream passwordItemsStream = authFrame.getTextItems().stream().filter(t -> t.getData() != null && t.getData().contains("txtPassword")); usernameItemsStream.forEach(txtUsername -> txtUsername.setText(System.getProperty("startinguser.name", ""))); passwordItemsStream.forEach(txtPassword -> { txtPassword.setText(""); txtPassword.invalidateAll(); }); MessageBay.warningMessages(org.expeditee.actions.Actions.Init()); // class load database classes Class.forName("org.sqlite.JDBC"); } private void protectAdmin() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException { // Fetch desired password @SuppressWarnings("resource") Scanner in = new Scanner(System.in); System.out.println("No administrative password set."); boolean passwordIsSet = false; for (int i = 0; i < 3; i++) { System.out.print("Please enter it now: "); System.out.flush(); String password = in.nextLine(); System.out.print("And again: "); System.out.flush(); if (in.nextLine().equals(password)) { // Register account. putKey(ADMINACCOUNT, password, new SecretKeySpec("null".getBytes(), AsymmetricAlgorithm)); //in.close(); passwordIsSet = true; break; } else { System.out.println("Mismatched passwords, let's try that again."); } } if (!passwordIsSet) { System.out.println("Failed to set an admin password. Exiting Expeditee."); System.exit(1); } } private void loadKeystore() throws IOException, NoSuchAlgorithmException, CertificateException, FileNotFoundException { final File keyStoreFile = new File(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME); if (!keyStoreFile.exists()) { keyStore.load(null, "ExpediteeAuthPassword".toCharArray()); } else { try (final InputStream in = new FileInputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME)) { keyStore.load(in, "ExpediteeAuthPassword".toCharArray()); } } } final void loadMailFromFile(Path dbFile) throws SQLException { // Load in all mail. Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.toAbsolutePath().toString()); String sql = "SELECT * FROM EXPMAIL"; PreparedStatement query = c.prepareStatement(sql); ResultSet allMail = query.executeQuery(); // Construct all mail objects using content from database. while(allMail.next()) { String timestamp = allMail.getString("time"); String from = allMail.getString("snd"); String to = allMail.getString("rec"); String msg = allMail.getString("msg"); String msg2 = allMail.getString("msg2"); String[] opts = allMail.getString("opts").split(","); opts[0] = opts[0].replace("[", ""); opts[opts.length - 1] = opts[opts.length - 1].replace("]", ""); String[] optsVal = allMail.getString("optsval").split(","); optsVal[0] = optsVal[0].replace("[", ""); optsVal[optsVal.length - 1] = optsVal[optsVal.length - 1].replace("]", ""); Map options = new HashMap(); for (int i = 0, o = 0; i < opts.length && o < optsVal.length; i++, o++) { String key = opts[i].trim(); String val = optsVal[o].trim(); options.put(key, val); } Mail.MailEntry mail = new Mail.MailEntry(timestamp, from, to, msg, msg2, options); mail.deadDropSource = dbFile; Mail.addEntry(mail); } // Disconnect from database. allMail.close(); query.close(); c.close(); } public final void loadMailDatabase() throws SQLException, FileNotFoundException, ParseException { Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH); for (File connectionDir: deadDropPath.toFile().listFiles()) { if (connectionDir.isDirectory()) { Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath()); Path dbFile = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".db"); if (dbFile.toFile().exists()) { loadMailFromFile(dbFile); } clearOldMailFromDatabase(deaddropforcontactPath); } } } public final void updateLastReadMailTime(Path deaddropforcontactPath) { Path timestamp = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".last-accessed"); try(FileWriter out = new FileWriter(timestamp.toFile())) { out.write(Formatter.getDateTime() + System.getProperty("line.separator")); } catch (IOException e) { e.printStackTrace(); } } private void clearOldMailFromDatabase(Path directory) throws FileNotFoundException, ParseException, SQLException { File[] files = directory.toFile().listFiles(new FileFilter() { @Override public boolean accept(File file) { return !file.getName().startsWith(UserSettings.UserName.get()); } }); File dbFile = null; File lastAccessedFile = null; for (File file: files) { if (file.getName().endsWith(".db")) { dbFile = file; } else { lastAccessedFile = file; } } if (dbFile == null || lastAccessedFile == null) { 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. } SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]"); Date timestamp = null; try(Scanner in = new Scanner(lastAccessedFile)) { timestamp = format.parse(in.nextLine()); } catch (ParseException e) { return; // Not the end of the world if we cannot clear out old messages, the database might be empty. } Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath()); String sql = "SELECT * FROM EXPMAIL"; PreparedStatement query = c.prepareStatement(sql); ResultSet allMail = query.executeQuery(); Set oldTimestamps = new HashSet(); while (allMail.next()) { String time = allMail.getString("time"); Date messageTimestamp = format.parse(time); if (timestamp.after(messageTimestamp)) { oldTimestamps.add(time); } } if (oldTimestamps.isEmpty()) { return; } for(String oldTimestamp: oldTimestamps) { System.out.println("Deleting message with timestamp: " + oldTimestamp); sql = "DELETE FROM EXPMAIL WHERE time='" + oldTimestamp + "'"; query = c.prepareStatement(sql); query.executeUpdate(); } } public final SecretKey getSecretKey(final String label, final String password) throws NoSuchAlgorithmException, KeyStoreException { char[] password_ca = password.toCharArray(); SecretKey secret_key; try { secret_key = (SecretKey) keyStore.getKey(label, password_ca); } catch (final UnrecoverableEntryException e) { return null; } return secret_key; } public final void putKey(final String label, final String password, final SecretKey key) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException { final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(key); final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(password.toCharArray()); keyStore.setEntry(label, entry, entryPassword); keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray()); } public final boolean confirmIntergalaticNumber(final String username, final String intergalacticNumber) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException { try { KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray()); KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(username + "_IntergalacticNumber", entryPassword); if (entry == null) { return false; } else if (Arrays.equals(entry.getSecretKey().getEncoded(), TRUE)) { keyStore.deleteEntry(username + "_IntergalacticNumber"); keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray()); return true; } else { return false; } } catch (final UnrecoverableEntryException e) { return false; } } public final String newIntergalacticNumber(final String username, final String email) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException { // generate intergalactic number SecureRandom rand = new SecureRandom(); byte[] intergalacticNumberBytes = new byte[16]; rand.nextBytes(intergalacticNumberBytes); String intergalacticNumber = Base64.getEncoder().encodeToString(intergalacticNumberBytes); // store intergalactic number KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm)); KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray()); keyStore.setEntry(username + "_IntergalacticNumber", entry, entryPassword); keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray()); return intergalacticNumber; } public final PublicKey getPublicKey(String username) throws InvalidKeySpecException, NoSuchAlgorithmException, FileNotFoundException { // load in frame with public key on it. String credentialsFramesetPath = FrameIO.CONTACTS_PATH + username + "-credentials" + File.separator; if (!new File(credentialsFramesetPath).exists()) { return null; } Scanner in = new Scanner(new File(credentialsFramesetPath + "credentials.inf")); String credentialsFrameNumber = in.nextLine().replace(ExpReader.EXTENTION, ""); in.close(); Frame frame = FrameIO.LoadFrame(username + "-credentials" + credentialsFrameNumber, FrameIO.CONTACTS_PATH); if (frame == null) { return null; } // obtain public key from frame Collection canditates = org.expeditee.auth.Actions.getByContent(frame, "PublicKey"); String keyEncoded = ""; for (Item i: canditates) { if (i.getData() != null) { keyEncoded = i.getData().get(0); } } if (keyEncoded.isEmpty()) { return null; } byte[] keyBytes = Base64.getDecoder().decode(keyEncoded); return KeyFactory.getInstance(AsymmetricAlgorithm).generatePublic(new X509EncodedKeySpec(keyBytes)); } public final void markRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException { KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm)); KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray()); keyStore.setEntry(username + "colleaguesRequested", entry, entryPassword); keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray()); } public final void clearRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException { KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(FALSE, SymmetricAlgorithm)); KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray()); keyStore.setEntry(username + "colleaguesRequested", entry, entryPassword); keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray()); } public final boolean hasRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { String alias = username + "colleaguesRequested"; if (!keyStore.containsAlias(alias)) { return false; } else { KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray()); KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(alias, entryPassword); return Arrays.equals(entry.getSecretKey().getEncoded(), TRUE); } } // final void putColleagues(String username, String[] colleagues) throws KeyStoreException { // String alias = username + "colleagues"; // final SecretKeySpec secretKeySpec = new SecretKeySpec((colleagues[0] + System.getProperty("line.separator") + colleagues[1]).getBytes(), SymmetricAlgorithm); // KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(secretKeySpec); // KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray()); // keyStore.setEntry(alias, entry, entryPassword); // } // // final String[] getColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { // String alias = username + "colleagues"; // if (!keyStore.containsAlias(alias)) { // return null; // } else { // KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray()); // KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(alias, entryPassword); // byte[] colleaguesEncoded = entry.getSecretKey().getEncoded(); // String colleagues = new String(colleaguesEncoded); // return colleagues.split(System.getProperty("line.separator")); // } // } private static void setInputManagerWindowRoutines() { InputManager manager = EcosystemManager.getInputManager(); // Refresh the layout when the window resizes manager.addWindowEventListener(new WindowEventListener() { @Override public void onWindowEvent(WindowEventType type) { if (type != WindowEventType.WINDOW_RESIZED) { return; } DisplayController.refreshWindowSize(); FrameIO.RefreshCacheImages(); for (Frame frame : DisplayController.getFrames()) { if (frame != null) { ItemUtils.Justify(frame); frame.refreshSize(); } } DisplayController.requestRefresh(false); } }); manager.addWindowEventListener(new WindowEventListener() { @Override public void onWindowEvent(WindowEventType type) { if (type != WindowEventType.MOUSE_EXITED_WINDOW) { return; } StandardGestureActions.mouseExitedWindow(); } }); manager.addWindowEventListener(new WindowEventListener() { @Override public void onWindowEvent(WindowEventType type) { if (type != WindowEventType.MOUSE_ENTERED_WINDOW) { return; } StandardGestureActions.mouseEnteredWindow(); } }); manager.addWindowEventListener(new WindowEventListener() { @Override public void onWindowEvent(WindowEventType type) { if (type != WindowEventType.WINDOW_CLOSED) { return; } if (Browser._theBrowser != null) { Browser._theBrowser.exit(); } } }); } }