au ケータイで公式IMAP その2

d:id:RobinEgg:20120909:p1 で書いた EzWeb メール(@ezweb.ne.jp)への IMAP アクセスに関する話ですが、どうやら改悪後、さらに更新があったようで、最終的には端末との紐付けを行った au ID によるログインで、設定の変更が可能になったようです。

ただし、

  • 設定画面への接続は au の回線(3G/LTE/WiMAX)を経由しなければならない
  • User Agent が iPhone でなければならない

ことは変わりないようです。

2013年夏頃を目処に IMAP アクセスをオープンにするとの公式アナウンスもあることから*1、これらの制限については近日中に撤廃されるのではないでしょうか。なお、変更画面のアドレスは

https://set.mail.ezweb.ne.jp/md/signin

です。

公衆無線LANサービスの自動ログインスクリプト

Python 版 WISPr クライアントはじめました。

国内で展開している、主に携帯キャリアの公衆無線LANサービスに対応した自動ログインスクリプトを公開します。

これまで色々と各キャリアの無線LANサービスについて書き連ねてきたわけですが、ここらでひとつコードとして纏めておこうと思い、さくっと書いてみました。NTT系以外*1の公衆無線LANサービスで動作確認を行っています。


ソースはこちら: tamias / PyWISPr / source / — Bitbucket

作成の経緯

そもそもは3G回線契約を持たない Android 端末でどうにか簡単に使えないかなというのが発端であり、純正アプリは軒並み低評価ということもあって、まずは Android 版の作成を考えていました。

が、1から作るのはなぁ…と思っていたところ、オープンソース(GPL)で公開されている 1st-wispr client の存在を知り、初めのうちはこれをベースにある程度使い物になるところまで作りかけたのですが、公開することを考え始めると、このアプリ、作者が台湾の方のようで、ソースコード内に台湾で展開しているサービスに特化した部分が複数あり、それらを逐一書き換えていくのはあまりにも骨が折れる作業だったため、結局フルスクラッチで書き上げることにしました。

似たようなスクリプトは既に「au Wi-Fi SPOTにLinuxから接続できるようにしてみた - Gマイナー志向」で作成されていましたが、あちらはユーザー情報を逐次入力するという、キーボード端末前提で書かれていたこと、また特別なUser Agent指定が必須の一部サービスには対応していなかったこと、そして何より Perl で書かれていたため、新たに作成し直すことにしました。

新規に作成するにあたり、 WISPr プロトコルへの準拠度を高めようとしましたが、以前のエントリでも書いたとおり、現行バージョンの2.0ならともかく、1.0を使うサービスが大勢を占める現状では準拠という言葉を使えないものの、それなりに想定されうるロジックは網羅しているはずです。

言語については携帯端末でもノートPCでも動作可能なものをと思い、Air/JavaScript などのより移植性の高い言語も考えたのですが、最終的になんだかんだで使いやすく、また Android であれば SL4A を通せば携帯端末でも動作する Python に落ち着きました。こちらの対応版は併せて PyWispr_sl4a.py として公開しています。中身というか処理ロジックは全く同じですし、大本の PyWispr.py も Android/SL4A 上で普通に動きますが*2、動作通知やパスワードファイルの保存位置などが携帯端末環境に適したコードになっています。

各社、というか主にソフトバンクモバイルワイヤ・アンド・ワイヤレスですが、User Agentを見て自社端末かどうかの識別を行っているようで、前者は特定の文字列がないと自社端末として見なさず、ログインさせないという方針をとっており、後者の一部のアクセスポイントでは逆に他社を特定可能な一部の文字列が存在すると、そもそもレスポンスを返さないという挙動を示すものがあります。これらの処理のせいで煩雑なコードになってしまっており、もうちょっとどうにかしてくれないかなぁというのが正直なところです。キャリアロックインを進めるのを止めるわけではありませんが…。

このような事情から、認証情報を記入する secret.xml に、ユーザーID、パスワードに加えて User Agent を記載しています。通常は User Agent 値を変更する必要はないものの、変更することは可能ですが、変更に伴って接続ができなくなる場合がありますのでご注意ください。

動作確認環境

標準ライブラリのみを使用して作成していますので、別途必要なライブラリはありません。

ただ、 XML のパースに ElementTree を使用していますので、これが標準で入っていないPython 2.5以前のバージョンでは別途導入およびコードの修正が必要です。その他、バージョン更新に伴って新たに導入されたメソッド群が存在している可能性がありますので、なるべく最新の環境で動作させることをお勧めします。 → Python Download

PyWispr
PyWispr_sl4a

つかいかた

  1. アクセスポイントへのWEP/WPAキーを入手し、OSに登録する cf) network/公衆無線/ホットスポット一覧 - Tomocha WikiPlus
  2. secret.xml を編集し、利用するサービスの認証情報を記入する*3
  3. 実行スクリプト(PyWispr.py もしくは PyWispr_sl4a.py)と secret.xml を同階層に保存する
  4. 外出し、認証情報を登録したサービスの無線LANアクセスポイントに接続する
  5. スクリプトを実行する
  6. 通常の環境の場合、ログインに成功すると Enter でログオフするか Ctrl+C で終了するか問われますので、状況に応じてご利用ください

注意点

  • 自己責任でご使用ください。サービスによっては、明確に自社配布アプリ経由以外での接続を禁じている場合があるかもしれません。
  • スクリプトでは、SSL証明書の確認は実施していません(Python 側の仕様です)。成りすましAPにはくれぐれもご注意ください。
  • SL4A では /sdcard/sl4a/scripts が保存フォルダになります(環境依存の可能性アリ)。ただし、実行時のCWDは /sdcard/sl4a です。
  • au_Wi-Fi の一部のアクセスポイントではログオフ機能は機能しません。これはログオフ機能のAPIがAP内のアドレスとなっているのに対し、認証に成功すると二度とAP内のアドレスを参照することができなくなるというサービス側の仕様によるものです。


スタバでのドヤリングのお供にも、コンビニ駐車場でのネットにも、どうぞご利用ください。

