Skip to content

Commit

Permalink
Merge pull request #360 from Hakky54/make-sslcontext-customizable
Browse files Browse the repository at this point in the history
Make SSLContext customizable and consistent with SSLSocket, SSLSocketFactory, SSLServerSocketFactory and SSLEngine
  • Loading branch information
Hakky54 authored Jun 23, 2023
2 parents 05c8cea + 6c9aea6 commit fc1898f
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 13 deletions.
23 changes: 10 additions & 13 deletions sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import nl.altindag.ssl.model.KeyStoreHolder;
import nl.altindag.ssl.model.TrustManagerParameters;
import nl.altindag.ssl.model.internal.SSLMaterial;
import nl.altindag.ssl.sslcontext.FenixSSLContext;
import nl.altindag.ssl.trustmanager.trustoptions.TrustAnchorTrustOptions;
import nl.altindag.ssl.trustmanager.trustoptions.TrustStoreTrustOptions;
import nl.altindag.ssl.trustmanager.validator.ChainAndAuthTypeValidator;
Expand All @@ -31,7 +32,6 @@
import nl.altindag.ssl.util.SSLContextUtils;
import nl.altindag.ssl.util.SSLParametersUtils;
import nl.altindag.ssl.util.SSLSessionUtils;
import nl.altindag.ssl.util.SSLSocketUtils;
import nl.altindag.ssl.util.TrustManagerUtils;
import nl.altindag.ssl.util.internal.StringUtils;
import nl.altindag.ssl.util.internal.UriUtils;
Expand Down Expand Up @@ -99,11 +99,11 @@ public SSLContext getSslContext() {
}

public SSLSocketFactory getSslSocketFactory() {
return SSLSocketUtils.createSslSocketFactory(sslMaterial.getSslContext(), getSslParameters());
return sslMaterial.getSslContext().getSocketFactory();
}

public SSLServerSocketFactory getSslServerSocketFactory() {
return SSLSocketUtils.createSslServerSocketFactory(sslMaterial.getSslContext(), getSslParameters());
return sslMaterial.getSslContext().getServerSocketFactory();
}

