Skip to content

Commit

Permalink
Merge pull request #1 from launchdarkly/pk/ch2199/enable-modern-tls
Browse files Browse the repository at this point in the history
Added SocketFactory that will ensure that modern TLS protocols are en…
  • Loading branch information
pkaeding authored May 5, 2017
2 parents 96a89fc + abd0384 commit 1f94e1b
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repositories {

allprojects {
group = 'com.launchdarkly'
version = "1.3.0"
version = "1.3.1-SNAPSHOT"
sourceCompatibility = 1.7
targetCompatibility = 1.7
}
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/launchdarkly/eventsource/EventSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
Expand Down Expand Up @@ -62,6 +68,12 @@ public class EventSource implements ConnectionHandler, Closeable {
.retryOnConnectionFailure(true)
.proxy(builder.proxy);

try {
clientBuilder.sslSocketFactory(new ModernTLSSocketFactory(), defaultTrustManager());
} catch (GeneralSecurityException e) {
// TLS is not available, so don't set up the socket factory, swallow the exception
}

if (builder.proxyAuthenticator != null) {
clientBuilder.proxyAuthenticator(builder.proxyAuthenticator);
}
Expand Down Expand Up @@ -119,6 +131,18 @@ public void close() throws IOException {
}
}

private X509TrustManager defaultTrustManager() throws GeneralSecurityException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}

private void connect() {
Response response = null;
int reconnectAttempts = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.launchdarkly.eventsource;

import com.google.common.annotations.VisibleForTesting;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* An {@link SSLSocketFactory} that tries to ensure modern TLS versions are used.
*/
public class ModernTLSSocketFactory extends SSLSocketFactory {
private static final String TLS_1_2 = "TLSv1.2";
private static final String TLS_1_1 = "TLSv1.1";
private static final String TLS_1 = "TLSv1";

private SSLSocketFactory defaultSocketFactory;

ModernTLSSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
this.defaultSocketFactory = context.getSocketFactory();
}

@Override
public String[] getDefaultCipherSuites() {
return this.defaultSocketFactory.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return this.defaultSocketFactory.getSupportedCipherSuites();
}

@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
return setModernTlsVersionsOnSocket(this.defaultSocketFactory.createSocket(socket, s, i, b));
}

@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
return setModernTlsVersionsOnSocket(this.defaultSocketFactory.createSocket(s, i));
}

@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
return setModernTlsVersionsOnSocket(this.defaultSocketFactory.createSocket(s, i, inetAddress, i1));
}

@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
return setModernTlsVersionsOnSocket(this.defaultSocketFactory.createSocket(inetAddress, i));
}

@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
return setModernTlsVersionsOnSocket(this.defaultSocketFactory.createSocket(inetAddress, i, inetAddress1, i1));
}

/**
* If either of TLSv1.2, TLSv1.1, or TLSv1 are supported, make them the only enabled protocols (listing in that order).
* <p>
* If the socket does not make these modern TLS protocols available at all, then just return the socket unchanged.
*
* @param s the socket
* @return
*/
@VisibleForTesting
static Socket setModernTlsVersionsOnSocket(Socket s) {
if (s != null && (s instanceof SSLSocket)) {
List<String> defaultEnabledProtocols = Arrays.asList(((SSLSocket) s).getSupportedProtocols());
ArrayList<String> newEnabledProtocols = new ArrayList<>();
if (defaultEnabledProtocols.contains(TLS_1_2)) {
newEnabledProtocols.add(TLS_1_2);
}
if (defaultEnabledProtocols.contains(TLS_1_1)) {
newEnabledProtocols.add(TLS_1_1);
}
if (defaultEnabledProtocols.contains(TLS_1)) {
newEnabledProtocols.add(TLS_1);
}
if (newEnabledProtocols.size() > 0) {
((SSLSocket) s).setEnabledProtocols(newEnabledProtocols.toArray(new String[newEnabledProtocols.size()]));
}
}
return s;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.launchdarkly.eventsource;

import org.junit.Test;

import javax.net.ssl.SSLSocket;
import java.net.Socket;

import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

public class ModernTLSSocketFactoryTest {

@Test
public void allModernTLSVersionsAddedWhenAvailable() {
SSLSocket s = mock(SSLSocket.class);
when(s.getSupportedProtocols())
.thenReturn(new String[]{"SSL", "SSLv2", "SSLv3", "TLS", "TLSv1", "TLSv1.1", "TLSv1.2"});

ModernTLSSocketFactory.setModernTlsVersionsOnSocket(s);
verify(s).setEnabledProtocols(eq(new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}));
}

@Test
public void oneModernTLSVersionsAddedWhenAvailable() {
SSLSocket s = mock(SSLSocket.class);
when(s.getSupportedProtocols())
.thenReturn(new String[]{"SSL", "SSLv2", "SSLv3", "TLS", "TLSv1"});

ModernTLSSocketFactory.setModernTlsVersionsOnSocket(s);
verify(s).setEnabledProtocols(eq(new String[]{"TLSv1"}));
}

@Test
public void enabledProtocolsUntouchedWhenNoModernProtocolsAvailable() {
SSLSocket s = mock(SSLSocket.class);
when(s.getSupportedProtocols())
.thenReturn(new String[]{"SSL", "SSLv2", "SSLv3", "TLS"});

ModernTLSSocketFactory.setModernTlsVersionsOnSocket(s);
verify(s, never()).setEnabledProtocols(new String[]{});
}
}

0 comments on commit 1f94e1b

Please sign in to comment.