Have you ever wondered how authentication works for things like Remote Desktop?

Yesterday I wrote a follow up thread to how Windows Authentication works and as usual folks have asked for it to be written in a more permanent location.

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.

The first thing the client does is ask what protocol is supported. In this case the target responded and said please do NLA -- network level authentication. The client then immediately prompts for credentials. Doesn't do anything special, just prompts.

Once the user enters their creds NLA kicks in. NLA is the first stage of the CredSSP protocol, which is how those creds you typed in make it to the target server securely. NLA works by first opening an SPNEGO Negotiate connection with the target.

The target happily responds and depending on a few conditions might do a couple different things. In the first case the target provides its machine account's Kerberos Ticket Granting Ticket. It's encrypted so only Active Directory can decrypt it, so its safe to pass around.

The client has the targets TGT and then does a Kerberos TGS-REQ to AD asking for a service ticket to the target name (EDIT host/) termsrv/target.domain.com, and passes the TGT into the additional-tickets field to do something called user-to-user or encrypt-in-session-key auth.

User-to-user is interesting because the KDC will encrypt the service ticket it's about to mint using the session key contained in the additional-tickets TGT instead of the usual long term key of the service itself. In any case the ticket is returned to the client.

In the second case user-to-user is skipped, and the client just requests a ticket to termsrv/target.domain.com and the KDC encrypts the ticket using the service long term key. Nothing exciting here.

In the third case NTLM happens. Blegh.

In the forth case something like PKU2U happens. This is special to AAD-joined target machines only. More on this later.

Anyway... the client has the service ticket to the target and the fun begins. The client converts the ticket into an AP-REQ and fires it off to the target. The target receives it and decrypts it using either the session key in it's machine TGT, or using it's machine password.

The target then generates a session key and stashes it in a place LSA can get to. An AP-REP is returned to the client containing this session key. Following along? It's complicated I know.

Okay, now the client has this session key. Remember how the client prompted for your creds a while back? Well now CredSSP takes those creds and encrypts them using the session key. The client then fires this blob off to the target server.

And the target receives the blob. It takes the session key it stashed away a while back and decrypts the blob. Now it has a username and password. That username and password is passed to the logon UI bits and now we're back to that original thread. Magic!

There are some notable differences here though. For instance remote connections never go through cached logon.

But what about things like smart cards and Windows Hello and Remote Credential Guard?

On Smart Cards and Windows Hello

Smart Cards and Windows Hello are effectively the same thing... ish. They differ in lots of ways, but to RDP it's all certs and stuff.

Smart Card-based CredSSP works similarly to passwords. The NLA portion works just the same. The difference is the creds themselves. It turns out RDP emulates the smart card hardware and literally passes hardware commands back and forth over the channel.

This is, incidentally, why it takes so long for RDP sessions to start when using smart cards. It's proxying hardware commands over the channel encrypted to that session key. It's kinda wild.

On Remote Credential Guard

Remote Credential Guard is something entirely different though. It's an incredibly clever mechanism that prevents clients from sending any primary credentials to the target machine, therefore mitigating any risk of leaking them if the target is compromised.

RCG works by creating a reverse proxy of sorts from the target to the client. Whenever an application on the target needs a ticket of some sort to something it asks LSA as all ten billion apps have done since 1990. LSA is aware of RCG and so it opens a channel back to the client.

Over the channel the target LSA asks the client to ask (ish) the client LSA for a ticket to whatever the target needs. The client obliges, and forwards the ticket. The target now has a ticket, and never saw the creds. It's ingenious.

Now the cool thing about RCG, aside from the security properties, is that it also solves a problem that plagues Windows Hello for Business Key Trust deployments -- specifically that you can't use your WHFB credentials during RDP.

RCG solves this problem by avoiding it entirely -- it doesn't even care what credentials you used. It just asks the client to do all the heavy lifting, and the client is happy to do all the heavy lifting.

On PKU2U

