diff --git a/app/core/.gitignore b/app/core/.gitignore
index 5486f9afe..7d9dd6293 100644
--- a/app/core/.gitignore
+++ b/app/core/.gitignore
@@ -170,6 +170,10 @@ out/
*.jks
*.asc
+# test-cert
+!**/test/resources/certs/test-cert.*
+!**/test/resources/certs/test-key.*
+
# SSH Keys
*.pub
*.priv
diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java
index 7675355da..e32f4fac6 100644
--- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java
+++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java
@@ -186,6 +186,7 @@ public class CertSignController {
"alias", privateKey, password.toCharArray(), new Certificate[] {cert});
break;
case "PKCS12":
+ case "PFX":
ks = KeyStore.getInstance("PKCS12");
ks.load(p12File.getInputStream(), password.toCharArray());
break;
diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java
index acb4b55fd..ac87fe61d 100644
--- a/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java
+++ b/app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java
@@ -15,20 +15,25 @@ public class SignPDFWithCertRequest extends PDFFile {
@Schema(
description = "The type of the digital certificate",
- allowableValues = {"PEM", "PKCS12", "JKS"},
+ allowableValues = {"PEM", "PKCS12", "PFX", "JKS"},
requiredMode = Schema.RequiredMode.REQUIRED)
private String certType;
@Schema(
description =
"The private key for the digital certificate (required for PEM type"
- + " certificates)")
+ + " certificates, supports .pem, .der, or .key files)")
private MultipartFile privateKeyFile;
- @Schema(description = "The digital certificate (required for PEM type certificates)")
+ @Schema(
+ description =
+ "The digital certificate (required for PEM type certificates, supports"
+ + " .pem, .der, .crt, or .cer files)")
private MultipartFile certFile;
- @Schema(description = "The PKCS12 keystore file (required for PKCS12 type certificates)")
+ @Schema(
+ description =
+ "The PKCS12/PFX keystore file (required for PKCS12 or PFX type certificates)")
private MultipartFile p12File;
@Schema(description = "The JKS keystore file (Java Key Store)")
diff --git a/app/core/src/main/resources/templates/security/cert-sign.html b/app/core/src/main/resources/templates/security/cert-sign.html
index d83f8c249..ea94b7ea9 100644
--- a/app/core/src/main/resources/templates/security/cert-sign.html
+++ b/app/core/src/main/resources/templates/security/cert-sign.html
@@ -31,17 +31,18 @@
+
@@ -96,6 +97,7 @@
var valueToGroupMap = {
'PEM': pemGroup,
'PKCS12': p12Group,
+ 'PFX': p12Group,
'JKS': jksGroup
};
for (var key in valueToGroupMap) {
diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java
new file mode 100644
index 000000000..4cbec3aa6
--- /dev/null
+++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java
@@ -0,0 +1,312 @@
+package stirling.software.SPDF.controller.api.security;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.web.multipart.MultipartFile;
+
+import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
+import stirling.software.common.service.CustomPDFDocumentFactory;
+
+@ExtendWith(MockitoExtension.class)
+class CertSignControllerTest {
+
+ @Mock private CustomPDFDocumentFactory pdfDocumentFactory;
+
+ @InjectMocks private CertSignController certSignController;
+
+ private byte[] pdfBytes;
+ private byte[] pfxBytes;
+ private byte[] p12Bytes;
+ private byte[] jksBytes;
+ private byte[] pemKeyBytes;
+ private byte[] pemCertBytes;
+ private byte[] keyBytes;
+ private byte[] crtCertBytes;
+ private byte[] cerCertBytes;
+ private byte[] derCertBytes;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ try (PDDocument doc = new PDDocument()) {
+ doc.addPage(new PDPage());
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ doc.save(baos);
+ pdfBytes = baos.toByteArray();
+ }
+ ClassPathResource pfxResource = new ClassPathResource("certs/test-cert.pfx");
+ try (InputStream is = pfxResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ pfxBytes = baos.toByteArray();
+ }
+ ClassPathResource p12Resource = new ClassPathResource("certs/test-cert.p12");
+ try (InputStream is = p12Resource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ p12Bytes = baos.toByteArray();
+ }
+ ClassPathResource jksResource = new ClassPathResource("certs/test-cert.jks");
+ try (InputStream is = jksResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ jksBytes = baos.toByteArray();
+ }
+ ClassPathResource pemKeyResource = new ClassPathResource("certs/test-key.pem");
+ try (InputStream is = pemKeyResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ pemKeyBytes = baos.toByteArray();
+ }
+ ClassPathResource pemCertResource = new ClassPathResource("certs/test-cert.pem");
+ try (InputStream is = pemCertResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ pemCertBytes = baos.toByteArray();
+ }
+ ClassPathResource keyResource = new ClassPathResource("certs/test-key.key");
+ try (InputStream is = keyResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ keyBytes = baos.toByteArray();
+ }
+ ClassPathResource crtResource = new ClassPathResource("certs/test-cert.crt");
+ try (InputStream is = crtResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ crtCertBytes = baos.toByteArray();
+ }
+ ClassPathResource cerResource = new ClassPathResource("certs/test-cert.cer");
+ try (InputStream is = cerResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ cerCertBytes = baos.toByteArray();
+ }
+ ClassPathResource derCertResource = new ClassPathResource("certs/test-cert.der");
+ try (InputStream is = derCertResource.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ is.transferTo(baos);
+ derCertBytes = baos.toByteArray();
+ }
+
+ when(pdfDocumentFactory.load(any(MultipartFile.class)))
+ .thenAnswer(
+ invocation -> {
+ MultipartFile file = invocation.getArgument(0);
+ return Loader.loadPDF(file.getBytes());
+ });
+ }
+
+ @Test
+ void testSignPdfWithPfx() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile pfxFile =
+ new MockMultipartFile("p12File", "test-cert.pfx", "application/x-pkcs12", pfxBytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("PFX");
+ request.setP12File(pfxFile);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+
+ @Test
+ void testSignPdfWithPkcs12() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile p12File =
+ new MockMultipartFile("p12File", "test-cert.p12", "application/x-pkcs12", p12Bytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("PKCS12");
+ request.setP12File(p12File);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+
+ @Test
+ void testSignPdfWithJks() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile jksFile =
+ new MockMultipartFile(
+ "jksFile", "test-cert.jks", "application/octet-stream", jksBytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("JKS");
+ request.setJksFile(jksFile);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+
+ @Test
+ void testSignPdfWithPem() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile keyFile =
+ new MockMultipartFile(
+ "privateKeyFile", "test-key.pem", "application/x-pem-file", pemKeyBytes);
+ MockMultipartFile certFile =
+ new MockMultipartFile(
+ "certFile", "test-cert.pem", "application/x-pem-file", pemCertBytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("PEM");
+ request.setPrivateKeyFile(keyFile);
+ request.setCertFile(certFile);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+
+ @Test
+ void testSignPdfWithCrt() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile keyFile =
+ new MockMultipartFile(
+ "privateKeyFile", "test-key.key", "application/x-pem-file", keyBytes);
+ MockMultipartFile certFile =
+ new MockMultipartFile(
+ "certFile", "test-cert.crt", "application/x-x509-ca-cert", crtCertBytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("PEM");
+ request.setPrivateKeyFile(keyFile);
+ request.setCertFile(certFile);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+
+ @Test
+ void testSignPdfWithCer() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile keyFile =
+ new MockMultipartFile(
+ "privateKeyFile", "test-key.key", "application/x-pem-file", keyBytes);
+ MockMultipartFile certFile =
+ new MockMultipartFile(
+ "certFile", "test-cert.cer", "application/x-x509-ca-cert", cerCertBytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("PEM");
+ request.setPrivateKeyFile(keyFile);
+ request.setCertFile(certFile);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+
+ @Test
+ void testSignPdfWithDer() throws Exception {
+ MockMultipartFile pdfFile =
+ new MockMultipartFile("fileInput", "test.pdf", "application/pdf", pdfBytes);
+ MockMultipartFile keyFile =
+ new MockMultipartFile(
+ "privateKeyFile", "test-key.key", "application/x-pem-file", keyBytes);
+ MockMultipartFile certFile =
+ new MockMultipartFile(
+ "certFile", "test-cert.der", "application/x-x509-ca-cert", derCertBytes);
+
+ SignPDFWithCertRequest request = new SignPDFWithCertRequest();
+ request.setFileInput(pdfFile);
+ request.setCertType("PEM");
+ request.setPrivateKeyFile(keyFile);
+ request.setCertFile(certFile);
+ request.setPassword("password");
+ request.setShowSignature(false);
+ request.setReason("test");
+ request.setLocation("test");
+ request.setName("tester");
+ request.setPageNumber(1);
+ request.setShowLogo(false);
+
+ ResponseEntity response = certSignController.signPDFWithCert(request);
+
+ assertNotNull(response.getBody());
+ assertTrue(response.getBody().length > 0);
+ }
+}
diff --git a/app/core/src/test/resources/certs/test-cert.cer b/app/core/src/test/resources/certs/test-cert.cer
new file mode 100644
index 000000000..729f85c73
--- /dev/null
+++ b/app/core/src/test/resources/certs/test-cert.cer
@@ -0,0 +1,26 @@
+Bag Attributes
+ friendlyName: alias
+ localKeyID: 43 4A B0 2D D5 03 52 9F 5B 78 50 64 54 22 AB F7 C8 0B 1F 2B
+subject=C = US, ST = CA, L = SF, O = Test, OU = Test, CN = Test
+issuer=C = US, ST = CA, L = SF, O = Test, OU = Test, CN = Test
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnGgAwIBAgIUdWDUiSWDll+owMQEzypIuChp+bcwDQYJKoZIhvcNAQEL
+BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG
+A1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDAeFw0yNTA4
+MjYwNzQxMTBaFw0yNjA4MjYwNzQxMTBaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
+DAJDQTELMAkGA1UEBwwCU0YxDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3Qx
+DTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM
+SfspXLx1WAKSo3AfDYIJAyeSrqFcTsPoNBEvT2U1b8w+SCTw4xR5sC3pNenbiEQ7
+4sI60hgURtOMOAt+iKvfI0A/9N8/wYadXUyis4qGZPkM/F6H5cBF9VaYisGptY2w
+ad9X8XcZgZFABYA5O50Jb5nbUM8fPwDYz2fISIejIpW36y+ApFsotJQCaISe4UWb
+K7bwW4UycghYh7AqfH/1OvgR35gGeL7S+SC0F+CZqGECgansFOh/yYL6VoatoggV
+oZxjIQblmuSrLtfwN1S7ngn85k3NFMBHm1ehMOHabx5G58Wg05/0mBK8bIrwjrNp
+Wzomit8BQJ7eIYUikZfVAgMBAAGjUzBRMB0GA1UdDgQWBBRm6hGFGnC1dxipumf/
+6ROdNE6/YDAfBgNVHSMEGDAWgBRm6hGFGnC1dxipumf/6ROdNE6/YDAPBgNVHRMB
+Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB66MPy5kZlSlBgsK4HtB1LSr3M
+dmBWbnQQMq9rmD9AIBQV/shiIjMXGRGnt9zaB0Gg9M39iEvISE6ByMpaDQqV0Md5
+9y4XJu0rg/aMXLaHOGDAWJsb7nCGDt12cWdgn1Ni2mmXUHv4SJCRXNQF7mSgIr+p
+Fvd1ljyvzu/iig8qxrcuWoZvY677p3yen4dN8ocgi8Df3KjduGbsTjFAESYqqNQC
+f+bvypQfhHjxdvz5W3Lpk2swUufqOvhO2b6+cshYJX98qLU8mhai/rOnYkHE7haq
+WDH6XEthnVGtk2VJ4XFDbz+FID440DPzy5u/1OZw2Mcoyp6y7rZDKC/D0Uvh
+-----END CERTIFICATE-----
diff --git a/app/core/src/test/resources/certs/test-cert.crt b/app/core/src/test/resources/certs/test-cert.crt
new file mode 100644
index 000000000..729f85c73
--- /dev/null
+++ b/app/core/src/test/resources/certs/test-cert.crt
@@ -0,0 +1,26 @@
+Bag Attributes
+ friendlyName: alias
+ localKeyID: 43 4A B0 2D D5 03 52 9F 5B 78 50 64 54 22 AB F7 C8 0B 1F 2B
+subject=C = US, ST = CA, L = SF, O = Test, OU = Test, CN = Test
+issuer=C = US, ST = CA, L = SF, O = Test, OU = Test, CN = Test
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnGgAwIBAgIUdWDUiSWDll+owMQEzypIuChp+bcwDQYJKoZIhvcNAQEL
+BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG
+A1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDAeFw0yNTA4
+MjYwNzQxMTBaFw0yNjA4MjYwNzQxMTBaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
+DAJDQTELMAkGA1UEBwwCU0YxDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3Qx
+DTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM
+SfspXLx1WAKSo3AfDYIJAyeSrqFcTsPoNBEvT2U1b8w+SCTw4xR5sC3pNenbiEQ7
+4sI60hgURtOMOAt+iKvfI0A/9N8/wYadXUyis4qGZPkM/F6H5cBF9VaYisGptY2w
+ad9X8XcZgZFABYA5O50Jb5nbUM8fPwDYz2fISIejIpW36y+ApFsotJQCaISe4UWb
+K7bwW4UycghYh7AqfH/1OvgR35gGeL7S+SC0F+CZqGECgansFOh/yYL6VoatoggV
+oZxjIQblmuSrLtfwN1S7ngn85k3NFMBHm1ehMOHabx5G58Wg05/0mBK8bIrwjrNp
+Wzomit8BQJ7eIYUikZfVAgMBAAGjUzBRMB0GA1UdDgQWBBRm6hGFGnC1dxipumf/
+6ROdNE6/YDAfBgNVHSMEGDAWgBRm6hGFGnC1dxipumf/6ROdNE6/YDAPBgNVHRMB
+Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB66MPy5kZlSlBgsK4HtB1LSr3M
+dmBWbnQQMq9rmD9AIBQV/shiIjMXGRGnt9zaB0Gg9M39iEvISE6ByMpaDQqV0Md5
+9y4XJu0rg/aMXLaHOGDAWJsb7nCGDt12cWdgn1Ni2mmXUHv4SJCRXNQF7mSgIr+p
+Fvd1ljyvzu/iig8qxrcuWoZvY677p3yen4dN8ocgi8Df3KjduGbsTjFAESYqqNQC
+f+bvypQfhHjxdvz5W3Lpk2swUufqOvhO2b6+cshYJX98qLU8mhai/rOnYkHE7haq
+WDH6XEthnVGtk2VJ4XFDbz+FID440DPzy5u/1OZw2Mcoyp6y7rZDKC/D0Uvh
+-----END CERTIFICATE-----
diff --git a/app/core/src/test/resources/certs/test-cert.der b/app/core/src/test/resources/certs/test-cert.der
new file mode 100644
index 000000000..b931702b5
Binary files /dev/null and b/app/core/src/test/resources/certs/test-cert.der differ
diff --git a/app/core/src/test/resources/certs/test-cert.jks b/app/core/src/test/resources/certs/test-cert.jks
new file mode 100644
index 000000000..5b6396b64
Binary files /dev/null and b/app/core/src/test/resources/certs/test-cert.jks differ
diff --git a/app/core/src/test/resources/certs/test-cert.p12 b/app/core/src/test/resources/certs/test-cert.p12
new file mode 100644
index 000000000..02f74b04f
Binary files /dev/null and b/app/core/src/test/resources/certs/test-cert.p12 differ
diff --git a/app/core/src/test/resources/certs/test-cert.pem b/app/core/src/test/resources/certs/test-cert.pem
new file mode 100644
index 000000000..729f85c73
--- /dev/null
+++ b/app/core/src/test/resources/certs/test-cert.pem
@@ -0,0 +1,26 @@
+Bag Attributes
+ friendlyName: alias
+ localKeyID: 43 4A B0 2D D5 03 52 9F 5B 78 50 64 54 22 AB F7 C8 0B 1F 2B
+subject=C = US, ST = CA, L = SF, O = Test, OU = Test, CN = Test
+issuer=C = US, ST = CA, L = SF, O = Test, OU = Test, CN = Test
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnGgAwIBAgIUdWDUiSWDll+owMQEzypIuChp+bcwDQYJKoZIhvcNAQEL
+BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsG
+A1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDAeFw0yNTA4
+MjYwNzQxMTBaFw0yNjA4MjYwNzQxMTBaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
+DAJDQTELMAkGA1UEBwwCU0YxDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3Qx
+DTALBgNVBAMMBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM
+SfspXLx1WAKSo3AfDYIJAyeSrqFcTsPoNBEvT2U1b8w+SCTw4xR5sC3pNenbiEQ7
+4sI60hgURtOMOAt+iKvfI0A/9N8/wYadXUyis4qGZPkM/F6H5cBF9VaYisGptY2w
+ad9X8XcZgZFABYA5O50Jb5nbUM8fPwDYz2fISIejIpW36y+ApFsotJQCaISe4UWb
+K7bwW4UycghYh7AqfH/1OvgR35gGeL7S+SC0F+CZqGECgansFOh/yYL6VoatoggV
+oZxjIQblmuSrLtfwN1S7ngn85k3NFMBHm1ehMOHabx5G58Wg05/0mBK8bIrwjrNp
+Wzomit8BQJ7eIYUikZfVAgMBAAGjUzBRMB0GA1UdDgQWBBRm6hGFGnC1dxipumf/
+6ROdNE6/YDAfBgNVHSMEGDAWgBRm6hGFGnC1dxipumf/6ROdNE6/YDAPBgNVHRMB
+Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB66MPy5kZlSlBgsK4HtB1LSr3M
+dmBWbnQQMq9rmD9AIBQV/shiIjMXGRGnt9zaB0Gg9M39iEvISE6ByMpaDQqV0Md5
+9y4XJu0rg/aMXLaHOGDAWJsb7nCGDt12cWdgn1Ni2mmXUHv4SJCRXNQF7mSgIr+p
+Fvd1ljyvzu/iig8qxrcuWoZvY677p3yen4dN8ocgi8Df3KjduGbsTjFAESYqqNQC
+f+bvypQfhHjxdvz5W3Lpk2swUufqOvhO2b6+cshYJX98qLU8mhai/rOnYkHE7haq
+WDH6XEthnVGtk2VJ4XFDbz+FID440DPzy5u/1OZw2Mcoyp6y7rZDKC/D0Uvh
+-----END CERTIFICATE-----
diff --git a/app/core/src/test/resources/certs/test-cert.pfx b/app/core/src/test/resources/certs/test-cert.pfx
new file mode 100644
index 000000000..02f74b04f
Binary files /dev/null and b/app/core/src/test/resources/certs/test-cert.pfx differ
diff --git a/app/core/src/test/resources/certs/test-key.key b/app/core/src/test/resources/certs/test-key.key
new file mode 100644
index 000000000..93b8804c0
--- /dev/null
+++ b/app/core/src/test/resources/certs/test-key.key
@@ -0,0 +1,34 @@
+Bag Attributes
+ friendlyName: alias
+ localKeyID: 43 4A B0 2D D5 03 52 9F 5B 78 50 64 54 22 AB F7 C8 0B 1F 2B
+Key Attributes:
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIB/3nui1td5QCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDY04ug+QgB6t2TdOWPgdtIBIIE
+0IaMRXXtpzLzSjlpyQpLMWLX9Lu+MauINVQMpan8qspC3RGkGcCQUzTkliM3Ls5Q
+Pwv02iFlKAzUYg/Z5V/kONfDkuxjeZvLFjmzomtWNy6yIxp4ShZinH8AGon16J6E
+s1+xlQBBLZYrRXX7WCpnHKE2OKquOoFWpYcb23py6FlD7Uq6XB0LEHR+C35tgnTQ
+WkTFK/La+cbJ+zmWA11Nrnz5XzuWTrNoNB4ygVON78T9o25Hf4V8rWhSZj2N79+B
+QuCAvuqZyAO12aUI9sxZZyis00JOnX7xbAeOkJk8Hhk4iQRMUUudKb5rqLrh/lcm
+F9zZjpu6PxJh22ztnRik3L3LyZLdEhMJJGWk4Z/3tKO87K4EiluzwZhAfMLpqfxx
+qfRKu6By97pbfJFBKqBTzmli2eeJLOwhERlovIaDiublFU8o8RE92PxUPOr7kqL7
+3cx8Qx5AF2Mnu7ftcLIGgg/lN+haoxpACDkC5ZvTFCrGr7jD1DlkswSMoai9gknx
+IMjID9nq6pVWyBm+wt9cALeK2wNa5RsE9fFvF/DBathV/WNmBwjnTKCeX3uPP1nw
+CUE6d+zicrz79kRWRnmscE3phTTu3/O9TokCMe3rLzC0f+gOpIE7vXDSeRuek/xs
+7uahAAWm94cHdz8QIBR/Ub+fFyrz/VHStAGlZhs0SoVnCl+VnZ9D9OqiyqslOihg
+LMcNwH8QjEv4zRAU/Sf1OdVJItXyKfII5zSUCW/TpD/vWPlG80Ib/bc+H9uZDZsg
+OADQYSyWjxA6OUThbCi6Wr+OxFUuDwVaMXxKjz1xH3HjmjpWZeTJy6BAuqe/OLDg
+VxDdEyL8fgz+QaaM/uqFarVMTir2A5VYNJzTXh02rUn3mXXHbH7uZYSwSg7fJ/hU
+ycSUkr/TFe9ZfqKOg1+ZKDu7Q97/tkL7gBTQbPqitUSinGvBgtMZKTHBznEn8foq
+NL/VaFSR4MxTOxFyE2e+9riNJmR0tavZCSgA7LcJtcT9l62cbmwmMj8DvEw8fiSD
+AYpgwovMtDoVDVQGb7ixLMz8/ta1BB7zPpr2aK8x5pVz5c+9rW/NiWQ68LCpEiAc
+HxExUVR0b9thC5YvG4VepUtmZ768yTYyus9jDiDNwRH/qttmAosn4pq5gGK+IVao
+oJX5jcroYaQnvXDBwve2XXXKSkIWe62r8h7Jv6mxR9yBQdVeWNtCGQ5AYNJNxI0i
+ZbCmCcQJnIuMHLYddaIEmUuUBFOquQC9y/pVbMbmdWOMw5Nama+/q6bke/XGk81I
+/Ov2gNN4Eu2V9N9MzlF0GiAmk1784qITj9iDIiYXPESnQfybFyhi2DaUM+KmeHpB
+I2KHL2KA0EGVhBjvCd7FVAqDJL7Dy3nCiLxNiDKChCP9+DDXB2mEfZafltSWai6p
+FPfGZJImQ6NO4/I/2aeXIwr4urJVFt3mr2b6w+gGRjr4qur0ZcqpvvcA3Es+tMX1
+eY5Or9V8iw/wj0x+CrHvvsRBfvCTSN/yqweMr5p1xSZm3Hfz906/q8HSaHb/sNne
+HCjUiKWJ6WTrjDjf9ewYnXb6Qxs3P0zjuHwSrpbq0Pr3HQveQvO5Tfrwr5+ikK1k
+FyqiU4e4vjpLujkIj2dmH0CkJ6ase1j/rWU8nLr1XZSR
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/app/core/src/test/resources/certs/test-key.pem b/app/core/src/test/resources/certs/test-key.pem
new file mode 100644
index 000000000..7653012a1
--- /dev/null
+++ b/app/core/src/test/resources/certs/test-key.pem
@@ -0,0 +1,34 @@
+Bag Attributes
+ friendlyName: alias
+ localKeyID: 43 4A B0 2D D5 03 52 9F 5B 78 50 64 54 22 AB F7 C8 0B 1F 2B
+Key Attributes:
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIXl98lJJ1MUsCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAcT6pXTGm0w+LUzlVH0GpJBIIE
+0NfOk8+haqEuGskrV8+JJVQLgqpKiOmXBjkiSHGReF4UTocKiUAwrHbvLj+j1VLM
+TNM/G68+SzGuWxI7gxpzA9u7p4Is5+2Sji9KsMuAh2CQlEuzkFsVaD9KXF2rje7g
+0G+4+ExZtsjlt/UqG2plFuWzJwji4J82Cy5dir1MQOOAweq5zG5/nzVpMmNoc1lo
+B9PO18R3SpY6qIp8Q0+d1QJC8zsXi/KKQ3ODiS83x5BL4KkQfjYDK/Lfr9yk5a3t
+JN8wE5jkDyGCLGGWgwy7Xq5N7m+kvcdeIEqKP9g5k5uZ7LppsDFe9dpHVymTHZGu
+tGrB74vi4D28YNhuG5qkTjp6CEehSjMwgWEo0Y6ZGu4WQvoTmkne88zly5vUFNrw
+JFM57YqE8U0Gzy7c/zeGtPq8U7y/Pd4z3muZe9sLpFoFAC7Aoq5yw662mPEBZRVb
+MDw8fK1OY9fnj9qHwQbYAD5AT9GmpwEP4tWkB6qNiDJBR8Jn3VmQ1uwR7oH+BiwX
+Y0xWjgl39JcpMORhzJim7K788FEjDrxR1ptepowC4EKjSeq92BGpO+Flf+lY/xYS
+3QR64h/wJEx7M3FrD7qxSHguW3h8rSMPHQg3YThyBUYsCc1tNpgmhQXNHXlE6G7o
+vdlDawf0Oybq6KzhdU25/kJyTaM7suiDkwyZf8SIElSD8R2VdYmL2AeowJsi26Qc
+0f7l/cL/Pws0j4vxYY+6DD5uw+bCBvsjE5Y8Fw6t0xgYwnMCALjfKr2p3CW/Ifa/
+uynI7Hd548orqkddc834DO6gcPuXMUgZ75RFYglpnD+DDvOzvqh7mrgDiCURZuXd
+eZkF3sr4Wfn4YsQfM0XdfB0/dmzLnGGIzbW9cuB4VQUswDZ9KCnZVMZOC8AMKvSQ
+eZn8VEYSr+qT5m8yKSmeUUQga6G/jN6yHj2mV8ura3o1NHvQpy82lHX3M+2d+cs1
+PWTcYM3AwPpHAM2HyisPYOeNNiEKvo3mtyw2SgV4P6kavdNXFk/xA7mzDWr0QnNX
+/j4ZZFynhUz46joCC6bew0yyRfL1Jqy+XDvtEOmjhy96nJvUDb5IqsMY5ZHRmGkc
+yO3uVQu7kexLcA8mYA5OK1llWuyHxffTyGuL5C0q7+8mBvPrkCakUjsLGAgIWYTE
+ftJ6q8u8xyDghXhRM0lvcoVLjzzjCIDaGVqeXl6HtgJ4grUaNCjESIfsURFylVxk
+3jNFojsxHPtv+zYAG0otqedSKjZaG0uNivjBt/v21luSs+lqEKbv4122yzC8H6pG
+zrS6OGkKb8fIqz3D5nAezMFuMjd+ORiGf/IUJToCeluqVGwXMXExdDSCDf0hFJny
+6y/eKmA88lu6uHYe4TB7ZR2wPyIGl1HPN3xj7Dc/T3wEhCDycKLN4/fY9ZNw5U6E
+F5yVnZFdcaA6qHiY99xvtOPX/EmxibcV6C84QV3HDmdXgjEIH52I9oK0WEjRb2hd
+U2lCnZDNqthn3zn0DZ/aSe4HDe5SfLnzFFGyD1wvCTRcM25901Op4kgVD/BPwWH+
+4E7KiBh91UueWn7m5h1B8cEnpsHwpQLxq2ZdNYzp3ZFyzvzSUXe3QvPveehAgr0M
+lEXzn1/fJpmRPP5hvt6uYqZ+y90BkiT6UlANFHpoA6x0
+-----END ENCRYPTED PRIVATE KEY-----