diff --git a/.gitignore b/.gitignore
index 1fac4d5..ce2526f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
+
+keystores/
+data/
+
.gradle
+
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
@@ -40,4 +45,4 @@ bin/
.vscode/
### Mac OS ###
-.DS_Store
\ No newline at end of file
+.DS_Store
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8852426..1bd18cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 6fc6099..2b42cc5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -17,7 +17,8 @@ dependencies {
implementation("org.bouncycastle:bcprov-jdk18on:1.83")
implementation("org.bouncycastle:bcpkix-jdk18on:1.83")
implementation("org.springframework.security:spring-security-crypto:7.1.0-M2")
-}
+
+ implementation("commons-logging:commons-logging:1.2")}
tasks.test {
useJUnitPlatform()
diff --git a/plan b/plan
new file mode 100644
index 0000000..d93bf09
--- /dev/null
+++ b/plan
@@ -0,0 +1,9 @@
+gui
+registration screen user type choice, and then needed fields, after registration user gets to download/save his certificate
+login screen drag/select file location and then unlock the p12 cert, then login screen.
+
+organizer screen - panel with all current polls, section bellow it for finished polls sorted by latest, above all that , user can select the previus polls to see vote graphs
+
+voter screen - only can see ongoing polls and after selection once he gets a radio button select, then after voting he gets notification? on success??
+
+
diff --git a/src/main/java/dev/ksan/CASystem/CA.java b/src/main/java/dev/ksan/CASystem/CA.java
index ac79568..5e9185c 100644
--- a/src/main/java/dev/ksan/CASystem/CA.java
+++ b/src/main/java/dev/ksan/CASystem/CA.java
@@ -7,6 +7,7 @@ import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -73,7 +74,7 @@ public class CA {
KeyPair keyPair = KeyService.generateRSAKeyPair();
- X500Name issuer = new X500Name(issuerCert.getSubjectX500Principal().getName());
+ X500Name issuer = new JcaX509CertificateHolder(issuerCert).getSubject();
X500Name subject = new X500Name("CN=" + type + ", O=CA, OU=VotingSystem, L=BL, ST=BA");
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
@@ -175,7 +176,7 @@ public class CA {
PublicKey userKey
) throws Exception {
- X500Name issuer = new X500Name(caCert.getSubjectX500Principal().getName());
+ X500Name issuer = new JcaX509CertificateHolder(caCert).getSubject();
X500Name subject = new X500Name("CN=" + username);
BigInteger serial = new BigInteger(64, new SecureRandom());
diff --git a/src/main/java/dev/ksan/CASystem/CAInitializer.java b/src/main/java/dev/ksan/CASystem/CAInitializer.java
new file mode 100644
index 0000000..18ce893
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/CAInitializer.java
@@ -0,0 +1,65 @@
+package dev.ksan.CASystem;
+
+import dev.ksan.temp.AppConfig;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class CAInitializer {
+
+ private static final Path ROOT_CA_PATH = Path.of("keystores/ca/root-ca.p12");
+ private static final char[] CA_PASSWORD = AppConfig.getCaKeystorePassword();
+ private static final char[] TRUSTSTORE_PASSWORD = AppConfig.getTruststorePassword();
+
+ public static void initializeIfNeeded() {
+ if (Files.exists(ROOT_CA_PATH)) {
+ System.out.println("CA hierarchy already initialized.");
+ return;
+ }
+
+ try {
+ System.out.println("Initializing CA hierarchy for the first time...");
+
+ // 1. Root CA
+ CAResult rootCA = CA.generateRootCA();
+
+ // 2. Sub-CAs signed by Root
+ CAResult organizerCA = CA.generateSubCA(
+ CAType.ORGANIZER,
+ rootCA.getCertificate(),
+ rootCA.getKeyPair().getPrivate()
+ );
+ CAResult voterCA = CA.generateSubCA(
+ CAType.VOTER,
+ rootCA.getCertificate(),
+ rootCA.getKeyPair().getPrivate()
+ );
+
+ // 3. Save CA keystores (private keys protected)
+ KeystoreService.saveCAKeyStore("root-ca",
+ rootCA.getKeyPair().getPrivate(), rootCA.getCertificate(), null, CA_PASSWORD);
+
+ KeystoreService.saveCAKeyStore("organizer-ca",
+ organizerCA.getKeyPair().getPrivate(), organizerCA.getCertificate(),
+ rootCA.getCertificate(), CA_PASSWORD);
+
+ KeystoreService.saveCAKeyStore("voter-ca",
+ voterCA.getKeyPair().getPrivate(), voterCA.getCertificate(),
+ rootCA.getCertificate(), CA_PASSWORD);
+
+ // 4. Save truststore (public certs only)
+ TruststoreService.createTruststore(
+ rootCA.getCertificate(),
+ organizerCA.getCertificate(),
+ voterCA.getCertificate(),
+ TRUSTSTORE_PASSWORD
+ );
+
+ System.out.println("CA hierarchy initialized successfully.");
+
+ } catch (Exception e) {
+ System.err.println("FATAL: Could not initialize CA hierarchy: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
\ 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 1b7e474..5fcc2d2 100644
--- a/src/main/java/dev/ksan/CASystem/KeystoreService.java
+++ b/src/main/java/dev/ksan/CASystem/KeystoreService.java
@@ -24,8 +24,8 @@ public class KeystoreService {
public static void saveUserKeystore(String username,
PrivateKey privateKey,
- X509Certificate caCert,
X509Certificate userCert,
+ X509Certificate caCert ,
char[] keystorePassword) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
@@ -61,7 +61,29 @@ public class KeystoreService {
PrivateKey privateKey,
X509Certificate caCert,
X509Certificate issuerCert,
- char[] keystorePassword) throws Exception {}
+ char[] keystorePassword) throws Exception {
+
+
+ KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
+ ks.load(null, null);
+
+ Certificate[] chain = issuerCert != null ?
+ new Certificate[]{caCert, issuerCert} :
+ new Certificate[]{caCert};
+
+ ks.setKeyEntry( KEY_ALIAS, privateKey, keystorePassword, chain);
+
+ Path path = CA_KEYSTORE_DIR.resolve(caName + ".p12");
+ Files.createDirectories(path.getParent());
+
+ try(FileOutputStream fos = new FileOutputStream(path.toFile())) {
+ ks.store(fos, keystorePassword);
+ }
+
+
+
+
+ }
private static KeyStore loadKeyStore(Path path, char[] password) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
@@ -89,8 +111,6 @@ public class KeystoreService {
return Base64.getEncoder().encodeToString(hash).toCharArray();
-
-
}
diff --git a/src/main/java/dev/ksan/CASystem/RegistrationService.java b/src/main/java/dev/ksan/CASystem/RegistrationService.java
index 9be0708..739e9be 100644
--- a/src/main/java/dev/ksan/CASystem/RegistrationService.java
+++ b/src/main/java/dev/ksan/CASystem/RegistrationService.java
@@ -4,6 +4,7 @@ import dev.ksan.Users.Organizer;
import dev.ksan.Users.User;
import dev.ksan.Users.UserRepository;
import dev.ksan.Users.Voter;
+import dev.ksan.temp.AppConfig;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.security.KeyPair;
import java.security.PrivateKey;
@@ -13,10 +14,8 @@ public class RegistrationService {
// CA certs are loaded from truststore (public, accessible before login)
// CA private keys are loaded from CA keystores (protected)
- private static final char[] CA_KEYSTORE_PASSWORD =
- System.getenv("CA_KEYSTORE_PASSWORD").toCharArray();
- private static final char[] TRUSTSTORE_PASSWORD =
- System.getenv("TRUSTSTORE_PASSWORD").toCharArray();
+ private static final char[] CA_KEYSTORE_PASSWORD = AppConfig.getCaKeystorePassword();
+ private static final char[] TRUSTSTORE_PASSWORD = AppConfig.getTruststorePassword();
public static void registerOrganizer(Organizer organizer, String rawPassword)
throws Exception {
@@ -40,27 +39,33 @@ public class RegistrationService {
organizer.setCertificate(cert);
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
- String hashedPassword = encoder.encode(rawPassword);
+ try {
- organizer.setHashedPassword(hashedPassword);
- char[] ksPassword = KeystoreService.deriveKeystorePassword(
- rawPassword,
- organizer.getId().toString()
- );
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
+ String hashedPassword = encoder.encode(rawPassword);
- KeystoreService.saveUserKeystore(
- organizer.getId().toString(),
- keyPair.getPrivate(),
- cert,
- organizerCACert,
- ksPassword
- );
- if (UserRepository.voterExists(organizer.getId().toString())) {
- throw new IllegalStateException("Id already taken: " + organizer.getId());
+ organizer.setHashedPassword(hashedPassword);
+
+ char[] ksPassword = KeystoreService.deriveKeystorePassword(
+ rawPassword,
+ organizer.getId().toString()
+ );
+
+ KeystoreService.saveUserKeystore(
+ organizer.getId().toString(),
+ keyPair.getPrivate(),
+ cert,
+ organizerCACert,
+ ksPassword
+ );
+ if (UserRepository.voterExists(organizer.getId().toString())) {
+ throw new IllegalStateException("Id already taken: " + organizer.getId());
+ }
+ UserRepository.save(organizer);
+ } catch (Exception e) {
+ e.printStackTrace();
}
- UserRepository.save(organizer);
}
public static void registerVoter(Voter voter, String rawPassword)
diff --git a/src/main/java/dev/ksan/CASystem/TruststoreService.java b/src/main/java/dev/ksan/CASystem/TruststoreService.java
index 6db9d9a..9fdfeec 100644
--- a/src/main/java/dev/ksan/CASystem/TruststoreService.java
+++ b/src/main/java/dev/ksan/CASystem/TruststoreService.java
@@ -28,6 +28,7 @@ public class TruststoreService {
ts.setCertificateEntry("voter-ca", voterCACert);
Files.createDirectories(TRUSTSTORE_PATH.getParent());
+
try (OutputStream os = Files.newOutputStream(TRUSTSTORE_PATH)) {
ts.store(os, truststorePassword);
}
diff --git a/src/main/java/dev/ksan/ConsoleApp.java b/src/main/java/dev/ksan/ConsoleApp.java
new file mode 100644
index 0000000..f6c3713
--- /dev/null
+++ b/src/main/java/dev/ksan/ConsoleApp.java
@@ -0,0 +1,26 @@
+package dev.ksan;
+
+import java.util.Scanner;
+
+// TODO temp class for testing functionality
+
+public class ConsoleApp {
+
+ private final Scanner scanner = new Scanner(System.in);
+
+ public void start() {
+ while (true) {
+ System.out.println("\n=== E-Voting System ===");
+ System.out.println("1. Register");
+ System.out.println("0. Exit");
+ System.out.print("Choose: ");
+
+ switch (scanner.nextLine().trim()) {
+ case "1" -> new RegistrationUI(scanner).show();
+ case "2" -> System.out.println("Login not yet implemented");
+ case "0" -> { System.out.println("Goodbye."); return; }
+ default -> System.out.println("Invalid option.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/Main.java b/src/main/java/dev/ksan/Main.java
index 2f1ddeb..32739de 100644
--- a/src/main/java/dev/ksan/Main.java
+++ b/src/main/java/dev/ksan/Main.java
@@ -1,9 +1,6 @@
package dev.ksan;
-import dev.ksan.CASystem.CA;
-import dev.ksan.CASystem.CAResult;
-import dev.ksan.CASystem.CAType;
-import dev.ksan.CASystem.KeyService;
+import dev.ksan.CASystem.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.KeyPair;
@@ -14,40 +11,48 @@ import static dev.ksan.CASystem.CA.publicKeyToPem;
public class Main {
+ static{
+ Security.addProvider(new BouncyCastleProvider());
+ }
public static void main(String args[]) throws Exception {
- Security.addProvider(new BouncyCastleProvider());
- System.out.println("added bouncycastle provider");
- KeyPair key =KeyService.generateRSAKeyPair();
- System.out.println(key.getPrivate());
- System.out.println(key.getPublic());
+// System.out.println("added bouncycastle provider");
+// KeyPair key =KeyService.generateRSAKeyPair();
+// System.out.println(key.getPrivate());
+// System.out.println(key.getPublic());
+//
+//
+//
+// int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
+// System.out.println("Max Key Size for AES : " + maxKeySize);
+//
+//
+//
+// System.out.println("test cert");
+//
+// CAResult cert = CA.generateRootCA();
+// System.out.println(cert.getCertificate().toString());
+//
+// System.out.println("test cert");
+// System.out.println("test cert");
+// System.out.println("test cert");
+// System.out.println("test cert");
+// CAResult prvi = CA.generateSubCA(CAType.VOTER ,cert.getCertificate(),cert.getKeyPair().getPrivate());
+// System.out.println(prvi.getCertificate().toString());
+//
+//
+// String publicKeyPem = publicKeyToPem(cert.getCertificate().getPublicKey());
+// System.out.println("Public Key PEM:\n" + publicKeyPem);
+//
+// System.out.println(CA.validateCertificate(prvi.getCertificate(),cert.getCertificate()));
+// System.out.println(CA.validateCertificate(cert.getCertificate(),cert.getCertificate()));
+// System.out.println(CA.validateCertificate(cert.getCertificate(),prvi.getCertificate()));
+ CAInitializer.initializeIfNeeded();
- int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
- System.out.println("Max Key Size for AES : " + maxKeySize);
-
-
-
- System.out.println("test cert");
-
- CAResult cert = CA.generateRootCA();
- System.out.println(cert.getCertificate().toString());
-
- System.out.println("test cert");
- System.out.println("test cert");
- System.out.println("test cert");
- System.out.println("test cert");
- CAResult prvi = CA.generateSubCA(CAType.VOTER ,cert.getCertificate(),cert.getKeyPair().getPrivate());
- System.out.println(prvi.getCertificate().toString());
-
-
- String publicKeyPem = publicKeyToPem(cert.getCertificate().getPublicKey());
- System.out.println("Public Key PEM:\n" + publicKeyPem);
-
- System.out.println(CA.validateCertificate(prvi.getCertificate(),cert.getCertificate()));
- System.out.println(CA.validateCertificate(cert.getCertificate(),cert.getCertificate()));
- System.out.println(CA.validateCertificate(cert.getCertificate(),prvi.getCertificate()));
+ ConsoleApp app = new ConsoleApp();
+ app.start();
}
}
diff --git a/src/main/java/dev/ksan/RegistrationUI.java b/src/main/java/dev/ksan/RegistrationUI.java
new file mode 100644
index 0000000..faf557b
--- /dev/null
+++ b/src/main/java/dev/ksan/RegistrationUI.java
@@ -0,0 +1,114 @@
+package dev.ksan;
+
+import dev.ksan.CASystem.RegistrationService;
+import dev.ksan.Users.Organizer;
+import dev.ksan.Users.UserRepository;
+import dev.ksan.Users.Voter;
+
+import java.util.Scanner;
+import java.util.UUID;
+
+public class RegistrationUI {
+
+ private final Scanner scanner;
+
+ public RegistrationUI(Scanner scanner) {
+ this.scanner = scanner;
+ }
+
+ public void show() {
+ System.out.println("\n-- Register --");
+ System.out.println("1. Register as Organizer");
+ System.out.println("2. Register as Voter");
+ System.out.print("Choose: ");
+
+ switch (scanner.nextLine().trim()) {
+ case "1" -> registerOrganizer();
+ case "2" -> registerVoter();
+ default -> System.out.println("Invalid option.");
+ }
+ }
+
+ private void registerOrganizer() {
+ try {
+ System.out.print("Organization name: ");
+ String orgName = scanner.nextLine().trim();
+
+ System.out.print("Identification number: ");
+ String idNumber = scanner.nextLine().trim();
+
+ if (UserRepository.organizerExists(idNumber)) {
+ System.out.println("An organizer with that ID already exists.");
+ return;
+ }
+
+ String password = readAndConfirmPassword();
+ if (password == null) return; // passwords didn't match
+
+ Organizer organizer = new Organizer(orgName, (idNumber));
+
+ System.out.println("Registering, please wait...");
+ RegistrationService.registerOrganizer(organizer, password);
+
+ System.out.println("Organizer registered successfully.");
+ System.out.println("Your keystore is saved at: keystores/users/" + idNumber + ".p12");
+
+ } catch (Exception e) {
+ System.out.println("Registration failed: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ private void registerVoter() {
+ try {
+ System.out.print("First name: ");
+ String firstName = scanner.nextLine().trim();
+
+ System.out.print("Last name: ");
+ String lastName = scanner.nextLine().trim();
+
+ System.out.print("Username: ");
+ String username = scanner.nextLine().trim();
+
+ if (UserRepository.voterExists(username)) {
+ System.out.println("That username is already taken.");
+ return;
+ }
+
+ String password = readAndConfirmPassword();
+ if (password == null) return;
+
+ Voter voter = new Voter(firstName, lastName, username);
+
+ System.out.println("Registering, please wait...");
+ RegistrationService.registerVoter(voter, password);
+
+ System.out.println("Voter registered successfully.");
+ System.out.println("Your keystore is saved at: keystores/users/" + username + ".p12");
+
+ } catch (Exception e) {
+ System.out.println("Registration failed: " + e.getMessage());
+ }
+ }
+
+ // Ask for password twice to catch typos
+ private String readAndConfirmPassword() {
+ System.out.print("Password: ");
+ String password = scanner.nextLine();
+
+ System.out.print("Confirm password: ");
+ String confirm = scanner.nextLine();
+
+ if (!password.equals(confirm)) {
+ System.out.println("Passwords do not match.");
+ return null;
+ }
+
+ if (password.length() < 3) {
+ System.out.println("Password must be at least 8 characters.");
+ return null;
+ }
+
+ return password;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/Users/Organizer.java b/src/main/java/dev/ksan/Users/Organizer.java
index 69f87a4..b2d7061 100644
--- a/src/main/java/dev/ksan/Users/Organizer.java
+++ b/src/main/java/dev/ksan/Users/Organizer.java
@@ -13,14 +13,12 @@ public class Organizer extends User {
@Serial
private static final long serialVersionUID = 1L;
private String name;
- private UUID id;
- private String password;
+ private String id;
- public Organizer(String name, UUID id, String password) {
+ public Organizer(String name, String id) {
super();
this.name = name;
this.id = id;
- this.password = password;
}
@@ -28,13 +26,9 @@ public class Organizer extends User {
return name;
}
- public UUID getId() {
+ public String getId() {
return id;
}
- public String getPassword() {
- return password;
- }
-
public String getOrganizationName() {
return getName();
diff --git a/src/main/java/dev/ksan/Users/UserRepository.java b/src/main/java/dev/ksan/Users/UserRepository.java
index 70f14c2..57a4e69 100644
--- a/src/main/java/dev/ksan/Users/UserRepository.java
+++ b/src/main/java/dev/ksan/Users/UserRepository.java
@@ -4,11 +4,10 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
+import java.nio.file.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -125,16 +124,15 @@ public class UserRepository {
private static void writeToFile(User user, Path targetPath) throws Exception {
Path tmpPath = targetPath.resolveSibling(targetPath.getFileName() + ".tmp");
- try (FileOutputStream fos = new FileOutputStream(tmpPath.toFile());
- FileLock fileLock = fos.getChannel().lock();
- ObjectOutputStream oos = new ObjectOutputStream(fos)) {
-
+ // Open a temporary file and write the object
+ try (ObjectOutputStream oos = new ObjectOutputStream(
+ Files.newOutputStream(tmpPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
oos.writeObject(user);
oos.flush();
- // fileLock released automatically on close
}
- Files.move(tmpPath, targetPath, StandardCopyOption.ATOMIC_MOVE,
+ Files.move(tmpPath, targetPath,
+ StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
}
diff --git a/src/main/java/dev/ksan/Users/Voter.java b/src/main/java/dev/ksan/Users/Voter.java
index 89560b6..6b60be1 100644
--- a/src/main/java/dev/ksan/Users/Voter.java
+++ b/src/main/java/dev/ksan/Users/Voter.java
@@ -6,14 +6,14 @@ public class Voter extends User {
@Serial
private static final long serialVersionUID = 1L;
private String name;
+ private String lastName;
private String username;
- private String password;
- public Voter(String name, String username, String password){
+ public Voter(String name, String username, String lastName){
super();
this.name = name;
this.username = username;
- this.password = password;
+ this.lastName = lastName;
}
public String getName(){
@@ -24,6 +24,6 @@ public class Voter extends User {
}
public String getPassword(){
- return password;
+ return lastName;
}
}
diff --git a/src/main/java/dev/ksan/temp/AppConfig.java b/src/main/java/dev/ksan/temp/AppConfig.java
new file mode 100644
index 0000000..a233c98
--- /dev/null
+++ b/src/main/java/dev/ksan/temp/AppConfig.java
@@ -0,0 +1,17 @@
+package dev.ksan.temp;
+
+import java.util.Arrays;
+
+public class AppConfig {
+
+ private static final char[] CA_KEYSTORE_PASSWORD = "ca-keystore-password-123".toCharArray();
+ private static final char[] TRUSTSTORE_PASSWORD = "truststore-password-123".toCharArray();
+
+ public static char[] getCaKeystorePassword() {
+ return Arrays.copyOf(CA_KEYSTORE_PASSWORD, CA_KEYSTORE_PASSWORD.length);
+ }
+
+ public static char[] getTruststorePassword() {
+ return Arrays.copyOf(TRUSTSTORE_PASSWORD, TRUSTSTORE_PASSWORD.length);
+ }
+}
\ No newline at end of file