Anyway... a few posts back I mentioned PKU2U. PKU2U is what happens when you take Kerberos, remove any concept of centralized authority, and convert all credentials to asymmetric keys. Seriously.

PKU2U was invented to solve the Homegroup problem. You have two machines that don't inherently trust one another or any shared authority. Well, if you spin up certificates for each side and then exchange them during the initial setup, you get something that kinda sorta works...

And it keeps two or more machines trusting each other without any of this symmetric key garbage.

But I digress. Why does PKU2U matter?

Well, it turns out when AAD was being built into Windows, AAD didn't know how to do Kerberos, and it sure as hell wasn't going to use NTLM for anything. What AAD did have was certificates. Lots of certificates.

And so when you have an AAD-enlightened machine a few certificates are stamped onto the box. When you kick off a PKU2U connection the client connects up to AAD to request a custom certificate just for this, and then kicks off the handshake with the target.

The target sees its PKU2U, checks the certificate from the user chains up to AAD, goes and gets it's certificate from AAD, returning it in the handshake. The client checks the server cert chains to AAD, and voila. Some key agreement goop occurs and now we have a session key.

This incidentally is why its difficult to RDP to AADJ machines. Your client doesn't have the AAD certificates. This was recently fixed where the certs are stamped on the client machine when you register your AAD account. This is also why turning off NLA "fixes" the issue.

Okay, but why NLA?

So that brings up the question: what exactly happens when I turn NLA off? Oh boy. Let me tell you. First of all, it skips NLA (duh). NLA is there to guarantee you're sending creds to a machine you genuinely expect to, not some person in the middle.

NLA provides that guarantee by asking a trusted 3rd party like AD or AAD. Without NLA, there's no check, so no guarantee, so no trust.

Without NLA you're connecting to some remote IP that can't provide any meaningful guarantee it is who you want it to be and you're typing your password into a textbox it has presented to you. If you find that trustworthy, check out this link to your "bank".

Have you ever wondered what happens behind the scenes when you type your password into the Windows logon screen and hit enter?

A few weeks back I wrote out a twitter thread about what happens when you type your password into Windows. A few people commented that it'd be nice if it were documented on a site somewhere instead of just on Twitter, so here we are.

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.

First things first. Windows needs to know how it'll accept your creds. It uses Credential Providers for this. They are the thing that ferries your human-entered credentials into LSA. All they do is take keystrokes and convert them to data structures LSA understands. For a bit of a detour into building Credential Providers see Creating Custom Windows Credential Providers in .NET.

Windows has a list of maybe 2 dozen credential providers to do various tasks, like take your password, handle your smart card, scan your fingerprint, etc. Windows knows what credentials are supported on this machine, so it enumerates them and shows them.

Okay, so you've selected your credential provider and entered your password. Now what? Well, the credential provider takes that password and converts it into a data structure and makes an RPC call to LSA.

Now we're in LSA, and LSA has a list of these things called authentication packages. There's only a handful of them. Maybe you've heard of them: negotiate, Kerberos, msv1_0, etc. Well, LSA itself doesn't really know what any of these do so it just loops through all of them.

LSA asks each package "hey can you deal with these credentials??". Some packages say "pfft, no." Others say "why yes, yes I can". One of these, msv1_0, chimes in and says it'll give it a try, and takes the password and runs it through a KDF (pbkdf2).

Here is, for instance, all the different functions a package can expose to LSA. The one in question here is LogonUser.

