From d96a3db60a2ced90141d8fcac398589ce6e95323 Mon Sep 17 00:00:00 2001 From: sbplat <71648843+sbplat@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:21:59 -0500 Subject: [PATCH 1/3] fix: add pem support for cert sign --- .../api/security/CertSignController.java | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) 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 c39eddd2..c4e5fe11 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 @@ -1,21 +1,37 @@ package stirling.software.SPDF.controller.api.security; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.Security; import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.Calendar; import org.apache.pdfbox.examples.signature.CreateSignatureBase; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -76,23 +92,27 @@ public class CertSignController { throw new IllegalArgumentException("Cert type must be provided"); } - InputStream ksInputStream = null; + KeyStore ks = null; switch (certType) { case "PKCS12": - ksInputStream = p12File.getInputStream(); + ks = KeyStore.getInstance("PKCS12"); + ks.load(p12File.getInputStream(), password.toCharArray()); break; case "PEM": - throw new IllegalArgumentException("TODO: PEM not supported yet"); - // ksInputStream = privateKeyFile.getInputStream(); - // break; + ks = KeyStore.getInstance("JKS"); + ks.load(null); + PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password); + Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes()); + ks.setKeyEntry( + "alias", privateKey, password.toCharArray(), new Certificate[] {cert}); + break; default: throw new IllegalArgumentException("Invalid cert type: " + certType); } // TODO: page number - KeyStore ks = getKeyStore(ksInputStream, password); CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); sign(pdf.getBytes(), baos, createSignature, name, location, reason); @@ -100,12 +120,6 @@ public class CertSignController { baos, pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf"); } - private static KeyStore getKeyStore(InputStream is, String password) throws Exception { - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(is, password.toCharArray()); - return ks; - } - private static void sign( byte[] input, OutputStream output, @@ -129,14 +143,35 @@ public class CertSignController { } } - // private byte[] parsePEM(byte[] content) throws IOException { - // PemReader pemReader = - // new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); - // return pemReader.readPemObject().getContent(); - // } + private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) + throws IOException, OperatorCreationException, PKCSException { + try (PEMParser pemParser = + new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { + Object pemObject = pemParser.readObject(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + PrivateKeyInfo pkInfo; + if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) { + InputDecryptorProvider decProv = + new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray()); + pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv); + } else if (pemObject instanceof PEMEncryptedKeyPair) { + PEMDecryptorProvider decProv = + new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); + pkInfo = + ((PEMEncryptedKeyPair) pemObject) + .decryptKeyPair(decProv) + .getPrivateKeyInfo(); + } else { + pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo(); + } + return converter.getPrivateKey(pkInfo); + } + } - // private boolean isPEM(byte[] content) { - // String contentStr = new String(content); - // return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); - // } + private Certificate getCertificateFromPEM(byte[] pemBytes) + throws IOException, CertificateException { + try (ByteArrayInputStream bis = new ByteArrayInputStream(pemBytes)) { + return CertificateFactory.getInstance("X.509").generateCertificate(bis); + } + } } From 97f581ad6dd9e3108c4fbc5ccca09d79522c9cd9 Mon Sep 17 00:00:00 2001 From: sbplat <71648843+sbplat@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:51:30 -0500 Subject: [PATCH 2/3] feat: add java keystore certificate option for pdf signing --- .../api/security/CertSignController.java | 13 +- .../api/security/SignPDFWithCertRequest.java | 5 +- src/main/resources/messages_en_US.properties | 2 + .../templates/security/cert-sign.html | 234 ++++++++---------- 4 files changed, 121 insertions(+), 133 deletions(-) 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 c4e5fe11..1ead1a97 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 @@ -81,6 +81,7 @@ public class CertSignController { MultipartFile privateKeyFile = request.getPrivateKeyFile(); MultipartFile certFile = request.getCertFile(); MultipartFile p12File = request.getP12File(); + MultipartFile jksfile = request.getJksFile(); String password = request.getPassword(); Boolean showSignature = request.isShowSignature(); String reason = request.getReason(); @@ -95,10 +96,6 @@ public class CertSignController { KeyStore ks = null; switch (certType) { - case "PKCS12": - ks = KeyStore.getInstance("PKCS12"); - ks.load(p12File.getInputStream(), password.toCharArray()); - break; case "PEM": ks = KeyStore.getInstance("JKS"); ks.load(null); @@ -107,6 +104,14 @@ public class CertSignController { ks.setKeyEntry( "alias", privateKey, password.toCharArray(), new Certificate[] {cert}); break; + case "PKCS12": + ks = KeyStore.getInstance("PKCS12"); + ks.load(p12File.getInputStream(), password.toCharArray()); + break; + case "JKS": + ks = KeyStore.getInstance("JKS"); + ks.load(jksfile.getInputStream(), password.toCharArray()); + break; default: throw new IllegalArgumentException("Invalid cert type: " + certType); } diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java index a1fc2fce..d3399db9 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java @@ -14,7 +14,7 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema( description = "The type of the digital certificate", - allowableValues = {"PKCS12", "PEM"}) + allowableValues = {"PEM", "PKCS12", "JKS"}) private String certType; @Schema( @@ -28,6 +28,9 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema(description = "The PKCS12 keystore file (required for PKCS12 type certificates)") private MultipartFile p12File; + @Schema(description = "The JKS keystore file (Java Key Store)") + private MultipartFile jksFile; + @Schema(description = "The password for the keystore or the private key") private String password; diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index 9b7efd3a..0c6572da 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -546,9 +546,11 @@ scalePages.submit=Submit certSign.title=Certificate Signing certSign.header=Sign a PDF with your certificate (Work in progress) certSign.selectPDF=Select a PDF File for Signing: +certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below. 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.selectJKS=Select Your Java Keystore File (.jks or .keystore): certSign.certType=Certificate Type certSign.password=Enter Your Keystore or Private Key Password (If Any): certSign.showSig=Show Signature diff --git a/src/main/resources/templates/security/cert-sign.html b/src/main/resources/templates/security/cert-sign.html index fbbf36d1..20148355 100644 --- a/src/main/resources/templates/security/cert-sign.html +++ b/src/main/resources/templates/security/cert-sign.html @@ -1,135 +1,113 @@ - - - + + -
-
-
-

-
-
-
-

- -
-
- -
-
-
- +
+
+
+

+
+
+
+

+ +
+ +
+
+ +
+
+
+ +
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+
+
+
+
+
+ - - -
- -
- -
-
-
-
-
-
+ document + .getElementById('showSignature') + .addEventListener( + 'change', + function() { + var signatureDetails = document.getElementById('signatureDetails'); + if (this.checked) { + signatureDetails.style.display = 'block'; + } else { + signatureDetails.style.display = 'none'; + } + }); + + From eadd513b0263de07f361a4301f77edba36255bb8 Mon Sep 17 00:00:00 2001 From: sbplat <71648843+sbplat@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:54:15 -0500 Subject: [PATCH 3/3] chore(translation): add certsign jks to en-GB --- src/main/resources/messages_en_GB.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 6cba440e..235bf07b 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -546,9 +546,11 @@ scalePages.submit=Submit certSign.title=Certificate Signing certSign.header=Sign a PDF with your certificate (Work in progress) certSign.selectPDF=Select a PDF File for Signing: +certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below. 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.selectJKS=Select Your Java Keystore File (.jks or .keystore): certSign.certType=Certificate Type certSign.password=Enter Your Keystore or Private Key Password (If Any): certSign.showSig=Show Signature