init docker

This commit is contained in:
Anthony Stirling
2024-12-09 18:18:16 +00:00
parent a772b4fa09
commit d59cb18666
18 changed files with 507 additions and 318 deletions

View File

@@ -28,7 +28,7 @@ public class LicenseKeyChecker {
this.checkLicense();
}
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() {
checkLicense();
}

View File

@@ -595,7 +595,9 @@ public class GetInfoOnPDF {
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility()));
permissionsNode.put(
"Extracting for accessibility",
getPermissionState(ap.canExtractForAccessibility()));
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));

View File

@@ -3,11 +3,12 @@ package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -41,7 +42,6 @@ import stirling.software.SPDF.service.CustomPDDocumentFactory;
@RequestMapping("/api/v1/security")
@Tag(name = "Security", description = "Security APIs")
public class ValidateSignatureController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CertificateValidationService certValidationService;
@@ -56,7 +56,7 @@ public class ValidateSignatureController {
@Operation(
summary = "Validate PDF Digital Signature",
description =
"Validates the digital signatures in a PDF file against default or custom certificates. Input:PDF Output:JSON Type:SISO")
"Validates digital signatures in a PDF file against default or custom certificates.")
@PostMapping(value = "/validate-signature")
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
@ModelAttribute SignatureValidationRequest request) throws IOException {
@@ -80,7 +80,6 @@ public class ValidateSignatureController {
for (PDSignature sig : signatures) {
SignatureValidationResult result = new SignatureValidationResult();
try {
byte[] signedContent = sig.getSignedContent(file.getInputStream());
byte[] signatureBytes = sig.getContents(file.getInputStream());
@@ -92,77 +91,90 @@ public class ValidateSignatureController {
SignerInformationStore signerStore = signedData.getSignerInfos();
for (SignerInformation signer : signerStore.getSigners()) {
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
X509CertificateHolder certHolder =
(X509CertificateHolder)
certStore.getMatches(signer.getSID()).iterator().next();
X509Certificate cert =
new JcaX509CertificateConverter().getCertificate(certHolder);
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
result.setValid(isValid);
// Basic signature validation
result.setValid(
signer.verify(
new JcaSimpleSignerInfoVerifierBuilder().build(cert)));
// Additional validations
result.setChainValid(customCert != null
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
: certValidationService.validateCertificateChain(cert));
result.setTrustValid(customCert != null
? certValidationService.validateTrustWithCustomCert(cert, customCert)
: certValidationService.validateTrustStore(cert));
result.setNotRevoked(!certValidationService.isRevoked(cert));
result.setNotExpired(!cert.getNotAfter().before(new Date()));
// Set basic signature info
result.setSignerName(sig.getName());
result.setSignatureDate(sig.getSignDate().getTime().toString());
result.setReason(sig.getReason());
result.setLocation(sig.getLocation());
// Set new certificate details
result.setIssuerDN(cert.getIssuerX500Principal().getName());
result.setSubjectDN(cert.getSubjectX500Principal().getName());
result.setSerialNumber(cert.getSerialNumber().toString(16)); // Hex format
result.setValidFrom(cert.getNotBefore().toString());
result.setValidUntil(cert.getNotAfter().toString());
result.setSignatureAlgorithm(cert.getSigAlgName());
// Get key size (if possible)
try {
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
} catch (Exception e) {
// If not RSA or error, set to 0
result.setKeySize(0);
// Perform chain validation
CertificateValidationService.ValidationResult chainResult;
if (customCert != null) {
chainResult =
certValidationService.validateWithCustomCert(cert, customCert);
} else {
chainResult = certValidationService.validateCertificateChain(cert);
}
result.setVersion(String.valueOf(cert.getVersion()));
// Set key usage
List<String> keyUsages = new ArrayList<>();
boolean[] keyUsageFlags = cert.getKeyUsage();
if (keyUsageFlags != null) {
String[] keyUsageLabels = {
"Digital Signature", "Non-Repudiation", "Key Encipherment",
"Data Encipherment", "Key Agreement", "Certificate Signing",
"CRL Signing", "Encipher Only", "Decipher Only"
};
for (int i = 0; i < keyUsageFlags.length; i++) {
if (keyUsageFlags[i]) {
keyUsages.add(keyUsageLabels[i]);
}
result.setChainValid(chainResult.isValid());
result.setTrustValid(chainResult.isValid());
result.setNotExpired(!chainResult.isExpired());
// Check if signature was valid at the time of signing
if (sig.getSignDate() != null) {
try {
cert.checkValidity(sig.getSignDate().getTime());
result.setValidAtTimeOfSigning(true);
} catch (CertificateExpiredException
| CertificateNotYetValidException e) {
result.setValidAtTimeOfSigning(false);
}
}
result.setKeyUsages(keyUsages);
// Check if self-signed
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
// Set signature info
populateSignatureInfo(result, sig, cert);
}
} catch (Exception e) {
result.setValid(false);
result.setErrorMessage("Signature validation failed: " + e.getMessage());
}
results.add(result);
}
}
return ResponseEntity.ok(results);
}
private void populateSignatureInfo(
SignatureValidationResult result, PDSignature sig, X509Certificate cert) {
result.setSignerName(sig.getName());
result.setSignatureDate(sig.getSignDate().getTime().toString());
result.setReason(sig.getReason());
result.setLocation(sig.getLocation());
result.setIssuerDN(cert.getIssuerX500Principal().getName());
result.setSubjectDN(cert.getSubjectX500Principal().getName());
result.setSerialNumber(cert.getSerialNumber().toString(16));
result.setValidFrom(cert.getNotBefore().toString());
result.setValidUntil(cert.getNotAfter().toString());
result.setSignatureAlgorithm(cert.getSigAlgName());
try {
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
} catch (Exception e) {
result.setKeySize(0);
}
result.setVersion(String.valueOf(cert.getVersion()));
List<String> keyUsages = new ArrayList<>();
boolean[] keyUsageFlags = cert.getKeyUsage();
if (keyUsageFlags != null) {
String[] keyUsageLabels = {
"Digital Signature", "Non-Repudiation", "Key Encipherment",
"Data Encipherment", "Key Agreement", "Certificate Signing",
"CRL Signing", "Encipher Only", "Decipher Only"
};
for (int i = 0; i < keyUsageFlags.length; i++) {
if (keyUsageFlags[i]) {
keyUsages.add(keyUsageLabels[i]);
}
}
}
result.setKeyUsages(keyUsages);
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
}
}

View File

@@ -16,16 +16,16 @@ public class SignatureValidationResult {
private boolean trustValid;
private boolean notExpired;
private boolean notRevoked;
private String issuerDN; // Certificate issuer's Distinguished Name
private String subjectDN; // Certificate subject's Distinguished Name
private String serialNumber; // Certificate serial number
private String validFrom; // Certificate validity start date
private String validUntil; // Certificate validity end date
private String signatureAlgorithm;// Algorithm used for signing
private int keySize; // Key size in bits
private String version; // Certificate version
private List<String> keyUsages; // List of key usage purposes
private boolean isSelfSigned; // Whether the certificate is self-signed
private boolean validAtTimeOfSigning;
private String issuerDN; // Certificate issuer's Distinguished Name
private String subjectDN; // Certificate subject's Distinguished Name
private String serialNumber; // Certificate serial number
private String validFrom; // Certificate validity start date
private String validUntil; // Certificate validity end date
private String signatureAlgorithm; // Algorithm used for signing
private int keySize; // Key size in bits
private String version; // Certificate version
private List<String> keyUsages; // List of key usage purposes
private boolean isSelfSigned; // Whether the certificate is self-signed
}

View File

@@ -1,13 +1,11 @@
package stirling.software.SPDF.service;
import io.github.pixee.security.BoundedLineReader;
import java.io.BufferedReader;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.*;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateExpiredException;
@@ -16,84 +14,126 @@ import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class CertificateValidationService {
private KeyStore trustStore;
private static final String AATL_RESOURCE = "/tl12.acrobatsecuritysettings";
@PostConstruct
private void initializeTrustStore() throws Exception {
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
loadMozillaCertificates();
loadAATLCertificatesFromPDF();
}
private void loadMozillaCertificates() throws Exception {
try (InputStream is = getClass().getResourceAsStream("/certdata.txt")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
StringBuilder certData = new StringBuilder();
boolean inCert = false;
int certCount = 0;
private void loadAATLCertificatesFromPDF() throws Exception {
log.debug("Starting AATL certificate loading from PDF...");
while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {
if (line.startsWith("CKA_VALUE MULTILINE_OCTAL")) {
inCert = true;
certData = new StringBuilder();
continue;
}
if (inCert) {
if ("END".equals(line)) {
inCert = false;
byte[] certBytes = parseOctalData(certData.toString());
if (certBytes != null) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert =
(X509Certificate)
cf.generateCertificate(
new ByteArrayInputStream(certBytes));
trustStore.setCertificateEntry("mozilla-cert-" + certCount++, cert);
}
} else {
certData.append(line).append("\n");
}
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;
}
}
}
}
private byte[] parseOctalData(String data) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String[] tokens = data.split("\\\\");
for (String token : tokens) {
token = token.trim();
if (!token.isEmpty()) {
baos.write(Integer.parseInt(token, 8));
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++;
}
} catch (Exception e) {
failedCerts++;
log.error("Failed to process AATL certificate: " + e.getMessage());
}
return baos.toByteArray();
} catch (Exception e) {
return null;
}
log.debug("AATL Certificate loading completed:");
log.debug(" Total root certificates successfully loaded: " + certCount);
log.debug(" Failed certificates: " + failedCerts);
}
public boolean validateCertificateChain(X509Certificate cert) {
@Data
public static class ValidationResult {
private boolean valid;
private boolean expired;
private boolean validAtSigningTime;
private String errorMessage;
}
public ValidationResult validateCertificateChain(X509Certificate signerCert) {
ValidationResult result = new ValidationResult();
try {
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
// Build the certificate chain
List<X509Certificate> certChain = buildCertificateChain(signerCert);
// Create certificate path
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<X509Certificate> certList = Arrays.asList(cert);
CertPath certPath = cf.generateCertPath(certList);
CertPath certPath = cf.generateCertPath(certChain);
// Set up trust anchors
Set<TrustAnchor> anchors = new HashSet<>();
Enumeration<String> aliases = trustStore.aliases();
while (aliases.hasMoreElements()) {
@@ -103,31 +143,113 @@ public class CertificateValidationService {
}
}
// Set up validation parameters
PKIXParameters params = new PKIXParameters(anchors);
params.setRevocationEnabled(false);
// Validate the path
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
validator.validate(certPath, params);
return true;
result.setValid(true);
result.setExpired(isExpired(signerCert));
return result;
} catch (Exception e) {
return false;
result.setValid(false);
result.setErrorMessage(e.getMessage());
return result;
}
}
public boolean validateTrustStore(X509Certificate cert) {
public ValidationResult validateWithCustomCert(
X509Certificate signerCert, X509Certificate customCert) {
ValidationResult result = new ValidationResult();
try {
// 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;
}
}
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;
}
}
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;
}
return chain;
}
private boolean isSelfSigned(X509Certificate cert) {
return cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
}
private X509Certificate findIssuer(X509Certificate cert) throws CertificateException {
try {
Enumeration<String> aliases = trustStore.aliases();
while (aliases.hasMoreElements()) {
Object trustCert = trustStore.getCertificate(aliases.nextElement());
if (trustCert instanceof X509Certificate && cert.equals(trustCert)) {
return true;
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
}
}
}
}
return false;
} catch (KeyStoreException e) {
return false;
throw new CertificateException("Error accessing trust store", e);
}
return null;
}
public boolean isRevoked(X509Certificate cert) {
private boolean isExpired(X509Certificate cert) {
try {
cert.checkValidity();
return false;
@@ -135,23 +257,4 @@ public class CertificateValidationService {
return true;
}
}
public boolean validateCertificateChainWithCustomCert(
X509Certificate cert, X509Certificate customCert) {
try {
cert.verify(customCert.getPublicKey());
return true;
} catch (Exception e) {
return false;
}
}
public boolean validateTrustWithCustomCert(X509Certificate cert, X509Certificate customCert) {
try {
// Compare the issuer of the signature certificate with the custom certificate
return cert.getIssuerX500Principal().equals(customCert.getSubjectX500Principal());
} catch (Exception e) {
return false;
}
}
}

Binary file not shown.