diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8fb11b1..8852426 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 5f462a1..6fc6099 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,7 +13,10 @@ dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+
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")
}
tasks.test {
diff --git a/src/main/java/dev/ksan/CASystem/CA.java b/src/main/java/dev/ksan/CASystem/CA.java
new file mode 100644
index 0000000..ac79568
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/CA.java
@@ -0,0 +1,224 @@
+package dev.ksan.CASystem;
+
+import dev.ksan.Users.Organizer;
+import dev.ksan.Users.User;
+import dev.ksan.Users.Voter;
+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.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+public class CA {
+
+ public static CAResult generateRootCA() throws Exception {
+
+ KeyPair keyPair = KeyService.generateRSAKeyPair();
+
+ X500Name issuer = new X500Name("CN=RootCA, O=CA, OU=CA, L=BL, ST=BA");
+ X500Name subject = issuer; //self-signed
+
+ BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+
+ Date notBefore = new Date();
+ Date notAfter = new Date(notBefore.getTime() + 3650L * 24 * 60 * 60 * 1000); // 10 year
+
+ JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer,
+ serial,
+ notBefore,
+ notAfter,
+ subject,
+ keyPair.getPublic());
+
+ certBuilder.addExtension(
+ Extension.basicConstraints,
+ true,
+ new BasicConstraints(true)
+ );
+
+ certBuilder.addExtension(
+ Extension.keyUsage,
+ true,
+ new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)
+ );
+
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
+ .setProvider("BC").build(keyPair.getPrivate());
+
+ X509CertificateHolder certHolder = certBuilder.build(signer);
+
+ X509Certificate certificate = new JcaX509CertificateConverter()
+ .setProvider("BC").getCertificate(certHolder);
+
+ certificate.verify(certificate.getPublicKey());
+
+ return new CAResult(certificate, keyPair);
+
+ }
+
+ public static CAResult generateSubCA(CAType type, X509Certificate issuerCert, PrivateKey issuerKey) throws Exception {
+
+ KeyPair keyPair = KeyService.generateRSAKeyPair();
+
+ X500Name issuer = new X500Name(issuerCert.getSubjectX500Principal().getName());
+ X500Name subject = new X500Name("CN=" + type + ", O=CA, OU=VotingSystem, L=BL, ST=BA");
+
+ BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+
+ Date notBefore = new Date();
+ Date notAfter = new Date(notBefore.getTime() + 365L * 24 * 60 * 60 * 1000);
+
+ JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer,
+ serial,
+ notBefore,
+ notAfter,
+ subject,
+ keyPair.getPublic());
+
+ certBuilder.addExtension(
+ Extension.basicConstraints,
+ true,
+ new BasicConstraints(true)
+ );
+
+ certBuilder.addExtension(
+ Extension.keyUsage,
+ true,
+ new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)
+ );
+
+
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
+ .setProvider("BC").build(issuerKey);
+
+ X509CertificateHolder certHolder = certBuilder.build(signer);
+
+ X509Certificate cert = new JcaX509CertificateConverter()
+ .setProvider("BC").getCertificate(certHolder);
+
+ return new CAResult(cert, keyPair);
+
+ }
+
+
+ public static String publicKeyToPem(PublicKey publicKey) throws Exception {
+ try (StringWriter stringWriter = new StringWriter();
+ PemWriter pemWriter = new PemWriter(stringWriter)) {
+ pemWriter.writeObject(new PemObject("PUBLIC KEY", publicKey.getEncoded()));
+
+ pemWriter.flush(); //idk why but why not
+ return stringWriter.toString();
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ public static boolean validateCertificate(
+ X509Certificate cert,
+ X509Certificate caCert
+ ) {
+
+ try {
+
+ cert.checkValidity();
+
+ cert.verify(caCert.getPublicKey());
+
+ return true;
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ // TODO delete probably idk
+ public static X509Certificate registerUser(
+ User user,
+ X509Certificate organizerCACert,
+ PrivateKey organizerCAKey,
+ X509Certificate voterCACert,
+ PrivateKey voterCAKey
+ ) throws Exception {
+
+ if (user instanceof Organizer organizer) {
+ String cn = organizer.getName() + "-" + organizer.getId();
+ return generateUserCertificate(cn, organizerCACert, organizerCAKey, user.getPublicKey());
+
+ } else if (user instanceof Voter voter) {
+ String cn = voter.getUsername();
+ return generateUserCertificate(cn, voterCACert, voterCAKey, user.getPublicKey());
+
+ } else {
+ throw new IllegalArgumentException("Unknown user type: " + user.getClass().getName());
+ }
+ }
+
+ public static X509Certificate generateUserCertificate(
+ String username,
+ X509Certificate caCert,
+ PrivateKey caKey,
+ PublicKey userKey
+ ) throws Exception {
+
+ X500Name issuer = new X500Name(caCert.getSubjectX500Principal().getName());
+ X500Name subject = new X500Name("CN=" + username);
+
+ BigInteger serial = new BigInteger(64, new SecureRandom());
+
+ Date notBefore = new Date();
+ Date notAfter = new Date(notBefore.getTime() + 365L * 24 * 60 * 60 * 1000);
+
+ JcaX509v3CertificateBuilder builder =
+ new JcaX509v3CertificateBuilder(
+ issuer,
+ serial,
+ notBefore,
+ notAfter,
+ subject,
+ userKey
+ );
+
+ builder.addExtension(
+ Extension.basicConstraints,
+ false,
+ new BasicConstraints(false)
+ );
+
+ builder.addExtension(
+ Extension.keyUsage,
+ true,
+ new KeyUsage(KeyUsage.digitalSignature)
+ );
+
+ builder.addExtension(
+ Extension.extendedKeyUsage,
+ false,
+ new ExtendedKeyUsage(KeyPurposeId.id_kp_clientAuth)
+ );
+
+ ContentSigner signer =
+ new JcaContentSignerBuilder("SHA256withRSA")
+ .setProvider("BC").build(caKey);
+
+ return new JcaX509CertificateConverter()
+ .setProvider("BC")
+ .getCertificate(builder.build(signer));
+ }
+
+
+}
diff --git a/src/main/java/dev/ksan/CASystem/CAResult.java b/src/main/java/dev/ksan/CASystem/CAResult.java
new file mode 100644
index 0000000..3b831b8
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/CAResult.java
@@ -0,0 +1,22 @@
+package dev.ksan.CASystem;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public class CAResult {
+
+ X509Certificate certificate;
+ KeyPair keyPair;
+
+ public CAResult(X509Certificate cert, KeyPair keyPair) {
+ this.certificate = cert;
+ this.keyPair = keyPair;
+ }
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
+
+ public KeyPair getKeyPair() {
+ return keyPair;
+ }
+}
diff --git a/src/main/java/dev/ksan/CASystem/CAType.java b/src/main/java/dev/ksan/CASystem/CAType.java
new file mode 100644
index 0000000..c26d03f
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/CAType.java
@@ -0,0 +1,6 @@
+package dev.ksan.CASystem;
+
+public enum CAType {
+ ORGANIZER,
+ VOTER
+}
diff --git a/src/main/java/dev/ksan/CASystem/KeyService.java b/src/main/java/dev/ksan/CASystem/KeyService.java
index e2f9196..5ab3b48 100644
--- a/src/main/java/dev/ksan/CASystem/KeyService.java
+++ b/src/main/java/dev/ksan/CASystem/KeyService.java
@@ -1,13 +1,42 @@
package dev.ksan.CASystem;
+import java.io.FileOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
public class KeyService {
public static KeyPair generateRSAKeyPair() throws Exception {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize(2048);
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
+ generator.initialize(2048, new SecureRandom());
return generator.generateKeyPair();
}
+ public static void storeUserKeyPair(
+ String alias,
+ KeyPair keyPair,
+ X509Certificate cert,
+ String password
+ ) throws Exception {
+
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(null, null);
+
+ ks.setKeyEntry(
+ alias,
+ keyPair.getPrivate(),
+ password.toCharArray(),
+ new Certificate[]{cert}
+ );
+
+ try (FileOutputStream fos =
+ new FileOutputStream(alias + ".p12")) {
+
+ ks.store(fos, password.toCharArray());
+ }
+ }
+
}
diff --git a/src/main/java/dev/ksan/CASystem/KeystoreService.java b/src/main/java/dev/ksan/CASystem/KeystoreService.java
new file mode 100644
index 0000000..1b7e474
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/KeystoreService.java
@@ -0,0 +1,112 @@
+package dev.ksan.CASystem;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+
+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 CA_KEYSTORE_DIR = Path.of("keystores/ca");
+
+
+ public static void saveUserKeystore(String username,
+ PrivateKey privateKey,
+ X509Certificate caCert,
+ X509Certificate userCert,
+ char[] keystorePassword) throws Exception {
+
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ keyStore.load(null, null);
+
+ Certificate[] chain = {userCert, caCert};
+
+ keyStore.setKeyEntry(KEY_ALIAS, privateKey, keystorePassword, chain);
+
+ Path path = USER_KEYSORE_DIR.resolve(username + ".p12");
+
+ Files.createDirectories(path.getParent());
+
+ try(FileOutputStream fos = new FileOutputStream(path.toFile())) {
+ keyStore.store(fos, keystorePassword);
+ }
+
+ }
+
+ public static PrivateKey getPrivateKey(String username, char[] keystorePassword) throws Exception {
+ KeyStore keystore = loadKeyStore(USER_KEYSORE_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);
+ return (X509Certificate) keyStore.getCertificate(KEY_ALIAS);
+ }
+
+ public static void saveCAKeyStore(String caName,
+ PrivateKey privateKey,
+ X509Certificate caCert,
+ X509Certificate issuerCert,
+ char[] keystorePassword) throws Exception {}
+
+ private static KeyStore loadKeyStore(Path path, char[] password) throws Exception {
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+
+ try(FileInputStream fis = new FileInputStream(path.toFile())) {
+ keyStore.load(fis, password);
+ }
+
+ return keyStore;
+
+ }
+
+ public static char[] deriveKeystorePassword(String rawPassword, String username) throws Exception {
+
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+
+ PBEKeySpec spec = new PBEKeySpec(
+ rawPassword.toCharArray(),
+ username.getBytes(StandardCharsets.UTF_8),
+ 100_000,
+ 256
+ );
+
+ byte[] hash = factory.generateSecret(spec).getEncoded();
+
+ return Base64.getEncoder().encodeToString(hash).toCharArray();
+
+
+
+ }
+
+
+ public static PrivateKey loadCAPrivateKey(
+ String caName,
+ char[] caPassword
+ ) throws Exception {
+
+ 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)) {
+ ks.load(is, password);
+ }
+ return ks;
+ }
+}
diff --git a/src/main/java/dev/ksan/CASystem/RegistrationService.java b/src/main/java/dev/ksan/CASystem/RegistrationService.java
new file mode 100644
index 0000000..9be0708
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/RegistrationService.java
@@ -0,0 +1,109 @@
+package dev.ksan.CASystem;
+
+import dev.ksan.Users.Organizer;
+import dev.ksan.Users.User;
+import dev.ksan.Users.UserRepository;
+import dev.ksan.Users.Voter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+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();
+
+ public static void registerOrganizer(Organizer organizer, String rawPassword)
+ throws Exception {
+
+ KeyPair keyPair = KeyService.generateRSAKeyPair();
+
+ X509Certificate organizerCACert = TruststoreService
+ .getTrustedCert("organizer-ca", TRUSTSTORE_PASSWORD);
+ PrivateKey organizerCAKey = KeystoreService
+ .loadCAPrivateKey("organizer-ca", CA_KEYSTORE_PASSWORD);
+
+ String cn = organizer.getName() + "-" + organizer.getId();
+
+ X509Certificate cert = CA.generateUserCertificate(
+ cn,
+ organizerCACert,
+ organizerCAKey,
+ keyPair.getPublic()
+ );
+
+ organizer.setCertificate(cert);
+
+
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
+ String hashedPassword = encoder.encode(rawPassword);
+
+ 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);
+ }
+
+ public static void registerVoter(Voter voter, String rawPassword)
+ throws Exception {
+
+ KeyPair keyPair = KeyService.generateRSAKeyPair();
+
+ X509Certificate voterCACert = TruststoreService
+ .getTrustedCert("voter-ca", TRUSTSTORE_PASSWORD);
+ PrivateKey voterCAKey = KeystoreService
+ .loadCAPrivateKey("voter-ca", CA_KEYSTORE_PASSWORD);
+
+ String cn = voter.getUsername();
+
+ X509Certificate cert = CA.generateUserCertificate(
+ cn,
+ voterCACert,
+ voterCAKey,
+ keyPair.getPublic()
+ );
+
+ voter.setCertificate(cert);
+
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
+ String hashedPassword = encoder.encode(rawPassword);
+
+ voter.setHashedPassword(hashedPassword);
+
+ char[] ksPassword = KeystoreService.deriveKeystorePassword(
+ rawPassword,
+ voter.getUsername()
+ );
+
+ KeystoreService.saveUserKeystore(
+ voter.getUsername(),
+ keyPair.getPrivate(),
+ cert,
+ voterCACert,
+ ksPassword
+ );
+ if (UserRepository.voterExists(voter.getUsername())) {
+ throw new IllegalStateException("Username already taken: " + voter.getUsername());
+ }
+ UserRepository.save(voter);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/CASystem/RootCA.java b/src/main/java/dev/ksan/CASystem/RootCA.java
deleted file mode 100644
index 8d43bd3..0000000
--- a/src/main/java/dev/ksan/CASystem/RootCA.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package dev.ksan.CASystem;
-
-public class RootCA {
-}
diff --git a/src/main/java/dev/ksan/CASystem/TruststoreService.java b/src/main/java/dev/ksan/CASystem/TruststoreService.java
new file mode 100644
index 0000000..6db9d9a
--- /dev/null
+++ b/src/main/java/dev/ksan/CASystem/TruststoreService.java
@@ -0,0 +1,86 @@
+package dev.ksan.CASystem;
+
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.io.InputStream;
+
+public class TruststoreService {
+
+ private static final String TRUSTSTORE_TYPE = "PKCS12";
+ private static final Path TRUSTSTORE_PATH = Path.of("keystores/truststore.p12");
+
+ // Build truststore on first run (during CA initialization)
+ public static void createTruststore(
+ X509Certificate rootCACert,
+ X509Certificate organizerCACert,
+ X509Certificate voterCACert,
+ char[] truststorePassword
+ ) throws Exception {
+
+ KeyStore ts = KeyStore.getInstance(TRUSTSTORE_TYPE);
+ ts.load(null, null);
+
+ ts.setCertificateEntry("root-ca", rootCACert);
+ ts.setCertificateEntry("organizer-ca", organizerCACert);
+ ts.setCertificateEntry("voter-ca", voterCACert);
+
+ Files.createDirectories(TRUSTSTORE_PATH.getParent());
+ try (OutputStream os = Files.newOutputStream(TRUSTSTORE_PATH)) {
+ ts.store(os, truststorePassword);
+ }
+ }
+
+ public static X509Certificate getTrustedCert(
+ String alias, // "root-ca", "organizer-ca", "voter-ca"
+ char[] truststorePassword
+ ) throws Exception {
+
+ KeyStore ts = loadTruststore(truststorePassword);
+ return (X509Certificate) ts.getCertificate(alias);
+ }
+
+ // Validate a user cert against the correct sub-CA
+ public static boolean validateUserCertificate(
+ X509Certificate userCert,
+ CAType caType, // ORGANIZER or VOTER
+ char[] truststorePassword
+ ) throws Exception {
+
+ String alias = switch (caType) {
+ case ORGANIZER -> "organizer-ca";
+ case VOTER -> "voter-ca";
+ };
+
+ X509Certificate caCert = getTrustedCert(alias, truststorePassword);
+
+ try {
+ userCert.checkValidity(); // time validity
+ userCert.verify(caCert.getPublicKey()); // issued by correct CA
+ verifyCertificateChain(caCert, truststorePassword); // CA itself is trusted
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ // Verify sub-CA was signed by Root CA
+ private static void verifyCertificateChain(
+ X509Certificate subCACert,
+ char[] truststorePassword
+ ) throws Exception {
+
+ X509Certificate rootCert = getTrustedCert("root-ca", truststorePassword);
+ subCACert.verify(rootCert.getPublicKey());
+ }
+
+ private static KeyStore loadTruststore(char[] password) throws Exception {
+ KeyStore ts = KeyStore.getInstance(TRUSTSTORE_TYPE);
+ try (InputStream is = Files.newInputStream(TRUSTSTORE_PATH)) {
+ ts.load(is, password);
+ }
+ return ts;
+ }
+}
\ 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 4ba2ba6..2f1ddeb 100644
--- a/src/main/java/dev/ksan/Main.java
+++ b/src/main/java/dev/ksan/Main.java
@@ -1,14 +1,20 @@
package dev.ksan;
+import dev.ksan.CASystem.CA;
+import dev.ksan.CASystem.CAResult;
+import dev.ksan.CASystem.CAType;
import dev.ksan.CASystem.KeyService;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.KeyPair;
import java.security.Security;
+import java.security.cert.X509Certificate;
+
+import static dev.ksan.CASystem.CA.publicKeyToPem;
public class Main {
- static void main() throws Exception {
+ public static void main(String args[]) throws Exception {
Security.addProvider(new BouncyCastleProvider());
System.out.println("added bouncycastle provider");
@@ -20,5 +26,28 @@ public class Main {
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()));
+
}
}
diff --git a/src/main/java/dev/ksan/Users/Organizer.java b/src/main/java/dev/ksan/Users/Organizer.java
index 134f5e7..69f87a4 100644
--- a/src/main/java/dev/ksan/Users/Organizer.java
+++ b/src/main/java/dev/ksan/Users/Organizer.java
@@ -1,7 +1,42 @@
package dev.ksan.Users;
+import dev.ksan.CASystem.CA;
+import dev.ksan.CASystem.KeyService;
+import dev.ksan.CASystem.KeystoreService;
+
+import java.io.Serial;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
import java.util.UUID;
public class Organizer extends User {
- UUID id ;
+ @Serial
+ private static final long serialVersionUID = 1L;
+ private String name;
+ private UUID id;
+ private String password;
+
+ public Organizer(String name, UUID id, String password) {
+ super();
+ this.name = name;
+ this.id = id;
+ this.password = password;
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+ public String getPassword() {
+ return password;
+ }
+
+
+ public String getOrganizationName() {
+ return getName();
+ }
}
diff --git a/src/main/java/dev/ksan/Users/User.java b/src/main/java/dev/ksan/Users/User.java
index 344c5eb..99c31a5 100644
--- a/src/main/java/dev/ksan/Users/User.java
+++ b/src/main/java/dev/ksan/Users/User.java
@@ -1,9 +1,39 @@
package dev.ksan.Users;
-public class User {
- private String username;
- private String password;
+import java.io.Serial;
+import java.io.Serializable;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+public abstract class User implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String hashedPassword;
+ private int failedLoginAttempts;
+ private boolean revoked;
+
+ private X509Certificate certificate;
+
+ public User(){
+
+ //generate key
+ }
+
+ public PublicKey getPublicKey() {
+ return certificate.getPublicKey();
+ }
+
+ public void setCertificate(X509Certificate certificate) {
+
+ this.certificate = certificate;
+ }
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
+
+ public void setHashedPassword(String hashedPassword) {
+ this.hashedPassword = hashedPassword;
+ }
- //keypair i digitalni cert (da li za sve korisnike il samo za glasaca??? kontam da bi trebalo za sve)
}
diff --git a/src/main/java/dev/ksan/Users/UserRepository.java b/src/main/java/dev/ksan/Users/UserRepository.java
new file mode 100644
index 0000000..70f14c2
--- /dev/null
+++ b/src/main/java/dev/ksan/Users/UserRepository.java
@@ -0,0 +1,196 @@
+package dev.ksan.Users;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class UserRepository {
+
+ private static final Path USER_DIR = Path.of("data/users");
+ private static final Path ORGANIZER_DIR = USER_DIR.resolve("organizers");
+ private static final Path VOTER_DIR = USER_DIR.resolve("voters");
+
+ private static final ConcurrentHashMap locks =
+ new ConcurrentHashMap<>();
+
+ private static ReentrantReadWriteLock getLock(String key) {
+ return locks.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
+ }
+
+ public static void save(User user) throws Exception {
+ String key = resolveKey(user);
+ Path path = resolveUserPath(user);
+ Files.createDirectories(path.getParent());
+
+ ReentrantReadWriteLock.WriteLock writeLock = getLock(key).writeLock();
+ writeLock.lock();
+ try {
+ writeToFile(user, path);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ public static Organizer findOrganizer(String identificationNumber) throws Exception {
+ Path path = ORGANIZER_DIR.resolve(identificationNumber + ".ser");
+ ReentrantReadWriteLock.ReadLock readLock = getLock("organizer:" + identificationNumber).readLock();
+ readLock.lock();
+ try {
+ return (Organizer) deserialize(path);
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public static Voter findVoter(String username) throws Exception {
+ Path path = VOTER_DIR.resolve(username + ".ser");
+ ReentrantReadWriteLock.ReadLock readLock = getLock("voter:" + username).readLock();
+ readLock.lock();
+ try {
+ return (Voter) deserialize(path);
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public static List findAllOrganizers() throws Exception {
+ return findAll(ORGANIZER_DIR, Organizer.class, "organizer:");
+ }
+
+ public static List findAllVoters() throws Exception {
+ return findAll(VOTER_DIR, Voter.class, "voter:");
+ }
+
+ public static boolean organizerExists(String identificationNumber) {
+ ReentrantReadWriteLock.ReadLock readLock = getLock("organizer:" + identificationNumber).readLock();
+ readLock.lock();
+ try {
+ return Files.exists(ORGANIZER_DIR.resolve(identificationNumber + ".ser"));
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public static boolean voterExists(String username) {
+ ReentrantReadWriteLock.ReadLock readLock = getLock("voter:" + username).readLock();
+ readLock.lock();
+ try {
+ return Files.exists(VOTER_DIR.resolve(username + ".ser"));
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public static void update(User user) throws Exception {
+ String key = resolveKey(user);
+ Path path = resolveUserPath(user);
+
+ ReentrantReadWriteLock.WriteLock writeLock = getLock(key).writeLock();
+ writeLock.lock();
+ try {
+ if (!Files.exists(path)) {
+ throw new IllegalStateException("Cannot update non-existent user: " + key);
+ }
+ writeToFile(user, path);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ public static void delete(User user) throws Exception {
+ String key = resolveKey(user);
+ Path path = resolveUserPath(user);
+
+ ReentrantReadWriteLock.WriteLock writeLock = getLock(key).writeLock();
+ writeLock.lock();
+ try {
+ Files.deleteIfExists(path);
+ locks.remove(key); // clean up lock entry
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ 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)) {
+
+ oos.writeObject(user);
+ oos.flush();
+ // fileLock released automatically on close
+ }
+
+ Files.move(tmpPath, targetPath, StandardCopyOption.ATOMIC_MOVE,
+ StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ 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)) {
+
+ if (fileLock == null) {
+ throw new IllegalStateException("Could not acquire read lock on: " + path);
+ }
+ return ois.readObject();
+ }
+ }
+
+ private static List findAll(
+ Path dir, Class type, String lockPrefix) throws Exception {
+
+ if (!Files.exists(dir)) return Collections.emptyList();
+
+ List results = new ArrayList<>();
+ try (DirectoryStream stream = Files.newDirectoryStream(dir, "*.ser")) {
+ for (Path path : stream) {
+ // Extract key from filename
+ String filename = path.getFileName().toString().replace(".ser", "");
+ String key = lockPrefix + filename;
+
+ ReentrantReadWriteLock.ReadLock readLock = getLock(key).readLock();
+ readLock.lock();
+ try {
+ Object obj = deserialize(path);
+ if (type.isInstance(obj)) {
+ results.add(type.cast(obj));
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+ }
+ return results;
+ }
+
+ private static Path resolveUserPath(User user) {
+ if (user instanceof Organizer o) {
+ return ORGANIZER_DIR.resolve(o.getId() + ".ser");
+ } else if (user instanceof Voter v) {
+ return VOTER_DIR.resolve(v.getUsername() + ".ser");
+ }
+ throw new IllegalArgumentException("Unknown user type: " + user.getClass().getName());
+ }
+
+ private static String resolveKey(User user) {
+ if (user instanceof Organizer o) return "organizer:" + o.getId();
+ if (user instanceof Voter v) return "voter:" + v.getUsername();
+ throw new IllegalArgumentException("Unknown user type: " + user.getClass().getName());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/Users/Voter.java b/src/main/java/dev/ksan/Users/Voter.java
index 69892fe..89560b6 100644
--- a/src/main/java/dev/ksan/Users/Voter.java
+++ b/src/main/java/dev/ksan/Users/Voter.java
@@ -1,5 +1,29 @@
package dev.ksan.Users;
+import java.io.Serial;
+
public class Voter extends User {
- String name;
+ @Serial
+ private static final long serialVersionUID = 1L;
+ private String name;
+ private String username;
+ private String password;
+
+ public Voter(String name, String username, String password){
+ super();
+ this.name = name;
+ this.username = username;
+ this.password = password;
+ }
+
+ public String getName(){
+ return name;
+ }
+ public String getUsername(){
+ return username;
+ }
+
+ public String getPassword(){
+ return password;
+ }
}