1 Jan 2012

MSCHAPv2 against MIT Kerberos… yes, you can.

Updated Nov 16, 2015: Fixed AUR links

This was probably the biggest frustration when trying to figure out the maze that is setting up WPA Enterprise on my primarily Linux-based network. By “primarily”, I mean if it’s a production machine, it runs Linux. All user accounts are in OpenLDAP (formerly NIS) and MIT Kerberos. This presented quite the challenge when I wanted to set up an enterprise network with 802.1x authentication.

Prerequisites

This guide assumes you have a working Kerberos setup, preferably with something like OpenLDAP on top of it. Remember, this guide only gets MSCHAPv2 authentication working – you still need to support other authentication methods like EAP-TTLS! I recommend you configure OpenLDAP, saslauthd, Kerberos, and FreeRADIUS completely and ensure you can log into your network with 802.1x before moving into this stuff. Setting all that up is way past the scope of this blog post, and there are countless resources on the Web to help you get to this point.

I am also assuming a basic knowledge of shell scripting.

Why MSCHAPv2 is needed, and the Linux answer

Now, first of all, 802.1x uses RADIUS. That means you need a RADIUS server and then some actual user management backend unless you want to put all your users in the config file. So, getting RADIUS to talk to OpenLDAP is pretty straightforward: enable the module and have it used for authorization and authentication, and everything Just Works(TM). Right?

Wrong. There’s a wrench in the gears: Windows clients.

While Linux and Mac clients are perfectly okay with standardized, reversible-encryption-based authentication methods like EAP-TTLS, Microsoft in their everlasting wisdom have decided to support 802.1x only with their own handshake protocol, PEAP-MSCHAPv2. Things get ugly when you realize that MSCHAPv2 isn’t your typical fetch-password-and-compare authentication; it requires the server to interact with the incoming challenge data. MIT Kerberos, of course, has no business doing this, and in fact does not by default store passwords using a hash that MSCHAPv2 can use.

So we’re stuck. Windows clients, by default, cannot authenticate to MIT Kerberos using Windows’ preferred method of authentication. We are presented with two choices:

  1. Install a third-party supplicant on your Windows clients that can talk EAP-TTLS or so
  2. Teach Linux to speak MSCHAPv2

Well, I don’t like making Windows clients install additional software just to get on my network so I chose the latter.

It turns out MIT Kerberos has a name for the MD4 hash MSCHAPv2 uses as the on-disk format for password storage. This is “arcfour-hmac:normal”. So, for starters, you’ll need to edit your kdc.conf (typically in your Kerberos database directory, /var/lib/krb5kdc in my case) and add the above string to the supported_enctypes field for your realm. Have any users who want to connect change their passwords; new users are covered. No way around that, sorry.

Next you need some additional software. KCRAP (Kerberos Challenge-Response Authentication Protocol) is a daemon that sits on your KDC and sticks its fingers into your Kerberos database to obtain the above hash. Then it does the crypto math that MIT has no business doing, and sends the result to whichever client wishes to authenticate.

BUT WAIT! There are a couple of critical pieces missing from KCRAP.

First, it doesn’t support MIT Kerberos 1.9.

Second, it doesn’t send back the “nthashhash” (the hash of the hash… wtg MS), which is a field FreeRADIUS’ MSCHAPv2 module needs for building the final session key used by 802.1x.

Both of these oversights are remediated in my Arch Linux AUR package of KCRAP. If you aren’t using Arch, my fake makepkg script will build and install the package for you, just make sure you have the Kerberos development libraries installed from your distribution’s package manager. If you’d rather do the building yourself, just look through the PKGBUILD file and it will make clear what needs to be done to patch and build the KCRAP server.

Now we need to configure KCRAP. That’s easy enough: Arch users can just enable kcrap in rc.conf, while users of other distros will be able to write an init script based on the one included in my Arch package easily enough. Once you have an init script in place and set to start on boot, construct a KCRAP config file (/etc/kcrap_server.conf):

[kcrap_server]
	port = 89
	realm = FUHRY.US

[realms]
	FUHRY.US = {
		database_name = /var/lib/krb5kdc/principal
		key_stash_file = /var/lib/krb5kdc/.k5.FUHRY.US
	}

Finally, update the realms section of krb5.conf on the server running FreeRADIUS (additions in bold):

[realms]
 FUHRY.US = {
  admin_server = kerberos.fuhry.us:749
  kcrap = kerberos.fuhry.us:89
 }

You’re ready to start KCRAP. The first time you should probably run it with -D to make sure it starts, then kick it off from the init script.

FreeRADIUS

So we’ve got KCRAP working, but that’s not going to magically fix anything. To make it actually work in FreeRADIUS, you need to patch FreeRADIUS’ rlm_mschapv2 module. Again, I have an AUR package that takes care of this with two patches. [Update November 16, 2015: You no longer need to patch FreeRADIUS! The new KCRAP package on AUR includes a new executable, kcrapclient, that emulates Samba’s ntlm_auth program. To use it, just add this to mods-available/mschap:

ntlm_auth = "/usr/bin/kcrapclient %{%{Stripped-User-Name}:-%{%{User-Name}:-None}} %{%{mschap:Challenge}:-00} %{%{mschap:NT-Response}:-00}"

As long as kcrap_server is running, this should take care of everything – FreeRADIUS will now perform MSCHAPv2 authentication using KCRAP.]

Follow the same routine as above to install the patched FreeRADIUS. If you’re feeling skimpy, you can probably get away with just installing the new rlm_mschapv2 module and leaving the rest of your distro’s package in place — just be warned, of course, that most distributions will overwrite the module when FreeRADIUS is upgraded.

Build and install your patched FreeRADIUS, and enable MSCHAPv2 authentication.

Success!

You should now have a working setup. This config file should work if you’re testing with wpa_supplicant:

#
#   eapol_test -c peap-mschapv2.conf -s testing123
#
network={
        ssid="example"
        key_mgmt=WPA-EAP
        eap=PEAP
        identity="testuser"
        anonymous_identity="anonymous"
        password="secret123"
        phase2="autheap=MSCHAPV2"

	#
	#  Uncomment the following to perform server certificate validation.
	ca_cert="/etc/raddb/certs/ca.der"
}
Posted in Uncategorized

9 Responses to “MSCHAPv2 against MIT Kerberos… yes, you can.” (post new)

5 Mar 2012
Sandro
 

Hi,
I tried to use your solution to have peap-mschapv2 working against a MIT kerberos kdc.
It seems that the mschap authentication is working fine, but the peap session doesn’t complete well.
Log of freeradius follows:

[mschapv2] +- entering group MS-CHAP {…}
[mschap] No Cleartext-Password configured. Cannot create LM-Password.
[mschap] No Cleartext-Password configured. Cannot create NT-Password.
[mschap] Creating challenge hash with username: user
[mschap] Told to do MS-CHAPv2 for user with NT-Password
[mschap] I think I can try KCRAP here
[mschap] Got KCRAP context
[mschap] Challenge: 0x59df4255259d279d (principal = user, response = 0x5da2a107a19f7a04)
Extra data length: 16
Extra data value: 0x00007fff8bbbbc9a
[mschap] KCRAP: Authentication succeeded.
[mschap] MD4(LM(password)) = 5d52d28cab6f652d3d2381e2c70e9e4b
[mschap] LM(password) = 000500000078ab150a08b1d9bf832e24
[mschap] adding MS-CHAPv2 MPPE keys
++[mschap] returns ok
MSCHAP Success
++[inner-eap] returns handled
} # server inner-tunnel
[peap] Got tunneled reply code 11
EAP-Message = 0x010800331a0307002e533d39364441353444324337353643424130344346444231434430453441363039433336383732353332
Message-Authenticator = 0x00000000000000000000000000000000
State = 0x77bb9d5776b387fad8c4ecd542b23da8
[peap] Got tunneled reply RADIUS code 11
EAP-Message = 0x010800331a0307002e533d39364441353444324337353643424130344346444231434430453441363039433336383732353332
Message-Authenticator = 0x00000000000000000000000000000000
State = 0x77bb9d5776b387fad8c4ecd542b23da8
[peap] Got tunneled Access-Challenge
++[eap] returns handled
Sending Access-Challenge of id 7 to 127.0.0.1 port 38874
EAP-Message = 0x0108005b1900170301005073d04f331f9db54cce4965a2446ac24b00ff782ca2e0d954f3c232dbe734ccf61a00daf513ec248af40d541bac0ee427bc6afdaf71c049848a034c8591b978072852c991e6b5bee9719a0073fc264257
Message-Authenticator = 0x00000000000000000000000000000000
State = 0xe6ce5623e1c64f25ae463e164679edb4
Finished request 20.
Going to the next request

In the eapol_test log I can read “EAP-MSCHAPV2: Invalid authenticator response in success request”, so it seems that Message-Authenticator is not valid.
Is there some further configuration to be used on freeradius to have your solution working?

Thank you
Sandro

March 5th, 2012 at 10:53 am
5 Mar 2012
 

Hrm… post your modules/eap, sites-available/default and sites-available/inner-tunnel and I’ll compare them to what I have running.

(edit) Also make sure you applied the KCRAP patches to both your client and your server (if they’re different machines). The whole protocol had to get changed to add the additional server authentication code field.

March 5th, 2012 at 12:14 pm
5 Mar 2012
Sandro
 

Thanks a lot.
The requested config files follow:

===== eap.conf ================================================

eap {
default_eap_type = ttls
timer_expire = 60
ignore_unknown_eap_types = no
cisco_accounting_username_bug = no
max_sessions = 4096
md5 {
}
leap {
}
gtc {
auth_type = PAP
}
tls {
certdir = ${confdir}/certs
cadir = ${confdir}/certs
private_key_file = ${certdir}/edugw-cainfn-key.pem
certificate_file = ${certdir}/edugw-cainfn-cert.pem
CA_file = ${cadir}/allcacerts.pem
dh_file = ${certdir}/dh
random_file = ${certdir}/random
fragment_size = 1200
check_crl = no
CA_path = ${cadir}
cipher_list = “DEFAULT”
make_cert_command = “${certdir}/bootstrap”
ecdh_curve = “prime256v1”
cache {
enable = no
max_entries = 255
}
verify {
}
ocsp {
enable = no
override_cert_url = yes
url = “http://127.0.0.1/ocsp/”
}
}
ttls {
copy_request_to_tunnel = yes
use_tunneled_reply = yes
virtual_server = “inner-tunnel”
}
peap {
default_eap_type = mschapv2
copy_request_to_tunnel = yes
use_tunneled_reply = yes
proxy_tunneled_request_as_eap = no
virtual_server = “inner-tunnel”
}
mschapv2 {
}
}

