2013年7月26日金曜日

AndroidのSSL通信をキャプチャする方法

Burp ProxyCharlesなどのリバースプロキシを使いましょう。

リバースプロキシとは、外部サーバーへの接続を肩代わりするプロキシサーバーです。

http通信ならば、これらのプログラムを立ち上げればそれで通信をキャプチャすることができますが、SSL通信では一手間必要です。サーバーとクライアント間でお互いに承認した乱数(マスターシークレット)を用いて暗号化するため、中間に入って通信を傍受することはできないからです。

そこでリバースプロキシのCA証明書を入手し、端末に信頼できるルートCAとして登録する必要があります。

Charlesの場合、FAQにiPhone用の証明書ファイルへのリンクがありますので、Androidのブラウザでアクセスしてインストールします。iPhone用と書いてありますが、Androidでも問題なく使えます。

Burp Proxyの場合、Burp Proxyが生成した証明書を取得する必要があります。

Androidの標準ブラウザやChoromeでは証明書の取得を行うことができないので、適当なデスクトップ用ブラウザを仕様します。Burp Proxyを起動->ブラウザ(OS)でプロキシサーバを設定->ブラウザでhttpsのサイトへアクセス->ルート証明書をエクスポート(firefoxの場合、「接続の安全性を確認できません」という警告が出るので、「危険性を理解した上で接続するには」->「例外を追加...」をクリックします。「セキュリティ例外の追加」ダイアログが表示されるので、証明書の状態の「表示(V)...」をクリックすると、証明書の詳細が表示されます。ここでルート証明書となっているBurp Suite PortSwiggerを選択し出力します。拡張子が付いていない場合.cerを付けます)で、証明書を取得することができます。あとは証明書をメール等でAndroid端末に送付してインストールします。

CA証明書のインストールは、SDカードのルートに保存(adb push hoge.cer /mnt/sdcard)して、「設定」->「セキュリティ」->「SDカードからのインストール」からでもできます。

CA証明書のインストールができたら、Macの設定から「インターネット共有」をONにしてWifiスポットにし、AndroidのWifi接続先をMacにします。そしてWifi接続の設定からプロキシを手動で追加し、Macのアドレスと8080番ポートを指定します。(同一のネットワークセグメントに接続されていれば、「インターネット共有」を使う必要はありません)

なおWifi接続の設定には、「HTTPプロキシはブラウザで使用されていますが、他のアプリでは使用できません」というような説明が書いてあるかと思いますが、実際には普通にアプリの通信でも有効です。(説明が間違っているのか、端末依存があるかどうかは分かりませんが、手元のAndroid4.0以降の端末だと可能です。httpClientのProxy設定を変更することでそれ以前のAndroidでも可能です)

---

リバースプロキシを使えば、通信内容をキャプチャするだけでなく、いろいろとやれます。
  • リクエスト内容を書き換える。
  • 特定の通信処理のみをInterceptすることができる。例えば「通信Aが成功したらその結果を使って通信Bを行う」といった処理で、通信Bが失敗したときの挙動を確実にテストできる(タイミングよくWifiを切る、とかそういう原始的な方法でテストしている人も世の中にはいるらしい)
  • CharlesのMap Localを使うと、レスポンスとしてローカルファイルを返すことができる。本番環境サーバーからデータを取得する処理を、ローカルファイルに切り替えることで、コードを変更せずにテストデータの変更を行える。

2013年7月21日日曜日

Java FX2のアニメーションをラムダで書き直してみる

Java FX2 を触ってみた。の続き。

Java8のラムダを使って記述したら、確かにTimelineにKeyFrameが並び、アニメーションが順番に実行される、という流れが非常に分かりやすくなりました。コード量も約半減。

@FXML
private Label resultText;

private TranslateTransition slideInAnimation;
private FadeTransition fadeInAnimation;
private FadeTransition fadeOutAnimation;

final private Timeline animation = new Timeline(
 new KeyFrame(Duration.seconds(0),e -> slideInAnimation.play()),
 new KeyFrame(Duration.seconds(0),e -> fadeInAnimation.play()),
 new KeyFrame(Duration.seconds(2),e -> fadeOutAnimation.play())
);

