Unable to connect to WPA2-Enterprise since 2.4-r1: WPA_ALG_PMK bug?
diff mbox

Message ID 20150708191100.GA24433@w1.fi
State Deferred
Headers show

Commit Message

Jouni Malinen July 8, 2015, 7:11 p.m. UTC
On Wed, Jul 08, 2015 at 07:14:58PM +0300, Jouni Malinen wrote:
> On Wed, Jul 08, 2015 at 04:00:57PM +0100, David Woodhouse wrote:
> > Obviously that's *horrid*, and I'm going to have to autoflagellate for
> > even thinking it. We'd only even want to contemplate it if the problem
> > really is widespread enough to warrant such. There really *is* a lot of
> > merit in saying "Your kit is crap. Get something better from someone
> > who actually provides decent support."
> 
> What I'm worried about is that some of the other client supplicant
> vendors could come up with implementations that have the same issue
> and this mess could remain in place for years.. As such, there is some
> benefit in making sure wpa_supplicant rejects the connection rather than
> hide the issue if there is any chance of that resulting in sufficient
> complaints to get the authentication server fixed.
> 
> That said, I might need to change the debug messages to make it much
> clearer that the issue in this case is most likely in mismatching MSK
> rather than something specific to PMKSA caching..

The following patch does this and I'll likely push it to hostap.git:

RSN: Stop connection attempt on apparent PMK mismatch

If WPA2-Enterprise connection with full EAP authentication (i.e., no
PMKSA caching used) results in a PMKID that does not match the one the
AP/Authenticator indicates in EAPOL-Key msg 1/4, there is not much point
in trying to trigger full EAP authentication by sending EAPOL-Start
since this sequence was immediately after such full authentication
attempt.

There are known examples of authentication servers with incorrect MSK
derivation when TLS v1.2 is used (e.g., FreeRADIUS 2.2.6 or 3.0.7 when
built with OpenSSL 1.0.2). Write a clear debug log entry and also send
it to control interface monitors when it looks likely that this case has
been hit. After doing that, stop the connection attempt by
disassociating instead of trying to send out EAPOL-Start to trigger new
EAP authentication round (such another try can be tried with a new
association).

Signed-off-by: Jouni Malinen <j@w1.fi>
---
 src/rsn_supp/wpa.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

Comments

David Woodhouse July 8, 2015, 7:26 p.m. UTC | #1
On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> Write a clear debug log entry and also send it to control interface 
> monitors when it looks likely that this case has been hit.

I'll test these patches later; thanks. In the meantime, this touches on
something else I was hoping to look at... is this failure mode actually
supposed to be reported coherently to NetworkManager, and then to the
user?

It is extremely suboptimal at the moment that any form of failure is
mostly just silent. I haven't yet worked out where the real fault lies.
Jouni Malinen July 8, 2015, 7:38 p.m. UTC | #2
On Wed, Jul 08, 2015 at 08:26:19PM +0100, David Woodhouse wrote:
> On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> > Write a clear debug log entry and also send it to control interface 
> > monitors when it looks likely that this case has been hit.
> 
> I'll test these patches later; thanks. In the meantime, this touches on
> something else I was hoping to look at... is this failure mode actually
> supposed to be reported coherently to NetworkManager, and then to the
> user?

Unfortunately, it is not. Some of the EAP operation events are still
available only through the (non-D-Bus) control interface and this new
message is in that category.

> It is extremely suboptimal at the moment that any form of failure is
> mostly just silent. I haven't yet worked out where the real fault lies.

Agreed. Someone would need to define a suitable extension to the D-Bus
events to make this information available from wpa_supplicant and
matching changes in NetworkManager to be able to display this. Some of
the related events are available as D-Bus signals (e.g., as far as TLS
certificate validation steps are concerned), but I don't think there is
any generic event signal that would allow wpa_supplicant to make NM
display something all the way to the user in a reasonable manner today.
David Woodhouse July 8, 2015, 10:47 p.m. UTC | #3
On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> 
> RSN: Stop connection attempt on apparent PMK mismatch
> 
> If WPA2-Enterprise connection with full EAP authentication (i.e., no
> PMKSA caching used) results in a PMKID that does not match the one the
> AP/Authenticator indicates in EAPOL-Key msg 1/4, there is not much point
> in trying to trigger full EAP authentication by sending EAPOL-Start
> since this sequence was immediately after such full authentication
> attempt.

That works...

