Microsoft SSPI provides message APIs that can be used to ensure application protocol message integrity. Message privacy APIs (data encryption) are not exposed directly but a particular provider may expose them and document them separately.
If the application wants to generate signed messages, the client must have specified the ISC_REQ_REPLAY_DETECT or ISC_REQ_SEQUENCE_DETECT flag as the Context Attributes argument in the first call to the InitializeSecurityContext function.
After an authenticated connection has been established, the security support providers on each side establish a common session key that is used to sign messages on the sending side and to verify messages on the receiving side. The algorithms used in message signatures are private to the security package.
The SSPI message APIs are the following:
The message APIs provide integrity for application data messages. MakeSignature generates a checksum of the message and also includes sequencing information to prevent message loss or insertion. The next sections show how the sender and receiver use the SSPI Message APIs.
The sender of a message calls MakeSignature API to get a signature for the message and appends it to the message at an appropriate place so that the receiver is able to extract it on receipt:
// // Setup the Buffer Descriptors. // OutBufferDesc.ulVersion = 0; OutBufferDesc.cBuffers = 2; OutBufferDesc.pBuffers = &OutSecBuffer; OutSecBuffer[0].cbBuffer = MessageLen; OutSecBuffer[0].BufferType = SECBUFFER_DATA | SECBUFFER_READONLY; OutSecBuffer[0].pvBuffer = Message; OutSecBuffer[1].cbBuffer = SignatureLen; OutSecBuffer[1].BufferType = SECBUFFER_EMPTY; OutSecBuffer[1].pvBuffer = (Message + MessageLen); // just after the message // // Now call MakeSignature API to get it signed. // SecurityStatus = (*SecurityInterface->MakeSignature)( &SecurityContext, 0, BufferDescriptor, Sequence );
The arguments to MakeSignature are the following:
The sender then uses the buffer descriptor (including the signature) to construct a message to send to the receiver.
The quality of protection value allows applications to select different cryptographic algorithms supported by the security package. By default NTLM does not support this parameter. Other security packages, however, may provide different quality of protection options.
The receiver takes the message and breaks it down to create the buffer descriptor as before. It then passes this buffer descriptor on to the VerifySignature API to verify the message integrity.
// // Setup the Buffer Descriptors. // InBufferDesc.ulVersion = 0; InBufferDesc.cBuffers = 2; InBufferDesc.pBuffers = &InSecBuffer; InSecBuffer[0].cbBuffer = MessageLen; InSecBuffer[0].BufferType = SECBUFFER_DATA | SECBUFFER_READONLY; InSecBuffer[0].pvBuffer = Message; InSecBuffer[1].cbBuffer = SignatureLen; InSecBuffer[1].BufferType = SECBUFFER_TOKEN; InSecBuffer[1].pvBuffer = (Message + MessageLen); // just after the message // // Now call MakeSignature API to get it signed. // SecurityStatus = (*SecurityInterface->VerifySignature)( &SecurityContext, BufferDescriptor, Sequence, &QualityOfProtection );
The arguments to VerifySignature are the following:
Once the receiver is ensured of the authenticity and integrity of the message, the receiver is free to use it as per the application protocol.
The Windows NT 4.0 security provider interface does not export message encryption routines for application-level message encryption. General purpose message encryption support is an area that Microsoft recognizes is an important dimension for building secure distributed applications.
The next release of Windows NT is expected to expose message encryption routines. The SSPI function entry points for message privacy will be:
The message functions will be similar to the message integrity routines described above, and full documentation for the new routines will be available in the Win32 SDK.
An important aspect of client/server communication besides authentication and message exchange is the ability of a server to determine whether it should service the client's request. A large number of servers run under system's context and therefore have far more privileges and abilities than a typical client requesting service. An example of this is a network file server which has full access to all files, whereas requesting users may not. Therefore, the server should carry out a client request if and only if the client has sufficient access rights for the requested service.
There are two approaches for determining whether a client has sufficient access rights for the operation: an access check by the server, or an access check by the system. The brute force approach builds the logic of doing authorization checks for client access into the server. The server code uses authorization information, for example, from a separate authorization file, and determines if the client has sufficient rights to perform the requested operation.
Windows NT provides a straightforward way for servers to determine client authorization by using a mechanism called impersonation. Impersonation is based on temporarily changing the security context of the server to match the client before attempting resource access. Windows NT implements the access check on a resource automatically when the server attempts to open the system resource. Using impersonation and system-level access verification simplifies server development and uses Windows NT access control.
During the connection setup, the client specifies an impersonation level that the server can impersonate the client at when servicing a request. There are four impersonation levels in Windows NT:
Anonymous—the server is not allowed to find any information on the client's security context.
Identification—the server can only authenticate the client but not use the client's security context for access checks.
Impersonation—the server can authenticate the client and use the client's security context for local access checks directly or by passing the context to another server on the same machine.
Delegation—the server can authenticate the client and use the client's security context for local access checks (direct or via other servers) as well as pass on the context to a remote server to request service on behalf of the client.
Windows NT 4.0 does not support delegation because of the current limitation of NTLM challenge response protocol. Implementation of the Kerberos authentication protocol in the next release of Windows NT will support delegation.
SSPI provides an API, ImpersonateSecurityContext, that allows a server to impersonate the client's security context as well as to revert back to it's own security context (RevertSecurityContext) when done servicing.
The example below shows how to use ImpersonateSecurityContext and RevertSecurityContext APIs:
// // When accessing a resource on behalf the client, we need to // impersonate the client so that appropriate access check is done. // SecurityStatus = (*SecurityInterface->ImpersonateSecurityContext) (&SecurityContext); if(SecurityStatus != SEC_E_OK) { // // We have a problem… // This security context is not at least impersonation level. // return error; } // // At this point the calling thread is under an impersonation token with client's credentials // // // Process the request.. // . . . // // Revert to primary token once we are done. // SecurityStatus = (*SecurityInterface->RevertSecurityContext)(&SecurityContext); if(SecurityStatus != SEC_E_OK) { // // check for any errors… // return error; } // // The server thread is back to its original context. //
Application servers can use impersonation and Windows NT access control for determining access to application data files and other resources. Servers can also use integrated access control for private application data by using security descriptors on registry keys that represent who is allowed to connect to the server, or for specific application data. Windows NT provides a number of system-level APIs to implement private object security descriptors for full integration.
The Kerberos authentication protocol supports delegation. When delegation is supported, the impersonating server can use the client's delegation level credentials to initialize a security context with a remote server to request a service on the client's behalf.
The diagram below shows how the client's security context, identified by C, is established on Server 1. When Server 1 impersonates the client, the impersonation context on Server 1 is identified as C/S1. Server 1 makes an off-machine connection to Server 2. Through the use of delegation, Server 2 is also able to impersonate the client's security context. Server 2's impersonation of the client is identified as C/S2.
Figure 3: Delegation of Security
The following example shows how delegation can be accomplished using SSPI:
// // When accessing a resource on behalf the client, we need to impersonate the client so that // appropriate access check is done. // SecurityStatus = (*SecurityInterface->ImpersonateSecurityContext)(&SecurityContext); if(SecurityStatus != SEC_E_OK) { // // We have a problem… // This security context is not at least impersonation level. // return error; } // // At this point the calling thread is under an impersonation token with client's credentials // // // Now we can call InitializeSecurityContext to get an authentication token with "current" // credentials (client's) to send to another remote server: // // Set up security buffers and the descriptor. // // // Call InitializeSecurityContext… // If this fails, then the client security context is not delegation level, // WATCH OUT FOR THIS, and handle according to the application protocol. // // // construct a message with the auth token from the SSP to send to the remote server. // and send the message. // // Wait for the reply from the server. // // // If SEC_I_CONTINUE_NEEDED is returned by the first call to Initialize, use // the auth token returned by the remote server to call InitializeSecurityContext again. // if(SecurityStatus == SEC_I_CONTINUE_NEEDED || SecurityStatus = SEC_I_COMPLETE_AND_CONTINUE) { // // Fill up Input security buffers and setup output security buffers. // // // call InitializeSecurityContext. // // // Convert the security buffers into a message. // and send the message. // // // wait for reply. // } // // At this point the connection is established. // // // Request service from the remote server by exchanging messages. Note that // the remote server will process these assuming that they are coming from the client. // . . . // // Once done, tear down the connection. // // // Now, you may revert to primary token. // SecurityStatus = (*SecurityInterface->RevertSecurityContext)(&SecurityContext); if(SecurityStatus != SEC_E_OK) { // // check for any errors… // return error; } // // The server thread is back to its original context. //
The code segments in the sections above show how SSPI APIs can be used for connection authentication, message integrity, and impersonation. The
Windows NT Software Developer's Kit includes two samples that use SSPI to obtain security services, they are sockauth and httpauth projects in \mstools\samples\win32\winnt\security. Please review the samples in the context of the description presented here to get a better understanding of how SSPI can be used. Note that both examples use NTLM SSP as the security provider which is currently the default security provider on Windows NT 4.0.
Here is a description of the SDK samples that use SSPI.
This is a simple commandline application that establishes a secure connection between a client and server using Windows sockets. It consists of following modules:
Client.c—contains the top level code for the client. It includes main(), ConnectAuthSocket(), CloseAuthSocket and DoAuthentication functions.
Server.c—contains the top level code for the server. It includes the server main(), AcceptAuthSocket(), CloseAuthSocket, and DoAuthentication functions.
Comm.c—is the common module for both the client and the server. It contains communication code including InitWinSock(), TermWinSock(), SendMsg(), ReceiveMsg(), SendBytes(), and ReceiveBytes().
Security.c—is another common module for both the client and the server. It contains code that uses the SSPI APIs for security services. The functions include InitPackage(), TermPackage(), GenClientContext(), GenServerContext(), ImpersonateContext(), RevertContext(), InitSession(), and TermSession().
Collect.c—is a helper module that provides functions for a simple collection that maps DWORD key values to binary large object (blob) of data.
Changing this example to use Kerberos authentication instead of NTLM is straightforward. The only modification is the security package name and ensuring that return codes from SSPI are properly handled.
HTTPAuth is a simple command line Web application. It allows the authenticated user to get a HTML document. It consists of three modules:
httpget.c—is the top level module for the client application. It contains main() which parses various command line arguments, and so forth.
get_sock.c—contains sockets code. The significant function here is HttpGetSocket() which issues a request to a HTTP server using authentication.
Httpauth.c—contains the security code. It implements InitAuthorizationHeader (), TerminiateAuthorizationHeader(), IsInAuthorization-Sequence(), ValidateAuthenticationMethods(), AddAuthorizationHeader(), AuthInit(), AuthTerminate(), AuthConverse(), uuencode(), and
uudecode() functions.
The Security Support Provider Interface model supports three types of security contexts, which are summarized in the following table.
Type |
Description |
Connection |
A connection-oriented context is the most common security context, and the simplest to use. The caller is responsible for the overall message format. The caller is responsible for the location of the data in the message. The caller is also responsible for the location of the security-relevant fields within a message, such as the location of the signature data. |
Datagram |
A datagram-oriented context has extra support for DCE RPC style datagram communication. It can also be used generically for a datagram-oriented transport application. |
Stream |
A stream-oriented context is responsible for the blocking and message formatting within the security package. The caller is not interested in formatting, but rather a raw stream of data. |
With a connection-oriented context, the caller of the function is responsible for formatting messages. The caller also relies on the security provider to authenticate connections, and to ensure the integrity of specific parts of the message. Most of the range of context options are available to connection-oriented contexts. These options include mutual authentication, replay detection, and sequence detection, as described in Context Requirements.
A security package sets the SECPKG_FLAG_CONNECTION flag to indicate that it supports connection-oriented semantics.
Datagram, or connectionless, contexts have slightly different semantics from connection-oriented contexts. A connectionless context implies that the server has no way of determining when the client has shut down or otherwise terminated the connection. In other words, no termination notice is passed from the transport application to the server, as would occur in a connection context. To better support some models, particularly DCE-style RPC, the following rules apply when the client specifies the ISC_REQ_DATAGRAM flag in its call to the InitializeSecurityContext function:
The security package does not produce an authentication blob (binary large object) on the first call to the InitializeSecurityContext function. However, the client can immediately use the returned security context in a call to the MakeSignature function to generate a signature for a message.
The security package must allow for the context to be re-established multiple times to allow the server to drop the connection without notice. This also implies that any keys used in the MakeSignature and VerifySignature functions can be reset to a consistent state.
The security package must allow for the caller to specify sequence information, and must provide it back again at the other end. This is not exclusive of any sequence information maintained by the package and can be viewed as a special payload.
A security package sets the SECPKG_FLAG_DATAGRAM flag to indicate that it supports datagram semantics.
Stream contexts are quite different from either connection or datagram contexts. Stream contexts were introduced to handle the secure streams-oriented protocols such as SSL or PCT.
In the interest of sharing the same interface, similar credential management, and so on, the Security Support Provider Interface has been extended to provide support for stream contexts. The security protocol incorporated both the authentication scheme, and the record formats. This posed a problem to the typical implementation, which required the blocking to be done by the caller.
To satisfy the requirements of the stream-oriented protocols, a security package that supports stream contexts has the following characteristics:
The package sets the SECPKG_FLAG_STREAM flag to indicate that it supports stream semantics, just as it would set a flag to indicate support for connection and datagram semantics.
A transport application requests stream semantics by setting the ISC_REQ_STREAM and ASC_REQ_STREAM flags in the calls to the InitializeSecurityContext and AcceptSecurityContext functions.
The application calls the QueryContextAttributes function with a SecPkgContext_StreamSizes structure to query the security context for the number of buffers to provide, and the sizes to reserve for headers or trailers.
The application provides buffer descriptors to spare during the actual processing of the data.
Obviously, item 4 is of the most interest. By specifying stream semantics, the caller is indicating a willingness to do extra work so the security provider can handle the blocking of the messages.
In essence, for the MakeSignature and VerifySignature functions, the caller passes in a list of buffers. When a message is received from a channel that
is stream-oriented (such as a TCP port), the caller passes in a buffer list as follows:
Buffer |
Length |
Buffer Type |
1 |
MessageLength |
SECBUFFER_DATA |
2 |
0 |
SECBUFFER_EMPTY |
3 |
0 |
SECBUFFER_EMPTY |
4 |
0 |
SECBUFFER_EMPTY |
5 |
0 |
SECBUFFER_EMPTY |
The security package then goes to work on the blob. If the function returns successfully, the buffer list looks like this:
Buffer |
Length |
Buffer Type |
1 |
Header Length |
SECBUFFER_STREAM_HEADER |
2 |
Data Length |
SECBUFFER_DATA |
3 |
Trailer Length |
SECBUFFER_STREAM_TRAILER |
4 |
0 |
SECBUFFER_EMPTY |
5 |
0 |
SECBUFFER_EMPTY |
The provider could have also returned buffer #4 as follows:
Buffer |
Length |
Buffer Type |
4 |
x |
SECBUFFER_EXTRA |
This indicates that the data in this buffer is part of the next record, and has not yet been processed.
Conversely, if the message function returns the SEC_E_INCOMPLETE_MESSAGE error code, the returned buffer list would look like this:
Buffer |
Length |
Buffer Type |
1 |
x |
SECBUFFER_MISSING |
This indicates that more data was needed to process the record. Unlike most errors returned from a message function, this buffer type does not indicate that the context has been compromised, just that more data is needed. Security providers must not update their state in this condition.
Similarly, on the send side of the communication, the caller can simply call the MakeSignature function, in which case the security package may need to reallocate the buffer, copy things around, and so on. Or the caller can be more efficient by providing a buffer list as follows:
Buffer |
Length |
Type |
1 |
Header Length |
SECBUFFER_STREAM_HEADER |
2 |
Data Length |
SECBUFFER_DATA |
3 |
Trailer Length |
SECBUFFER_STREAM_TRAILER |
This allows the caller to use the buffers more efficiently. By calling the QueryContextAttributes function to determine the amount of space to reserve before calling MakeSignature, the operation is more efficient for the application and the security package.
Context requirements are expressed as a combination of bit flags, passed to either the InitializeSecurityContext or AcceptSecurityContext function. These flags affect the context in a number of ways, and are detailed in the following table. Not all flags apply to all contexts; some are valid only for the server, others only for the client.
The caller uses the fContextReq parameter of the InitializeSecurityContext or AcceptSecurityContext call to specify a set of flags that indicate the required capabilities. When the function returns, the pfContextAttr parameter indicates the attributes of the established context. The caller is responsible for determining whether the final context attributes are acceptable. For example, if the caller requested mutual authentication, but the security package indicates that it was not or could not be performed, the caller must decide whether to cancel the context or continue on.
The following table describes the various context requirements.
Type |
Description |
||||||
DELEGATE |
Indicates that the server in the transport application should be allowed simple delegation rights, that is, impersonation of the client on the node at which the server is executing. |
||||||
MUTUAL_AUTH |
Indicates that both parties must authenticate the identity of the peer. |
||||||
REPLAY_DETECT |
Indicates that the context should be established to allow detection of replayed packets later through the message support functions, MakeSignature and VerifySignature. Implies INTEGRITY. |
||||||
SEQUENCE_DETECT |
Indicates that the context should be established to allow detection of out-of-order delivery of packets later through the message support functions. Implies INTEGRITY. |
||||||
CONFIDENTIALITY |
Indicates that the context should be established to protect data while in transit. Reserved for future use. |
||||||
USE_SESSION_KEY |
Indicates that a new session key should be negotiated. |
||||||
PROMPT_FOR_CREDS |
Indicates that, if the client is an interactive user, the security package should prompt the user for the appropriate credentials to use, if possible. |
||||||
USE_SUPPLIED_CREDS |
Indicates that package-specific credential information is available in the input buffer. The security package should use these credentials to authenticate the connection. |
||||||
ALLOCATE_MEMORY |
Indicates that the security package should allocate the memory. The caller must eventually call the FreeContextBuffer function to free memory allocated by the security package. |
||||||
USE_DCE_STYLE |
Indicates that the caller expects a three-leg authentication transaction. |
||||||
DATAGRAM |
Indicates that datagram semantics should be used. For more information, see Datagram Contexts. |
||||||
CONNECTION |
Indicates that connection semantics should be used. For more information, see Connection-Oriented Contexts. |
||||||
STREAM |
Indicates that stream semantics should be used. For more information, see Stream Contexts. |
Type |
Description |
|||
EXTENDED_ERROR |
Indicates that if the context fails (or failed), it will generate an error reply message for the peer. |
|||
INTEGRITY |
Buffer integrity can be verified, but no sequencing or reply detection is enabled. |
Building Secure
Distributed
Applications
Application developers have the option of using SSPI directly in a distributed application. Calling SSPI to add security to existing socket-based applications is a straightforward way to integrate Windows NT security.
On the other hand, developers who want to concentrate more on the design and development of the application rather than worrying about details of how to add security into the application have other options available. These developers would much rather have a transparent capability provided to them by the underlying transport mechanism so that they don't have to worry too much about it and let the transport handle the operation itself.
As shown in the Figure 1 in the Introduction, there are other ways to use the security services available provided by SSPI without calling SSPI functions directly. The most efficient way to integrate Windows security providers into distributed applications is through DCOM security. DCOM is layered on authenticated RPC and provides security features based on SSPI without exposing SSPI details to the application developer.
Authenticated RPC also provides simplified RPC binding semantics to use the authentication and message integrity features implemented by SSPI. The RPC run time uses SSPI directly for security support, but like DCOM, does not expose SSPI semantics to the application developer.
WinSock 2.0 and WinInet also provide a mechanism to use transport-level security providers and these providers themselves are implemented to depend on SSPI for security services.
In this section we walk through developing secure distributed applications using security features of higher-level application interfaces that are layered above SSPI.