Blog Post

Windows Authentication & Smart Cards

A primer on the Windows authentication process, Kerberos, smart cards and password-less entry.

Windows Authentication & Smart Cards - A primer on the Windows authentication process, Kerberos, smart cards and password-less entry.

Intro

I've been recently involved in the research of the logon and authentication process implemented in Windows, and would like to share some general information that may be useful to anyone starting to learn the Windows authentication concepts. Additionally, I've done some research on implementation of the logon via a physical device, such as a smart card, RFID token, or using an external web server, that I will also share further down this blog post.

Table Of Contents

Here's the table of contents for a quick access:

Credential Provider vs. Authentication provider

There's some confusion over the purpose of each of the components involved in the Windows logon process, namely: credential providers and authentication providers.

I'm sure, you can read the links above, but in a nutshell, a credential provider is a COM Interface that executes in the LogonUI.exe process. The main purpose of the credential provider is to collect credentials from the user (such as the user name, password, PIN, etc.) and pass it to the system. A credential provider itself does not allow or deny access to the user. It is mainly just a UI and a conduit to pass the credentials.

I would say that the main method that any credential provider needs to implement is ICredentialProviderCredential::GetSerialization. That is where the system requests from a credential provider the login information packaged in the CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION struct, in which ulAuthenticationPackage is the ID of the authentication provider that will process the data, and rgbSerialization is a pointer to the serialized user credentials themselves of cbSerialization size. This method is called when a user presses "Submit" button after specifying their user name and password.

Windows 10 has an undocumented way of invoking a submit action for the user login, with the ICredentialProviderCredentialEvents4::RequestSerialization method:
C++
virtual HRESULT STDMETHODCALLTYPE RequestSerialization();

If credential provider successfully returns some serialized data, the system in turn calls LsaLogonUser function with it. This isn't done directly from LogonUI, but instead the request is passed to winlogon.exe. It's very easy to see the similarity between the parameters:

CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION vs LsaLogonUser
ulAuthenticationPackage -> AuthenticationPackage
rgbSerialization        -> AuthenticationInformation
cbSerialization         -> AuthenticationInformationLength

LsaLogonUser in turn makes an RPC call to lsass.exe. That process contains all available authentication providers.

All authentication providers are registered in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa key, under Authentication Packages.

But quite often for a workstation there's only one default provider called MSV1_0. And a computer joined into a domain also has additional providers with security packages, such as Kerberos, which may also contain custom subauthentication packages.

To complete authentication, the chosen authentication provider receives a call to its LsaApLogonUserEx2 method, which then requests permission for the specified user to acquire a logon to the system. One of the most important results that it needs to return, in case of success, is a pointer to one of the LSA_TOKEN_INFORMATION_V* structs in the TokenInformation parameter.

If we look into the LSA_TOKEN_INFORMATION_V3 struct, you will see the following important logon parameters, such as:

LSA_TOKEN_INFORMATION_V3 struct
TOKEN_USER          User;
PTOKEN_GROUPS       Groups;
TOKEN_PRIMARY_GROUP PrimaryGroup;
PTOKEN_PRIVILEGES   Privileges;

That is, a User SID, different user groups that the user is a member of, a set of privileges. All of these are required to make a new user token.

Thus the system calls ZwCreateToken[Ex] function with the parameters supplied by the credential provider:

C++
NTSTATUS ZwCreateToken
(
	PHANDLE	hToken,
	ACCESS_MASK DesiredAccess,
	POBJECT_ATTRIBUTES ObjectAttributes,
	TOKEN_TYPE Type,
	PLUID AutenticId,
	PLARGE_INTEGER ExpirationTime,
	PTOKEN_USER User,
	PTOKEN_GROUPS Groups,
	PTOKEN_PRIVILEGES Provileges,
	PTOKEN_OWNER Owner,
	PTOKEN_PRIMARY_GROUP PrimaryGroup,
	PTOKEN_DEFAULT_DACL DefaultDacl,
	PTOKEN_SOURCE Source 
);