wlo1: SME: Trying to authenticate with 18:33:9d:0c:da:de (SSID='TSNOfficeWLAN' freq=5300 MHz)
wlo1: Trying to associate with 18:33:9d:0c:da:de (SSID='TSNOfficeWLAN' freq=5300 MHz)
wlo1: Associated with 18:33:9d:0c:da:de
wlo1: CTRL-EVENT-EAP-STARTED EAP authentication started
wlo1: CTRL-EVENT-REGDOM-CHANGE init=COUNTRY_IE type=COUNTRY alpha2=FR
wlo1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=13
TLS - SSL error: error:0B07C065:x509 certificate routines:X509_STORE_add_cert:cert already in hash table
wlo1: CTRL-EVENT-EAP-METHOD EAP vendor 0 method 13 (TLS) selected
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=3 subject='/C=US/O=Intel Corporation/CN=Intel Root CA'
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=2 subject='/C=US/O=Intel Corporation/CN=Intel Intranet Basic Policy CA'
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=1 subject='/C=US/O=Intel Corporation/CN=Intel Intranet Basic Issuing CA 1A'
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=0 subject='/CN=ir10d-pra1.ir.intel.com'
wlo1: CTRL-EVENT-EAP-SUCCESS EAP authentication completed successfully
wlo1: RSN: PMKID mismatch - authentication server may have derived different MSK?!
wlo1: CTRL-EVENT-DISCONNECTED bssid=18:33:9d:0c:da:de reason=1 locally_generated=1

We end up *blacklisting* the offending BSSIDs and not trying them again
for a while... would it be possible to start by disabling TLSv1.2 for
the offending BSSIDs, rather than giving up entirely?

That might be a simpler workaround than the other one (which I'm about
to test).
David Woodhouse July 8, 2015, 11:38 p.m. UTC | #4
On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> 
> 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.

That appears to work here. Less trivial to backport to 2.4 though :)

wlo1: SME: Trying to authenticate with 18:33:9d:0c:da:de (SSID='TSNOfficeWLAN' freq=5300 MHz)
wlo1: Trying to associate with 18:33:9d:0c:da:de (SSID='TSNOfficeWLAN' freq=5300 MHz)
wlo1: Associated with 18:33:9d:0c:da:de
wlo1: CTRL-EVENT-EAP-STARTED EAP authentication started
wlo1: CTRL-EVENT-REGDOM-CHANGE init=COUNTRY_IE type=COUNTRY alpha2=FR
wlo1: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=13
TLS - SSL error: error:0B07C065:x509 certificate routines:X509_STORE_add_cert:cert already in hash table
wlo1: CTRL-EVENT-EAP-METHOD EAP vendor 0 method 13 (TLS) selected
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=3 subject='/C=US/O=Intel Corporation/CN=Intel Root CA'
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=2 subject='/C=US/O=Intel Corporation/CN=Intel Intranet Basic Policy CA'
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=1 subject='/C=US/O=Intel Corporation/CN=Intel Intranet Basic Issuing CA 1A'
wlo1: CTRL-EVENT-EAP-PEER-CERT depth=0 subject='/CN=ir10d-pra1.ir.intel.com'
wlo1: CTRL-EVENT-EAP-SUCCESS EAP authentication completed successfully
wlo1: RSN: PMKID mismatch - authentication server used incorrect MSK derivation with TLS v1.2 - accept that as an interoperability workaround
wlo1: WPA: Key negotiation completed with 18:33:9d:0c:da:de [PTK=CCMP GTK=TKIP]
wlo1: CTRL-EVENT-CONNECTED - Connection to 18:33:9d:0c:da:de completed [id=0 id_str=]
Jouni Malinen July 10, 2015, 5:57 p.m. UTC | #5
On Thu, Jul 09, 2015 at 12:38:54AM +0100, David Woodhouse wrote:
> On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> > 
> > EAP-TLS/TTLS/PEAP workaround for incorrect TLS v1.2 MSK derivation

> That appears to work here. Less trivial to backport to 2.4 though :)

Thanks for testing. Was it clear that this uses an authentication server
other than FreeRADIUS? If so, there would be at least two different
authentication servers with this issue and it would be good to make sure
the vendor becomes aware (and well, a suitable person at the vendor so
that this actually gets fixed rather sooner than later)..
Jouni Malinen July 10, 2015, 6:07 p.m. UTC | #6
On Wed, Jul 08, 2015 at 11:47:17PM +0100, David Woodhouse wrote:
> On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> > 
> > RSN: Stop connection attempt on apparent PMK mismatch

> wlo1: CTRL-EVENT-EAP-SUCCESS EAP authentication completed successfully
> wlo1: RSN: PMKID mismatch - authentication server may have derived different MSK?!
> wlo1: CTRL-EVENT-DISCONNECTED bssid=18:33:9d:0c:da:de reason=1 locally_generated=1
> 
> We end up *blacklisting* the offending BSSIDs and not trying them again
> for a while... would it be possible to start by disabling TLSv1.2 for
> the offending BSSIDs, rather than giving up entirely?
> 
> That might be a simpler workaround than the other one (which I'm about
> to test).

