Index: trunk/src/org/expeditee/auth/Actions.java
===================================================================
--- trunk/src/org/expeditee/auth/Actions.java	(revision 1202)
+++ trunk/src/org/expeditee/auth/Actions.java	(revision 1202)
@@ -0,0 +1,575 @@
+package org.expeditee.auth;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableEntryException;
+import java.security.cert.CertificateException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.sql.SQLException;
+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 javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.expeditee.auth.Mail.MailEntry;
+import org.expeditee.auth.gui.MailBay;
+import org.expeditee.core.Colour;
+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.ngikm.cryptography.CryptographyConstants;
+
+public class Actions implements CryptographyConstants {
+	
+	/**
+	 * Display Expeditee Mail
+	 */
+	public static void MailMode() {
+		DisplayController.ToggleMailMode();
+	}
+ 	
+	/**
+	 * Action used to navigate the authorised user back to their desktop.
+	 */
+	public static void AuthGoToDesktop() {
+		DisplayController.setCurrentFrame(FrameIO.LoadFrame(UserSettings.HomeFrame.get()), true);
+	}
+	
+	/**
+	 * Log out of current user and navigate to authentication1
+	 */
+	public static void AuthLogout() {
+		UserSettings.Authenticated.set(false);
+		setUser(System.getProperty("user.name"));
+		DisplayController.setCurrentFrame(FrameIO.LoadFrame("authentication1"), true);
+	}
+	
+	/**
+	 * 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 Exception 
+	 */
+	public static void AuthCreateAccount() throws Exception {
+		final Collection<Text> textItems = DisplayController.getCurrentFrame().getTextItems();
+		final Optional<Map<AuthenticationTag, String>> userdata = AuthenticationTag.fetchUserData(textItems, AuthenticationTag.Username, AuthenticationTag.Password, AuthenticationTag.PasswordAgain);
+		if (userdata.isPresent()) { 
+			final Map<AuthenticationTag, String> userData = userdata.get();
+			final String password = userData.get(AuthenticationTag.Password);
+			final String passwordAgain = userData.get(AuthenticationTag.PasswordAgain);
+			if (password.compareTo(passwordAgain) == 0) {
+				createAccount(userData);
+			} else {
+				MessageBay.errorMessage("The passwords provided do not match.");
+			}
+		} else {
+			MessageBay.errorMessage("Please enter a username and password ( + repeated password) to create an account."); 
+		}
+	}
+	
+	/**
+	 * 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() throws Exception {
+		final Collection<Text> textItems = DisplayController.getCurrentFrame().getTextItems();
+		final Optional<Map<AuthenticationTag, String>> userdata = AuthenticationTag.fetchUserData(textItems, AuthenticationTag.Username, AuthenticationTag.Password);
+		if (userdata.isPresent()) { 
+			login(userdata.get());
+		}
+		else {
+			MessageBay.errorMessage("Please enter a username and password to log in.");
+		}
+	}
+	
+	/**
+	 * 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<Text> textItems = DisplayController.getCurrentFrame().getTextItems();
+		
+		if (!UserSettings.Authenticated.get()) {
+			MessageBay.errorMessage("You must be logged in to change your password.");
+		} else {
+			final Optional<Map<AuthenticationTag, String>> userdata = AuthenticationTag.fetchUserData(textItems, AuthenticationTag.Password, AuthenticationTag.NewPassword, AuthenticationTag.NewPasswordAgain);
+			if (userdata.isPresent()) {
+				final Map<AuthenticationTag, String> userData = userdata.get();
+				if (userData.get(AuthenticationTag.NewPassword).compareTo(userData.get(AuthenticationTag.NewPasswordAgain)) != 0) {
+					MessageBay.errorMessage("The passwords provided do not match.");
+				} else {
+					userData.put(AuthenticationTag.Username, UserSettings.UserName.get());
+					changePassword(userData);
+				}
+			} else {
+				MessageBay.errorMessage("Insufficient information provided to complete this action.");
+			}
+		}
+	}
+	
+	/**
+	 * Part of the process to secure an authorised account, the AuthAssociateEmail action associates a supplied email
+	 * with the currently logged in user.  
+	 */
+	public static void AuthAssociateEmail() {
+		Collection<Text> textItems = DisplayController.getCurrentFrame().getTextItems();
+		
+		if (!UserSettings.Authenticated.get()) {
+			MessageBay.errorMessage("You must be logged in to perform this action.");
+		} else { 
+			Optional<Map<AuthenticationTag, String>> userdata = AuthenticationTag.fetchUserData(textItems, AuthenticationTag.Email);
+			if (userdata.isPresent()) {
+				Map<AuthenticationTag, String> userData = userdata.get();
+				userData.put(AuthenticationTag.Username, UserSettings.UserName.get());
+				String emailRegex = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$";
+				if (!userData.get(AuthenticationTag.Email).toUpperCase().matches(emailRegex)) {
+					MessageBay.errorMessage("Invalid email address supplied.");
+				} else {
+					try {
+						associateEmail(userData);
+						DisplayController.setCurrentFrame(FrameIO.LoadFrame("authentication5"), true);
+					} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | ClassNotFoundException | IOException | SQLException  e) {
+						MessageBay.errorMessage("An unknown error has occured. Details sent to standard error.");
+						e.printStackTrace();
+					}
+				}
+			} else {
+				MessageBay.errorMessage("Insufficient information provided to complete this action.");
+			}
+		}
+	}
+	
+	/**
+	 * Navigation action for progressing the process of recruiting colleagues to assist in password recovery.
+	 * Hides certain content that AuthSubmitPWCollegues goes onto show if it does not fail.
+	 */
+	public static void AuthGotoColleagueSubmissionFrame() {
+		Frame destination = FrameIO.LoadFrame("authentication7");
+		DisplayController.setCurrentFrame(destination, true);
+		Collection<Item> toHide = getByData(destination, "ShowOnProgress");
+		for (Item i: toHide) {
+			i.setVisible(false);
+		}
+	}
+	
+	/**
+	 * 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 AuthSubmitPWColleagues() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, IOException, SQLException, InvalidKeySpecException {
+		final Frame currentFrame = DisplayController.getCurrentFrame();
+		Collection<Text> textItems = currentFrame.getTextItems();
+		
+		if (!UserSettings.Authenticated.get()) {
+			MessageBay.errorMessage("You must be logged in to perform this action.");
+		} else {
+			Optional<Map<AuthenticationTag, String>> userdata = AuthenticationTag.fetchUserData(textItems, AuthenticationTag.ColleagueOne, AuthenticationTag.ColleagueTwo);
+			if (userdata.isPresent()) {
+				Map<AuthenticationTag, String> userData = userdata.get();
+				userData.put(AuthenticationTag.Username, UserSettings.UserName.get());
+				boolean succsess = submitPWColleagues(userData);
+				if (succsess) {
+					Collection<Item> toShow = getByData(currentFrame, "ShowOnProgress");
+					for (Item i: toShow) {
+						i.setVisible(true);
+					}
+					currentFrame.change();
+				}
+				FrameIO.SaveFrame(currentFrame);
+				DisplayController.requestRefresh(false);
+			} else {
+				MessageBay.errorMessage("Insufficient information provided to complete this action.");
+			}
+		}
+	}
+	
+	public static void AuthSetupPasswordRecovery() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, IOException, SQLException, UnrecoverableEntryException {
+		if (!UserSettings.Authenticated.get()) {
+			MessageBay.errorMessage("You must be logged in to perform this action.");
+		} else if (!Authenticator.getInstance().hasRegisteredEmail(UserSettings.UserName.get())) {
+			Frame registerEmailFrame = FrameIO.LoadFrame("authentication4");
+			DisplayController.setCurrentFrame(registerEmailFrame, true);
+		} else if (!Authenticator.getInstance().hasRequestedColleagues(UserSettings.UserName.get()) && Authenticator.getInstance().getColleagues(UserSettings.UserName.get()) == null) {
+			Frame submitColleaguesFrame = FrameIO.LoadFrame("authentication5");
+			DisplayController.setCurrentFrame(submitColleaguesFrame, true);
+		} else if (Authenticator.getInstance().hasRequestedColleagues(UserSettings.UserName.get()) && Authenticator.getInstance().getColleagues(UserSettings.UserName.get()) == null) {
+			MessageBay.displayMessage("You have already nominated two colleagues to assist you in the process of password recovery and are awaiting their response."
+					+ "  You will be alerted on Expeditee startup when they have both responded.");
+		} else if (Authenticator.getInstance().getColleagues(UserSettings.UserName.get()) != null) {
+			MessageBay.displayMessage("You have completed the Password Recovery Setup process, there is nothing more to do here.");
+		}
+	}
+	
+	public static void AuthConfirmPasswordColleagueRelationship(String colleagueName) {
+		
+	}
+	
+	public static void AuthDenyPasswordColleagueRelationship(String colleagueName) throws InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, ClassNotFoundException, IOException, SQLException {
+		denyPasswordColleagueRelationship(colleagueName);
+	}
+	
+	public static void AuthClearPWColleaguesNominated() {
+		
+	}
+	
+	private static void denyPasswordColleagueRelationship(String colleagueName) throws InvalidKeySpecException, NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, ClassNotFoundException, IOException, SQLException {
+		String sender = UserSettings.UserName.get();
+		String message = "You have received a reply from " + sender + " reguarding your request for assistance.";
+		String message2 = "Unfortunately " + sender + " has indicated that they are unable to help you with your potential password recovery.";
+		Map<String, String> options = new HashMap<String, String>();
+		options.put("Clear Preview Colleague Nominations", "AuthClearPWColleaguesNominated");
+		MailEntry mail = new MailEntry(sender, colleagueName, message, message2, options);
+		Mail.sendMail(mail, Authenticator.getInstance().getPublicKey(colleagueName));
+	}
+	
+	private static boolean submitPWColleagues(Map<AuthenticationTag, String> userData) throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, IOException, SQLException, InvalidKeySpecException {
+		// Confirm both colleagues exist
+		String colleagueOne = userData.get(AuthenticationTag.ColleagueOne);
+		String colleagueTwo = userData.get(AuthenticationTag.ColleagueTwo);
+		PublicKey colleagueOneKey = null;
+		PublicKey colleagueTwoKey = null;
+		if (!FrameIO.canAccessFrameset(colleagueOne) && FrameIO.canAccessFrameset(colleagueTwo)) {
+			MessageBay.errorMessage("At least one of the colleagues you specified does not have a Expeditee profile.");
+			return false;
+		} else if ((colleagueOneKey = Authenticator.getInstance().getPublicKey(colleagueOne)) == null || 
+				(colleagueTwoKey = Authenticator.getInstance().getPublicKey(colleagueTwo)) == null) {
+			MessageBay.errorMessage("At least one of the colleagues you specified is not set up to be authorised.");
+			return false;
+		} else {
+			String sender = UserSettings.UserName.get();
+			String message = "You have received a request for cooperation from your colleague " + sender;
+			String message2 = "\"I would like to receive your help if I happen to forget my password.\"";
+			Map<String, String> arguments = new HashMap<String, String>();
+			arguments.put("I Agree To Assist " + sender + " If They Forget Their Password" , "AuthConfirmPasswordColleagueRelationship " + sender);
+			arguments.put("I Wish To Excuse Myself From This Responsibility", "AuthDenyPasswordColleagueRelationship " + sender);
+			MailEntry mail = new MailEntry(sender, colleagueOne, message, message2, arguments);
+			Mail.sendMail(mail, colleagueOneKey);
+			mail = new MailEntry(sender, colleagueTwo, message, message2, arguments);
+			Mail.sendMail(mail, colleagueTwoKey);
+			Authenticator.getInstance().markRequestedColleagues(UserSettings.UserName.get());
+			return true;
+		}
+	}
+
+	/*
+	 * Function used to authenticate as a specified user (via function arguments).
+	 */
+	private static void login(final Map<AuthenticationTag, String> userdata) throws Exception {
+		final String username = userdata.get(AuthenticationTag.Username);
+		final String password = userdata.get(AuthenticationTag.Password);
+		
+		final SecretKey personalKey = Authenticator.getInstance().getSecretKey(username, password);
+		if (personalKey == null) {
+			// Incorrect username and password
+			MessageBay.errorMessage("The username + password combination was incorrect.");
+		} else {
+			// Proceed with login process.
+			org.expeditee.settings.auth.secrets.KeyList.PersonalKey.setSetting(new Text(Base64.getEncoder().encodeToString(personalKey.getEncoded())));
+			setUser(username);
+			MessageBay.displayMessage("Logged in as: " + UserSettings.UserName.get());
+			
+			// Set Public Key and private key in settings datastructure
+			String profileName = UserSettings.ProfileName.get();
+			int lastNumber = FrameIO.getLastNumber(profileName);
+			for (int i = 1; i <= lastNumber; i++) {
+				Frame frame = FrameIO.LoadFrame(profileName + i);
+				if (frame == null) {
+					continue;
+				}
+				Collection<Item> items = frame.getAllItems();
+				boolean foundPublic = false;
+				boolean foundPrivate = false;
+				for (Item item: items) {
+					if (item.getText().compareTo("PublicKey") == 0) {
+						org.expeditee.settings.auth.KeyList.PublicKey.set((Text) item);
+						foundPublic = true;
+					} else if (item.getText().compareTo("PrivateKey") == 0) {
+						org.expeditee.settings.auth.secrets.KeyList.PrivateKey.set((Text) item);
+						foundPrivate = true;
+					}
+				}
+				if (foundPublic && foundPrivate) { break; }
+			}
+						
+			// Check mail
+			MailBay.clear();
+			Authenticator.getInstance().loadMailDatabase();
+			Text keyItem = org.expeditee.settings.auth.secrets.KeyList.PrivateKey.get();
+			if (keyItem.getData() != null) {
+				String keyEncoded = keyItem.getData().get(0);
+				byte[] keyBytes = Base64.getDecoder().decode(keyEncoded);
+				PrivateKey key = KeyFactory.getInstance(AsymmetricAlgorithm).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
+				List<MailEntry> mailForLoggingInUser = Mail.getEntries(UserSettings.UserName.get(), key);
+				for (MailEntry mail: mailForLoggingInUser) {
+					MailBay.addMessage(mail.message, mail.message2, mail.options);
+				}
+			}
+			
+			UserSettings.Authenticated.set(true);
+			// navigate to logged in profile
+			DisplayController.setCurrentFrame(loadProfile(username), false);
+		}
+	}
+	
+	/*
+	 * Creates an account from specified user data.
+	 */
+	private static void createAccount(final Map<AuthenticationTag, String> userdata) throws Exception {
+		String username = userdata.get(AuthenticationTag.Username);
+		String password = userdata.get(AuthenticationTag.Password);
+		if (FrameIO.LoadProfile(username) != null) {
+			MessageBay.errorMessage("A Expeditee profile with this username already exists, please choose another.");
+		} else {				
+			// generate the newly created user a profile frame using the specialized default frame as a blueprint
+			String defaultBackup = UserSettings.DEFAULT_PROFILE_NAME;
+			UserSettings.DEFAULT_PROFILE_NAME = "multiusermodedefault";
+			Frame profile = FrameIO.CreateNewProfile(username);
+			UserSettings.DEFAULT_PROFILE_NAME = defaultBackup;
+			
+			// perform necessary changes to generated frame: remove default title, set background color, set custom title 
+			profile.getTitleItem().delete();
+			profile.setBackgroundColor(new Colour(1, 1, 0.39f));
+			getByData(profile, "txtUsername").stream().forEach(i -> i.setText(username));
+			getByText(profile, "“If Aphroditē be the godess of love").stream().forEach(i -> i.setWidth(300));
+			getByText(profile, "Sleek like Aphroditē, Wise like Athēnâ.").stream().forEach(i -> i.setWidth(300));
+			
+			// update ownership
+			int lastNumber = FrameIO.getLastNumber(profile.getFramesetName());
+			for (int i = 1; i <= lastNumber; i++) {
+				Frame frame = FrameIO.LoadFrame(profile.getFramesetName() + i);
+				frame.setOwner(username);
+				Collection<Item> items = frame.getAllItems();
+				for (Item item: items) {
+					String newText = item.getText().replace("authadmin", username);
+					item.setText(newText);
+				}
+			}
+			
+			// generate and store personal, public and private keys
+			String backupUser = UserSettings.UserName.get();
+			setUser(username);
+			UserSettings.Authenticated.set(true);
+			
+			// personal key
+			Random rand = new Random();
+			byte[] keyBytes = new byte[16];
+			rand.nextBytes(keyBytes);
+			SecretKey key = new SecretKeySpec(keyBytes, SymmetricAlgorithm);
+			Authenticator.getInstance().putKey(username, password, key);
+			org.expeditee.settings.auth.secrets.KeyList.PersonalKey.setSetting(new Text(Base64.getEncoder().encodeToString(key.getEncoded())));
+			
+			// public+private key pair
+			KeyPairGenerator keyGen = KeyPairGenerator.getInstance(AsymmetricAlgorithm);
+			keyGen.initialize(1024);
+			KeyPair keyPair = keyGen.generateKeyPair();
+			
+			org.expeditee.settings.auth.secrets.KeyList.PrivateKey.setSetting(new Text(Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())));
+			org.expeditee.settings.auth.KeyList.PublicKey.setSetting(new Text(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())));
+			
+			// ensure save of keys
+			lastNumber = FrameIO.getLastNumber(profile.getFramesetName());
+			for (int i = 1; i <= lastNumber; i++) {
+				Frame frame = FrameIO.LoadFrame(profile.getFramesetName() + i);
+				frame.setChanged(true);
+				FrameIO.SaveFrame(frame);
+			}
+			setUser(backupUser);
+			UserSettings.Authenticated.set(false);
+			
+			// proceed to log into new account.
+			login(userdata);
+		}
+	}
+	
+	/*
+	 * Changes the recorded password for a user in the key store.
+	 */
+	private static void changePassword(final Map<AuthenticationTag, String> userdata) throws NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException {
+		final String username = userdata.get(AuthenticationTag.Username);
+		final String password = userdata.get(AuthenticationTag.Password);
+		final String newpassword = userdata.get(AuthenticationTag.NewPassword);
+
+		final SecretKey key = Authenticator.getInstance().getSecretKey(username, password);
+		if (key == null) {
+			MessageBay.errorMessage("The username + existing password combination was incorrect.");
+		} else {
+			Authenticator.getInstance().putKey(username, newpassword, key);
+			MessageBay.displayMessage("Password changed successfully.");
+		}
+	}
+	
+	private static void associateEmail(Map<AuthenticationTag, String> userdata) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, ClassNotFoundException, IOException, SQLException {
+		Authenticator.getInstance().putEmail(
+				userdata.get(AuthenticationTag.Username), 
+				userdata.get(AuthenticationTag.Email));
+	}
+
+	private static void setUser(final String username) {
+		UserSettings.UserName.set(username);
+		UserSettings.ProfileName.set(username);
+		UserSettings.HomeFrame.set(username + "1");
+		UserSettings.UserName.setDefault(username);
+		UserSettings.ProfileName.setDefault(username);
+		UserSettings.HomeFrame.setDefault(username + "1");
+	}
+	
+	private static Frame loadProfile(final String username) throws Exception {
+		final Frame frameOne = FrameIO.LoadProfile(username);
+		if (frameOne != null) { 
+			return frameOne;
+		} else {
+			final Frame profile = FrameIO.CreateNewProfile(username);
+			for (int i = 0; i < FrameIO.getLastNumber(profile.getFramesetName()); i++) {
+				final Frame frame = FrameIO.LoadFrame(profile.getFramesetName() + i);
+				final Collection<Text> textItems = frame.getTextItems();
+				for (final Text t: textItems) {
+					if (t.getText().startsWith("User name:")) {
+						t.setText("User name: " + UserSettings.UserName.get());
+					} else if (t.getText().startsWith("Profile name:")) {
+						t.setText("Profile name: " + UserSettings.ProfileName.get());
+					}
+				}
+				FrameIO.ForceSaveFrame(frame);
+			}
+			return profile;
+		}
+	}
+
+//	public static void AuthRecoverPasswordStage1() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
+//		final Collection<Text> textItems = DisplayController.getCurrentFrame().getTextItems();
+//		final Optional<Text> txtUsername = textItems.stream().filter(t -> t.getData() != null && t.getData().contains("txtUsername")).findFirst();
+//		final Optional<Text> txtRecoveryEmail = textItems.stream().filter(t -> t.getData() != null && t.getData().contains("txtRecoveryEmail")).findFirst();
+//		final String username = txtUsername.isPresent() ? txtUsername.get().getText() : System.getProperty("user.name");
+//		final String email = txtRecoveryEmail.isPresent() ? txtRecoveryEmail.get().getText() : System.getProperty("user.recoveryemail");
+//		if (areAllPresent(new String[] { username, email })) {
+//			// generate intergalactic number
+//			final String intergalacticNumber = Authenticator.getInstance().newIntergalacticNumber(username, email);
+//
+//			// establish properties
+//			final String from = "noreply@expeditee.com";
+//			final Properties properties = System.getProperties();
+//			
+//			properties.setProperty("mail.transport.protocol", "smtp");
+//			properties.setProperty("mail.smtp.host", "smtp.gmail.com");
+//			properties.setProperty("mail.smtp.port", "465");
+//			properties.setProperty("mail.smtp.starttls.enable", "true");
+//			properties.setProperty("mail.smtp.auth", "true");
+//			properties.setProperty("mail.smtp.debug", "true");
+//			properties.setProperty("mail.smtp.auth", "true");
+//			properties.setProperty("mail.smtp.socketFactory.port", "465");
+//			properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+//			properties.setProperty("mail.smtp.socketFactory.fallback", "false");
+//			
+//			final Session session = Session.getDefaultInstance(properties, new javax.mail.Authenticator() {
+//				@Override
+//				protected PasswordAuthentication getPasswordAuthentication() {
+//					return new PasswordAuthentication("noreply.expeditee", "intergalacticnumber");
+//				};
+//			});
+//
+//			try {
+//				// construct email message
+//				final MimeMessage message = new MimeMessage(session);
+//				message.setFrom(new InternetAddress(from));
+//				message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
+//				message.setSubject("Expeditee Password Recovery");
+//				message.setText(intergalacticNumber);
+//				
+//				// send email message
+//				Transport.send(message);
+//			} catch (final MessagingException e) {
+//				e.printStackTrace();
+//			}
+//			
+//			FrameUtils.DisplayFrame("4", false, true);
+//		} else {
+//			MessageBay.errorMessage("To recover a lost password please fill in all the provided fields.");
+//		}
+//	}
+//	
+//	public static void AuthRecoverPasswordStage2() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
+//		final Collection<Text> textItems = DisplayController.getCurrentFrame().getTextItems();
+//		final Optional<Text> txtUsername = textItems.stream().filter(t -> t.getData() != null && t.getData().contains("txtUsername")).findFirst();
+//		final Optional<Text> txtRecoveryEmail = textItems.stream().filter(t -> t.getData() != null && t.getData().contains("txtRecoveryEmail")).findFirst();
+//		final Optional<Text> txtIntergalacticNumber = textItems.stream().filter(t -> t.getData() != null && t.getData().contains("txtIntergalacticNumber")).findFirst();
+//		final String username = txtUsername.isPresent() ? txtUsername.get().getText() : System.getProperty("user.name");
+//		final String email = txtRecoveryEmail.isPresent() ? txtRecoveryEmail.get().getText() : System.getProperty("user.recoveryemail");
+//		final String intergalacticNumber = txtIntergalacticNumber.isPresent() ? txtIntergalacticNumber.get().getText() : System.getProperty("user.intergalacticnumber");
+//		if (areAllPresent(new String[] { username, email, intergalacticNumber })) {
+//			// confirm intergalactic number matches what is stored
+//			final boolean intergalaticNumberMatches = Authenticator.getInstance().confirmIntergalaticNumber(username, email, intergalacticNumber);
+//			if (intergalaticNumberMatches) { 
+//				final Frame changePasswordFrame = FrameIO.LoadFrame("5");
+//				final Optional<Text> actionNewPassword = changePasswordFrame.getTextItems().stream().filter(t -> t.getAction().contains("AuthNewPassword")).findFirst();
+//				if (actionNewPassword.isPresent()) {
+//					actionNewPassword.get().setData(intergalacticNumber);
+//				}
+//				FrameUtils.DisplayFrame("5", false, true);
+//			}
+//		} else {
+//			MessageBay.errorMessage("To recover a lost password please fill in all the provided fields.");
+//		}
+//	}
+
+	public static void TickBox(final Text item) {
+		if (item.getBackgroundColor() != Colour.RED) {
+			item.setBackgroundColor(Colour.RED);
+		} else {
+			item.setBackgroundColor(Colour.GREEN);
+		}
+	}
+	
+	/*
+	 *  Gets all items on a specified frame that contain the specified text.
+	 */
+	private static Collection<Item> getByText(final Frame frame, final String text) {
+		final Collection<Item> allItems = frame.getAllItems();
+		allItems.removeIf(i -> i.getText() == null || !i.getText().contains(text));
+		return allItems;
+	}
+	
+	/*
+	 * Gets all items on a specified frame that contain the specified data.
+	 */
+	private static Collection<Item> getByData(final Frame frame, final String data) {
+		final Collection<Item> allItems = frame.getAllItems();
+		allItems.removeIf(i -> i.getData() == null || !i.hasData(data));
+		return allItems;
+	}
+}
Index: trunk/src/org/expeditee/auth/AuthenticationTag.java
===================================================================
--- trunk/src/org/expeditee/auth/AuthenticationTag.java	(revision 1202)
+++ trunk/src/org/expeditee/auth/AuthenticationTag.java	(revision 1202)
@@ -0,0 +1,52 @@
+package org.expeditee.auth;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.expeditee.items.Text;
+
+public enum AuthenticationTag {
+	Username ("txtUsername", System.getProperty("user.name")), 
+	Password ("txtPassword", System.getProperty("user.password")), 
+	PasswordAgain ("txtPasswordAgain", System.getProperty("user.password.again")), 
+	NewPassword ("txtNewPassword", System.getProperty("user.new.password")), 
+	NewPasswordAgain ("txtNewPasswordAgain", System.getProperty("user.new.password.again")), 
+	Email ("txtEmail", System.getProperty("user.email")), 
+	ColleagueOne ("txtColleagueOne", System.getProperty("user.colleague.one")), 
+	ColleagueTwo ("txtColleagueTwo", System.getProperty("user.colleague.two"));
+	
+	public String val;
+	public String def;
+
+	private AuthenticationTag(final String tag, final String def) {
+		this.val = tag;
+		this.def = def;		
+	}
+	
+	public static Optional<Map<AuthenticationTag, String>> fetchUserData(final Collection<Text> from, final AuthenticationTag... tags) {
+		final Map<AuthenticationTag, String> fetchedData = new HashMap<AuthenticationTag, String>();
+		
+		for (final AuthenticationTag tag: tags) {
+			for (final Text canditate: from) {
+				// use the canditate if it exists
+				if (canditate.hasData(tag.val)) {
+					fetchedData.put(tag, canditate.getText());
+					break;
+				}
+				// otherwise try use the default
+				final String property = tag.def;
+				if (property != null && !property.isEmpty()) { 
+					fetchedData.put(tag, property); 
+				}
+			}
+		}
+		
+		if (fetchedData.entrySet().size() == tags.length) {
+			return Optional.of(fetchedData);
+		} else { 
+			return Optional.empty();
+		}
+	}
+}
Index: trunk/src/org/expeditee/auth/Authenticator.java
===================================================================
--- trunk/src/org/expeditee/auth/Authenticator.java	(revision 1201)
+++ trunk/src/org/expeditee/auth/Authenticator.java	(revision 1202)
@@ -5,28 +5,173 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Writer;
+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.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
 
 import javax.crypto.SecretKey;
