Authentication the SSPI way

Many people ask frequently on the Microsoft Newsgroups how can they verify that a username and password match against the SAM. Well, on NT, at least, it turns out there are two different methods you can use:

  1. LogonUser(): It’s easy to use, and gives you an access token you can use for impersonation later, but unfortunately, requires that the caller holds the SeTcb privilege, which poses a great security risk. The usual workaround is to create an authentication server that runs as a service under the SYSTEM account and does the real work of calling LogonUser().

  2. SSPI: This is the Security Service Provider Interface, which provides a set of APIs you can use to authenticate a user, and acquire credentials for it. The problem with using SSPI is that is complicated, and some aspects of it are not that well documented. To make matter worse, the SSPI samples on the KB and Platform SDK leave a lot to be desired. The nice thing about SSPI, on the other hand, is that it’s transport-neutral, as you are responsible for transmitting the packets between client and server (which might be in the same process). Also, some providers support message encription.

So, how can you use SSPI, in a single process, to authenticate a given username and password? Here it is step by step:

  1. Load security.dll and get a pointer to the function table.
  2. Say you want to use the NTLM package (which is the most common), so you call QuerySecurityPackageInfo() to obtain a SecPkgInfo pointer (don’t forget to release it later using FreeContextBuffer()).
  3. Now, think of your app as having two sides in the authentication process: a client and a server. For the server, you’ll need to call AcquireCredentialsHandle() once. For the client, you need to do that, too, with a minor change: since you want to get credentials for a different user, you need to fill in a SEC_WINNT_AUTH_IDENTITY struct and pass a pointer to it to AcquireCredentialsHandle() as the 5th parameter. Keep in mind that you should keep the struct around until your done.
  4. Start the client/server conversation by successive calls to InitializeSecurityContext (for the client side) followed by AcceptSecurityContext (for the server side), passing the output buffer of one as the input buffer to the other. Note that on the first call to ISC() you’ll pass NULL as the input buffer.
  5. Finally, keep doing that until the client gets a return value of SEC_E_OK (assuming the auth went ok). It’s important that you watch the client side, because when AcceptSecurityContext() returns SEC_E_OK, you still have to pass the client the buffer returned, so you’re not really done yet.
  6. Impersonate: A call to ImpersonateSecurityContext() will cause you to impersonate the security context of the user you just authenticated. Keep in mind that you call this function with the Server’s context handle, not the client’s. After that, you can go back to the original security context with a call toRevertSecurityContext().

One important thing to notice while you are calling InitializeSecurityContext() and AcceptSecurityContext() is that one of those returns SEC_I_COMPLETE_NEEDED or SEC_I_COMPLETE_AND_CONTINUE, you need to call CompleteAuthToken().

If you are using SSPI in server/client applications, you should be aware of what I consider a rather annoying problem with SSPI: The SSPI client is not notified of the authentication results, and rather always returns SEC_E_OK (unless another error pops up) in the last round, even if the server later denies the logon request. This means you need to make your server trap the return of the last call to AcceptSecurityContext() and explicitly tell your client if the logon request was accepted or not.

I’ve built a C++ library that greatly simplifies the use of SSPI, while keeping it transport-independent, giving the programmer control of the buffers. Included in the package are:

  1. Library Source Code
  2. Precompiled static libs for release ansi and unicode, but you can build the debug versions from the source code
  3. A simple test app
  4. Some documentation, although it’s quite outdated, so don’t trust it too much :)

Minor Comment: The library is very much oriented toward creating authentication services built on top of NTLM or Kerb, so currently you won’t be able to (easily) support things like SCHANNEL. That’s one of my goals for the next version, but I really haven’t had the time to sit down and start version 2.0

Get the file here: wsspi.zip

Update: WSSPI V2.0 development is pretty far ahead, so you should see it in a couple of weeks. Currently, I’m testing the library implementation, and adding SCHANNEL support to it. Here’s some of the things you can expect:

  • Correction of some bugs in the older library.
  • More support for NTLM/Kerb and SCHANNEL providers, plus generic support for any provider.
  • Increased Flexibility (capital F!) plus easier extensibility.
  • Added support for the rest of the SSPI api, like EncryptMessage() and MakeSignature()
  • Automatic Client notification of authentication result.

Update 08/01/2001: There are finally two updates to WSSPI. The first one is courtesy of Jim Johnson, which added support to the basic WSSPI 1.0 for message signing and verification. You can get this version here.
Since I’ve been meaning to finish WSSPI 2.0 for so long, and have not posted anything, I decided to post what’s already built in case someone finds it useful. Note that this is a complete rewrite, from scratch of WSSPI, called WSSPI 2.1, but it is unfinished. Mostly, the Kerberos support is broken. You can get the file here