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

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

Stopped exception from being printed to err upon incorrect username+password combination.

When logging in, each user now removes mail from each colleagues database file if the colleague has read it (as determined by the date stored alongside the database file).

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 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 System.out.println("Running Expeditee in Authentication Mode.");
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 }
139
140 private void protectAdmin() throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
141 FileNotFoundException, IOException {
142 // Fetch desired password
143 Scanner in = new Scanner(System.in);
144 System.out.println("No administrative password set.");
145 boolean passwordIsSet = false;
146
147 for (int i = 0; i < 3; i++) {
148 System.out.print("Please enter it now: ");
149 System.out.flush();
150 String password = in.nextLine();
151 System.out.print("And again: ");
152 System.out.flush();
153 if (in.nextLine().equals(password)) {
154 // Register account.
155 putKey(ADMINACCOUNT, password, new SecretKeySpec("null".getBytes(), AsymmetricAlgorithm));
156 in.close();
157 passwordIsSet = true;
158 break;
159 } else {
160 System.out.println("Mismatched passwords, let's try that again.");
161 }
162 }
163
164 if (!passwordIsSet) {
165 System.out.println("Failed to set an admin password. Exiting Expeditee.");
166 System.exit(1);
167 }
168 }
169
170 private void loadKeystore()
171 throws IOException, NoSuchAlgorithmException, CertificateException, FileNotFoundException {
172 final File keyStoreFile = new File(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME);
173 if (!keyStoreFile.exists()) {
174 keyStore.load(null, Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
175 } else {
176 try (final InputStream in = new FileInputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME)) {
177 keyStore.load(in, Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
178 }
179 }
180 }
181
182 final void loadMailFromFile(Path dbFile) throws SQLException {
183 // Load in all mail.
184 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.toAbsolutePath().toString());
185 String sql = "SELECT * FROM EXPMAIL";
186 PreparedStatement query = c.prepareStatement(sql);
187 ResultSet allMail = query.executeQuery();
188
189 // Construct all mail objects using content from database.
190 while(allMail.next()) {
191 String timestamp = allMail.getString("time");
192 String from = allMail.getString("snd");
193 String to = allMail.getString("rec");
194 String msg = allMail.getString("msg");
195 String msg2 = allMail.getString("msg2");
196 String[] opts = allMail.getString("opts").split(",");
197 opts[0] = opts[0].replace("[", "");
198 opts[opts.length - 1] = opts[opts.length - 1].replace("]", "");
199 String[] optsVal = allMail.getString("optsval").split(",");
200 optsVal[0] = optsVal[0].replace("[", "");
201 optsVal[optsVal.length - 1] = optsVal[optsVal.length - 1].replace("]", "");
202
203 Map<String, String> options = new HashMap<String, String>();
204 for (int i = 0, o = 0; i < opts.length && o < optsVal.length; i++, o++) {
205 String key = opts[i].trim();
206 String val = optsVal[o].trim();
207 options.put(key, val);
208 }
209
210 Mail.addEntry(new Mail.MailEntry(timestamp, from, to, msg, msg2, options));
211 }
212
213 // Disconnect from database.
214 allMail.close();
215 query.close();
216 c.close();
217 }
218
219 final void loadMailDatabase() throws SQLException, FileNotFoundException, ParseException {
220 Path deadDropPath = Paths.get(FrameIO.DEAD_DROPS_PATH);
221 for (File connectionDir: deadDropPath.toFile().listFiles()) {
222 if (connectionDir.isDirectory()) {
223 Path deaddropforcontactPath = Paths.get(connectionDir.getAbsolutePath());
224 Path dbFile = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".db");
225 if (dbFile.toFile().exists()) {
226 loadMailFromFile(dbFile);
227 }
228 Path timestamp = deaddropforcontactPath.resolve(UserSettings.UserName.get() + ".last-accessed");
229 try(FileWriter out = new FileWriter(timestamp.toFile())) {
230 out.write(Formatter.getDateTime() + System.getProperty("line.separator"));
231 } catch (IOException e) {
232 e.printStackTrace();
233 }
234 clearOldMailFromDatabase(deaddropforcontactPath);
235 }
236 }
237 }
238
239 private void clearOldMailFromDatabase(Path directory) throws FileNotFoundException, ParseException, SQLException {
240 File[] files = directory.toFile().listFiles(new FileFilter() {
241 @Override
242 public boolean accept(File file) {
243 return !file.getName().startsWith(UserSettings.UserName.get());
244 }
245 });
246
247 File dbFile = null;
248 File lastAccessedFile = null;
249 for (File file: files) {
250 if (file.getName().endsWith(".db")) {
251 dbFile = file;
252 } else {
253 lastAccessedFile = file;
254 }
255 }
256
257 if (dbFile == null || lastAccessedFile == null) {
258 System.err.println("Failed to clear old messages from database file.");
259 return; // Not the end of the world if we cannot clear out old messages.
260 }
261
262 SimpleDateFormat format = new SimpleDateFormat("ddMMMyyyy[HH:mm]");
263 Date timestamp = null;
264 try(Scanner in = new Scanner(lastAccessedFile)) {
265 timestamp = format.parse(in.nextLine());
266 } catch (ParseException e) {
267 System.err.println("Failed to clear old messages from database file.");
268 return; // Not the end of the world if we cannot clear out old messages.
269 }
270
271 Connection c = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
272 String sql = "SELECT * FROM EXPMAIL";
273 PreparedStatement query = c.prepareStatement(sql);
274 ResultSet allMail = query.executeQuery();
275 Set<String> oldTimestamps = new HashSet<String>();
276
277 while (allMail.next()) {
278 String time = allMail.getString("time");
279 Date messageTimestamp = format.parse(time);
280 if (timestamp.after(messageTimestamp)) {
281 oldTimestamps.add(time);
282 }
283 }
284
285 if (oldTimestamps.isEmpty()) {
286 return;
287 }
288
289 for(String oldTimestamp: oldTimestamps) {
290 System.out.println("Deleting message with timestamp: " + oldTimestamp);
291 sql = "DELETE FROM EXPMAIL WHERE time='" + oldTimestamp + "'";
292 query = c.prepareStatement(sql);
293 query.executeUpdate();
294 }
295 }
296
297 final SecretKey getSecretKey(final String label, final String password) throws NoSuchAlgorithmException, KeyStoreException {
298
299 char[] password_ca = password.toCharArray();
300
301 SecretKey secret_key;
302 try {
303 secret_key = (SecretKey) keyStore.getKey(label, password_ca);
304 } catch (final UnrecoverableEntryException e) {
305 return null;
306 }
307
308 return secret_key;
309 }
310
311 final void putKey(final String label, final String password, final SecretKey key) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
312 final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(key);
313 final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(password.toCharArray());
314 keyStore.setEntry(label, entry, entryPassword);
315 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
316 }
317
318 final boolean confirmIntergalaticNumber(final String username, final String email, final String intergalacticNumber) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
319 try {
320 final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray());
321 final KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(email + username, entryPassword);
322 if (entry == null) {
323 return false;
324 } else if (entry.getSecretKey().getEncoded() == TRUE) {
325 keyStore.deleteEntry(email + username);
326 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
327 return true;
328 } else { return false; }
329 } catch (final UnrecoverableEntryException e) {
330 return false;
331 }
332 }
333
334 final String newIntergalacticNumber(final String username, final String email) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
335 // generate intergalactic number
336 final SecureRandom rand = new SecureRandom();
337 final byte[] intergalacticNumberBytes = new byte[20];
338 rand.nextBytes(intergalacticNumberBytes);
339 final String intergalacticNumber = Base64.getEncoder().encodeToString(intergalacticNumberBytes);
340
341 // store intergalactic number
342 final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm));
343 final KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(intergalacticNumber.toCharArray());
344 keyStore.setEntry(email + username, entry, entryPassword);
345 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
346
347 return intergalacticNumber;
348 }
349
350 final PublicKey getPublicKey(String username) throws InvalidKeySpecException, NoSuchAlgorithmException, FileNotFoundException {
351 // load in frame with public key on it.
352 String credentialsFramesetPath = FrameIO.CONTACTS_PATH + username + "-credentials" + File.separator;
353 if (!new File(credentialsFramesetPath).exists()) {
354 return null;
355 }
356 Scanner in = new Scanner(new File(credentialsFramesetPath + "credentials.inf"));
357 String credentialsFrameNumber = in.nextLine().replace(ExpReader.EXTENTION, "");
358 in.close();
359 Frame frame = FrameIO.LoadFrame(username + "-credentials" + credentialsFrameNumber, FrameIO.CONTACTS_PATH);
360 if (frame == null) {
361 return null;
362 }
363
364 // obtain public key from frame
365 Collection<Item> canditates = org.expeditee.auth.Actions.getByContent(frame, "PublicKey");
366 String keyEncoded = "";
367 for (Item i: canditates) {
368 if (i.getData() != null) {
369 keyEncoded = i.getData().get(0);
370 }
371 }
372 if (keyEncoded.isEmpty()) {
373 return null;
374 }
375 byte[] keyBytes = Base64.getDecoder().decode(keyEncoded);
376 return KeyFactory.getInstance(AsymmetricAlgorithm).generatePublic(new X509EncodedKeySpec(keyBytes));
377 }
378
379 final void markRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
380 KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(TRUE, SymmetricAlgorithm));
381 KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
382 keyStore.setEntry(username + "colleaguesRequested", entry, entryPassword);
383 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
384 }
385
386 final void clearRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
387 KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(FALSE, SymmetricAlgorithm));
388 KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
389 keyStore.setEntry(username + "colleaguesRequested", entry, entryPassword);
390 keyStore.store(new FileOutputStream(FrameIO.PARENT_FOLDER + KEYSTOREFILENAME), Constants.CREDENTIALS_KEYSTORE_PASSWORD.toCharArray());
391 }
392
393 final boolean hasRequestedColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
394 String alias = username + "colleaguesRequested";
395 if (!keyStore.containsAlias(alias)) {
396 return false;
397 } else {
398 KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
399 KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(alias, entryPassword);
400 return Arrays.equals(entry.getSecretKey().getEncoded(), TRUE);
401 }
402 }
403
404// final void putColleagues(String username, String[] colleagues) throws KeyStoreException {
405// String alias = username + "colleagues";
406// final SecretKeySpec secretKeySpec = new SecretKeySpec((colleagues[0] + System.getProperty("line.separator") + colleagues[1]).getBytes(), SymmetricAlgorithm);
407// KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(secretKeySpec);
408// KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
409// keyStore.setEntry(alias, entry, entryPassword);
410// }
411//
412// final String[] getColleagues(String username) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
413// String alias = username + "colleagues";
414// if (!keyStore.containsAlias(alias)) {
415// return null;
416// } else {
417// KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(KeyList.PersonalKey.get().getText().toCharArray());
418// KeyStore.SecretKeyEntry entry = (SecretKeyEntry) keyStore.getEntry(alias, entryPassword);
419// byte[] colleaguesEncoded = entry.getSecretKey().getEncoded();
420// String colleagues = new String(colleaguesEncoded);
421// return colleagues.split(System.getProperty("line.separator"));
422// }
423// }
424
425 private static void setInputManagerWindowRoutines() {
426 InputManager manager = EcosystemManager.getInputManager();
427
428 // Refresh the layout when the window resizes
429 manager.addWindowEventListener(new WindowEventListener() {
430 @Override
431 public void onWindowEvent(WindowEventType type)
432 {
433 if (type != WindowEventType.WINDOW_RESIZED) {
434 return;
435 }
436 DisplayController.refreshWindowSize();
437 FrameIO.RefreshCacheImages();
438 for (Frame frame : DisplayController.getFrames()) {
439 if (frame != null) {
440 ItemUtils.Justify(frame);
441 frame.refreshSize();
442 }
443 }
444 DisplayController.requestRefresh(false);
445 }
446 });
447
448 manager.addWindowEventListener(new WindowEventListener() {
449 @Override
450 public void onWindowEvent(WindowEventType type)
451 {
452 if (type != WindowEventType.MOUSE_EXITED_WINDOW) {
453 return;
454 }
455 StandardGestureActions.mouseExitedWindow();
456 }
457 });
458
459 manager.addWindowEventListener(new WindowEventListener() {
460 @Override
461 public void onWindowEvent(WindowEventType type)
462 {
463 if (type != WindowEventType.MOUSE_ENTERED_WINDOW) {
464 return;
465 }
466 StandardGestureActions.mouseEnteredWindow();
467 }
468 });
469
470 manager.addWindowEventListener(new WindowEventListener() {
471 @Override
472 public void onWindowEvent(WindowEventType type)
473 {
474 if (type != WindowEventType.WINDOW_CLOSED) {
475 return;
476 }
477 if (Browser._theBrowser != null) {
478 Browser._theBrowser.exit();
479 }
480 }
481 });
482 }
483}
Note: See TracBrowser for help on using the repository browser.