*1:非フレッツ&OCN契約でもないため、NTT-SPOT/HOTSPOT のアカウントがなかったもので…

*2:secret.xml へのパスの書き換え、あるいはファイル配置を修正する必要があります

*3:au wi-fi ではこの時点で接続端末の登録を済ませておく必要があります。

WISPr について

図らずも数ヶ月に渡って公衆無線LANサービスについて色々と書いてきましたが、その中で出ていたWISPrについての説明を少々しておこうと思います。

そもそもWISPrとは、 "Wireless Internet Service Provider Roaming"の頭文字を取ったものです。ここからわかるとおり、無線ISP間のシームレスなローミングを目的として策定が行われています。

Ver.1.0 は Wi-Fi Alliance 主導の下、2003年2月に策定が行われました。このバージョンでは、バックエンドにRADIUSを、フロントエンドにHTTPを用いた形が想定されており、Appendixという形で802.1xによる認証方式が記載されています。

が、策定時点において既に規格よりも実装が先行していたため、あくまでも「ベストプラクティス」集として制定されました。このため、特許など知的財産権侵害の可能性検討など、標準規格として必須の処理が行われないままに策定されてしまい、その辺りの処理は実装ベンダーに任されるような形になってしまいました。また、RFC2119で示されるような、いわゆる SHOULD/MUST/MAY を用いた実装要求水準が示されておらず、実装側もどの程度まで盛り込めば「準拠」と名乗れるのかがわからないという代物だったので、これらの問題点の解決を目標に、2010年4月、今度はWireless Broadband Allianceが主導してWISPr 2.0 が策定されました。

2.0では、1.0のAppendix Dで規定されていた"Smart Client to Access Gateway Protocol"を拡張するという形で規格が決められており、クライアント側の接続仕様(要はフロントエンド側)に焦点を絞ることで、よりクリアな規格とするような努力が払われています。また、このAppendix D以外に関する規定、例えば802.1xに関するものなどは全て棄却されており、代わりにEAP(Extensible Authentication Protocol)への統合が試みられています*1

もちろん、旧来の規格を捨て去るわけにはいかないので、後方互換性を取るよう図られています。が、個人的には一般的な公衆無線LANサービスにはEAP方式の認証は重すぎることもあり、2.0系はあまり普及しないのではないかなと思っています。また、国内で既に展開済みの携帯キャリアによる公衆無線LANサービスでは、ドコモが接続開始時には2.0でアナウンスし、その後1.0への移行を宣言、KDDI系(au/wi2)、ソフトバンク系(0001softbank/mobilepoint)は接続開始時から終了時まで全て1.0となっています*2

仕様書とXML Schemaはそれぞれ以下の場所で配布されています。

WISPR 2.0

Wireless Broadband Alliance の公式ページでメールアドレスを入力すると、ダウンロードサイトのアドレスが送られてきます。

*1:が、EAPにおいても特許関係の問題はそのまま店晒しのようです

*2:au_Wi-Fiに至っては、2.0でdeprecatedとなった PollNotification を使っててアレな感じ

Docomo Wi-Fi のWISPr対応について

先日、Docomo Wi-Fi に接続したところ、表記のとおりWISPrプロトコルに対応されていることが確認できました。
これで、国内携帯キャリアが提供する公衆無線LANサービスはすべてWISPrプロトコルに対応したことになります。
対応バージョンは2.0フォーマットで1.0でした*1。WISPr認証情報は初回アクセス時の302 Redirect中の Response Body 内に記載されていたので、 au Wifi と同様ですね。

全キャリア対応のAndroid版WISPr接続アプリをつらつら書いてみたので、そのうち公開するかもですね。

*1:何を言っているかわからないと思うので、次項参照

au ケータイで公式IMAP

更に追記

2013.04.01 現在、au ID によるログインで設定変更が可能になったようです。新たに記事を立てましたので、詳細は d:id:RobinEgg:20130406:p1 をご覧ください。

追記

2012.12.03現在、改悪されてしまったようです。

下記に記載している送信先番号の「00900000」は「00900015」に変更になり、この番号にSMSを打った時点で強制的に非公開モードに変更されてしまうようです。

下記でも記載していますが、非公開とはいえ、中身はIMAPなのですが、ID/Passを公開してくれないため手も足も出ないようです。

iPhone専用番号である「#5000」はまだ生きているのでこちらに送信してやれば何とか使えはするのですが、下記記載の通り、一部端末を除くと純正メッセージングアプリはおろか、Google Playで公開されているようなSMSアプリケーションを使用しても特殊番号への送信ができない、というのが現状であるため、今後は望み薄だと思われます*1

しっかし、なんでこんな改悪しますかね。Mediba ad の IS NETシュリンクラップ契約も大概ですが、最近ほんと酷い > KDDI

以下、公開当時の文章

いまだに au one メールとか言ってる情弱はお呼びじゃないですね。

au では、昨秋のiPhoneの導入に伴って公式にIMAPサーバーとSMTPサーバー(とExchange(ActiveSync/DirectPush)サーバー)を一般に公開しました。これはすなわち、

  • どこの回線からでも、どんなIMAPクライアントからでも、ezweb Mailを確認することができる
  • さらにキャリアSMTPサーバーを経由してメールを送信することができる

という、これまでの閉じた網からの脱却という観点からは非常に大きな出来事です。

既にSoftbankも同様の対応をしていましたが、こちらはiPhoneに限った扱い*2となっており、キャリア提供の全端末で接続回線の制限なくIMAPサーバー公開というのは携帯キャリアで初めての対応ではないでしょうか*3

各方面で散々指摘されていますが、auのメールシステムはどうやら以前から内部的にはIMAPであったこともあり*4、今回比較的スムーズに公開することができたのかなと思います。ガラケーでもSMSを通知トリガーにした中身はIMAPだったようで、SMS受信後IMAPサーバーにアクセスしてメールを表示、というシステムだったようです。

