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[] TRUSTSTORE_PASSWORD = AppConfig.getTruststorePassword();
|
||||
|
||||
public static void initializeIfNeeded() {
|
||||
public static void initializeIfNeeded() throws Exception {
|
||||
if (Files.exists(ROOT_CA_PATH)) {
|
||||
CRLService.initializeIfNeeded();
|
||||
System.out.println("CA hierarchy already initialized.");
|
||||
return;
|
||||
}
|
||||
@ -20,10 +21,8 @@ public class CAInitializer {
|
||||
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(),
|
||||
@ -35,7 +34,6 @@ public class CAInitializer {
|
||||
rootCA.getKeyPair().getPrivate()
|
||||
);
|
||||
|
||||
// 3. Save CA keystores (private keys protected)
|
||||
KeystoreService.saveCAKeyStore("root-ca",
|
||||
rootCA.getKeyPair().getPrivate(), rootCA.getCertificate(), null, CA_PASSWORD);
|
||||
|
||||
@ -47,7 +45,6 @@ public class CAInitializer {
|
||||
voterCA.getKeyPair().getPrivate(), voterCA.getCertificate(),
|
||||
rootCA.getCertificate(), CA_PASSWORD);
|
||||
|
||||
// 4. Save truststore (public certs only)
|
||||
TruststoreService.createTruststore(
|
||||
rootCA.getCertificate(),
|
||||
organizerCA.getCertificate(),
|
||||
@ -55,6 +52,8 @@ public class CAInitializer {
|
||||
TRUSTSTORE_PASSWORD
|
||||
);
|
||||
|
||||
CRLService.initializeIfNeeded();
|
||||
|
||||
System.out.println("CA hierarchy initialized successfully.");
|
||||
|
||||
} 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 String hashedPassword;
|
||||
private int failedLoginAttempts;
|
||||
private int failedLoginAttempts = 0;
|
||||
private boolean revoked;
|
||||
|
||||
private X509Certificate certificate;
|
||||
@ -36,4 +36,23 @@ public abstract class User implements Serializable {
|
||||
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