diff --git a/.idea/misc.xml b/.idea/misc.xml index 8fb11b1..1bd18cd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/src/main/java/dev/ksan/CASystem/KeystoreService.java b/src/main/java/dev/ksan/CASystem/KeystoreService.java index 5fcc2d2..51aea48 100644 --- a/src/main/java/dev/ksan/CASystem/KeystoreService.java +++ b/src/main/java/dev/ksan/CASystem/KeystoreService.java @@ -3,6 +3,7 @@ package dev.ksan.CASystem; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -18,7 +19,7 @@ public class KeystoreService { private static String KEYSTORE_TYPE = "PKCS12"; private static String KEY_ALIAS = "key"; - private static final Path USER_KEYSORE_DIR = Path.of("keystores/users"); + private static final Path USER_KEYSTORE_DIR = Path.of("keystores/users"); private static final Path CA_KEYSTORE_DIR = Path.of("keystores/ca"); @@ -35,7 +36,7 @@ public class KeystoreService { keyStore.setKeyEntry(KEY_ALIAS, privateKey, keystorePassword, chain); - Path path = USER_KEYSORE_DIR.resolve(username + ".p12"); + Path path = USER_KEYSTORE_DIR.resolve(username + ".p12"); Files.createDirectories(path.getParent()); @@ -46,14 +47,14 @@ public class KeystoreService { } public static PrivateKey getPrivateKey(String username, char[] keystorePassword) throws Exception { - KeyStore keystore = loadKeyStore(USER_KEYSORE_DIR.resolve(username+".p12"), keystorePassword); + KeyStore keystore = loadKeyStore(USER_KEYSTORE_DIR.resolve(username+".p12"), keystorePassword); return (PrivateKey) keystore.getKey(KEY_ALIAS, keystorePassword); } public static X509Certificate getCertificate(String username, char[] keystorePassword) throws Exception { - KeyStore keyStore = loadKeyStore(USER_KEYSORE_DIR.resolve(username+".p12"), keystorePassword); + KeyStore keyStore = loadKeyStore(USER_KEYSTORE_DIR.resolve(username+".p12"), keystorePassword); return (X509Certificate) keyStore.getCertificate(KEY_ALIAS); } @@ -122,6 +123,7 @@ public class KeystoreService { KeyStore ks = loadKeystore(CA_KEYSTORE_DIR.resolve(caName + ".p12"), caPassword); return (PrivateKey) ks.getKey(KEY_ALIAS, caPassword); } + private static KeyStore loadKeystore(Path path, char[] password) throws Exception { KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE); try (InputStream is = Files.newInputStream(path)) { @@ -129,4 +131,5 @@ public class KeystoreService { } return ks; } + } diff --git a/src/main/java/dev/ksan/CASystem/LoginService.java b/src/main/java/dev/ksan/CASystem/LoginService.java index 0455ce3..2483f8c 100644 --- a/src/main/java/dev/ksan/CASystem/LoginService.java +++ b/src/main/java/dev/ksan/CASystem/LoginService.java @@ -108,6 +108,7 @@ public class LoginService { user.setFailedLoginAttempts(user.getFailedLoginAttempts() + 1); + System.out.println("Attempt failed: " + user.getFailedLoginAttempts()); if (user.getFailedLoginAttempts() >= 3) { user.setRevoked(true); diff --git a/src/main/java/dev/ksan/Users/User.java b/src/main/java/dev/ksan/Users/User.java index 1e175f2..4c23457 100644 --- a/src/main/java/dev/ksan/Users/User.java +++ b/src/main/java/dev/ksan/Users/User.java @@ -17,7 +17,6 @@ public abstract class User implements Serializable { public User(){ - //generate key } public PublicKey getPublicKey() { diff --git a/src/main/java/dev/ksan/Users/UserRepository.java b/src/main/java/dev/ksan/Users/UserRepository.java index 57a4e69..ac517ea 100644 --- a/src/main/java/dev/ksan/Users/UserRepository.java +++ b/src/main/java/dev/ksan/Users/UserRepository.java @@ -136,20 +136,35 @@ public class UserRepository { StandardCopyOption.REPLACE_EXISTING); } + //this was annoying i dont wannt look at java.nio.channels.ClosedChannelException again private static Object deserialize(Path path) throws Exception { if (!Files.exists(path)) return null; - try (FileInputStream fis = new FileInputStream(path.toFile()); - FileLock fileLock = fis.getChannel().tryLock(0, Long.MAX_VALUE, true); // shared read lock - ObjectInputStream ois = new ObjectInputStream(fis)) { + FileInputStream fis = new FileInputStream(path.toFile()); + FileLock fileLock = null; + + try { + fileLock = fis.getChannel().tryLock(0, Long.MAX_VALUE, true); if (fileLock == null) { throw new IllegalStateException("Could not acquire read lock on: " + path); } - return ois.readObject(); + + ObjectInputStream ois = new ObjectInputStream(fis); + Object obj = ois.readObject(); + ois.close(); // close stream AFTER reading + + return obj; + + } finally { + if (fileLock != null && fileLock.isValid()) { + fileLock.release(); // release FIRST + } + fis.close(); // then close channel } } + private static List findAll( Path dir, Class type, String lockPrefix) throws Exception { diff --git a/src/main/java/dev/ksan/Users/Voter.java b/src/main/java/dev/ksan/Users/Voter.java index 6b60be1..dca04b8 100644 --- a/src/main/java/dev/ksan/Users/Voter.java +++ b/src/main/java/dev/ksan/Users/Voter.java @@ -9,7 +9,7 @@ public class Voter extends User { private String lastName; private String username; - public Voter(String name, String username, String lastName){ + public Voter(String name, String lastName, String username){ super(); this.name = name; this.username = username; diff --git a/src/main/java/dev/ksan/ui/HomeController.java b/src/main/java/dev/ksan/ui/HomeController.java index 40d908c..907e115 100644 --- a/src/main/java/dev/ksan/ui/HomeController.java +++ b/src/main/java/dev/ksan/ui/HomeController.java @@ -23,7 +23,7 @@ public class HomeController { @FXML public void initialize() { - loginButton.setOnAction(e -> System.out.println("Go to login")); + loginButton.setOnAction(e -> goToLogin()); registerButton.setOnAction(e -> goToRegister()); } @@ -47,4 +47,23 @@ public class HomeController { } } + private void goToLogin() { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LoginView.fxml")); + Scene oldScene = stage.getScene(); + Scene scene = new Scene(loader.load(), + oldScene.getWidth(), + oldScene.getHeight() + ); + + LoginController controller = loader.getController(); + controller.setStage(stage); + + stage.setScene(scene); + stage.setTitle("Register"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } \ No newline at end of file diff --git a/src/main/java/dev/ksan/ui/LoginController.java b/src/main/java/dev/ksan/ui/LoginController.java new file mode 100644 index 0000000..575efaf --- /dev/null +++ b/src/main/java/dev/ksan/ui/LoginController.java @@ -0,0 +1,313 @@ +package dev.ksan.ui; + +import dev.ksan.CASystem.CAType; +import dev.ksan.CASystem.KeystoreService; +import dev.ksan.CASystem.LoginService; +import dev.ksan.Users.Organizer; +import dev.ksan.Users.User; +import dev.ksan.Users.Voter; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +public class LoginController { + + @FXML private StackPane dropZone; + //currently everything is saved as .p12? so we also need to insert password here???? maybe?? + // or change it so that the user just has the plain cert and we hand that in? and we get + // the private only key then after they login finally + // this is the password field for the p12 keystore + @FXML private PasswordField passwordField; + @FXML private Label dropZoneLabel; + @FXML private Label errorLabel; + @FXML private Button loginButton; + + @FXML private VBox form1; + @FXML private VBox form2; + @FXML + private Button finishLoginButton; + + @FXML + private TextField usernameField; + + @FXML + private PasswordField userPasswordField; + + + private User certUser; + + private Stage stage; + private File droppedFile; + private X509Certificate loadedCert; + private CAType detectedCAType; + + public void setStage(Stage stage) { + this.stage = stage; + } + + @FXML + public void initialize() { + showForm1(); + dropZone.setStyle( + "-fx-border-color: #888;" + + "-fx-border-width: 2;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + + "-fx-background-color: #f9f9f9;" + ); + + errorLabel.setVisible(false); + loginButton.setDisable(true); // disabled until cert + password are provided + + setupDropZone(); + + passwordField.textProperty().addListener((obs, oldVal, newVal) -> { + loginButton.setDisable(droppedFile == null || newVal.trim().isEmpty()); + }); + userPasswordField.textProperty().addListener((obs, oldVal, newVal) -> { + finishLoginButton.setDisable( newVal.trim().isEmpty()); + }); + } + + private void setupDropZone() { + dropZone.setOnDragEntered(e -> { + if (e.getDragboard().hasFiles()) + dropZone.setStyle(dropZone.getStyle() + "-fx-border-color: #4A90D9;"); + }); + + dropZone.setOnDragExited(e -> { + dropZone.setStyle(dropZone.getStyle().replace("-fx-border-color: #4A90D9;", "-fx-border-color: #888;")); + }); + + dropZone.setOnDragOver(event -> { + if (event.getGestureSource() != dropZone && event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY); + } + event.consume(); + }); + + dropZone.setOnDragDropped(event -> { + Dragboard db = event.getDragboard(); + boolean success = false; + + if (db.hasFiles()) { + File file = db.getFiles().get(0); + if (file.getName().endsWith(".p12")) { + droppedFile = file; + dropZoneLabel.setText("📄 " + file.getName()); + success = true; + showError("Certificate loaded. Enter your password and click Login."); + } else { + showError("Please drop a .p12 keystore file."); + } + } + + event.setDropCompleted(success); + event.consume(); + }); + + dropZone.setOnMouseClicked(e -> browseForKeystore()); + } + + private void browseForKeystore() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select your keystore (.p12)"); + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter("PKCS12 Keystore", "*.p12")); + + File file = fileChooser.showOpenDialog(stage); + if (file != null) { + droppedFile = file; + dropZoneLabel.setText("📄 " + file.getName()); + } + } + + @FXML + public void onLoginClicked() { + clearError(); + + if (droppedFile == null) { + showError("Please provide your keystore (.p12) file."); + return; + } + + String password = passwordField.getText(); + if (password.isBlank()) { + showError("Please enter your password."); + return; + } + + String identity = droppedFile.getName().replace(".p12", ""); + + loginButton.setDisable(true); + + Task loginTask = new Task<>() { + @Override + protected User call() throws Exception { + + char[] ksPassword = KeystoreService.deriveKeystorePassword(password, identity); + KeyStore ks = KeyStore.getInstance("PKCS12"); + + try (InputStream is = new FileInputStream(droppedFile)) { + ks.load(is, Arrays.copyOf(ksPassword, ksPassword.length)); + } catch (Exception e) { + // TODO + // if this also counts as unsuccessful attempt then we need + // to add handle failed attempt here too + } + + + X509Certificate cert = (X509Certificate) ks.getCertificate("key"); + if (cert == null) + throw new SecurityException("No certificate found in keystore."); + + CAType caType = LoginService.validateCertificate(cert); + + + showForm2(); + loadedCert = cert; + detectedCAType = caType; + return LoginService.validateCredentials(identity, password, cert, caType); + } + }; + + loginTask.setOnSucceeded(e -> { + User user = loginTask.getValue(); + certUser=user; + }); + + loginTask.setOnFailed(e -> { + certUser=null; + Throwable ex = loginTask.getException(); + showError(ex.getMessage()); + loginButton.setDisable(false); + }); + + new Thread(loginTask).start(); + } + @FXML + public void onFinalLoginClicked() { + clearError(); + + + String password = userPasswordField.getText(); + if (password.isBlank()) { + showError("Please enter your password."); + return; + } + + + finishLoginButton.setDisable(true); + + Task loginTask = new Task<>() { + @Override + protected Boolean call() throws Exception { + + + return userLogin(); + } + }; + + loginTask.setOnSucceeded(e -> { + navigateToUserUI(certUser); + }); + + loginTask.setOnFailed(e -> { + certUser = null; + loadedCert = null; + detectedCAType = null; + Throwable ex = loginTask.getException(); + showError(ex.getMessage()); + finishLoginButton.setDisable(true); + }); + + new Thread(loginTask).start(); + } + + private boolean userLogin() { + + try{ + + User user = LoginService.validateCredentials(usernameField.getText().trim(), + userPasswordField.getText().trim(), loadedCert, detectedCAType); + return user == certUser; + } catch (Exception e) { + e.printStackTrace(); + } + + return true; + + } + + + // TODO maybe pass in the certificate and/or keypair? + private void navigateToUserUI(User user) { + try { + + if (user instanceof Organizer organizer) { + FXMLLoader loader = new FXMLLoader( + getClass().getResource("/fxml/OrganizerView.fxml")); + Parent root = loader.load(); + OrganizerController controller = loader.getController(); + controller.setOrganizer(organizer); + controller.setStage(stage); + stage.setScene(new Scene(root)); + + } else if (user instanceof Voter voter) { + FXMLLoader loader = new FXMLLoader( + getClass().getResource("/fxml/VoterView.fxml")); + Parent root = loader.load(); + VoterController controller = loader.getController(); + controller.setVoter(voter); + controller.setStage(stage); + stage.setScene(new Scene(root)); + } + + } catch (Exception e) { + showError("Failed to load UI: " + e.getMessage()); + loginButton.setDisable(false); + } + } + + private void showError(String message) { + Platform.runLater(() -> { + errorLabel.setText(message); + errorLabel.setVisible(true); + }); + } + + private void clearError() { + errorLabel.setText(""); + errorLabel.setVisible(false); + } + + + @FXML + private void showForm2() { + form1.setVisible(false); + form2.setVisible(true); + } + + @FXML + private void showForm1() { + form1.setVisible(true); + form2.setVisible(false); + } +} \ No newline at end of file diff --git a/src/main/java/dev/ksan/ui/OrganizerController.java b/src/main/java/dev/ksan/ui/OrganizerController.java new file mode 100644 index 0000000..53b94f8 --- /dev/null +++ b/src/main/java/dev/ksan/ui/OrganizerController.java @@ -0,0 +1,18 @@ +package dev.ksan.ui; + +import dev.ksan.Users.Organizer; +import javafx.stage.Stage; + +public class OrganizerController { + private Organizer organizer; + + private Stage stage; + public void setOrganizer(Organizer organizer) { + this.organizer = organizer; + } + + public void setStage(Stage stage) { + this.stage = stage; + + } +} diff --git a/src/main/java/dev/ksan/ui/RegisterOrganizerController.java b/src/main/java/dev/ksan/ui/RegisterOrganizerController.java index 6f38270..23c85f6 100644 --- a/src/main/java/dev/ksan/ui/RegisterOrganizerController.java +++ b/src/main/java/dev/ksan/ui/RegisterOrganizerController.java @@ -1,5 +1,8 @@ package dev.ksan.ui; +import dev.ksan.CASystem.RegistrationService; +import dev.ksan.Users.Organizer; +import dev.ksan.Users.Voter; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.PasswordField; @@ -31,4 +34,69 @@ public class RegisterOrganizerController { public void setStage(Stage stage) { this.stage = stage; } + + @FXML + public void initialize() { + registerButton.setOnAction(event -> tryRegisterVoter()); + } + + private void tryRegisterVoter() { + + try { + if (!checkAndHighlightFields()) return; + + Organizer organizer = new Organizer(nameField.getText().trim(),idField.getText().trim()); + + + + + RegistrationService.registerOrganizer(organizer, passwordField.getText()); + + }catch (Exception e) { + displayError("Username already Taken"); + } + + } + + //TODO + private void loginUser(Voter voter) { + } + + + //TODO + private void displayError(String usernameAlreadyTaken) { + } + + // return true if everything is filled + private boolean checkAndHighlightFields() { + if (nameField.getText().trim().isEmpty()) { + nameField.setStyle("-fx-text-fill: red"); + return false; + }else{ + nameField.setStyle("-fx-text-fill: white"); + } + if (idField.getText().isEmpty()) { + idField.setStyle("-fx-text-fill: red"); + return false; + }else{ + idField.setStyle("-fx-text-fill: white"); + } + if (passwordField.getText().isEmpty()) { + passwordField.setStyle("-fx-text-fill: red"); + return false; + }else{ + passwordField.setStyle("-fx-text-fill: white"); + } + if (confirmPasswordField.getText().isEmpty() || !confirmPasswordField.getText().equals(passwordField.getText())) { + confirmPasswordField.setStyle("-fx-text-fill: red"); + displayError("Passwords do not match"); + return false; + }else{ + confirmPasswordField.setStyle("-fx-text-fill: white"); + } + + + return true; + } + } diff --git a/src/main/java/dev/ksan/ui/RegisterVoterController.java b/src/main/java/dev/ksan/ui/RegisterVoterController.java index 213f0b6..e44fe77 100644 --- a/src/main/java/dev/ksan/ui/RegisterVoterController.java +++ b/src/main/java/dev/ksan/ui/RegisterVoterController.java @@ -1,5 +1,8 @@ package dev.ksan.ui; +import dev.ksan.CASystem.RegistrationService; +import dev.ksan.Users.UserRepository; +import dev.ksan.Users.Voter; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.PasswordField; @@ -38,6 +41,79 @@ public class RegisterVoterController { } @FXML public void initialize() { + registerButton.setOnAction(event -> tryRegisterVoter()); } + private void tryRegisterVoter() { + + System.out.println("Registering voter"); + try { + if (!checkAndHighlightFields()) return; + + Voter voter = new Voter(nameField.getText().trim(), surnameField.getText().trim(), + usernameField.getText().trim()); + + + + RegistrationService.registerVoter(voter, passwordField.getText()); + + loginUser(voter); + }catch (Exception e) { + displayError("Username already Taken"); + } + + } + + //TODO + private void loginUser(Voter voter) { + System.out.println("registered: " + voter); + } + + + //TODO + private void displayError(String usernameAlreadyTaken) { + } + + // return true if everything is filled + private boolean checkAndHighlightFields() { + if (nameField.getText().isEmpty()) { + nameField.setStyle("-fx-text-fill: red"); + return false; + }else{ + nameField.setStyle("-fx-text-fill: black"); + } + if (surnameField.getText().isEmpty()) { + surnameField.setStyle("-fx-text-fill: red"); + return false; + }else{ + surnameField.setStyle("-fx-text-fill: black"); + } + if (usernameField.getText().isEmpty()) { + usernameField.setStyle("-fx-text-fill: red"); + return false; + }else{ + usernameField.setStyle("-fx-text-fill: black"); + } + if (passwordField.getText().isEmpty()) { + passwordField.setStyle("-fx-text-fill: red"); + return false; + }else{ + passwordField.setStyle("-fx-text-fill: black"); + } + if (confirmPasswordField.getText().isEmpty() || !confirmPasswordField.getText().equals(passwordField.getText())) { + confirmPasswordField.setStyle("-fx-text-fill: red"); + displayError("Passwords do not match"); + return false; + }else{ + confirmPasswordField.setStyle("-fx-text-fill: black"); + } + + if(UserRepository.voterExists(usernameField.getText().trim())){ + displayError("Username already Taken"); + return false; + } + return true; + } + + } diff --git a/src/main/java/dev/ksan/ui/VoterController.java b/src/main/java/dev/ksan/ui/VoterController.java new file mode 100644 index 0000000..726b9e0 --- /dev/null +++ b/src/main/java/dev/ksan/ui/VoterController.java @@ -0,0 +1,17 @@ +package dev.ksan.ui; + +import dev.ksan.Users.Voter; +import javafx.stage.Stage; + +public class VoterController { + private Voter voter; + private Stage stage; + + public void setVoter(Voter voter) { + this.voter = voter; + } + + public void setStage(Stage stage) { + this.stage = stage; + } +} diff --git a/src/main/resources/fxml/LoginView.fxml b/src/main/resources/fxml/LoginView.fxml index 71977ec..694bb47 100644 --- a/src/main/resources/fxml/LoginView.fxml +++ b/src/main/resources/fxml/LoginView.fxml @@ -1,14 +1,88 @@ - - - - - + + + + + + + + + + + + - - - + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/src/main/resources/fxml/RegisterVoterView.fxml b/src/main/resources/fxml/RegisterVoterView.fxml index 30a62f2..b769444 100644 --- a/src/main/resources/fxml/RegisterVoterView.fxml +++ b/src/main/resources/fxml/RegisterVoterView.fxml @@ -12,7 +12,6 @@ -
@@ -83,7 +82,7 @@ - + diff --git a/src/main/resources/fxml/VoterView.fxml b/src/main/resources/fxml/VoterView.fxml index e69de29..6f7f04d 100644 --- a/src/main/resources/fxml/VoterView.fxml +++ b/src/main/resources/fxml/VoterView.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +