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..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
@@ -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;
@@ -65,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();
@@ -76,23 +93,31 @@ public class CertSignController {
throw new IllegalArgumentException("Cert type must be provided");
}
- InputStream ksInputStream = null;
+ KeyStore ks = null;
switch (certType) {
- case "PKCS12":
- ksInputStream = p12File.getInputStream();
- 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;
+ 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);
}
// 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 +125,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 +148,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);
+ }
+ }
}
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_GB.properties b/src/main/resources/messages_en_GB.properties
index 326829de..fdaf73d3 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
diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties
index d2894897..d91dc280 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 85954d21..20148355 100644
--- a/src/main/resources/templates/security/cert-sign.html
+++ b/src/main/resources/templates/security/cert-sign.html
@@ -1,135 +1,113 @@
-
-
-