cert-encoder.git

commit 8e6434d3fe106afc2bbb1982d9520723b8dbf7af

Author: Paolo Lulli <paolo@lulli.net>

Symmetric and PKI enc/dec working

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2f90af11be34977f529b564f85b04fc12b68252a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+target/*
+.dqt
+.idea/*
+dependency-reduced-pom.xml




diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a203e795a1fa1e07fb1cb43536343c43f5f46679
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>net.lulli</groupId>
+  <artifactId>cert-encoder</artifactId>
+  <version>0.0.1</version>
+  <repositories>
+    <repository>
+      <id>code.lulli.net</id>
+      <url>https://code.lulli.net/maven</url>
+    </repository>
+  </repositories>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>17</source>
+          <target>17</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>3.3.0</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <minimizeJar>false</minimizeJar>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <manifestEntries>
+                    <Main-Class>net.lulli.nojs.server.ServerRunner</Main-Class>
+                    <Build-Number>1.0</Build-Number>
+                  </manifestEntries>
+                </transformer>
+              </transformers>
+              <filters>
+		<!-- avoid issue shading BouncyCastle signed jars -->
+                <filter>
+                  <artifact>*:*</artifact>
+                  <excludes>
+                    <exclude>META-INF/*.SF</exclude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.wagon</groupId>
+        <artifactId>wagon-ssh</artifactId>
+        <version>3.5.3</version>
+      </extension>
+    </extensions>
+  </build>
+  <distributionManagement>
+    <repository>
+      <id>${repository.id}</id>
+      <name>${repository.name}</name>
+      <url>${repository.url}</url>
+    </repository>
+    <site>
+      <id>${repository.id}</id>
+      <name>${repository.name}</name>
+      <url>${repository.site.url}</url>
+    </site>
+  </distributionManagement>
+  <!-- ****************************************** -->
+  <dependencies>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpkix-jdk15to18</artifactId>
+      <version>1.77</version>
+    </dependency>
+    <dependency>
+      <groupId>org.json</groupId>
+      <artifactId>json</artifactId>
+      <version>20231013</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.13.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>




diff --git a/src/main/java/net/lulli/encrypt/Pems.java b/src/main/java/net/lulli/encrypt/Pems.java
new file mode 100644
index 0000000000000000000000000000000000000000..52ee1e2fc6e4e42effbb7dab35fe7efc2d0976cc
--- /dev/null
+++ b/src/main/java/net/lulli/encrypt/Pems.java
@@ -0,0 +1,76 @@
+package net.lulli.encrypt;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+public class Pems {
+    private Pems(){}
+
+    public static X509Certificate readX509Certificate(String certificate) throws Exception {
+        InputStream targetStream = new ByteArrayInputStream(certificate.getBytes());
+        return (X509Certificate) CertificateFactory
+                .getInstance("X509")
+                .generateCertificate(targetStream);
+    }
+
+    public static X509Certificate certificateFromFile(String fileName) {
+        try {
+            var keyContent = Files.readString(Paths.get(fileName));
+            return readX509Certificate(keyContent);
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+    public static PublicKey publicKeyFromFile(String fileName) {
+        try {
+            var keyContent = Files.readString(Paths.get(fileName));
+            return readX509PublicKey(keyContent);
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    public static PrivateKey privateKeyFromFile(String fileName) {
+        try {
+            var keyContent = Files.readString(Paths.get(fileName));
+            return readPKCS8PrivateKey(keyContent);
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    public static PublicKey readX509PublicKey(String key) throws Exception {
+        String publicKeyPEM = key
+                .replace("-----BEGIN PUBLIC KEY-----", "")
+                .replaceAll(System.lineSeparator(), "")
+                .replace("-----END PUBLIC KEY-----", "");
+
+        byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
+
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
+        return (PublicKey) keyFactory.generatePublic(keySpec);
+    }
+
+    public static PrivateKey readPKCS8PrivateKey(String key) throws Exception {
+        String privateKeyPEM = key
+                .replace("-----BEGIN PRIVATE KEY-----", "")
+                .replaceAll(System.lineSeparator(), "")
+                .replace("-----END PRIVATE KEY-----", "");
+
+        byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+        return (PrivateKey) keyFactory.generatePrivate(keySpec);
+    }
+}




diff --git a/src/main/java/net/lulli/encrypt/pki/PkiEncryptionManager.java b/src/main/java/net/lulli/encrypt/pki/PkiEncryptionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fc02bd941791443d39e7790f588e99c72440e55
--- /dev/null
+++ b/src/main/java/net/lulli/encrypt/pki/PkiEncryptionManager.java
@@ -0,0 +1,179 @@
+package net.lulli.encrypt.pki;
+
+import net.lulli.encrypt.Pems;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.*;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Store;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+
+public class PkiEncryptionManager {
+    public static final PkiEncryptionManager INSTANCE = new PkiEncryptionManager();
+    private PkiEncryptionManager() {}
+
+    public  void signFile(File fileToSign) {
+        try {
+            char[] password = "yourpass".toCharArray();
+            String certificateAlias = "";
+            String keyAlias = "";
+            var keystoreFile = "full/path/to/your/original/certificate.pfx";
+
+            Security.addProvider(new BouncyCastleProvider());
+            KeyStore ks = loadKeyStore(keystoreFile, password);
+
+            X509Certificate cert = (X509Certificate) ks.getCertificate(certificateAlias);
+            PrivateKey privatekey = (PrivateKey) ks.getKey(keyAlias, password);
+
+            String signatureFileName = fileToSign.getName().toString() + ".signature";
+
+            byte[] buffer = loadFileIntoBuffer(fileToSign);
+
+            byte[] signeddata = generateSignature(cert, cert.getPublicKey(), privatekey, buffer);
+
+            writeSignatureToFile(signatureFileName, signeddata);
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    private static byte[] loadFileIntoBuffer(File fileToSign) {
+        try {
+            byte[] buffer = new byte[(int) fileToSign.length()];
+            try (DataInputStream in = new DataInputStream(new FileInputStream(fileToSign))) {
+                in.readFully(buffer);
+            }
+            return buffer;
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    private static byte[] generateSignature(X509Certificate cert, PublicKey publicKey, PrivateKey privatekey, byte[] buffer) {
+        try {
+            ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+            certList.add(cert);
+            Store<?> certs = new JcaCertStore(certList);
+            CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();
+
+            ContentSigner sha1signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privatekey);
+            signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1signer, cert));
+            signGen.addCertificates(certs);
+            CMSTypedData content = new CMSProcessableByteArray(buffer);
+            CMSSignedData signedData = signGen.generate(content, false);
+            byte[] signeddata = signedData.getEncoded();
+            return signeddata;
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    public  byte[] encryptData(byte[] data, X509Certificate encryptionCertificate) {
+        try {
+            Security.addProvider(new BouncyCastleProvider());
+
+            if (null != data && null != encryptionCertificate) {
+                var cmsEnvelopedDataGenerator = new CMSEnvelopedDataGenerator();
+
+                //JceKeyTransRecipientInfoGenerator jceKey = new JceKeyTransRecipientInfoGenerator(encryptionCertificate);
+                cmsEnvelopedDataGenerator.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(encryptionCertificate).setProvider("BC"));
+                var cmsProcessableByteArray = new CMSProcessableByteArray(data);
+                var outputEncryptor = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC).setProvider("BC").build();
+                var cmsEnvelopedData = cmsEnvelopedDataGenerator.generate(cmsProcessableByteArray, outputEncryptor);
+                return cmsEnvelopedData.getEncoded();
+            }
+            return null;
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+
+    public void decryptCms(PrivateKey privateKey, byte[] encryptedData, String destinationFile) {
+        try {
+            var decryptedDestination = new File(destinationFile);
+            //byte[] encryptedData = Files.readAllBytes(encrypted.toPath());
+
+            var cmsEnvelopedDataParser = new CMSEnvelopedDataParser(encryptedData);
+
+            var singleRecipient = getSingleRecipient(cmsEnvelopedDataParser);
+            var jceKeyTransEnvelopedRecipient = new JceKeyTransEnvelopedRecipient(privateKey);
+
+            try (InputStream decryptedStream = singleRecipient.getContentStream(jceKeyTransEnvelopedRecipient).getContentStream()) {
+                Files.copy(decryptedStream, decryptedDestination.toPath());
+            }
+
+            System.out.println(String.format("Decrypted  to file :'%s'", decryptedDestination.getAbsolutePath()));
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    private static RecipientInformation getSingleRecipient(CMSEnvelopedDataParser parser) {
+        var recInfos = parser.getRecipientInfos().getRecipients();
+        var recipientIterator = recInfos.iterator();
+        if (!recipientIterator.hasNext()) {
+            throw new RuntimeException("Could not find recipient");
+        }
+        return recipientIterator.next();
+    }
+
+    private static void writeSignatureToFile(String fileName, byte[] signeddata) {
+        try {
+            var fileOutputStream = new FileOutputStream(fileName);
+            byte[] outputString = Base64.encode(signeddata);
+            int fullLines = (int) Math.floor(outputString.length / 64);
+            for (int i = 0; i < fullLines; i++) {
+                fileOutputStream.write(outputString, i * 64, 64);
+                fileOutputStream.write("\r\n".getBytes());
+            }
+
+            fileOutputStream.write(outputString, fullLines * 64, outputString.length % 64);
+            fileOutputStream.close();
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    private static KeyStore loadKeyStore(String keystoreFile, char[] password) {
+        try {
+            var keyStore = KeyStore.getInstance("PKCS12");
+            keyStore.load(new FileInputStream(keystoreFile), password);
+            return keyStore;
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    /*
+    public static void main(String[] args) throws Exception {
+        var plainText = "this is a sample text";
+        X509Certificate x509Certificate = Pems.certificateFromFile("/home/plulli/.x509crypt/local/certs/local.crt");
+        //var enc = EncDec.encrypt(plainText, x509Certificate.getPublicKey().getEncoded());
+        byte[] enc = encryptData(plainText.getBytes(), x509Certificate);
+        System.out.printf("Cleartext: [%s]\n Encrypted: [%s]\n", plainText, new String(enc));
+
+        Files.write(Paths.get("/tmp/output2.txt.enc"), enc);
+
+        PrivateKey privateKey = Pems.privateKeyFromFile("/home/plulli/.x509crypt/local/certs/local.key");
+        decryptCms(privateKey, enc, "/tmp/output2.txt");
+    }
+
+     */
+}




