あけましておめでとうございます。 今年もよろしくお願いします。
自宅の無線LAN を WPA2-EAP (WPA2 エンタープライズ) にして 1ヶ月になるが、 すこぶる快適。 アクセスポイントがエンタープライズモードに対応していなければいけないのと、 RADIUS サーバが必要である上、 EAP-TLS を用いる場合は SSL クライアント証明書を発行する 認証局 (CA) を作る必要まであるので、 自宅LAN での導入にはややハードルが高いが、 いったん導入してしまえば WPA/WPA2 パーソナルより手間がかからない。
WPA/WPA2 パーソナルだと、 秘密のパスワードを全ての無線LAN 端末で共有するので、 端末の台数が増えてくるとどんどん漏洩のリスクが高まる。
自宅LAN でそんなに端末があるのか? という突っ込みが入りそうだが、 格安 NetBook や、 無線LAN 機能付スマートフォンなどを買っていると、 自宅LAN とはいえ、 「エンタープライズ」 的なニーズが出てくる。
WPA2 エンタープライズで認証方式として EAP-TLS を使うと、 無線LAN 端末にはそれぞれ個別の証明書をインポートしておくだけでよく、 万一その端末を紛失しても、 その端末の証明書を無効にするだけで済む。
EAP-TLS には対応機器/OS が多いというメリットもある。 無線LAN 端末として、 Windows VISTA, Windows XP, Ubuntu 8.04 LTS などを使ってみたが、 いずれもあっけないくらい簡単に接続できてしまった。 証明書さえインポートしておけば (Windows ならダブルクリックだけ)、 あとはパスワードを入力しなくてもいいぶん WPA/WPA2 パーソナルより設定が簡単。
既存の OS (Linux の場合はディストリビューション) で無線接続できるようになったので、 次は私が独自に構築した GNU/Linux (いわば my distribution) 上で WPA2 EAP-TLS 接続を試みた。
まず wpa_supplicant 0.5.11 をダウンロードしてコンパイル。 続いて設定ファイルである wpa_supplicant.conf を書く。
network={ ssid="XXXXXXXXXX" key_mgmt=WPA-EAP eap=TLS identity="olevia" ca_cert="/usr/local/ssl/certs/GCD_Root_CA.pem" client_cert="/usr/local/ssl/certs/olevia.pem" private_key="/usr/local/ssl/private/olevia.pem" private_key_passwd="" }
「ca_cert=」 「client_cert=」 「private_key=」 にそれぞれ CA の公開鍵、端末の公開鍵、端末の秘密鍵のファイル名を指定している。 で、wpa_supplicant を実行。
# wpa_supplicant -i eth1 -c /etc/wpa_supplicant.conf -d -D wext Initializing interface 'eth1' conf '/etc/wpa_supplicant.conf' driver 'wext' ctrl_interface 'N/A' bridge 'N/A' Configuration file '/etc/wpa_supplicant.conf' -> '/etc/wpa_supplicant.conf' Reading configuration file '/etc/wpa_supplicant.conf' Priority group 0 id=0 ssid='XXXXXXXXXX' Initializing interface (2) 'eth1' EAPOL: SUPP_PAE entering state DISCONNECTED EAPOL: KEY_RX entering state NO_KEY_RECEIVE EAPOL: SUPP_BE entering state INITIALIZE EAP: EAP entering state DISABLED ...(中略)...
wpa_supplicant はデバッグモード (-d オプション) にすると大量のログを出力するので動作を追いにくいが、 wpa_supplicant のプログラムは状態遷移機械 (オートマトン) として動作するよう書かれているので、 「EAP: EAP entering state」 の部分を追っていくとよい。 「DISABLED」 と出力されているのが、 その時点におけるオートマトンの状態。
状態遷移機械 (オートマトン) と言ってしまうと、 コンピュータ/プログラムは全てオートマトンなのであるが (^^;)、 プログラミングの方法として状態遷移機械 (state machine) と言うときは、 各状態に名前を付けて、 各状態ごとの動作を (switch 文や if ... else if ... 文などで) 分けて書く方法を指す。
状態には INITIALIZE, DISABLED, IDLE, RECEIVED, GET_METHOD, METHOD, SEND_RESPONSE, DISCARD, IDENTITY, NOTIFICATION, RETRANSMIT, SUCCESS, FAILURE の 13状態がある。 もちろん 「SUCCESS」 が受理状態。 SUCCESS 状態は、 アクセスポイントが接続を許可した状態を意味する。
ところが、ログを追っていくと、
EAP: EAP entering state RECEIVED EAP: Received EAP-Success EAP: EAP entering state FAILURE CTRL-EVENT-EAP-FAILURE EAP authentication failed
FAILURE 状態 (非受理状態) に遷移している (*_*)。当然、接続できず。
その直前に 「Received EAP-Success」 と出ているにもかかわらず!
アクセスポイント側 (正確に言うと RADIUS サーバ) のログを調べてみると、 ちゃんと接続を許可している (EAP-Success を送っているのだから当然だが)。 一体これはどうしたことか?
私がコンパイルした wpa_supplicant に問題があるのかと思って、 この wpa_supplicant を、 既に接続できることが確認済みの Ubuntu 上にコピーして実行してみる。 「Received EAP-Success」 をログの中から探してみると、
EAP: EAP entering state RECEIVED EAP: Received EAP-Success EAP: EAP entering state SUCCESS CTRL-EVENT-EAP-SUCCESS EAP authentication completed successfully
ちゃんと SUCCESS 状態に遷移しているし、接続もできた。
同一バイナリなのに、異なる環境 (ディストリビューション) だと、
どうして結果が異なるのか?
どのような可能性が考えられるだろうか? 腕に覚えがあるかたは、 この続きを見ずに (といっても、既にタイトルでネタバレしてしまっているが ^^;) 原因を推測してみてはいかがだろうか?
同一バイナリと言っても、 動的リンクしているので、 リンクしているライブラリは異なる。
% ldd /usr/local/sbin/wpa_supplicant linux-gate.so.1 => (0xffffe000) libssl.so.0.9.8 => /usr/local/lib/libssl.so.0.9.8 (0xf7f68000) libcrypto.so.0.9.8 => /usr/local/lib/libcrypto.so.0.9.8 (0xf7e41000) libdl.so.2 => /lib/libdl.so.2 (0xf7e3d000) libc.so.6 => /lib/libc.so.6 (0xf7d0b000) /lib/ld-linux.so.2 (0xf7fbf000)
OpenSSL のライブラリである libssl.so.0.9.8 と libcrypto.so.0.9.8 が怪しそう。 試しにこの 2 ファイルを Ubuntu 上へコピーしてみる。 wpa_supplicant を再度実行してみると、 果たせるかな FAILURE 状態に遷移した。
といってもこの 2 ライブラリに問題があると決めつけるのは早計である。 実はこのライブラリは、 アクセスポイントが使用している RADIUS サーバにあるものと同一のもの (RADIUS サーバも、私独自の my distribution 上で動いている)。 もしこのライブラリに問題があるなら他でも問題が出るはず。 一応、 念のため OpenSSL のバージョンを最新版の 0.9.8i に上げてみたが、 症状は変わらず FAILURE 状態に遷移する。
もうちょっとログを詳しく調べてみることにした。 SUCCESS に遷移するログ (Ubuntu に含まれる OpenSSL ライブラリを使用) と、 FAILURE に遷移するログ (自前でコンパイルしたライブラリを使用) とを、 「Received EAP-Success」 の時点から遡って比較してみる。
Ubuntu に含まれる OpenSSL ライブラリを使った場合:
EAP: EAP entering state METHOD SSL: Received packet(len=69) - Flags 0x80 SSL: TLS Message Length: 59 SSL: (where=0x1001 ret=0x1) SSL: SSL_connect:SSLv3 read finished A SSL: (where=0x20 ret=0x1) SSL: (where=0x1002 ret=0x1) SSL: 0 bytes pending from ssl_out OpenSSL: tls_connection_handshake - Failed to read possible Application Data error:00000000:lib(0):func(0):reason(0) SSL: No data to be sent out EAP-TLS: Done EAP-TLS: Derived key - hexdump(len=64): [REMOVED] EAP-TLS: Derived EMSK - hexdump(len=64): [REMOVED] SSL: Building ACK EAP: method process -> ignore=FALSE methodState=DONE decision=UNCOND_SUCC EAP: EAP entering state SEND_RESPONSE
METHOD 状態というのは、 Server Hello メッセージ (SSL 通信開始時のハンドシェークを参照) の中の compression_method を読み終えた状態。 compression_method は Server Hello メッセージの最後のデータなので、 ログに 「SSL: SSL_connect:SSLv3 read finished A」 と出力し、 wpa_supplicant は続いて送信状態 SEND_RESPONSE へ遷移する。
一方、自前でコンパイルした OpenSSL ライブラリを使った場合:
EAP: EAP entering state METHOD SSL: Received packet(len=1024) - Flags 0xc0 SSL: TLS Message Length: 1290 SSL: Need 276 bytes more input data SSL: Building ACK EAP: method process -> ignore=FALSE methodState=MAY_CONT decision=FAIL EAP: EAP entering state SEND_RESPONSE
METHOD 状態に遷移して compression_method を読み終えたはずなのに、 まだデータがある (Need 276 bytes more input data)。
...というところまでログの意味が理解できた時点で、 原因に思い当たった。 後者は Server Hello メッセージではなく、 Extended Server Hello メッセージだった。 Extended Server Hello メッセージにおいては、 compression_method が最後ではなく server_hello_extension_list が後に続く (RFC 3546 参照)。
なぜ同じ RADIUS サーバが Server Hello メッセージを送信したり、 Extended Server Hello メッセージを送信したり、 という違いが生じるかと言えば、 Ubuntu に含まれるライブラリを使った場合は、 wpa_supplicant が Client Hello メッセージを送信するのに対し、 自前でコンパイルしたライブラリを使った場合は、 Extended Client Hello メッセージを送信するから。
つまり、私が OpenSSL をコンパイルするときは、 TLS拡張 (TLS Extensions, RFC 3546) のサポートを有効にしていたから、 このような差が生じたのだった (RADIUS サーバが使用する OpenSSL も TLS拡張のサポートを有効にしていた、という点もミソ)。 だから、試しに TLS拡張のサポートを無効にして OpenSSL をコンパイルし直してみると、 無事 SUCCESS 状態に遷移して接続することができた。
動的ライブラリで TLS拡張のサポートを無効にしてしまうと、 TLS拡張を使用するプログラム (例えば stone) を実行するとき困るので、 TLS拡張のサポートを無効にしたライブラリを、 wpa_supplicant と静的リンクしておくことにした。
OpenSSL 側で TLS拡張のサポートを無効にすれば対処できるとはいえ、 wpa_supplicant が正しく Extended Server Hello メッセージを解釈できたほうが、 より望ましいに違いない。
そこで、 開発元のバグ報告ページ に、 バグレポートを出してみた:
Summary: wpa_supplicant linked with TLSEXT-enabled OpenSSL always fails in WPA-EAP wpa_supplicant linked with TLSEXT-enabled OpenSSL 0.9.8i always enters `state FAILURE' in WPA-EAP although radius server authorizes. OpenSSL supported TLS Extensions (RFC 3546) since 0.9.8 and if you want to make TLSEXT enabled OpenSSL library, do: $ ./config enable-tlsext $ make With this library, wpa_supplicant sends Extended Client Hello (see RFC 3546 Section 2.1), then TLSEXT-enabled radius server*1 responds with Extended Server Hello (Section 2.2). But unfortunately wpa_supplicant-0.5.11 won't expect `server_hello_extension_list' field in the Extended Server Hello, and enters the state FAILURE in spite of receiving EAP-Success. *1 TLSEXT-disabled radius server simply ignores `client_hello_extension_list' field in Extended Client Hello EAP: EAP entering state RECEIVED EAP: Received EAP-Request id=9 method=13 vendor=0 vendorMethod=0 EAP: EAP entering state METHOD SSL: Received packet(len=1024) - Flags 0xc0 SSL: TLS Message Length: 1290 SSL: Need 276 bytes more input data SSL: Building ACK EAP: method process -> ignore=FALSE methodState=MAY_CONT decision=FAIL EAP: EAP entering state SEND_RESPONSE EAP: EAP entering state IDLE EAPOL: SUPP_BE entering state RESPONSE EAPOL: txSuppRsp EAPOL: SUPP_BE entering state RECEIVE RX EAPOL from XX:XX:XX:XX:XX:XX EAPOL: Received EAP-Packet frame EAPOL: SUPP_BE entering state REQUEST EAPOL: getSuppRsp EAP: EAP entering state RECEIVED EAP: Received EAP-Success EAP: EAP entering state FAILURE CTRL-EVENT-EAP-FAILURE EAP authentication failed I examined wpa_supplicant linked with TLSEXT-disabled OpenSSL library, and it works perfectly.