added registration

This commit is contained in:
Ksan 2026-03-27 17:38:17 +01:00
parent 9fa1a7d914
commit 22711c30f7
16 changed files with 347 additions and 86 deletions

5
.gitignore vendored
View File

@ -1,4 +1,9 @@
keystores/
data/
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/

2
.idea/misc.xml generated
View File

@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -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()

9
plan Normal file
View File

@ -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 <create new poll button>, 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??

View File

@ -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());

View File

@ -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();
}
}
}

View File

@ -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();
}

View File

@ -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)

View File

@ -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);
}

View File

@ -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.");
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}