1001001

73。CTFのWrite-upや技術的な備忘録を書きとめたいです。

【Cordova】Androidのリリースビルドで通信エラー

Apache Cordovaを使ったハイブリッドアプリ開発にて,Androidのapkのリリースビルドをしたところ,開発用では動いていたアプリが通信エラーを吐くようになった.その原因と対策についてメモ.

環境

  • Cordova 7.0.1
  • cordova-android@6.2.3

症状

  • cordova build android --releaseにてリリース用のapkをビルドした後,jarsignerで署名したapkをadb installで実機にインストールして起動したところ,通信エラーが発生し,サーバ側で用意していたAPIへアクセスできなくなった.
  • デバッグ用のapkでは正常に通信ができた.

最初はAndroidManifest.xmlに,<uses-permission android:name="android.permission.INTERNET" /> を記入し忘れていた等の理由を疑ったのだが,どうやらそういうわけではなかった.

調査

stackoverflowでこんな質問を発見.
stackoverflow.com
これを読むと,

  • リリースビルドしたら通信エラーが出た
  • どうやらSSL通信におけるエラーが出ているとのことらしい

原因

今回の原因は,サーバの中間証明書がないことでした.
chunkynews.com

Androidのデフォルトのブラウザは中間証明書を確認できない場合,信頼できないページとしてユーザに判断を促すためのポップアップが表示されます.自己署名証明書(いわゆるオレオレ証明書)を使ったページを閲覧した時や期限切れの証明書を使っているSSLサイトを見るときに出るのと同様のものです.実機デバッグに使用していたAndroidで試してみたところ信頼できない証明書を使用している旨が表示されました.

CordovaのWebviewでも同様に,中間証明書が確認できないサイトへの通信をSSL通信エラーとするため,正常に通信ができなかったようです.
つまり,「デバッグモードでのビルドの場合は,まだユニークなサーバ証明書が用意できておらずワイルドカード証明書を使用していたり,自己署名証明書を使用している場合があることを考えてSSL通信エラーを無視できる.しかし,リリースビルドの場合は無視できないため通信エラーが出てしまった」ということのようです.

対処

推奨する対処

サーバに中間証明書を追加するのが適切な対処でしょう.レンタルサーバ等を使用している場合もそのレンタルサーバ会社のページに詳しく設定方法が載っていたりするのですぐにできる対処でもあるかと思います(サーバの再起動等は必要になりますが..).

一時的な対処

力技ですが,SSL通信エラー判定部分を書き換えて通信を続行させるという手もあります.
ivancevich.me

「今すぐに,サーバ証明書を用意できない・中間証明書を設定できないけど,どうしてもリリースビルドで実機でテストしたい」という場合や,「取り急ぎ通信エラーの原因が今回の件であることを確認したい」という場合に有効だと思います.

書き換えるファイルはproject/platforms/android/CordovaLib/src/org/apache/cordova/engine/配下にあり,

  • 4系以前のバージョンの場合はCordovaWebViewClient.java
  • 5系以降のバージョンの場合はSystemWebViewClient.java

となっているようです.以下のファイルがバージョン8.0.0のcordovaのSystemWebViewClient.javaになりますが,debug=falseの時はSSLエラーを返すことが分かります.

public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {

    final String packageName = parentEngine.cordova.getActivity().getPackageName();
    final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();

    ApplicationInfo appInfo;
    try {
        appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
        if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
            // debug = true
            handler.proceed();
            return;
        } else {
            // debug = false
            super.onReceivedSslError(view, handler, error);
        }
    } catch (NameNotFoundException e) {
        // When it doubt, lock it out!
        super.onReceivedSslError(view, handler, error);
    }
}

なので,debug=trueの処理に書き換えるとリリースビルドでも動きます.

if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
    // debug = true
    handler.proceed();
    return;
} else {
    // debug = false
    // コメントアウト
    // super.onReceivedSslError(view, handler, error);
    
    // 追記
    handler.proceed();
    return;
}

ただし,これをやるのはあくまで前述のような理由でリリースビルドでの実機テストをしたい場合に限ったほうが良いでしょう.

まとめ

デバッグモードでは正常に通信ができたのにリリースビルドにした途端通信エラーが出る症状の原因として,SSLサーバ証明書の中間証明書の設定不備が考えられます.サーバの中間証明書を適切に設定することで改善する場合があります.

参考

  • 記事中に適宜記載したもの