That call creates and returns a user token back to winlogon (via LsaLogonUser call.) And then winlogon starts new process(-es), that are registered under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon key, under Userinit. In most cases, it is C:\Windows\system32\userinit.exe. Such process(-es) are started with the new user token using the CreateProcessAsUserW call. userinit.exe in turn runs the logon scripts, sets up a network connection, applies the Group Policies, and loads the Windows user shell.

This is the process of a local user logon in Windows, in a nutshell.

A Recap

So to recap, the job of a credential provider is to collect the login information from the user (or from a physical security device.) Such information may be user name and password, but may also include a TOTP security PIN, and some other biometric details.

The job of an authentication provider is to validate credentials passed by a credential provider, and to return information (LSA_TOKEN_INFORMATION_V1, LSA_TOKEN_INFORMATION_V2 or LSA_TOKEN_INFORMATION_V3) needed for creation of a user token for a successful login.

The format of the information that a credential provider passes to the system (via its GetSerialization method) must adhere to the format understandable by an authentication provider. In other words, those two must be able to communicate with each other.

For example: MSV1_0 authentication provider only uses MSV1_0_INTERACTIVE_LOGON format, which always requires a user name and a password!

Microsoft's default MSV1_0 authentication provider uses the SAM database (stored in the \REGISTRY\MACHINE\SAM\SAM\Domains\Account\Users key) to check if requested user name and password match. (The database stores only encrypted password hashes.) If authentication passes, it returns the results in the LSA_TOKEN_INFORMATION_V* struct.

Kerberos, on the other hand, also accepts KERB_SMART_CARD_LOGON and KERB_CERTIFICATE_LOGON formats.

And that's where the authentication via a physical device, such as a smart card, or an external web server comes into play.

Authentication Via a Smart Card

A smart card is technically a digital certificate in a physical form that holds containers, that have the certificate and a private key, that corresponds to a public key from the certificate.

A smart card implements a primitive internal file system, that holds several crypto-containers, with data pertaining to the digital certificate. For instance, such containers have the certificate info and a private key.

It is important to note that a private key never leaves the smart card. And a public key is stored in the certificate and is publicly accessible.

When a smart card is inserted, the smart card credential provider checks if it has a certificate, and for the certificate selected by the user, it offers to enter a PIN. The smart card replies whether the PIN matched or not. If its a match, it passes the name of the smart card and the digital certificate (or container), along with the PIN into the GetSerialization method.

Technically, instead of the password a smart card uses its private key that is stored inside and is never exposed in any request. After a request a smart card can sign data with its private key, or create an encrypted hash. In other words, it realizes the BCryptSignHash function, that can encrypt and decrypt data.

The details of the sign-in process flow with smart cards in Windows contains multiple steps. So just to clarify some of them:

  • 11. A smart card needs to establish the relation between the certificate and provided user account. This is usually done using the "Subject alternative name" in the certificate. (Ex: user@domain.)
  • 12. It needs to verify trust of the certificate in the system. It must be digitally signed by another certificate, whose Issuer is present in the "NTAUTH certificate store", and is trusted by the system (which usually means that it is present in the "Trusted ROOT store".)
  • 12. Additionally, there's also a check that a smart card contains a public key corresponding to the user's public key. This can be done as follows: a random hash is generated and is sent to the smart card, which digitally signs it. Then the signature is verified using the public key. Or, some random hash is first encrypted and then sent into smart card for decryption. If the smart card can decrypt it and return it, the hash is compared for a match.

Kerberos

The way Kerberos performs its authentication is as follows:

  • It checks if the digital certificate that it receives is registered in the system.
  • If yes, it then reads the public key from that certificate.
  • Then calls BCryptImportKeyPair with BCRYPT_RSAPUBLIC_BLOB.
  • It then generates random bytes with a call to BCryptGenRandom and hashed with BCryptHashData.
  • The resulting hash is passed into the smart card with a request to digitally sign it. (The smart card technically emulates a call to the BCryptSignHash function.)
    The specification also requires a smart card to request a PIN, and if such is not done, or if the PIN is not valid, the smart card must refuse to digitally sign the hash.
    In case of a virtual smart card, it may emulate the physical smart card actions in the following way: The private key can be stored encrypted in the BCRYPT_RSAPRIVATE_BLOB format. When the virtual card receives the PIN, it reads its hash and creates a symmetric key from it. It then decrypted the private key in memory from BCRYPT_RSAPRIVATE_BLOB and calls BCryptImportKeyPair. And if the PIN doesn't match, the private key cannot be decrypted, and a call to BCryptImportKeyPair returns an error. Otherwise, the card returns a digitally signed hash back.
  • It then calls BCryptVerifySignature on the digitally signed hash returned by the smart card, that verifies the signature.