It would certainly be simpler, but I don't really like the idea of
wpa_supplicant disabling TLSv1.2 completely at least as far as modifying
the network configuration is concerned. I guess I could live with a
temporary disabling of TLSv1.2 (i.e., just do that outside the
persistent configuration parameters and for limited duration). However,
I'd rather do that only in case this can really be shown to be because
of the incorrect MSK derivation. Or well, I guess it could be considered
secure enough to do this even without checking the "alternative MSK
derivation" and just check that TLSv1.2 was used during the exchange.
That would already be enough to show that TLSv1.2 was successfully
completed which would make it quite a bit less likely for an attacker to
be able to use this for a downgrade attack.

An attack would still be possible with the simple implementation,
though, since all it takes is a quick transmission of a bogus EAPOL-Key
msg 1/4 immediately after the EAP-Success message which would be doable
without that much effort.. In other words, I'm not really sure I would
be accepting such a change into hostap.git or well, at least not
enabling that behavior by default and with that in mind, it might be as
simple to just have an out-of-tree patch available for anyone who wants
to build a binary with such a capability while understanding the
implications this would have on security (allowing TLS downgrade attack
from v1.2 to v1.0/1.1).
David Woodhouse July 10, 2015, 7:37 p.m. UTC | #7
On Fri, 2015-07-10 at 20:57 +0300, Jouni Malinen wrote:
> On Thu, Jul 09, 2015 at 12:38:54AM +0100, David Woodhouse wrote:
> > On Wed, 2015-07-08 at 22:11 +0300, Jouni Malinen wrote:
> > > 
> > > EAP-TLS/TTLS/PEAP workaround for incorrect TLS v1.2 MSK derivation
> 
> > That appears to work here. Less trivial to backport to 2.4 though :)
> 
> Thanks for testing. Was it clear that this uses an authentication server
> other than FreeRADIUS? If so, there would be at least two different
> authentication servers with this issue and it would be good to make sure
> the vendor becomes aware (and well, a suitable person at the vendor so
> that this actually gets fixed rather sooner than later)..

Last I knew it was Cisco ISE. Which would probably mean that there's
not much prospect of actually getting it *fixed* before 2016. Cisco are
not known for the competence of their support.

The IT folks who own it are in Israel, so I should hopefully know more
when they get back from their weekend, on Sunday.
David Woodhouse July 10, 2015, 8:02 p.m. UTC | #8
On Fri, 2015-07-10 at 21:07 +0300, Jouni Malinen wrote:
> However, I'd rather do that only in case this can really be shown to 
> be because of the incorrect MSK derivation.

Yeah, that makes sense. In which case you'd still need the whole
infrastructure to calculate the 'alternative' MSK. So we might as well
stick with your existing patch which just *uses* the alternative MSK.

> it might be as simple to just have an out-of-tree patch available for 
> anyone who wants to build a binary with such a capability

I don't think there's much benefit in that. If they're going to have to
fight the lack of coherent error reporting to work out what the problem
is, and then take remedial action, then they might as well just
*configure* it not to use TLSv1.2. A patch is probably harder than the
config change (although Dan we *will* need NetworkManager to be able to
set it on demand according to the config).

The benefit in a code-based 'fix' is only really if it can be merged by
default and enabled whenever eap_workaround is set.
Jouni Malinen July 10, 2015, 8:10 p.m. UTC | #9
On Fri, Jul 10, 2015 at 09:02:11PM +0100, David Woodhouse wrote:
> On Fri, 2015-07-10 at 21:07 +0300, Jouni Malinen wrote:
> > However, I'd rather do that only in case this can really be shown to 
> > be because of the incorrect MSK derivation.
> 
> Yeah, that makes sense. In which case you'd still need the whole
> infrastructure to calculate the 'alternative' MSK. So we might as well
> stick with your existing patch which just *uses* the alternative MSK.

Yes, that sounds likely in practice.

> > it might be as simple to just have an out-of-tree patch available for 
> > anyone who wants to build a binary with such a capability
> 
> I don't think there's much benefit in that. If they're going to have to
> fight the lack of coherent error reporting to work out what the problem
> is, and then take remedial action, then they might as well just
> *configure* it not to use TLSv1.2. A patch is probably harder than the
> config change (although Dan we *will* need NetworkManager to be able to
> set it on demand according to the config).
> 
> The benefit in a code-based 'fix' is only really if it can be merged by
> default and enabled whenever eap_workaround is set.

Agreed. eap_workaround is enabled by default, so for this to be
acceptable, that complexity of calculating the incorrect MSK would be
needed..

Once I get a bit more information on the scale of the issue (mainly,
whether it is only the two previously identified server components that
have clear fixes already available or whether there are some other
servers impacted as well with no easy fix), I'll figure out whether I
can convince myself to accept the workaround into hostap.git..

