Skip to content

Commit

Permalink
Add support for SNI (Server Name Indication) for the HTTP server.
Browse files Browse the repository at this point in the history
With SNI enabled, the server can handle multiple certificates. During the TLS handshake, the client indicates the service name allowing the service to looks for the correct certificate and completes the handshake.

The JKS and PKCS12 format allow the key stores to contain multiple certificates. However, the PEM format does not allow that. For this reason, the configuration of the PEM certificate and key are now accepting lists of paths. The previous (singular) form are deprecated but still supported.

SNI must be enabled explicitly as the server cannot verify if the key stores contain multiple certificates (JKS / PKCS12).

This commit does not enable SNI on gRPC, as the gRPC server does not handle it. See vert-x3/vertx-grpc#70 for details.

Fix quarkusio#16851
  • Loading branch information
cescoffier committed May 7, 2021
1 parent d7048f3 commit 272119d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Enable SSL, configure the key store
quarkus.http.ssl.certificate.file=server-cert.pem
quarkus.http.ssl.certificate.key-file=server-key.pem
quarkus.http.ssl.certificate.files=server-cert.pem
quarkus.http.ssl.certificate.key-files=server-key.pem
# Test that server starts with this option
# See https://github.com/quarkusio/quarkus/issues/8336
quarkus.http.insecure-requests=disabled
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.vertx.http.runtime;

import java.nio.file.Path;
import java.util.List;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
Expand All @@ -10,19 +11,43 @@
* A certificate configuration. Either the certificate and key files must be given, or a key store must be given.
*/
@ConfigGroup
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class CertificateConfig {

/**
* The file path to a server certificate or certificate chain in PEM format.
*
* @deprecated Use {@link #files} instead.
*/
@ConfigItem
@Deprecated
public Optional<Path> file;

/**
* The list of path to server certificates using the PEM format.
* Specifying multiple files require SNI to be enabled.
*/
@ConfigItem
public Optional<List<Path>> files;

/**
* The file path to the corresponding certificate private key file in PEM format.
*
* @deprecated Use {@link #keyFiles} instead.
*/
@ConfigItem
@Deprecated
public Optional<Path> keyFile;

/**
* The list of path to server certificates private key file using the PEM format.
* Specifying multiple files require SNI to be enabled.
*
* The order of the key files must match the order of the certificates.
*/
@ConfigItem
public Optional<List<Path>> keyFiles;

/**
* An optional key store which holds the certificate information instead of specifying separate files.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ public class ServerSslConfig {
@ConfigItem(defaultValue = "TLSv1.3,TLSv1.2")
public List<String> protocols;

/**
* Enables Server Name Indication (SNI), an TLS extension allowing the server to use multiple certificates.
* The client indicate the server name during the TLS handshake, allowing the server to select the right certificate.
*/
@ConfigItem(defaultValue = "false")
public boolean sni;

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigInstantiator;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.runtime.configuration.MemorySize;
import io.quarkus.runtime.shutdown.ShutdownConfig;
import io.quarkus.vertx.core.runtime.VertxCoreRecorder;
Expand Down Expand Up @@ -578,9 +579,24 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC
}

ServerSslConfig sslConfig = httpConfiguration.ssl;
//TODO: static fields break config

final Optional<Path> certFile = sslConfig.certificate.file;
final Optional<Path> keyFile = sslConfig.certificate.keyFile;
final List<Path> keys = new ArrayList<>();
final List<Path> certificates = new ArrayList<>();
if (sslConfig.certificate.keyFiles.isPresent()) {
keys.addAll(sslConfig.certificate.keyFiles.get());
}
if (sslConfig.certificate.files.isPresent()) {
certificates.addAll(sslConfig.certificate.files.get());
}
if (keyFile.isPresent()) {
keys.add(keyFile.get());
}
if (certFile.isPresent()) {
certificates.add(certFile.get());
}

final Optional<Path> keyStoreFile = sslConfig.certificate.keyStoreFile;
final String keystorePassword = sslConfig.certificate.keyStorePassword;
final Optional<Path> trustStoreFile = sslConfig.certificate.trustStoreFile;
Expand All @@ -599,8 +615,8 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC
serverOptions.setMaxFormAttributeSize(httpConfiguration.limits.maxFormAttributeSize.asBigInteger().intValueExact());
setIdleTimeout(httpConfiguration, serverOptions);

if (certFile.isPresent() && keyFile.isPresent()) {
createPemKeyCertOptions(certFile.get(), keyFile.get(), serverOptions);
if (!certificates.isEmpty() && !keys.isEmpty()) {
createPemKeyCertOptions(certificates, keys, serverOptions);
} else if (keyStoreFile.isPresent()) {
final Path keyStorePath = keyStoreFile.get();
final Optional<String> keyStoreFileType = sslConfig.certificate.keyStoreFileType;
Expand Down Expand Up @@ -649,6 +665,7 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC
}
}
serverOptions.setSsl(true);
serverOptions.setSni(sslConfig.sni);
serverOptions.setHost(httpConfiguration.host);
serverOptions.setPort(httpConfiguration.determineSslPort(launchMode));
serverOptions.setClientAuth(buildTimeConfig.tlsClientAuth);
Expand Down Expand Up @@ -676,13 +693,30 @@ private static byte[] getFileContent(Path path) throws IOException {
return data;
}

private static void createPemKeyCertOptions(Path certFile, Path keyFile,
private static void createPemKeyCertOptions(List<Path> certFile, List<Path> keyFile,
HttpServerOptions serverOptions) throws IOException {
final byte[] cert = getFileContent(certFile);
final byte[] key = getFileContent(keyFile);

if (certFile.size() != keyFile.size()) {
throw new ConfigurationException("Invalid certificate configuration - `files` and `keyFiles` must have the "
+ "same number of elements");
}

List<Buffer> certificates = new ArrayList<>();
List<Buffer> keys = new ArrayList<>();

for (Path p : certFile) {
final byte[] cert = getFileContent(p);
certificates.add(Buffer.buffer(cert));
}

for (Path p : keyFile) {
final byte[] key = getFileContent(p);
keys.add(Buffer.buffer(key));
}

PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
.setCertValue(Buffer.buffer(cert))
.setKeyValue(Buffer.buffer(key));
.setCertValues(certificates)
.setKeyValues(keys);
serverOptions.setPemKeyCertOptions(pemKeyCertOptions);
}

Expand Down

0 comments on commit 272119d

Please sign in to comment.