source: trunk/src/org/expeditee/auth/AuthenticatorBrowser.java@ 1287

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

Updated VM property auth to expeditee.authentication.
Added two static functions to AuthenticationBrowser to help with the language used to determine if a user is authenticated (isAuthenticated and isAuthenticationRequired)

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