Skip to content

Commit

Permalink
Implementation of the internal TLS registry
Browse files Browse the repository at this point in the history
  • Loading branch information
cescoffier committed Apr 22, 2024
1 parent c1108d0 commit 26eeba8
Show file tree
Hide file tree
Showing 132 changed files with 7,607 additions and 1 deletion.
11 changes: 11 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,17 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry-deployment</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Quarkus extensions -->

<dependency>
Expand Down
3 changes: 2 additions & 1 deletion extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
<name>Quarkus - Extensions - Parent pom</name>
<packaging>pom</packaging>
<modules>
<!-- Netty loom adaptor-->
<module>virtual-threads</module>
<module>tls-registry</module>

<!-- Plumbing -->
<module>arc</module>
<module>scheduler</module>
Expand Down
89 changes: 89 additions & 0 deletions extensions/tls-registry/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-tls-registry-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-tls-registry-deployment</artifactId>
<name>Quarkus - TLS Registry - Deployment</name>


<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-credentials-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry</artifactId>
</dependency>

<!-- Tests -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.tls;

import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.tls.runtime.CertificateRecorder;
import io.quarkus.tls.runtime.config.TlsConfig;
import io.quarkus.vertx.deployment.VertxBuildItem;

public class CertificatesProcessor {

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
public void initializeCertificate(
TlsConfig config, VertxBuildItem vertx, CertificateRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
List<TlsCertificateBuildItem> otherCertificates) {
recorder.validateCertificates(config, vertx.getVertx());
for (TlsCertificateBuildItem certificate : otherCertificates) {
recorder.register(certificate.name, certificate.supplier);
}

SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(Registry.class)
.supplier(recorder.getSupplier())
.scope(ApplicationScoped.class)
.unremovable()
.setRuntimeInit();

syntheticBeans.produce(configurator.done());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.tls;

import java.util.function.Supplier;

import io.quarkus.builder.item.MultiBuildItem;

/**
* A build item to register a TLS certificate.
*/
public final class TlsCertificateBuildItem extends MultiBuildItem {

public final String name;

public final Supplier<TlsConfiguration> supplier;

/**
* Create an instance of {@link TlsCertificateBuildItem} to register a TLS certificate.
* The certificate will be registered just after the regular TLS certificate configuration is registered.
*
* @param name the name of the certificate, cannot be {@code null}, cannot be {@code <default>}
* @param supplier the supplier providing the TLS configuration, must not return {@code null}
*/
public TlsCertificateBuildItem(String name, Supplier<TlsConfiguration> supplier) {
this.name = name;
this.supplier = supplier;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.quarkus.tls;

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

import java.security.KeyStore;
import java.util.function.Consumer;
import java.util.function.Supplier;

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.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.test.QuarkusUnitTest;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = {
@Certificate(name = "test-registration", password = "password", formats = Format.PKCS12)
})
public class BuildTimeRegistrationTest {

private static final String configuration = """
# no configuration by default
""";

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

@Inject
Registry registry;

@Test
void testBuildTimeRegistration() {
TlsConfiguration conf = registry.get("named").orElseThrow();
assertThat(conf.getKeyStore()).isNotNull();
assertThat(conf.getTrustStore()).isNotNull();
}

static Consumer<BuildChainBuilder> buildCustomizer() {
return new Consumer<BuildChainBuilder>() {
@Override
public void accept(BuildChainBuilder builder) {
builder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
TlsCertificateBuildItem item = new TlsCertificateBuildItem("named", new MyCertificateSupplier());
context.produce(item);
}
})
.produces(TlsCertificateBuildItem.class)
.build();
}
};
}

public static class MyCertificateSupplier implements Supplier<TlsConfiguration> {

@Override
public TlsConfiguration get() {
try {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(getClass().getResourceAsStream("target/certs/test-registration-keystore.p12"),
"password".toCharArray());
KeyStore ts = KeyStore.getInstance("PKCS12");
ts.load(getClass().getResourceAsStream("target/certs/test-registration-truststore.p12"),
"password".toCharArray());
return new BaseTlsConfiguration() {
@Override
public KeyStore getKeyStore() {
return ks;
}

@Override
public KeyStore getTrustStore() {
return ts;
}
};
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.tls;

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

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;

public class CertificateRevocationListTest {

private static final String configuration = """
quarkus.tls.certificate-revocation-list=src/test/resources/revocations/revoked-cert.der
quarkus.tls.foo.certificate-revocation-list=src/test/resources/revocations/revoked-cert.der
""";

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

@Inject
Registry certificates;

@Test
void test() {
TlsConfiguration def = certificates.getDefault().orElseThrow();
TlsConfiguration foo = certificates.get("foo").orElseThrow();
assertThat(def.getSSLOptions().getCrlValues()).hasSize(1);
assertThat(foo.getSSLOptions().getCrlValues()).hasSize(1);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.quarkus.tls;

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

import java.security.KeyStoreException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;

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 me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

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

private static final String configuration = """
quarkus.tls.key-store.jks.path=target/certs/test-formats-keystore.jks
quarkus.tls.key-store.jks.password=password
quarkus.tls.trust-store.jks.path=target/certs/test-formats-truststore.jks
quarkus.tls.trust-store.jks.password=password
""";

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

@Inject
Registry certificates;

@Test
void test() throws KeyStoreException, CertificateParsingException {

TlsConfiguration def = certificates.getDefault().orElseThrow();

assertThat(def.getKeyStoreOptions()).isNotNull();
assertThat(def.getKeyStore()).isNotNull();

assertThat(def.getTrustStore()).isNotNull();
assertThat(def.getTrustStoreOptions()).isNotNull();

X509Certificate certificate = (X509Certificate) def.getKeyStore().getCertificate("test-formats");
assertThat(certificate).isNotNull();
assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> {
assertThat(l.get(0)).isEqualTo(2);
assertThat(l.get(1)).isEqualTo("localhost");
});

certificate = (X509Certificate) def.getTrustStore().getCertificate("test-formats");
assertThat(certificate).isNotNull();
assertThat(certificate.getSubjectAlternativeNames()).anySatisfy(l -> {
assertThat(l.get(0)).isEqualTo(2);
assertThat(l.get(1)).isEqualTo("localhost");
});
}
}
Loading

0 comments on commit 26eeba8

Please sign in to comment.