source: trunk/src/org/expeditee/auth/Authenticator.java@ 1281

Last change on this file since 1281 was 1281, checked in by bln4, 5 years ago

Implemented band-aid to fix authentication mode not saving last edits on exit (and I imagine other problems).

The issue was that Browser._theBrowser was not being set because the Browser.init method was being highjacked by Authenticator. Authenticator now extends Browser, calls a newly minted (mostly) empty super constructor and sets Browser._theBrowser to itself. Once commits concerning how Expeditee startup has been made this can be tidied up.

File size: 19.5 KB
Line 
1package org.expeditee.auth;
2
3import java.io.File;
4import java.io.FileFilter;
5import java.io.FileInputStream;
6import java.io.FileNotFoundException;
7import java.io.FileOutputStream;
8import java.io.FileWriter;
9import java.io.IOException;
10import java.io.InputStream;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13import java.security.KeyFactory;
14import java.security.KeyStore;
15import java.security.KeyStore.SecretKeyEntry;
16import java.security.KeyStoreException;
17import java.security.NoSuchAlgorithmException;
18import java.security.PublicKey;
19import java.security.SecureRandom;
20import java.security.UnrecoverableEntryException;
21import java.security.cert.CertificateException;
22import java.security.spec.InvalidKeySpecException;
23import java.security.spec.X509EncodedKeySpec;
24import java.sql.Connection;
25import java.sql.DriverManager;
26import java.sql.PreparedStatement;
27import java.sql.ResultSet;
28import java.sql.SQLException;
29import java.text.ParseException;
30import java.text.SimpleDateFormat;
31import java.util.Arrays;
32import java.util.Base64;
33import java.util.Collection;
34import java.util.Date;
35import java.util.HashMap;
36import java.util.HashSet;
37import java.util.Map;
38import java.util.Scanner;
39import java.util.Set;
40import java.util.stream.Stream;
41
42import javax.crypto.SecretKey;
43import javax.crypto.spec.SecretKeySpec;
44
45import org.expeditee.actions.Actions;
46import org.expeditee.auth.tags.Constants;
47import org.expeditee.core.Dimension;
48import org.expeditee.core.Point;
49import org.expeditee.gio.EcosystemManager;
50import org.expeditee.gio.GraphicsManager;
51import org.expeditee.gio.InputManager;
52import org.expeditee.gio.InputManager.WindowEventListener;
53import org.expeditee.gio.InputManager.WindowEventType;
54import org.expeditee.gio.gesture.StandardGestureActions;
55import org.expeditee.gui.Browser;
56import org.expeditee.gui.DisplayController;
57import org.expeditee.gui.Frame;
58import org.expeditee.gui.FrameIO;
59import org.expeditee.gui.FrameUtils;
60import org.expeditee.gui.MessageBay;
61import org.expeditee.io.ExpReader;
62import org.expeditee.items.Item;
63import org.expeditee.items.ItemUtils;
64import org.expeditee.items.Text;
65import org.expeditee.settings.Settings;
66import org.expeditee.settings.UserSettings;
67import org.expeditee.settings.identity.secrets.KeyList;
68import org.expeditee.stats.Formatter;
69import org.ngikm.cryptography.CryptographyConstants;
70
71public final class Authenticator extends Browser implements CryptographyConstants {
72
73 // The frame number of the frame containing the current authenticated users public key.
74 public static int CREDENTIALS_FRAME = -1;
75 public static final String ADMINACCOUNT = "authadmin";
76
77 public static boolean Authenticated = false;
78
79 private KeyStore keyStore = KeyStore.getInstance(KeystoreType);
80
81 private static final byte[] TRUE = "yes".getBytes();
82 private static final byte[] FALSE = "no".getBytes();
83 private static final String KEYSTOREFILENAME = "keystore.ks" + File.separator;
84
85 private static Authenticator instance;
86
87 public static Authenticator getInstance() throws KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException, ClassNotFoundException, SQLException {
88 if (instance == null) { instance = new Authenticator(); }
89 return instance;
90 }
91
92 private Authenticator() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, CertificateException, ClassNotFoundException, SQLException {
93 super("Authentication");
94 UserSettings.setupDefaultFolders();
95
96 // initialise keystore and actions
97 loadKeystore();
98 Actions.LoadMethods(org.expeditee.auth.Actions.class);
99 Actions.LoadMethods(org.expeditee.auth.sharing.Actions.class);
100
101 // Does the account Authentication.ADMINACCOUNT exist?
102 // If not then we have get the user to assign a password to it.
103 if (!keyStore.containsAlias(Authenticator.ADMINACCOUNT)) {
104 new File(FrameIO.PARENT_FOLDER).mkdirs();
105 protectAdmin();
106 }
107
108 // draw the window
109 GraphicsManager g = EcosystemManager.getGraphicsManager();
110 g.setWindowLocation(new Point(50, 50));
111 DisplayController.Init();
112 g.setWindowSize(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get()));
113 setInputManagerWindowRoutines();
114
115 // Load documentation and start pages
116 FrameUtils.extractResources(false);
117
118 // Load fonts before loading any frames so the items on the frames will be able to access their fonts
119 Text.InitFonts();
120
121 // initialing settings does not require a user profile established
122 Settings.Init();
123
124 // navigate to authentication frame
125 Frame authFrame = FrameIO.LoadFrame("authentication1");
126 DisplayController.setCurrentFrame(authFrame, true);
127
128 // set initial values
129 Stream<Text> usernameItemsStream = authFrame.getTextItems().stream().filter(t -> t.getData() != null && t.getData().contains("txtUsername"));
130 Stream<Text> passwordItemsStream = authFrame.getTextItems().stream().filter(t -> t.getData() != null && t.getData().contains("txtPassword"));
131 usernameItemsStream.forEach(txtUsername -> txtUsername.setText(System.getProperty("startinguser.name", "")));
132 passwordItemsStream.forEach(txtPassword -> { txtPassword.setText(""); txtPassword.invalidateAll(); });
133
134 MessageBay.warningMessages(org.expeditee.actions.Actions.Init());
135
136 // class load database classes
137 Class.forName("org.sqlite.JDBC");
138 _theBrowser = this;
139 }
140
141 private void protectAdmin() throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
142 FileNotFoundException, IOException {
143 // Fetch desired password
144 Scanner in = new Scanner(System.in);
145 System.out.println("No administrative password set.");
146 boolean passwordIsSet = false;
147
148 for (int i = 0; i < 3; i++) {
149 System.out.print("Please enter it now: ");
150 System.out.flush();
151 String password = in.nextLine();
152 System.out.print("And again: ");
153 System.out.flush();
154 if (in.nextLine().equals(password)) {
155 // Register account.
156 putKey(ADMINACCOUNT, password, new SecretKeySpec("null".getBytes(), AsymmetricAlgorithm));
157 in.close();
158 passwordIsSet = true;
159 break;
160 } else {
161 System.out.println("Mismatched passwords, let's try that again.");
162 }
163 }
164
165 if (!passwordIsSet) {
166 System.out.println("Failed to set an admin password. Exiting Expeditee.");
167 System.exit(1);
168 }
169 }
170
171 private void loadKeystore()
172 throws IOException, NoSuchAlgorithmException, CertificateException, FileNotFoundException {
173 final File keyStoreFile = new File(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME);
174 if (!keyStoreFile.exists()) {
175 keyStore.load(null, Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
176 } else {
177 try (final InputStream in = new FileInputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME)) {
178 keyStore.load(in, Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
179 }
180 }
181 }
182
183 final void loadMailFromFile(Path dbFile) throws SQLException {
184 // Load in all mail.
185 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.toAbsolutePath().toString());
186 String sql = "SELECT * FROM EXPMAIL";
187 PreparedStatement query = c.prepareStatement(sql);
188 ResultSet allMail = query.executeQuery();
189
190 // Construct all mail objects using content from database.
191 while(allMail.next()) {
192 String timestamp = allMail.getString("time");
193 String from = allMail.getString("snd");
194 String to = allMail.getString("rec");
195 String msg = allMail.getString("msg");
196 String msg2 = allMail.getString("msg2");
197 String[] opts = allMail.getString("opts").split(",");
198 opts[0] = opts[0].replace("[", "");
199 opts[opts.length - 1] = opts[opts.length - 1].replace("]", "");
200 String[] optsVal = allMail.getString("optsval").split(",");
201 optsVal[0] = optsVal[0].replace("[", "");
202 optsVal[optsVal.length - 1] = optsVal[optsVal.length - 1].replace("]", "");
203
204 Map<String, String> options = new HashMap<String, String>();
205 for (int i = 0, o = 0; i < opts.length && o < optsVal.length; i++, o++) {
206 String key = opts[i].trim();
207 String val = optsVal[o].trim();
208 options.put(key, val);
209 }
210
211 Mail.addEntry(new Mail.MailEntry(timestamp, from, to, msg, msg2, options));
212 }
213
214 // Disconnect from database.
215 allMail.close();
216 query.close();
217 c.close();
218 }
219
220 final void loadMailDatabase() throws SQLException, FileNotFoundException, ParseException {
221 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH);
222 for (File connectionDir: deadDropPath.toFile().listFiles()) {
223 if (connectionDir.isDirectory()) {
224 Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath());
225 Path dbFile = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".db");
226 if (dbFile.toFile().exists()) {
227 loadMailFromFile(dbFile);
228 }
229 Path timestamp = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".last-accessed");
230 try(FileWriter out = new FileWriter(timestamp.toFile())) {
231 out.write(Formatter.getDateTime() + System.getProperty("line.separator"));
232 } catch (IOException e) {
233 e.printStackTrace();
234 }
235 clearOldMailFromDatabase(deaddropforcontactPath);
236 }
237 }
238 }
239
240 private void clearOldMailFromDatabase(Path directory) throws FileNotFoundException, ParseException, SQLException {
241 File[] files = directory.toFile().listFiles(new FileFilter() {
242 @Override
243 public boolean accept(File file) {
244 return !file.getName().startsWith(UserSettings.UserName.get());
245 }
246 });
247
248 File dbFile = null;
249 File lastAccessedFile = null;
250 for (File file: files) {
251 if (file.getName().endsWith(".db")) {
252 dbFile = file;
253 } else {
254 lastAccessedFile = file;
255 }
256 }
257
258 if (dbFile == null || lastAccessedFile == null) {
259 System.err.println("Failed to clear old messages from database file.");
260 return; // Not the end of the world if we cannot clear out old messages.
261 }
262
263 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]");
264 Date timestamp = null;
265 try(Scanner in = new Scanner(lastAccessedFile)) {
266 timestamp = format.parse(in.nextLine());
267 } catch (ParseException e) {
268 System.err.println("Failed to clear old messages from database file.");
269 return; // Not the end of the world if we cannot clear out old messages.
270 }
271
272 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
273 String sql = "SELECT * FROM EXPMAIL";
274 PreparedStatement query = c.prepareStatement(sql);
275 ResultSet allMail = query.executeQuery();
276 Set<String> oldTimestamps = new HashSet<String>();
277
278 while (allMail.next()) {
279 String time = allMail.getString("time");
280 Date messageTimestamp = format.parse(time);
281 if (timestamp.after(messageTimestamp)) {
282 oldTimestamps.add(time);
283 }
284 }
285
286 if (oldTimestamps.isEmpty()) {
287 return;
288 }
289
290 for(String oldTimestamp: oldTimestamps) {
291 System.out.println("Deleting message with timestamp: " + oldTimestamp);
292 sql = "DELETE FROM EXPMAIL WHERE time='" + oldTimestamp + "'";
293 query = c.prepareStatement(sql);
294 query.executeUpdate();
295 }
296 }
297
298 final SecretKey getSecretKey(final String label, final String password) throws NoSuchAlgorithmException, KeyStoreException {
299
300 char[] password_ca = password.toCharArray();
301
302 SecretKey secret_key;
303 try {
304 secret_key = (SecretKey) keyStore.getKey(label, password_ca);
305 } catch (final UnrecoverableEntryException e) {
306 return null;
307 }
308
309 return secret_key;
310 }
311
312 final void putKey(final String label, final String password, final SecretKey key) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
313 final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(key);
314 final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(password.toCharArray());
315 keyStore.setEntry(label, entry, entryPassword);
316 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
317 }
318
319 final boolean confirmIntergalaticNumber(final String username, final String email, final String intergalacticNumber) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
320 try {
321 final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray());
322 final KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(email + username, entryPassword);
323 if (entry == null) {
324 return false;
325 } else if (entry.getSecretKey().getEncoded() == TRUE) {
326 keyStore.deleteEntry(email + username);
327 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
328 return true;
329 } else { return false; }
330 } catch (final UnrecoverableEntryException e) {
331 return false;
332 }
333 }
334
335 final String newIntergalacticNumber(final String username, final String email) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
336 // generate intergalactic number
337 final SecureRandom rand = new SecureRandom();
338 final byte[] intergalacticNumberBytes = new byte[20];
339 rand.nextBytes(intergalacticNumberBytes);
340 final String intergalacticNumber = Base64.getEncoder().encodeToString(intergalacticNumberBytes);
341
342 // store intergalactic number
343 final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm));
344 final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray());
345 keyStore.setEntry(email + username, entry, entryPassword);
346 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
347
348 return intergalacticNumber;
349 }
350
351 final PublicKey getPublicKey(String username) throws InvalidKeySpecException, NoSuchAlgorithmException, FileNotFoundException {
352 // load in frame with public key on it.
353 String credentialsFramesetPath = FrameIO.CONTACTS_PATH + username + "-credentials" + File.separator;
354 if (!new File(credentialsFramesetPath).exists()) {
355 return null;
356 }
357 Scanner in = new Scanner(new File(credentialsFramesetPath + "credentials.inf"));
358 String credentialsFrameNumber = in.nextLine().replace(ExpReader.EXTENTION, "");
359 in.close();
360 Frame frame = FrameIO.LoadFrame(username + "-credentials" + credentialsFrameNumber, FrameIO.CONTACTS_PATH);
361 if (frame == null) {
362 return null;
363 }
364
365 // obtain public key from frame
366 Collection<Item> canditates = org.expeditee.auth.Actions.getByContent(frame, "PublicKey");
367 String keyEncoded = "";
368 for (Item i: canditates) {
369 if (i.getData() != null) {
370 keyEncoded = i.getData().get(0);
371 }
372 }
373 if (keyEncoded.isEmpty()) {
374 return null;
375 }
376 byte[] keyBytes = Base64.getDecoder().decode(keyEncoded);
377 return KeyFactory.getInstance(AsymmetricAlgorithm).generatePublic(new X509EncodedKeySpec(keyBytes));
378 }
379
380 final void markRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
381 KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm));
382 KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
383 keyStore.setEntry(username + "colleaguesRequested", entry, entryPassword);
384 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
385 }
386
387 final void clearRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
388 KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(FALSE, SymmetricAlgorithm));
389 KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
390 keyStore.setEntry(username + "colleaguesRequested", entry, entryPassword);
391 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
392 }
393
394 final boolean hasRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
395 String alias = username + "colleaguesRequested";
396 if (!keyStore.containsAlias(alias)) {
397 return false;
398 } else {
399 KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
400 KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(alias, entryPassword);
401 return Arrays.equals(entry.getSecretKey().getEncoded(), TRUE);
402 }
403 }
404
405// final void putColleagues(String username, String[] colleagues) throws KeyStoreException {
406// String alias = username + "colleagues";
407// final SecretKeySpec secretKeySpec = new SecretKeySpec((colleagues[0] + System.getProperty("line.separator") + colleagues[1]).getBytes(), SymmetricAlgorithm);
408// KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(secretKeySpec);
409// KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
410// keyStore.setEntry(alias, entry, entryPassword);
411// }
412//
413// final String[] getColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
414// String alias = username + "colleagues";
415// if (!keyStore.containsAlias(alias)) {
416// return null;
417// } else {
418// KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
419// KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(alias, entryPassword);
420// byte[] colleaguesEncoded = entry.getSecretKey().getEncoded();
421// String colleagues = new String(colleaguesEncoded);
422// return colleagues.split(System.getProperty("line.separator"));
423// }
424// }
425
426 private static void setInputManagerWindowRoutines() {
427 InputManager manager = EcosystemManager.getInputManager();
428
429 // Refresh the layout when the window resizes
430 manager.addWindowEventListener(new WindowEventListener() {
431 @Override
432 public void onWindowEvent(WindowEventType type)
433 {
434 if (type != WindowEventType.WINDOW_RESIZED) {
435 return;
436 }
437 DisplayController.refreshWindowSize();
438 FrameIO.RefreshCacheImages();
439 for (Frame frame : DisplayController.getFrames()) {
440 if (frame != null) {
441 ItemUtils.Justify(frame);
442 frame.refreshSize();
443 }
444 }
445 DisplayController.requestRefresh(false);
446 }
447 });
448
449 manager.addWindowEventListener(new WindowEventListener() {
450 @Override
451 public void onWindowEvent(WindowEventType type)
452 {
453 if (type != WindowEventType.MOUSE_EXITED_WINDOW) {
454 return;
455 }
456 StandardGestureActions.mouseExitedWindow();
457 }
458 });
459
460 manager.addWindowEventListener(new WindowEventListener() {
461 @Override
462 public void onWindowEvent(WindowEventType type)
463 {
464 if (type != WindowEventType.MOUSE_ENTERED_WINDOW) {
465 return;
466 }
467 StandardGestureActions.mouseEnteredWindow();
468 }
469 });
470
471 manager.addWindowEventListener(new WindowEventListener() {
472 @Override
473 public void onWindowEvent(WindowEventType type)
474 {
475 if (type != WindowEventType.WINDOW_CLOSED) {
476 return;
477 }
478 if (Browser._theBrowser != null) {
479 Browser._theBrowser.exit();
480 }
481 }
482 });
483 }
484}
Note: See TracBrowser for help on using the repository browser.