diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 8adeef4..faffe36 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -249,6 +249,17 @@ static int wpa_supplicant_get_pmk(struct wpa_sm *sm,
 					"RSN: the new PMK matches with the "
 					"PMKID");
 				abort_cached = 0;
+			} else if (sa && !sm->cur_pmksa && pmkid) {
+				/*
+				 * It looks like the authentication server
+				 * derived mismatching MSK. This should not
+				 * really happen, but bugs happen.. There is not
+				 * much we can do here without knowing what
+				 * exactly caused the server to misbehave.
+				 */
+				wpa_dbg(sm->ctx->msg_ctx, MSG_INFO,
+					"RSN: PMKID mismatch - authentication server may have derived different MSK?!");
+				return -1;
 			}
 
 			if (!sm->cur_pmksa)


> > A variant on this workaround idea is that we don't just add the 'wrong'
> > one to the PMKSA cache, but we add some kind of 'poison' instead, which
> > if matched will trigger a fallback to TLSv1.1. But given that falling
> > back to TLSv1.1 will cause us to use the old PRF *anyway*, it's not
> > clear that there's any real benefit in doing it that way instead...
> 
> Probably not.. This is likely more complex and I don't think the
> possibility of using HMAC-SHA1+MD5-based PRF after a successfully
> completed TLS v1.2 exchange would be that horrible price to pay as a
> temporary (hopefully..) interop workaround.

It is a bit more complex than I hoped for, but the following patch does
this. However, I'm not sure I really would like to apply this.. Anyway,
here it is if someone would like to have such a workaround and run a
test with an authentication server to confirm whether this exact TLSv1.2
PRF issue is behind the interoperability issue. I verified that this
works with FreeRADIUS.


EAP-TLS/TTLS/PEAP workaround for incorrect TLS v1.2 MSK derivation

Some authentication servers (e.g., FreeRADIUS 2.2.6 or 3.0.7 when built
with OpenSSL 1.0.2) are known to derive MSK incorrectly with TLS v1.2 is
used. If WPA2-Enterprise is used with an AP that includes PMKID in
EAPOL-Key msg 1/4, it is possible to detect this incorrect
authentication server behavior and work around it by using matching,
incorrect MSK derivation on the peer side.

Check for this interoperability issue and allow 4-way handshake to
continue if the alternative (incorrect) MSK derivation design results in
a PMKID that matches the one sent by the AP/Authenticator.

Signed-off-by: Jouni Malinen <j@w1.fi>
---
 src/crypto/tls.h               |  7 ++++++
 src/crypto/tls_openssl.c       | 18 ++++++++++++++
 src/eap_peer/eap.c             | 33 ++++++++++++++++++++++++++
 src/eap_peer/eap.h             |  1 +
 src/eap_peer/eap_i.h           | 15 ++++++++++++
 src/eap_peer/eap_peap.c        | 25 ++++++++++++++++++++
 src/eap_peer/eap_tls.c         | 18 ++++++++++++++
 src/eap_peer/eap_tls_common.c  | 19 +++++++++++++++
 src/eap_peer/eap_tls_common.h  |  2 ++
 src/eap_peer/eap_ttls.c        | 18 ++++++++++++++
 src/eapol_supp/eapol_supp_sm.c | 30 ++++++++++++++++++++++++
 src/eapol_supp/eapol_supp_sm.h |  1 +
 src/rsn_supp/wpa.c             | 53 ++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 240 insertions(+)

diff --git a/src/crypto/tls.h b/src/crypto/tls.h
index dbe9fd1..a69f86d 100644
--- a/src/crypto/tls.h
+++ b/src/crypto/tls.h
@@ -350,6 +350,13 @@ int __must_check  tls_connection_prf(void *tls_ctx,
 				     int skip_keyblock,
 				     u8 *out, size_t out_len);
 
