Android4系端末のTLS1.1&1.2対応について

# 各バージョンの対応状況
Androidの各バージョンにおいてTLSの対応状況は以下のようになっています。
![スクリーンショット 2018-02-03 15.09.47.png](https://qiita-image-store.s3.amazonaws.com/0/33071/1ec53a89-f5a3-4f71-6969-8807693cc707.png)
※ [Template:ウェブブラウザにおけるTLS/SSLの対応状況の変化](https://ja.wikipedia.org/wiki/Template:%E3%82%A6%E3%82%A7%E3%83%96%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8BTLS/SSL%E3%81%AE%E5%AF%BE%E5%BF%9C%E7%8A%B6%E6%B3%81%E3%81%AE%E5%A4%89%E5%8C%96)より

5.0以降については「[TLS / SSL のデフォルトの構成の変更](https://developer.android.com/about/versions/android-5.0-changes.html#ssl)」にも記載の通り、デフォルトで有効になっています。

Android4.0.4未満については非対応ですが、4.1から4.4.4については<b>規定で無効</b>となっています。本記事ではこれを有効にする方法について書いていきます。


参考：[SSLSocket：Default configuration for different Android versions](https://developer.android.com/reference/javax/net/ssl/SSLSocket.html)

# GooglePlayサービス経由でセキュリティプロバイダをアップデートする
公式の「[SSLの脆弱性攻撃から保護するためにセキュリティプロバイダをアップデートする](https://developer.android.com/training/articles/security-gms-provider.html?hl=ja)」へ記載の通り、

[GooglePlayサービス](https://developers.google.com/android/guides/overview)を利用している場合には、ApplicationクラスやActivityなどで

```java
ProviderInstaller.installIfNeeded(context);
```
のように実行することでセキュリティプロバイダのアップデートを行うことができます。(最大で350ミリ秒かかるとのことで、非同期での実行も可能です。)

アップデートを行うと、SSLSocketFactoryが最新のものになり、新しいプロトコルがデフォルトで有効になります。


参考：https://github.com/square/okhttp/issues/1934

この対応は、GooglePlayサービスが必須となることため、無い端末でもサポートしたい場合には、以下の方法で対応が可能です。

# SSLSocketFactoryを拡張して明示的に有効にする
明示的にTLSを有効にしたSSLSocketを利用することで、TLS1.1、1.2を有効にすることもできます。 

参考：https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/

SSLSocketFactoryを継承して、TLS1.1、TLS1.2を有効にする以外は元の処理を返すクラスを作成します。

```java
public class TLSSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory() 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;
    }
}
```
OkHttpClientで上記で作成したTLSSocketFactoryを利用します。[sslSocketFactoryの説明](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory-javax.net.ssl.X509TrustManager-)に

> Most applications should not call this method, and instead use the system defaults. Those classes include special optimizations that can be lost if the implementations are decorated.

とあり、可能な限りシステムのデフォルトを利用した方が良いようなので、以下のコードでは4.4.4以前のみを対象にしています。
※　minSDKが16前提の分岐になっています。


```java
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();

// 4.4.4以前のみで作成したsslSocketFactoryを使うようにする
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
    try {
        okHttpClientBuilder
                .connectionSpecs(Collections.singletonList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS).build()))
                .sslSocketFactory(new TLSSocketFactory(), getTrustManager());
    } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
        throw new RuntimeException(e);
    }
}

// その他の設定
okHttpClientBuilder
        .addInterceptor(new HttpLoggingInterceptor().setLevel(getLogLevel()));

OkHttpClient okHttpClient = okHttpClientBuilder.build();
```
この際、

```java
sslSocketFactory(SSLSocketFactory sslSocketFactory)
```

はdeprecatedなので、

```java
sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)
```

を利用するため、X509TrustManagerが必要になりますが、同[sslSocketFactoryの説明](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory-javax.net.ssl.X509TrustManager-)に記載の通りの記述で取得できます。

```java
private X509TrustManager getTrustManager() throws NoSuchAlgorithmException, KeyStoreException {
        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];
    }

```
参考：https://github.com/square/okhttp/issues/2372

# WebViewについて
Googleからパッチが提供されていない4.3未満のWebView(WebKit)については対応ができず、4.4以降のWebView(Chromium)に限定する必要があります。

補足として、5.0以降のWebViewはGoogle Playでアップデートされますが、4.4のWebViewはChromiumになったもののOS同梱なので、メーカーによってアップデート状況が異なる可能性があります。
4.4のChromeのデフォルトバージョンは30.0であり、TLS1.2については対応となっていますが、今後はアップデートされないものとして注意したほうが良さそうです。

参考：[WebView for Android : What version of Chrome is it based on?](https://developer.chrome.com/multidevice/webview/overview#what_version_of_chrome_is_it_based_on_)

![スクリーンショット 2018-02-25 0.14.46.png](https://qiita-image-store.s3.amazonaws.com/0/33071/52cf951c-79f7-8ce9-511c-e703c6eb2303.png)

# 結論
minSDK21にしたい！