diff --git a/src/main/java/net/lulli/encrypt/symmetric/SymmetricEncryptionManager.java b/src/main/java/net/lulli/encrypt/symmetric/SymmetricEncryptionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..e124e2dc0768011c7adbf762955dd4f57cf02441
--- /dev/null
+++ b/src/main/java/net/lulli/encrypt/symmetric/SymmetricEncryptionManager.java
@@ -0,0 +1,77 @@
+package net.lulli.encrypt.symmetric;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
+public class SymmetricEncryptionManager {
+    public static final SymmetricEncryptionManager INSTANCE = new SymmetricEncryptionManager();
+    private SymmetricEncryptionManager() {}
+
+    public byte[] encrypt(String plainText, byte[] key) {
+        try {
+            byte[] clean = plainText.getBytes();
+
+            int ivSize = 16;
+            byte[] iv = new byte[ivSize];
+            var random = new SecureRandom();
+            random.nextBytes(iv);
+            var ivParameterSpec = new IvParameterSpec(iv);
+
+            // Hashing key.
+            var messageDigest = MessageDigest.getInstance("SHA-256");
+            messageDigest.update(key);
+            byte[] keyBytes = new byte[16];
+            System.arraycopy(messageDigest.digest(), 0, keyBytes, 0, keyBytes.length);
+            var secretKeySpec = new SecretKeySpec(keyBytes, "AES");
+
+            // Encrypt.
+            var cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+            byte[] encrypted = cipher.doFinal(clean);
+
+            // Combine IV and encrypted part.
+            byte[] encryptedIVAndText = new byte[ivSize + encrypted.length];
+            System.arraycopy(iv, 0, encryptedIVAndText, 0, ivSize);
+            System.arraycopy(encrypted, 0, encryptedIVAndText, ivSize, encrypted.length);
+
+            return encryptedIVAndText;
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+
+    public byte[] decrypt(byte[] encryptedIvTextBytes, byte[] key) {
+        try {
+            int ivSize = 16;
+            int keySize = 16;
+
+            // Extract IV.
+            byte[] iv = new byte[ivSize];
+            System.arraycopy(encryptedIvTextBytes, 0, iv, 0, iv.length);
+            var ivParameterSpec = new IvParameterSpec(iv);
+
+            // Extract encrypted part.
+            int encryptedSize = encryptedIvTextBytes.length - ivSize;
+            byte[] encryptedBytes = new byte[encryptedSize];
+            System.arraycopy(encryptedIvTextBytes, ivSize, encryptedBytes, 0, encryptedSize);
+
+            // Hash key.
+            byte[] keyBytes = new byte[keySize];
+            var messageDigest = MessageDigest.getInstance("SHA-256");
+            messageDigest.update(key);
+            System.arraycopy(messageDigest.digest(), 0, keyBytes, 0, keyBytes.length);
+            var secretKeySpec = new SecretKeySpec(keyBytes, "AES");
+
+            // Decrypt.
+            var cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
+            return cipherDecrypt.doFinal(encryptedBytes);
+
+        } catch (Exception e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+    }
+}




diff --git a/src/test/java/EncryptionTest.java b/src/test/java/EncryptionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7443f8bd2f3b8d4b69c843df050e3b55a1a91f7e
--- /dev/null
+++ b/src/test/java/EncryptionTest.java
@@ -0,0 +1,53 @@
+import net.lulli.encrypt.Pems;
+import net.lulli.encrypt.pki.PkiEncryptionManager;
+import net.lulli.encrypt.symmetric.SymmetricEncryptionManager;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public class EncryptionTest {
+
+    @Test
+    public void symmetricEncryptDecrypt() {
+        var symmetricEncryptionManager = SymmetricEncryptionManager.INSTANCE;
+        var plainTest = "This is some long cleartext";
+        var key = "abcdefghijklmnopqrstuvz".getBytes(Charset.forName("UTF-8"));
+
+        byte[] encrypted = symmetricEncryptionManager.encrypt(plainTest, key);
+
+        byte[] decrypted = symmetricEncryptionManager.decrypt(encrypted, key);
+
+        assert Arrays.equals(plainTest.getBytes(), decrypted);
+    }
+
+    @Ignore //TODO
+    @Test
+    public void pkiEncryptDecrypt() throws Exception {
+        var pkiEncryptionManager = PkiEncryptionManager.INSTANCE;
+
+        File outputEncryptedFile = File.createTempFile("pkiEncrDecr.", ".enc");
+        File outputDencryptedFile = File.createTempFile("pkiEncrDecr", ".dec");
+        var plainText = "this is a sample text";
+        String certString = Files.readString(Paths.get(getClass().getResource("local.crt").toURI()), Charset.forName("utf-8"));
+
+        String keyString = Files.readString(Paths.get(getClass().getResource("local.key").toURI()), Charset.forName("utf-8"));
+        X509Certificate x509Certificate = Pems.readX509Certificate(certString);
+
+        byte[] enc = pkiEncryptionManager.encryptData(plainText.getBytes(), x509Certificate);
+        System.out.printf("Cleartext: [%s]\n Encrypted: [%s]\n", plainText, new String(enc));
+
+        Files.write(Paths.get(outputEncryptedFile.getAbsolutePath()), enc);
+
+        PrivateKey privateKey = Pems.readPKCS8PrivateKey(keyString);
+        pkiEncryptionManager.decryptCms(privateKey, enc, outputDencryptedFile.getAbsolutePath());
+
+    }
+}




diff --git a/src/test/resources/local.crt b/src/test/resources/local.crt
new file mode 100644
index 0000000000000000000000000000000000000000..7b82fd80890590bb43a772b299c284a680839e2b
--- /dev/null
+++ b/src/test/resources/local.crt
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFYTCCA0kCFBGl8TU2PlZ5mx7Q/5AAkisMU0ezMA0GCSqGSIb3DQEBCwUAMG0x
+CzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xDjAMBgNVBAcMBUZsb2J5
+MRwwGgYDVQQKDBNLZXZ3ZSBUZWNobm9sb2d5IEFCMQwwCgYDVQQLDANEZXYxDjAM
+BgNVBAMMBWxvY2FsMB4XDTI0MDQwNTIxNDIwN1oXDTI1MDQwNTIxNDIwN1owbTEL
+MAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9sbTEOMAwGA1UEBwwFRmxvYnkx
+HDAaBgNVBAoME0tldndlIFRlY2hub2xvZ3kgQUIxDDAKBgNVBAsMA0RldjEOMAwG
+A1UEAwwFbG9jYWwwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDWRP0V
+OivcmAZtGgif3Q/xnn3FqYIypiGt4Fq8Qz780zvPFMJ8SnBpfQh1Xhy6XRtqCB/i
+NqYwOnhMo+UyqiFWCOHfVtn/oLpEurM5jHrJmULVyL5Pucx3zHelrFGQkvOt9HMj
+2hwGC/1MZl4Aksg9WDqhDSJ6pwdgsmd0UGXDYTGbRPNdu7ywAu8RLqgyY1NkNstB
+7sMoqCZd8NFjiGxjfDXoPZ/1mGcQzj24UjX8tH+bIJhoFrRvadWoHfA9GWJM1n+H
+P4JEfGTL30WhWW4PntTBvPI6ro8y5LY6jtj+OV45WKe2lkFi/xO8wpwi7skcTK76
+anH6VKOLW7/uy21pbWt7yyx0e7TqExE0I2RoIohtUJ49VbBdexhEdsHt0q1T4RR5
+qAPEQNAduEqr1UH511Etly+Az2PJE9ZqeDbxQdvu0FjK8Tbws+mAVyDBZQ30e1op
+/31e7v+8suPyRf8v/wq5Ut9Vse7d81rHE83CPvjqzvN49p+nylrySrnuP0qL1HU7
+tD239FU2szMpxkc6f/v/vQzNFfCylZdKd9/k4l3v7t4ECoLNZ8esnYLrHbJuNhN7
+VGOUsTcAwEFK9n5zOpcS2oU3lO12Cq6vr7OoxY4DGkY0fsye/eVHpUwI/ltVa3jU
+i49Esn9fsEraflDTz9ru0u3BIvWuHKG714LtSQIDAQABMA0GCSqGSIb3DQEBCwUA
+A4ICAQAexne/vcaEaqyZ8ZNH7RpBwFGtCAJtZiCncbIr1Ih80VGHSgmP/hStxIpl
+7Nf8aAm2G0WBQldX7EryJwmTXuoyp9kahJbaCOpzknc+Ckck5mO5X6+u8HvkQ6rt
+SzPmQc+RL86v/nYiTVj85oLH2IHA+Ui+DqiUc+oUd8tF4bLH++ocKsfjt1+nPEU9
+ClL2jV80ZVoYjKx/0JpPFRVRr09AZktbR1KijCu0KcCq8ZyE+DYAVUlzV5u73Oih
+a/CjXinVYHmaG1td4gXoTB+zF58SOi5Uo/6ccgGoNFixhLJqx4ZtluspWm0aO/yD
+kijTjR4zdvEx+xtnOgjwHNFs6cx4SVF+xehPTxvChCIweqmlm4uJEj7oT4fkC3Tn
+zs1Vq4ERzHDexxWDBZF9Qi3yvvkIXIahx3rKh9c436gFuI8IEdVdYBrt4qJUQclL
+xJQKi0kB8m1tcjp1SH1YgOLR2p4ziv403MeebqVOTaMhI/0KFU1RBPoHc8zM8pKE
+TA3VFt1W2EIDyg+c52EE/LSAytuKT7hfWSFsaws+4uHJmf2F4mOl/bn5DCH6yOE7
+sWcDnSTOXlHmTaFwYMpKd6J7AsroxxpsMfF38ah3Ji8ZOqewA+jwO8V84X9eI/bx
+gZUtOzHBQhLULySehgBt1CyUPWoawR16n46vXQ4viX4ndldmlQ==
+-----END CERTIFICATE-----




diff --git a/src/test/resources/local.key b/src/test/resources/local.key
new file mode 100644
index 0000000000000000000000000000000000000000..7a6ce209765b5d640a793872b52d8976a1a7d4da
--- /dev/null
+++ b/src/test/resources/local.key
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+3rV4KEVHvpYDNFOy8iTrQU4y2EE9pA==
+-----END PRIVATE KEY-----