From 3cad43006a2a6b889e678986a80a702f28c502d5 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 20 May 2023 22:48:59 +0100 Subject: [PATCH] compress finalise, cert test, locale test --- build.gradle | 5 +- .../stirling/software/SPDF/config/Beans.java | 13 ++ .../api/other/CompressController.java | 34 +++-- .../api/security/CertSignController.java | 144 ++++++++++++++++++ .../software/SPDF/utils/PdfUtils.java | 7 +- src/main/resources/messages_en_GB.properties | 7 +- .../templates/other/compress-pdf.html | 13 +- 7 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java diff --git a/build.gradle b/build.gradle index 3d8100e9..fabbcfeb 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,9 @@ dependencies { //general PDF implementation 'org.apache.pdfbox:pdfbox:2.0.28' - - + implementation 'org.bouncycastle:bcprov-jdk15on:1.70' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' + implementation 'com.itextpdf:itext7-core:7.2.5' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-core' diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index a65879d3..982aedd2 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -2,8 +2,10 @@ package stirling.software.SPDF.config; import java.util.Locale; +import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -50,4 +52,15 @@ public class Beans implements WebMvcConfigurer { return slr; } + @Bean + public MessageSource messageSource() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + //messageSource.setBasename("classpath:messages"); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setUseCodeAsDefaultMessage(true); + messageSource.setDefaultLocale(Locale.UK); // setting default locale + return messageSource; + } + + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index 2671e1b5..0c0ce77f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -56,8 +56,10 @@ public class CompressController { } Long expectedOutputSize = 0L; - if (expectedOutputSizeString != null) { + boolean autoMode = false; + if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) { expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString); + autoMode = true; } // Save the uploaded file to a temporary location @@ -69,8 +71,8 @@ public class CompressController { // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Determine initial optimization level based on expected size reduction, only if optimizeLevel is not provided - if(optimizeLevel == null) { + // Determine initial optimization level based on expected size reduction, only if in autoMode + if(autoMode) { double sizeReductionRatio = expectedOutputSize / (double) inputFileSize; if (sizeReductionRatio > 0.7) { optimizeLevel = 1; @@ -79,12 +81,12 @@ public class CompressController { } else if (sizeReductionRatio > 0.35) { optimizeLevel = 3; } else { - optimizeLevel = 4; + optimizeLevel = 3; } } - boolean sizeMet = expectedOutputSize == 0L; - while (!sizeMet && optimizeLevel <= 5) { + boolean sizeMet = false; + while (!sizeMet && optimizeLevel <= 4) { // Prepare the Ghostscript command List command = new ArrayList<>(); command.add("gs"); @@ -99,12 +101,9 @@ public class CompressController { command.add("-dPDFSETTINGS=/printer"); break; case 3: - command.add("-dPDFSETTINGS=/default"); - break; - case 4: command.add("-dPDFSETTINGS=/ebook"); break; - case 5: + case 4: command.add("-dPDFSETTINGS=/screen"); break; default: @@ -119,20 +118,27 @@ public class CompressController { int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); - // Check if file size is within expected size + // Check if file size is within expected size or not auto mode so instantly finish long outputFileSize = Files.size(tempOutputFile); - if (outputFileSize <= expectedOutputSize) { + if (outputFileSize <= expectedOutputSize || !autoMode) { sizeMet = true; } else { // Increase optimization level for next iteration optimizeLevel++; - System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); + if(autoMode && optimizeLevel > 3) { + System.out.println("Skipping level 4 due to bad results in auto mode"); + sizeMet = true; + } else if(optimizeLevel == 5) { + + } else { + System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); + } } } - if (expectedOutputSize != null) { + if (expectedOutputSize != null && autoMode) { long outputFileSize = Files.size(tempOutputFile); if (outputFileSize > expectedOutputSize) { try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java new file mode 100644 index 00000000..321e6444 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -0,0 +1,144 @@ +package stirling.software.SPDF.controller.api.security; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Security; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.itextpdf.kernel.pdf.*; +import com.itextpdf.signatures.*; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + + +@RestController +public class CertSignController { + + private static final Logger logger = LoggerFactory.getLogger(CertSignController.class); + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") + public ResponseEntity signPDF( + @RequestParam("pdf") MultipartFile pdf, + @RequestParam(value = "key", required = false) MultipartFile privateKeyFile, + @RequestParam(value = "cert", required = false) MultipartFile certFile, + @RequestParam(value = "p12", required = false) MultipartFile p12File, + @RequestParam(value = "password", required = false) String password) throws Exception { + BouncyCastleProvider provider = new BouncyCastleProvider(); + Security.addProvider(provider); + + PrivateKey privateKey = null; + X509Certificate cert = null; + + if (p12File != null) { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); + String alias = ks.aliases().nextElement(); + privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); + cert = (X509Certificate) ks.getCertificate(alias); + } else { + // Load private key + KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider); + if (isPEM(privateKeyFile.getBytes())) { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); + } else { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); + } + + // Load certificate + CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider); + if (isPEM(certFile.getBytes())) { + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); + } else { + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes())); + } + } + + // Set up the PDF reader and stamper + PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes())); + ByteArrayOutputStream signedPdf = new ByteArrayOutputStream(); + PdfSigner signer = new PdfSigner(reader, signedPdf, new StampingProperties()); + + // Set up the signing appearance + PdfSignatureAppearance appearance = signer.getSignatureAppearance() + .setReason("Test") + .setLocation("TestLocation"); + + // Set up the signer + PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); + IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); + IExternalDigest digest = new BouncyCastleDigest(); + + // Call iTex7 to sign the PDF + signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS); + + // This is just an example, you might want to save this signed PDF into your system or send it back in the response. + // For simplicity, we will just print out the size of the signed PDF. + System.out.println("Signed PDF size: " + signedPdf.size()); + + return ResponseEntity.ok("Signed PDF successfully"); + } + + private byte[] parsePEM(byte[] content) throws IOException { + PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); + return pemReader.readPemObject().getContent(); + } + + private boolean isPEM(byte[] content) { + String contentStr = new String(content); + return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); + } + + + + + +} diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index d3427f79..2787363d 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -297,11 +297,11 @@ public class PdfUtils { sizeStr = sizeStr.trim().toUpperCase(); try { if (sizeStr.endsWith("KB")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024; + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); } else if (sizeStr.endsWith("MB")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024; + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024); } else if (sizeStr.endsWith("GB")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024; + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024); } else if (sizeStr.endsWith("B")) { return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); } else { @@ -313,5 +313,6 @@ public class PdfUtils { return null; } + } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 2604350f..5f29d6c5 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -223,8 +223,11 @@ fileToPDF.submit=Convert to PDF compress.title=Compress compress.header=Compress PDF compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation. -compress.selectText.1=Optimization level: -compress.selectText.2=Expected PDF Size (e.g. 100MB, 25KB, 500B) +compress.selectText.1=Manual Mode - From 1 to 4 +compress.selectText.2=Optimization level: +compress.selectText.3=4 (Terrible for text images) +compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size +compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB) compress.submit=Compress diff --git a/src/main/resources/templates/other/compress-pdf.html b/src/main/resources/templates/other/compress-pdf.html index bd3931ae..50c2b5cc 100644 --- a/src/main/resources/templates/other/compress-pdf.html +++ b/src/main/resources/templates/other/compress-pdf.html @@ -11,7 +11,7 @@


-
R +

@@ -19,22 +19,21 @@
-

Manual Mode - From 1 to 5

- +

+
-

Auto mode - Auto adjusts quality to get PDF to exact size

- +

+