Android

2013年05月02日

QR コードを機器間での秘密情報の輸送に利用する試み

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!

QR コードのもうひとつの利点

QR コードはインターネットアドレスなど所定のテキストデータを簡単に機器に取り込むための手段として広く利用されています。印刷物に限らずさまざまな媒体で扱えるのも便利ですね。

先日、公開鍵暗号を使う Android アプリを試作していた折に、自分の端末 A で生成した秘密鍵を手持ちの別の端末 B へ安全に輸送する手段の検討が必要になりました。やり方はいろいろありそうですが、よりシンプルな方法をとあれこれ考えている内に、机上に無造作に置いたチラシの QR コードにふと目が留まりました。

QR コードからのデータ取り込みは通常光学的に行われます。このことは電子機器の視覚を利用したデータ通信と考えることができるでしょう。第三者による情報窃取の可能性がしばしば問題となるネットワーク通信とは異なり、「目」を使っての情報伝達には物理的に介入することが難しいため、デジタルデータを画像で表現する QR コードは秘匿性の求められる局面での通信手段としても有用と考えられます。

今回の話題はふたつの端末の間でのデータ授受なので、送り側の端末で QR コードの生成と画面表示を行い、受け側の端末でそれを読み取ればよさそうです。情報の受け渡しさえできればコードを残しておく必要はないので QR コードは常に動的に生成し使い捨てとすればよいでしょう。

留意すべき点

一件の QR コードに格納できるデータの量には上限があります。QR コードの開発元である株式会社デンソーウェーブ様のサイトの記事から概要を示す表を以下に引用します。

QRコード規格化・標準化|QRコードドットコム|株式会社デンソーウェーブ

コードの大きさ 21セル×21セル〜177セル×177セル(4セル /辺毎に増加)
情報の種類及び 情報量(混在も可)
QRコードの情報量とバージョンについて
数字
英数字
8ビットバイト(バイナリ)
漢字
最大7,089文字
最大4,296文字
最大2,953文字
最大1,817文字
誤り訂正能力
(データ復元機能)
誤り訂正能力について
レベルL
レベルM
レベルQ
レベルH
コードワードの約 7% が復元可能
コードワードの約 15% が復元可能
コードワードの約 25% が復元可能
コードワードの約 30% が復元可能
コード連結機能 最大16分割 (細長いエリアなどへの印刷)

表には、セル(QR コードを構成する最小の点)構成の最大値である 177 セル×177 セルから成る「バージョン 40」の器に、誤り訂正用の冗長データの最も薄い「レベル L」でエンコードしたデータを格納する際の最大文字数が記載されています。すべての半角英数記号を使う場合は 2953 文字です。この容量の多寡はさておき、普段目にする QR コードのそれとはかけ離れた規模のデータを納めた場合、実用的にはどうなのでしょう。

下の QR コードには半角英数記号 1675 文字を格納しています。これを一般的なサイズ・解像度の PC のモニタで表示した状態であればおそらく多くの携帯端末の QR コードリーダーで読み取ることが可能でしょう。しかし、スマートフォンの小さな画面にこれを表示した状態だと読み取りに失敗するリーダーが出てくるかもしれません。また、一般に情報の密度が高くなるほど読み取りにより多くの時間がかかるため、たとえ成功しても別のストレスが残る可能性もあります。

そういった事情を考え合わせると、今回のような目的で QR コードを利用する場合は、一件のコードに強引に多くのデータを押し込むのではなく、複数のコードに分けて利用することを前提に余裕を持ってデータを扱うほうが賢明でしょう。

実は上の表にも記述があるように、QR コードには一件のデータを最大 16 件のコードに分割して格納することの可能な「コード連結」という仕様があります。次の記事には分割 QR コードの実例が掲載されています。 「IT4206,QRコードの連結機能に対応していますか。」 - 株式会社エイポック様 公式サイトより -

残念ながらすべてのリーダー・ライブラリがこの機能に対応しているわけではなく、現在手元で使っているメジャーな ZXing のライブラリも未対応のようです。しかし、今回はリーダーだけではなくライターも自作することが前提なので、両者間で整合性のとれる内容で独自の分割プロトコルを用意すれば事足りると判断しました。また、二台の端末を操作しながら複数のコードを順番に処理していくのは想像するだけで非常に面倒なので、両者の連携にネットワーク通信を併用し、リーダーが正しくコードを読み取ったらライターが自動的に次のコードを表示することにしました。

ちなみに、先日の記事で操作性の良い QR コードリーダーを自作するために行った取り組みを紹介しましたが、そのきっかけは今回の一連の話題にありました。リーダーを自分で実装すれば上記のような細かい取り回しも柔軟に実現できるわけですね。QR コードの読み取りが主目的ではないアプリへ補助機能としてリーダー処理を組み込むことにはちょっと新鮮な印象があります。

試作と実験

そんなわけで次のような QR コードライターアプリと QR コードリーダーアプリを作ってみました。

  • ライターとリーダーは QR コードの出入力処理とネットワーク通信の併用によって連携する
  • リーダーは起動時に UDP ブロードキャストにより LAN 上のライターを探索、ライターはこれに呼応する
  • ライターはダミーの RSA 秘密鍵 PEM データを 200 文字ごとに分割しそれを格納した QR コードを順次生成・表示する
  • QR コードリーダーはライターの表示するコードをカメラ経由で順次読み取る
  • ライターは生成するコードに毎回連番情報とランダム文字列を挿入、読み取りを終えたリーダーがそれに一致する文字列をネットワーク通信でライターへ返した場合のみライターは次のコードを生成・表示する
  • リーダーは最後のコードの読み取りを終えると受信した秘密鍵情報を表示する

デモ動画

動作の様子です。奥の端末で QR コードライター、手前の端末でリーダーを動かしています。

リソース

今回試作したソフトウェア一式を公開します。興味のある方はお試し下さい。


QR コードライター・リーダーのソースコード
MyQRCodeReaderEx - github
QR コードライターのビルドずみ apk

http://dsas.blog.klab.org/data/qr_secret/QR_writer.apk

QR コードリーダーのビルドずみ apk

http://dsas.blog.klab.org/data/qr_secret/MyQRCodeReaderEx.apk


(tanabe)

klab_gijutsu2 at 14:25|この記事のURLComments(0)TrackBack(0)
2013年04月15日

「ZXing QR コードスキャナー」の内部処理を追う

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
ZXing ("Zebra Crossing") Team による「QR コードスキャナー (以下、"ZXing スキャナ"と略記) は操作性が良く人気の高い定番 Android アプリのひとつです。同アプリでは ZXing Team 自らが開発を継続しているオープンソースのバーコード処理用ライブラリが使用されています。
zxing Multi-format 1D/2D barcode image processing library with clients for Android, Java
このライブラリの優秀さはスマートフォンでバーコード/QR コードを処理する際の実質標準の座にある状況からも裏打ちされていますが、ZXing スキャナの「使いやすさ」は本ライブラリの性能のみに依るものではなくアプリの実装に大きく支えられています。ZXing スキャナのソースコードは公開されているので、それに学べば本家と同等の使いやすさを備えた QR コードリーダーの自作が可能となるはずですね。操作性の良いリーダーを自作できるのであれば QR コード読み取り機能をアプリへ組み込む際にわざわざ ZXing スキャナをインテントで呼び出す必要はなく、独自の処理を柔軟に組み込むこともできるでしょう。また、上記ライブラリを知り尽くした ZXing 謹製スキャナのコードはライブラリの性能を最大限に引き出すための最良のお手本となりそうです。

ZXing スキャナのソースコードを正面から分析した情報はほとんど見当たらないようですが、手元での調査を通じて得られた情報と、そのエッセンスを小さくまとめた形で試作した自作リーダーのリソース一式を公開します。なお、この記事では ZXing 2.1 Release 中の zxing-2.1/android/ 配下のソースコードを対象としています。