+int __must_check tls_connection_prf_alt(void *tls_ctx,
+					struct tls_connection *conn,
+					const char *label,
+					int server_random_first,
+					int skip_keyblock,
+					u8 *out, size_t out_len);
+
 /**
  * tls_connection_handshake - Process TLS handshake (client side)
  * @tls_ctx: TLS context data from tls_init()
diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c
index eff942c..c2ba64b 100644
--- a/src/crypto/tls_openssl.c
+++ b/src/crypto/tls_openssl.c
@@ -2784,6 +2784,24 @@ int tls_connection_prf(void *tls_ctx, struct tls_connection *conn,
 }
 
 
+int tls_connection_prf_alt(void *tls_ctx, struct tls_connection *conn,
+			   const char *label, int server_random_first,
+			   int skip_keyblock, u8 *out, size_t out_len)
+{
+	const char *name;
+
+	if (conn == NULL || conn->ssl == NULL)
+		return -1;
+
+	name = SSL_get_version(conn->ssl);
+	if (name == NULL || os_strcmp(name, "TLSv1.2") != 0)
+		return -1;
+
+	return openssl_tls_prf(tls_ctx, conn, label, server_random_first,
+			       skip_keyblock, out, out_len);
+}
+
+
 static struct wpabuf *
 openssl_handshake(struct tls_connection *conn, const struct wpabuf *in_data,
 		  int server)
diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c
index c8a1231..f6b393b 100644
--- a/src/eap_peer/eap.c
+++ b/src/eap_peer/eap.c
@@ -99,6 +99,10 @@ static void eap_sm_free_key(struct eap_sm *sm)
 		bin_clear_free(sm->eapKeyData, sm->eapKeyDataLen);
 		sm->eapKeyData = NULL;
 	}
+	if (sm->eapKeyData_alt) {
+		bin_clear_free(sm->eapKeyData_alt, sm->eapKeyDataLen_alt);
+		sm->eapKeyData_alt = NULL;
+	}
 }
 
 
@@ -683,6 +687,11 @@ SM_STATE(EAP, METHOD)
 		eap_sm_free_key(sm);
 		sm->eapKeyData = sm->m->getKey(sm, sm->eap_method_priv,
 					       &sm->eapKeyDataLen);
+		if (sm->m->getKey_alt) {
+			sm->eapKeyData_alt = sm->m->getKey_alt(
+				sm, sm->eap_method_priv,
+				&sm->eapKeyDataLen_alt);
+		}
 		os_free(sm->eapSessionId);
 		sm->eapSessionId = NULL;
 		if (sm->m->getSessionId) {
@@ -2775,6 +2784,30 @@ const u8 * eap_get_eapKeyData(struct eap_sm *sm, size_t *len)
 
 
 /**
+ * eap_get_eapKeyData_alt - Get alternative master session key (MSK) from EAP
+ * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
+ * @len: Pointer to variable that will be set to number of bytes in the key
+ * Returns: Pointer to the EAP keying data or %NULL on failure
+ *
+ * Fetch EAP keying material (MSK, eapKeyData) from the EAP state machine using
+ * an alternative derivation function (incorrect TLS v1.2 PRF). The key is
+ * available only after a successful authentication. EAP state machine continues
+ * to manage the key data and the caller must not change or free the returned
+ * data.
+ */
+const u8 * eap_get_eapKeyData_alt(struct eap_sm *sm, size_t *len)
+{
+	if (sm == NULL || sm->eapKeyData_alt == NULL) {
+		*len = 0;
+		return NULL;
+	}
+
+	*len = sm->eapKeyDataLen_alt;
+	return sm->eapKeyData_alt;
+}
+
+
+/**
  * eap_get_eapKeyData - Get EAP response data
  * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
  * Returns: Pointer to the EAP response (eapRespData) or %NULL on failure
diff --git a/src/eap_peer/eap.h b/src/eap_peer/eap.h
index 1a645af..9ae3e50 100644
--- a/src/eap_peer/eap.h
+++ b/src/eap_peer/eap.h
@@ -336,6 +336,7 @@ void eap_notify_success(struct eap_sm *sm);
 void eap_notify_lower_layer_success(struct eap_sm *sm);
 const u8 * eap_get_eapSessionId(struct eap_sm *sm, size_t *len);
 const u8 * eap_get_eapKeyData(struct eap_sm *sm, size_t *len);
+const u8 * eap_get_eapKeyData_alt(struct eap_sm *sm, size_t *len);
 struct wpabuf * eap_get_eapRespData(struct eap_sm *sm);
 void eap_register_scard_ctx(struct eap_sm *sm, void *ctx);
 void eap_invalidate_cached_session(struct eap_sm *sm);
diff --git a/src/eap_peer/eap_i.h b/src/eap_peer/eap_i.h
index 5f8b5fa..2bbae44 100644
--- a/src/eap_peer/eap_i.h
+++ b/src/eap_peer/eap_i.h
@@ -139,6 +139,19 @@ struct eap_method {
 	u8 * (*getKey)(struct eap_sm *sm, void *priv, size_t *len);
 
 	/**
+	 * getKey_alt - Get EAP method specific keying material (eapKeyData_alt)
+	 * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
+	 * @priv: Pointer to private EAP method data from eap_method::init()
+	 * @len: Pointer to variable to store key length (eapKeyDataLen_alt)
+	 * Returns: Keying material (eapKeyData_alt) or %NULL if not available
+	 *
+	 * This function can be used to get the alternative keying material from
+	 * the EAP method as an interoperability workaround for incorrect TLS
+	 * v1.2 implementation in some authentication servers.
+	 */
+	u8 * (*getKey_alt)(struct eap_sm *sm, void *priv, size_t *len);
+
+	/**
 	 * get_status - Get EAP method status
 	 * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
 	 * @priv: Pointer to private EAP method data from eap_method::init()
@@ -322,6 +335,8 @@ struct eap_sm {
 	Boolean eapKeyAvailable; /* peer to lower layer */
 	u8 *eapKeyData; /* peer to lower layer */
 	size_t eapKeyDataLen; /* peer to lower layer */