typedef struct _SECPKG_FUNCTION_TABLE {
  PLSA_AP_INITIALIZE_PACKAGE              InitializePackage;
  PLSA_AP_LOGON_USER                      LogonUser;
  PLSA_AP_CALL_PACKAGE                    CallPackage;
  PLSA_AP_LOGON_TERMINATED                LogonTerminated;
  PLSA_AP_CALL_PACKAGE_UNTRUSTED          CallPackageUntrusted;
  PLSA_AP_CALL_PACKAGE_PASSTHROUGH        CallPackagePassthrough;
  PLSA_AP_LOGON_USER_EX                   LogonUserEx;
  PLSA_AP_LOGON_USER_EX2                  LogonUserEx2;
  SpInitializeFn                          *Initialize;
  SpShutdownFn                            *Shutdown;
  SpGetInfoFn                             *GetInfo;
  SpAcceptCredentialsFn                   *AcceptCredentials;
  SpAcquireCredentialsHandleFn            *AcquireCredentialsHandle;
  SpQueryCredentialsAttributesFn          *QueryCredentialsAttributes;
  SpFreeCredentialsHandleFn               *FreeCredentialsHandle;
  SpSaveCredentialsFn                     *SaveCredentials;
  SpGetCredentialsFn                      *GetCredentials;
  SpDeleteCredentialsFn                   *DeleteCredentials;
  SpInitLsaModeContextFn                  *InitLsaModeContext;
  SpAcceptLsaModeContextFn                *AcceptLsaModeContext;
  SpDeleteContextFn                       *DeleteContext;
  SpApplyControlTokenFn                   *ApplyControlToken;
  SpGetUserInfoFn                         *GetUserInfo;
  SpGetExtendedInformationFn              *GetExtendedInformation;
  SpQueryContextAttributesFn              *QueryContextAttributes;
  SpAddCredentialsFn                      *AddCredentials;
  SpSetExtendedInformationFn              *SetExtendedInformation;
  SpSetContextAttributesFn                *SetContextAttributes;
  SpSetCredentialsAttributesFn            *SetCredentialsAttributes;
  SpChangeAccountPasswordFn               *ChangeAccountPassword;
  SpQueryMetaDataFn                       *QueryMetaData;
  SpExchangeMetaDataFn                    *ExchangeMetaData;
  SpGetCredUIContextFn                    *GetCredUIContext;
  SpUpdateCredentialsFn                   *UpdateCredentials;
  SpValidateTargetInfoFn                  *ValidateTargetInfo;
  LSA_AP_POST_LOGON_USER                  *PostLogonUser;
  SpGetRemoteCredGuardLogonBufferFn       *GetRemoteCredGuardLogonBuffer;
  SpGetRemoteCredGuardSupplementalCredsFn *GetRemoteCredGuardSupplementalCreds;
  SpGetTbalSupplementalCredsFn            *GetTbalSupplementalCreds;
  PLSA_AP_LOGON_USER_EX3                  LogonUserEx3;
  PLSA_AP_PRE_LOGON_USER_SURROGATE        PreLogonUserSurrogate;
  PLSA_AP_POST_LOGON_USER_SURROGATE       PostLogonUserSurrogate;
} SECPKG_FUNCTION_TABLE, *PSECPKG_FUNCTION_TABLE;

The blob from this is then compared to the verifier stored in the SAM cache and if it matches it tells LSA the user is logged on. This logon triggers your desktop to load. You've signed in and all you've done is touched the cache. What about Active Directory? What about kerberos??

Authenticating to Active Directory

Well, it turns out cached logon always happens first. You're at the desktop and LSA moves on to the next package: Kerberos. Kerberos uses the password to do an AS-REQ to Active Directory and gets a TGT. That TGT gets cached in-memory. It doesn't get stored in the SAM.

At this point you're at the desktop, you now have a TGT and can run around the network, but how exactly did the local computer know anything about you, like your name, your groups, any of that? The TGT is encrypted where the client can't ever read any contents, so what gives?

Identifying Yourself to the the Local Machine

Well, the next thing Kerberos does is uses the TGT to make a TGS-REQ to AD for the machine you just logged on to: host/yourdesktop.your.domain.com. Your machine is registered as a computer object in AD and has a password. The ticket for this machine is encrypted to that password.

Well LSA knows that password so it decrypts the ticket! Within that ticket is your PAC -- Privilege Attribute Certificate -- yeah, silly name. It contains all your user details including your display name and group memberships.