注目すべき要素

なぜ ZXing スキャナは QR コードリーダーとして使いやすいのでしょう?その要素こそが実装を調べる上で注目すべきポイントとなるでしょう。特長をみっつピックアップしてみます。

  • コードの認識が早い
    ターゲットにレンズを向けてじっとしていればすぐに認識が完了する
  • 対象とするコードをファインダ枠内に留める以外の操作が不要
    フォーカスを合わせたり画面をタップするといった操作をする必要がない
  • コード識別パターン検出位置を示すポイントが描画される
    プレビュー表示にコード識別パターンを発見した位置を示すポイント (ResultPoint) がリアルタイムで描画されるため端末の位置・角度を加減し易い
ResultPoint の描画

ソースコードの追跡とまとめ

コード読みの途中で迷子にならないための道具としてホワイトボード代わりにエクセルシートを使いました。あくまでも作業用なので決して見た目の良いものではありませんが記録としてそのまま掲載します。

   コードリーディング時のメモ

以下、ソースコードから得られた情報を整理してみます。

※クリックすると大きな図が開きます

  • プレビュー表示中に裏側で走っている太い処理の内容
    ※カメラには 2 秒ごとに autoFocus() が適用される
    1. 現在プレビュー表示中のフレームイメージを取得
    2. 取得したイメージをバックグラウンドスレッドへ渡す
    3. バックグラウンドスレッドはイメージを ZXing ライブラリ処理に渡す
    4. ライブラリはコードの検出過程で QR コード画像の識別パターンである可能性のある箇所(ResultPoint)を見つけるとその座標を UI 側へ逐次通知する
    5. ResultPoint を受け取とった UI 側はそれを表示中のプレビュー画面に重ねて描画する
    6. ライブラリ側処理が今回のフレームイメージから QR コードを検出しなかった場合、1. からの処理が繰り返される
    つまり、ZXing スキャナ は QR コードを見つけるまで絶えずフレームイメージの取得とその画像分析を繰り返している

  • 重要なクラスとその処理の概要
    - com.google.zxing.client.android
    • CaptureActivity
      QR コードスキャナーの Activity クラス
    • CaptureActivityHandler
      CaptureActivity のハンドラ。DecodeThread インスタンスを生成し DecodeHandler からライブラリ処理による QR コード認識結果を受け取る。コードが認識されなかった場合はこのクラスが CameraManager.requestPreviewFrame() を叩くことで、次のフレームイメージ取得〜QR コード認識という全体のループを継続させる
    • DecodeThread
      プレビュー中のフレームイメージから QR コードを検出するための手続きを連続的に繰り返すバックグラウンドスレッド
    • DecodeHandler
      DecodeThread のハンドラ。ZXing ライブラリの MultiFormatReader クラスの QR コード検出メソッドを呼び、認識結果を CaptureActivityHandler へ伝える
    • ViewfinderView
      View の継承クラス。プレビュー画面の描画、ファインダ矩形上に捕捉したイメージについての識別パターン検出点(ResultPoint)の描画を onDraw() メソッド内で実施
    • ViewfinderResultPointCallback
      ZXing ライブラリ内に宣言のある「com.google.zxing.ResultPointCallback」インターフェイスの実装クラス。DecodeThread 経由でライブラリ側処理へ登録され、ライブラリが ResultPoint 認識時にこのクラスの foundPossibleResultPoint() メソッドをコールバックする。同メソッドは ViewfinderView クラスへ ResultPoint を伝達する
    - com.google.zxing.client.android.camera
    • CameraManager
      カメラのオープン・クローズや PreviewCallback へのフレームイメージ取得指示など
    • PreviewCallback
      Camera.PreviewCallback の実装クラス。onPreviewFrame() 発生時に DecodeHandler へキャプチャイメージを渡す
    • AutoFocusManager
      バックグラウンドで 2 秒ごとにカメラに autoFocus() を適用
    • CameraConfigurationManager
      カメラまわりの解像度や各種パラメータの取得・設定

  • 使いやすさを支えているもの
    • 「コードの認識が早い」
      → プレビュー表示中、コードを検出するまで絶えずフレームイメージの取得とバックグラウンドスレッドでの分析を繰り返している
    • 「対象とするコードをファインダ枠内に留める以外の操作が不要」
      → カメラへのオートフォーカスの適用を効果的に利用している
    • 「コード識別パターン検出位置を示すポイントが描画される」
      → 検出位置を直ちに通知するためにライブラリの深部に用意されたコールバック機構と UI への描画処理を適切に組み合わせて使用している

リーダーの試作

以上のように、全体像が見えてしまえば ZXing スキャナの操作性を支えているのは意外なほどシンプルなしくみであることがわかります。それを自作のコードに組み込むことは難しくなさそうですね。そこで ZXing スキャナの実装に倣いつつできるだけ短いコードでざっくりとリーダーを作ってみることにしました。認識結果はダイアログ表示のみとしています。動作確認は一部の環境でのみ行っており、不具合があれば適宜手を入れて下さい。

MyQRCodeReader - github

ソースコードの一覧と概要を以下に示します。

  • MyActivity.java
    本アプリの Activity クラス。implements SurfaceHolder.Callback, Handler.Callback, Camera.PreviewCallback
  • MyCameraConfigurationManager.java
    ZXing スキャナの CameraConfigurationManager より。カメラパラメータの設定を決め打ちに
  • MyDecodeThread.java
    ZXing スキャナの DecodeThreadより。対応コードフォーマットを決め打ちに
  • MyDecodeHandler.java
    ZXing スキャナの DecodeHandlerより。コード認識成功時に MyActivity へ Bitmap は送らず検出したテキストの情報のみに
  • MyFinderView.java
    ZXing スキャナの ViewfinderView および ViewfinderResultPointCallback より。オリジナルの ViewfinderView はプレビュー画面全体を覆っているが単純化のためここではファインダ矩形と一対一に変更。ResultPoint が 4 以上になるとファインダ内に「Warning!」と表示
試作リーダーの動作の様子

ビルドずみの apk はここにあります

http://dsas.blog.klab.org/data/zxing_qr_scanner/MyQRCodeReader.apk


(tanabe)
klab_gijutsu2 at 17:00|この記事のURLComments(0)TrackBack(0)
2012年12月11日

Android の GCM をプライベートな目的に使う

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
GCM (Google Cloud Messaging) は所定の端末へメッセージを送るための有用なしくみですが、何も一斉通知やソーシャルな用途ばかりではなくもちろんきわめて個人的な目的のためにも利用できます。今回はその方面でのアイディアをひとつざっくり形にしてみた例を紹介します。いろいろ応用もできるでしょう。

自分の端末を遠隔操作

Android 界隈は依然にぎやかで次々に新しい製品が発売されています。そのため複数の端末を持っている人も少なくないでしょう。まだまだ使える端末を遊ばせておくのはもったいないので、これを外出中の自宅の監視カメラとして使うことにしました。 端末を室内の対象物に向けて固定しておき、出先や仕事場から GCM 経由で端末へメッセージを送出、それをトリガーにアプリが撮影したスナップを Dropbox 経由で確認します。

シンプルな実装の割に結構役に立っています。 アプリ本体とソースコードを以下で公開しています。

RemoteWand - Google Play
RemoteWand - github

処理の流れ

端末の登録

遠隔操作の対象とする端末上で「RemoteWand」を起動して「登録」ボタンを押下すると GCM サーバへ端末が登録されトリガー発信用のフォームを含む HTML ファイルの添付された自分あてのメールが生成されます。

トリガーの送出〜撮影

PC やスマホのウェブブラウザに上記のフォームをロードし登録時に指定したパスワードを添えてサブミットするとアプリケーションサーバから GCM サーバへ要求が送出され GCM サーバが当該端末へ所定のメッセージをプッシュします。端末にインストールずみの RemoteWand はこのメッセージをトリガーにカメラで静止画の撮影を行います。Dropbox の「カメラアップロード」機能は自動的に撮影画像のアップロードを行うためあらかじめ端末にインストールしておけばリモートで簡単に写真を確認できます。