@Override
public void initialize(URL arg0, ResourceBundle arg1) {
 slideInAnimation = TranslateTransitionBuilder.create()
  .node(resultText)
  .duration(Duration.seconds(0.4))
  .fromX(100.0)
  .toX(0)
  .interpolator(Interpolator.EASE_OUT)
  .build();

 fadeInAnimation = FadeTransitionBuilder.create()
  .node(resultText)
  .duration(Duration.seconds(0.5))
  .fromValue(0)
  .toValue(1)
  .build();

 fadeOutAnimation = FadeTransitionBuilder.create()
  .node(resultText)
  .duration(Duration.seconds(0.5))
  .fromValue(1)
  .toValue(0)
  .build();
}

private void showMessage(String text){
 animation.stop();
 resultText.setOpacity(0);
 resultText.setVisible(true);
 resultText.setStyle("-fx-text-fill:#606060");
 resultText.setText(text);
 animation.play();
}


あとnew Duration()ではなくて、Duration#seconds()の方が分かりやすいね。

ついでにアニメーションをinitializeのタイミングで一度だけ生成するように修正するとともに、TransitionBuilderを利用するように修正しました。

しかし、JDK1.8にした際にJava FXも同梱のものに変えてみたのですが、TransitionBuilderはDeprecatedになっているようです。どうもjavaFX8のドキュメントを見るとBuilder系のクラスが軒並みDeprecatedになってるようで、次期バージョンでは消えるらしいのですが、理由が見当たらない…。

Java FX2 を触ってみた。

JavaのGUIといえば、貧弱なSwingの印象が強すぎるのですが、JDK1.7からはJava FX2が非常にプッシュされていまして、こちらはOSネイティブ描画を利用するので、ちょっとしたツールにGUIを付けたいといった場合に非常に便利となりました。

JDK1.7u2ころには、Java FX2を別途インストールするよう求められていたと思うのですが、最近では自動的にバンドルされるようになったようで、インポートするライブラリの場所が変更されていることに注意です(Program Files/Oracle/Java FX 2.0/以下ではなくて、Program Files/Java/jdk1.7.0_XX/jre/libに変更)。そのせいでしばらくは勘違いしてJava FX2.0を弄っていました。

よかった探し

Swingと比較すると記述が非常に楽です。

GUIはXMLで記述しますが、GUIビルダーも用意されています。このGUIビルダーでViewを作り、Controllerクラスを設定すれば、自動的にGUIやアクションがbindされるため、お約束的なコードを書く必要がほとんどありません。

Modelクラスのロジックとの関連付けを、Controllerクラスに少し書くだけでGUIアプリケーションが作れてしまいます。Android Annotationに近い感じでしょうか。

見栄えの変更にCSSを使用できるのも、非常に面白いと思います。.NETのXAMLにしても、AndroidのThemeResourceにしても、標準UIから外れたデザインを作ろうとしたときに、独自記法を学ぶコストが必要になってしまうのに対して、FXMLではWeb標準となっているCSSの知識を流用できます。

イケてないところ

標準UIコンポーネントの貧弱さはSwingから退化してさえいます。FileChooserやDirectoryChooserはOSネイティブのものが利用できるようになったのに、確認警告のダイアログは自作しなければいけないちぐはぐさがよく分かりません。

Labelは単行しか表示できません。改行文字を使って2行に見せること自体はできますが、長い文章を折り返すような処理ができないのです。それでいながらWindowsとMacでそれぞれ起動したときに、Systemフォントの10ptの大きさが違って困りました。これはレイアウトの組み方次第でどうにかなる範疇かもしれませんが…。

最後にデプロイ方法に難を抱えています。JRE7がインストールされている環境はまだ少ないでしょう。Java FX2.2でネイティブデプロイを実現する方法自体は提供されたのですが、その方法というのがパッケージ内にJRE7をまるごと突っ込むという力業で、当然ながらファイルサイズが膨れ上がってしまいます。

微妙なところ

