Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowed cipher suite can now be specified for WebServer and WebClient #3144

Merged
merged 4 commits into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
* Copyright (c) 2019, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,6 +28,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
Expand Down Expand Up @@ -149,6 +150,9 @@ Optional<SslContext> sslContext() {
if (webClientTls.trustAll()) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}
if (!webClientTls.allowedCipherSuite().isEmpty()) {
sslContextBuilder.ciphers(webClientTls.allowedCipherSuite());
}

sslContext = sslContextBuilder.build();
}
Expand All @@ -159,8 +163,9 @@ Optional<SslContext> sslContext() {
}

private SslContext nettySslFromJavaNet(SSLContext javaNetContext) {
Set<String> allowedCipherSuite = webClientTls.allowedCipherSuite();
return new JdkSslContext(
javaNetContext, true, null,
javaNetContext, true, allowedCipherSuite.isEmpty() ? null : allowedCipherSuite,
IdentityCipherSuiteFilter.INSTANCE, null,
ClientAuth.OPTIONAL, null, false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
* Copyright (c) 2020, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import javax.net.ssl.SSLContext;

Expand All @@ -37,6 +38,7 @@ public class WebClientTls {
private final PrivateKey clientPrivateKey;
private final List<X509Certificate> certificates;
private final List<X509Certificate> clientCertificateChain;
private final Set<String> allowedCipherSuite;
private final SSLContext sslContext;

private WebClientTls(Builder builder) {
Expand All @@ -46,6 +48,7 @@ private WebClientTls(Builder builder) {
this.clientPrivateKey = builder.clientPrivateKey;
this.clientCertificateChain = builder.clientCertificateChain;
this.sslContext = builder.sslContext;
this.allowedCipherSuite = builder.allowedCipherSuite;
}

/**
Expand Down Expand Up @@ -115,6 +118,15 @@ Optional<SSLContext> sslContext() {
return Optional.ofNullable(sslContext);
}

/**
* Allowed cipher suite.
*
* @return allowed cipher suite
*/
Set<String> allowedCipherSuite() {
return allowedCipherSuite;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -129,13 +141,19 @@ public boolean equals(Object o) {
&& Objects.equals(clientPrivateKey, that.clientPrivateKey)
&& Objects.equals(certificates, that.certificates)
&& Objects.equals(clientCertificateChain, that.clientCertificateChain)
&& Objects.equals(allowedCipherSuite, that.allowedCipherSuite)
&& Objects.equals(sslContext, that.sslContext);
}

@Override
public int hashCode() {
return Objects
.hash(trustAll, disableHostnameVerification, clientPrivateKey, certificates, clientCertificateChain, sslContext);
return Objects.hash(trustAll,
disableHostnameVerification,
clientPrivateKey,
certificates,
clientCertificateChain,
allowedCipherSuite,
sslContext);
}

/**
Expand All @@ -148,6 +166,7 @@ public static final class Builder implements io.helidon.common.Builder<WebClient
private PrivateKey clientPrivateKey;
private List<X509Certificate> certificates = new ArrayList<>();
private List<X509Certificate> clientCertificateChain = new ArrayList<>();
private Set<String> allowedCipherSuite = Set.of();
private SSLContext sslContext;

private Builder() {
Expand Down Expand Up @@ -211,6 +230,21 @@ public Builder sslContext(SSLContext sslContext) {
return this;
}

/**
* Set allowed cipher suite for the client.
*
* @param allowedCipherSuite cipher suite
* @return updated builder instance
*/
public Builder allowedCipherSuite(List<String> allowedCipherSuite) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a vararg method as well

Objects.requireNonNull(allowedCipherSuite, "Allowed cipher suite cannot be null");
if (allowedCipherSuite.isEmpty()) {
throw new IllegalStateException("Allowed cipher suite has to have at least one cipher specified");
}
this.allowedCipherSuite = Set.copyOf(allowedCipherSuite);
return this;
}

/**
* Configure a metric from configuration.
* The following configuration key are used:
Expand All @@ -237,6 +271,11 @@ public Builder sslContext(SSLContext sslContext) {
* <td>Trust store which contains trusted certificates. If set, replaces those present by default</td>
* </tr>
* <tr>
* <td>server.cipher-suite</td>
* <td>{@code no default}</td>
* <td>List of allowed ciphers. If set, replaces those present by default</td>
* </tr>
* <tr>
* <td>client.keystore</td>
* <td>{@code no default}</td>
* <td>Client key store name/location</td>
Expand All @@ -251,6 +290,7 @@ public Builder config(Config config) {
serverConfig.get("disable-hostname-verification").asBoolean().ifPresent(this::disableHostnameVerification);
serverConfig.get("trust-all").asBoolean().ifPresent(this::trustAll);
serverConfig.as(KeyConfig::create).ifPresent(this::certificateTrustStore);
serverConfig.get("cipher-suite").asList(String.class).ifPresent(this::allowedCipherSuite);

config.get("client").as(KeyConfig::create).ifPresent(this::clientKeyStore);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
* Copyright (c) 2020, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@
package io.helidon.webclient;

import java.util.Optional;
import java.util.Set;

import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
Expand All @@ -40,6 +41,7 @@ public void sslDefaults() {
assertThat(webClientTls.clientCertificateChain().size(), is(0));
assertThat(webClientTls.clientPrivateKey(), is(Optional.empty()));
assertThat(webClientTls.sslContext(), is(Optional.empty()));
assertThat(webClientTls.allowedCipherSuite(), is(Set.of()));
}

//@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -133,7 +134,7 @@ class NettyWebServer implements WebServer {

ServerBootstrap bootstrap = new ServerBootstrap();

SslContext sslContext = createSslContext(soConfig.ssl(), soConfig.enabledSslProtocols(), soConfig.clientAuth());
SslContext sslContext = soConfig.tls().map(this::createSslContext).orElse(null);

if (soConfig.backlog() > 0) {
bootstrap.option(ChannelOption.SO_BACKLOG, soConfig.backlog());
Expand All @@ -159,9 +160,11 @@ class NettyWebServer implements WebServer {
}
}

private SslContext createSslContext(SSLContext context, Set<String> enabledProtocols, ClientAuthentication clientAuth) {
private SslContext createSslContext(WebServerTls webServerTls) {
// Transform java SSLContext into Netty SslContext
SSLContext context = webServerTls.sslContext();
if (context != null) {
Collection<String> enabledProtocols = webServerTls.enabledTlsProtocols();
String[] protocols;
if (enabledProtocols.isEmpty()) {
protocols = null;
Expand All @@ -181,10 +184,11 @@ private SslContext createSslContext(SSLContext context, Set<String> enabledProto
ApplicationProtocolNames.HTTP_1_1);
}

Set<String> cipherSuite = webServerTls.cipherSuite();
return new JdkSslContext(
context, false, null,
context, false, cipherSuite.isEmpty() ? null : cipherSuite,
IdentityCipherSuiteFilter.INSTANCE, appProtocolConfig,
clientAuth.nettyClientAuth(), protocols, false);
webServerTls.clientAuth().nettyClientAuth(), protocols, false);
}
return null;
}
Expand Down Expand Up @@ -439,14 +443,15 @@ public void updateTls(WebServerTls tls) {

@Override
public void updateTls(WebServerTls tls, String socketName) {
Objects.requireNonNull(tls, "Tls could not be updated. WebServerTls is required to be non-null");
HttpInitializer httpInitializer = initializers.get(socketName);
if (httpInitializer == null) {
throw new IllegalStateException("Unknown socket name: " + socketName);
} else {
if (!tls.enabled()) {
throw new IllegalStateException("Tls could not be updated. WebServerTls is required to be enabled");
}
SslContext context = createSslContext(tls.sslContext(), new HashSet<>(tls.enabledTlsProtocols()), tls.clientAuth());
SslContext context = createSslContext(tls);
httpInitializer.updateSslContext(context);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.net.InetAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -72,6 +71,11 @@ public Set<String> enabledSslProtocols() {
return socketConfig.enabledSslProtocols();
}

@Override
public Set<String> allowedCipherSuite() {
return socketConfig.allowedCipherSuite();
}

@Override
public ClientAuthentication clientAuth() {
return socketConfig.clientAuth();
Expand Down Expand Up @@ -107,6 +111,11 @@ public int receiveBufferSize() {
return socketConfig.receiveBufferSize();
}

@Override
public Optional<WebServerTls> tls() {
return socketConfig.tls();
}

@Override
public int maxHeaderSize() {
return socketConfig.maxHeaderSize();
Expand Down Expand Up @@ -174,11 +183,9 @@ static class SocketConfig implements SocketConfiguration {
private final int backlog;
private final int timeoutMillis;
private final int receiveBufferSize;
private final SSLContext sslContext;
private final Set<String> enabledSslProtocols;
private final WebServerTls webServerTls;
private final String name;
private final boolean enabled;
private final ClientAuthentication clientAuth;
private final int maxHeaderSize;
private final int maxInitialLineLength;
private final int maxChunkSize;
Expand All @@ -205,17 +212,8 @@ static class SocketConfig implements SocketConfiguration {
this.initialBufferSize = builder.initialBufferSize();
this.enableCompression = builder.enableCompression();
this.maxPayloadSize = builder.maxPayloadSize();

WebServerTls webServerTls = builder.tlsConfig();
if (webServerTls.enabled()) {
this.sslContext = webServerTls.sslContext();
this.enabledSslProtocols = new HashSet<>(webServerTls.enabledTlsProtocols());
this.clientAuth = webServerTls.clientAuth();
} else {
this.sslContext = null;
this.enabledSslProtocols = Set.of();
this.clientAuth = ClientAuthentication.NONE;
}
this.webServerTls = webServerTls.enabled() ? webServerTls : null;
}

@Override
Expand Down Expand Up @@ -243,19 +241,29 @@ public int receiveBufferSize() {
return receiveBufferSize;
}

@Override
public Optional<WebServerTls> tls() {
return Optional.ofNullable(webServerTls);
}

@Override
public SSLContext ssl() {
return sslContext;
return tls().map(WebServerTls::sslContext).orElse(null);
}

@Override
public Set<String> enabledSslProtocols() {
return enabledSslProtocols;
return tls().map(WebServerTls::enabledTlsProtocols).map(Set::copyOf).orElseGet(Set::of);
}

@Override
public Set<String> allowedCipherSuite() {
return tls().map(WebServerTls::cipherSuite).orElseGet(Set::of);
}

@Override
public ClientAuthentication clientAuth() {
return clientAuth;
return tls().map(WebServerTls::clientAuth).orElse(ClientAuthentication.NONE);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,10 @@ public interface ServerConfiguration extends SocketConfiguration {
* Additional named server socket configuration is accessible through
* the {@link #socket(String)} and {@link #sockets()} methods.
*
* @deprecated use {@code tls().sslContext()} instead. This method will be removed at 3.0.0 version.
* @return a SSL context to use
*/
@Deprecated(since = "2.3.1", forRemoval = true)
@Override
SSLContext ssl();

Expand Down
Loading