AndroidアプリケーションのSSL通信をプロキシで解析する(2)

0x00. はじめに


前回の記事ではURLConnectionクラスを使ったアクセスについて調査した。今回はSSLSocketFactoryを使い、生のSSLソケットを生成する場合について同様のアプローチを用いる。

0x01. SSLSocketを用いてSSLウェブサイトにアクセス


一般的に、AndroidアプリケーションからHTTPやHTTPSを使ったアクセスを行いたい場合、URLConnectionクラスを用いると思われる。しかしより詳細なコントロールを行いたい場合には、SSLSocketクラスのインスタンスを生成することになるだろう。例えば次のようなコードを使えば、ベリサイン(日本)のウェブサイトにアクセスすることができる。

private String socketTest()
{
try
{
SocketFactory sf = SSLSocketFactory.getDefault();
Socket s = sf.createSocket( "www.verisign.co.jp", 443 );
s.getOutputStream().write( "GET / HTTP/1.0\r\nHost: www.verisign.co.jp\r\n\r\n".getBytes() );
byte[] buf = new byte[ 1024 * 10 ];
s.getInputStream().read( buf );
s.close();
String res = new String( buf );
Log.i( TAG, res );
return res.substring( 0, 30 );
}
catch( Exception e )
{
return logException( e );
}
}

前回と同様に、エミュレータ起動時のコマンドラインオプションでHTTP(HTTPS)プロキシを使うように指定し、Doormanなどのローカルプロキシを経由させる場合、このコードは証明書が不正であるとし、例外を送出してしまう。スタックトレースは以下のようになる。

03-08 02:10:02.704: INFO/HA(6902): javax.net.ssl.SSLException: Not trusted server certificate
03-08 02:10:02.704: INFO/HA(6902):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:371)
03-08 02:10:02.704: INFO/HA(6902):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLOutputStream.(OpenSSLSocketImpl.java:564)
03-08 02:10:02.704: INFO/HA(6902):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getOutputStream(OpenSSLSocketImpl.java:479)
03-08 02:10:02.704: INFO/HA(6902):     at net.jumperz.app.android.test1.HelloActivity.socketTest(HelloActivity.java:66)
03-08 02:10:02.704: INFO/HA(6902):     at net.jumperz.app.android.test1.HelloActivity.onKey(HelloActivity.java:109)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.View.dispatchKeyEvent(View.java:3735)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:788)
03-08 02:10:02.704: INFO/HA(6902):     at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1667)
03-08 02:10:02.704: INFO/HA(6902):     at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1102)
03-08 02:10:02.704: INFO/HA(6902):     at android.app.Activity.dispatchKeyEvent(Activity.java:2063)
03-08 02:10:02.704: INFO/HA(6902):     at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1643)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewRoot.deliverKeyEventToViewHierarchy(ViewRoot.java:2471)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2441)
03-08 02:10:02.704: INFO/HA(6902):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1735)
03-08 02:10:02.704: INFO/HA(6902):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-08 02:10:02.704: INFO/HA(6902):     at android.os.Looper.loop(Looper.java:123)
03-08 02:10:02.704: INFO/HA(6902):     at android.app.ActivityThread.main(ActivityThread.java:4627)
03-08 02:10:02.704: INFO/HA(6902):     at java.lang.reflect.Method.invokeNative(Native Method)
03-08 02:10:02.704: INFO/HA(6902):     at java.lang.reflect.Method.invoke(Method.java:521)
03-08 02:10:02.704: INFO/HA(6902):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
03-08 02:10:02.704: INFO/HA(6902):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
03-08 02:10:02.704: INFO/HA(6902):     at dalvik.system.NativeStart.main(Native Method)
03-08 02:10:02.704: INFO/HA(6902): Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: TrustAnchor for CertPath not found.
03-08 02:10:02.704: INFO/HA(6902):     at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:168)
03-08 02:10:02.704: INFO/HA(6902):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:366)
03-08 02:10:02.704: INFO/HA(6902):     ... 24 more
03-08 02:10:02.704: INFO/HA(6902): Caused by: java.security.cert.CertPathValidatorException: TrustAnchor for CertPath not found.
03-08 02:10:02.704: INFO/HA(6902):     at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(PKIXCertPathValidatorSpi.java:149)
03-08 02:10:02.704: INFO/HA(6902):     at java.security.cert.CertPathValidator.validate(CertPathValidator.java:202)
03-08 02:10:02.704: INFO/HA(6902):     at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:164)
03-08 02:10:02.704: INFO/HA(6902):     ... 25 more

0x02. 書き換え


今回はわかりやすい箇所としてTrustManagerImplクラスのcheckServerTrustedメソッドを選択する。このメソッドは戻り値を持たないメソッドであり、証明書の検証を行う。もし証明書が正しいと判断できない場合には例外を送出するという動作をする。そこで、このメソッドを何もしない実装に書き換えてしまう。

143     public void checkServerTrusted(X509Certificate[] chain, String authType)
144             throws CertificateException {
145             }

145行目以降をばっさり削除し、このように中身のない関数にする。対象となるファイルは、Froyoでは ./dalvik/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.javaとなる。

makeを行えば、SSLSocketクラスからのアクセスについて証明書を一切検証しないDalvikを載せたFroyoが出来上がる。コマンドラインオプションでプロキシを使うように指定すれば、先述したコードからプロキシを通してベリサインのウェブサイトにアクセスすることが可能となる。