仙石浩明の日記

2009年1月8日

wpa_supplicant のバグ: TLS拡張 (TLS Extensions) 対応の OpenSSL と使うと、WPA EAP-TLS が常に失敗する

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

自宅の無線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.
Filed under: システム構築・運用 — hiroaki_sengoku @ 08:58

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment