テスト用に通信鯖をandroid内部に立てたい(SSL対応)の試行錯誤
関連記事のまとめ
- debugする時に追加している記述の備忘録(1) - exception think
- debugする時に追加している記述の備忘録(2) - exception think
- debugする時に追加している記述の備忘録(3) - exception think
今回は
MockWebServerで、httpsのレスポンスをテストしたい為の試行錯誤メモです。
残念ながら解決はできていません。とりあえず現状の状況を書き出してみる感じかと
なぜSSL鯖のテストをしたいのか
にも絡む話なのですが、Appleの方針で、通信に関してSSLを使うことを推奨しています。
ただ各社苦戦している所もやはり多いようで、現在のところリジェクトする対応までは
現時点では保留延長されている状況のようです*1
でどこらへんが問題になるのか?
- https状況下での
- ページのリダイレクト
- JSの実行
等のようですね。でできれば事前にココらへんのテストもしたいわけです
実際の試行状態
実装コード
public class DebugHogeApplication extends HogeApplication { private MockWebServer server; //〜中略〜 // 詳細は debugする時に追加している記述の備忘録(2)/(3) 参照 private boolean useHttps = true; private void initServer(){ server = new MockWebServer(); //SSLの初期化設定 if(useHttps){ //★ 今回追加 useHttps = setUseHttps(); } Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(final RecordedRequest request) throws InterruptedException { if (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } MockResponse res = new MockResponse() .addHeader("Content-Type", "application/json") .addHeader("charset", "utf-8") .setResponseCode(200); MockResponse resB = new MockResponse() .addHeader("Content-Type", "image/png") .setResponseCode(200); String path = request.getPath(); String BASE_MAIN_URL = RestUtil.createBaseUrl().toString(); if (path.indexOf(BASE_MAIN_URL + "/hoge") != -1) { return res.setBody(readJson("hoge.json")); } if (path.indexOf(BASE_MAIN_URL + "/fuga") != -1) { return res.setBody(readJson("fuga.json")); } if (path.indexOf(BASE_MAIN_URL + "/maiu") != -1) { MockResponse resB = new MockResponse() .addHeader("Content-Type", "image/png") .setResponseCode(200); return resB.setBody(readBinary("sample.png")); } return new MockResponse().setResponseCode(404); } }; server.setDispatcher(dispatcher); try{ server.start(); if(!useHttps){ //★ 今回追加 RestUtil.BASE_SCHEME = RestUtil.SCHEME_HTTP; } RestUtil.BASE_HOST = server.getHostName(); RestUtil.BASE_PORT = server.getPort(); }catch(Exception ex){ shutdown(); server = null; } } private boolean setUseHttps(){ try { AssetManager assetManager = getApplicationContext().getResources().getAssets(); char[] serverKeyStorePassword = "android".toCharArray(); InputStream stream = assetManager.open("mystore.bks"); KeyStore serverKeyStore = KeyStore.getInstance("BKS"); serverKeyStore.load(stream, serverKeyStorePassword); String kmfAlgoritm = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgoritm); kmf.init(serverKeyStore, serverKeyStorePassword); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(kmfAlgoritm); trustManagerFactory.init(serverKeyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); SSLSocketFactory sf = sslContext.getSocketFactory(); server.useHttps(sf, false); return true; } catch (Exception e) { Log.e(TAG,"setUseHttps",e); } return false; }
assetに置く 証明書の作成
crazybob.org: Android: Trusting SSL certificates を読みながら
以下のshで作成
- mystore.bks
- app/debug/asset に配置
export CLASSPATH=`pwd`/bcprov-jdk15on-1.56.jar CERTSTORE=mystore.bks if [ -a $CERTSTORE ]; then rm $CERTSTORE || exit 1 fi keytool \ -import \ -v \ -trustcacerts \ -alias 0 \ -file <(openssl x509 -in mycert.pem) \ -keystore $CERTSTORE \ -storetype BKS \ -provider org.bouncycastle.jce.provider.BouncyCastleProvider \ -providerpath `pwd`/bcprov-jdk15on-1.56.jar \ -storepass android
実行結果
- MockWebServerのuseHttps 自体は成功
- Clientサイドから上記のローカル鯖にアクセスしようとすると下記のエラーが発生する
- OS5/OS6 で確認
javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x7f7beb3f80: Failure in SSL library, usually a protocol error error:100c543e:SSL routines:ssl3_read_bytes:TLSV1_ALERT_INAPPROPRIATE_FALLBACK (external/boringssl/src/ssl/s3_pkt.c:972 0x7f66a3ce20:0x00000001)
- 考えられるのは、
- 下記のページのクライアント側のOkhttp3 Clientを加工していないから?
- でもできれば実装コードはあまりいじりたくないなorz
比較的な話
のnanohttp もSSL対応できるっぽいんだけど、SSLの立て方とはちがうんだよな・・・
という所で詰まってしまっている感じだったりします
GitHub - NanoHttpd/nanohttpd: Tiny, easily embeddable HTTP server in Java. のreadme.mdに書いてある
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass android -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999
で作るキーだと、JKSの証明書はAndroidにはないのでエラーになるんですよね・・*4
上記の記載例は、普通にPC on Gradle でnanohttpの鯖を立ち上げる設定なのだろうか・・
2/22追加検証
の 不明の認証局 のあたり
みて再チャレンジしてみたけど、やっぱり駄目だった・・*5
うーん。どうしたらいいんだろう?知見者のアドバイスがほしい。。
private boolean setUseHttps(){ try { AssetManager assetManager = getApplicationContext().getResources().getAssets(); char[] serverKeyStorePassword = "android".toCharArray(); /* InputStream stream = assetManager.open("mystore.bks"); KeyStore serverKeyStore = KeyStore.getInstance("BKS"); serverKeyStore.load(stream, serverKeyStorePassword); */ //変更箇所 InputStream stream = assetManager.open("server.der.crt"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); // From https://www.washington.edu/itconnect/security/ca/load-der.crt InputStream caInput = new BufferedInputStream(stream); Certificate ca; try { ca = cf.generateCertificate(caInput); System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); } finally { caInput.close(); } // Create a KeyStore containing our trusted CAs String keyStoreType = KeyStore.getDefaultType(); KeyStore serverKeyStore = KeyStore.getInstance(keyStoreType); serverKeyStore.load(null, null); serverKeyStore.setCertificateEntry("ca", ca); String kmfAlgoritm = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgoritm); kmf.init(serverKeyStore, serverKeyStorePassword); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(kmfAlgoritm); trustManagerFactory.init(serverKeyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); SSLSocketFactory sf = sslContext.getSocketFactory(); server.useHttps(sf, false); return true; } catch (Exception e) { Log.e(TAG,"setUseHttps",e); } return false; }