wpa_supplicantのKRACKs修正パッチについて

wpa_supplicantのKRACKs修正パッチの中身を見てみました。

論文を読むよりも、ソースコードの修正箇所を見た方が、分かりやすいかもしれません。

とりあえず、4way handshakeに関する修正だけです。

# patch
wpa_supplicantのKRACKs対策パッチは、https://w1.fi/security/2017-1/ に置いてあります。

現時点で８個のパッチがあります。

| name | date | size |
|:--|:-:|:-:|
|0001-hostapd-Avoid-key-reinstallation-in-FT-handshake.patch	|02-Oct-2017 16:15	|6.1K|
|0002-Prevent-reinstallation-of-an-already-in-use-group-ke.patch	|02-Oct-2017 16:15	|8.0K|
|0003-Extend-protection-of-GTK-IGTK-reinstallation-of-WNM-.patch	|02-Oct-2017 16:15	|7.1K|
|0004-Prevent-installation-of-an-all-zero-TK.patch	|02-Oct-2017 16:15	|2.9K|
|0005-Fix-PTK-rekeying-to-generate-a-new-ANonce.patch	|02-Oct-2017 16:15	|1.9K|
|0006-TDLS-Reject-TPK-TK-reconfiguration.patch	|02-Oct-2017 16:15|	4.2K|
|0007-WNM-Ignore-WNM-Sleep-Mode-Response-without-pending-r.patch	|02-Oct-2017 16:15	|1.6K|
|0008-FT-Do-not-allow-multiple-Reassociation-Response-fram.patch	|02-Oct-2017 16:15	|2.7K|


# wpa_sm について

wpa_smは、wpa_supplicant/hostapdの内部で使うステートマシンの構造体です。STA対APの接続毎にwpa_smを作って接続の状態と鍵などを管理しています。

# 0002-Prevent-reinstallation-of-an-already-in-use-group-ke.patch

0002-Prevent-reinstallation-of-an-already-in-use-group-ke.patch の修正内容

```
Track the current GTK and IGTK that is in use and when receiving a
(possibly retransmitted) Group Message 1 or WNM-Sleep Mode Response, do
not install the given key if it is already in use. This prevents an
attacker from trying to trick the client into resetting or lowering the
sequence counter associated to the group key.
```
の日本語訳

```
使用中の現在のGTKとIGTKを追跡し、（再送信された可能性のある）Group Message 1または
WNM Sleep Mode Responseを受信した場合、指定された鍵がすでに使用されている場合はインストールしない。 
これは、攻撃者がクライアントをトリックしてグループキーに関連付けられたシーケンスカウンタを
リセットまたは低下させようとするのを防ぎます。
```

パッチの主要部分

```
+	/* Detect possible key reinstallation */
+	if (sm->gtk.gtk_len == (size_t) gd->gtk_len &&
+	    os_memcmp(sm->gtk.gtk, gd->gtk, sm->gtk.gtk_len) == 0) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"WPA: Not reinstalling already in-use GTK to the driver (keyidx=%d tx=%d len=%d)",
+			gd->keyidx, gd->tx, gd->gtk_len);
+		return 0;
+	}
```
いままでは、同じGTKが再送されてきて受信した場合にチェックしていなかったのでチェックが追加されています。gtkの鍵長、gtkの中身が一致するかをチェックしています。
IGTKにも同様なチェックを追加しています。

# 0004-Prevent-installation-of-an-all-zero-TK.patch

0004-Prevent-installation-of-an-all-zero-TK.patch の修正内容は

```
Properly track whether a PTK has already been installed to the driver
and the TK part cleared from memory. This prevents an attacker from
trying to trick the client into installing an all-zero TK.

This fixes the earlier fix in commit
ad00d64e7d8827b3cebd665a0ceb08adabf15e1e ('Fix TK configuration to the
driver in EAPOL-Key 3/4 retry case') which did not take into account
possibility of an extra message 1/4 showing up between retries of
message 3/4.
```
の日本語訳

```
PTKが既にドライバにインストールされており、TK部分がメモリからクリアされているかどうかを適切に追跡します。 
これにより、攻撃者はクライアントを騙して、すべてゼロのTKをインストールすることができなくなります。

これにより、メッセージ3/4の再試行の間に表示される余分なメッセージ1/4の可能性を考慮しなかった
以前のコミットad00d64e7d8827b3cebd665a0ceb08adabf15e1e（ 'EAPOL-Key 3/4再試行の場合のドライバへのTK設定の修正'）の修正が修正されました 。
```

パッチです。