public Optional<X509ExtendedKeyManager> getKeyManager() {
Expand Down Expand Up @@ -151,15 +151,11 @@ public SSLEngine getSSLEngine() {
}

public SSLEngine getSSLEngine(String peerHost, Integer peerPort) {
SSLEngine sslEngine;
if (nonNull(peerHost) && nonNull(peerPort)) {
sslEngine = sslMaterial.getSslContext().createSSLEngine(peerHost, peerPort);
return sslMaterial.getSslContext().createSSLEngine(peerHost, peerPort);
} else {
sslEngine = sslMaterial.getSslContext().createSSLEngine();
return sslMaterial.getSslContext().createSSLEngine();
}

sslEngine.setSSLParameters(getSslParameters());
return sslEngine;
}

public static Builder builder() {
Expand Down Expand Up @@ -826,7 +822,7 @@ public SSLFactory build() {

X509ExtendedKeyManager keyManager = isIdentityMaterialPresent() ? createKeyManager() : null;
X509ExtendedTrustManager trustManager = isTrustMaterialPresent() ? createTrustManager() : null;
SSLContext sslContext = SSLContextUtils.createSslContext(
SSLContext baseSslContext = SSLContextUtils.createSslContext(
keyManager,
trustManager,
secureRandom,
Expand All @@ -836,16 +832,17 @@ public SSLFactory build() {
);

if (sessionTimeoutInSeconds >= 0) {
SSLSessionUtils.updateSessionTimeout(sslContext, sessionTimeoutInSeconds);
SSLSessionUtils.updateSessionTimeout(baseSslContext, sessionTimeoutInSeconds);
}

if (sessionCacheSizeInBytes >= 0) {
SSLSessionUtils.updateSessionCacheSize(sslContext, sessionCacheSizeInBytes);
SSLSessionUtils.updateSessionCacheSize(baseSslContext, sessionCacheSizeInBytes);
}

sslParameters.setCipherSuites(ciphers.isEmpty() ? null : ciphers.stream().distinct().toArray(String[]::new));
sslParameters.setProtocols(protocols.isEmpty() ? null : protocols.stream().distinct().toArray(String[]::new));
SSLParameters baseSslParameters = SSLParametersUtils.merge(sslParameters, sslContext.getDefaultSSLParameters());
SSLParameters baseSslParameters = SSLParametersUtils.merge(sslParameters, baseSslContext.getDefaultSSLParameters());
SSLContext sslContext = new FenixSSLContext(baseSslContext, baseSslParameters);

SSLMaterial sslMaterial = new SSLMaterial.Builder()
.withSslContext(sslContext)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.sslcontext;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;

/**
* <strong>NOTE:</strong>
* Please don't use this class directly as it is part of the internal API. Class name and methods can be changed any time.
*
* @author Hakan Altindag
*/
public class FenixSSLContext extends SSLContext {

public FenixSSLContext(SSLContext baseSslContext, SSLParameters baseSslParameters) {
super(new FenixSSLContextSpi(baseSslContext, baseSslParameters), baseSslContext.getProvider(), baseSslContext.getProtocol());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.sslcontext;

import nl.altindag.ssl.util.SSLParametersUtils;
import nl.altindag.ssl.util.SSLSocketUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLContextSpi;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.security.SecureRandom;

import static java.util.Objects.nonNull;

/**
* @author Hakan Altindag
*/
class FenixSSLContextSpi extends SSLContextSpi {

private static final Logger LOGGER = LoggerFactory.getLogger(FenixSSLContextSpi.class);

private final SSLContext sslContext;
private final SSLParameters sslParameters;

FenixSSLContextSpi(SSLContext sslContext, SSLParameters sslParameters) {
this.sslContext = sslContext;
this.sslParameters = sslParameters;
}

@Override
protected void engineInit(KeyManager[] km, TrustManager[] tm, SecureRandom sr) {
LOGGER.debug("The provided parameters are being ignored as the SSLContext has already been initialized");
}

@Override
protected SSLSocketFactory engineGetSocketFactory() {
return SSLSocketUtils.createSslSocketFactory(sslContext, engineGetDefaultSSLParameters());
}

@Override
protected SSLServerSocketFactory engineGetServerSocketFactory() {
return SSLSocketUtils.createSslServerSocketFactory(sslContext, engineGetDefaultSSLParameters());
}

@Override
protected SSLEngine engineCreateSSLEngine() {
return getSSLEngine(null, 0);
}

@Override
protected SSLEngine engineCreateSSLEngine(String host, int port) {
return getSSLEngine(host, port);
}

private SSLEngine getSSLEngine(String peerHost, int peerPort) {
SSLEngine sslEngine;
if (nonNull(peerHost)) {
sslEngine = sslContext.createSSLEngine(peerHost, peerPort);
} else {
sslEngine = sslContext.createSSLEngine();
}

sslEngine.setSSLParameters(engineGetDefaultSSLParameters());
return sslEngine;
}

@Override
protected SSLSessionContext engineGetServerSessionContext() {
return sslContext.getServerSessionContext();
}

@Override
protected SSLSessionContext engineGetClientSessionContext() {
return sslContext.getClientSessionContext();
}

@Override
protected SSLParameters engineGetDefaultSSLParameters() {
return SSLParametersUtils.copy(sslParameters);
}

@Override
protected SSLParameters engineGetSupportedSSLParameters() {
return SSLParametersUtils.copy(sslContext.getSupportedSSLParameters());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ void executeHttpsRequestWithMutualAuthentication() throws IOException {
.withIdentityMaterial("keystore/client-server/server-one/identity.jks", "secret".toCharArray())
.withTrustMaterial("keystore/client-server/server-one/truststore.jks", "secret".toCharArray())
.withNeedClientAuthentication()
.withProtocols("TLSv1.3")
.build();

Server server = Server.createDefault(sslFactoryForServer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
Expand Down Expand Up @@ -1781,6 +1787,69 @@ void createSSLFactoryWithDummyTrustMaterial() {
assertThat(sslFactory.getTrustManager().get()).isInstanceOf(DummyX509ExtendedTrustManager.class);
}

@Test
void haveConsistentParameterConfiguration() throws IOException {
String configuredProtocol = "TLSv1.2";
String configuredCipher = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
boolean configuredNeedClientAuthentication = true;

SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(KEYSTORE_LOCATION + IDENTITY_FILE_NAME, IDENTITY_PASSWORD)
.withTrustMaterial(KEYSTORE_LOCATION + TRUSTSTORE_FILE_NAME, TRUSTSTORE_PASSWORD)
.withProtocols(configuredProtocol)
.withNeedClientAuthentication(configuredNeedClientAuthentication)
.withCiphers(configuredCipher)
.build();

SSLContext sslContext = sslFactory.getSslContext();
SSLParameters defaultSSLParameters = sslContext.getDefaultSSLParameters();
SSLParameters supportedSSLParameters = sslContext.getSupportedSSLParameters();

assertThat(defaultSSLParameters.getProtocols()).containsExactly(configuredProtocol);
assertThat(defaultSSLParameters.getCipherSuites()).containsExactly(configuredCipher);
assertThat(defaultSSLParameters.getNeedClientAuth()).isTrue();
assertThat(defaultSSLParameters.getWantClientAuth()).isFalse();

assertThat(supportedSSLParameters.getProtocols()).hasSizeGreaterThan(1).contains(configuredProtocol);
assertThat(supportedSSLParameters.getCipherSuites()).hasSizeGreaterThan(1).contains(configuredCipher);
assertThat(supportedSSLParameters.getNeedClientAuth()).isFalse();
assertThat(supportedSSLParameters.getWantClientAuth()).isFalse();

SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
assertThat(sslSocketFactory.getDefaultCipherSuites()).containsExactly(configuredCipher);
assertThat(sslSocketFactory.getSupportedCipherSuites()).containsExactly(configuredCipher);

SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket();
assertThat(socket.getEnabledProtocols()).containsExactly(configuredProtocol);
assertThat(socket.getSupportedProtocols()).hasSizeGreaterThan(1).contains(configuredProtocol);
assertThat(socket.getEnabledCipherSuites()).containsExactly(configuredCipher);
assertThat(socket.getSupportedCipherSuites()).hasSizeGreaterThan(1).contains(configuredCipher);
assertThat(socket.getNeedClientAuth()).isTrue();
assertThat(socket.getWantClientAuth()).isFalse();
socket.close();

SSLServerSocketFactory sslServerSocketFactory = sslFactory.getSslServerSocketFactory();
assertThat(sslSocketFactory.getDefaultCipherSuites()).containsExactly(configuredCipher);
assertThat(sslSocketFactory.getSupportedCipherSuites()).containsExactly(configuredCipher);

SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket();
assertThat(serverSocket.getEnabledProtocols()).containsExactly(configuredProtocol);
assertThat(serverSocket.getSupportedProtocols()).hasSizeGreaterThan(1).contains(configuredProtocol);
assertThat(serverSocket.getEnabledCipherSuites()).containsExactly(configuredCipher);
assertThat(serverSocket.getSupportedCipherSuites()).hasSizeGreaterThan(1).contains(configuredCipher);
assertThat(serverSocket.getNeedClientAuth()).isTrue();
assertThat(serverSocket.getWantClientAuth()).isFalse();
serverSocket.close();

SSLEngine sslEngine = sslFactory.getSSLEngine();
assertThat(sslEngine.getEnabledProtocols()).containsExactly(configuredProtocol);
assertThat(sslEngine.getSupportedProtocols()).hasSizeGreaterThan(1).contains(configuredProtocol);
assertThat(sslEngine.getEnabledCipherSuites()).containsExactly(configuredCipher);
assertThat(sslEngine.getSupportedCipherSuites()).hasSizeGreaterThan(1).contains(configuredCipher);
assertThat(sslEngine.getNeedClientAuth()).isTrue();
assertThat(sslEngine.getWantClientAuth()).isFalse();
}

@Test
void throwIllegalArgumentExceptionWhenCertificateIsAbsent() {
List<Certificate> certificates = Collections.emptyList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.sslcontext;

import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

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

@Test
void ignoreProvidedParametersAndDebugLogWhenEngineInitIsBeingCalled() {
LogCaptor logCaptor = LogCaptor.forClass(FenixSSLContextSpi.class);

FenixSSLContextSpi sslContextSpi = new FenixSSLContextSpi(null, null);
sslContextSpi.engineInit(null, null, null);

assertThat(logCaptor.getDebugLogs())
.containsExactly("The provided parameters are being ignored as the SSLContext has already been initialized");
}

}

0 comments on commit fc1898f

Please sign in to comment.