-
+import javax.crypto.spec.SecretKeySpec;
+
+import org.expeditee.actions.Actions;
+import org.expeditee.core.Dimension;
+import org.expeditee.core.Point;
+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.items.ItemUtils;
+import org.expeditee.items.Text;
+import org.expeditee.settings.Settings;
+import org.expeditee.settings.UserSettings;
+import org.expeditee.settings.auth.secrets.KeyList;
 import org.ngikm.cryptography.CryptographyConstants;
 
 public final class Authenticator implements CryptographyConstants {
-	final KeyStore keyStore = KeyStore.getInstance(KeystoreType);
-	private static final String keystoreFileName = FrameIO.PARENT_FOLDER + "keystore.ks" + File.separator;
-	
-	public Authenticator() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, CertificateException {
-		final File keyStoreFile = new File(keystoreFileName);
+	
+	// The frame number of the frame containing the current authenticated users public key.
+	public static final int PUBLIC_KEY_FRAME = 4;
+	
+	private final KeyStore keyStore = KeyStore.getInstance(KeystoreType);
+	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 Authenticator instance;
+	
+	public static Authenticator getInstance() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException, ClassNotFoundException, SQLException {
+		if (instance == null) { instance = new Authenticator(); } 
+		return instance;
+	}
+	
+	private Authenticator() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, SQLException {
+		UserSettings.setupDefaultFolders();
+		
+		// initialise keystore and actions
+		loadKeystore();
+		Actions.LoadMethods(org.expeditee.auth.Actions.class);
+
+		// draw the window
+		final 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
+		final Frame authFrame = FrameIO.LoadFrame("authentication1");
+		DisplayController.setCurrentFrame(authFrame, true);
+		
+		// set initial values
+		final Stream<Text> usernameItemsStream = authFrame.getTextItems().stream().filter(t -> t.getData() != null && t.getData().contains("txtUsername"));
+		final Stream<Text> passwordItemsStream = authFrame.getTextItems().stream().filter(t -> t.getData() != null && t.getData().contains("txtPassword"));
+		usernameItemsStream.forEach(txtUsername -> txtUsername.setText(System.getProperty("user.name", "")));
+		passwordItemsStream.forEach(txtPassword -> txtPassword.setText(System.getProperty("user.password", "")));
+						
+		MessageBay.warningMessages(org.expeditee.actions.Actions.Init());
+		
+		// ensure database
+		Class.forName("org.sqlite.JDBC");
+		if (!mailDatabaseExists()) { 
+			createMailDatabase();
+		}
+	}
+	
+	private void createMailDatabase() throws ClassNotFoundException, SQLException {
+		Connection c = DriverManager.getConnection("jdbc:sqlite:" + FrameIO.PARENT_FOLDER + "/expmail.db");
+		Statement createTable = c.createStatement();
+		String sql = "CREATE TABLE EXPMAIL (" + 
+				"SND		TEXT					NOT NULL, " + 
+				"REC		TEXT					NOT NULL, " + 
+				"MSG		TEXT					NOT NULL, " +
+				"MSG2		TEXT					NOT NULL, " + 
+				"OPTS		ARRAY					NOT NULL, " +
+				"OPTSVAL	ARRAY					NOT NULL)";
+		createTable.executeUpdate(sql);
+		createTable.close();
+		c.close();
+	}
+	
+	final void loadMailDatabase() throws SQLException {
+		Connection c = DriverManager.getConnection("jdbc:sqlite:" + FrameIO.PARENT_FOLDER + "/expmail.db");
+		Statement query = c.createStatement();
+		ResultSet results = query.executeQuery("SELECT * FROM EXPMAIL");
+		Mail.clear();
+		while (results.next()) {
+			String from = results.getString("snd");
+			String to = results.getString("rec");
+			String msg = results.getString("msg");
+			String msg2 = results.getString("msg2");
+			String[] opts = results.getString("opts").split(",");
+			opts[0] = opts[0].replace("[", "");
+			opts[opts.length - 1] = opts[opts.length - 1].replace("]", "");
+			String[] optsVal = results.getString("optsval").split(",");
+			optsVal[0] = optsVal[0].replace("[", "");
+			optsVal[optsVal.length - 1] = optsVal[optsVal.length - 1].replace("]", "");
+			
+			Map<String, String> options = new HashMap<String, String>();
+			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.addEntry(new Mail.MailEntry(from, to, msg, msg2, options));
+		}
+		results.close();
+		query.close();
+		c.close();
+	}
+	
+	private boolean mailDatabaseExists() {
+		final File file = new File(FrameIO.PARENT_FOLDER + "/expmail.db");
+		return file.exists();
+	}
+		
+	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(keystoreFileName)) {
+			try (final InputStream in = new FileInputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME)) {
 				keyStore.load(in, "ExpediteeAuthPassword".toCharArray());
 			}
@@ -34,16 +179,199 @@
 	}
 	
