aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java')
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java138
1 files changed, 138 insertions, 0 deletions
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java b/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java
new file mode 100644
index 00000000000..90848f1dfd4
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java
@@ -0,0 +1,138 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl.impl;
+
+import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth;
+import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SslContextBuilder;
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.security.tls.AutoReloadingX509KeyManager;
+import com.yahoo.security.tls.TlsContext;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static com.yahoo.jdisc.http.ssl.impl.SslContextFactoryUtils.setEnabledCipherSuites;
+import static com.yahoo.jdisc.http.ssl.impl.SslContextFactoryUtils.setEnabledProtocols;
+
+/**
+ * An implementation of {@link SslContextFactoryProvider} that uses the {@link ConnectorConfig} to construct a {@link SslContextFactory}.
+ *
+ * @author bjorncs
+ */
+public class ConfiguredSslContextFactoryProvider implements SslContextFactoryProvider {
+
+ private volatile AutoReloadingX509KeyManager keyManager;
+ private final ConnectorConfig connectorConfig;
+
+ public ConfiguredSslContextFactoryProvider(ConnectorConfig connectorConfig) {
+ validateConfig(connectorConfig.ssl());
+ this.connectorConfig = connectorConfig;
+ }
+
+ @Override
+ public SslContextFactory getInstance(String containerId, int port) {
+ ConnectorConfig.Ssl sslConfig = connectorConfig.ssl();
+ if (!sslConfig.enabled()) throw new IllegalStateException();
+
+ SslContextBuilder builder = new SslContextBuilder();
+ if (sslConfig.certificateFile().isBlank() || sslConfig.privateKeyFile().isBlank()) {
+ PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(getPrivateKey(sslConfig));
+ List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(getCertificate(sslConfig));
+ builder.withKeyStore(privateKey, certificates);
+ } else {
+ keyManager = AutoReloadingX509KeyManager.fromPemFiles(Paths.get(sslConfig.privateKeyFile()), Paths.get(sslConfig.certificateFile()));
+ builder.withKeyManager(keyManager);
+ }
+ List<X509Certificate> caCertificates = getCaCertificates(sslConfig)
+ .map(X509CertificateUtils::certificateListFromPem)
+ .orElse(List.of());
+ builder.withTrustStore(caCertificates);
+
+ SSLContext sslContext = builder.build();
+
+ SslContextFactory.Server factory = new SslContextFactory.Server();
+ factory.setSslContext(sslContext);
+
+ factory.setNeedClientAuth(sslConfig.clientAuth() == ClientAuth.Enum.NEED_AUTH);
+ factory.setWantClientAuth(sslConfig.clientAuth() == ClientAuth.Enum.WANT_AUTH);
+
+ List<String> protocols = !sslConfig.enabledProtocols().isEmpty()
+ ? sslConfig.enabledProtocols()
+ : new ArrayList<>(TlsContext.getAllowedProtocols(sslContext));
+ setEnabledProtocols(factory, sslContext, protocols);
+
+ List<String> ciphers = !sslConfig.enabledCipherSuites().isEmpty()
+ ? sslConfig.enabledCipherSuites()
+ : new ArrayList<>(TlsContext.getAllowedCipherSuites(sslContext));
+ setEnabledCipherSuites(factory, sslContext, ciphers);
+
+ return factory;
+ }
+
+ @Override
+ public void close() {
+ if (keyManager != null) {
+ keyManager.close();
+ }
+ }
+
+ private static void validateConfig(ConnectorConfig.Ssl config) {
+ if (!config.enabled()) return;
+
+ if(hasBoth(config.certificate(), config.certificateFile()))
+ throw new IllegalArgumentException("Specified both certificate and certificate file.");
+
+ if(hasBoth(config.privateKey(), config.privateKeyFile()))
+ throw new IllegalArgumentException("Specified both private key and private key file.");
+
+ if(hasNeither(config.certificate(), config.certificateFile()))
+ throw new IllegalArgumentException("Specified neither certificate or certificate file.");
+
+ if(hasNeither(config.privateKey(), config.privateKeyFile()))
+ throw new IllegalArgumentException("Specified neither private key or private key file.");
+ }
+
+ private static boolean hasBoth(String a, String b) { return !a.isBlank() && !b.isBlank(); }
+ private static boolean hasNeither(String a, String b) { return a.isBlank() && b.isBlank(); }
+
+ private static Optional<String> getCaCertificates(ConnectorConfig.Ssl sslConfig) {
+ if (!sslConfig.caCertificate().isBlank()) {
+ return Optional.of(sslConfig.caCertificate());
+ } else if (!sslConfig.caCertificateFile().isBlank()) {
+ return Optional.of(readToString(sslConfig.caCertificateFile()));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ private static String getPrivateKey(ConnectorConfig.Ssl config) {
+ if(!config.privateKey().isBlank()) return config.privateKey();
+ return readToString(config.privateKeyFile());
+ }
+
+ private static String getCertificate(ConnectorConfig.Ssl config) {
+ if(!config.certificate().isBlank()) return config.certificate();
+ return readToString(config.certificateFile());
+ }
+
+ private static String readToString(String filename) {
+ try {
+ return Files.readString(Paths.get(filename), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+}