Skip to content

Commit

Permalink
Added support for excluding ciphers and protocols (#433)
Browse files Browse the repository at this point in the history
* Added methods for excluding ciphers and protocols

* Added test for fallback ciphers and protocols

* Added test for base merge function
  • Loading branch information
Hakky54 authored Jan 12, 2024
1 parent 01e02ba commit 5c7a885
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ public static class Builder {
private final Map<String, List<URI>> preferredAliasToHost = new HashMap<>();
private final List<String> protocols = new ArrayList<>();
private final List<String> ciphers = new ArrayList<>();
private final List<String> excludedProtocols = new ArrayList<>();
private final List<String> excludedCiphers = new ArrayList<>();

private boolean swappableKeyManagerEnabled = false;
private boolean swappableTrustManagerEnabled = false;
Expand Down Expand Up @@ -685,6 +687,11 @@ public Builder withCiphers(String... ciphers) {
return this;
}

public Builder withExcludedCiphers(String... ciphers) {
this.excludedCiphers.addAll(Arrays.asList(ciphers));
return this;
}

public Builder withSystemPropertyDerivedCiphers() {
ciphers.addAll(extractPropertyValues("https.cipherSuites"));
return this;
Expand All @@ -695,6 +702,11 @@ public Builder withProtocols(String... protocols) {
return this;
}

public Builder withExcludedProtocols(String... protocols) {
this.excludedProtocols.addAll(Arrays.asList(protocols));
return this;
}

public Builder withSystemPropertyDerivedProtocols() {
protocols.addAll(extractPropertyValues("https.protocols"));
return this;
Expand Down Expand Up @@ -927,7 +939,7 @@ private SSLParameters createSslParameters(SSLContext sslContext) {
sslParameters.setCipherSuites(preferredCiphers);
sslParameters.setProtocols(preferredProtocols);

return SSLParametersUtils.merge(sslParameters, defaultSSLParameters);
return SSLParametersUtils.merge(sslParameters, defaultSSLParameters, excludedCiphers, excludedProtocols);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
package nl.altindag.ssl.util;

import javax.net.ssl.SSLParameters;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -41,6 +44,10 @@ public static SSLParameters copy(SSLParameters source) {
}

public static SSLParameters merge(SSLParameters baseSslParameters, SSLParameters alternativeSslParameters) {
return merge(baseSslParameters, alternativeSslParameters, Collections.emptyList(), Collections.emptyList());
}

public static SSLParameters merge(SSLParameters baseSslParameters, SSLParameters alternativeSslParameters, List<String> excludedCiphers, List<String> excludedProtocols) {
SSLParameters target = new SSLParameters();

String[] ciphers = Optional.ofNullable(baseSslParameters.getCipherSuites())
Expand All @@ -50,6 +57,26 @@ public static SSLParameters merge(SSLParameters baseSslParameters, SSLParameters
.filter(array -> array.length != 0)
.orElseGet(alternativeSslParameters::getProtocols);

if (!excludedCiphers.isEmpty()) {
ciphers = Arrays.stream(ciphers)
.filter(cipher -> !excludedCiphers.contains(cipher))
.toArray(String[]::new);

if (ciphers.length == 0) {
ciphers = alternativeSslParameters.getCipherSuites();
}
}

if (!excludedProtocols.isEmpty()) {
protocols = Arrays.stream(protocols)
.filter(cipher -> !excludedProtocols.contains(cipher))
.toArray(String[]::new);

if (protocols.length == 0) {
protocols = alternativeSslParameters.getProtocols();
}
}

target.setCipherSuites(ciphers);
target.setProtocols(protocols);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,91 @@ void returnSpecifiedCiphers() {
.containsExactlyInAnyOrder("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384");
}

@Test
void returnWithExcludedCiphers() {
SSLFactory sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.build();

assertThat(sslFactory.getCiphers()).contains("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");

sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.withExcludedCiphers("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384")
.build();

assertThat(sslFactory.getCiphers())
.isNotEmpty()
.doesNotContainSequence("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");
}

@Test
void returnWithDefaultCiphersWhenAllIsExcluded() {
SSLFactory sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.build();

assertThat(sslFactory.getCiphers()).contains("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");

sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.withExcludedCiphers(
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV"
).build();

assertThat(sslFactory.getCiphers())
.isNotEmpty()
.contains("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");
}

@Test
void returnSpecifiedCiphersAndProtocolsWithinSslParameters() {
SSLFactory sslFactory = SSLFactory.builder()
Expand Down Expand Up @@ -1635,6 +1720,42 @@ void returnSpecifiedProtocols() {
assertThat(sslFactory.getProtocols()).contains("TLSv1.2");
}

@Test
void returnWithExcludedProtocols() {
SSLFactory sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.build();

assertThat(sslFactory.getProtocols()).contains("TLSv1.2");

sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.withExcludedProtocols("TLSv1.2")
.build();

assertThat(sslFactory.getProtocols())
.isNotEmpty()
.doesNotContainSequence("TLSv1.2");
}

@Test
void returnWithDefaultProtocolsWhenAllIsExcluded() {
SSLFactory sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.build();

assertThat(sslFactory.getProtocols()).contains("TLSv1.2");

sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.withExcludedProtocols("TLSv1.2", "TLSv1.3", "TLSv1.1")
.build();

assertThat(sslFactory.getProtocols())
.isNotEmpty()
.contains("TLSv1.2");
}

@Test
void returnSpecifiedNeedClientAuthenticationWithoutOptions() {
SSLFactory sslFactory = SSLFactory.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2019 Thunderberry.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nl.altindag.ssl.util;

import nl.altindag.ssl.SSLFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.net.ssl.SSLParameters;

import static org.mockito.Mockito.spy;

/**
* @author Hakan Altindag
*/
class SSLParametersUtilsShould {

@Test
void useBaseSslParametersIfItIsFilledWithData() {
SSLParameters baseSslParameters = spy(
new SSLParameters(
new String[]{"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"},
new String[]{"TLSv1.2"}
)
);

SSLFactory sslFactory = SSLFactory.builder()
.withDummyTrustMaterial()
.build();

SSLParameters mergedParameters = SSLParametersUtils.merge(baseSslParameters, sslFactory.getSslParameters());

Assertions.assertThat(mergedParameters.getCipherSuites()).containsExactly("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");
Assertions.assertThat(mergedParameters.getProtocols()).containsExactly("TLSv1.2");
}

}

0 comments on commit 5c7a885

Please sign in to comment.