All of the tutorials I could find on setting up single-sign on into an Apache-hosted website using Active Directory authentication do so by configuring Kerberos with insecure settings. It's been best practice for awhile now to disable RC4-HMAC encryption for Kerberos in Active Directory, but a lot of tutorials call for overriding krb5.conf's defaults and making everything work with RC4-HMAC.
I wanted to try setting up single sign-on with AES256 encryption, and I managed to make it work, so I'm recording this question and answer for anyone else who wants better security for their site.
Starting with RC4-HMAC
We'll start by getting it working with RC4-HMAC first, since this is easier. The standard steps to set up SSO start with creating a domain account with an associated SPN, which browsers will use to get encrypted credentials to send to the server. For this example, my user is REALM\HostServiceAccount:
- UPN: HostServiceAccount@realm.com (and therefore the Kerberos principal name is HostServiceAccount@REALM.COM)
- servicePrincipalName attribute (also settable by setspn): HTTP/host.example.com; HTTP/host (Kerberos: HTTP/host.example.com@REALM.COM)
- Leave encryption settings alone; by default, this makes RC4-HMAC the strongest encryption allowed, so tickets from the domain will have this encryption
We add the following entries to /etc/krb5.conf on our target server:
[libdefaults]
default_realm = REALM.COM
[domain_realm]
.realm.com = REALM.COM
realm.com = REALM.COM
We create the keytab and let the server read it:
# ktutil
ktutil: add_entry -password -p HTTP/host.example.com@REALM.COM -k 1 -e rc4-hmac
Password for HTTP/host.example.com@REALM.COM: <enter password here>
ktutil: write_kt /etc/apache2/service.keytab
ktutil: q
# chown -v www-data:root /etc/apache2/service.keytab
# chmod -v 440 /etc/apache2/service.keytab
(At this point, of course, you would want to use kinit -kt service.keytab -S HTTP/host.example.com@REALM.COM HostServiceAccount@REALM.COM
to test the keytab.)
Finally, we set Apache to authenticate users using our keytab:
KrbDelegateBasic off
KrbAuthoritative on
KrbMethodK5Passwd off
Krb5Keytab /etc/apache2/service.keytab
KrbAuthRealms REALM.COM
LogLevel debug
After we restart Apache, if all goes well, we do a request to the server from our Windows domain machine and see the following in Apache's error log:
[debug] src/mod_auth_kerb.c(1641): [client ****] kerb_authenticate_user entered with user (NULL) and auth_type Kerberos, referer: ****
[debug] src/mod_auth_kerb.c(1395): [client ****] Verifying client data using KRB5 GSS-API , referer: ****
[debug] src/mod_auth_kerb.c(1411): [client ****] Client didn't delegate us their credential, referer: ****
[debug] src/mod_auth_kerb.c(1430): [client ****] GSS-API token of length 185 bytes will be sent back, ****
Running klist on our client machine (or Wireshark to see the ticket in the request), we see that indeed we have used an RC4-HMAC ticket to authenticate:
#4> Client: fluggo @ REALM.COM
Server: HTTP/host.example.com @ REALM.COM
KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
Moving up to better encryption
All is well, but again, this is not our goal. RC4-HMAC is considered insecure, so let's disable that and try to make the same setup work with AES256.
First, we'll ask our friendly neighborhood domain admin to enable the advanced encryption on REALM\HostServiceAccount, which will be two checkboxes labeled:
- This account supports Kerberos AES 128 bit encryption
- This account supports Kerberos AES 256 bit encryption
These appear in various places depending on the tools you're using; the end result should be that the LDAP attribute msDS-SupportedEncryptionTypes should be 0x18 or decimal 24, which represents that only AES128 and AES256 are supported.
To make this effective, we'll kill our local client tickets:
C:>klist purge
Current LogonId is 0:0xdeadbeef
Deleting all tickets:
Ticket(s) purged!
If we perform our request again, we'll see that the request has failed, but we've got an updated ticket:
C:>klist
...
#3> Client: fluggo @ REALM.COM
Server: HTTP/host.example.com @ REALM.COM
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Now we should just need to update our keytab with the new algorithms, and we should be golden:
# mv /etc/apache2/service.keytab ~/old.keytab
# ktutil
ktutil: add_entry -password -p HTTP/host.example.com@REALM.COM -k 1 -e aes256-cts-hmac-sha1-96
Password for HTTP/host.example.com@REALM.COM: <enter password here>
ktutil: add_entry -password -p HTTP/host.example.com@REALM.COM -k 1 -e aes128-cts-hmac-sha1-96
Password for HTTP/host.example.com@REALM.COM: <enter password here>
ktutil: write_kt /etc/apache2/service.keytab
ktutil: q
# chown -v www-data:root /etc/apache2/service.keytab
# chmod -v 440 /etc/apache2/service.keytab
We don't even need to restart Apache. Just resubmit the request.
Oops... it doesn't work. If we look at Apache's error log, we see:
[debug] src/mod_auth_kerb.c(1641): [client ****] kerb_authenticate_user entered with user (NULL) and auth_type Kerberos
[debug] src/mod_auth_kerb.c(1249): [client ****] Acquiring creds for HTTP@host.example.com
[debug] src/mod_auth_kerb.c(1395): [client ****] Verifying client data using KRB5 GSS-API
[debug] src/mod_auth_kerb.c(1411): [client ****] Client didn't delegate us their credential
[debug] src/mod_auth_kerb.c(1430): [client ****] GSS-API token of length 9 bytes will be sent back
[debug] src/mod_auth_kerb.c(1110): [client ****] GSS-API major_status:000d0000, minor_status:000186a5
[error] [client ****] gss_accept_sec_context() failed: Unspecified GSS failure. Minor code may provide more information (, )
Well that's a ridculously unhelpful error message, but what went wrong? The answer, and one solution, to follow!