今回は、ガラケー、あるいはAndroid端末でIMAP認証情報を取得するためのTipsを紹介します。

なお、同様の手順でいわゆる ActiveSync/DirectPush と呼ばれる、Exchange互換のリアルタイム受信(Android端末上の表記はコーポレート同期)を使用することもできます。ただし、この設定を行った場合、iPhone以外の端末では通常のEzメールアプリでの受信はできなくなりますのでご注意ください*5

手順としては、既にiPhone向けに公になっているSMSによる設定方法とまったく同じです。iPhoneの場合、宛先に #5000 を設定したSMSを送信することで設定用のページを取得することができましたが、ガラケー、もしくはAndroid端末ではこの宛先に送信できないものが多かったため、専用のアドレスに送信することになります。

公式に発表されている手順はこちら。(PDF原本

  1. SMS(宛先: 00090000、内容: 1234)で設定用アドレスを取得
  2. 取得したアドレスにUser AgentをiPhoneに偽装して、auの回線(含WiMAX)から接続。接続元は確認されないため、認証情報を取得したい回線とは異なる回線から接続しても問題なし。
  3. 設定完了後、認証情報がSMSで送られてくる

たとえばガラケーAndroid端末の二台持ちの場合、ガラケー宛のメールもAndroid端末で確認したい、という場合、ガラケーからSMSで 00090000 に送信すると設定ページのURLが送られてくるので、そのURLにAndroid端末から接続し、設定に成功すると、ガラケーに認証情報がSMSで送られてくる、という寸法です。

なお、多くのAndroid端末では、アドレスバーに

about:debug

と打ち込んでデバッグモードに移行してやると、設定メニューからUserAgentをiPhoneに変更することが可能ですので、この状態でアクセスするとすんなり設定を行えます。

*1:特にガラケーなんてどうしようもないですしね…

*2:i.softbank.jp アドレスのみ対応

*3:WillcomってPOP/IMAPできたっけ?自社回線からだけだったような。もしかしたらEMobileって既に可能?

*4:一応、PacketOne/PacketWin時代から自社回線からの接続は可能だった

*5:ガラケーだと全くメールを受信しなくなります

0001softbank、あるいはソフトバンクWi-Fiスポットに関する一考察

前回KDDIWifiスポットに続き、ソフトバンク版についても認証プロセスを考えてみたいと思います。
結論から言えば、アクセスしてくる端末のUserAgentを見て挙動を変えているという話に帰結してしまうのですが、それだけでは面白みが少ないのでもうちょっと話を広げてみましょう。

ソフトバンクWi-Fiスポット、とは

公式ページはこちら。言わずと知れたキャリアによる公衆無線LANサービスの草分けであり、スポット数は公衆無線LANサービス中でもダントツのトップを誇ります。
しかしながら、絨毯爆撃のような営業に伴って起こっている2.4GHz帯の汚染、またバックホールを1.5GHz帯の自社回線(ULTRA SPEED)としたスポットの増大による回線帯域の無駄遣いという批判*1など、いろいろと問題が提起されているサービスでもあります。

そうはいってもスポット数は圧倒的であり、3G/LTE/WiMAX回線を持たない端末を使う場合には重宝するサービスでもあります。最近ではソフトバンク側もそれを理解しているのか、初代iPadの2年縛り契約がそろそろ終了するのを見越してかソフトバンクWi-Fiスポット(EX)というサービスを始めたりもしています。

サービスの対象には、ソフトバンクモバイルが販売する

挙げられています

アクセスポイントのSSIDとしては、認証不要の「0001softbank」および偽FONと呼ばれる「FON」に加え、WPA2による認証に対応した「0002softbank*2」が設定されています。これらのAPはソフトバンクモバイルによるものですが、他にも、同じくソフトバンクグループのソフトバンクテレコムが提供する、マクドナルドなどでよく見かける mobilepoint も使うことができるうえ、認証系がソフトバンクモバイル提供のAPと共通となっている為、本稿の内容をそのまま適用することができます。

なお、現在無料体験実施中とのことらしく、パケット定額プランに加入していない端末(一説では電話番号さえあれば良いとか…)でも接続が可能となっています。

iPhone /SoftBankスマートフォン/モバイルデータ通信端末(ソフトバンクWi-Fiスポット対応機種)をご利用でソフトバンクWi-Fiスポットに未加入のお客さまは、期間限定で無料にて体験いただけます。(無料体験は、2012年11月30日迄。当社の都合により無料体験期間を変更する場合があります。)

ご利用いただけるお客さまについて | ソフトバンクモバイル

認証系について

基本的に、すべての認証は w-lan.jp 配下のドメインで行われますが、接続元の端末によって動作が変化します。このため、解析が非常に面倒くさいものとなっています。

APに接続後、任意のページを開こうとすると、0001softbank/FONでは plogin1.pub.w-lan.jp に、mobilepoint では www.login*.w-lan.jp (*は数字)にリダイレクトされ、上記で挙げた対応端末ごとに用意された認証に関するメッセージが表示されます。

なお、mobilepointは各社からのローミングを受け入れている関係上、どのクライアントから接続しても、一応は認証情報の入力画面が表示されます。SoftBank端末からローミングアカウントでのログインも考えられるため、例えばiPhoneで接続していても Yahoo!Wireless のアカウントでログインすることも可能です。以下に示す各パターンは、0001softbank/FONにアクセスした場合のものとお考えください。

iPhoneの場合

UserAgentが

 Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9A405 Safari/7534.48.3

の場合。

未認証だと、初期登録を sbwifi.jp で行うよう指示するメッセージが表示されます。このサイトではプロファイルを登録するよう促されますが、認証のキモはその前のページリダイレクトにあります。
iPhone の場合、認証キーの実体は接続ID(Eメール(i)のアドレス)と、パスワードと思われる何らかのハッシュ化文字列をBase64エンコードしたものを含むCookieであり、Safariに対して2038年まで有効なCookieとして埋め込んでいるようです。

Cookieの中身は、

fon.com
MBN=(Strings encoded by BASE64), fonsb=(Hashed strings); Domain=fon.com; Expires=Mon, 18-Jan-2038 15:00:00 GMT; Path=/; Secure
lw.livedoor.com
MBN=(Strings encoded by BASE64); Domain=lw.livedoor.com; Expires=Mon, 18-Jan-2038 15:00:00 GMT; Path=/; Secure
wi2.ne.jp
MBN=(Strings encoded by BASE64); Domain=wi2.ne.jp; Expires=Mon, 18-Jan-2038 15:00:00 GMT; Path=/; Secure
w-lan.jp
MBN=(Strings encoded by BASE64); Domain=w-lan.jp; Expires=Mon, 18-Jan-2038 15:00:00 GMT; Path=/; Secure

という感じです。最後の w-lan.jp 以外はすべてローミングサービス向けのものですが、

fon.com
fonsb は FON_FREE_INTERNET の認証用でしょうが、端末に関わらず固定値になっている気がします。
lw.livedoor.com
Livedoor Wireless向けと思われますが、この提携は本年6月を以て終了している為、今後は使いようがないと思われます。
wi2.ne.jp
ローミングはwi2単独エリアのみ対応しているそうなので、最近急激に増加している au_Wi-Fi との相乗りエリアは対象外と思われます。

という落とし穴が存在します。

また、MBNというCookieBASE64エンコードされている部分は

BBMP_iPhone hogehoge@i.softbank.jp (32文字ハッシュ(MD5?))

となっており、これは sbwifi.jp に接続せずとも、UserAgentをiPhoneに偽装して、 mobilepoint に接続・認証に成功すると取得することができます。

なお、ここで挙げた認証情報は結局のところCookieでしかないため、上記のドメインを持つホストに接続した場合、自動的に送信されてしまいます。
ただ、仮に悪意のある0001softbankに偽装したAPに接続し、 w-lan.jp ドメインを持つ偽装サイトにリダイレクトされたとしても、一応、これらのCookieにはSecureフラグがついているので、HTTP接続時には送信されませんし、HTTPS接続だとiPhone側でSSL証明書の相違メッセージが表示されることが予想され、接続者のメールアドレスが攻撃者に流出する可能性は低いと思われます。しかしながら、SSL証明書の相違メッセージをどれだけの人が理解しているのか、という観点から考えると、あまり筋の良い実装とは言い難いのも事実です。

iPadの場合

UserAgentが

Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3

の場合。自動認証(おそらくiPhoneと同様の仕組み)に失敗した場合、ログイン用のメールアドレス(i)とパスワードを入力する画面が表示されますので、入力すれば通ります。ただ、契約種別を見ているのか、 iPhone のアカウントでは通らない模様。

Androidの場合

接続用アプリによる接続が必須となりますが、交換機用の暗証番号を入力する必要があること、また導入時に電話番号の読み取り許可が必須となっていることから、電話番号と暗証番号による認証を行っていることが推測されます。

アプリ名にもがっつり jp.co.softbank.wispr.froyo とありますので、WISPrコードを探してみたところ、接続用アプリのUserAgentが

WSP_CLNT;SoftBank/1.0

であると思われたことから、結局、Android端末においてはケータイWifi対応機の認証系を間借りしているのではないか、と推測されました。

ケータイWifi対応機の場合

ケータイWifiとは、ガラケーWifiチップを積み、3G回線以外に高速回線でネットができるということを売りにしたサービスです。一昔前には各キャリアとも力を入れていましたが、スマホの登場に伴いどんどん縮小しており、docomoに至ってはサービスを終了してしまいましたし、ソフトバンクでも2010年秋冬モデルの004SHを最後に新規機種が発表されていません

この004SHのUserAgentは、

SoftBank/2.0/004SH/SHJ001/SN(端末シリアル) Browser/NetFront/3.5 Profile/MIDP-2.0 Configuration/CLDC-1.1

となっています。

このUAに偽装して「0001softbank」にアクセスしてみると、

のようなページが表示されます。このソース中には、

というようにWISPrのコードが記載されており、このURLに Username として電話番号を、 Password として回線暗証番号をPostしてやると、正常に認証が通ります。なお、POST時にもUserAgentを見ているようで、「SoftBank/2.0」などのような文字列を判別できない場合にはログインエラーとなってしまいます。

また、APごとに認証URLが微妙に変化するようですので、決め打ちで実行するのは難しい模様。ちなみに、 mobilepoint でも同様にWISPrコードが表示されるので、同じようにログインが可能です。

これらの結果を受けて、Androidでもこの仕組みを使用しているのであろうことがほぼ確定的となりました。

*1:一応1.5GHz帯はUMTSのグローバルバンドではないので、2.1GHz帯の間引き目的という言い訳はあり得る。と思っていたら、最大手のDocomoがXiをバックホールにした、もっと言い訳の立たないスポットを設置しだしたらしくてドン引き

*2:未だにiPhoneのプロファイル上以外で存在を確認できたことがない

au Wi-Fi および wi2 系公衆無線LAN接続用 Bookmarklet

後編です
認証系を解析した詳細は、前編の d:id:RobinEgg:20120617 に記載しています。

d:id:RobinEgg:20120617 で書いた内容だと、その気になれば OS に関係なく Javascript で認証系組めますよね、ということでざっと書いてみた。動作確認が取れているのは、 KDDI 系の公衆無線LANサービス(wi2/au_Wi-Fi/UQ-WiFi) です。

wi2 系ではログインページ内 XML を切り出して都度読み込ませていますが、 au_wi-fi ではリダイレクト中のレスポンスボディを読み取るのが難しいため、認証 URL を決め打ちにしています。このため、仕様変更があると使えなくなる可能性が高いです。

実際に使用される場合は、

https://auwifi-signup.auone.jp/su2/?{"mac_addrs":["***大文字MACアドレス***"],"manufacturer":"Windows","model":"7","request_type":"0"}

にアクセスして取得した ID およびパスワードで、

  var userid = '***USERID***';
  var pass = '***PASS***';

の部分だけを書き換えてください。

アクセスポイントに接続後、適当なページへアクセスした際に表示される、サービスへのログインページ、あるいは「接続ツールによるログインが必要です」というメッセージ画面にて実行してください。

javascript: (function(){
  var userid = '***USERID***';
  var pass = '***PASS***';

  var authURI;
  var domain = document.domain.substring(document.domain.length - 9);

  if(domain == 'wi2.ne.jp'){
    var de = document.documentElement.innerHTML;
    if(de.indexOf('WISPAccessGatewayParam') < de.indexOf('LoginURL')){
      authURI = de.split('LoginURL')[1].replace(/(^\s+)|(\s+$)|(<)|(>)|(\/$)/g, '');
    }else{
      return;
    }
  }else if(domain == '.kddi.com'){
    authURI = 'https://' + location.host + '/smartlogin';
  }else{
    return;
  }

  if(userid.substr(userid.length - 3) != '@au'){userid += '@au';}
  var ce = function(en, ev){
    var inp = document.createElement('input');
    inp.type = 'hidden';
    inp.name = en;
    inp.value = ev;
    return inp;
  };
  var f = document.createElement('form');
  with(f){
    method = 'post';
    action = authURI;
    id = 'authBookmarkletForm';
    appendChild(ce('UserName', userid));
    appendChild(ce('Password', pass));
  }

  document.body.appendChild(f);
  document.getElementById('authBookmarkletForm').submit();

}())

これで、評価の微妙な純正アプリを入れるまでもなく、快適な無限インターネットを楽しめます。良かったですね。

au wifi の認証方式について

au Wi-Fi SPOTは2012年3月1日からスマートフォンだけでなく、PCやタブレットなど2台目の端末でも利用できるようになりました。(略)ついカッとなってLinuxから接続できるコマンドラインツールを作りました。GitHubにて公開しています。

au Wi-Fi SPOTにLinuxから接続できるようにしてみた - Gマイナー志向

という素晴らしい記事を見かけたので、試してみました。が、初っぱなの端末 MAC アドレスの登録が、どういうわけか上手く行かない。

{"code":"S08"}

とか

{"code":"S26"}

とかが表示されて全く動かず、同じようにハマっている人も見かける始末。

レスポンスの内容をscrapeして,次のURLを取得する処理を行っているが、上記の結果なので、URLを設定できず、次のPOSTでエラーとなっている。
code: S26だけでは調査する情報が少なすぎる。一旦、調査は断念。。。
http://peke2peke2.sakura.ne.jp/wpblog/2012/05/10/68/

仕方がないので、しびれを切らして公式の接続ツールを試しつつ、 sniff してみたところ、au の認証側サーバーが「登録するMACアドレスを大文字で書かないとエラーを吐く」という素敵仕様と判明。まさかそんなところでハマるとは…*1

iPhoneの場合(追記)

iPhone の場合、どうやら購入時点で端末の情報(MACアドレスとIMEI)がKDDIからWi2側へ伝えられているようです。

auiPhoneを購入したり、交換したりすると、その端末のMACアドレスとMEIDをauからwi2側に引き渡される。
 ↓
その端末情報をwi2側で登録する。
 ↓
その端末からのアクセスにはパスワードなしでwi2のWi-Fi SPOTの門戸を開ける。

iPhoneでau Wi-Fi SPOT繋がらない問題やっと解決: jaydashの備忘録

このためiPhone購入者は特段アプリ等を入れずとも即通信が可能になるわけですが、おそらく端末枠はAndroid端末と同様に二台に制限されているのではないかなと思います。手元にiPhoneがないので確認できませんが、iPhoneを使ってau IDを取得の上、端末登録状況を確認するとどうなっているのでしょうか。

それにしても、KDDIといい、SoftBankといいiOS端末ではWISPrを使えないのは何故なんでしょうね?

登録の流れ

au Wi-Fi SPOTにLinuxから接続できるようにしてみた - Gマイナー志向 にも書かれていますが、まずは au 側のサーバーに端末自体を登録してやる必要があります。この作業は1デバイスにつき1度行なってやればよく、この登録作業に対して接続用のIDとパスワードが発行されます。

実際にアクセスポイントに接続する際にはこの接続用のIDとパスワードを用いて認証が行われますが、他のデバイスに対して発行されたID/パスワードの流用はできないため、そもそもデバイスの登録が行われていないと接続すらできない、ということになります。この端末登録作業に「ケータイPC連動設定」がなされた au ID が必要になりますが、必ずしも接続端末はこの au ID に紐付けられた端末でなくとも構いません。

端末の初期登録

つらつら通信を追いかけていくと、結局のところ、登録までは

https://auwifi-signup.auone.jp/su2/?{"mac_addrs":["***大文字MACアドレス***"],"manufacturer":"Windows","model":"7","request_type":"0"}

で一発で通るようです。

別に実際の接続端末のOSが何であれ、後半の Manufacture 等のパラメタはチェックしていないようで、Android 端末を繋いでも問題なく通りました。

このアドレス(「***大文字MACアドレス***」の部分はご自身のデバイスMACアドレスに置き換えてください)に接続すると、自動的に au ID の認証ページである connect.auone.jp 配下のページにリダイレクトされるので、そこで au ID の認証を通してやります。

正常に応答があれば、

{"code":"N22","device_num":"01","max_device_num":"02","passwd":"**PASS**","user_id":"**UID**"}

というような json コードが返ってくるはずですので、これで初回の登録は終了です。ここの "user_id" と "passwd" が使用時認証で必要となるので、書き留めておくこと。

なお、現在の au ID 認証では画像認証が必須となっていますので、 GitHub - matsuu/auwifispot-client: au Wi-Fi SPOT CLI client で公開されているスクリプトでは認証を通過できないと思われます。

端末の登録解除とか

上記サンプルコードでは端末の登録可能台数は2台まで、うち1台目の登録が完了しました(N22)というレスポンスになっていますが、 "request_type" をいろいろ変化させてみると、他にもレスポンスタイプがあることが窺えます。

"request_type" は、

0
通常登録・ID/パスワードの確認(MACアドレス、Manufacturer、Model 必須)
1
パスワードの変更(MACアドレス必須)
2
登録ID/パスワードの両変更(MACアドレス必須)
3
登録端末の列挙
4
端末登録解除(MACアドレス必須)

というオプションがあるようで、それぞれにレスポンス("code" 値)も変化するようです。なお、規定台数以上の端末を登録しようとすると、 "E40" というエラーで怒られます。この場合は端末を列挙させ、不要な端末を削除してください。

なお、当然ながらこれらの作業は au id による認証を通過後、Cookie を保持した状態での作業が必須です。また、(カッコ)で記載したとおり、各情報は MAC アドレスをキーとして識別するようです。

使用時認証

au wifi サービスと銘打っていますが、実際には wi2 系 wifi サービスへのローミングサービスとなっています。AP側がマルチSSID対応になっており、回線のバックホールには WiMAX または固定回線を、バックエンド側は KDDI 網で回収しているようで、認証系と設置作業は wi2 もしくは UQ のお仕事のようです。

なので、接続時には、先の登録作業時に回収した接続用 "user_id" に

@au

を付けて認証を受ける必要があります。 @wi2 とかと同じような感じで。

Wifi AP の認証鍵がわからないという場合は、適当に https://play.google.com/store/apps/details?id=jp.co.netvision.WifiConnectWI2&hl=ja でも入れてやれば良いんじゃないでしょうか。インストールしてアンインストールしてしまえば、WEP/WPA鍵だけ残してくれます。

通常、Hotspot 系の AP では、未認証の状態で何かしらのURLを送付すると自動的にログイン認証ページに誘導する仕組みになっていますが、WISPr の良いところは、この仕組みを逆手にとって認証用画面のソース内にコメントなどの形で認証に必要な情報が記載されているという点です。このため、認証プロセスをすべて Web ベースで処理することができ、結果としてクライアント側で Web API 様の処理を行うことが可能になります。

wi2 系サービスの場合

UQ-Wifi も同じく service.wi2.ne.jp ドメイン配下での認証となり、システム自体は同じようです。ログイン | 公衆無線LANサービス Wi2 300 のソースを見ると、このように XML で WISPr の接続情報がしっかりと記載されています。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<!-- ここから -->
<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGatewayParam.xsd">
<Redirect>
<AccessProcedure>1.0</AccessProcedure>
<AccessLocation>wi2-0000000-0000-00000000</AccessLocation>
<LocationName>wi2</LocationName>
<LoginURL>https://service.wi2.ne.jp/wi2net/RLogin/1/?SSID=c0000000000000000000000000000000</LoginURL>
<MessageType>100</MessageType>
<ResponseCode>0</ResponseCode>
</Redirect>
</WISPAccessGatewayParam>
<!-- ここまで -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>

au wifi の場合

au_Wi-Fi ではどうやら2種類の認証系があり、 service.wi2.ne.jp ドメイン配下での認証となる場合と、ランダム文字列.wi-fi.kddi.com ドメイン配下での認証となる場合があるようです。

どちらもログインページが表示されないのは同じですが、前者の場合は上述の wi2 系と同様、ログイン先の情報が記載された WISPr コードがページ内に記載されているため、この情報を元にログインを行うことができます。一方、後者の場合、接続を試みると「au 接続ツールを使用して接続してください」というメッセージが表示されるのですが、実は WISPr コードはこのページには記述がなく、このページへのリダイレクト命令(HTTP 302)のレスポンスボディ中に埋め込まれています。通常の HTTP クライアントは 200 OK 以外の異常時にはレスポンスヘッダを優先して読み取ってしまうため、レスポンスボディを読み取ることは難しく、筋の悪い実装と言えるでしょう。

mobilepoint の場合

今回の記事の主旨からは若干ずれますが、 mobilepoint でも wi2 系同様、認証ページ内 body 部に WISPr 用コードが埋め込まれています。

mobilepoint は AP の設置場所によって幾つか亜種があるようで、必ずしも全ての AP で API URL など認証系が統一されているわけではないようです。例えば、 N700 系内の AP では Boingo のログイン URL が提示されていたのに、他のマクドナルド系 AP では提示されていなかったり、ということがあります。

mobilepoint の場合、通常のログインページの遷移先がそもそも WISPr ログイン URL だったりするので、 WISPr ログインに iPhone 契約の認証情報を投げ込むと User-agent の確認が行われるのですが、新幹線内 AP で見かけた Boingo のログイン URL では、 User-agent の確認を省いているようです。このような些細な差異を除けば、概ね認証用ページ内に記述があるというのは共通しているようです。

docomo/mzone の場合

docomo は記事執筆時点では 802.1x のみ対応で、 WISPr 対応という話は出ていないようですが、認証用のページにはきっちり WISPr 用と思われるコメントが記載されています。そのうち対応するんじゃないでしょうか。
追記)対応しました。