If you do get confirmation on the authentication server (ideally
including its version number) being from Cisco, I can also check with
the engineers directly to avoid going through normal support requests so
as to see if this could be fixed soon for wpa_supplicant not having to
care too much.
David Woodhouse July 10, 2015, 8:19 p.m. UTC | #10
On Fri, 2015-07-10 at 23:10 +0300, Jouni Malinen wrote:
> 
> Once I get a bit more information on the scale of the issue (mainly,
> whether it is only the two previously identified server components that
> have clear fixes already available or whether there are some other
> servers impacted as well with no easy fix), I'll figure out whether I
> can convince myself to accept the workaround into hostap.git..
> 
> If you do get confirmation on the authentication server (ideally
> including its version number) being from Cisco, I can also check with
> the engineers directly to avoid going through normal support requests so
> as to see if this could be fixed soon for wpa_supplicant not having to
> care too much.

Ack. I'll chase them up and hopefully have that information on Sunday.
David Woodhouse July 12, 2015, 8:52 a.m. UTC | #11
On Fri, 2015-07-10 at 23:10 +0300, Jouni Malinen wrote:
> Once I get a bit more information on the scale of the issue (mainly,
> whether it is only the two previously identified server components that
> have clear fixes already available or whether there are some other
> servers impacted as well with no easy fix), I'll figure out whether I
> can convince myself to accept the workaround into hostap.git..
> 
> If you do get confirmation on the authentication server (ideally
> including its version number) being from Cisco, I can also check with
> the engineers directly to avoid going through normal support requests so
> as to see if this could be fixed soon for wpa_supplicant not having to
> care too much.

The initial response was:

  "We are using Aruba ClearPass Policy Manager release 6.5.1 as our 
   RADIUS server. This release does not support TLSv1.2."

I have showed them a packet trace which clearly shows a client
authenticating using EAP-TLSv1.2. And invited further comment :)
Jan Ceuleers July 12, 2015, 9:30 a.m. UTC | #12
On 12/07/15 10:52, David Woodhouse wrote:
> The initial response was:
> 
>   "We are using Aruba ClearPass Policy Manager release 6.5.1 as our 
>    RADIUS server. This release does not support TLSv1.2."
> 
> I have showed them a packet trace which clearly shows a client
> authenticating using EAP-TLSv1.2. And invited further comment :)

According to [1] TLSv1.2 support was added in release 6.5.2

[1]:
https://support.arubanetworks.com/Documentation/tabid/77/DMXModule/512/Command/Core_Download/Default.aspx?EntryId=17940
Nick Lowe July 14, 2015, 12:10 p.m. UTC | #13
It would be worth checking what the Windows supplicant does after
configuring it to use TLS 1.2.

See "More Information" from:

https://support.microsoft.com/en-us/kb/2977292
Jouni Malinen July 14, 2015, 6:01 p.m. UTC | #14
On Sun, Jul 12, 2015 at 09:52:27AM +0100, David Woodhouse wrote:
> The initial response was:
> 
>   "We are using Aruba ClearPass Policy Manager release 6.5.1 as our 
>    RADIUS server. This release does not support TLSv1.2."
> 
> I have showed them a packet trace which clearly shows a client
> authenticating using EAP-TLSv1.2. And invited further comment :)

Thanks. I asked Aruba and got a response that this was fixed in 6.5.2
which I interpreted as 6.5.1 unfortunately enabling TLSv1.2 even though
it was not "supported" and then not using the correct PRF.. Anyway, this
issue will hopefully go away with the server upgrade. As such, I'm not
currently planning on adding the workaround into hostap.git.
David Woodhouse July 14, 2015, 8:02 p.m. UTC | #15
On Tue, 2015-07-14 at 21:01 +0300, Jouni Malinen wrote:
> On Sun, Jul 12, 2015 at 09:52:27AM +0100, David Woodhouse wrote:
> > The initial response was:
> > 
> >   "We are using Aruba ClearPass Policy Manager release 6.5.1 as our 
> >    RADIUS server. This release does not support TLSv1.2."
> > 
> > I have showed them a packet trace which clearly shows a client
> > authenticating using EAP-TLSv1.2. And invited further comment :)
> 
> Thanks. I asked Aruba and got a response that this was fixed in 6.5.2
> which I interpreted as 6.5.1 unfortunately enabling TLSv1.2 even though
> it was not "supported" and then not using the correct PRF.. Anyway, this
> issue will hopefully go away with the server upgrade. 

At least for us, the server upgrade isn't planned imminently because of
issues with it — I'm told of a vulnerability in 6.5.2, as well as the
fact that there's no easy deployment rollback.

If you have competent contacts in Aruba, please could you ask them if
it's possible to *prevent* 6.5.1 from using TLSv1.2? Either in
configuration, or a minor bugfix update without requiring users to do a
full upgrade to 6.5.2?

Thanks.

Patch
diff mbox

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;