Skip to content

Commit

Permalink
Merge pull request #45117 from cescoffier/expired-certificate-strategy
Browse files Browse the repository at this point in the history
TLS - Enable Policy Configuration for Expired or Not Yet Valid Certificates
  • Loading branch information
sberyozkin authored Dec 15, 2024
2 parents deb9b6e + 4f8602d commit 75c5f5e
Show file tree
Hide file tree
Showing 11 changed files with 712 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import io.quarkus.tls.TlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.quarkus.tls.runtime.keystores.ExpiryTrustOptions;
import io.smallrye.reactive.messaging.ClientCustomizer;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.KeyCertOptions;
Expand Down Expand Up @@ -46,6 +47,10 @@ public ClientBuilder customize(String channel, Config channelConfig, ClientBuild
KeyCertOptions keyStoreOptions = configuration.getKeyStoreOptions();
TrustOptions trustStoreOptions = configuration.getTrustStoreOptions();

if (trustStoreOptions instanceof ExpiryTrustOptions) {
trustStoreOptions = ((ExpiryTrustOptions) trustStoreOptions).unwrap();
}

if (keyStoreOptions instanceof PemKeyCertOptions keyCertOptions
&& trustStoreOptions instanceof PemTrustOptions trustCertOptions) {
Buffer trust = trustCertOptions.getCertValues().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package io.quarkus.tls;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.concurrent.CountDownLatch;

import javax.net.ssl.SSLHandshakeException;

import jakarta.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.certs.Format;
import io.smallrye.certs.junit5.Certificate;
import io.smallrye.certs.junit5.Certificates;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;

@Certificates(baseDir = "target/certs", certificates = {
@Certificate(name = "expired-test-formats", password = "password", formats = { Format.JKS, Format.PEM,
Format.PKCS12 }, duration = -5)
})
public class ExpiredJKSTrustStoreTest {

private static final String configuration = """
# Server
quarkus.tls.key-store.jks.path=target/certs/expired-test-formats-keystore.jks
quarkus.tls.key-store.jks.password=password
# Clients
quarkus.tls.warn.trust-store.jks.path=target/certs/expired-test-formats-truststore.jks
quarkus.tls.warn.trust-store.jks.password=password
quarkus.tls.warn.trust-store.certificate-expiration-policy=warn
quarkus.tls.reject.trust-store.jks.path=target/certs/expired-test-formats-truststore.jks
quarkus.tls.reject.trust-store.jks.password=password
quarkus.tls.reject.trust-store.certificate-expiration-policy=reject
""";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
() -> ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset(configuration), "application.properties"));

@Inject
TlsConfigurationRegistry certificates;

@Inject
Vertx vertx;

@Test
void testWarn() throws InterruptedException {
TlsConfiguration cf = certificates.get("warn").orElseThrow();
assertThat(cf.getTrustStoreOptions()).isNotNull();

WebClient client = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustOptions(cf.getTrustStoreOptions()));

vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificates.getDefault().orElseThrow().getKeyStoreOptions()))
.requestHandler(rc -> rc.response().end("Hello")).listen(8081).toCompletionStage().toCompletableFuture().join();

CountDownLatch latch = new CountDownLatch(1);
client.get(8081, "localhost", "/").send(ar -> {
assertThat(ar.succeeded()).isTrue();
assertThat(ar.result().bodyAsString()).isEqualTo("Hello");
latch.countDown();
});

assertThat(latch.await(10, java.util.concurrent.TimeUnit.SECONDS)).isTrue();
}

@Test
void testReject() {
TlsConfiguration cf = certificates.get("reject").orElseThrow();
assertThat(cf.getTrustStoreOptions()).isNotNull();

WebClient client = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustOptions(cf.getTrustStoreOptions()));

vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificates.getDefault().orElseThrow().getKeyStoreOptions()))
.requestHandler(rc -> rc.response().end("Hello")).listen(8081).toCompletionStage().toCompletableFuture().join();

assertThatThrownBy(() -> client.get(8081, "localhost", "/")
.send().toCompletionStage().toCompletableFuture().join()).hasCauseInstanceOf(SSLHandshakeException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package io.quarkus.tls;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.concurrent.CountDownLatch;

import javax.net.ssl.SSLHandshakeException;

import jakarta.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.certs.Format;
import io.smallrye.certs.junit5.Certificate;
import io.smallrye.certs.junit5.Certificates;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;

@Certificates(baseDir = "target/certs", certificates = {
@Certificate(name = "expired-test-formats", password = "password", formats = { Format.JKS, Format.PEM,
Format.PKCS12 }, duration = -5)
})
public class ExpiredP12TrustStoreTest {

private static final String configuration = """
# Server
quarkus.tls.key-store.p12.path=target/certs/expired-test-formats-keystore.p12
quarkus.tls.key-store.p12.password=password
# Clients
quarkus.tls.warn.trust-store.p12.path=target/certs/expired-test-formats-truststore.p12
quarkus.tls.warn.trust-store.p12.password=password
quarkus.tls.warn.trust-store.certificate-expiration-policy=warn
quarkus.tls.reject.trust-store.p12.path=target/certs/expired-test-formats-truststore.p12
quarkus.tls.reject.trust-store.p12.password=password
quarkus.tls.reject.trust-store.certificate-expiration-policy=reject
""";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
() -> ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset(configuration), "application.properties"));