で、このようにして得られた LoginURL に先の認証情報を POST してやれば、自動的に認証が通って幸せ、ということになります。POSTのパラメータは、

{"UserName": "hogehoge@au", "Password": "fugafuga" }

エンコードしてやればOK。というか、 WISPr による認証系ではこの2つの引数は統一しろ、という話になっているようです。

接続認証用Bookmarklet

d:id:RobinEgg:20120626:p1 に記載しましたのでご覧ください。

蛇足

Lawson Wi-Fi
一時期巷を賑わせたローソン Wifi ですが、接続インフラこそ wi2 系なものの、実際には wi2 を GW というかプロキシとして、さらに上流のどこか(おそらく https://wifi.lawson.jp/WiFiApi/wifiAuth.do )に MAC アドレスと Android ID の登録問い合わせをしているようです。従って、完全にクローズドなネットワークとなっており、こちらを Hack するのは Android 端末の実機でパケットキャプチャするか、エミュレータを使ってじっくり粘るかしないと難しそうです。ただ、一つ気になったのは、接続インフラを wi2 に投げてはいるものの、認証系は独自に構築しているようなので、その辺の法律的な区分けってのはどういうふうになってるんでしょう?少なくともいま手元にある情報だけでは通信事業者とは言い切れないでしょうが、パケットの流れ次第では通信事業者に該当する可能性もあったりするのでしょうか。
0001softbank/FON
Wi-Fi スポット設定ソフトの名称は「jp.co.softbank.wispr.froyo」となっており、 WISPr を使ってはいるらしいのですが、こちらも XML で接続情報が供給されないタイプのもののようです。ただ、Android 端末の場合、認証情報は単純に SIM に記載された電話番号を読み取り、暗証番号と組み合わせて https://wifiota.sbwifi.jp 以下にあるファイルに契約の有無を問い合わせて確認するというもののようであり、そう複雑なスキームにはなっていないものの、やはり Lawson Wi-Fi 同様に解析は厄介そうです。追記: 推測は間違いだったようです。解析した結果を d:id:RobinEgg:20120829:p1 に記載しました。

*1:ちなみに、この公式ソフト for Windows も WLAN AutoConfig サービスを動かさないとそもそも起動すらしないというアレさ加減

複数台の Wiimote をPCに認識させて使う

タイトルの通りですが、snesgtだったりPowerPointだったりいろいろな使い道のあるHIDことWiiリモコンですが、制御ソフトを開発する人の多くは一人プレイヤーだからかどういうわけか1台のみ制御可というソフトが多いところで、複数台(Bluetoothの限界上は7台?)のWiimoteを認識させる場合について。

Wiimote自体は多数の機能を持っているため、例えばモーションセンサやジャイロなどの機能を使おうとすると場合によっては複数のソフトウェアやドライバが必要になりますが、通常のソフトウェアでの使用であれば、キーボードエミュレーションの機能があれば十二分という場合が殆どであり、その場合は GlovePie 一つで事足ります。

また、Bluetoothスタックによるかも知れませんが、少なくとも東芝スタックにおいては、Wiimote自体は汎用HID(ヒューマンインターフェースデバイス)としてシステムに登録されるため、追加のデバイスドライバも必要にはなりません。従って、Win7以降、x64/x86の環境による違いを意識する必要がほとんど無いということも利点となります。

今回はWiimote+GlovePieという例ですが、これはPS3のコントローラーと混載した環境でも成立します。従って、複数人で何かしらのソフトウェアを用いる場合、ソフトウェア側で予め適切にキーの割り当てを行っておき、そのキーに対し、GlovePie側でWiimoteもしくはPS3のコントローラーボタンを各キーに割り当てるという作業を行うことで、多数のWiimoteを使用する環境が成立するという仕組みです。

なお、MotionPlus内蔵のWiimoteは、現時点で最新版の GlovePie 0.45 では対応していないようで、こちらの記事から別途 PIEFree.exe のみ取得する必要があります。

以下に複数台の Wiimote を使用する場合のスクリプト例を記載します。wiimoteオブジェクトの配列、とかではなく、認識した順番に wiimote1, wiimote2,…というような割り当てになるようなので、そのまま順次記載していけば良いと思われます。

wiimote1.Led1=true
key.A=wiimote1.One
key.B=wiimote1.Two
key.C=wiimote1.Up
key.D=wiimote1.Right
key.E=wiimote1.Down
Key.F=wiimote1.Left

wiimote2.Led2=true
key.G=wiimote2.One
key.H=wiimote2.Two
key.I=wiimote2.Up
key.J=wiimote2.Right
key.K=wiimote2.Down
key.L=wiimote2.Left

wiimote3.Led3=true
key.M=wiimote3.One
key.N=wiimote3.Two
key.O=wiimote3.Up
key.P=wiimote3.Right
key.Q=wiimote3.Down
key.R=wiimote3.Left

wiimote4.Led4=true
key.S=wiimote4.One
key.T=wiimote4.Two
key.U=wiimote4.Up
key.V=wiimote4.Right
key.W=wiimote4.Down
key.X=wiimote4.Left

もうパスワードで悩まない!

とある通販サイトで適当にパスワード打ち込んだら、後日思い出せなくなって涙目になったのでカッとなってパスワードジェネレーターを作ってみた。

Bookmarklet にしてご利用ください。Firefox 8.0でのみ動作確認しています。

概要

ソースコードは最下部に記載しています。ドメイン名とマスターパスワード(Salt)から、都度パスワードを算出する Bookmarklet です。マスターパスワードが変わらない限り、同じドメインのページでは常に同じパスワードが発行されますので、もうこのサイトのパスワード何だったっけ、と頭を悩ます必要がありません。ただし、マスターパスワードが漏洩すると元も子もありませんので、マスターパスワードの管理だけはしっかりするようにしてください。

初回実行時には記号使用の有無と文字数を聞かれます。これらの値はブラウザの LocalStorage に保存されるため、履歴が有効であれば次回以降は生成されたパスワードのみが表示されます。

下記ソースをコピーし、都度マスターパスワードを打ち込む場合は

var salt = prompt('Salt?');

に、いちいち打ち込むのが面倒くさい場合は

var salt = '適当なパスワードに書き変えてください';

としてください。書き換える場合は日本語でもアルファベットでも構いませんが、強度的には日本語の方が望ましいです。

詳細

元ネタはこちら。ソースが Obsolete になってたので、書き直すついでに Bookmarklet 化して、HMACを使うようにアルゴリズムを変更。

設定した Salt を秘密鍵に、ドメイン名をメッセージとして HMAC を算出し、求めたバイト列を元に、パスワードとして使う文字列を切り出しています。一応、なんちゃって重複排除機能を搭載。

Salt は UTF-8 対応の文字列であれば何でも構いません。日本語の文章でも問題はなく、むしろ攻撃回避の観点からはそちらの方が望ましいとも言えます。また、HMACの構造上、仮に算出されたパスワードから HMAC ハッシュ値が判明したとしても、元の秘密鍵にたどり着くことはできませんが、念のためパスワード文字列の算出に用いる文字列をシャッフルして、パスワードからハッシュ値への解読をしにくいような対策を施しています*1

HMAC-SHA256 の算出には Crypto-js を使用しており、動的にコードを読みに行っています。このため、外部コードの読み込みにかかるタイムラグを回避するため、 setTimeout で時間差実行しています。

パスワード算出には乱数・変数を使用していませんので、 salt を変更しない限り、あるドメインに対して常に同じパスワードが返されます。これは、例えば毎月パスワードを変えている、などという場合は不便になるので、

var hmacBytes = Crypto.HMAC(Crypto.SHA256, document.domain + '_' + (new Date()).getFullYear().toString() + ((new Date()).getMonth() + 1).toString(), salt, { asBytes: true });

というように書き換える等の対処が必要です。

問題点

  • 算出根拠がドメインベースなので、サブドメインが変われば異なるパスワードができてしまう
  • 同様に、異なるサービスでも同じドメイン内に共存していれば同じパスワードが生成されてしまう

といったところでしょうか。

強度の確認

英大文字・小文字、数字および指示がある場合は記号も、必ず一字は使用するような設計にしています。強度確認は コンピューターとインターネット セキュリティ | Microsoft セーフティとセキュリティ センター が便利です。

どうぞご利用ください。

ソース

javascript: (function(){

  /* Salt がマスターパスワードになります。漏らさないよう注意。 */
  var salt = prompt('Salt?') || '適当なパスワードに書き変えてください';

  if (salt == '' || typeof(salt) != 'string') {
    alert('salt が設定されていません!');
  }else{

    if(!document.getElementById('crypto-sha256-hmac')){
      var s = document.createElement('script');
      s.id = 'crypto-sha256-hmac';
      s.src = 'https://crypto-js.googlecode.com/files/2.3.0-crypto-sha256-hmac.js';
      void(document.body.appendChild(s));
    }

    var mod = function(e, s){ return Math.abs(parseInt(e)) % s.length; };

    var shuffle = function(s, seed){
      var q = ''; var p = -2;
      while(mod(seed.slice(p-1, p), s) == mod(seed.slice(p, p+1), s)){ if ( Math.abs(p-2) > seed.length ){ p = -2; break; }else{ p--; } }
      var border = [ mod(seed.slice(p-1, p), s), mod(seed.slice(p, p+1), s)].sort(function(a,b){return a-b;});
      switch ( mod(seed.slice(p-2, p-1), 'aaa') ){
        case 0:
          q = s.substring(0, border[0]) + s.substring(border[1]) + s.substring(border[0], border[1]);
          break;
        case 1:
          q = s.substring(border[0], border[1]) + s.substring(0, border[0]) + s.substring(border[1]);
          break;
        case 2:
          q = s.substring(border[1]) + s.substring(border[0], border[1]) + s.substring(0, border[0]);
          break;
      }
      return q;
    };

    var digit = '0123456789';
    var uc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    var lc = 'abcdefghijklmnopqrstuvwxyz';
    var symbol = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
    var allstr = ''; for(var i=33; i<127; i++){ allstr += String.fromCharCode(i); }

    var genPass = function(binarray, len, isUseSymbol){
      var i = 3;
      var src = '';
      var str = [];
      var dupCheck = true;
      var isDuplicated = false;
      var duplication = [];

      str.push(uc.charAt(mod(binarray[0], uc)));
      str.push(lc.charAt(mod(binarray[1], lc)));
      str.push(digit.charAt(mod(binarray[2], digit)));

      if(isUseSymbol){
        str.push(symbol.charAt(mod(binarray[3], symbol)));
        i = 4;
        src = shuffle(allstr, binarray);
      }else{
        src = shuffle(digit + uc + lc, binarray);
      }

      while(str.length < len){
        if(dupCheck){
          for(var j = 0; j < str.length; j++){
            if( str[j] == src.charAt(mod(binarray[i], src)) ){
              isDuplicated = true;
              duplication.push(mod(binarray[i], src));
              break;
            }
          }
        }
        if(!isDuplicated){ str.push(src.charAt(mod(binarray[i], src))); }
        isDuplicated = false; i++;
        if (i >=  binarray.length) {
          for(var k = 0; k < duplication.length; k++){ if(str.length < len){ str.push(src.charAt(duplication[k])); } }
          dupCheck = false; i = 0;
        }
      }
      return shuffle(str.join(''), binarray);
    };

    setTimeout(function(){
      var hmacBytes = Crypto.HMAC(Crypto.SHA256, document.domain, salt, { asBytes: true });
      var l = parseInt(localStorage.getItem('length'));
      var useSymbol = eval(localStorage.getItem('useSymbol'));
      var q = 3;

      if ( ( isNaN(l) ) || ( typeof(useSymbol) != 'boolean' ) ) {
        useSymbol = window.confirm('記号を使用しますか?');
        if (useSymbol) { q = 4; }
        l = prompt(q + '文字以上、' + hmacBytes.length + '文字以下で文字数を設定してください。', 10);
        if ( ( !isNaN(parseInt(l)) ) && ( parseInt(l) <= hmacBytes.length ) && ( parseInt(l) >= q ) ){
          localStorage.setItem('length', l.toString());
          localStorage.setItem('useSymbol', useSymbol.toString());
        }else{
          return 0;
        }
      }
      
      window.prompt('Pass phrase:', genPass(hmacBytes, l, useSymbol));
      
    }, 500);

  }
})();

*1:そもそも生成されたパスワードが第三者の目に触れる可能性は低く、またその上でこのアルゴリズムに則って生成されたものであると判明するという極めて低い確率で起こる条件をクリアした上での、パスワードの元になるハッシュ値の先頭部分を解読しにくくするというある種の「難読化」ですので、例えば得られたHMACが全て同じ値になってしまった場合にはシャッフルは起こりませんが、そこまで想定したコードにする必然性が感じられないため、中途半端と言えば中途半端な実装になっています。