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

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

The role of USER_NOBODY is different from ProfileManagment.USER_NAME_FLAG. This change reflects a better understanding of that.
We can set USER_NOBDY back to 'nobody' as originally designed.

Checking if you are authenticated now uses the username value kept track of in the Java property.

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