Share via


The Security Support Provider Interface Revisited
Keith Brown
T

his is a follow-up to an earlier column on the Security Support Provider Interface (SSPI), the Windows® equivalent of the GSSAPI interface. It's extremely unfortunate for developers trying to write platform-neutral code that they are not equivalent, but back when SSPI was developed, platform neutrality was not a priority. Way back in August 2000, I explained the need for the SSPI interface; namely, it abstracts the differences between various authentication protocols. I also showed how you can use SSPI to perform an initial authentication handshake by simply ferrying opaque tokens back and forth between the client- and server-side SSPs over whatever network protocol the client and server happen to be using to communicate.
      The client and server first must select the credentials they want to use during the exchange (via AcquireCredentialsHandle). Then they use InitializeSecurityContext and AcceptSecurityContext to generate outgoing authentication tokens and process incoming tokens on the client- and server side of the conversation, respectively.
      This month I'd like to show you how to use the resulting session key that both the client and server discover during the initial authentication handshake. Session keys can be used to encrypt messages or to simply affix a message authentication code (MAC) to allow tamper detection and authentication of cleartext messages. After showing the SSPI APIs you need to call, I'll show how the SSPI workbench utility (presented with my August 2000 column) can be used to send encrypted or signed messages. Then I'll show how SSPI can be used to validate passwords. Finally, I'll describe a few experiments that you can try with the workbench that will help you explore how Kerberos, NTLM, and SPNEGO are implemented in Windows.