Additionally, there's another method that is used by the certutil tool:

  • It encrypts a hash to some random data with a public key:
    pseudo-code: certutil
    cert -> BCRYPT_RSAPUBLIC_BLOB
    BCryptImportKeyPair(BCRYPT_RSAPUBLIC_BLOB) -> key
    BCryptGenRandom -> data
    BCryptHashData(data) -> hash
    BCryptEncrypt(key, hash) -> xxx
  • I then sends such encrypted data to the smart card (again, along with the PIN) with a request to decrypt it and return the result.
  • When it receives the result, it decrypts it and compares to the original:
    pseudo-code:
    decrypt (BCRYPT_RSAPRIVATE_BLOB) with PIN
    BCryptImportKeyPair(BCRYPT_RSAPRIVATE_BLOB) -> key
    BCryptDecrypt(key, xxx) -> yyy

Lastly, a software developer can write their own cryptographic algorithm for the authentication provider, or add some extra security to the system, such as an outside web server that can perform additional authentication and storage of sensitive logon information.

Authentication With External Web Server

This method could be used for an implementation of the classic 2FA credential provider via an external web server:

It works like an external wrapper over the built-in system authentication via a password, PIN, WLID, smart card, etc.
  • On the first step, our credential provider lets the default credential provider to collect login data from the user. It can be a user name and password, but it can also be a PIN from another provider, and even some biometric data. All that data is eventually transferred in the MSV1_0_INTERACTIVE_LOGON format for the MSV1_0 to be able to understand it.
  • Then we ask the user to enter a one-time-password (or OTP). After that we send it to our authentication web server for verification.
    One classic approach here is to use the time-based-one-time-password algorithm to generate the OTP in our credential provider and on the web server.

    Additionally we can use a random nonce for authentication. But then the question is, where do you store that nonce? We can store it on the server, which is understandable. But what about the local system? There, it has to be encrypted. For that we have to use a PIN provided by the user before they enter the OTP, so that we can use it to decrypt our random nonce.

  • If the server replies with OK, we pass on the MSV1_0_INTERACTIVE_LOGON struct that we received from the default credential provider, which allows user login.
  • But if the server replies that the OTP is not correct, we do not continue with the serialization and return an error from the call to GetSerialization. This effectively prevents user login.

Password-less Authentication

One has to remember that a credential provider must pass a password to a (default) authentication provider. But where do you get that password from, if the authentication process was not intended to have one? Say, if some external device, such as a smartphone was used to verify a user's identity.

One option is to store such password on the web server. Or, to store it locally encrypted by the PIN. The built-in PIN-provider in Windows 10 uses this scheme: it first receives a PIN from the user and then decrypts the password with it, that it uses later with the authentication provider.

Or, here's the password-less authentication that I developed with the use of RFID cards and a web server:

  • The device contains a unique ID and the credential provider sends it to the web server.
  • The web server checks if such ID is registered in its database.
  • Then the user sends in the OTP passcode, generated by the smart card.
  • The web server checks the OTP and if it's correct, replies back with the password for the authentication provider. Otherwise returns an error.

In many ways, a password-less authentication doesn't mean that the password is not used. As I showed above, the password is either returned by a web server, or by some outside device, or is stored on the local system and is decrypted after receiving a PIN or some other uniquely identifying information from the user. In other words, even though the authentication password is not directly exposed to the user it may still be used internally.

To create a true password-less authentication in Windows one needs to create their own authentication provider besides a credential provider. The latter one is needed to provide user interface for the custom authentication provider. That way, it can be written to implement any of its own authentication schemes.

Conclusion

This was just the basic information on writing a custom logon & authentication in Windows. It seems like this subject started to gain traction now, so if anyone is interested to learn more internals about this concept I can expand this blog post upon further requests from the readers.

Related Articles