diff --git a/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java b/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java index 39f40738..bf39e11c 100644 --- a/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java +++ b/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java @@ -1,11 +1,14 @@ package com.limelight.nvstream.http; +import android.os.Build; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; +import java.net.InetAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.URI; @@ -33,6 +36,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; @@ -343,7 +348,16 @@ public class NvHTTP { try { SSLContext sc = SSLContext.getInstance("TLS"); sc.init(new KeyManager[] { keyManager }, new TrustManager[] { trustManager }, new SecureRandom()); - return client.newBuilder().sslSocketFactory(sc.getSocketFactory(), trustManager).build(); + + // TLS 1.2 is not enabled by default prior to Android 5.0, so we'll need a custom + // SSLSocketFactory in order to connect to GFE 3.20.4 which requires TLSv1.2 or later. + // We don't just always use TLSv12SocketFactory because explicitly specifying TLS versions + // prevents later TLS versions from being negotiated even if client and server otherwise + // support them. + return client.newBuilder().sslSocketFactory( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? + sc.getSocketFactory() : new TLSv12SocketFactory(sc), + trustManager).build(); } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new RuntimeException(e); } @@ -727,4 +741,62 @@ public class NvHTTP { return true; } + + // Based on example code from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/ + private static class TLSv12SocketFactory extends SSLSocketFactory { + private SSLSocketFactory internalSSLSocketFactory; + + public TLSv12SocketFactory(SSLContext context) { + internalSSLSocketFactory = context.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return internalSSLSocketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return internalSSLSocketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket()); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); + } + + private Socket enableTLSv12OnSocket(Socket socket) { + if (socket instanceof SSLSocket) { + // TLS 1.2 is not enabled by default prior to Android 5.0. We must enable it + // explicitly to ensure we can communicate with GFE 3.20.4 which blocks TLS 1.0. + ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"}); + } + return socket; + } + } }