```
diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h
index 0872b12..8411686 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -219,6 +219,7 @@ struct wpa_ptk {
 	size_t kck_len;
 	size_t kek_len;
 	size_t tk_len;
+	int installed; /* 1 if key has already been installed to driver */
 };
 
 struct wpa_gtk {
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 3c8871d..cf9bf1c 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -594,7 +594,6 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
 		os_memset(buf, 0, sizeof(buf));
 	}
 	sm->tptk_set = 1;
-	sm->tk_to_set = 1;
 
 	kde = sm->assoc_wpa_ie;
 	kde_len = sm->assoc_wpa_ie_len;
@@ -701,7 +700,7 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
 	enum wpa_alg alg;
 	const u8 *key_rsc;
 
-	if (!sm->tk_to_set) {
+	if (sm->ptk.installed) {
 		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
 			"WPA: Do not re-install same PTK to the driver");
 		return 0;
@@ -745,7 +744,7 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
 
 	/* TK is not needed anymore in supplicant */
 	os_memset(sm->ptk.tk, 0, WPA_TK_MAX_LEN);
-	sm->tk_to_set = 0;
+	sm->ptk.installed = 1;
 
 	if (sm->wpa_ptk_rekey) {
 		eloop_cancel_timeout(wpa_sm_rekey_ptk, sm, NULL);
@@ -4183,6 +4182,7 @@ int fils_process_assoc_resp(struct wpa_sm *sm, const u8 *resp, size_t len)
 	 * takes care of association frame encryption/decryption. */
 	/* TK is not needed anymore in supplicant */
 	os_memset(sm->ptk.tk, 0, WPA_TK_MAX_LEN);
+	sm->ptk.installed = 1;
 
 	/* FILS HLP Container */
 	fils_process_hlp_container(sm, ie_start, end - ie_start);
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index 156e6cb..3b42245 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -24,7 +24,6 @@ struct wpa_sm {
 	struct wpa_ptk ptk, tptk;
 	int ptk_set, tptk_set;
 	unsigned int msg_3_of_4_ok:1;
-	unsigned int tk_to_set:1;
 	u8 snonce[WPA_NONCE_LEN];
 	u8 anonce[WPA_NONCE_LEN]; /* ANonce from the last 1/4 msg */
 	int renew_snonce;
--
```
struct wpa_sm からtk_to_setを削除して、ptk.installedを追加する。
ptk.installedに１がセットされていたら、新たな鍵をセットしないようにしています。
wpa_supplicant_install_ptk()で正常に鍵がセットされたたら、ptk.installedを1にセットする。

tk が all zeroになる問題は、wpa_supplicant 2.6 の ad00d64e7d8827b3cebd665a0ceb08adabf15e1e で対策されていたが、
このときはmsg1の受信でtk_to_setをリセットしていたが、今回の修正では、msg1でptkを再受信した場合にptk.installedがリセット
されるようになったので、all zeroが設定される可能性を排除した。

wpa_supplicant 2.4と2.5のときは、ad00d64e7d8827b3cebd665a0ceb08adabf15e1eの対策前なので、msg3を再受信するとall zero
のTKが設定されていた。

# 0005-Fix-PTK-rekeying-to-generate-a-new-ANonce.patch 

このパッチはAP (Authenticator) の修正でした。

0005-Fix-PTK-rekeying-to-generate-a-new-ANonce.patch の修正内容は、

```
The Authenticator state machine path for PTK rekeying ended up bypassing
the AUTHENTICATION2 state where a new ANonce is generated when going
directly to the PTKSTART state since there is no need to try to
determine the PMK again in such a case. This is far from ideal since the
new PTK would depend on a new nonce only from the supplicant.

Fix this by generating a new ANonce when moving to the PTKSTART state
for the purpose of starting new 4-way handshake to rekey PTK.
```

この日本語訳

```
このような場合にPMKを再度決定しようとする必要がないため、PTK再キーイングのためのオーセンティケータ状態マシンパスはAUTHENTICATION2状態をバイパスして終了し、PTKSTART状態に直接進むときに新しいANonceが生成される。 新しいPTKはサプリカントからの新しいnonceに依存するため、これは理想とはかけ離れています。

新しい4ウェイハンドシェイクを開始してPTKを再生成する目的で、PTKSTART状態に移行するときに新しいANonceを生成することでこれを修正します。
```

コードは

```
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 42ef0bf..3b2f97c 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -1953,6 +1953,21 @@ SM_STATE(WPA_PTK, AUTHENTICATION2)
 }
 
 
+static int wpa_auth_sm_ptk_update(struct wpa_state_machine *sm)
+{
+	if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) {
+		wpa_printf(MSG_ERROR,
+			   "WPA: Failed to get random data for ANonce");
+		sm->Disconnect = TRUE;
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "WPA: Assign new ANonce", sm->ANonce,
+		    WPA_NONCE_LEN);
+	sm->TimeoutCtr = 0;
+	return 0;
+}
+
+
 SM_STATE(WPA_PTK, INITPMK)
 {
 	u8 msk[2 * PMK_LEN];
@@ -3129,9 +3144,12 @@ SM_STEP(WPA_PTK)
 		SM_ENTER(WPA_PTK, AUTHENTICATION);
 	else if (sm->ReAuthenticationRequest)
 		SM_ENTER(WPA_PTK, AUTHENTICATION2);
-	else if (sm->PTKRequest)
-		SM_ENTER(WPA_PTK, PTKSTART);
-	else switch (sm->wpa_ptk_state) {
+	else if (sm->PTKRequest) {
+		if (wpa_auth_sm_ptk_update(sm) < 0)
+			SM_ENTER(WPA_PTK, DISCONNECTED);
+		else
+			SM_ENTER(WPA_PTK, PTKSTART);
+	} else switch (sm->wpa_ptk_state) {
 	case WPA_PTK_INITIALIZE:
 		break;
 
```

この修正はAP側の修正です。
修正では、wpa_supplicantの状態遷移を変更しています。sm->PTKRequestに1が設定されていたら、無条件にSM_ENTER(WPA_PTK, PTKSTART)に状態遷移していたのを、ANonceに再度ランダム列なANonceを再設定してから状態遷移するように変更しています。
また、ランダム列の生成に失敗したら、接続を切断します。


supplicantの4way handshakeについての修正は、2と4のふたつでした。