LSA takes that information and merges it into your logon token. Now you're logged in. LSA buttons everything up, updates the cache with any additional details it needs, and you're on your way.

BUT how can you access all your local files and stuff if you don't get your PAC until after you've hit the desktop? Well, it turns out some details get cached with your password verifier in the SAM and they're used until AD can tell you otherwise.

That's why you don't need line of sight to a DC to access your stuff while out on the road for instance.

Okay, but what happens when you've never logged onto this machine before or done something silly like disabled cache logon. Well, it skips the cache check and waits for Kerberos to complete. Then it continues on as normal from here. <See section: Identifying Yourself to the the Local Machine>

What if your password changes or your account has been disabled? Nothing changes. You get to the desktop, and then the AS-REQ fails. Windows recognizes the error if disabled starts flagging the account so the cache will fail the next time around.

If it's just a blip or your password requires changing you'll get the "Windows requires your credentials" notification that asks you to lock and unlock. That way it can kick off the flow again. But what about that notification "your password is going to expire in 5 days"?

...the notification that everyone, myself included, ignores. Well, your PAC includes this information and once Windows reads the PAC and recognizes its nearing <5 days it'll prompt. Incidentally this is why it takes a moment after logon to see the prompt.

Well, one of the reasons.

On Windows Hello

Okay, great, I'm not using a password. I'm using Windows Hello. How does that work? Well, you do the cred prov thing and you pick your face. The cred prov invokes the magic sauce that scans your face and gets a template. It looks up your Windows Hello keys on disk and...

Asks the TPM to decrypt them using the template plus additional information as the key and entropy. The key is then converted into one of those structs and is sent off to LSA and LSA begins its dance again.

LSA loops through everything and hits the cache verifier, does it's thing, and is now at the desktop. From there it invokes our friend Kerberos, but this time does PKINIT, and away we go. <See section: Authenticating to Active Directory>

On Kerberos PKINIT

Maybe lets dig into that a bit: PKINIT. It's almost a four letter word. How do you make a symmetric crypto protocol work with asymmetric keys? Well, first the client generates a Diffie-Hellman key pair and signs the public key using the client certificate private key.

The client fires that message (plug other goo) off to the KDC and the KDC checks the certificate to see if it matches anyone on file in the directory. Suppose it does, and it generates a TGT. The KDC then generates its own Diffie-Hellman key pair, and signs it with the KDC cert.

The KDC then grabs the client DH key, takes the KDC DH key, and derives a shared secret. The TGT is encrypted to that shared key, and then returned to the client. The client receives it, checks the KDC cert is one it trusts, and uses the KDC DH key to derive the same shared key.

The client decrypts the message, gets the TGT, and away we go, again.

On Credential Guard

Okay okay, what about Credential Guard? How does that fit into this madness? Well, we have to go back to when LSA is receiving those creds from the cred prov. LSA has a special channel (well, RPC, it's always some form of RPC) to the secure world LsaIso process.

Anyway, LSA fires the password off to Credential Guard, and when the various packages need to use the password they kindly ask Cred Guard to do whatever it is they needed the password for. In the case of Kerberos LSA asks Cred Guard to encrypt the AS-REQ PA timestamp.

And then LSA asks Cred Guard to decrypt the AS-REP response. Cred Guard then knows the TGT session key is super special so it protects it in such a way that only it can decrypt. Then whenever Kerberos needs the session key it asks Cred Guard.

This basically works like an oracle. Cred Guard returns an opaque blob to LSA. Later LSA asks Cred Guard to decrypt something so LSA hands it the opaque blob and the thing to decrypt. Cred Guard decrypts the opaque blob, then using the key in that blob, decrypts the thing.

It's all very abstract, but this is why you can't steal TGTs when using Cred Guard. The TGT itself is kinda worthless, what you need is the TGT plus the session key, otherwise you can't decrypt the KDC response.

And of course, we already did FIDO the other day.