indexwritingsjournal › 2021

symlink.jnl[2021]

Kerberos on non-domain Windows clients

Apparently, non-domain Windows systems can use Kerberos authentication when connecting to Active Directory domain services, i.e. local accounts aren't limited to just NTLM. This works without any login screen nor runas trickery (and somewhat explains the confusion I had in my earlier post back in 2011).

When prompted for credentials, it is necessary to enter a Kerberos principal name as the username, e.g. fred@AD.EXAMPLE.COM would cause it to acquire Kerberos tickets using the specified password (whereas EXAMPLE\fred results in NTLM).

It's important that the username needs to be the raw Kerberos principal name (with uppercase realm), not the UPN that's customary in AD, as the latter don't have any relationship to DNS and are impossible to resolve without already being in contact with the DC.

This works with software which explicitly prompts for credentials does the necessary calls to acquire tickets with a password – this includes the RDP remote desktop client and SMB file shares, and it seems that Google Chrome & Edge (but not Firefox) support the same for HTTP Negotiate. (Fortunately, RDP and SMB is exactly where Kerberos is most useful in my case.)

"Pure" Kerberos clients do also work, as long as the credentials for that specific host have already been stored in the Credential Manager (and SSPI doesn't seem to care about having a TGT already cached or not). If the host doesn't run SMB, then cmdkey can be used to manually store the credentials:

cmdkey /add:ember /user:grawity@NULLROUTE.EU.ORG /pass

(The target may be a FQDN, but then it only works with FQDNs everywhere. For SPNEGO clients – SMB and RDP – the target should be a short name, that way it'll work with both \\ember and \\ember.nullroute.eu.org.)

(In other words, the reason I was able to SSH to 2 of my servers was only because I'd stored the Kerberos password for them for SMB earlier.)

Making this work with non-AD realms

By default Windows expects the realm to run Active Directory specific services, so it'll use the dc._msdcs subdomain for SRV lookups and will issue CLDAP "DC pings" to each discovered KDC (and will assume the domain is unreachable if the CLDAP pings are unreplied).

However, it won't do this for explicitly configured realms, so it is also possible to use Kerberos authentication against services running plain MIT/Heimdal (such as my Samba fileserver with kerberos method = dedicated keytab). The usual tool for adding custom realm configurations is ksetup, and a MIT realm can be declared using:

ksetup /AddKdc NULLROUTE.EU.ORG

It isn't necessary to actually specify any KDC names or other configuration; the mere presence of a realm subkey in the Registry will cause Windows to treat this as a non-AD realm and it'll look up standard _kerberos._udp SRV records. It might be desirable to enable TCP for the Kerberos requests though:

ksetup /AddRealmFlags NULLROUTE.EU.ORG TcpSupported

(There's no need to use /AddKdc before setting the flags – all it does is create an empty Registry key, which /AddRealmFlags can do on its own. The actual configuration is stored under HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos in the Registry, and as long as there's a realm subkey underneath the Domains key, whether empty or not, it will be treated as a non-AD realm.)

Making this work automatically

One problem is that credentials need to be entered and saved for each service separately – it doesn't result in real Kerberos-style "single sign-on". As there is no standalone kinit in Windows, you can't just acquire a TGT and be done with it.

Well, there sort of is a "kinit"; specifying a Kerberos principal actually works with runas /netonly, although RunAs creates a whole separate LSA session instead of putting the tickets in the main session (it's a bit like ksu or setpag in Linux terms.) Regardless, it does make Kerberos work in all other apps (PuTTY, curl, Firefox) started via RunAs, with SSPI being able to acquire credentials without prompting.

(Yet, storing new TGTs in the existing session is something that MSTSC and Explorer are clearly capable of doing, so I hope to one day figure out the necessary SSPI magick to do the same.)

Remote access to PKCS#11 tokens

For a long time I had one "primary" SSH key which I carried around everywhere – there was a copy of it on all my PCs, on my USB stick, and later even some servers – and it could access everything as well, making it… not the best idea. (On some hosts I did also have local keypairs, but didn't want to bother with the combinatioral explosion of having to add each key on many different systems: Git forges, shell accounts, and so on.) In fact, I'd been using the same key since 2011 and it was configured in so many places that I couldn't easily rotate it.

After re-doing my SSH keys from scratch this year, I still ended up having many host-specific keypairs as well as one "main" keypair, but decided to guard it a bit better this time – instead of being copied around to all my systems, now it only exists on hardware tokens (and encrypted backups).

One of those hardware tokens is a TPM1.2 module that I've installed on my server (ember), where I do most of my linuxing nowadays. Setting up the obsolete TPM1.2 software stack (TrouSerS + OpenCryptoki) was kind of a pain, but it still somehow works and I can SSH to various hosts using ssh foo -I libopencryptoki.so.

However, this only works when ssh'ing from the server, yet sometimes I still want to use the same key from hosts which don't have any kind of embedded TPM (such as my work laptop), and I'm usually not in the mood for juggling USB devices, so instead I decided to access the server's TPM module remotely via SSH. The goal is similar to SSH "agent forwarding", but in reverse – instead of signing requests being forwarded from distant servers to the local ssh-agent, they have to be forwarded from the local SSH client to a remote host.

Many Linux systems now ship the p11-kit library, which acts as a PKCS#11 multiplexer and includes a "proxy" module – instead of each program needing to know which PKCS#11 module to use (and usually only one can be used at a time), they can be told to use p11-kit-proxy.so and will immediately see all tokens in all slots through that single module. When invoked, p11-kit looks for *.module configuration files that look like this:

# ~/.config/pkcs11/modules/tpm.module
module: /usr/lib/pkcs11/libopencryptoki.so

However, p11-kit also supports out-of-process modules – it can be configured to spawn the p11-kit remote helper and marshal all PKCS#11 requests through its stdin/stdout. This is very easy to configure by just specifying a remote: command instead of module, so the same tpm.module on a different host would look like:

# ~/.config/pkcs11/modules/tpm@ember.module
remote: |ssh ember p11-kit remote /usr/lib/pkcs11/libopencryptoki.so
enable-in: ssh, ssh-keygen

With this in place, I can now run ssh foo -I p11-kit-proxy.so from my laptop and it will authenticate using the key stored on the remote system, showing a PIN prompt just as if I were using a local smartcard.

Of course, the "background" SSH connection invoked by p11-kit has to use some other credentials (you really don't want it to recursively invoke itself), which in my case is done through Kerberos. (The optionally enable-in parameter limits which programs will see this token, so that e.g. Chrome won't try to load client certificates via SSH.)

In general, this might not be very secure – it just gives me some peace of mind regarding the many copies and locations of the private key. (For example, I don't have to worry about deleting five copies of id_rsa_main.ppk from a dead machine, I can just centrally remove that host from ember's authorized_keys.) The current setup also has a slight downside that TPM1.2 only supports RSA2048 keys, which is sufficient for now but will eventually need an upgrade.