Skip to content

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

Closed
@mfrischknecht

Description

@mfrischknecht

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions