dotnet / Kerberos.NET

A Kerberos implementation built entirely in managed code.
MIT License
520 stars 91 forks source link

"Invalid checksum" Error When Trying to Authenticate With KerberosAuthenticator #367

Closed ldapcoder closed 6 months ago

ldapcoder commented 7 months ago

I have written an LDAP server that, among other things, processes Kerberos authentications. I had been using an old C SSPI library to process these authentications, but would like to use Kerberos.NET for a multitude of reasons. My code looks like:

byte[] token = ... (parsed Kerberos request bytes from incoming request)

KeyTable table = new KeyTable(File.ReadAllBytes()); KerberosAuthenticator authenticator = new KerberosAuthenticator(table);

Task authenticateTask = authenticator.Authenticate(token); authenticateTask.Wait(); KerberosIdentity claimsIdentity = (KerberosIdentity)authenticateTask.Result;

I am getting an exception with the following stacktrace when calling authenticateTask.Wait():

System.AggregateException: One or more errors occurred. ---> System.Security.SecurityException: Invalid checksum at Kerberos.NET.Crypto.AESTransformer.Decrypt(ReadOnlyMemory1 cipher, KerberosKey kerberosKey, KeyUsage usage) in D:\a\1\s\Kerberos.NET\Crypto\AES\AESTransformer.cs:line 112 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 39 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.d15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 75 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.d13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.d12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 --- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at LdapSession.d58.MoveNext() in LdapSession.cs:line 629 ---> (Inner Exception #0) System.Security.SecurityException: Invalid checksum at Kerberos.NET.Crypto.AESTransformer.Decrypt(ReadOnlyMemory1 cipher, KerberosKey kerberosKey, KeyUsage usage) in D:\a\1\s\Kerberos.NET\Crypto\AES\AESTransformer.cs:line 112 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 39 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.d15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 75 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.d13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.d__12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 The Zone of the assembly that failed was: MyComputer<---

SteveSyfuhs commented 7 months ago

The Invalid Checksum error means the key used to decrypt the incoming AP-REQ is wrong. Since it's pulling the key from KeyTable table = new KeyTable(File.ReadAllBytes()) that would suggest the keytable either has the wrong key or no key at all (though no key should trigger a different error).

You can test this by ruling out any oddities with KeyTable lookups with something like this:

foreach (var entry in keyTable.Entries)
{
    KerberosAuthenticator authenticator = new KerberosAuthenticator(new KerberosValidator(entry.Key));

    try 
    {
        var identity = await authenticator.Authenticate(token);
    }
    catch (Exception ex) 
    {
        // blah
    }
}

If it is a key selection issue then I can make that logic a bit smarter based on what you happen to find with the above test.

ldapcoder commented 7 months ago

Thanks for your quick reply. I put in the lines of code you suggested and am getting the following errors now:

System.InvalidOperationException: Key EType DES_CBC_CRC must match the encrypted data EType AES256_CTS_HMAC_SHA1_96 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 27 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.<Validate>d__15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 70 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at LdapSession.d__58.MoveNext() in LdapSession.cs:line 629

System.InvalidOperationException: Key EType DES_CBC_MD5 must match the encrypted data EType AES256_CTS_HMAC_SHA1_96 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 27 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.<Validate>d__15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 70 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at LdapSession.d__58.MoveNext() in LdapSession.cs:line 629

System.InvalidOperationException: Key EType RC4_HMAC_NT must match the encrypted data EType AES256_CTS_HMAC_SHA1_96 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 27 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.<Validate>d__15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 70 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at LdapSession.d__58.MoveNext() in LdapSession.cs:line 629

System.Security.SecurityException: Invalid checksum at Kerberos.NET.Crypto.AESTransformer.Decrypt(ReadOnlyMemory1 cipher, KerberosKey kerberosKey, KeyUsage usage) in D:\a\1\s\Kerberos.NET\Crypto\AES\AESTransformer.cs:line 112 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 39 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.d15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 75 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.d13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.d12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at LdapSession.d58.MoveNext() in LdapSession.cs:line 629 The Zone of the assembly that failed was: MyComputer

System.InvalidOperationException: Key EType AES128_CTS_HMAC_SHA1_96 must match the encrypted data EType AES256_CTS_HMAC_SHA1_96 at Kerberos.NET.Entities.KrbEncryptedData.Decrypt[T](KerberosKey key, KeyUsage usage, Func2 func) in D:\a\1\s\Kerberos.NET\Entities\Krb\KrbEncryptedData.cs:line 27 at Kerberos.NET.Crypto.DecryptedKrbApReq.Decrypt(KerberosKey ticketEncryptingKey) in D:\a\1\s\Kerberos.NET\Crypto\DecryptedKrbApReq.cs:line 75 at Kerberos.NET.Entities.ContextToken.DecryptApReq(KrbApReq token, KeyTable keytab) in D:\a\1\s\Kerberos.NET\Entities\SpNego\ContextToken.cs:line 45 at Kerberos.NET.KerberosValidator.<Validate>d__15.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosValidator.cs:line 70 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__13.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 73 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Kerberos.NET.KerberosAuthenticator.<Authenticate>d__12.MoveNext() in D:\a\1\s\Kerberos.NET\KerberosAuthenticator.cs:line 69 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at LdapSession.d__58.MoveNext() in LdapSession.cs:line 629

SteveSyfuhs commented 7 months ago

Okay, not that surprising. The key in the keytab "just" isn't the correct key. How did you generate the keytab file?

ldapcoder commented 7 months ago

I have 2 domains, which have a 2-way transitive trust. The LDAP server is running in domain A and I'm trying to authenticate users from domain B. I created the keytab file in domain B:

ktpass /princ host/krbsvt@B.local /mapuser B\krbsvt /pass password /out Bkeys.keytab /crypto all /PTYPE KRB5_NT_PRINCIPAL /mapop add

B\krbsvt has “This account supports Kerberos AES 256 bit encryption”, "Password never expires" and "User cannot change password" enabled. I have verified that the UPN for user B\krbsvt got changed to host/krbsvt@B.local after generating the keytab file. I also validated that I could log into domain B with B\krbsvt and password.

SteveSyfuhs commented 7 months ago

If the LDAP server is in domain A why are you giving it a service account from domain B?

What is the SPN you're using for the LDAP server? What is the name you're passing to the client to resolve that SPN? I would guess it's getting a referral to A, and resolving an SPN from domain A.

ldapcoder commented 7 months ago

I created the keytab file in domain B, which is where the accounts are that will be used for Kerberos authentications, and which is why I used the service account from domain B.

The SPN, registered on the LDAP server in domain A, is ldap/host.A.local:port. I connect to ldap://host.A.local:port from the client in domain B. Domain B does not have any SPNs registered for ldap/host.A.local:port. Should I have the SPN registered in domain B as well? And if so, to the same service account I'm using for the keytab file?

Thanks again.

SteveSyfuhs commented 7 months ago

There are two sides to this. Clients making outbound calls -- "hello, please get me a ticket to ldap/blah@domain", and servers that accept inbound calls -- "hello, I am ldap/blah@domain". The client must go ask AD for a ticket to "ldap/blah" and AD encrypts a ticket to the key that only "ldap/blah" can decrypt via the keytab. The call you have here is just the server accept side. If you're asking for ldap/host.A.local then the domain of the client is going to think it needs a referral to A.local, and then the A.local domain controller is going to resolve an SPN by that name. You definitely got a ticket, so that SPN exists explicitly or it's getting resolved through the wildcard "host/" SPN.

ldapcoder commented 7 months ago

So it sounds like SPNs are OK. Did you see anything of concern with the way I'm creating the keytab file, since that seems the be the cause of the errors I'm seeing?

SteveSyfuhs commented 7 months ago

You have an SPN of ldap/host.a.local[:port] registered to the krbsvt@b.local account?

ldapcoder commented 7 months ago

Yes, I added the SPN to the domain B krbsvt user, re-generated the keytab file and still no luck.

I also used Bruce with the verify flag to inspect the keytab file. I see the Principal Name is listed as host/krbsvt. Is that correct even though I set the principal to host/krbsvt@b.local?

SteveSyfuhs commented 7 months ago

From within bruce what does klist get ldap/host.a.local --verbose with or without the port get you? If you get Cannot request a service ticket until a user is authenticated then please also do kinit from there too.

ldapcoder commented 7 months ago

I assume you wanted the output from bruce on server B:

`bruce>klist get ldap/host.A.local --verbose

[Info] A ticket was retrieved for ldap@host.A.local

Ticket Count: 3

0> Client : MYUSER @ B.LOCAL

               Server : krbtgt@B.LOCAL @ B.LOCAL
         Ticket EType : AES256-CTS-HMAC-SHA1-96 (18)
                Flags : 0x40e10000 -> Encrypted Pre-Authentication, Pre-Auth

enticated, Initial, Renewable, Forwardable Start Time : End Time : 4/18/2024 1:59:46 AM -04:00 Renew Until : 4/18/2024 3:59:46 PM -04:00 Session Key Type : AES256-CTS-HMAC-SHA1-96 (18)

1> Client : MYUSER @ B.LOCAL

               Server : krbtgt@A.LOCAL @ B.LOCAL
         Ticket EType : AES256-CTS-HMAC-SHA1-96 (18)
                Flags : 0x40a10000 -> Encrypted Pre-Authentication, Pre-Auth

enticated, Renewable, Forwardable Start Time : End Time : 4/18/2024 1:59:46 AM -04:00 Renew Until : 4/18/2024 3:59:46 PM -04:00 Session Key Type : AES256-CTS-HMAC-SHA1-96 (18)

2> Client : MYUSER @ B.LOCAL

               Server : ldap@host.A.local @ A.LOCAL
         Ticket EType : AES256-CTS-HMAC-SHA1-96 (18)
                Flags : 0x40a50000 -> Encrypted Pre-Authentication, Ok as De

legate, Pre-Authenticated, Renewable, Forwardable Start Time : End Time : 4/18/2024 1:59:46 AM -04:00 Renew Until : 4/18/2024 3:59:46 PM -04:00 Session Key Type : AES256-CTS-HMAC-SHA1-96 (18)

bruce>`

SteveSyfuhs commented 7 months ago

There's your issue. If you walk the list you'll see the order of operations.

  1. Get TGT(B) for B.LOCAL
  2. Have TGT(B), get ldap/..., KDC said it needs a referral to A.local, get TGT(A)
  3. Have TGT(A), get ldap/... from A.local

The SPN is not registered correctly in B.local so the KDC never finds it, and thinks it needs to go ask A.local instead.

ldapcoder commented 7 months ago

Thanks again for all your help. Would this still be your explanation, knowing that I can authenticate via Kerberos from server B to server A AD (not my LDAP instance)?

SteveSyfuhs commented 7 months ago

Yes, because the Server A would have the key of host.A.local because that's its key.