如何为 Exoplayer enable/support TLS 1.1,1.2?

How to enable/support TLS 1.1,1.2 for Exoplayer?

错误: SSL 握手中止:ssl=0x676a5680:SSL 库失败,通常是协议错误

根据这个 Android doc, TLS 1.1 and 1.2 is supported on API 16+ but not enabled by default until API 20+. I found some solutions(here, here, here and here) for enabling TLS 1.1 and 1.2 support for OkHttp. How can I enable the TLS 1.1/1.2 support for Exoplayer? The only post I found for Exoplayer TLS 1.1/1.2 support is from this github issue 建议在这里提问。

"07-27 13:21:09.817 8925-9065/com.ftsgps.monarch E/ExoPlayerImplInternal: Source error. com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: Unable to connect to https://liveStream/LIVE-0089000D05/manifest.mpd at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:194) at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:147) at com.google.android.exoplayer2.upstream.DataSourceInputStream.checkOpened(DataSourceInputStream.java:102) at com.google.android.exoplayer2.upstream.DataSourceInputStream.open(DataSourceInputStream.java:65) at com.google.android.exoplayer2.upstream.ParsingLoadable.load(ParsingLoadable.java:129) at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:308) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) at java.lang.Thread.run(Thread.java:841) Caused by: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x722c3af8: Failure in SSL library, usually a protocol error error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744 0x689d8f10:0x00000000)"

This is happening only below API 21 version (lollipop). Server is using TLS1.2 protocol, which isn't support on Android below Lollipop version.

DefaultHttpDataSource 使用 HttpsURLConnection,它有一个默认的静态字段 SSLSocketFactoryHttpsURLConnection 的所有新实例都将分配此默认值 SSLSocketFactory,除非在实例上调用 setSSLSocketFactory()。所以从技术上讲,如果您在 实例化 DefaultHttpsDataSource 之前调用设置默认值 SSLSocketFactory ,它应该可以工作:

HttpsURLConnection.setDefaultSSLSocketFactory(new MyCustomSSLSocketFactory());

其中 MyCustomSSLSocketFactoy 可能看起来像这样:

class MyCustomSSLSocketFactory extends SSLSocketFactory {

    private javax.net.ssl.SSLSocketFactory internalSSLSocketFactory;

    public MyCustomSSLSocketFactory () throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);
        internalSSLSocketFactory = context.getSocketFactory();
    }

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

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

    @Override
    public Socket createSocket() throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
    }

    private Socket enableTLSOnSocket(Socket socket) {
        if(socket != null && (socket instanceof SSLSocket)) {
            ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
        }
        return socket;
    }
}

但是请记住,这可能会在意想不到的地方改变您的应用程序的行为(极不可能,但谨慎一点也无妨),为了避免这种情况,您可以在使用后将默认的 SSLSocketFactory 恢复为旧的DefaultHttpDataSource.

不过,还有一个更靠谱的方案。 您可以使用 OkHttpDataSource ,您可以在构造函数中传递 OkHttpClient 实例。这个 OkHttpClient 实例可以配置为使用我们的自定义 SSLSocketFactory。它看起来像这样:

okhttpclient.sslSocketFactory(new MyCustomSSLSocketFactory());
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter,
    new OkHttpDataSource(okHttpClient, userAgent, null, bandwidthMeter));

经过一番挖掘,我所要做的就是使用 ProviderInstaller 在应用程序 class 中启用 TLS1.2。我已经通过提供不接受 TLS1.0 的视频内容的服务器对其进行了验证,并且有效。

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        try {
          // Google Play will install latest OpenSSL 
          ProviderInstaller.installIfNeeded(getApplicationContext());
          SSLContext sslContext;
          sslContext = SSLContext.getInstance("TLSv1.2");
          sslContext.init(null, null, null);
          sslContext.createSSLEngine();
        } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException
            | NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
    }
}

参考:

https://guides.codepath.com/android/Using-OkHttp#enabling-tls-v1-2-on-older-devices