2024-12-05 15:56:22 +00:00
|
|
|
package stirling.software.SPDF.service;
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
import java.io.*;
|
2024-12-05 15:56:22 +00:00
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.security.KeyStore;
|
|
|
|
|
import java.security.KeyStoreException;
|
2024-12-09 18:18:16 +00:00
|
|
|
import java.security.cert.*;
|
2024-12-05 15:56:22 +00:00
|
|
|
import java.security.cert.CertPath;
|
|
|
|
|
import java.security.cert.CertPathValidator;
|
|
|
|
|
import java.security.cert.CertificateExpiredException;
|
|
|
|
|
import java.security.cert.CertificateFactory;
|
|
|
|
|
import java.security.cert.CertificateNotYetValidException;
|
|
|
|
|
import java.security.cert.PKIXParameters;
|
|
|
|
|
import java.security.cert.TrustAnchor;
|
|
|
|
|
import java.security.cert.X509Certificate;
|
2024-12-09 18:18:16 +00:00
|
|
|
import java.util.*;
|
2024-12-05 15:56:22 +00:00
|
|
|
import java.util.Enumeration;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
2024-12-09 18:18:16 +00:00
|
|
|
import java.util.Map;
|
2024-12-05 15:56:22 +00:00
|
|
|
import java.util.Set;
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
import org.apache.pdfbox.Loader;
|
|
|
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
|
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
|
|
|
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
|
|
|
|
import org.springframework.core.io.ClassPathResource;
|
2024-12-05 15:56:22 +00:00
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
|
|
import jakarta.annotation.PostConstruct;
|
2024-12-09 18:18:16 +00:00
|
|
|
import lombok.Data;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
2024-12-05 15:56:22 +00:00
|
|
|
|
|
|
|
|
@Service
|
2024-12-09 18:18:16 +00:00
|
|
|
@Slf4j
|
2024-12-05 15:56:22 +00:00
|
|
|
public class CertificateValidationService {
|
|
|
|
|
private KeyStore trustStore;
|
2024-12-09 18:18:16 +00:00
|
|
|
private static final String AATL_RESOURCE = "/tl12.acrobatsecuritysettings";
|
2024-12-05 15:56:22 +00:00
|
|
|
|
|
|
|
|
@PostConstruct
|
|
|
|
|
private void initializeTrustStore() throws Exception {
|
|
|
|
|
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
|
|
|
trustStore.load(null, null);
|
2024-12-09 18:18:16 +00:00
|
|
|
loadAATLCertificatesFromPDF();
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
private void loadAATLCertificatesFromPDF() throws Exception {
|
|
|
|
|
log.debug("Starting AATL certificate loading from PDF...");
|
|
|
|
|
|
|
|
|
|
try (InputStream pdfStream = new ClassPathResource(AATL_RESOURCE).getInputStream()) {
|
|
|
|
|
PDDocument document = Loader.loadPDF(pdfStream.readAllBytes());
|
|
|
|
|
|
|
|
|
|
PDEmbeddedFilesNameTreeNode embeddedFiles =
|
|
|
|
|
document.getDocumentCatalog().getNames().getEmbeddedFiles();
|
|
|
|
|
Map<String, PDComplexFileSpecification> files = embeddedFiles.getNames();
|
|
|
|
|
|
|
|
|
|
for (Map.Entry<String, PDComplexFileSpecification> entry : files.entrySet()) {
|
|
|
|
|
log.debug(entry.getKey());
|
|
|
|
|
if (entry.getKey().equals("SecuritySettings.xml")) {
|
|
|
|
|
byte[] xmlContent = entry.getValue().getEmbeddedFile().toByteArray();
|
|
|
|
|
processSecuritySettingsXML(xmlContent);
|
|
|
|
|
break;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
private void processSecuritySettingsXML(byte[] xmlContent) throws Exception {
|
|
|
|
|
// Simple XML parsing using String operations
|
|
|
|
|
String xmlString = new String(xmlContent, "UTF-8");
|
|
|
|
|
int certCount = 0;
|
|
|
|
|
int failedCerts = 0;
|
|
|
|
|
|
|
|
|
|
// Find all Certificate tags
|
|
|
|
|
String startTag = "<Certificate>";
|
|
|
|
|
String endTag = "</Certificate>";
|
|
|
|
|
int startIndex = 0;
|
|
|
|
|
|
|
|
|
|
while ((startIndex = xmlString.indexOf(startTag, startIndex)) != -1) {
|
|
|
|
|
int endIndex = xmlString.indexOf(endTag, startIndex);
|
|
|
|
|
if (endIndex == -1) break;
|
|
|
|
|
|
|
|
|
|
// Extract certificate data
|
|
|
|
|
String certData = xmlString.substring(startIndex + startTag.length(), endIndex).trim();
|
|
|
|
|
startIndex = endIndex + endTag.length();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
byte[] certBytes = Base64.getDecoder().decode(certData);
|
|
|
|
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
|
|
|
X509Certificate cert =
|
|
|
|
|
(X509Certificate)
|
|
|
|
|
cf.generateCertificate(new ByteArrayInputStream(certBytes));
|
|
|
|
|
|
|
|
|
|
// Only store root certificates (self-signed)
|
|
|
|
|
if (cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())) {
|
|
|
|
|
trustStore.setCertificateEntry("aatl-cert-" + certCount, cert);
|
|
|
|
|
log.trace(
|
|
|
|
|
"Successfully loaded AATL root certificate #"
|
|
|
|
|
+ certCount
|
|
|
|
|
+ "\n Subject: "
|
|
|
|
|
+ cert.getSubjectX500Principal().getName()
|
|
|
|
|
+ "\n Valid until: "
|
|
|
|
|
+ cert.getNotAfter());
|
|
|
|
|
certCount++;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
2024-12-09 18:18:16 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
|
failedCerts++;
|
|
|
|
|
log.error("Failed to process AATL certificate: " + e.getMessage());
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-12-09 18:18:16 +00:00
|
|
|
|
|
|
|
|
log.debug("AATL Certificate loading completed:");
|
|
|
|
|
log.debug(" Total root certificates successfully loaded: " + certCount);
|
|
|
|
|
log.debug(" Failed certificates: " + failedCerts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Data
|
|
|
|
|
public static class ValidationResult {
|
|
|
|
|
private boolean valid;
|
|
|
|
|
private boolean expired;
|
|
|
|
|
private boolean validAtSigningTime;
|
|
|
|
|
private String errorMessage;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
public ValidationResult validateCertificateChain(X509Certificate signerCert) {
|
|
|
|
|
ValidationResult result = new ValidationResult();
|
2024-12-05 15:56:22 +00:00
|
|
|
try {
|
2024-12-09 18:18:16 +00:00
|
|
|
// Build the certificate chain
|
|
|
|
|
List<X509Certificate> certChain = buildCertificateChain(signerCert);
|
|
|
|
|
|
|
|
|
|
// Create certificate path
|
2024-12-05 15:56:22 +00:00
|
|
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
2024-12-09 18:18:16 +00:00
|
|
|
CertPath certPath = cf.generateCertPath(certChain);
|
2024-12-05 15:56:22 +00:00
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
// Set up trust anchors
|
2024-12-05 15:56:22 +00:00
|
|
|
Set<TrustAnchor> anchors = new HashSet<>();
|
|
|
|
|
Enumeration<String> aliases = trustStore.aliases();
|
|
|
|
|
while (aliases.hasMoreElements()) {
|
|
|
|
|
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
|
|
|
|
if (trustCert instanceof X509Certificate) {
|
|
|
|
|
anchors.add(new TrustAnchor((X509Certificate) trustCert, null));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
// Set up validation parameters
|
2024-12-05 15:56:22 +00:00
|
|
|
PKIXParameters params = new PKIXParameters(anchors);
|
|
|
|
|
params.setRevocationEnabled(false);
|
2024-12-09 18:18:16 +00:00
|
|
|
|
|
|
|
|
// Validate the path
|
|
|
|
|
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
2024-12-05 15:56:22 +00:00
|
|
|
validator.validate(certPath, params);
|
2024-12-09 18:18:16 +00:00
|
|
|
|
|
|
|
|
result.setValid(true);
|
|
|
|
|
result.setExpired(isExpired(signerCert));
|
|
|
|
|
|
|
|
|
|
return result;
|
2024-12-05 15:56:22 +00:00
|
|
|
} catch (Exception e) {
|
2024-12-09 18:18:16 +00:00
|
|
|
result.setValid(false);
|
|
|
|
|
result.setErrorMessage(e.getMessage());
|
|
|
|
|
return result;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
public ValidationResult validateWithCustomCert(
|
|
|
|
|
X509Certificate signerCert, X509Certificate customCert) {
|
|
|
|
|
ValidationResult result = new ValidationResult();
|
2024-12-05 15:56:22 +00:00
|
|
|
try {
|
2024-12-09 18:18:16 +00:00
|
|
|
// Build the complete chain from signer cert
|
|
|
|
|
List<X509Certificate> certChain = buildCertificateChain(signerCert);
|
|
|
|
|
|
|
|
|
|
// Check if custom cert matches any cert in the chain
|
|
|
|
|
boolean matchFound = false;
|
|
|
|
|
for (X509Certificate chainCert : certChain) {
|
|
|
|
|
if (chainCert.equals(customCert)) {
|
|
|
|
|
matchFound = true;
|
|
|
|
|
break;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-12-09 18:18:16 +00:00
|
|
|
|
|
|
|
|
if (!matchFound) {
|
|
|
|
|
// Check if custom cert is a valid issuer for any cert in the chain
|
|
|
|
|
for (X509Certificate chainCert : certChain) {
|
|
|
|
|
try {
|
|
|
|
|
chainCert.verify(customCert.getPublicKey());
|
|
|
|
|
matchFound = true;
|
|
|
|
|
break;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// Continue checking next cert
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.setValid(matchFound);
|
|
|
|
|
if (!matchFound) {
|
|
|
|
|
result.setErrorMessage(
|
|
|
|
|
"Custom certificate is not part of the chain and is not a valid issuer");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
result.setValid(false);
|
|
|
|
|
result.setErrorMessage(e.getMessage());
|
|
|
|
|
return result;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
private List<X509Certificate> buildCertificateChain(X509Certificate signerCert)
|
|
|
|
|
throws CertificateException {
|
|
|
|
|
List<X509Certificate> chain = new ArrayList<>();
|
|
|
|
|
chain.add(signerCert);
|
|
|
|
|
|
|
|
|
|
X509Certificate current = signerCert;
|
|
|
|
|
while (!isSelfSigned(current)) {
|
|
|
|
|
X509Certificate issuer = findIssuer(current);
|
|
|
|
|
if (issuer == null) break;
|
|
|
|
|
chain.add(issuer);
|
|
|
|
|
current = issuer;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
2024-12-09 18:18:16 +00:00
|
|
|
|
|
|
|
|
return chain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isSelfSigned(X509Certificate cert) {
|
|
|
|
|
return cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
private X509Certificate findIssuer(X509Certificate cert) throws CertificateException {
|
2024-12-05 15:56:22 +00:00
|
|
|
try {
|
2024-12-09 18:18:16 +00:00
|
|
|
Enumeration<String> aliases = trustStore.aliases();
|
|
|
|
|
while (aliases.hasMoreElements()) {
|
|
|
|
|
Certificate trustCert = trustStore.getCertificate(aliases.nextElement());
|
|
|
|
|
if (trustCert instanceof X509Certificate) {
|
|
|
|
|
X509Certificate x509TrustCert = (X509Certificate) trustCert;
|
|
|
|
|
if (cert.getIssuerX500Principal()
|
|
|
|
|
.equals(x509TrustCert.getSubjectX500Principal())) {
|
|
|
|
|
try {
|
|
|
|
|
cert.verify(x509TrustCert.getPublicKey());
|
|
|
|
|
return x509TrustCert;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// Continue searching if verification fails
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (KeyStoreException e) {
|
|
|
|
|
throw new CertificateException("Error accessing trust store", e);
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
2024-12-09 18:18:16 +00:00
|
|
|
return null;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-09 18:18:16 +00:00
|
|
|
private boolean isExpired(X509Certificate cert) {
|
2024-12-05 15:56:22 +00:00
|
|
|
try {
|
2024-12-09 18:18:16 +00:00
|
|
|
cert.checkValidity();
|
2024-12-05 15:56:22 +00:00
|
|
|
return false;
|
2024-12-09 18:18:16 +00:00
|
|
|
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
|
|
|
|
|
return true;
|
2024-12-05 15:56:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|