added login logic, still needs some work
This commit is contained in:
parent
7ba9e4f5e6
commit
80ca3240f1
@ -11,8 +11,9 @@ public class CAInitializer {
|
|||||||
private static final char[] CA_PASSWORD = AppConfig.getCaKeystorePassword();
|
private static final char[] CA_PASSWORD = AppConfig.getCaKeystorePassword();
|
||||||
private static final char[] TRUSTSTORE_PASSWORD = AppConfig.getTruststorePassword();
|
private static final char[] TRUSTSTORE_PASSWORD = AppConfig.getTruststorePassword();
|
||||||
|
|
||||||
public static void initializeIfNeeded() {
|
public static void initializeIfNeeded() throws Exception {
|
||||||
if (Files.exists(ROOT_CA_PATH)) {
|
if (Files.exists(ROOT_CA_PATH)) {
|
||||||
|
CRLService.initializeIfNeeded();
|
||||||
System.out.println("CA hierarchy already initialized.");
|
System.out.println("CA hierarchy already initialized.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -20,10 +21,8 @@ public class CAInitializer {
|
|||||||
try {
|
try {
|
||||||
System.out.println("Initializing CA hierarchy for the first time...");
|
System.out.println("Initializing CA hierarchy for the first time...");
|
||||||
|
|
||||||
// 1. Root CA
|
|
||||||
CAResult rootCA = CA.generateRootCA();
|
CAResult rootCA = CA.generateRootCA();
|
||||||
|
|
||||||
// 2. Sub-CAs signed by Root
|
|
||||||
CAResult organizerCA = CA.generateSubCA(
|
CAResult organizerCA = CA.generateSubCA(
|
||||||
CAType.ORGANIZER,
|
CAType.ORGANIZER,
|
||||||
rootCA.getCertificate(),
|
rootCA.getCertificate(),
|
||||||
@ -35,7 +34,6 @@ public class CAInitializer {
|
|||||||
rootCA.getKeyPair().getPrivate()
|
rootCA.getKeyPair().getPrivate()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Save CA keystores (private keys protected)
|
|
||||||
KeystoreService.saveCAKeyStore("root-ca",
|
KeystoreService.saveCAKeyStore("root-ca",
|
||||||
rootCA.getKeyPair().getPrivate(), rootCA.getCertificate(), null, CA_PASSWORD);
|
rootCA.getKeyPair().getPrivate(), rootCA.getCertificate(), null, CA_PASSWORD);
|
||||||
|
|
||||||
@ -47,7 +45,6 @@ public class CAInitializer {
|
|||||||
voterCA.getKeyPair().getPrivate(), voterCA.getCertificate(),
|
voterCA.getKeyPair().getPrivate(), voterCA.getCertificate(),
|
||||||
rootCA.getCertificate(), CA_PASSWORD);
|
rootCA.getCertificate(), CA_PASSWORD);
|
||||||
|
|
||||||
// 4. Save truststore (public certs only)
|
|
||||||
TruststoreService.createTruststore(
|
TruststoreService.createTruststore(
|
||||||
rootCA.getCertificate(),
|
rootCA.getCertificate(),
|
||||||
organizerCA.getCertificate(),
|
organizerCA.getCertificate(),
|
||||||
@ -55,6 +52,8 @@ public class CAInitializer {
|
|||||||
TRUSTSTORE_PASSWORD
|
TRUSTSTORE_PASSWORD
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CRLService.initializeIfNeeded();
|
||||||
|
|
||||||
System.out.println("CA hierarchy initialized successfully.");
|
System.out.println("CA hierarchy initialized successfully.");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
140
src/main/java/dev/ksan/CASystem/CRLService.java
Normal file
140
src/main/java/dev/ksan/CASystem/CRLService.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package dev.ksan.CASystem;
|
||||||
|
|
||||||
|
import dev.ksan.temp.AppConfig;
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.CRLReason;
|
||||||
|
import org.bouncycastle.cert.X509CRLEntryHolder;
|
||||||
|
import org.bouncycastle.cert.X509CRLHolder;
|
||||||
|
import org.bouncycastle.cert.X509v2CRLBuilder;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CRLService {
|
||||||
|
|
||||||
|
private static final Path CRL_DIR = Path.of("data/crl");
|
||||||
|
private static final String ORGANIZER_CRL = "organizer-ca.crl";
|
||||||
|
private static final String VOTER_CRL = "voter-ca.crl";
|
||||||
|
|
||||||
|
public static void initializeIfNeeded() throws Exception {
|
||||||
|
Files.createDirectories(CRL_DIR);
|
||||||
|
|
||||||
|
if (!Files.exists(CRL_DIR.resolve(ORGANIZER_CRL))) {
|
||||||
|
createEmptyCRL(CAType.ORGANIZER);
|
||||||
|
System.out.println("Organizer CRL initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(CRL_DIR.resolve(VOTER_CRL))) {
|
||||||
|
createEmptyCRL(CAType.VOTER);
|
||||||
|
System.out.println("Voter CRL initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void revokeCertificate(
|
||||||
|
X509Certificate certToRevoke,
|
||||||
|
CAType caType
|
||||||
|
) throws Exception {
|
||||||
|
|
||||||
|
PrivateKey caKey = KeystoreService.loadCAPrivateKey(
|
||||||
|
caType == CAType.ORGANIZER ? "organizer-ca" : "voter-ca",
|
||||||
|
AppConfig.getCaKeystorePassword()
|
||||||
|
);
|
||||||
|
|
||||||
|
X509Certificate caCert = TruststoreService.getTrustedCert(
|
||||||
|
caType == CAType.ORGANIZER ? "organizer-ca" : "voter-ca",
|
||||||
|
AppConfig.getTruststorePassword()
|
||||||
|
);
|
||||||
|
|
||||||
|
List<BigInteger> revokedSerials = loadRevokedSerials(caType);
|
||||||
|
|
||||||
|
revokedSerials.add(certToRevoke.getSerialNumber());
|
||||||
|
|
||||||
|
buildAndSaveCRL(revokedSerials, caCert, caKey, caType);
|
||||||
|
|
||||||
|
System.out.println("Certificate revoked. Serial: " + certToRevoke.getSerialNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRevoked(X509Certificate cert, CAType caType) {
|
||||||
|
try {
|
||||||
|
List<BigInteger> revokedSerials = loadRevokedSerials(caType);
|
||||||
|
return revokedSerials.contains(cert.getSerialNumber());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createEmptyCRL(CAType caType) throws Exception {
|
||||||
|
PrivateKey caKey = KeystoreService.loadCAPrivateKey(
|
||||||
|
caType == CAType.ORGANIZER ? "organizer-ca" : "voter-ca",
|
||||||
|
AppConfig.getCaKeystorePassword()
|
||||||
|
);
|
||||||
|
X509Certificate caCert = TruststoreService.getTrustedCert(
|
||||||
|
caType == CAType.ORGANIZER ? "organizer-ca" : "voter-ca",
|
||||||
|
AppConfig.getTruststorePassword()
|
||||||
|
);
|
||||||
|
buildAndSaveCRL(new ArrayList<>(), caCert, caKey, caType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void buildAndSaveCRL(
|
||||||
|
List<BigInteger> revokedSerials,
|
||||||
|
X509Certificate caCert,
|
||||||
|
PrivateKey caKey,
|
||||||
|
CAType caType
|
||||||
|
) throws Exception {
|
||||||
|
|
||||||
|
X500Name issuerName = new JcaX509CertificateHolder(caCert).getSubject();
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
Date nextUpdate = new Date(now.getTime() + 24L * 60 * 60 * 1000); // 24h
|
||||||
|
|
||||||
|
X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerName, now);
|
||||||
|
crlBuilder.setNextUpdate(nextUpdate);
|
||||||
|
|
||||||
|
for (BigInteger serial : revokedSerials) {
|
||||||
|
crlBuilder.addCRLEntry(serial, now, CRLReason.unspecified);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
|
||||||
|
.setProvider("BC").build(caKey);
|
||||||
|
|
||||||
|
X509CRLHolder crlHolder = crlBuilder.build(signer);
|
||||||
|
|
||||||
|
Path crlPath = CRL_DIR.resolve(
|
||||||
|
caType == CAType.ORGANIZER ? ORGANIZER_CRL : VOTER_CRL);
|
||||||
|
|
||||||
|
Files.write(crlPath, crlHolder.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<BigInteger> loadRevokedSerials(CAType caType) throws Exception {
|
||||||
|
Path crlPath = CRL_DIR.resolve(
|
||||||
|
caType == CAType.ORGANIZER ? ORGANIZER_CRL : VOTER_CRL);
|
||||||
|
|
||||||
|
if (!Files.exists(crlPath)) return new ArrayList<>();
|
||||||
|
|
||||||
|
byte[] crlBytes = Files.readAllBytes(crlPath);
|
||||||
|
X509CRLHolder crlHolder = new X509CRLHolder(crlBytes);
|
||||||
|
|
||||||
|
List<BigInteger> serials = new ArrayList<>();
|
||||||
|
|
||||||
|
Collection<?> revoked = crlHolder.getRevokedCertificates();
|
||||||
|
if (revoked != null) {
|
||||||
|
for (Object obj : revoked) {
|
||||||
|
X509CRLEntryHolder entry = (X509CRLEntryHolder) obj;
|
||||||
|
serials.add(entry.getSerialNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serials;
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/main/java/dev/ksan/CASystem/LoginService.java
Normal file
137
src/main/java/dev/ksan/CASystem/LoginService.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
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 dev.ksan.temp.AppConfig;
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCrypt;
|
||||||
|
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
public class LoginService {
|
||||||
|
|
||||||
|
private static User currentUser = null;
|
||||||
|
|
||||||
|
public static CAType validateCertificate(X509Certificate submittedCert) throws Exception {
|
||||||
|
|
||||||
|
// try organizer CA first, then voter CA
|
||||||
|
if (TruststoreService.validateUserCertificate(
|
||||||
|
submittedCert, CAType.ORGANIZER, AppConfig.getTruststorePassword())) {
|
||||||
|
|
||||||
|
if (CRLService.isRevoked(submittedCert, CAType.ORGANIZER))
|
||||||
|
throw new SecurityException("Certificate has been revoked.");
|
||||||
|
|
||||||
|
return CAType.ORGANIZER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TruststoreService.validateUserCertificate(
|
||||||
|
submittedCert, CAType.VOTER, AppConfig.getTruststorePassword())) {
|
||||||
|
|
||||||
|
if (CRLService.isRevoked(submittedCert, CAType.VOTER))
|
||||||
|
throw new SecurityException("Certificate has been revoked.");
|
||||||
|
|
||||||
|
return CAType.VOTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SecurityException("Certificate is not valid or not issued by this system.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static User validateCredentials(
|
||||||
|
String usernameOrId,
|
||||||
|
String rawPassword,
|
||||||
|
X509Certificate submittedCert,
|
||||||
|
CAType caType
|
||||||
|
) throws Exception {
|
||||||
|
|
||||||
|
User user = loadUser(usernameOrId, caType);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new SecurityException("User not found.");
|
||||||
|
|
||||||
|
if (user.isRevoked())
|
||||||
|
throw new SecurityException("Account is revoked.");
|
||||||
|
|
||||||
|
if (!certBelongsToUser(submittedCert, user)) {
|
||||||
|
handleFailedAttempt(user, caType);
|
||||||
|
throw new SecurityException("Certificate does not belong to this user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!BCrypt.checkpw(rawPassword, user.getHashedPassword())) {
|
||||||
|
handleFailedAttempt(user, caType);
|
||||||
|
throw new SecurityException("Invalid password. Attempts remaining: "
|
||||||
|
+ (3 - user.getFailedLoginAttempts()));
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setFailedLoginAttempts(0);
|
||||||
|
UserRepository.update(user);
|
||||||
|
|
||||||
|
currentUser = user;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
public static void logout() {
|
||||||
|
System.out.println("Goodbye, " + getDisplayName(currentUser));
|
||||||
|
currentUser = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static User getCurrentUser() { return currentUser; }
|
||||||
|
|
||||||
|
public static boolean isLoggedIn() { return currentUser != null; }
|
||||||
|
|
||||||
|
private static boolean certBelongsToUser(X509Certificate cert, User user) throws Exception {
|
||||||
|
|
||||||
|
X500Name subject = new JcaX509CertificateHolder(cert).getSubject();
|
||||||
|
String cn = subject.getRDNs(BCStyle.CN)[0]
|
||||||
|
.getFirst().getValue().toString();
|
||||||
|
|
||||||
|
if (user instanceof Organizer organizer) {
|
||||||
|
|
||||||
|
String expectedCN = organizer.getOrganizationName()
|
||||||
|
+ "-" + organizer.getId();
|
||||||
|
return cn.equals(expectedCN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user instanceof Voter voter) {
|
||||||
|
|
||||||
|
return cn.equals(voter.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleFailedAttempt(User user, CAType caType) throws Exception {
|
||||||
|
|
||||||
|
user.setFailedLoginAttempts(user.getFailedLoginAttempts() + 1);
|
||||||
|
|
||||||
|
if (user.getFailedLoginAttempts() >= 3) {
|
||||||
|
|
||||||
|
user.setRevoked(true);
|
||||||
|
CRLService.revokeCertificate(user.getCertificate(), caType);
|
||||||
|
UserRepository.update(user);
|
||||||
|
|
||||||
|
throw new SecurityException(
|
||||||
|
"Too many failed attempts. Account and certificate have been revoked.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UserRepository.update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static User loadUser(String usernameOrId, CAType caType) throws Exception {
|
||||||
|
return switch (caType) {
|
||||||
|
case ORGANIZER -> UserRepository.findOrganizer(usernameOrId);
|
||||||
|
case VOTER -> UserRepository.findVoter(usernameOrId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDisplayName(User user) {
|
||||||
|
if (user instanceof Organizer o) return o.getOrganizationName();
|
||||||
|
if (user instanceof Voter v) return v.getUsername();
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@ public abstract class User implements Serializable {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private String hashedPassword;
|
private String hashedPassword;
|
||||||
private int failedLoginAttempts;
|
private int failedLoginAttempts = 0;
|
||||||
private boolean revoked;
|
private boolean revoked;
|
||||||
|
|
||||||
private X509Certificate certificate;
|
private X509Certificate certificate;
|
||||||
@ -36,4 +36,23 @@ public abstract class User implements Serializable {
|
|||||||
this.hashedPassword = hashedPassword;
|
this.hashedPassword = hashedPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRevoked() {
|
||||||
|
return revoked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHashedPassword() {
|
||||||
|
return hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFailedLoginAttempts() {
|
||||||
|
return failedLoginAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailedLoginAttempts(int i) {
|
||||||
|
this.failedLoginAttempts = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRevoked(boolean b) {
|
||||||
|
this.revoked = b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user