Okay folks, let's talk Kerberos. Strap in. It'll be a long one. https://t.co/VE4VmLQfdD
— Steve Syfuhs (@SteveSyfuhs) September 15, 2020
Twitter warning: Like all good things this is mostly correct, with a few details fuzzier than others for reasons: a) details are hard on twitter; b) details are fudged for greater clarity; c) maybe I'm just dumb.
We'll keep the crypto to a minimum because I'm kind of dumb, but here it is in it's most basic form. Wikipedia has everything you need on the crypto aspects of it.
Anyway, the point of this key agreement protocol is so two different parties, A and B, can prove to one another that they have knowledge of the same key without leaking the key to the other party, all the while proving each party is who they say they are.
Why is this the basis for authentication? Because we don't actually care about how a user authenticated (I mean, we do, but...). What we really care about is the application communicating between client A and server B is doing so securely. This is done by using that key.
Remember, we're talking about a time long before SSL and TLS were invented, so this key is *the thing* securing all communications.
It turns out this agreement thing is incredibly difficult to do securely with just two parties (at scale, at least). So we introduce a third party C. Both A and B trust C, so C can act as a go-between for both parties.
If client A can authenticate to C, C can prove to B that A is actually A. Then, C can prove to A that B is actually B. This way A and B really don't need to know anything about each other, other than C says its all good. Let's build this into a protocol: Kerberos.
Okay, so we have our three parties: The client (user/human), the application (say SMB share), and the trusted third party (KDC).
The client knows it needs to talk to the application, so it begins by speaking to the KDC. The client and KDC don't implicitly trust each other, so they need to prove to one another that they are who they say they are. This is done by doing an authentication request (AS-REQ).
An AS-REQ is a message sent to the authentication service (AS). All the AS does is exchange credentials for tickets. These credentials can be anything, but are often passwords.
You'll notice there's nothing in there that includes any password information. That's because of the key agreement thing -- don't leak the key. The KDC happens to know the password, so generates a response (AS-REP).
The AS-REP includes two things: an encrypted ticket, and an encrypted client blob. The encrypted client blob is encrypted using the user password. The KDC has now proven to the client it is the KDC because only the KDC knows the password.
Within the blob is metadata about that other (double) encrypted thing-- the ticket. One other part the metadata is this thing called the session key. The session key encrypts the doubly-encrypted ticket.
The client decrypts this ticket and gets, well, not a lot -- another encrypted blob. That's okay though because the client has everything it needs to set up a session with application B now. This might be confusing because I'm leaving a flow out, but we'll get there. Trust me.
This opaque blob of a ticket was encrypted by the KDC to the target applications long term key -- it's password. The application knows the password so it can decrypt it, and since the KDC knew the password too, the application knows the KDC is the KDC.
Within the blob is a whole bunch of metadata, in fact the same metadata in the client blob. Including that same key.
Aha. So now client and the application know a key only they know (well, the KDC too, hold that thought though). This means the client and application can talk to each other securely! Woohoo!
Hold up you say! I'm missing a huge important piece -- the TGS-REQ. Yep, lets talk about that.
Remember how I said the only job of the AS-REQ is to exchange credentials for tickets? Well it's true. You can say AS-REQ => (creds, 'host/some-app') and the KDC will happily oblige. It turns out this is incredibly inefficient and possibly insecure though.
Credentials are super secret. We don't want them around all the time, and we do some heavy crypto with those creds before we can encrypt stuff to them, so that could just be resource intensive. So what do?
What we do is an AS-REQ and we ask for a special service ticket to a special service called the Ticket-Granting-Service (TSG), i.e. krbtgt. This solves both credential problems. How does a TGS-REQ work?
The TGS-REQ is almost identical to the AS-REQ. In fact the message structure is identical. The difference is that we include a special thing called pre-auth data, which is that encrypted ticket (plus goo, we'll get into it).
The TGS is almost always the same server as the AS, it just has a logically different purpose. The REQ message contains the name of the requested service (host/service.threadabort.net) in this case, plus the ticket granting ticket in the preauth-data.
The TGS receives this message and first checks the preauth-data, extracting that TGT. The TGT is encrypted to the krbtgt long term key -- it's password. Only the KDC knows the krbtgt password, so it knows its genuine and came from itself previously.
Once decrypted we now have that session key that only the client (and now TGS) knows. The KDC generates a response (REP) and instead of encrypting the client metadata to the client password, it encrypts it to the session key.
And so we're back to the client. The client receives the REP, decrypts the client metadata blob, and extracts the key. The key is used to decrypt the doubly-encrypted ticket blob, and voila the client now has a ticket and a key it can send to the application.
But we glossed over the sending-to-the-application part. It turns out this is partially undefined, that way the application protocol (say SMB) can include the ticket as they need it. Mostly, we have some housekeeping first though.
That housekeeping is the act of converting the encrypted ticket blob into an application request (AP-REQ). Why do this, and not just send the ticket? Well, the AP-REQ serves a few purposes. First, it provides a way to prevent message replay, and includes a way to switch up keys.
An AP-REQ is made up of two things: a ticket and an authenticator. We already know the ticket -- it's encrypted to the application long term key, and contains a special session key. The authenticator is special however.
The authenticator is encrypted with the ticket session key. The application should decrypt it because it includes additional metadata, like a sequence number.
This sequence number can be tracked by the application. If it sees the same number more than once it can treat it as a replay and kill the second attempt. See, each time a client kicks off a request it must generate a new AP-REQ, that way the application can prevent replay.
The other important bit of information in this authenticator is a sub-session key. This key is a special key that only the client (and now the application) know, so you can guarantee only the two parties know it without the KDC knowing it. Neat.
The application may or may not decide it wants to switch to the sub-session key. Regardless of that, it still needs to do one final thing: respond to the client with an AP-REP.
The AP-REP is mostly just an ACK. It contains a blob encrypted to the (sub-)session key, and this tells the client that the application is really who they say they are because it could decrypt the ticket issued by the KDC, and therefore the authenticator.
But then it has one last nugget: yet another sub-session key. It turns out the application may decide it wants to use a different key for whatever reason it wants. Now any future communications between client and application are encrypted using keys that can be authenticated.
BUT WAIT THERES MORE!
I go into great detail about how this all fits into the Windows logon process in this other thread, but maybe we can go a bit deeper?