Many people ask frequently on the Microsoft Newsgroups how can they verify that a username and password math against the SAM. Well, on NT, at least, it turns out there are two different methods you can use:
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().
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:
QuerySecurityPackageInfo() to obtain a
SecPkgInfo pointer (don't forget to release it later using
FreeContextBuffer()).
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.
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.
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.
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:
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:
EncryptMessage() and MakeSignature()
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