idk kms
This commit is contained in:
parent
e866f771a1
commit
9fa1a7d914
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@ -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 {
|
||||
|
||||
224
src/main/java/dev/ksan/CASystem/CA.java
Normal file
224
src/main/java/dev/ksan/CASystem/CA.java
Normal file
@ -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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
src/main/java/dev/ksan/CASystem/CAResult.java
Normal file
22
src/main/java/dev/ksan/CASystem/CAResult.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
6
src/main/java/dev/ksan/CASystem/CAType.java
Normal file
6
src/main/java/dev/ksan/CASystem/CAType.java
Normal file
@ -0,0 +1,6 @@
|
||||
package dev.ksan.CASystem;
|
||||
|
||||
public enum CAType {
|
||||
ORGANIZER,
|
||||
VOTER
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
112
src/main/java/dev/ksan/CASystem/KeystoreService.java
Normal file
112
src/main/java/dev/ksan/CASystem/KeystoreService.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
109
src/main/java/dev/ksan/CASystem/RegistrationService.java
Normal file
109
src/main/java/dev/ksan/CASystem/RegistrationService.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package dev.ksan.CASystem;
|
||||
|
||||
public class RootCA {
|
||||
}
|
||||
86
src/main/java/dev/ksan/CASystem/TruststoreService.java
Normal file
86
src/main/java/dev/ksan/CASystem/TruststoreService.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
196
src/main/java/dev/ksan/Users/UserRepository.java
Normal file
196
src/main/java/dev/ksan/Users/UserRepository.java
Normal file
@ -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<String, ReentrantReadWriteLock> 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<Organizer> findAllOrganizers() throws Exception {
|
||||
return findAll(ORGANIZER_DIR, Organizer.class, "organizer:");
|
||||
}
|
||||
|
||||
public static List<Voter> 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 <T extends User> List<T> findAll(
|
||||
Path dir, Class<T> type, String lockPrefix) throws Exception {
|
||||
|
||||
if (!Files.exists(dir)) return Collections.emptyList();
|
||||
|
||||
List<T> results = new ArrayList<>();
|
||||
try (DirectoryStream<Path> 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());
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user