+	u8 *eapKeyData_alt; /* peer to lower layer */
+	size_t eapKeyDataLen_alt; /* peer to lower layer */
 	u8 *eapSessionId; /* peer to lower layer */
 	size_t eapSessionIdLen; /* peer to lower layer */
 	const struct eap_method *m; /* selected EAP method */
diff --git a/src/eap_peer/eap_peap.c b/src/eap_peer/eap_peap.c
index 4f68fce..e8f6282 100644
--- a/src/eap_peer/eap_peap.c
+++ b/src/eap_peer/eap_peap.c
@@ -1208,6 +1208,30 @@ static u8 * eap_peap_getKey(struct eap_sm *sm, void *priv, size_t *len)
 }
 
 
+static u8 * eap_peap_getKey_alt(struct eap_sm *sm, void *priv, size_t *len)
+{
+	struct eap_peap_data *data = priv;
+	u8 *key;
+	char *label;
+
+	if (data->crypto_binding_used)
+		return NULL;
+
+	if (data->force_new_label)
+		label = "client PEAP encryption";
+	else
+		label = "client EAP encryption";
+	key = eap_peer_tls_derive_key_alt(sm, &data->ssl, label,
+					  EAP_TLS_KEY_LEN);
+	if (key == NULL)
+		return NULL;
+
+	*len = EAP_TLS_KEY_LEN;
+
+	return key;
+}
+
+
 static u8 * eap_peap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
 {
 	struct eap_peap_data *data = priv;
@@ -1242,6 +1266,7 @@ int eap_peer_peap_register(void)
 	eap->process = eap_peap_process;
 	eap->isKeyAvailable = eap_peap_isKeyAvailable;
 	eap->getKey = eap_peap_getKey;
+	eap->getKey_alt = eap_peap_getKey_alt;
 	eap->get_status = eap_peap_get_status;
 	eap->has_reauth_data = eap_peap_has_reauth_data;
 	eap->deinit_for_reauth = eap_peap_deinit_for_reauth;
diff --git a/src/eap_peer/eap_tls.c b/src/eap_peer/eap_tls.c
index 66a027a..b4c0eb4 100644
--- a/src/eap_peer/eap_tls.c
+++ b/src/eap_peer/eap_tls.c
@@ -309,6 +309,23 @@ static u8 * eap_tls_getKey(struct eap_sm *sm, void *priv, size_t *len)
 }
 
 
