Android 4系(API16-19)のTLS1.1, 1.2対応

TL; DR

  1. API16-19はデフォルトでTLS1.1, 1.2が有効になってないので適宜ONにしてやる
  2. TLS1.1, 12を有効にしたとて、強いCipher suitesが使えるかは別問題

知ってる人は知っている。知らない人は覚えてね。

前口上

さて、iOS 9からTLS1.2が必須になったのは記憶に新しい。このタイミングで社内のAPIサーバの設定が変わって芋づる式に対応に追われたAndroiderも少なくないはずだ。

本件、僕自身もすぐ忘れるのでAndroid 4系(API16-19)のTLS1.1, 1.2対応について改めてまとめておきたい。

Default configuration for different Android versions に書いてあることが全てなんだけど、AndroidAPIレベルとSSL/TLSの関係は次のとおりだ。

  • SSLv3…使うな
  • TLSv1…API 1からサポート、API 1から有効
  • TLSv1.1…API 16からサポート、API 20から有効
  • TLSv1.2…API 16からサポート、API 20から有効

TLS 1.0で検索すると、各社サポートを打ち切る方針を打ち出していることが分かる。

このタイミングで最低でもサポートするAPIレベルを16を下限としたいところだ。

サーバの設定を確認

さて、TLS 1.0を切ると決めたらApacheなりNginxなりでサポートするTLSのバージョンを設定するはずだ。ここではそれについては解説しない。ここではcurlコマンドでちゃんと設定が正しくなされているか確認してみる。

MacOSデフォルトのcurlはOpenSSLが組み込まれてないので再インストールする。

 

% brew install --with-openssl curl

% brew link curl --force

% cat<<'EOS'>>~/.zshrc
export CURL_HOME="/usr/local/opt/curl"
export PATH="$CURL_HOME/bin:$PATH"
EOS

% source ~/.zshrc

% curl --version | grep -i openssl
curl 7.57.0 (x86_64-apple-darwin16.7.0) libcurl/7.57.0 OpenSSL/1.0.2n zlib/1.2.8

 

これでOK。

curl -I --tlsv1.2 -s -v "https://your_server"

こんな感じで --tlsv1.1, --tlsv1.10 でそれぞれ試してみよう。

サポートしていないバージョンで

error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

みたいになればOK。

Android側でどうするか

で、Android側でどうするかが関心事だと思う。

ここではOkHttpをHTTPクライアントに使った場合に絞る。

 

まず、SSLSocketFactoryを拡張して、TLS 1.1, 1.2を有効にする以外はほとんど処理を元のクラスにdelegateするだけのファクトリクラスを作る。

private static 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;
}
}

 

次に、X509TrustManagerを取得する処理をコピペする。

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));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
return trustManager;
}

 

あとはいつもどおり。

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS).build();
OkHttpClient okHttpClient;
try {
okHttpClient = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.sslSocketFactory(new TLSSocketFactory(), getTrustManager())
.build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException(e);
}
return okHttpClient;

 

これでこのOkHttpClientを使ったHTTPリクエスト/レスポンスはTLS1.1, 1.2を使って行われる。

 

なお、Builder#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.

と明記されているので、この部分を自前で書き換える場合はパフォーマンス上の不利に留意しつつ、 Build.VERSION.SDK_INT で条件式でくくったりする方が良さそうだ。

Cipher suites

冒頭で書いたように、TLS 1.1, 1.2に対応することと、充分に強度のあるCipher suitesを使えることは別問題である。

サーバ側で低APIレベルでは対応していないCipher suitesしか許容しない設定にした場合、本記事の内容を適用してもSSL Handshakeに(当然ながら)失敗する。


SSLSocket | Android Developers

を見て、どのCipher suitesを許容するかAPIチームと相談しつつ、最低APIレベルをどこで妥協するか決めるのが望ましそうだ。以上。

 

参考リンク