@Inject
TlsConfigurationRegistry certificates;

@Inject
Vertx vertx;

@Test
void testWarn() throws InterruptedException {
TlsConfiguration cf = certificates.get("warn").orElseThrow();
assertThat(cf.getTrustStoreOptions()).isNotNull();

WebClient client = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustOptions(cf.getTrustStoreOptions()));

vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificates.getDefault().orElseThrow().getKeyStoreOptions()))
.requestHandler(rc -> rc.response().end("Hello")).listen(8081).toCompletionStage().toCompletableFuture().join();

CountDownLatch latch = new CountDownLatch(1);
client.get(8081, "localhost", "/").send(ar -> {
assertThat(ar.succeeded()).isTrue();
assertThat(ar.result().bodyAsString()).isEqualTo("Hello");
latch.countDown();
});

assertThat(latch.await(10, java.util.concurrent.TimeUnit.SECONDS)).isTrue();
}

@Test
void testReject() {
TlsConfiguration cf = certificates.get("reject").orElseThrow();
assertThat(cf.getTrustStoreOptions()).isNotNull();

WebClient client = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustOptions(cf.getTrustStoreOptions()));

vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificates.getDefault().orElseThrow().getKeyStoreOptions()))
.requestHandler(rc -> rc.response().end("Hello")).listen(8081).toCompletionStage().toCompletableFuture().join();

assertThatThrownBy(() -> client.get(8081, "localhost", "/")
.send().toCompletionStage().toCompletableFuture().join()).hasCauseInstanceOf(SSLHandshakeException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.quarkus.tls;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.concurrent.CountDownLatch;

import javax.net.ssl.SSLHandshakeException;

import jakarta.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.certs.Format;
import io.smallrye.certs.junit5.Certificate;
import io.smallrye.certs.junit5.Certificates;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;

@Certificates(baseDir = "target/certs", certificates = {
@Certificate(name = "expired-test-formats", password = "password", formats = { Format.JKS, Format.PEM,
Format.PKCS12 }, duration = -5)
})
public class ExpiredPemTrustStoreTest {

private static final String configuration = """
# Server
quarkus.tls.key-store.p12.path=target/certs/expired-test-formats-keystore.p12
quarkus.tls.key-store.p12.password=password
# Clients
quarkus.tls.warn.trust-store.pem.certs=target/certs/expired-test-formats.crt
quarkus.tls.warn.trust-store.certificate-expiration-policy=warn
quarkus.tls.reject.trust-store.pem.certs=target/certs/expired-test-formats.crt
quarkus.tls.reject.trust-store.certificate-expiration-policy=reject
""";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
() -> ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset(configuration), "application.properties"));

@Inject
TlsConfigurationRegistry certificates;

@Inject
Vertx vertx;

@Test
void testWarn() throws InterruptedException {
TlsConfiguration cf = certificates.get("warn").orElseThrow();
assertThat(cf.getTrustStoreOptions()).isNotNull();

WebClient client = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustOptions(cf.getTrustStoreOptions()));

vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificates.getDefault().orElseThrow().getKeyStoreOptions()))
.requestHandler(rc -> rc.response().end("Hello")).listen(8081).toCompletionStage().toCompletableFuture().join();

CountDownLatch latch = new CountDownLatch(1);
client.get(8081, "localhost", "/").send(ar -> {
assertThat(ar.succeeded()).isTrue();
assertThat(ar.result().bodyAsString()).isEqualTo("Hello");
latch.countDown();
});

assertThat(latch.await(10, java.util.concurrent.TimeUnit.SECONDS)).isTrue();
}

@Test
void testReject() {
TlsConfiguration cf = certificates.get("reject").orElseThrow();
assertThat(cf.getTrustStoreOptions()).isNotNull();

WebClient client = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustOptions(cf.getTrustStoreOptions()));

vertx.createHttpServer(new HttpServerOptions()
.setSsl(true)
.setKeyCertOptions(certificates.getDefault().orElseThrow().getKeyStoreOptions()))
.requestHandler(rc -> rc.response().end("Hello")).listen(8081).toCompletionStage().toCompletableFuture().join();

assertThatThrownBy(() -> client.get(8081, "localhost", "/")
.send().toCompletionStage().toCompletableFuture().join()).hasCauseInstanceOf(SSLHandshakeException.class);
}
}
Loading

0 comments on commit 75c5f5e

Please sign in to comment.