+static u8 * eap_tls_getKey_alt(struct eap_sm *sm, void *priv, size_t *len)
+{
+	struct eap_tls_data *data = priv;
+	u8 *key;
+
+	key = eap_peer_tls_derive_key_alt(sm, &data->ssl,
+					  "client EAP encryption",
+					  EAP_TLS_KEY_LEN + EAP_EMSK_LEN);
+	if (key == NULL)
+		return NULL;
+
+	*len = EAP_TLS_KEY_LEN;
+
+	return key;
+}
+
+
 static u8 * eap_tls_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
 {
 	struct eap_tls_data *data = priv;
@@ -362,6 +379,7 @@ int eap_peer_tls_register(void)
 	eap->process = eap_tls_process;
 	eap->isKeyAvailable = eap_tls_isKeyAvailable;
 	eap->getKey = eap_tls_getKey;
+	eap->getKey_alt = eap_tls_getKey_alt;
 	eap->getSessionId = eap_tls_get_session_id;
 	eap->get_status = eap_tls_get_status;
 	eap->has_reauth_data = eap_tls_has_reauth_data;
diff --git a/src/eap_peer/eap_tls_common.c b/src/eap_peer/eap_tls_common.c
index 2a108da..5a20d31 100644
--- a/src/eap_peer/eap_tls_common.c
+++ b/src/eap_peer/eap_tls_common.c
@@ -330,6 +330,25 @@ u8 * eap_peer_tls_derive_key(struct eap_sm *sm, struct eap_ssl_data *data,
 }
 
 
+u8 * eap_peer_tls_derive_key_alt(struct eap_sm *sm, struct eap_ssl_data *data,
+				 const char *label, size_t len)
+{
+	u8 *out;
+
+	out = os_malloc(len);
+	if (out == NULL)
+		return NULL;
+
+	if (tls_connection_prf_alt(data->ssl_ctx, data->conn, label, 0, 0,
+				   out, len)) {
+		os_free(out);
+		return NULL;
+	}
+
+	return out;
+}
+
+
 /**
  * eap_peer_tls_derive_session_id - Derive a Session-Id based on TLS data
  * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
diff --git a/src/eap_peer/eap_tls_common.h b/src/eap_peer/eap_tls_common.h
index acd2b78..e7e1776 100644
--- a/src/eap_peer/eap_tls_common.h
+++ b/src/eap_peer/eap_tls_common.h
@@ -95,6 +95,8 @@ int eap_peer_tls_ssl_init(struct eap_sm *sm, struct eap_ssl_data *data,
 void eap_peer_tls_ssl_deinit(struct eap_sm *sm, struct eap_ssl_data *data);
 u8 * eap_peer_tls_derive_key(struct eap_sm *sm, struct eap_ssl_data *data,
 			     const char *label, size_t len);
+u8 * eap_peer_tls_derive_key_alt(struct eap_sm *sm, struct eap_ssl_data *data,
+				 const char *label, size_t len);
 u8 * eap_peer_tls_derive_session_id(struct eap_sm *sm,
 				    struct eap_ssl_data *data, u8 eap_type,
 				    size_t *len);
diff --git a/src/eap_peer/eap_ttls.c b/src/eap_peer/eap_ttls.c
index 189a6f1..0c322cf 100644
--- a/src/eap_peer/eap_ttls.c
+++ b/src/eap_peer/eap_ttls.c
@@ -1631,6 +1631,23 @@ static u8 * eap_ttls_getKey(struct eap_sm *sm, void *priv, size_t *len)
 }
 
 
+static u8 * eap_ttls_getKey_alt(struct eap_sm *sm, void *priv, size_t *len)
+{
+	struct eap_ttls_data *data = priv;
+	u8 *key;
+
+	key = eap_peer_tls_derive_key_alt(sm, &data->ssl,
+					  "ttls keying material",
+					  EAP_TLS_KEY_LEN + EAP_EMSK_LEN);
+	if (key == NULL)
+		return NULL;
+
+	*len = EAP_TLS_KEY_LEN;
+
+	return key;
+}
+
+
 static u8 * eap_ttls_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
 {
 	struct eap_ttls_data *data = priv;
@@ -1684,6 +1701,7 @@ int eap_peer_ttls_register(void)
 	eap->process = eap_ttls_process;
 	eap->isKeyAvailable = eap_ttls_isKeyAvailable;
 	eap->getKey = eap_ttls_getKey;
+	eap->getKey = eap_ttls_getKey_alt;
 	eap->getSessionId = eap_ttls_get_session_id;
 	eap->get_status = eap_ttls_get_status;
 	eap->has_reauth_data = eap_ttls_has_reauth_data;
diff --git a/src/eapol_supp/eapol_supp_sm.c b/src/eapol_supp/eapol_supp_sm.c
index 39b4319..eba9291 100644
--- a/src/eapol_supp/eapol_supp_sm.c
+++ b/src/eapol_supp/eapol_supp_sm.c
@@ -1577,6 +1577,36 @@ key_fetched:
 
 
 /**
+ * eapol_sm_get_key_alt - Get alternative master session key (MSK) from EAP
+ * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
+ * @key: Pointer for key buffer
+ * @len: Number of bytes to copy to key
+ * Returns: 0 on success (len of key available), or -1 on failure.
+ *
+ * Fetch EAP keying material (MSK, eapKeyData) from EAP state machine. The key
+ * is available only after a successful authentication. This function is similar
+ * to eapol_sm_get_key(), but requests an alternative MSK derivation algorithm
+ * to be used. This is a workaround for incorrect TLSv1.2 PRF use in some
+ * authentication servers.
+ */
+int eapol_sm_get_key_alt(struct eapol_sm *sm, u8 *key, size_t len)
+{
+	const u8 *eap_key;
+	size_t eap_len;
+
+	if (sm == NULL || !eap_key_available(sm->eap))
+		return -1;
+	eap_key = eap_get_eapKeyData_alt(sm->eap, &eap_len);
+	if (eap_key == NULL || len > eap_len)
+		return -1;
+	if (len > eap_len)
+		return eap_len;
+	os_memcpy(key, eap_key, len);
+	return 0;
+}
+
+
+/**
  * eapol_sm_get_session_id - Get EAP Session-Id
  * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init()
  * @len: Pointer to variable that will be set to number of bytes in the session
diff --git a/src/eapol_supp/eapol_supp_sm.h b/src/eapol_supp/eapol_supp_sm.h
index 1309ff7..79f173b 100644
--- a/src/eapol_supp/eapol_supp_sm.h
+++ b/src/eapol_supp/eapol_supp_sm.h
@@ -312,6 +312,7 @@ void eapol_sm_notify_config(struct eapol_sm *sm,
 			    struct eap_peer_config *config,
 			    const struct eapol_config *conf);
 int eapol_sm_get_key(struct eapol_sm *sm, u8 *key, size_t len);
+int eapol_sm_get_key_alt(struct eapol_sm *sm, u8 *key, size_t len);
 const u8 * eapol_sm_get_session_id(struct eapol_sm *sm, size_t *len);
 void eapol_sm_notify_logoff(struct eapol_sm *sm, Boolean logoff);
 void eapol_sm_notify_cached(struct eapol_sm *sm);
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index faffe36..a0fd2e2 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -171,6 +171,56 @@ static void wpa_supplicant_key_mgmt_set_pmk(struct wpa_sm *sm)
 }
 
 
+static int rsn_msk_mismatch_workaround(struct wpa_sm *sm, const u8 *src_addr,
+				       const u8 *pmkid)
+{
+	u8 pmk[PMK_LEN], new_pmkid[PMKID_LEN];
+	int res;
+
+	if (!sm->eap_workaround || wpa_key_mgmt_suite_b(sm->key_mgmt) ||
+	    wpa_key_mgmt_ft(sm->key_mgmt))
+		return 0; /* EAP workarounds not allowed */
+
+	/*
+	 * If TLS v1.2 was used, try to derive MSK in a known, incorrect way
+	 * that some authentication servers used. If that results in a matching
+	 * PMKID, continue authentication with the incorrectly derived MSK as
+	 * an interoperability workaround.
+	 */
+
+	res = eapol_sm_get_key_alt(sm->eapol, pmk, PMK_LEN);
+	if (res)
+		return 0;
+	wpa_hexdump_key(MSG_DEBUG,
+			"RSN: A possible workaround PMK from EAPOL state machines",
+			pmk, PMK_LEN);
+
+	rsn_pmkid(pmk, PMK_LEN, src_addr, sm->own_addr, new_pmkid,
+		  wpa_key_mgmt_sha256(sm->key_mgmt));
+	wpa_hexdump(MSG_DEBUG, "RSN: PMKID for the possible workaround PMK",
+		    new_pmkid, PMKID_LEN);
+	if (os_memcmp(pmkid, new_pmkid, PMKID_LEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "RSN: Workaround PMK did not match PMKID from AP");
+		os_memset(pmk, 0, PMK_LEN);
+		return 0;
+	}
+
+	sm->cur_pmksa = pmksa_cache_add(sm->pmksa, pmk, PMK_LEN, NULL, 0,
+					src_addr, sm->own_addr, sm->network_ctx,
+					sm->key_mgmt);
+
+	os_memcpy(sm->pmk, pmk, PMK_LEN);
+	os_memset(pmk, 0, PMK_LEN);
+	sm->pmk_len = PMK_LEN;
+	wpa_supplicant_key_mgmt_set_pmk(sm);
+
+	wpa_dbg(sm->ctx->msg_ctx, MSG_INFO,
+		"RSN: PMKID mismatch - authentication server used incorrect MSK derivation with TLS v1.2 - accept that as an interoperability workaround");
+	return 1;
+}
+
+
 static int wpa_supplicant_get_pmk(struct wpa_sm *sm,
 				  const unsigned char *src_addr,
 				  const u8 *pmkid)
@@ -257,6 +307,9 @@ static int wpa_supplicant_get_pmk(struct wpa_sm *sm,
 				 * much we can do here without knowing what
 				 * exactly caused the server to misbehave.
 				 */
+				if (rsn_msk_mismatch_workaround(sm, src_addr,
+								pmkid) == 1)
+					return 0;
 				wpa_dbg(sm->ctx->msg_ctx, MSG_INFO,
 					"RSN: PMKID mismatch - authentication server may have derived different MSK?!");
 				return -1;