例えば「フェードイン+フレームイン」のアニメーションを実現したいとき、FadeTransitionとTranslateTransitionの複合アニメーションを実行する手段がないので、それぞれのアニメーションをキーフレームアニメーションとして実行することで、擬似的に複合アニメーションを実現しています。

iOSのblocksの記述や、AndroidのAnimationResourceに慣れているのもあると思うのですが、この記述はものすごく気持ち悪い…。

例えば以下は50ミリ秒後(0ミリ秒後だとうまくいかない。原因調査中)にフェードとスライドでテキストを表示して、2000ミリ秒後にフェードアウトさせる、といったコードです。覚えたてで、雑に書いてるのもあるのですが、それにしても無意味に冗長な感があります。

    private Timeline mLastTimeline = null;

    private void showText(String text){
        if(mLastTimeline != null){
            mLastTimeline.stop();
        }
        resultText.setVisible(true);
        resultText.setOpacity(0);
        resultText.setText(text);
        mLastTimeline = new Timeline(
                new KeyFrame(new Duration(50), new EventHandler<ActionEvent>(){
                    @Override
                    public void handle(ActionEvent action) {
                        slideAnimation();
                    }
                }),
                new KeyFrame(new Duration(50),new EventHandler<ActionEvent>(){
                    @Override
                    public void handle(ActionEvent action) {
                        showAnimation();
                    }
                }),
                new KeyFrame(new Duration(2000),new EventHandler<ActionEvent>(){
                    @Override
                    public void handle(ActionEvent action) {
                        hideText();
                    }
                })
                );
        mLastTimeline.setOnFinished(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                mLastTimeline = null;                
            }
            
        });
        mLastTimeline.play();
    }

    private void slideAnimation(){
        final TranslateTransition translate = new TranslateTransition(new Duration(500));
        translate.setNode(resultText);
        translate.setFromX(100.0);
        translate.setToX(0);
        translate.setInterpolator(Interpolator.EASE_OUT);
        translate.play();
    }

    private void showAnimation(){
        final FadeTransition fadein = new FadeTransition(new Duration(500));
        fadein.setNode(resultText);
        fadein.setFromValue(0.0);
        fadein.setToValue(1.0);
        fadein.play();
    }

    private void hideText(){
        final FadeTransition fadein = new FadeTransition(new Duration(500));
        fadein.setNode(resultText);
        fadein.setFromValue(1.0);
        fadein.setToValue(0.0);
        fadein.setOnFinished(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent e) {
                resultText.setText("");
                resultText.setVisible(false);
            }
        });
        fadein.play();
    }

可変長引数のKeyFrameでアニメーションタイムラインを構成する、という方法論自体は悪くないと思いますし、Flashなどに馴染みがあるとむしろとっつきやすいと思います。この辺、JDK1.8のラムダを使えばもう少しマシになるかもしれませんけど…。

同じようなことをiOSでやろうとするとこんな感じかな。
 
self.resultText.text =@"text";
self.resultText.hidden = NO;
self.resultText.alpha = 0.0f;
self.resultText.transform = CGAffinetransformMakeTranslation(1.0f, 0.0f);
[UIView animateWithDuration:0.5f
                 animations:^{
                     self.resultText.alpha = 1.0f;
                     self.resultText.transform = CGAffineTransformIdentity;
                 }
                 completion:^(BOOL finished){
                     //dispatchAfterとかで1.5f秒後にラベルを消す
                     //最初からCoreAnimation.frameworkを使ってキーフレームアニメーションで実現してもいい
                 }

…うん。

結局どうなの

Swing、Java FX1.0の失敗を踏まえつつ、ネイティブの使い心地を実現した、使い勝手のよいUIフレームワークになっているようには思います。Modelロジックに集中することができるため、ちょっとした開発支援ツールを作るのには最適だとすら思っています。

ただし少なくとも国内ではマイナーで終わると思うので、まず業務で使える可能性は低いでしょうし、英語圏の情報をガンガン収集して第一人者になってやるぜ!ってやる気がなければ手を出さない方がいいような気がひしひしとしてます。

次は何に手を出そうかな。