Encrypting Messages for Confidentiality

      Once you've established a fully formed SSPI context handle (clients do this by calling InitializeSecurityContext, while servers do this by calling AcceptSecurityContext), you can begin to make use of the session key you now share with your peer by sending encrypted (and/or signed) messages.
      The SSPI functions for encryption and decryption appear to be quite simple, but there are some subtleties that can make them somewhat tricky to use, as the following code shows:

  SECURITY_STATUS EncryptMessage(
  
PCtxtHandle phContext,
ULONG fQOP,
PSecBufferDesc pMessage,
ULONG MessageSeqNo
);
SECURITY_STATUS DecryptMessage(
PCtxtHandle phContext,
PSecBufferDesc pMessage,
ULONG MessageSeqNo,
PULONG pfQOP
);

 

      For a long time, due to export restrictions, these functions were implemented, but not documented. As of Windows 2000, the documentation was updated, but unfortunately the documentation only really discusses how to use these functions with Secure Sockets Layer (SSL), so some clarification is in order.
      First, if the SSP context attributes include integrity protection (ISC_RET_INTEGRITY), which you can request by specifying ISC_REQ_INTEGRITY as a context requirement during the initial authentication handshake, then when you encrypt messages your SSP will also append a MAC (you don't need to also call MakeSignature). Because the SSP often appends a MAC along with other bookkeeping information, and because with many cryptographic algorithms a bit of padding is necessary for alignment, you'll need to provide an extra buffer to store this added information. I'll describe how to do this shortly.
      The parameters to these functions are quite straightforward: phContext refers to a context handle previously established via an authentication handshake (remember, the context handle ultimately abstracts the session key discovered during authentication). The fQOP flag allows extra control on a per-SSP basis. The only documented flag is related to Kerberos, and has to do with GSSAPI compatibility, which I'll discuss later in this column. The pMessage parameter points to the message that will be encrypted or decrypted in place, and MessageSeqNo is just a 32-bit number that describes the expected sequence number of the message. If you requested sequence detection (ISC_REQ_SEQUENCE_DETECT) during the initial authentication handshake, then when encrypting a message you can specify a sequence number for the message. If sequence detection is turned on, then by definition, integrity protection is also turned on, which means the entire message, including the sequence number, will be protected by a MAC. Whoever calls DecryptMessage should specify the expected sequence number via MessageSeqNo, and if the sequence numbers don't match, the call to DecryptMessage fails, indicating an out-of-sequence message.
      As I mentioned earlier, the effective size of the message will sometimes grow slightly after calling EncryptMessage due to block cipher padding, SSP bookkeeping information, and perhaps even the addition of a MAC and a sequence number. Since the message is encrypted in place, where does this extra information go? It obviously needs to be sent along with the encrypted message to your peer because it'll be required when decrypting the message.
      It turns out that the pMessage pointer doesn't point directly to the bits of the message; rather, it points to a buffer descriptor. The buffer descriptor consists of a counted array of elements that each describe a range of memory via a pointer and a byte count, plus a flag that indicates how you want your SSP to use that particular range of memory:

  typedef struct _SecBuffer {
  
unsigned long cbBuffer; // bytes in buffer
unsigned long BufferType; // type of buffer
void SEC_FAR * pvBuffer; // the buffer
} SecBuffer, SEC_FAR * PSecBuffer;

typedef struct _SecBufferDesc {
unsigned long ulVersion; // SECBUFFER_VERSION
unsigned long cBuffers; // count of buffers
PSecBuffer pBuffers; // array of buffers
} SecBufferDesc, SEC_FAR * PSecBufferDesc;

 

      The idea is that when you present a buffer to your SSP for encryption, you can specify which parts of the buffer contain the data to be encrypted and which parts of the buffer are reserved for the SSP to write all the extra information that I described earlier. If you're using Kerberos or NTLM, you dictate to the SSP exactly where this extra information should go. However, if you're using SSL, the buffer management works quite differently, which is why I'm not going to discuss SSL in this particular column. The problem with the SSPI documentation of EncryptMessage and DecryptMessage is that it assumes you're using SSL, so it takes a bit of experimentation to figure out how to lay out your buffer descriptors when using other protocols.
      SSPI.H defines several constants that describe the various types of buffers (see Figure 1). When encrypting and decrypting messages, the SECBUFFER_DATA flag indicates the part of the buffer that is to be encrypted or decrypted. There are several options for where an SSP can write its extra information, and this is where the current SSPI documentation falls short. The following types all seem as though they could be used to house this information:

  SECBUFFER_TOKEN
  
SECBUFFER_STREAM_HEADER
SECBUFFER_STREAM_TRAILER
SECBUFFER_PADDING

 

      If you're using Kerberos authentication, the buffer types that you use and the way that you lay out your buffer depend on whether you want compatibility with GSSAPI. If you desire compatibility with GSSAPI (perhaps you are communicating with a Unix box), you'll sandwich the data you want to encrypt between buffers of type SECBUFFER_TOKEN and SECBUFFER_PADDING, as shown in Figure 2, and pass this to the EncryptMessage function. You'll then concatenate the resulting three buffers and send the result as the encrypted message. The required sizes for these wrapper blocks are not at all obvious, as the figure shows. In order to retrieve the sizes variable that I mention in Figure 2, call QueryContextAttributes, specifying SECPKG_ATTR_SIZES.
      When you receive a GSSAPI encrypted or signed message, you'll describe it with a buffer of type SECBUFFER_STREAM, as shown in Figure 3, and provide that buffer along with a second buffer of type SECBUFFER_DATA that will receive the decrypted message. If your application needs to be compatible with GSSAPI, I strongly recommend that you spend some time with the Platform SDK GSSAPI sample (�\Samples\WinBase\Security\Win2000\GSS), which demonstrates the correct technique.
      If you're not worried about compatibility with GSSAPI, things are a bit more intuitive, and you'll only need two types of buffers: SECBUFFER_DATA for your data, and SECBUFFER_TOKEN for the extra data that your SSP will produce. Figure 4 shows how this looks, along with the size of each buffer. Since the SSPI workbench is designed to talk only to another instance of itself, it takes this simpler approach. See Figure 5 for an annotated example from the SSPI workbench.

Using the SSPI Workbench to Send Encrypted Messages

      Try sending an encrypted message with the SSPI workbench. To do this, run two copies of the workbench, one as the server and one as the client, and choose Kerberos for your provider. Go through the authentication handshake (InitializeSecurityContext on the client side and AcceptSecurityContext on the server side). Now that you've exchanged a session key, you should be able to send encrypted messages back and forth. To do this, go to the client and press the Compose button. At this point, the large window on the right that's normally used for viewing information created by your SSP becomes active, and you can type in a short text message to be encrypted. Press the Encrypt button to encrypt the message.
      Note at this point that two buffers are displayed in the output window. These are the buffers I referred to in Figure 4. The first is the data buffer; notice that it was encrypted in place. The second holds data that the server-side SSP will need during decryption. This includes an encrypted sequence number to help detect replay attacks. Go ahead and send this to the server by pressing the Transmit button. On the server side, press the Receive and Decrypt buttons, and out pops the original message. If you don't get any errors, the sequence number that the workbench maintains automatically was also verified, so the server trusts that this is not a replay.
      You should be able to send encrypted messages back and forth from the client to the server and vice versa, but keep in mind that the workbench relies on a very simple control mechanism: the user is expected to keep the client and server conversation synchronized. Make sure while one of the parties (client or server) is busy composing and sending a message, the other party is getting ready to receive. If you both speak at once, the workbench will get confused.

Signing Messages for Integrity and Replay Detection

      Creating signatures works much the same way as encrypting messages, at least programmatically. Once again, if you want compatibility with GSSAPI, you will need to be very careful about how you arrange your buffers; the sample code from the Platform SDK that I mentioned earlier should help you. If you're not worried about compatibility with GSSAPI, the buffer setup (see Figure 4) is the same as for encrypting messages. In this case, the SECBUFFER_TOKEN type buffer will hold the resulting signature. Here are the SSPI calls you'll make to create or verify a signature:

  SECURITY_STATUS MakeSignature(
  
PCtxtHandle phContext,
ULONG fQOP,
PSecBufferDesc pMessage,
ULONG MessageSeqNo
);
SECURITY_STATUS VerifySignature(
PCtxtHandle phContext,
PSecBufferDesc pMessage,
ULONG MessageSeqNo,
PULONG pfQOP
);

 

Note the first parameter to each of these calls is the context handle, which points SSPI to the session key that should be used to create or verify the signature. See Figure 6 for an annotated example from the SSPI workbench.

Another Use for SSPI

      Many people have sent me mail asking how to verify a user name and password combination, having been flustered by the LogonUser API, which requires its callers to have the Trusted Computing Base (TCB) privilege. I've talked about the TCB quite a bit in past columns, but suffice it to say that if you're not running in the SYSTEM logon session, it's generally unlikely that you will have this privilege. So how do normal applications go about verifying passwords for user accounts if they have such a need?
      It turns out that SSPI can be used quite easily for this purpose, by simply playing the role of both the client and the server and simulating a network authentication handshake within a single process. If the user name and password combination isn't valid, depending on whether you're using the NTLM or Kerberos SSP, either AcceptSecurityContext or InitializeSecurityContext will fail at some point. If, on the other hand, validation succeeds, the result is a token that refers to a network logon session for the client. You can even place this token on your thread via impersonation, which will allow you to acquire local resources using the credentials you've specified. But note that since you've performed a network authentication handshake, it's unlikely that the logon session will have the client's network credentials. So don't try to access remote secured resources while impersonating. It will not work without enabling delegation, which would make the code considerably less reliable, as delegation is only allowed in very specific, administrator-controlled circumstances. In many cases, you'll simply throw the resulting token away, especially if all you want to do is validate a password (see Figure 7).
      I've tested the code in Figure 7 with both the NTLM and Kerberos providers, and it works flawlessly in both cases. Note that running this code will sometimes cause network packets to be sent to a domain controller, if your code is not already running on a domain controller that can validate the password. Also note that just because you can use this code for password validation doesn't mean that the SSP is sending the password across the wire to the domain controller. Rather, the NTLM server-side SSP will use pass-through authentication to contact the domain controller, and the Kerberos client-side SSP will use a KRB_TGS_REQ message (sometimes preceded by a KRB_AS_REQ message) to obtain tickets that can be validated by the server-side SSP. You just happen to be playing both roles and talking to the client- and server-side SSPs from the same process, which is perfectly legal.

Exercises for the Reader

      The SSPI workbench can teach you a lot about how network authentication works on Windows. Consider using it in conjunction with TKTVIEW.EXE, a sample I developed in the May 2000 issue, and with NETMON.EXE, which will help you see the contents of network packets being sent to and from the domain controller. The SSPI workbench shows you the tokens being sent between the client and server, but you'll need to sniff the network to see how your SSP communicates with domain controllers.
      The following is a laundry list of exercises that you can use to explore the way authentication works, using the workbench. (I'll post answers on my Web site, https://www.develop.com/securitybriefs, but do the exercises first and try to figure out what's going on before peeking.) You'll need three machines for these tests: the first will be a domain controller, and the second and third will be designated as the client and server machines, respectively. The client and server machines should be members of the domain hosted on the domain controller. For these experiments, be sure to use Windows 2000 domain accounts instead of local accounts which are always authenticated using NTLM, and all of these exercises use Kerberos in some way.
Authorization Attribute Latency
Log in to the client machine using a domain account, and use the workbench to establish an authenticated connection to the server machine using NTLM. Once this is done, examine the resulting token by pressing the Impersonate button on the server-side workbench, paying particular attention to the group SIDs. Then, add the client to a new groupâ€"not a local group, but a global group managed on the domain controller. Now reset both workbenches and reauthenticate again with NTLM and see if the new group is present in the token. Remove the user from the group, log the client out and back in again, and repeat the exercise with Kerberos. Why does Kerberos behave differently?

Figure 8 Selecting SPNEGO in the SSPI Workbench
Figure 8 Selecting SPNEGO in the SSPI Workbench

Protocol Negotiation
Try choosing SPNEGO as both your client and server's SSP. On both sides, use the following string for the protocols you offer (Figure 8 shows this in the workbench):

  Kerberos,NTLM
  

 

Perform a network authentication handshake. Which network authentication protocol is actually used, Kerberos or NTLM? How many packets were exchanged? Now try again, but exclude Kerberos:

  NTLM
  

 

Does authentication succeed in this case? How many packets were exchanged? What's the final leg of the authentication handshake all about in both cases?
Kerberos and Clock Skew
Choose Kerberos as your provider, turn on the Mutual Authentication option in the client-side workbench, and authenticate with a copy of the workbench running on the server. Now reset both workbenches and try again, but before you do, change the server's clock by adding or subtracting 15 minutes to the current time displayed there. The server's clock is now out-of-skew. Now reauthenticate using the same options and the same account. What happens? Does authentication fail as you might expect it to? If not, what is happening to make it work?

Conclusion

      I hope that you've found this column and my August 2000 column helpful in understanding SSPI, an API that has not received nearly the attention it deserves. This API is remarkably easy to use to authenticate clients using NTLM or Kerberos, as these columns have demonstrated. Have fun with the workbench, and send any comments my way.

Send questions and comments to Keith at briefs@microsoft.com.

Keith Brown works at DevelopMentor researching, writing, teaching, and promoting an awareness of security among programmers. Keith authored Programming Windows Security (Addison-Wesley, 2000). He coauthored Effective COM, and is currently working on a .NET security book. He can be reached at https://www.develop.com/kbrown.

From the April 2001 issue of MSDN Magazine.