Skip to content

SPN selection fails for keytabs with multiple keys when validating Windows AD Kerberos tickets #392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mfrischknecht opened this issue Apr 14, 2025 · 7 comments · May be fixed by #393
Open
Labels

Comments

@mfrischknecht
Copy link

Describe the bug

I'm in the process of writing a service that uses Kerberos.NET to process and validate Windows AD-issued Kerberos tickets without the service being part of the ticket issuing domain. My service must have the ability to accept tickets for multiple SPNs (for multiple hostnames), which is why I'm dealing with multiple keys exported through Windows' ktpass command (one for each SPN, all of which have a dedicated service user for the secret in the ticket issuing domain). I've merged all of these keytab entries into a single keytab file using the MIT kerberos tooling (i.e. ktutil).

What I'm observing with this setup is that the Kerberos.NET validator (created with the KeyTable from said file) fails to select the appropriate key for the ticket I want to validate (see here). This causes KeyTable.GetKey() to fall back to the first key with the required EncryptionType or simply to the first key in the keytab file. In a keytab file where all keys have the same encryption type and the right key isn't the first entry, this leads to the validator complaining about an "invalid checksum":

System.Security.SecurityException: Invalid checksum
   at Kerberos.NET.Crypto.AESTransformer.Decrypt(ReadOnlyMemory`1 cipher, KerberosKey kerberosKey, KeyUsage usage) in D:\a\1\s\Kerberos.NET\Crypto\AES\AESTransformer.cs:line 128
   at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func`2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 34
   at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75
   at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KeyTable keytab) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 70
   at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 43
   at Kerberos.NET.Entities.KerberosContextToken.DecryptApReq(KeyTable keys) in D:\a\1\s\Kerberos.NET\Entities\SpNego\KerberosContextToken.cs:line 38
   at Kerberos.NET.Entities.NegotiateContextToken.DecryptApReq(KeyTable keys) in D:\a\1\s\Kerberos.NET\Entities\SpNego\NegotiateContextToken.cs:line 38
   at Kerberos.NET.KerberosValidator.Validate(ReadOnlyMemory`1 requestBytes) in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 70
   at Kerberos.NET.KerberosAuthenticator.Authenticate(ReadOnlyMemory`1 token) in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73
   at Kerberos.NET.KerberosAuthenticator.Authenticate(Byte[] token) in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69
   at Kerberos.NET.KerberosAuthenticator.Authenticate(String token) in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 65

Splitting the KeyTable into its entries, creating dedicated validators (one for each key) and selecting the right one for a given ticket (with some ugly workaround code to determine the ticket's SPN) works as expected and the validator for the right key also accepts the provided ticket.

To Reproduce

  • Create multiple service users with multiple SPNs (for multiple hostnames) for a single service in a Windows AD domain
  • Export the secrets for these SPNs into dedicated keytab files using ktpass
  • Merge these keytab files using the MIT Kerberos ktutil binary
  • Use the resulting keytab file to create a Kerberos.NET KerberosValidator
  • Access the service with a Kerberos ticket for an SPN that is not the first entry in said keytab file
  • The validator will throw the mentioned exception

Expected behavior
The validator should pick the right key for the specified SPN and succeed in validating a valid ticket.

Additional context
I dug into the matching logic that decides whether a KeyTable entry corresponds to the SPN of a given ticket and found that the approach of KrbPrincipalName.Matches() doesn't work properly for Windows AD keytab entries and Windows AD tickets:

The method compares the output of KrbPrincipalName.MakeFullName() applied to both principal names being compared. MakeFullName replaces the "service" portion of the principal name (as dubbed by RFC4120, i.e. the protocol such as HTTP/) with the string host/ ONLY IF the principal name was split into two or more segments already. Upon inspecting the keytab entries and tickets issued by Windows AD servers, I noticed that the tickets specified a Name that has been split into its components (i.e. HTTP and the myhost.mydomain.com) whereas the keytab entry exported by Windows' ktpass utility contains the Name as a single, concatenated string (i.e. HTTP/myhost.mydomain.com).

This means that the Matches() call will end up comparing "host/myhost.mydomain.com" to "HTTP/myhost.mydomain.com" and deducing that the keytab entry and ticket aren't compatible. I'm adding two screenshots from bruce's kdecode utility to illustrate the issue in the keytab/ticket structure.

Screenshots

Keytab entry:
Image

Note that this depicts the decoded keytab file as exported by Windows' ktpass utility. This keytab entry hasn't been passed into ktutil yet, so I'm quite certain that the MIT Kerberos implementation is not to blame.

Ticket:
Image

@SteveSyfuhs SteveSyfuhs linked a pull request Apr 14, 2025 that will close this issue
4 tasks
@SteveSyfuhs
Copy link
Collaborator

I've been meaning to deal with this problem for a while now, so thanks for opening the bug and giving me an excuse to do it.

@mfrischknecht
Copy link
Author

mfrischknecht commented Apr 17, 2025

Thank you very much for your swift response and action!

Would it be helpful if I throw together a test setup for PR #393 in the scenario I described above?

(To keep the test realistic, I'd need access to the system we first noticed the issue on; I think that should be doable next week after the Easter holidays)

@SteveSyfuhs
Copy link
Collaborator

It would absolutely be helpful and would be much appreciated. That said, if you want to just grab the change and try it out manually that also works. It's a relatively simple change so it should theoretically "just" work.

@mfrischknecht
Copy link
Author

So, today I finally managed to have a test session with the person maintaining the system in question. We found that there is still a problem with the current changes: While they solve the problem on the decryption side of things, there's a separate signature check that's still behaving like before (and, thus, failing).

I've applied a similar patch to the signature check here:
2604752

With this patch also included, our tests were successful.

@SteveSyfuhs
Copy link
Collaborator

Oh yeah, that makes sense. Let me merge those changes in.

@mfrischknecht
Copy link
Author

mfrischknecht commented Apr 24, 2025

As an aside: I tried to adapt the existing web example into a simple test server executable to perform our tests, but had trouble simply building the project (it's still written for an old .NET Core version and you'd have to wrangle the project setup to get it to build).

So, I quickly sketched out a very basic example using .NET 8.0 in the newer "Minimal API" style instead:
f3ea1db

It's very bare bones (no middleware etc., which certainly is what one would do in a production setting), but I think it doesn't do too bad of a job outlining how the HTTP SPNEGO dance for Kerberos auth works from a protocol perspective. Might this be an interesting example to keep in the official repo? If so, I could prepare a PR…

@SteveSyfuhs
Copy link
Collaborator

Might this be an interesting example to keep in the official repo? If so, I could prepare a PR…

Absolutely. Always willing to accept these sorts of things. I haven't done a good job keeping the samples up to date. Everything tends to fall into the Bruce tool these days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants