From 763aeb5fae2a9f261ebc28b7b838654890b52b42 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 21 May 2023 12:32:24 +0100 Subject: [PATCH] further sign stuff --- .../stirling/software/SPDF/config/Beans.java | 18 +-- .../api/security/CertSignController.java | 144 ++++++++++++++---- .../controller/web/SecurityWebController.java | 7 + src/main/resources/messages_en_GB.properties | 11 ++ .../resources/templates/fragments/common.html | 4 +- .../templates/security/cert-sign.html | 134 ++++++++++++++++ 6 files changed, 280 insertions(+), 38 deletions(-) create mode 100644 src/main/resources/templates/security/cert-sign.html diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index 982aedd2..814fa653 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -52,15 +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; - } +// @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/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index 321e6444..c6f9b1c6 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -44,6 +44,9 @@ import org.springframework.web.multipart.MultipartFile; import com.itextpdf.kernel.pdf.*; import com.itextpdf.signatures.*; + +import stirling.software.SPDF.utils.PdfUtils; + import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -55,7 +58,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; - +import com.itextpdf.kernel.geom.Rectangle; @RestController public class CertSignController { @@ -66,39 +69,55 @@ public class CertSignController { } @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") - public ResponseEntity signPDF( + public ResponseEntity signPDF( @RequestParam("pdf") MultipartFile pdf, + @RequestParam(value = "certType", required = false) String certType, @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 { + @RequestParam(value = "password", required = false) String password, + @RequestParam(value = "showSignature", required = false) Boolean showSignature, + @RequestParam(value = "reason", required = false) String reason, + @RequestParam(value = "location", required = false) String location, + @RequestParam(value = "name", required = false) String name, + @RequestParam(value = "pageNumber", required = false) Integer pageNumber) throws Exception { + BouncyCastleProvider provider = new BouncyCastleProvider(); Security.addProvider(provider); PrivateKey privateKey = null; X509Certificate cert = null; + + if (certType != null) { + switch (certType) { + case "PKCS12": + 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); + } + break; + case "PEM": + if (privateKeyFile != null && certFile != null) { + // 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())); + } - 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())); + // 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())); + } + } + break; } } @@ -112,6 +131,42 @@ public class CertSignController { .setReason("Test") .setLocation("TestLocation"); + if (showSignature != null && showSignature) { + // Get the page size + PdfPage page = signer.getDocument().getPage(1); + Rectangle pageSize = page.getPageSize(); + + // Define the size of the signature rectangle + float sigWidth = 200; // adjust this as needed + float sigHeight = 100; // adjust this as needed + + // Define the margins from the page edges + float marginRight = 36; // adjust this as needed + float marginBottom = 36; // adjust this as needed + + // Define the position and dimension of the signature field + Rectangle rect = new Rectangle( + pageSize.getRight() - sigWidth - marginRight, + pageSize.getBottom() + marginBottom, + sigWidth, + sigHeight + ); + + // Creating the appearance + appearance + .setPageRect(rect) + .setPageNumber(pageNumber) + .setReason(reason) + .setLocation(location) + .setLayer2Text(name) // Set the signer name to be displayed in the signature field + .setReuseAppearance(false); + signer.setFieldName("sig"); + + signer.setFieldName("sig"); + } else { + appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); + } + // Set up the signer PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); @@ -120,13 +175,48 @@ public class CertSignController { // 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"); + System.out.println("PDF signed = " + isPdfSigned(signedPdf.toByteArray())); + return PdfUtils.bytesToWebResponse(signedPdf.toByteArray(), "example.pdf"); } +public boolean isPdfSigned(byte[] pdfData) throws IOException { + InputStream pdfStream = new ByteArrayInputStream(pdfData); + PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfStream)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDoc); + List names = signatureUtil.getSignatureNames(); + + boolean isSigned = false; + + for (String name : names) { + PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(name); + if (pkcs7 != null) { + System.out.println("Signature found."); + + // Log certificate details + Certificate[] signChain = pkcs7.getSignCertificateChain(); + for (Certificate cert : signChain) { + if (cert instanceof X509Certificate) { + X509Certificate x509 = (X509Certificate) cert; + System.out.println("Certificate Details:"); + System.out.println("Subject: " + x509.getSubjectDN()); + System.out.println("Issuer: " + x509.getIssuerDN()); + System.out.println("Serial: " + x509.getSerialNumber()); + System.out.println("Not Before: " + x509.getNotBefore()); + System.out.println("Not After: " + x509.getNotAfter()); + } + } + + isSigned = true; + } + } + + pdfDoc.close(); + + return isSigned; +} private byte[] parsePEM(byte[] content) throws IOException { PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); return pemReader.readPemObject().getContent(); diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index 9eb10267..98821c85 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -34,4 +34,11 @@ public class SecurityWebController { model.addAttribute("currentPage", "add-watermark"); return "security/add-watermark"; } + + @GetMapping("/cert-sign") + @Hidden + public String certSignForm(Model model) { + model.addAttribute("currentPage", "cert-sign"); + return "security/cert-sign"; + } } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 5f29d6c5..d3adacf9 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -129,6 +129,17 @@ downloadPdf=Download PDF text=Text font=Font +certSign.title=Certificate Signing +certSign.header=Sign a PDF with your certificate +certSign.selectPDF=Select a PDF File for Signing: +certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der): +certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der): +certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate): +certSign.password=Enter Your Keystore or Private Key Password (If Any): +certSign.submit=Sign PDF + + + removeBlanks.title=Remove Blanks removeBlanks.header=Remove Blank Pages removeBlanks.threshold=Threshold: diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index dfd066e3..7d12a536 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -218,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function () { - + + + +
+ +
+ + + + + +
+ + + \ No newline at end of file