Skip to content

Commit

Permalink
Add support for the encrypted PEM format
Browse files Browse the repository at this point in the history
  • Loading branch information
cescoffier committed Nov 16, 2024
1 parent 39eaeef commit 12f709b
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ public CertificateRequest withFormats(List<Format> formats) {
}

public CertificateRequest withFormat(Format format) {
if (format.equals(Format.PEM)) {
if (formats.contains(Format.ENCRYPTED_PEM)) {
throw new IllegalArgumentException("Cannot mix PEM and ENCRYPTED_PEM formats");
}
}
if (format.equals(Format.ENCRYPTED_PEM)) {
if (formats.contains(Format.PEM)) {
throw new IllegalArgumentException("Cannot mix PEM and ENCRYPTED_PEM formats");
}
}
this.formats.add(format);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.smallrye.certs;

import static io.smallrye.certs.CertificateUtils.writeTruststoreToPem;

import java.io.File;
import java.nio.file.Path;
import java.security.KeyPair;
Expand All @@ -11,6 +9,8 @@
import java.util.List;
import java.util.Map;

import static io.smallrye.certs.CertificateUtils.writeTruststoreToPem;

public class CertificateRequestManager {

/**
Expand Down Expand Up @@ -58,7 +58,7 @@ public List<CertificateFiles> generate(Path root, boolean replaceIfExists) throw
List<CertificateFiles> output = new ArrayList<>();
for (Format format : request.formats()) {
switch (format) {
case PEM -> output.addAll(generatePemCertificates(root, replaceIfExists));
case PEM, ENCRYPTED_PEM -> output.addAll(generatePemCertificates(root, replaceIfExists));
case JKS -> output.add(generateJksCertificates(root, replaceIfExists));
case PKCS12 -> output.add(generatePkcs12Certificates(root, replaceIfExists));
}
Expand Down Expand Up @@ -149,7 +149,13 @@ private List<CertificateFiles> generatePemCertificates(Path root, boolean replac

private CertificateFiles writePem(String name, CertificateHolder holder, Path root, boolean replaceIfExists)
throws Exception {
PemCertificateFiles files = new PemCertificateFiles(root, name, holder.hasClient());
PemCertificateFiles files = new PemCertificateFiles(root, name, holder.hasClient(), request.getPassword());

if (request.formats().contains(Format.ENCRYPTED_PEM)) {
if (request.getPassword() == null) {
throw new IllegalArgumentException("The password is required for the encrypted PEM format");
}
}

X509Certificate serverCert = holder.certificate();
X509Certificate clientCert = holder.clientCertificate();
Expand All @@ -167,7 +173,11 @@ private CertificateFiles writePem(String name, CertificateHolder holder, Path ro
CertificateUtils.writeCertificateToPEM(serverCert, certFile);
}
if (replaceIfExists || !keyFile.isFile()) {
CertificateUtils.writePrivateKeyToPem(serverKey.getPrivate(), request.getPassword(), keyFile);
if (request.formats().contains(Format.ENCRYPTED_PEM)) {
CertificateUtils.writePrivateKeyToPem(serverKey.getPrivate(), request.getPassword(), keyFile);
} else {
CertificateUtils.writePrivateKeyToPem(serverKey.getPrivate(), null, keyFile);
}
}
if (replaceIfExists || !clientTrustFile.isFile()) {
writeTruststoreToPem(List.of(serverCert), clientTrustFile);
Expand All @@ -178,7 +188,11 @@ private CertificateFiles writePem(String name, CertificateHolder holder, Path ro
CertificateUtils.writeCertificateToPEM(clientCert, clientCertFile);
}
if (replaceIfExists || !clientKeyFile.isFile()) {
CertificateUtils.writePrivateKeyToPem(clientKey.getPrivate(), request.getPassword(), clientKeyFile);
if (request.formats().contains(Format.ENCRYPTED_PEM)) {
CertificateUtils.writePrivateKeyToPem(clientKey.getPrivate(), request.getPassword(), clientKeyFile);
} else {
CertificateUtils.writePrivateKeyToPem(clientKey.getPrivate(), null, clientKeyFile);
}
}
if (replaceIfExists || !serverTrustfile.isFile()) {
writeTruststoreToPem(List.of(clientCert), serverTrustfile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private static byte[] generateSalt() {
public static void writePrivateKeyToPem(PrivateKey privateKey, String password, File output) throws Exception {

byte[] content = privateKey.getEncoded();
;

if (password != null) {
byte[] salt = generateSalt();
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
*/
public enum Format {
PEM,
ENCRYPTED_PEM,
JKS,
PKCS12;

String extension() {
return switch (this) {
case PEM -> "pem";
case PEM, ENCRYPTED_PEM -> "pem";
case JKS -> "jks";
case PKCS12 -> "p12";
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public final class PemCertificateFiles implements CertificateFiles {
private final Path root;
private final String name;
private final boolean client;
private final String password;

private final Path certFile;
private final Path keyFile;
Expand All @@ -16,7 +17,7 @@ public final class PemCertificateFiles implements CertificateFiles {
private final Path clientKeyFile;
private final Path serverTrustFile;

public PemCertificateFiles(Path root, String name, boolean client) {
public PemCertificateFiles(Path root, String name, boolean client, String password) {
this.root = root;
this.name = name;
this.client = client;
Expand All @@ -26,10 +27,14 @@ public PemCertificateFiles(Path root, String name, boolean client) {
this.clientCertFile = root.resolve(name + "-client.crt");
this.clientKeyFile = root.resolve(name + "-client.key");
this.serverTrustFile = root.resolve(name + "-server-ca.crt");
this.password = password;
}

@Override
public Format format() {
if (password != null) {
return Format.ENCRYPTED_PEM;
}
return Format.PEM;
}

Expand All @@ -50,7 +55,7 @@ public boolean client() {

@Override
public String password() {
return null;
return password;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void PEMGeneration(@Dir Path tempDir) throws Exception {
void PEMGenerationWithEncryptedPrivateKey(@Dir Path tempDir) throws Exception {
CertificateRequest request = new CertificateRequest()
.withName("test")
.withFormat(Format.PEM)
.withFormat(Format.ENCRYPTED_PEM)
.withPassword("secret");
Collection<CertificateFiles> files = new CertificateGenerator(tempDir, true).generate(request);
Assertions.assertThat(files).hasSize(1);
Expand Down Expand Up @@ -251,6 +251,46 @@ void mTLSWithPKCS12Generation(@Dir Path tempDir) throws Exception {
assertThat(response.statusCode()).isEqualTo(200);
}

@Test
void mTLSWithJKSAndEncryptedPemGeneration(@Dir Path tempDir) throws Exception {
CertificateRequest request = new CertificateRequest()
.withName("test")
.withPassword("secret")
.withClientCertificate()
.withFormat(Format.JKS)
.withFormat(Format.ENCRYPTED_PEM);
new CertificateGenerator(tempDir, true).generate(request);

File serverKeyStore = new File(tempDir.toFile(), "test-keystore.jks");
assertThat(serverKeyStore).isFile();
KeyCertOptions serverOptions = new JksOptions().setPath(serverKeyStore.getAbsolutePath()).setPassword("secret")
.setAlias("test");
File serverTrustStore = new File(tempDir.toFile(), "test-server-truststore.jks");
assertThat(serverTrustStore).isFile();
TrustOptions serverTrustOptions = new JksOptions().setPath(serverTrustStore.getAbsolutePath()).setPassword("secret")
.setAlias("test");

File clientKey = new File(tempDir.toFile(), "test-client.key");
assertThat(clientKey).isFile();
File clientCert = new File(tempDir.toFile(), "test-client.crt");
assertThat(clientCert).isFile();

Buffer buffer = decrypt(new File(tempDir.toFile(), "test-client.key"), "secret");

KeyCertOptions clientOptions = new PemKeyCertOptions()
.addKeyValue(buffer)
.addCertPath(clientCert.getAbsolutePath());
File clientTrustStore = new File(tempDir.toFile(), "test-client-ca.crt");
assertThat(clientTrustStore).isFile();
TrustOptions clientTrustOptions = new PemTrustOptions().addCertPath(clientTrustStore.getAbsolutePath());

var server = VertxHttpHelper.createHttpServerWithMutualAuth(vertx, serverOptions, serverTrustOptions);
var response = VertxHttpHelper.createHttpClientWithMutualAuthAndInvoke(vertx, server, clientOptions,
clientTrustOptions);

assertThat(response.statusCode()).isEqualTo(200);
}

@Test
void mTLSWithJKSAndPemGeneration(@Dir Path tempDir) throws Exception {
CertificateRequest request = new CertificateRequest()
Expand All @@ -275,6 +315,44 @@ void mTLSWithJKSAndPemGeneration(@Dir Path tempDir) throws Exception {
File clientCert = new File(tempDir.toFile(), "test-client.crt");
assertThat(clientCert).isFile();

KeyCertOptions clientOptions = new PemKeyCertOptions()
.addKeyPath(clientKey.getAbsolutePath())
.addCertPath(clientCert.getAbsolutePath());
File clientTrustStore = new File(tempDir.toFile(), "test-client-ca.crt");
assertThat(clientTrustStore).isFile();
TrustOptions clientTrustOptions = new PemTrustOptions().addCertPath(clientTrustStore.getAbsolutePath());

var server = VertxHttpHelper.createHttpServerWithMutualAuth(vertx, serverOptions, serverTrustOptions);
var response = VertxHttpHelper.createHttpClientWithMutualAuthAndInvoke(vertx, server, clientOptions,
clientTrustOptions);

assertThat(response.statusCode()).isEqualTo(200);
}

@Test
void mTLSWithP12AndEncryptedPemGeneration(@Dir Path tempDir) throws Exception {
CertificateRequest request = new CertificateRequest()
.withName("test")
.withPassword("secret")
.withClientCertificate()
.withFormat(Format.PKCS12)
.withFormat(Format.ENCRYPTED_PEM);
new CertificateGenerator(tempDir, true).generate(request);

File serverKeyStore = new File(tempDir.toFile(), "test-keystore.p12");
assertThat(serverKeyStore).isFile();
KeyCertOptions serverOptions = new PfxOptions().setPath(serverKeyStore.getAbsolutePath()).setPassword("secret")
.setAlias("test");
File serverTrustStore = new File(tempDir.toFile(), "test-server-truststore.p12");
assertThat(serverTrustStore).isFile();
TrustOptions serverTrustOptions = new PfxOptions().setPath(serverTrustStore.getAbsolutePath()).setPassword("secret")
.setAlias("test");

File clientKey = new File(tempDir.toFile(), "test-client.key");
assertThat(clientKey).isFile();
File clientCert = new File(tempDir.toFile(), "test-client.crt");
assertThat(clientCert).isFile();

Buffer buffer = decrypt(new File(tempDir.toFile(), "test-client.key"), "secret");

KeyCertOptions clientOptions = new PemKeyCertOptions()
Expand Down Expand Up @@ -315,10 +393,8 @@ void mTLSWithP12AndPemGeneration(@Dir Path tempDir) throws Exception {
File clientCert = new File(tempDir.toFile(), "test-client.crt");
assertThat(clientCert).isFile();

Buffer buffer = decrypt(new File(tempDir.toFile(), "test-client.key"), "secret");

KeyCertOptions clientOptions = new PemKeyCertOptions()
.addKeyValue(buffer)
.addKeyPath(clientKey.getAbsolutePath())
.addCertPath(clientCert.getAbsolutePath());
File clientTrustStore = new File(tempDir.toFile(), "test-client-ca.crt");
assertThat(clientTrustStore).isFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import io.smallrye.certs.pem.parsers.EncryptedPKCS8Parser;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.*;
import io.vertx.core.net.JksOptions;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.core.net.PemTrustOptions;
import io.vertx.core.net.TrustOptions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down Expand Up @@ -58,17 +62,14 @@ public void testMixingKeystoreAndTruststoreFormat(Format serverKeystoreFormat, F
generate();

KeyCertOptions serverKS = switch (serverKeystoreFormat) {
case PEM -> {
case PEM -> new PemKeyCertOptions()
.addKeyPath("target/certs/test-mixed-mtls.key")
.addCertPath("target/certs/test-mixed-mtls.crt");
case ENCRYPTED_PEM -> {
Buffer buffer = decrypt(new File("target/certs/test-mixed-mtls.key"), "password");
if (buffer != null) {
yield new PemKeyCertOptions()
.addKeyValue(buffer)
.addCertPath("target/certs/test-mixed-mtls.crt");
} else {
yield new PemKeyCertOptions()
.addKeyPath("target/certs/test-mixed-mtls.key")
.addCertPath("target/certs/test-mixed-mtls.crt");
}
yield new PemKeyCertOptions()
.addKeyValue(buffer)
.addCertPath("target/certs/test-mixed-mtls.crt");
}
case JKS -> new JksOptions()
.setPath("target/certs/test-mixed-mtls-keystore.jks")
Expand All @@ -79,17 +80,14 @@ yield new PemKeyCertOptions()
};

KeyCertOptions clientKS = switch (clientKeystoreFormat) {
case PEM -> {
case PEM -> new PemKeyCertOptions()
.addKeyPath("target/certs/test-mixed-mtls-client.key")
.addCertPath("target/certs/test-mixed-mtls-client.crt");
case ENCRYPTED_PEM -> {
Buffer buffer = decrypt(new File("target/certs/test-mixed-mtls-client.key"), "password");
if (buffer != null) {
yield new PemKeyCertOptions()
.addKeyValue(buffer)
.addCertPath("target/certs/test-mixed-mtls-client.crt");
} else {
yield new PemKeyCertOptions()
.addKeyPath("target/certs/test-mixed-mtls-client.key")
.addCertPath("target/certs/test-mixed-mtls-client.crt");
}
yield new PemKeyCertOptions()
.addKeyValue(buffer)
.addCertPath("target/certs/test-mixed-mtls-client.crt");
}
case JKS -> new JksOptions()
.setPath("target/certs/test-mixed-mtls-client-keystore.jks")
Expand All @@ -100,7 +98,7 @@ yield new PemKeyCertOptions()
};

TrustOptions serverTS = switch (serverTruststoreFormat) {
case PEM -> new PemTrustOptions()
case PEM, ENCRYPTED_PEM -> new PemTrustOptions()
.addCertPath("target/certs/test-mixed-mtls-server-ca.crt");
case JKS -> new JksOptions()
.setPath("target/certs/test-mixed-mtls-server-truststore.jks")
Expand All @@ -111,7 +109,7 @@ yield new PemKeyCertOptions()
};

TrustOptions clientTS = switch (clientTrustoreFormat) {
case PEM -> new PemTrustOptions()
case PEM, ENCRYPTED_PEM -> new PemTrustOptions()
.addCertPath("target/certs/test-mixed-mtls-client-ca.crt");
case JKS -> new JksOptions()
.setPath("target/certs/test-mixed-mtls-client-truststore.jks")
Expand All @@ -137,7 +135,7 @@ private void generate() throws Exception {
.withFormats(List.of(Format.JKS, Format.PKCS12, Format.PEM))
.withClientCertificate()
.withPassword("password");
CertificateGenerator generator = new CertificateGenerator(new File("target/certs").toPath(), false);
CertificateGenerator generator = new CertificateGenerator(new File("target/certs").toPath(), true);
generator.generate(request);
}

Expand Down
Loading

0 comments on commit 12f709b

Please sign in to comment.