(tanabe)


klab_gijutsu2 at 15:39|この記事のURLComments(2)TrackBack(0)
2012年09月21日

Android のプッシュ通知用コネクションに関するメモ(補記)

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
前回の記事では、Android のプッシュ通知機構 (GCM) が 「Google 所管のサーバと Android 端末との間の持続接続」 をどのように使用しているかを調べてみました。
DSAS 開発者の部屋 : 「Android のプッシュ通知用コネクションに関するメモ」
この記事の文中に、上記の持続接続が「プッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい」 と注釈を添えています。今回はその内容と関連する話題を控えます。

まとめ

端末と mtalk.google.com:5228 との間の接続はプッシュ通知以外に次の用途で使用される

  • Android 版 Google Talk クライアントのメッセージ送受信 (ボイスチャットを除く)
  • Google Play アプリのウェブページから端末へのプッシュインストール指示

- 他にもあるかも?
- 非公開の内部仕様につき今後変更される可能性も

Google Talk について

  • Android 版 Google Talk クライアントは、com.google.process.gapps (GoogleServicesFramework) プロセスの管理する 「mtalk.google.com:5228」 とのコネクションを使用する。このコネクションを使用できない場合はエラーとなり、独自に代替ポートが使用されることはない

  • ちなみに PC 版 Google Talk クライアント (googletalk.exe) はメッセージ送受信に 「talk.google.com:5222」 とのコネクションを使用する。5222 番ポートを使用できない場合は代わりに 「talk.google.com:443」 を使用する

  • 「mtalk.google.com」 には 「mobile-gtalk.l.google.com」 という別名がある。ネーミングから想像すると、このホストとのコネクションは本来モバイル端末において Google Talk からの利用を主目的とするものであり、プッシュ通知機構からの利用はあるいはむしろ副次的・応用的なものだったのかも?
    $ dig mtalk.google.com
    
    ; <<>> DiG 9.2.1 <<>> mtalk.google.com
                             :
    ;; ANSWER SECTION:
    mtalk.google.com.       81547   IN      CNAME   mobile-gtalk.l.google.com.
    mobile-gtalk.l.google.com. 291  IN      A       74.125.31.188
                             :
    

Google Play ページからのプッシュインストールについて

    

  • Google Play アプリケーションページからのインストール指示は mtalk.google.com:5228 コネクション経由で端末へ送出されパッケージマネージャへ渡される

  • このプッシュインストールは感覚的には GCM によるプッシュ通知とあまり変わらないが、GCM の端末要件が Android 2.2 以上であるのに対し、Play ストアページからのプッシュインストールは 2.2 未満のバージョンでも機能するという違いがある

  • Android 1.6 環境を確認したところ、システムブート後に mtalk.google.com:5228 コネクションが自動的に確立する点は共通しているが、違いとして目に付くところでは 2.2 以降での com.google.process.gapps プロセスの実体である "/system/app/GoogleServicesFramework.apk" が存在しない。中核となる持続接続のハンドリング自体は旧バージョンにおいても実装されているわけで、C2DM, GCM の要件が 2.2 以上とされた背景にはおそらくはモジュール設計上の複合的な要因があるものと察せられる

mtalk.google.com:5228 に接続できない場合は?