-	public final SecretKey getSecretKey(final String label, final String password) throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException {		
+	final SecretKey getSecretKey(final String label, final String password) throws NoSuchAlgorithmException, KeyStoreException {		
 		final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(password.toCharArray());
-		final KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(label, entryPassword);
+		KeyStore.SecretKeyEntry entry = null;
+		try {
+			entry = (SecretKeyEntry) keyStore.getEntry(label, entryPassword);
+		} catch (final UnrecoverableEntryException e) {
+			entry = null;
+		}
 		if (entry == null) { return null; }
 		else { return entry.getSecretKey(); }
 	}
 	
-	public final void putKey(final String label, final String password, final SecretKey key) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
+	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(keystoreFileName), "ExpediteeAuthPassword".toCharArray());
+		keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray());
+	}
+	
+	final void putEmail(String username, String email) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
+		// Establish location of email file
+		String emailFilePath = FrameIO.PROFILE_PATH + username + File.separator + username + ".email";
+		File emailFile = new File(emailFilePath);
+		
+		// Delete old version if it exists.
+		if (emailFile.exists()) { 
+			emailFile.delete();
+		}
+		
+		// Write email to file
+		emailFile.createNewFile();
+		Writer w = new FileWriter(emailFile);
+		w.write(email);
+		w.flush();
+		w.close();
+		
+		// TODO: set rights on file to be read only by 'owner' once installed on drive.
+	}
+	
+	final boolean hasRegisteredEmail(String username) throws KeyStoreException {
+		String emailFilePath = FrameIO.PROFILE_PATH + username + File.separator + username + ".email";
+		File emailFile = new File(emailFilePath);
+		return emailFile.exists();
+	}
+	
+	final boolean confirmIntergalaticNumber(final String username, final String email, final String intergalacticNumber) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
+		try {
+			final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray());
+			final KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(email + username, entryPassword);
+			if (entry == null) { 
+				return false;
+			} else if (entry.getSecretKey().getEncoded() == TRUE) {
+				keyStore.deleteEntry(email + username);
+				keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray());
+				return true;
+			} else { return false; }
+		} catch (final UnrecoverableEntryException e) {
+			return false;
+		}
+	}
+	
+	final String newIntergalacticNumber(final String username, final String email) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
+		// generate intergalactic number
+		final SecureRandom rand = new SecureRandom();
+		final byte[] intergalacticNumberBytes = new byte[20];
+		rand.nextBytes(intergalacticNumberBytes);
+		final String intergalacticNumber = Base64.getEncoder().encodeToString(intergalacticNumberBytes);
+		
+		// store intergalactic number
+		final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm));
+		final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray());
+		keyStore.setEntry(email + username, entry, entryPassword);
+		keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), "ExpediteeAuthPassword".toCharArray());
+		
+		return intergalacticNumber;
+	}
+	
+	final PublicKey getPublicKey(String username) throws InvalidKeySpecException, NoSuchAlgorithmException {
+		Frame withTargetPublic = FrameIO.LoadFrame(username + Authenticator.PUBLIC_KEY_FRAME);
+		Collection<Text> textItems = withTargetPublic.getTextItems(); 
+		textItems.removeIf(t -> !t.getText().startsWith("PublicKey"));
+		
+		if (textItems.isEmpty()) {
+			return null;
+		} else {
+			String keyEncoded = textItems.stream().findFirst().get().getData().get(0);
+			byte[] keyBytes = Base64.getDecoder().decode(keyEncoded);
+			PublicKey key = KeyFactory.getInstance(AsymmetricAlgorithm).generatePublic(new X509EncodedKeySpec(keyBytes));
+			return key;
+		}
+	}
+	
+	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());
+	}
+	
+	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());
+	}
+	
+	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();
+				}
+			}
+		});
 	}
 }
