Android
TLS

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

各バージョンの対応状況

Androidの各バージョンにおいてTLSの対応状況は以下のようになっています。
スクリーンショット 2018-02-03 15.09.47.png
Template:ウェブブラウザにおけるTLS/SSLの対応状況の変化より

5.0以降については「TLS / SSL のデフォルトの構成の変更」にも記載の通り、デフォルトで有効になっています。

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

参考:SSLSocket:Default configuration for different Android versions

GooglePlayサービス経由でセキュリティプロバイダをアップデートする

公式の「SSLの脆弱性攻撃から保護するためにセキュリティプロバイダをアップデートする」へ記載の通り、

GooglePlayサービスを利用している場合には、ApplicationクラスやActivityなどで

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を有効にする以外は元の処理を返すクラスを作成します。

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の説明

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前提の分岐になっています。

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();

この際、

sslSocketFactory(SSLSocketFactory sslSocketFactory)

はdeprecatedなので、

sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)

を利用するため、X509TrustManagerが必要になりますが、同sslSocketFactoryの説明に記載の通りの記述で取得できます。

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?

スクリーンショット 2018-02-25 0.14.46.png

結論

minSDK21にしたい!