Android 4系(API16-19)のTLS1.1, 1.2対応
TL; DR
- API16-19はデフォルトでTLS1.1, 1.2が有効になってないので適宜ONにしてやる
- 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 に書いてあることが全てなんだけど、AndroidのAPIレベルと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
% 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レベルをどこで妥協するか決めるのが望ましそうだ。以上。
参考リンク
- https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
- https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
- https://github.com/square/okhttp/issues/2372
- https://github.com/square/okhttp/issues/1934