Index: trunk/src/org/expeditee/auth/EncryptedExpReader.java
===================================================================
--- trunk/src/org/expeditee/auth/EncryptedExpReader.java	(revision 1202)
+++ trunk/src/org/expeditee/auth/EncryptedExpReader.java	(revision 1202)
@@ -0,0 +1,205 @@
+package org.expeditee.auth;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.expeditee.gui.Frame;
+import org.expeditee.io.ExpReader;
+import org.expeditee.items.Text;
+import org.expeditee.settings.auth.secrets.KeyList;
+import org.ngikm.Util.ThrowingFunction;
+import org.ngikm.cryptography.CryptographyConstants;
+
+public class EncryptedExpReader extends ExpReader implements CryptographyConstants {
+	static final String ENCRYPTED_EXP_FLAG = "EncryptedExp";
+	private SecretKey personalKey;
+	private List<SecretKey> multiKey;
+	private boolean accessDenied = false;
+	private boolean usePersonalKey;
+	
+	public EncryptedExpReader(final String frameName, final boolean usePersonalKey) throws UnsupportedEncodingException {
+		super(frameName);
+		this.usePersonalKey = usePersonalKey;
+	}
+	
+	public static boolean isEncryptedExpediteeFile(final String path) throws IOException {
+		final BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));
+		final String firstLine = in.readLine();
+		in.close();
+		return firstLine.startsWith(ENCRYPTED_EXP_FLAG);
+	}
+	
+	public int getVersionEnc(String fullpath) {
+		try {
+			BufferedReader reader;
+			if (usePersonalKey) {
+				reader = new EncryptedProfileLineReader(new BufferedReader(new FileReader(fullpath)));
+			} else {
+				reader = new EncryptedLineReader(new BufferedReader(new FileReader(fullpath)));
+			}
+			String next = "";
+			// First read the header lines until we get the version number
+			while (reader.ready() && !(next = reader.readLine()).equals("Z")) {
+				if (isValidLine(next)) {
+					Character tag = getTag(next);
+					String value = getValue(next);
+					if (tag.equals('V')) {
+						reader.close();
+						return Integer.parseInt(value);
+					}
+				}
+			}
+			reader.close();
+		} catch (Exception e) {
+		}
+		return -1;
+	}
+	
+	@Override
+	public Frame readFrame(final String fullPath) throws IOException {
+		final Reader in = new InputStreamReader(new FileInputStream(fullPath), "UTF-8");
+		if (usePersonalKey) {
+			return readFrame(new EncryptedProfileLineReader(new BufferedReader(in)));
+		} else {
+			return readFrame(new EncryptedLineReader(new BufferedReader(in)));
+		}
+	}
+	
+	@Override
+	public Frame readFrame(final BufferedReader reader) throws IOException {
+		if (accessDenied) { return null; }
+		else return super.readFrame(reader);
+	}
+	
+	private static byte[] DecryptSymmetric(final byte[] toDecrypt, final SecretKey key) {
+		try {
+			final Cipher cipher = Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters);
+			cipher.init(Cipher.DECRYPT_MODE, key);
+			final byte[] decryptedBytes = cipher.doFinal(toDecrypt);
+			int indexOfZero = decryptedBytes.length - 1;
+			for (int i = decryptedBytes.length - 1; i >= 0; i--) {
+				if (decryptedBytes[i] != (byte) 0) {
+					indexOfZero = i + 1;
+					break;
+				}
+			}
+			if (indexOfZero < 0) { return decryptedBytes; }
+			else { return Arrays.copyOf(decryptedBytes, indexOfZero); }
+		} catch (final NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
+				| IllegalBlockSizeException | BadPaddingException e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+	
+	private class EncryptedProfileLineReader extends BufferedReader {
+
+		public EncryptedProfileLineReader(final Reader in) {
+			super(in);
+		}
+		
+		@Override
+		/**
+		 * Reads a line from an encrypted exp file that uses profile encryption (single key; personal key)
+		 * Returns that line to process, null if the currently logged in users personal key is not the appropriate one (access denied).
+		 */
+		public String readLine() throws IOException {
+			// read encrypted line
+			final String line = super.readLine();
+			
+			if (line.isEmpty()) { return ""; }
+			if (line.startsWith(ENCRYPTED_EXP_FLAG)) {
+				// record/overwrite previous personal key then ignore this line by recursing
+				final Text text = KeyList.PersonalKey.get();
+				final byte[] keyBytes = Base64.getDecoder().decode(text.getData().get(0));
+				personalKey = new SecretKeySpec(keyBytes, SymmetricAlgorithm);
+				return readLine();
+			}
+			
+			// decrypt line and return result
+			final byte[] toDecrypt = Base64.getDecoder().decode(line);
+			final byte[] decrypted = DecryptSymmetric(toDecrypt, personalKey);
+			if (decrypted == null) {
+				accessDenied = true;
+				return null; // access denied
+			} else {
+				final String decryptedLine = new String(decrypted);
+				if (decryptedLine.startsWith("Z")) { return decryptedLine.trim(); }
+				else { return decryptedLine; }
+			}
+		}	
+	}
+	
+	private class EncryptedLineReader extends BufferedReader {
+		
+		public EncryptedLineReader(final Reader in) {
+			super(in);
+		}
+		
+		@Override
+		/**
+		 * Reads a line from an encrypted exp file that uses (potentially multiple) labeled keys
+		 * Returns that line to process, null if the ...
+		 */
+		public String readLine() throws IOException {
+			// read encrypted line
+			final String line = super.readLine();
+			
+			if (line.isEmpty()) { return ""; }
+			if (line.startsWith(ENCRYPTED_EXP_FLAG)) {
+				// resolve labels to secret keys
+				final List<String> labels = Arrays.asList(line.split(" ")).stream().skip(1).collect(Collectors.toList());
+				final ThrowingFunction<String, SecretKey, Exception> worker = new ThrowingFunction<String, SecretKey, Exception>() {
+					@Override
+					public SecretKey applyThrows(final String label) throws Exception {
+						return Authenticator.getInstance().getSecretKey(label, System.getProperty("password"));
+					}
+				};
+				multiKey = labels.stream().map(l -> {
+					try {
+						return worker.apply(l);
+					} catch (final Exception e) {
+						return null;
+					}
+				}).collect(Collectors.toList());
+				
+				// confirm you have all the keys necessary for decryption
+				if (multiKey.contains(null)) {
+					return null;
+				}
+				
+				// move onto the next line
+				return readLine();
+			}
+			
+			// decrypt line and return result
+			final byte[] toDecrypt = Base64.getDecoder().decode(line);
+			byte[] decryptedBytes = null;
+			for (final SecretKey key: multiKey) {
+				decryptedBytes = DecryptSymmetric(toDecrypt, key);
+				if (decryptedBytes == null) { return null; }
+			}
+			final String decrypted = new String(decryptedBytes);
+			if (decrypted.startsWith("Z")) { return decrypted.trim(); }
+			else { return decrypted; }
+		}
+	}
+}
Index: trunk/src/org/expeditee/auth/EncryptedProfileExpWriter.java
===================================================================
--- trunk/src/org/expeditee/auth/EncryptedProfileExpWriter.java	(revision 1202)
+++ trunk/src/org/expeditee/auth/EncryptedProfileExpWriter.java	(revision 1202)
@@ -0,0 +1,72 @@
+package org.expeditee.auth;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.expeditee.io.ExpWriter;
+import org.expeditee.items.Text;
+import org.expeditee.settings.auth.secrets.KeyList;
+import org.ngikm.cryptography.CryptographyConstants;
+
+public class EncryptedProfileExpWriter extends ExpWriter implements CryptographyConstants {
+	private SecretKey personalKey;
+	private static final String nl = "\n";
+	
+	public EncryptedProfileExpWriter() throws IOException {
+		// obtain personal key
+		final Text text = KeyList.PersonalKey.get();
+		final byte[] keyBytes = Base64.getDecoder().decode(text.getData().get(0));
+		personalKey = new SecretKeySpec(keyBytes, SymmetricAlgorithm);		
+	}
+
+	@Override
+	protected void preOutputFrame() {
+		try {
+			final String line = EncryptedExpReader.ENCRYPTED_EXP_FLAG + nl;
+			_writer.write(line);
+			_stringWriter.append(line);
+		} catch (final IOException e) { 
+			e.printStackTrace();
+		}
+	}
+	
+	@Override
+	protected void writeLine(String line) throws IOException {
+		// do not write empty lines
+		if (line == null) { return; }
+		
+		// prepare line to write out
+		final byte[] encrypted = EncryptSymmetric(line.getBytes(), personalKey);
+		final String toWrite = Base64.getEncoder().encodeToString(encrypted) + nl;
+		
+		// output
+		_writer.write(toWrite);
+		_stringWriter.append(toWrite);
+	}
+	
+	private static byte[] EncryptSymmetric(final byte[] toEncrypt, final SecretKey key) {
+		try {
+			final Cipher cipher = Cipher.getInstance(SymmetricAlgorithm + SymmetricAlgorithmParameters);
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			//could use modulus
+			final int length = (int) ((Math.ceil(toEncrypt.length / 16f)) * 16);
+			final byte[] toEncryptSizeAdjusted = Arrays.copyOf(toEncrypt, length);
+			final byte[] result = cipher.doFinal(toEncryptSizeAdjusted);
+			return result;
+		} catch (final NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
+				| IllegalBlockSizeException | BadPaddingException e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+}
Index: trunk/src/org/expeditee/auth/Mail.java
===================================================================
--- trunk/src/org/expeditee/auth/Mail.java	(revision 1202)
+++ trunk/src/org/expeditee/auth/Mail.java	(revision 1202)
@@ -0,0 +1,150 @@
+package org.expeditee.auth;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import org.expeditee.gui.FrameIO;
+import org.ngikm.cryptography.CryptographyConstants;
+
+public class Mail implements CryptographyConstants {
+	
+	private static List<MailEntry> messages = new ArrayList<MailEntry>();
+	
+	/**
+	 * Add a piece of mail, used during initialisation.
+	 */
+	public static void addEntry(MailEntry mail) {
+		messages.add(mail);
+	}
+	
+	public static void clear() {
+		messages.clear();
+	}
+	
+	public static void sendMail(MailEntry mail, PublicKey key) {
+		try {
+			Cipher cipher = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters);
+			
+			// encrypt the necessary parts of the message
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			String sender = Base64.getEncoder().encodeToString(cipher.doFinal(mail.sender.getBytes()));
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			String rec = Base64.getEncoder().encodeToString(cipher.doFinal(mail.receiver.getBytes()));
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			String message = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message.getBytes()));
+			cipher.init(Cipher.ENCRYPT_MODE, key);
+			String message2 = Base64.getEncoder().encodeToString(cipher.doFinal(mail.message2.getBytes()));
+			
+			Map<String, String> options = new HashMap<String, String>(); 
+			for (String label: mail.options.keySet()) {
+				cipher.init(Cipher.ENCRYPT_MODE, key);
+				String labelEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(label.getBytes()));
+				cipher.init(Cipher.ENCRYPT_MODE, key);
+				String actionNameEncrypted = Base64.getEncoder().encodeToString(cipher.doFinal(mail.options.get(label).getBytes()));
+				options.put(labelEncrypted, actionNameEncrypted);
+			}
+			
+			// write to mail database
+			Connection c = DriverManager.getConnection("jdbc:sqlite:" + FrameIO.PARENT_FOLDER + "/expmail.db");
+			String sql = "INSERT INTO EXPMAIL (SND,REC,MSG,MSG2,OPTS,OPTSVAL) VALUES (?, ?, ?, ?, ?, ?);";
+			PreparedStatement statement = c.prepareStatement(sql);
+			statement.setString(1, sender);
+			statement.setString(2, rec);
+			statement.setString(3, message);
+			statement.setString(4, message2);
+			String opts = Arrays.toString(options.keySet().toArray());
+			statement.setString(5, opts);
+			String optsval = Arrays.toString(options.values().toArray());
+			statement.setString(6, optsval);
+			statement.execute();
+			statement.close();
+			c.close();			
+		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | SQLException e) {
+			e.printStackTrace();
+		}
+	}
+	
+	/**
+	 * Gets the mail messages that the specified user is able to read.
+	 * The caller supplies their username and private key.  
+	 * If the private key can decrypt a message, then it was encrypted using the users public key, and is therefore for them.
+	 */
+	public static List<MailEntry> getEntries(String name, PrivateKey key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
+		List<MailEntry> filtered = new ArrayList<MailEntry>();
+		
+		for (MailEntry mail: messages) {
+			// confirm this is a message for the requester of entries
+			String receiver = mail.receiver;
+			byte[] receiverBytes = Base64.getDecoder().decode(receiver);
+			String receiverDecrypted = null;
+			Cipher c = Cipher.getInstance(AsymmetricAlgorithm + AsymmetricAlgorithmParameters);
+			try {
+				c.init(Cipher.DECRYPT_MODE, key);
+				receiverDecrypted = new String(c.doFinal(receiverBytes));
+			} catch (InvalidKeyException | BadPaddingException e) {
+				// this is not a message for 'us'
+				continue;
+			}
+			
+			// add an unencrypted version of the message to the return list
+			if (receiverDecrypted.compareTo(name) == 0) {
+				c.init(Cipher.DECRYPT_MODE, key);
+				String sender = new String(c.doFinal(Base64.getDecoder().decode(mail.sender)));
+				c.init(Cipher.DECRYPT_MODE, key);
+				String message = new String(c.doFinal(Base64.getDecoder().decode(mail.message)));
+				c.init(Cipher.DECRYPT_MODE, key);
+				String message2 = new String(c.doFinal(Base64.getDecoder().decode(mail.message2)));
+				
+				Map<String, String> options = new HashMap<String, String>(); //mail.options;
+				for (String label: mail.options.keySet()) {
+					c.init(Cipher.DECRYPT_MODE, key);
+					String labelDecrypted = new String(c.doFinal(Base64.getDecoder().decode(label)));
+					c.init(Cipher.DECRYPT_MODE, key);
+					String actionNameDecrypted = new String(c.doFinal(Base64.getDecoder().decode(mail.options.get(label))));
+					options.put(labelDecrypted, actionNameDecrypted);
+				}
+				
+				//String arguments = new String(c.doFinal(Base64.getDecoder().decode(mail.args)));
+				filtered.add(new MailEntry(sender, receiverDecrypted, message, message2, options));
+			}
+		}
+		
+		return filtered;
+	}
+	
+	/**
+	 * Describes a piece of mail, either encrypted or decrypted.
+	 */
+	public static class MailEntry {
+		public String sender;
+		public String receiver;
+		public String message;
+		public String message2;
+		public Map<String, String> options;
+		
+		public MailEntry(String sender, String rec, String message, String message2, Map<String, String> options) {
+			this.sender = sender;
+			this.receiver = rec;
+			this.message = message;
+			this.message2 = message2;
+			this.options = options;
+		}
+	}
+}
