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

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

Added ability to send one off secure messages.

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