===== default ================================================

authorize {
preprocess
chap
mschap
digest
suffix
eap {
ok = return
}
files
expiration
logintime
pap
}
authenticate {
Auth-Type PAP {
pap
}
Auth-Type CHAP {
chap
}
Auth-Type MS-CHAP {
mschap
}
digest
unix
eap
}
preacct {
preprocess
acct_unique
suffix
files
}
accounting {
detail
unix
radutmp
exec
attr_filter.accounting_response
}
session {
radutmp
}
post-auth {
reply_log
exec
Post-Auth-Type REJECT {
attr_filter.access_reject
}
}
pre-proxy {
}
post-proxy {
eap
}

===== inner-tunnel ================================================

server inner-tunnel {
listen {
ipaddr = 127.0.0.1
port = 18120
type = auth
}
authorize {
chap
update control {
MS-CHAP-Use-NTLM-Auth := 0
}
mschap
suffix
update control {
Proxy-To-Realm := LOCAL
}
inner-eap {
}
files
expiration
logintime
pap
}
authenticate {
Auth-Type PAP {
pap
}
Auth-Type CHAP {
chap
}
Auth-Type MS-CHAP {
mschap
}
unix
inner-eap
}
session {
radutmp
}
post-auth {
Post-Auth-Type REJECT {
attr_filter.access_reject
}
}
pre-proxy {
}
post-proxy {
inner-eap
}

===== inner-eap ================================================

eap inner-eap {
default_eap_type = mschapv2
timer_expire = 60
ignore_unknown_eap_types = no
cisco_accounting_username_bug = no
max_sessions = 4096
md5 {
}
gtc {
auth_type = PAP
}
mschapv2 {
}
tls {
certdir = ${confdir}/certs
cadir = ${confdir}/certs
private_key_file = ${certdir}/edugw-cainfn-key.pem
certificate_file = ${certdir}/edugw-cainfn-cert.pem
CA_file = ${cadir}/allcacerts.pem
dh_file = ${certdir}/dh
random_file = ${certdir}/random
fragment_size = 1024
check_crl = no
cipher_list = “DEFAULT”
}
}

============================================================

I patched kcrap on both client and server but only for the ntlm extra_data, because I supposed that the other patches aren’t involved in my case (mit < 1.9).
In freeradius I didn't patch rlm_krb5 because that module isn't used.
Thanks
Bye
Sandro

March 5th, 2012 at 12:56 pm
5 Mar 2012
 

First off, are your certificates in order? It looks like FreeRADIUS is doing everything right but if the client rejects it, it could be because the certificate is invalid.

Try creating a test RADIUS user with a plaintext password, and see if eapol_test works. You can do this by adding to /etc/raddb/users:

testuser Cleartext-Password := “secret123”

The presence of a cleartext password enables mschapv2 to bypass KCRAP and do everything locally. Generally what I advise is that you get FreeRADIUS, MSCHAPv2 and 802.1x working, then add in KCRAP and Kerberos.

March 5th, 2012 at 10:12 pm
6 Mar 2012
Sandro
 

Using a plaintext password all work fine, so I think the certificates are in order and the configuration files are ok!
Any further hints?
Thanks
Sandro

March 6th, 2012 at 8:42 am
7 Mar 2012
Sandro
 

Hi,
now it works!
I found that there was a trouble in passing back the key21 from the kcrap server to the patched freeradius.
I modified the definition of key21 adding the keyword static in ntlm.c source code.
However I don’t like this solution and I would like to propose a protocol modification,
so could you send me your email to explain my thoughts better?
Thank you
Sandro

March 7th, 2012 at 12:41 pm
7 Mar 2012
 

Good catch. My e-mail is dan@fuhry.us if you want to send a patch or suggestion for a protocol modification.

[edit] ntlm.c deals with the original MSCHAP, not v2. The ntlmv2.c file doesn’t make any mention of a variable called key21. Perhaps I’m missing something? You were authenticating with MSCHAPv2, right?

March 7th, 2012 at 12:47 pm
21 Apr 2013
Thomas
 

Dan,

Can I patch freeradius using your patches and make it work with Windows AD instead of MIT Kerberos? Right now we have to use ntlm via winbind to pass MSCHAPv2 data to AD.

Thanks,

Thomas

April 21st, 2013 at 12:17 am
25 Apr 2013
 

Thomas – I don’t know. Actually winbind is the “right” way to do it with AD. My patches basically just hack in support for what the ntlm_auth program already odes.

April 25th, 2013 at 5:32 pm

Leave a Reply