source: trunk/src/org/expeditee/auth/account/Password.java@ 1504

Last change on this file since 1504 was 1504, checked in by bnemhaus, 4 years ago

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

File size: 18.4 KB
Line 
1package org.expeditee.auth.account;
2
3import java.io.File;
4import java.io.FileNotFoundException;
5import java.io.IOException;
6import java.nio.file.Path;
7import java.nio.file.Paths;
8import java.security.KeyStoreException;
9import java.security.NoSuchAlgorithmException;
10import java.security.SecureRandom;
11import java.security.cert.CertificateException;
12import java.sql.SQLException;
13import java.util.Base64;
14import java.util.Collection;
15import java.util.Date;
16import java.util.HashMap;
17import java.util.Iterator;
18import java.util.Map;
19import java.util.Properties;
20import java.util.Scanner;
21
22import javax.crypto.SecretKey;
23import javax.crypto.spec.SecretKeySpec;
24import javax.mail.Message;
25import javax.mail.MessagingException;
26import javax.mail.PasswordAuthentication;
27import javax.mail.Session;
28import javax.mail.Transport;
29import javax.mail.internet.AddressException;
30import javax.mail.internet.InternetAddress;
31import javax.mail.internet.MimeMessage;
32
33import org.expeditee.auth.AuthenticatorBrowser;
34import org.expeditee.auth.mail.Mail;
35import org.expeditee.auth.mail.Mail.MailEntry;
36import org.expeditee.auth.mail.gui.MailBay;
37import org.expeditee.auth.tags.AuthenticationTag;
38import org.expeditee.encryption.CryptographyConstants;
39import org.expeditee.encryption.io.EncryptedExpReader;
40import org.expeditee.gui.DisplayController;
41import org.expeditee.gui.Frame;
42import org.expeditee.gui.FrameIO;
43import org.expeditee.gui.MessageBay;
44import org.expeditee.items.Text;
45import org.expeditee.settings.UserSettings;
46import org.expeditee.settings.identity.passwordrecovery.Colleagues;
47import org.expeditee.settings.identity.secrets.KeyList;
48
49import com.codahale.shamir.Scheme;
50
51public class Password implements CryptographyConstants {
52 /**
53 * Changes the recorded password for a user in the key store; given the username signaling whose password to change, along with the existing and new password.
54 */
55 public static void changePassword(Map<AuthenticationTag, String> userdata) throws NoSuchAlgorithmException, KeyStoreException, FileNotFoundException, CertificateException, IOException, ClassNotFoundException, SQLException {
56 String username = userdata.get(AuthenticationTag.Username);
57 String password = userdata.get(AuthenticationTag.Password);
58 String newpassword = userdata.get(AuthenticationTag.NewPassword);
59
60 final SecretKey key = AuthenticatorBrowser.getInstance().getSecretKey(username, password);
61 if (key == null) {
62 MessageBay.errorMessage("The username + existing password combination was incorrect.");
63 } else {
64 AuthenticatorBrowser.getInstance().putKey(username, newpassword, key);
65 MessageBay.displayMessage("Password changed successfully.");
66 DisplayController.setCurrentFrame(FrameIO.LoadFrame("multiuser1"), true);
67 }
68 }
69
70 /**
71 * Generates a intergalaictic number for a specified user and emails that number using the specified email.
72 * @param userData
73 */
74 public static void generateAndDeliverIntergalacticNumber(Map<AuthenticationTag, String> userData) {
75 String username = userData.get(AuthenticationTag.Username);
76 String email = userData.get(AuthenticationTag.Email);
77 try {
78 // Generate message text.
79 String intergalacticNumber = AuthenticatorBrowser.getInstance().newIntergalacticNumber(username, email);
80 String nl = System.getProperty("line.separator");
81 StringBuilder sb = new StringBuilder();
82 sb.append("You are receiving this email because someone is attempting to reset your Expeditee password." + nl);
83 sb.append("If you did not make this request then no action is required." + nl);
84 sb.append("If it was you who made this request, the following string of characters is your identity number: " + intergalacticNumber + nl);
85
86 sendEmail(email, sb);
87 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | ClassNotFoundException
88 | IOException | SQLException | MessagingException e) {
89 e.printStackTrace();
90 }
91 }
92
93 /**
94 * Confirms that the specified intergalaictic number matches the one of file for the specified username.
95 * Passing this test it then alerts the users pw colleagues through a one-off secure Expeditee message.
96 * @param tags
97 */
98 public static void confirmIntergalacticNumberAndAlertTrustedUsers(Map<AuthenticationTag, String> tags) {
99 // Confirm intergalactic numbers match
100 String username = tags.get(AuthenticationTag.Username);
101 String intergalacticNumber = tags.get(AuthenticationTag.IntergalacticNumber);
102 boolean match = false;
103 try {
104 match = AuthenticatorBrowser.getInstance().confirmIntergalaticNumber(username, intergalacticNumber);
105 } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | ClassNotFoundException
106 | IOException | SQLException e) {
107 e.printStackTrace();
108 return;
109 }
110 if (!match) {
111 MessageBay.errorMessage("The provided identity number does not match the one stored on file.");
112 return;
113 }
114
115 // Get colleagues to distribute messages too.
116 String[] trustedUsers = getPasswordColleaguesFromUsername(username);
117 // Send secure message to colleague one
118 String trustedUserOne = trustedUsers[0];
119 String time = Mail.FORMAT.format(new Date());
120 String topic = "Password Recovery for " + username;
121 String message = "Your colleague " + username + " would like you to help them recover access to their account.";
122 Map<String, String> options = new HashMap<String, String>();
123 options.put("Provide assistance", "AuthEmailPasswordShare " + username);
124 Mail outbox = MailBay.getMailClient(username);
125 MailEntry mail = outbox.new MailEntry(time, username, trustedUserOne, topic, message, options);
126 SecretKeySpec key = new SecretKeySpec(Base64.getDecoder().decode(intergalacticNumber), SymmetricAlgorithm);
127 outbox.sendOneOffMail(mail, trustedUserOne, key);
128
129 // Send secure message to colleague two
130 String trustedUserTwo = trustedUsers[1];
131 outbox.sendOneOffMail(mail, trustedUserTwo, key);
132
133 String nl = System.getProperty("line.separator");
134 StringBuilder sb = new StringBuilder();
135 sb.append("You are receiving this email because one of your Expeditee contacts has sent you a one-off secure message." + nl);
136 sb.append("When you log into Expeditee and check your mail it will be there waiting for you." + nl);
137 sb.append("You will need the following key to read this message: " + nl);
138 sb.append(intergalacticNumber);
139 try {
140 // Send email with key to colleague one
141 String colleagueOneEmail = trustedUsers[2];
142 sendEmail(colleagueOneEmail, sb);
143 // Send email with key to colleague two
144 String colleagueTwoEmail = trustedUsers[3];
145 sendEmail(colleagueTwoEmail, sb);
146
147 MessageBay.displayMessage("Identity confirmed. Your trusted contacts have been notified via one-off secure Expeditee message. "
148 + "You will recieve an email message with a password share from each once they have completed their part of the process. Enter them below.");
149 } catch (MessagingException e) {
150 e.printStackTrace();
151 }
152 }
153
154 private static String[] getPasswordColleaguesFromUsername(String username) {
155 Path credentialsFilePath = Paths.get(FrameIO.PROFILE_PATH).resolve(username).resolve("pwcolleagues.inf");
156 String fileName = null;
157 if (credentialsFilePath.toFile().exists()) {
158 try (Scanner in = new Scanner(credentialsFilePath)) {
159 fileName = in.nextLine();
160 } catch (IOException e) {
161 MessageBay.errorMessage("Unable to find trusted users contact frame for specified user, are they registered on this computer?");
162 return null;
163 }
164 } else {
165 MessageBay.errorMessage("Unable to find trusted users contact frame for specified user, are they registered on this computer?");
166 return null;
167 }
168
169 int number = Integer.parseInt(fileName.replace(".exp", ""));
170 Frame pwColleagueFrame = FrameIO.LoadFrame(username + number, FrameIO.PROFILE_PATH);
171 Collection<Text> textItems = pwColleagueFrame.getTextItems();
172 textItems.removeIf(text -> !text.getText().startsWith("User_"));
173
174 String[] ret = new String[4];
175 Iterator<Text> it = textItems.iterator();
176 while(it.hasNext()) {
177 String content = it.next().getText().toLowerCase().trim();
178 if (content.contains("user_one:")) {
179 ret[0] = content.replace("user_one:", "").trim();
180 } else if (content.contains("user_two:")) {
181 ret[1] = content.replace("user_two:", "").trim();
182 }
183 }
184
185 // find colleague one email
186 Path credentialsDirectoryPath = UserSettings.PublicAndPrivateResources
187 ? Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + username)
188 : Paths.get(FrameIO.PARENT_FOLDER);
189 credentialsDirectoryPath = credentialsDirectoryPath.resolve("contacts").resolve(ret[0] + "-credentials");
190 try (Scanner in = new Scanner(credentialsDirectoryPath.resolve("credentials.inf").toFile())) {
191 int parseInt = Integer.parseInt(in.nextLine().replace(".exp", ""));
192 Frame frame = FrameIO.LoadFrame(ret[0] + "-credentials" + parseInt, credentialsDirectoryPath.toAbsolutePath().getParent().toString() + File.separator);
193 textItems = frame.getTextItems();
194 textItems.removeIf(text -> !text.getText().startsWith("Email:"));
195 ret[2] = textItems.iterator().next().getText().replace("Email:", "").trim();
196 } catch (FileNotFoundException e) {
197 MessageBay.errorMessage("You do not appear to have contact with your nominated password colleague: " + ret[0]);
198 return null;
199 }
200
201
202 // find colleague two email
203 credentialsDirectoryPath = UserSettings.PublicAndPrivateResources
204 ? Paths.get(FrameIO.PARENT_FOLDER).resolve("resources-" + username)
205 : Paths.get(FrameIO.PARENT_FOLDER);
206 credentialsDirectoryPath = credentialsDirectoryPath.resolve("contacts").resolve(ret[1] + "-credentials");
207 try (Scanner in = new Scanner(credentialsDirectoryPath.resolve("credentials.inf").toFile())) {
208 int parseInt = Integer.parseInt(in.nextLine().replace(".exp", ""));
209 Frame frame = FrameIO.LoadFrame(ret[1] + "-credentials" + parseInt, credentialsDirectoryPath.toAbsolutePath().getParent().toString() + File.separator);
210 textItems = frame.getTextItems();
211 textItems.removeIf(text -> !text.getText().startsWith("Email:"));
212 ret[3] = textItems.iterator().next().getText().replace("Email:", "").trim();
213 } catch (FileNotFoundException e) {
214 MessageBay.errorMessage("You do not appear to have contact with your nominated password colleague: " + ret[1]);
215 return null;
216 }
217
218 return ret;
219 }
220
221
222 public static void sendEmail(String email, StringBuilder sb) throws MessagingException, AddressException {
223 // Establish properties for email.
224 Properties properties = System.getProperties();
225 properties.setProperty("mail.transport.protocol", "smtp");
226 properties.setProperty("mail.smtp.host", "smtp.gmail.com");
227 properties.setProperty("mail.smtp.port", "465");
228 properties.setProperty("mail.smtp.starttls.enable", "true");
229 properties.setProperty("mail.smtp.auth", "true");
230 properties.setProperty("mail.smtp.debug", "true");
231 properties.setProperty("mail.smtp.auth", "true");
232 properties.setProperty("mail.smtp.socketFactory.port", "465");
233 properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
234 properties.setProperty("mail.smtp.socketFactory.fallback", "false");
235
236 Session session = Session.getDefaultInstance(properties, new javax.mail.Authenticator() {
237 @Override
238 protected PasswordAuthentication getPasswordAuthentication() {
239 //return new PasswordAuthentication("noreply.expeditee", "intergalacticnumber");
240 return new PasswordAuthentication("noreply.expeditee", "exped!tee");
241 };
242 });
243
244 // construct email message
245 final MimeMessage message = new MimeMessage(session);
246 message.setFrom(new InternetAddress("[email protected]"));
247 message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
248 message.setSubject("Expeditee Password Recovery");
249 message.setText(sb.toString());
250
251 // send email message
252 Transport.send(message);
253 }
254
255 public static void setPWColleagues(String trustedUserOne, String trustedUserTwo) {
256 // Get needed text items.
257 Frame pwRecoveryFrame = FrameIO.LoadFrame(UserSettings.UserName.get() + AuthenticatorBrowser.PASSWORD_RECOVERY_FRAME);
258 Collection<Text> textItems = pwRecoveryFrame.getTextItems();
259 textItems.removeIf(t -> !t.getText().toLowerCase().startsWith("user_"));
260
261 // Find colleague one and two text items. Set appropriate values.
262 Iterator<Text> it = textItems.iterator();
263 while (it.hasNext()) {
264 Text text = it.next();
265 if (text.getText().toLowerCase().startsWith("user_one:")) {
266 text.setText("User_one: " + trustedUserOne);
267 Colleagues.User_One.set(trustedUserOne);
268 } else if (text.getText().toLowerCase().startsWith("user_two:")) {
269 text.setText("User_two: " + trustedUserTwo);
270 Colleagues.User_Two.set(trustedUserTwo);
271 }
272 }
273 FrameIO.ForceSaveFrame(pwRecoveryFrame);
274
275 // Key to split and distribute
276 String fullKey = KeyList.PersonalKey.get().getData().get(0);
277 byte[] keyBytes = Base64.getDecoder().decode(fullKey);
278
279 // Initialise Shamir
280 int totalShares = 2;
281 int requiredShares = 2;
282 Scheme scheme = new Scheme(new SecureRandom(), totalShares, requiredShares);
283
284 // Create shares
285 Map<Integer, byte[]> shares = scheme.split(keyBytes);
286 String trustedUserOneShare = Base64.getEncoder().encodeToString(shares.get(1));
287 String trustedUserTwoShare = Base64.getEncoder().encodeToString(shares.get(2));
288
289 // Distribute share zero to colleague one
290 String time = Mail.FORMAT.format(new Date());
291 String sender = UserSettings.UserName.get();
292 String topic = "Please help me secure my Expeditee account.";
293 String message = "Run the below action to store a secret key that will help me recover access to my account should I ever loose it.";
294 Map<String, String> options = new HashMap<String, String>();
295 options.put("Store Secret Key for " + sender, "AuthAddSecretKey " + sender + "PersonalKeyShare " + trustedUserOneShare);
296 Mail outbox = MailBay.getMailClient();
297 MailEntry mail = outbox.new MailEntry(time, sender, trustedUserOne, topic, message, options);
298 outbox.sendMail(mail, trustedUserOne);
299
300 // Distribute share one to colleague two
301 options = new HashMap<String, String>();
302 options.put("Store Secret Key for " + sender, "AuthAddSecretKey " + sender + "PersonalKeyShare " + trustedUserTwoShare);
303 mail = outbox.new MailEntry(time, sender, trustedUserTwo, topic, message, options);
304 outbox.sendMail(mail, trustedUserTwo);
305
306 MessageBay.displayMessage("You Trusted Users have been set to " + trustedUserOne + " and " + trustedUserTwo + ". "
307 + "They have been sent a Expeditee mail that they can use to store a share of your secret key.");
308 }
309
310 public static void emailPasswordShare(String colleagueName) {
311 Path credentialsDirectoryPath = Paths.get(FrameIO.CONTACTS_PATH).resolve(colleagueName + "-credentials");
312 String colleagueEmail = null;
313 try (Scanner in = new Scanner(credentialsDirectoryPath.resolve("credentials.inf").toFile())) {
314 int parseInt = Integer.parseInt(in.nextLine().replace(".exp", ""));
315 Frame frame = FrameIO.LoadFrame(colleagueName + "-credentials" + parseInt, credentialsDirectoryPath.toAbsolutePath().getParent().toString() + File.separator);
316 Collection<Text> textItems = frame.getTextItems();
317 textItems.removeIf(text -> !text.getText().startsWith("Email:"));
318 colleagueEmail = textItems.iterator().next().getText().replace("Email:", "").trim();
319 } catch (FileNotFoundException e) {
320 MessageBay.errorMessage("You do not appear to have contact with: " + colleagueName);
321 return;
322 }
323
324 Frame secretsFrame = FrameIO.LoadFrame(UserSettings.UserName.get() + AuthenticatorBrowser.SECRETS_FRAME);
325 Collection<Text> textItems = secretsFrame.getTextItems();
326 textItems.removeIf(text -> !text.getText().toLowerCase().equals(colleagueName + "personalkeyshare"));
327 String key = textItems.iterator().next().getData().get(0);
328
329 String nl = System.getProperty("line.separator");
330 StringBuilder sb = new StringBuilder();
331 sb.append("In responce to your request for assistance regaining access to your Expeditee account, your colleague " + UserSettings.UserName.get() + " has provided you with the following key share:" + nl);
332 sb.append(key + nl);
333
334 try {
335 sendEmail(colleagueEmail, sb);
336 MessageBay.displayMessage("Your share of " + colleagueName + "'s password has been sent to their public email address.");
337 } catch (MessagingException e) {
338 MessageBay.errorMessage("An error occured sending a email to your colleage " + colleagueName + " with the email " + colleagueEmail);
339 }
340 }
341
342 public static void regainAccountAccess(Map<AuthenticationTag, String> userData) {
343 regainAccountAccess(userData, false);
344 }
345
346 private static void regainAccountAccess(Map<AuthenticationTag, String> userData, boolean isAttemptTwo) {
347 // Store shares in map
348 Map<Integer, byte[]> contributingParts = new HashMap<Integer, byte[]>();
349 if (isAttemptTwo) {
350 contributingParts.put(1, Base64.getDecoder().decode(userData.get(AuthenticationTag.PasswordSliceTwo)));
351 contributingParts.put(2, Base64.getDecoder().decode(userData.get(AuthenticationTag.PasswordSliceOne)));
352 } else {
353 contributingParts.put(1, Base64.getDecoder().decode(userData.get(AuthenticationTag.PasswordSliceOne)));
354 contributingParts.put(2, Base64.getDecoder().decode(userData.get(AuthenticationTag.PasswordSliceTwo)));
355 }
356
357 // initialise shamir
358 int totalShares = 2;
359 int requiredShares = 2;
360 Scheme scheme = new Scheme(new SecureRandom(), totalShares, requiredShares);
361
362 // perform joining
363 byte[] join = scheme.join(contributingParts);
364
365 try {
366 String username = userData.get(AuthenticationTag.Username);
367 SecretKey key = new SecretKeySpec(join, SymmetricAlgorithm);
368 String filePathCheck = Paths.get(FrameIO.PROFILE_PATH).resolve(username).resolve("1.exp").toAbsolutePath()
369 .toString();
370 if (EncryptedExpReader.isAccessibleExpediteeFile(filePathCheck, key)) {
371 AuthenticatorBrowser.getInstance().putKey(username, userData.get(AuthenticationTag.NewPassword), key);
372 MessageBay.displayMessage("Your new password has been set.");
373 } else {
374 if (isAttemptTwo) {
375 MessageBay.displayMessage("Invalid information given for changing password for " + username);
376 } else {
377 regainAccountAccess(userData, true);
378 }
379 }
380 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | ClassNotFoundException
381 | IOException | SQLException e) {
382 e.printStackTrace();
383 }
384 }
385}
Note: See TracBrowser for help on using the repository browser.