端末上の com.google.process.gapps プロセスが 「mtalk.google.com:5228」 へコネクションを確立できない場合の所作を確認した

  • 実験環境のルータの設定で、「mtalk.google.com」 の既知の分散先サーバである 「173.194.72.188」 「74.125.31.188」 の 5228 番ポートへの LAN 上の各ノードからのアクセスを遮断。この状態で Android 端末を起動し観察を行った

  • 端末から mtalk.google.com:5228 へ接続不可につき connect タイムアウトまでステートは SYN_SENT のまま変化せず。タイムアウト後は何度かリトライが発生。対象は常に 5228 番ポート
    Active Internet connections (w/o servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 10.10.0.10:43695        173.194.35.15:80        TIME_WAIT   -
    tcp        0      0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
    tcp        0      0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
    tcp        0      1 ::ffff:10.10.0.10:34224 ::ffff:173.194.72.188:5228 SYN_SENT    256/com.google.proc
                            :
    
                       (時間の経過・・・)
    Active Internet connections (w/o servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 10.10.0.10:43695        173.194.35.15:80        TIME_WAIT   -
    tcp        0      0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
    tcp        0      0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
                            :
    
                       (時間の経過・・・)
    Active Internet connections (w/o servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 10.10.0.10:43695        173.194.35.15:80        TIME_WAIT   -
    tcp        0      0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
    tcp        0      0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
    tcp        0      1 ::ffff:10.10.0.10:32648 ::ffff:173.194.72.188:5228 SYN_SENT    256/com.google.proc
                            :
    

  • 開発者向けの公式ページ に以下の記述が見られるためリトライ時に 5229 or 5230 番ポートが対象となることに期待したが今回の手順による観察ではその所作は見られなかった
     Note: If your organization has a firewall that restricts the traffic
     to or from the Internet, you'll need to configure it to allow
     connectivity with GCM. The ports to open are: 5228, 5229, and 5230. 
     GCM typically only uses 5228, but it sometimes uses 5229 and 5230.

  • 以上の結果から Android 端末の参加するネットワークでは必ず外向きの 5228 ポートへのアクセスを許可しなければならないことがわかる

  • この調査の過程で、「mtalk.google.com」 の分散先として、前出の 2 サーバに加え 「173.194.79.188」 の存在を確認した (2012 年 8 月末時点)

付録:端末上で拾ってみたパケットの様子

Google Talk     

プッシュインストール     


(tanabe)
klab_gijutsu2 at 13:36|この記事のURLComments(0)TrackBack(0)
2012年08月07日

Android のプッシュ通知用コネクションに関するメモ

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!

Android のプッシュ通知機構(GCM, 旧 C2DM)は有用なしくみですが、オープンソースではないソフトウェア要素が関わっているためか内部仕様に近い情報をあまり見かけないのが残念です。手元での観察結果をもとにプッシュ通知で使用されるネットワークコネクションまわりの情報をいくつかまとめてみました。

まとめ

  • Android 端末上の com.google.process.gapps プロセス は mtalk.google.com:5228 へ TCP コネクション [A] を張る
    (通常は 5228 番ポートだが 5229, 5230 番ポートが使用される場合もある)
  • com.google.process.gapps プロセスは基本的に [A] をずっと張りっ放しにしており接続維持のため無応酬 15分ごとに Keep-Alive パケットを流す
  • GCM, C2DM のプッシュ通知はいずれも [A] 経由で端末へ送られる

※[A] はプッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい

端末− Google サーバ間のコネクションについて

コネクションの存在確認
GCM に関する開発者向け情報へアクセスすると次の記述が見つかります。

[GCM Architectural Overview | Android Developers] より

Note: If your organization has a firewall that restricts the traffic to or from the Internet, you'll need to configure it to allow connectivity with GCM. The ports to open are: 5228, 5229, and 5230. GCM typically only uses 5228, but it sometimes uses 5229 and 5230.
GCM では端末と所定の外部サーバとの接続に通常 5228 番ポートを使うということですね。 さっそく Android 端末実機上で netstat コマンドを実行してみます。

※ここで使用しているのは root 化ずみの実験用端末であり、
各コマンドは Android オリジナルのものではなくBusyBox 版を利用しています。

pid=3547 のプロセスが 「173.194.72.188:5228」との間に TCP コネクションを張っていることがわかります。

# netstat -p
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 10.10.0.10:60272        74.125.235.111:80       ESTABLISHED 6337/berserker.andr
tcp        1      0 ::ffff:10.10.0.10:33375 ::ffff:74.125.235.145:443 CLOSE_WAIT  3547/com.google.pro
tcp        0      0 ::ffff:10.10.0.10:22    ::ffff:10.10.0.6:3667   ESTABLISHED 6385/dropbear
tcp        1      0 ::ffff:10.10.0.10:52412 ::ffff:74.125.235.180:443 CLOSE_WAIT  7318/com.google.and
tcp        0      0 ::ffff:10.10.0.10:22    ::ffff:10.10.0.6:3911   ESTABLISHED 6879/dropbear
tcp        0      0 ::ffff:10.10.0.10:44984 ::ffff:173.194.72.188:5228 ESTABLISHED 3547/com.google.pro
tcp        0      0 ::ffff:10.10.0.10:36091 ::ffff:74.125.235.122:80 ESTABLISHED 6337/berserker.andr
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags       Type       State         I-Node PID/Program name    Path
unix  2      [ ]         DGRAM                     10725 3445/system_server  /data/misc/wifi/sockets/wpa_ctrl_3445-307
unix  2      [ ]         DGRAM                     10727 3445/system_server  /data/misc/wifi/sockets/wpa_ctrl_3445-308
unix  2      [ ]         STREAM                     1293 103/rild            /dev/socket/rild-debug
unix  2      [ ]         STREAM                     1295 103/rild            /dev/socket/rild
unix  4      [ ]         DGRAM                     10722 3762/wpa_supplicant /dev/socket/wpa_wlan0
unix  3      [ ]         STREAM     CONNECTED      17159 108/adbd
unix  3      [ ]         STREAM     CONNECTED      17158 108/adbd
                           :

当該コネクションを保持するプロセスについて
pid=3547 は「com.google.process.gapps」というプロセス。
# ps | grep 3547
  3547 app_1      0:05 com.google.process.gapps

「com.google.process.gapps」は「Google Services Framework」のプロセス名。
(関連記事)
"Fix for Google services Framework (process com.google.process.gapps) has stopped unexpectedly"

「Google services Framework」の実体は以下のシステムパッケージ。(※ソース未公開)

 # ls -l /system/app/GoogleServicesFramework.apk
-rw-r--r--    1 root     root       2867063 Feb  6  2012 /system/app/GoogleServicesFramework.apk

接続先サーバについて
「173.194.72.188」に該当するホスト名は「mtalk.google.com」。
$ host -v mtalk.google.com
Trying "mtalk.google.com"
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30177
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;mtalk.google.com.              IN      A

;; ANSWER SECTION:
mtalk.google.com.       85662   IN      CNAME   mobile-gtalk.l.google.com.
mobile-gtalk.l.google.com. 66   IN      A       173.194.72.188

Received 79 bytes from 10.10.0.18#53 in 0 ms
※2012 年 8 月現在、「mtalk.google.com」の分散先として「173.194.72.188」「74.125.31.188」の 2 サーバの存在を確認

接続維持用の Keep-Alive パケットについて
tcpdump を走らせ 5228 ポートをめぐる応酬を監視。沈黙状態の 15 分ごとに端末から Keep-Alive パケットが送出される様子が見てとれます。
# tcpdump  -nl -s 256 port 5228
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlan0, link-type EN10MB (Ethernet), capture size 256 bytes
13:16:23.504179 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 78:103(25) ack 541win 11236 <nop,nop,timestamp 9585688 3638038533>
13:16:23.560095 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 541:566(25) ack 103 win 274 <nop,nop,timestamp 3638938605 9585688>
13:16:23.560168 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 566 win 11236 <nop,nop,timestamp 9585694 3638938605>
13:31:23.568391 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 103:128(25) ack 566 win 11236 <nop,nop,timestamp 9675694 3638938605>
13:31:23.624242 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 566:591(25) ack 128 win 274 <nop,nop,timestamp 3639838674 9675694>
13:31:23.624371 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 591 win 11236 <nop,nop,timestamp 9675700 3639838674>
13:46:23.637744 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 128:153(25) ack 591 win 11236 <nop,nop,timestamp 9765701 3639838674>
13:46:23.696340 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 591:616(25) ack 153 win 274 <nop,nop,timestamp 3640738749 9765701>
13:46:23.696469 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 616 win 11236 <nop,nop,timestamp 9765707 3640738749>
14:01:23.712443 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 153:178(25) ack 616 win 11236 <nop,nop,timestamp 9855709 3640738749>
14:01:23.770009 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 616:641(25) ack 178 win 274 <nop,nop,timestamp 3641638829 9855709>
14:01:23.770164 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 641 win 11236 <nop,nop,timestamp 9855715 3641638829>
14:16:23.783100 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 178:203(25) ack 641 win 11236 <nop,nop,timestamp 9945716 3641638829>
14:16:23.841258 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 641:666(25) ack 203 win 274 <nop,nop,timestamp 3642538904 9945716>
14:16:23.841349 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 666 win 11236 <nop,nop,timestamp 9945722 3642538904>
14:31:23.853398 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 203:228(25) ack 666 win 11236 <nop,nop,timestamp 10035723 3642538904>
14:31:23.912470 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 666:691(25) ack 228 win 274 <nop,nop,timestamp 3643438980 10035723>
14:31:23.912555 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 691 win 11236 <nop,nop,timestamp 10035729 3643438980>

GCM, C2DM で通知を送ってみる

以下は自作アプリから C2DM, GCM の順に端末へプッシュ通知を送った時の様子です。 いずれも「173.194.72.188:5228」経由で通知が端末へ配信されていることがわかります。

11:49:49.268419 IP 173.194.72.188.5228 > 10.10.0.10.44984: P 974497881:974498015(134) ack 436376500 win 272 <nop,nop,timestamp 1641184578 3415344>
11:49:49.269687 IP 10.10.0.10.44984 > 173.194.72.188.5228: . ack 134 win 11236 <nop,nop,timestamp 3483319 1641184578>

11:49:59.146970 IP 173.194.72.188.5228 > 10.10.0.10.44984: P 134:262(128) ack 1 win 272 <nop,nop,timestamp 1641194459 3483319>
11:49:59.147103 IP 10.10.0.10.44984 > 173.194.72.188.5228: . ack 262 win 11236 <nop,nop,timestamp 3484307 1641194459>



(tanabe)
klab_gijutsu2 at 17:20|この記事のURLComments(0)TrackBack(0)
2012年07月09日

Android パッケージインストール処理のしくみを追う

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
身近な話題でありながら中身のよくわからないことを調べてみるのは興味ぶかいもので、そこから得た知識が意外なところで役に立つことも少なくありません。かねてより Android 環境へアプリケーションをインストールする際に内部でどのような処理が行われるのかに関心を持っていたのですが、知りたい情報がなかなか見当たらないため手元で調査を行いました。その内容を公開します。

まとめ

※クリックすると大きな図が開きます

※PackageInstaller を起動した状態での関連プロセスの例

$ ps
USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
root      1     0     268    180   c009b74c 0000875c S /init
root      36    1     812    244   c02181f4 afd0b45c S /system/bin/installd
root      33    1     60900  16628 c009b74c afd0b844 S zygote
system    62    33    126452 28368 ffffffff afd0b6fc S system_server
app_30    372   33    74876  18852 ffffffff afd0c51c S com.android.packageinstaller
                              :
※Package Manager Service と installd プロセスの連携用 UNIX ドメインソケット
$ ls -l /dev/socket/installd
srw------- system   system            2012-07-04 11:43 installd
  • Android OS 上でパッケージインストールにもっとも深い関わりを持つシステムサービスは、system_server プロセス内で稼動する Package Manager Service と、単独のネイティブプロセスとして動作する installd デーモンである。両者はいずれもシステムブート時に動作を開始する。
  • Package Manager Service は起動時に /data/system/packages.xml ファイルより現システム上のパッケージに関する情報を読み込んで実状と照合し、何らかの不整合があれば再インストール処理を含む自動復旧を試みた上で同ファイルへ最新の状態を反映する。
  • Package Manager Service は起動時に /system/etc/permissions/platform.xml を参照し既定の Android パーミッションに関する情報をロードし、同ディレクトリ下の他の *.xml より当該端末がカバーする機能の情報をロードする。
  • installd デーモンは UNIX ドメインソケット /dev/socket/installd 経由で Package Manager Service から処理要求を受信し、パッケージのインストール・アンインストールまわりの一連の手順のうち root 権限を要する処理を主に担当する。

  • PackageInstaller は通常のパッケージを対話的にインストールするための Android 既定のアプリケーションである。ユーザからインストール指示を受けると PackageInstaller は InstallAppProgress アクティビティを呼び出し、InstallAppProgress は Package Manager Service の installPackage() API 経由で当該パッケージのインストールをシステムへ依頼する。
  • Package Manager Service はパッケージインストール要求を受けると当該パッケージの情報を取得し以下を実施する
    - インストール処理用のキューへパッケージ情報を追加〜順番待ち
    - 当該パッケージの適切なインストールロケーションを判定
    - 新規インストール/更新インストールの判別
    - 所定のディレクトリへの apk ファイルのコピー
    - 当該アプリの UID の決定
    - installd デーモンへ処理を要求
         - アプリケーションディレクトリの作成とパーミッション設定
         - dex コードのキャッシュディレクトリへの切り出し
    - 最新の状態を /data/system/packages.xml および packages.list へ反映
    - インストール完了の旨をパッケージ名を添えてシステムへブロードキャスト
     (新規の場合:Intent.ACTION_PACKAGE_ADDED
       更新の場合:Intent.ACTION_PACKAGE_REPLACED)

  • 上記の PackageInstaller 経由でのインストール経路とは別に、Package Manager Service は内部クラス「AppDirObserver」による非対話式のインストール経路を用意している。 AppDirObserver は android.os.FileObserver の派生クラスであり、所定のディレクトリ直下に .apk の拡張子を持つファイルが配置/削除されると自動的にシステムへのインストール/アンインストールを行う機能を持つ。(※別経路でインストールが行われた場合に重複処理発生を抑制するための機構を備える) Package Manager Service は起動時に /system/app, /data/app, /data/app-private, /system/framwrorks, /vendor/app の各ディレクトリを対象に AppDirObserver クラスのインスタンスを生成する。なお、これらのディレクトリへの書き込みには特権が必要であるため一般のアプリケーションプロセスから直接 AppDirObserver の提供する機構を利用することはできない。

コード読み

パッケージインストール処理を構成する一連のソースコードはかなりのボリュームがありますが、コードリーディングの一助として注釈を添えた抜粋を以下に掲載します。コードはいずれも 2012-07-06 現在の内容です。 なお、オンラインソースへのリンクには便宜上 http://android.git.linaro.org/ を使用しています。 続きを読む

klab_gijutsu2 at 12:45|この記事のURLComments(2)TrackBack(0)
2012年05月30日

inotify で Android 上のファイル I/O を監視する

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
先日 Android のソースコードを読んでいた折に、所定の処理においてシステム上で発生するファイル I/O の流れを確認したいと思いました。処理の全体像を把握する上で有用と考えたためです。そこで inotify を利用することにしました。

Android の Linux カーネルには inotify が含まれており、Android SDK には inotify を利用した FileObserver クラスが用意されています。

Android では一般のアプリケーションプロセスからアクセスできるファイル・ディレクトリが制限されるため、FileObserver を使い Android アプリの形でツールを用意しても今回の目的にはあまり役に立たないでしょう。こういう時には CUI のコマンドラインツールの方が何かと融通がききます。Android エミュレータ環境への adb shell 接続で得られる root 権限のコンソール上でツールを動かすことにしました。

Android の notify コマンドを使う

Android には inotify API を使ってファイル I/O をモニタするコマンドラインツール「notify」が含まれています。 [platform/system/core/toolbox/notify.c]

# notify
Usage: notify [-m eventmask] [-c count] [-p] [-v verbosity] path [path ...]
この notify コマンドを使って "/data/local/tmp" ディレクトリを監視しつつ別プロセスから以下のコマンドを実行した時の出力例を示します。
# pwd
/data/local/tmp
# echo aaa > test.txt
# mv test.txt aaa.txt
# notify -c 10 /data/local/tmp
/data/local/tmp: 00000100 00000000 "test.txt"  ; 0x00000100:IN_CREATE
/data/local/tmp: 00000020 00000000 "test.txt"  ; 0x00000020:IN_OPEN
/data/local/tmp: 00000002 00000000 "test.txt"  ; 0x00000002:IN_MODIFY
/data/local/tmp: 00000008 00000000 "test.txt"  ; 0x00000008:IN_CLOSE_WRITE
/data/local/tmp: 00000040 00000033 "test.txt"  ; 0x00000040:IN_MOVED_FROM
/data/local/tmp: 00000080 00000033 "aaa.txt"   ; 0x00000080:IN_MOVED_TO
※ I/O イベントの定義は [platform/bionic/libc/kernel/common/linux/inotify.h]

inotifywait コマンドを使う

Android の notify コマンドはコンパクトで手軽なツールですが、出力の読みにくさに加え、ディレクトリ配下を再帰的に監視できない点が不便です。改造したりツールを自作するのも面白そうではありますが、Linux 界隈には inotify-tools というパッケージがあることを知り、そこに含まれる inotifywait コマンドを利用することにしました。

すでに誰かが Android 上で動作する inotifywait をビルドし配布しているのではないかとネット上を探したところ見当たらなかったため手元でビルドを行いました。inotifywait 本体と NDK でのビルド用に手を加えたプロジェクト一式を以下に公開します。

inotifywait_bin_android.zip
md5sum [55BF0FD8365A4139D679CE0D6A3A07B3]
プロジェクト一式(※) - github
(※) 以下の著作物が含まれます。利用に際しては各ソフトウェアのライセンス規約を遵守して下さい。

・ inotify-tools
  著作権者:Rohan McGovern, Radu Voicilas
  ライセンス:GPL version 2
  サイト:https://github.com/rvoicilas/inotify-tools/wiki/
・ GNU glibc POSIX 正規表現関数群 (glibc-2.11.3 より)
  著作権者:Free Software Foundation, Inc.
  ライセンス:LGPL version 2.1 or later
  サイト:http://www.gnu.org/software/libc/

inotifywait に /data /cache /system の各ディレクトリを再帰的に監視させた状態で、自作のアプリ「HelloApp」を起動した際の出力例を以下に示します。

# ./inotifywait -r -m /data /cache /system
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.

/system/framework/ ACCESS framework-res.apk
/system/framework/ ACCESS framework-res.apk
/data/app/ OPEN com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ OPEN com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/dalvik-cache/ OPEN data@app@com.example.helloapp-1.apk@classes.dex
/data/dalvik-cache/ ACCESS data@app@com.example.helloapp-1.apk@classes.dex
/data/dalvik-cache/ ACCESS data@app@com.example.helloapp-1.apk@classes.dex
/data/app/ OPEN com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/system/framework/ ACCESS framework-res.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/system/fonts/ OPEN DroidSans-Bold.ttf
/system/lib/hw/ OPEN gralloc.default.so
/system/lib/hw/ ACCESS gralloc.default.so
/system/lib/hw/ ACCESS gralloc.default.so
/system/framework/ ACCESS framework-res.apk
/system/framework/ ACCESS framework-res.apk
/data/app/ CLOSE_NOWRITE,CLOSE com.example.helloapp-1.apk
              :

(tanabe)
klab_gijutsu2 at 16:51|この記事のURLComments(0)TrackBack(0)
2012年05月21日

Android アプリ「SundayPad」を公開しました

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
2012-06-07: バージョン 1.0.1 を公開しました


予定のない休日の午後に PC の前でごろりと横になって DVD を観たりあちこちのサイトを見て回ったりするのは楽しいのですが、ひとたび「のんびりモード」に浸るとキーボードに触るのが億劫になります。ウェブ検索用にちょっとキーワードを入力したいのだけれど今の体勢を崩すのはなんだか悔しい。こんな時スマホなら音声入力を手軽に使えるので便利です。でも外出先ならともかく休日の自宅でわざわざ端末の小さな画面を追いかけるのはあまり嬉しくない気もする。不精者はあれこれ悩みます。

ある時ふと思いました。スマホには得意で賢い音声認識だけをやらせて、その結果を簡単に PC 上で利用できるようにすれば何かと便利ではないか?
さっそく Google 音声認識と連携し認識結果文字列を送信する簡単な Android アプリと、それを受信して PC 上のアプリの所定のフィールドへその文字列を送出する Windows プログラムを作ってみました。期待通りそれはシンプルな割に便利なものになりましたが、しばらく使っている内に「PC のマウスカーソルを操作できればもっと便利になる」ということに気づき、タッチパッド機能と必要最小限のソフトウェアキーボードを加えることにしました。

同僚たちに試作版を見せるとなるほどと受けもよく、そこで出されたアイディアを含めて機能追加と改良を行いひと通り形になったのが Android アプリ「SundayPad」と、PC 用のエージェントプログラム「SpAgent」です。興味のある方はお試し下さい。

改訂履歴

v1.0.1 (2012-06-07)

  • 要求元の Android 端末のアドレスが前回認証ずみのものと同一であれば PC 側での受け入れ確認ダイアログの表示をスキップ
  • Windows 環境でのカーソルの細かい移動をなめらかに

インストール・ダウンロード

Android アプリ「SundayPad」 (2012-06-07 更新)


SundayPad - Google Play のページ

PC 用エージェントプログラム「SpAgent」

Windows 版 - SpAgent.zip (2012-06-07 更新)

Mac 版 - SpAgent.dmg (2012-06-07 更新)

動作環境

SundayPad は Android OS 2.1 以上に対応します。
バージョン 2.2 以上の環境をお勧めします。

SpAgent の動作は以下のそれぞれ単一のテスト環境においてのみ確認しています

Windows 版

  • Windows 8 Consumer Preview 32 bit/ 64 bit (デスクトップ環境)
  • Windows 7 Professional SP1 32 bit/ 64 bit
  • Windows Vista Business SP2 32 bit/ 64 bit
  • Windows XP Professional SP3 32 bit
  • Windows XP Home SP3
  • Windows 2000 SP4

Mac 版

  • Mac OS X バージョン 10.6.8
続きを読む
klab_gijutsu2 at 18:23|この記事のURLComments(0)TrackBack(0)
2011年12月26日

Android アプリ「AppNetBlocker」を公開しました

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
以前から自分自身がほしいと思っていた Android アプリが形になったためマーケットで公開しました。 今回はそのアプリ、「AppNetBlocker」をご紹介します。

AppNetBlocker は、所定のアプリから「完全なインターネットアクセス」の許可を除去するツールです。実行に root 権限は必要ありません。Android 1.6 以上の環境で動作します。興味のある方はご利用下さい。もちろん無料です。

(2011/12/26 追記)
本アプリは、現時点では安全面において不安要素の少なくない Android をめぐる状況において Android 利用者が自分自身を守るためにとり得る対策のひとつを形にしたものであり、他者の権利を脅かすことを目的とするものではありません。
もし、Android を今よりもさらに安全に利用することが可能となればより多くの利用者・開発者の利益につながることでしょう。本アプリはたとえ僅かでもその一助になればと手がけたものであり、開発の動機もそこにあります。
しかしながら、一部の方から本アプリと Android マーケット規約とのかねあいを懸念するご指摘がありました。その話題については判断の余地があるものと認識していますが、少なくとも利用者の不安を誘引することはまったく本意ではなく、マーケットでの配布という形態は一時中断することとします。

実験用のいわゆる「野良アプリ」として apk のダウンロードリンクを当面残しておきます。このリンクから端末へ直接インストールすることはできません。意図を理解される方のみ自己責任でご利用下さい。

  
[ AppNetBlocker.apk ]
md5sum [CC8104C9DDE44AD308F09FF22B551575]

AppNetBlocker とは?

Android 端末上のデータを狙うマルウェアの問題が取り沙汰されていることもあり、アプリに付与された「許可」の内容は何かと気になります。 特に、それがネットワークアプリやバナー広告を表示するアプリではなく、また、機能面でインターネットへのアクセスが必須とは考えにくい内容のアプリであるにもかかわらず「完全なインターネットアクセス」許可を持っている場合は悩ましいですね。 そんな時には AppNetBlocker が役に立つかもしれません。 続きを読む

klab_gijutsu2 at 10:00|この記事のURLComments(22)TrackBack(0)
2011年11月22日

AndroidからL2TP/IPsec CRT VPNに接続する

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!

Android端末には、標準でVPN接続機能が搭載されています。

その中でもL2TP/IPsec方式では、x509証明書による認証がサポートされていて、端末ごとに個別の証明書を発行するなどの方法でセキュリティの高いVPN接続を行うことができます。

KLabでは、社内用の認証局を運用していて、社員が自分のユーザ名の入ったクライアント証明書を持てるようになっています。
管理用サイトへのブラウザでのアクセスのほか、社内の無線アクセスポイントもWPA2 Enterprise EAP-TLS認証に対応させて、クライアント証明書による強力な認証を活用しています。
もちろん、万が一の秘密鍵流出の際には、認証局側でCRLにより証明書を失効させることができるよう整備しています。

本記事では、クライアント証明書を使ったAndroid端末からのL2TP/IPsec VPN接続を試してみたいと思います。

Android 2.2系までの端末では・・・

残念ながら、Android 2.2系までの端末では、組み込まれている racoon(ipsec-tools)に問題があり、x509証明書による認証が実質的に使えない状態です。理由は以下のとおりです。

IPsecでは、IKEの認証フェーズ1にて、お互いのIDを交換します。
このIDには、「ID_IPV4_ADDR」・「ID_FQDN」などいくつかの種類があり、x509証明書を認証に使う場合には、証明書の識別名を使う「ID_DER_ASN1_DN」というIDタイプを使うことができます。

本記事のように、接続元IPアドレスを固定せずx509証明書を使う環境では、ID_DER_ASN1_DNタイプのIDを使うのがベストですが、Android 2.2系のracoonでは、「L2TP/IPsec CRT VPN」を選んでいても常にID_IPV4_ADDRタイプのIDがサーバに通知されてしまいます。

x509証明書による認証で、ID_IPV4_ADDR等をIDに使うことは可能ですが、この場合は使用する証明書のsubjectAltName拡張等のフィールドに「IP:192.168.0.1」等の値を含めておく必要があります。
そしてモバイル端末では、キャリアから動的に割り当てられるIPアドレスを使いますから、事前にIPアドレス等を記載した証明書を用意することも難しいです。
このため、Android 2.2までのOSを搭載した端末では、証明書によるIPsecは実質使えないのです。

この問題は、バグレポートされていて、Android 2.3では改善されています。 そのため、以降はAndroid 2.3系のOSを搭載した端末を前提とします。

認証に使う証明書とCA

認証に使うクライアント証明書・サーバ証明書、発行するCAには、Android端末の実装上、いくつかの制約があるため注意が必要です。

  • サーバ証明書のコモンネームには、サーバのFQDNを記載する

一般のWebサーバ用の証明書と同様に、コモンネームに記載されたFQDNと、端末の設定画面に入力した接続先サーバ名は一致する必要があります。

  • サーバ証明書は、シングルルートCAから発行する

サーバ証明書やクライアント証明書の発行元は、いわゆるオレオレ認証局を使うことができますが、自己署名しているルートCAから直接発行する必要があります。

Android端末のIPsecの処理では、端末ビルトインの証明書ストアは使われず、設定画面で指定したクライアント証明書とCA証明書だけが認証に使われます。ですから、サーバ証明書の検証に必要なルート証明書は、CA証明書としてインポートしてあげれば良いことになります。
しかし、Android端末のCertInstallerでは、複数の証明書をまとめてインポートできない仕様らしく、PEMフォーマットの証明書を複数連結したファイルをインポートしようとしても、「CA証明書1件」と表示されて、中間証明書を含めてインポートすることができません。
IPsecの認証プロトコル中でやり取りできる証明書も各方向1枚だけのため、インポートしたルート証明書から直接サーバ証明書を発行するしか手がありません。
(IKEのプロコトル仕様では、複数の証明書ペイロードを送ることができます。 実際にstrongSwanに改造を加えて実験してみましたが、なんとracoon側が1つ目の証明書ペイロード以外を無視する実装になっていたため失敗しました・・・)

  • クライアント証明書については、自由度は高い

クライアント証明書の検証は、サーバ側の仕事ですので、サーバに必要な中間証明書をインストールすればどのような証明書でも使用することができます。

openswanやstrongswanでは、クライアントのID_DER_ASN1_DNタイプのIDにワイルドカードを指定できるため、証明書の識別名についても自由度があります。
「rightid = "C=JP, ST=Tokyo, O=KLab Inc., CN=*」と設定すれば、各ユーザに個別に発行している「C=JP, ST=Tokyo, O=KLab Inc., CN=USERNAME」のような識別名の証明書をすべて使えるようになります。

端末への証明書・秘密鍵のインポート方法

インポートするクライアント証明書と秘密鍵は 「.p12」拡張子のPKCS#12形式、CA証明書は「.crt」拡張子のPEM形式もしくはDER形式にして、SDカードのルートかdownloadディレクトリに置きます。
Android端末のCertInstallerは「.p12」または「.crt」の拡張子の付いたファイルのみインポートしようとしますので、必ず所定の拡張子にする必要があります。

インポートは、設定の「現在地情報とセキュリティ」メニューの「SDカードからインストール」から行えます。

証明書のインポート画面

それぞれ読み込みできると証明書名を聞かれますが、これは後ほどVPN設定を行う際、証明書を選ぶメニューでの名前になりますので、わかりやすい名前を設定しておきます。

Android端末では、インポート済みの証明書の一覧表示や削除のインタフェースが充実していないので、証明書名には「シリアル番号+コモンネーム」等、証明書の個体を識別しやすい名前にするとよいでしょう。

サーバサイドの準備

Android端末からのVPN接続を受け入れるサーバ側の構築を行います。

使用するソフトウェアは、以下のとおりです。

  • openswan-2.6.36
  • xl2tpd-1.3.0 (後述のチューニングのためにパッチ適用あり)
  • ppp-2.4.5

それぞれ、ビルドオプションに特別なものを指定する必要はありません。

カーネルのESPサポートは、linux-3.0.4の標準のものを使用しています。
また、LinuxでのIPsec実装は、openswanのほかにstrongswanもありますが、strongswanではVPN切断後の再接続が上手くいかないという問題が発生したため、openswanを使用します。

Android端末からのL2TP/IPsec接続では、VPN切断時にサーバ側のSAをうまく削除してくれないという問題があるようで、サーバ側にlifetime時間までSAが残ってしまいます。strongswanを使用するとSAが残っている間は再接続(2本目のSA)が出来ず通信不能になってしまいました。

openswanの設定ファイル(ipsec.conf)は以下のとおりです。

config setup
    interfaces = %defaultroute
    syslog = auth.error
    plutodebug = "control"
    uniqueids = no
    nat_traversal = yes

conn Android-L2TP-IPsec
    auto = add
    type = transport
    keyexchange = ike
    auth = esp
    authby = rsasig
    pfs = no
    keyingtries = 1
    ikelifetime = 8h
    keylife = 8h
    rekeymargin = 10m
    left = %defaultroute
    leftid = "C=JP, ST=Tokyo, O=KLab inc., (略)"
    leftrsasigkey = %cert
    leftcert = Android_IPsec_Server.pem
    leftupdown = "/bin/true"
    leftprotoport = 17/1701
    right = %any
    rightca = "C=JP, ST=Tokyo, O=KLab Inc., (略)"
    rightid = "C=JP, ST=Tokyo, O=KLab Inc., (略)"
    rightrsasigkey = %cert
    rightprotoport = 17/%any

読み込む証明書の指定(leftcert)、各ID等は環境に合わせて調整する必要があります。

サーバ証明書を/etc/ipsec.d/certsに、秘密鍵を/etc/ipsec.d/private、クライアント証明書の検証に必要な中間CA証明書を/etc/ipsec.d/cacertsにそれぞれ設置します。
ファイル名は、OpenSSL流のハッシュ形式にしなくても自動的に読み込んでくれます。
また、秘密鍵の復号化に必要なパスワードは/etc/ipsec.secretsに以下のように記述します。
(leftcertに指定しているAndroid_IPsec_Server.pemに対応する秘密鍵が、/etc/ipsec.d/private以下にAndroid_IPsec_Server.keyの名前で保存されている前提です。)

: RSA Android_IPsec_Server.key "hogefuga"

xl2tpd側の設定ファイル(xl2tpd.conf)とpppオプション(ppp-options)は以下のとおりです。
トンネルに割り当てるIPアドレス(local ipやip range)もお好みで選んでください。

[global]
listen-addr                 = 0.0.0.0
port                        = 1701
debug network               = no
debug state                 = no
debug tunnel                = no

[lns default]
pppoptfile                  = /etc/xl2tpd/ppp-options
hostname                    = gw1
exclusive                   = No
local ip                    = 10.100.254.1
ip range                    = 10.100.254.128 - 10.100.254.254
length bit                  = yes
require chap                = yes
refuse pap                  = yes
require authentication      = yes

pppoptfileでは、認証可否の設定の他、Android端末に通知するDNSサーバを設定します。

auth
refuse-pap
require-chap
ms-dns 10.100.254.1

上記設定では、CHAP認証を行う設定になっています。許可するユーザ名・パスワードは/etc/ppp/chap-secretsに記述します。

USERNAME * "PASSWORD" *

PAP/CHAPともにrefuseとすると、L2TP上では認証しない設定にすることができますが、Android端末ではユーザ名・パスワードの入力が必須になっているため、ダミーのパスワードを入力する必要があります。

Android側からのVPN接続を行うと、端末からのすべての通信がトンネルを経由してサーバへルーティングされます。
サーバ側では、トンネルごとにPPPインタフェースが作られますので、適宜インターネットや社内のシステムに対してルーティング・アクセス許可してあげれば通信可能になります。
名前解決に使われるDNSサーバはpppoptfileのms-dnsで指定したサーバが使用されます。

Android端末サイドのVPN設定

Android端末に、VPN接続のプロファイルを作ります。

設定の「無線とネットワーク」から「VPN設定」とたどります。
「VPNの追加」をタップして「L2TP/IPsec CRP VPNを追加」を選びます。

各項目を以下のように設定して保存すると、プロファイルが作られます。

  • VPN名: 任意の名前を設定します
  • VPNサーバの設定: 接続するサーバのFQDNを指定します
  • L2TPセキュリティ保護を設定: チェックを外したままにします
  • 証明書を設定する:インポートしたクライアント証明書を選びます
  • CA証明書を設定する:インポートしたCA証明書を選びます
  • DNS検索ドメイン: 社内のドメイン名等を任意で設定します
VPNプロファイル画面

保存後、VPN欄に現れたプロファイルをタップして、サーバのchap-secretsに記述したユーザ名・パスワードを入力し接続します。

画面上部に「VPN [プロファイル名] が接続されました」と出ればVPN接続は完了です。
切断する場合には、プロファイルを再度タップすれば切断されます。

VPN接続中に通信できなくなる問題

筆者の環境では、長時間接続していると、突然通信不能になる症状が発生しました。

調査してみたところ、キャリアあるいは電波状況によっては、不定期的にパケロスする状況が発生するようで、L2TPサーバ側で定期的に送っているHelloメッセージがロスし最大再送回数に到達してしまったためにトンネルが閉じられているという状態でした。

Android端末側から、L2TPトンネルの切断を検知できるようなメッセージを送出できると良いのですが、端末標準の設定インタフェースでは実現できないため、サーバサイドで出来る限りの調整を行なってみます。

今回使用したL2TPサーバ(xl2tpd)では、HelloメッセージによるKeepaliveの間隔(60秒)、コントロールメッセージの再送間隔(1秒)・最大再送回数(5回)がソースコード中にハードコードされています。

この再送条件では、電波状況等により通信品質が変化するモバイル環境には厳しすぎますので、設定ファイルからパラメータを変更できるようにするパッチを書いてみました。

xl2tpd-1.3.0-add-ctrl-retrans-opt.patch
(2012/02/17:コメントにて、1.3.1では上記パッチが当たらないという連絡をいただきましたのでdiffを取り直しました: xl2tpd-1.3.1-add-ctrl-retrans-opt.patch )

このパッチを適用すると、設定ファイルのglobalセクションに以下の3つのパラメータを設定できるようになります。

  • hello delay (Helloメッセージの送信間隔)
  • ctrl retrans max (最大再送回数)
  • ctrl retrans delay (再送回数)

各パラメータをそれぞれ、Hello送信間隔を5分(hello delay = 300)、コントロールパケットの再送間隔を10秒(ctrl retrans delay = 10)で最大再送回数を18回(ctrl retrans max = 18)とすれば、最大3分程度通信不能な時間が発生してもトンネルを閉じられずにすみます。
長すぎる値を設定すると、本当に端末がオフラインになってしまっていても、長時間サーバ側にトンネルが残り続けてしまうため注意が必要です。

このパッチによるパラメータの変更は、あくまで切断される条件の緩和ですので、確実にVPNが切れなくなるわけではありませんが、多少の効果はあると思います。


#dSn

klab_gijutsu2 at 17:49|この記事のURLComments(9)TrackBack(0)
2011年10月25日

エンコードされた AndroidManifest.xml を読む

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
アプリケーションマニフェストである AndroidManifest.xml をはじめ、Android アプリを構成する各種 XML ファイルは apk へのパッケージングの段階でパースされ独自のバイナリ形式にエンコードされます。 このファイルを扱う処理をコンパクトに実装したいと思ったのですが、現時点では形式に関する公式の資料が存在しないことがわかり aapt を参考に手元でフォーマットの分析を行いました。パーサ試作例とともにその内容を公開します。

1. データ例

(A) テストアプリ「MyApp」用に記述した生の AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.klab.sample.myapp"
    android:versionCode="1"
    android:versionName="1.0">
    <uses-sdk android:minSdkVersion="4" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MyApp"
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:excludeFromRecents="false"
            android:configChanges="orientation|keyboardHidden">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

(B) パッケージ後の MyApp.apk に含まれる エンコードずみ AndroidManifest.xml のダンプ

(C) 試作コードにより上記 (B) に含まれる情報を XML ツリー形式に再構成したもの

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="1.0"
    package="jp.klab.sample.myapp">
    <uses-sdk android:minSdkVersion="4" />
    <application android:label="@0x7F050001" android:icon="@0x7F020000">
        <activity
            android:label="@0x7F050001"
            android:name=".MyApp"
            android:excludeFromRecents="false"
            android:launchMode="2"
            android:configChanges="0x000000A0">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
続きを読む
klab_gijutsu2 at 08:47|この記事のURLComments(0)TrackBack(0)
2011年08月31日

Android 上のアプリから SSL クライアント認証の必要なサーバへアクセスする方法

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
Android の Web ブラウザが SSL クライアント認証に対応していないのは不便だという話を以前このブログに書きました。記事では、Android 上の stone に SSL まわりの処理を代行させる方法について触れました。
Android 上のブラウザから SSL クライアント認証の必要なサーバへアクセスする方法


それから半年、本家の Android 標準ブラウザは現在も未対応のままですが、「SandroB」のようにクライアント認証に独自対応したブラウザも登場し始めています。こうした動きは Android ユーザとしてとても喜ばしいことで、対応環境の拡大や機能面の向上など今後の進化が大いに期待されます。

一方で、たとえば KLab の「VPN-Warp」のような SSL-VPN システム経由での利用が想定されるアプリケーションは Web ブラウザばかりではありません。私の場合、出先から自宅 PC へのアクセスには VNC を VPN-Warp+stone 経由で使っています。VNC ビューワそのものは SSL に対応していませんが、通信に stone を介在させることで所定のアプリ本体が SSL まわりの実装を持っていなくてもそういう使い方が可能となるわけですね。もちろん、人によってはここで言う「所定のアプリ」に、「普段使い慣れているブラウザ」が該当する場合もあるでしょう。

冒頭の記事を書いた時点では Android 用 stone は何かと面倒な CUI ベースでしたが、現在は通常の Android アプリとしてマーケットで公開しています。社内からの声もあり、今回はこのアプリ版に固有の Android キーストアへ証明書をインストールする機能に関する話題に触れながら、stone for Android でクライアント証明書を利用する手順等を紹介します。 続きを読む
klab_gijutsu2 at 10:06|この記事のURLComments(0)TrackBack(0)
2011年08月09日

root 化ずみ端末に対応した Android アプリを書く方法

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!
Android 端末を root 化するユーザが増えています。
一部の開発者向け製品以外では root 権限を取得するための公式の手段は提供されていないので、その方法を自分で探索するにせよ誰かが開拓した手順をトレースするにせよすべては自己責任であるわけですが、一方で root 権限を必要とするアプリはマーケットにもあれこれ出回っており、つまりはこの件に関する安全性と利便性のトレードオフに満足できない向きが世界中に大勢いるということでしょう。

その状況自体もいろいろ興味ぶかいのですが、ところで、その「root 権限を利用するアプリ」というものはどうやって書けばいいのでしょう?Google 公式の開発者向け資料は言うに及ばず、その他のリソースにも今のところほとんどこの話題に関する情報は見当たりません。そこで今回は、実際に手元のアプリを root 権限での実行に対応させる試みを通じて得た情報やノウハウを紹介したいと思います。 続きを読む

klab_gijutsu2 at 21:00|この記事のURLComments(4)TrackBack(0)
2008年01月07日

Ant とテキストエディタではじめる Android

register to: はてなブックマークに登録 | del.icio.usに登録 | この記事をクリップ!

皆様、新年あけましておめでとうございます。今年もよろしくお願いいたします。

さて、The Open Handset Alliance が発表した Android SDK ですが、既に Eclipse + Eclipse Plugin を使用してお試しになられた方もいらっしゃるかと思 います。

私は通常の開発業務で Eclipse を使っているのですが、plugin を入れすぎたせ いか動作が重いので、もっと軽い環境で開発できたらと思ってました。という 訳で Emacsと か vi などのテキストエディタを使って Android のアプリケーション を開発できる方法をご紹介します。

続きを読む
klab_gijutsu2 at 11:38|この記事のURLComments(0)TrackBack(1)
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在7ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
KLabについて
KLab株式会社は、信頼性の高いクラウドサービス、ソフトウェアパッケージ、自社で企画・開発したソーシャルアプリやデジタルコンテンツを提供しています。
Blog内検索
最新トラックバック
Archives