diff options
author | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2022-12-19 16:04:48 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2023-01-06 11:33:59 +0100 |
commit | 6e162af9a091d2ac1c229281c47349e46d6c8239 (patch) | |
tree | 7acb73d5a41283608bd07d96e3db7b8b56f87eca /http-utils | |
parent | 7d839355259eca823da9396c1ed15b43f7c98768 (diff) |
Ensure that HTTPS clients only use allowed ciphers and protocol versions
Diffstat (limited to 'http-utils')
6 files changed, 140 insertions, 21 deletions
diff --git a/http-utils/src/main/java/ai/vespa/util/http/AcceptAllHostnamesVerifier.java b/http-utils/src/main/java/ai/vespa/util/http/AcceptAllHostnamesVerifier.java new file mode 100644 index 00000000000..77d718bccb3 --- /dev/null +++ b/http-utils/src/main/java/ai/vespa/util/http/AcceptAllHostnamesVerifier.java @@ -0,0 +1,21 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.util.http; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +/** + * @author bjorncs + */ +public class AcceptAllHostnamesVerifier implements HostnameVerifier { + + private static final AcceptAllHostnamesVerifier INSTANCE = new AcceptAllHostnamesVerifier(); + + public static AcceptAllHostnamesVerifier instance() { return INSTANCE; } + + private AcceptAllHostnamesVerifier() {} + + @Override public boolean verify(String hostname, SSLSession session) { return true; } + +} + diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc4/SslConnectionSocketFactory.java b/http-utils/src/main/java/ai/vespa/util/http/hc4/SslConnectionSocketFactory.java new file mode 100644 index 00000000000..16449a72524 --- /dev/null +++ b/http-utils/src/main/java/ai/vespa/util/http/hc4/SslConnectionSocketFactory.java @@ -0,0 +1,54 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.util.http.hc4; + +import ai.vespa.util.http.AcceptAllHostnamesVerifier; +import com.yahoo.security.tls.TlsContext; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import java.util.Collection; + +import static com.yahoo.security.tls.TlsContext.getAllowedCipherSuites; +import static com.yahoo.security.tls.TlsContext.getAllowedProtocols; + +/** + * Provides {@link SSLConnectionSocketFactory} that applies protocol restrictions from {@link TlsContext}. + * + * @author bjorncs + */ +public class SslConnectionSocketFactory { + private SslConnectionSocketFactory() {} + + public static SSLConnectionSocketFactory of(SSLContext ctx, HostnameVerifier verifier) { + return new SSLConnectionSocketFactory(ctx, protocols(ctx), cipherSuites(ctx), verifier); + } + + public static SSLConnectionSocketFactory of(SSLContext ctx) { return of(ctx, defaultVerifier()); } + + public static SSLConnectionSocketFactory of(TlsContext ctx, HostnameVerifier verifier) { + return new SSLConnectionSocketFactory( + ctx.context(), ctx.parameters().getProtocols(), ctx.parameters().getCipherSuites(), verifier); + } + + public static SSLConnectionSocketFactory of(SSLSocketFactory fac, HostnameVerifier verifier) { + return new SSLConnectionSocketFactory(fac, protocols(), cipherSuites(), verifier); + } + + public static SSLConnectionSocketFactory of() { + return new SSLConnectionSocketFactory(TlsContext.defaultSslContext(), protocols(), cipherSuites(), defaultVerifier()); + } + + public static SSLConnectionSocketFactory of(TlsContext ctx) { return of(ctx, defaultVerifier()); } + + public static HostnameVerifier defaultVerifier() { return SSLConnectionSocketFactory.getDefaultHostnameVerifier(); } + + public static HostnameVerifier noopVerifier() { return AcceptAllHostnamesVerifier.instance(); } + + private static String[] cipherSuites(SSLContext ctx) { return array(getAllowedCipherSuites(ctx)); } + private static String[] protocols(SSLContext ctx) { return array(getAllowedProtocols(ctx)); } + private static String[] cipherSuites() { return array(getAllowedCipherSuites()); } + private static String[] protocols() { return array(getAllowedProtocols()); } + private static String[] array(Collection<String> c) { return c.toArray(String[]::new); } +} diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc4/VespaHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/hc4/VespaHttpClientBuilder.java index 953abcb04bc..af01b123a27 100644 --- a/http-utils/src/main/java/ai/vespa/util/http/hc4/VespaHttpClientBuilder.java +++ b/http-utils/src/main/java/ai/vespa/util/http/hc4/VespaHttpClientBuilder.java @@ -29,6 +29,8 @@ import java.net.InetAddress; import java.util.logging.Level; import java.util.logging.Logger; +import static ai.vespa.util.http.hc4.SslConnectionSocketFactory.noopVerifier; + /** * Http client builder for internal Vespa communications over http/https. * @@ -101,9 +103,8 @@ public class VespaHttpClientBuilder { } } - private static SSLConnectionSocketFactory createSslSocketFactory(TlsContext tlsContext) { - SSLParameters parameters = tlsContext.parameters(); - return new SSLConnectionSocketFactory(tlsContext.context(), parameters.getProtocols(), parameters.getCipherSuites(), new NoopHostnameVerifier()); + private static SSLConnectionSocketFactory createSslSocketFactory(TlsContext ctx) { + return SslConnectionSocketFactory.of(ctx, noopVerifier()); } private static Registry<ConnectionSocketFactory> createRegistry(SSLConnectionSocketFactory sslSocketFactory) { diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc5/DefaultHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/hc5/DefaultHttpClientBuilder.java index 8866d67fd60..8575bc16ee8 100644 --- a/http-utils/src/main/java/ai/vespa/util/http/hc5/DefaultHttpClientBuilder.java +++ b/http-utils/src/main/java/ai/vespa/util/http/hc5/DefaultHttpClientBuilder.java @@ -1,19 +1,12 @@ package ai.vespa.util.http.hc5; -import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; -import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpHeaders; -import org.apache.hc.core5.util.Timeout; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.time.Duration; -import java.util.Map; import java.util.function.Supplier; /** @@ -34,14 +27,12 @@ public class DefaultHttpClientBuilder { /** Creates an HTTP client builder with the given SSL context, and using the provided timeouts for requests where config is not overridden. */ public static HttpClientBuilder create(Supplier<SSLContext> sslContext, HostnameVerifier verifier, String userAgent) { + SSLContext ctx = sslContext.get(); + var factory = ctx == null ? SslConnectionSocketFactory.of(verifier) : SslConnectionSocketFactory.of(ctx, verifier); return HttpClientBuilder.create() .setConnectionManager(PoolingHttpClientConnectionManagerBuilder .create() - .setSSLSocketFactory(SSLConnectionSocketFactoryBuilder - .create() - .setSslContext(sslContext.get()) - .setHostnameVerifier(verifier) - .build()) + .setSSLSocketFactory(factory) .build()) .setUserAgent(userAgent) .disableCookieManagement() diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc5/SslConnectionSocketFactory.java b/http-utils/src/main/java/ai/vespa/util/http/hc5/SslConnectionSocketFactory.java new file mode 100644 index 00000000000..7ba408c260b --- /dev/null +++ b/http-utils/src/main/java/ai/vespa/util/http/hc5/SslConnectionSocketFactory.java @@ -0,0 +1,57 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.util.http.hc5; + +import ai.vespa.util.http.AcceptAllHostnamesVerifier; +import com.yahoo.security.tls.TlsContext; +import org.apache.hc.client5.http.ssl.HttpsSupport; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +import java.util.Collection; + +import static com.yahoo.security.tls.TlsContext.getAllowedCipherSuites; +import static com.yahoo.security.tls.TlsContext.getAllowedProtocols; + +/** + * Provides {@link SSLConnectionSocketFactory} that applies protocol restrictions from {@link TlsContext}. + * + * @author bjorncs + */ +public class SslConnectionSocketFactory { + private SslConnectionSocketFactory() {} + + public static SSLConnectionSocketFactory of(SSLContext ctx, HostnameVerifier verifier) { + return new SSLConnectionSocketFactory(ctx, protocols(ctx), cipherSuites(ctx), verifier); + } + + public static SSLConnectionSocketFactory of(SSLContext ctx) { return of(ctx, defaultVerifier()); } + + public static SSLConnectionSocketFactory of(TlsContext ctx, HostnameVerifier verifier) { + return new SSLConnectionSocketFactory( + ctx.context(), ctx.parameters().getProtocols(), ctx.parameters().getCipherSuites(), verifier); + } + + public static SSLConnectionSocketFactory of(TlsContext ctx) { return of(ctx, defaultVerifier()); } + + public static SSLConnectionSocketFactory of(SSLSocketFactory fac, HostnameVerifier verifier) { + return new SSLConnectionSocketFactory(fac, protocols(), cipherSuites(), verifier); + } + + public static SSLConnectionSocketFactory of(HostnameVerifier verifier) { + return of(TlsContext.defaultSslContext(), verifier); + } + + public static HostnameVerifier defaultVerifier() { return HttpsSupport.getDefaultHostnameVerifier(); } + + public static HostnameVerifier noopVerifier() { return AcceptAllHostnamesVerifier.instance(); } + + private static String[] cipherSuites(SSLContext ctx) { return array(getAllowedCipherSuites(ctx)); } + private static String[] protocols(SSLContext ctx) { return array(getAllowedProtocols(ctx)); } + private static String[] cipherSuites() { return array(getAllowedCipherSuites()); } + private static String[] protocols() { return array(getAllowedProtocols()); } + private static String[] array(Collection<String> c) { return c.toArray(String[]::new); } + +} diff --git a/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java index 52f7ad9b56b..a33c4c119c2 100644 --- a/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java +++ b/http-utils/src/main/java/ai/vespa/util/http/hc5/VespaHttpClientBuilder.java @@ -12,7 +12,6 @@ import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.RegistryBuilder; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLParameters; import static com.yahoo.security.tls.MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER; import static com.yahoo.security.tls.TransportSecurityUtils.getInsecureMixedMode; @@ -65,11 +64,7 @@ public class VespaHttpClientBuilder { private static void addSslSocketFactory(HttpClientBuilder builder, HttpClientConnectionManagerFactory connectionManagerFactory, HostnameVerifier hostnameVerifier) { getSystemTlsContext().ifPresent(tlsContext -> { - SSLParameters parameters = tlsContext.parameters(); - SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(tlsContext.context(), - parameters.getProtocols(), - parameters.getCipherSuites(), - hostnameVerifier); + SSLConnectionSocketFactory socketFactory = SslConnectionSocketFactory.of(tlsContext, hostnameVerifier); builder.setConnectionManager(connectionManagerFactory.create(createRegistry(socketFactory))); // Workaround that allows re-using https connections, see https://stackoverflow.com/a/42112034/1615280 for details. // Proper solution would be to add a request interceptor that adds a x500 principal as user token, |