The Security Support Provider Interface

The Microsoft® Security Support Provider Interface (SSPI) is the well-defined common API for obtaining integrated security services for authentication, message integrity, message privacy, and security quality of service for any distributed application protocol. Application protocol designers can take advantage of this interface to obtain different security services without modification to the protocol itself.

On This Page

Introduction
SSPI API
Building Secure Distributed Applications
Summary
SUMMARY

Introduction

The Microsoft's Security Support Provider Interface (SSPI) is the Win32® interface between transport level applications and network security service providers. The interface is supported by the following:

  • Microsoft® Windows 2000 operating system

  • Microsoft® Windows NT® operating system

  • Microsoft Windows 98 operating system

  • Microsoft remote procedure call (RPC) run-time for the Windows 95 operating system

  • RPC run-time for the MS-DOS®, Windows 3.11, and Macintosh® operating systems

This paper describes SSPI and discusses the following issues:

  • How to use SSPI to enhance security capabilities of a distributed application.

  • How to develop distributed applications using existing communication mechanisms—such as the distributed version of the Component Object Model (DCOM), Secure RPC, WinSock, and WinInet—and have integrated security capabilities from Windows NT and Windows 2000.

This paper begins with a review of the SSPI APIs and how to integrate Windows NT authentication, message integrity, and privacy into distributed applications. Then the paper examines how application developers use the DCOM application framework and authenticated RPC to take advantage of SSPI services from higher-level interfaces. Finally, it presents other examples of how SSPI security services are available when using application level interfaces such as WinSock 2.0 and WinInet.

The following illustration shows where the SSPI security services fit into the overall distributed application architecture.

Figure 1: SSPI and the Windows NT Security Model

Figure 1: SSPI and the Windows NT Security Model

Figure 1 shows the options available to application developers for building distributed applications. The SSPI provides an abstraction layer between application-level protocols and security protocols. The following are some of the ways to use SSPI services:

  • Traditional socket-based applications can call SSPI routines directly and implement the application protocol that carries SSPI security-related data, using request and response messages.

  • DCOM applications provide the best level of integrated security features. Applications can use DCOM to call security options, which are implemented using authenticated RPC and SSPI at lower levels. Applications do not call SSPI APIs directly.

  • WinSock 2.0 extends the Windows Sockets interface to allow transport providers to expose security features. This approach integrates the SSPI security provider into the network stack and provides both security and transport services through a common interface.

  • WinInet is an application protocol interface that is designed to support Internet security protocols, such as Secure Sockets Layer (SSL), over Internet protocols. The implementation of WinInet security support uses the SSPI interface to the Secure Channel (Windows NT implementation of SSL) security provider.

The remainder of this document is designed to help both application developers and security support provider writers understand how to use SSPI. First, the paper reviews the SSPI APIs and the security provider concepts. Then it describes how SSPI APIs can be used to establish secure connections, with descriptions of the SSPI-related samples in the Microsoft Platform SDK.

SSPI API

The Security Support Provider Interface (SSPI) provides a common interface between transport-level applications, such as Microsoft RPC or a file system redirector, and security providers, such as Windows NT Distributed Security. SSPI provides a mechanism by which a distributed application can call one of several security providers to obtain an authenticated connection without knowledge of the details of the security protocol.

SSPI consists of the following interfaces:

  • Credential-management interfaces—Provide access to credentials (password data, tickets, and so on) or free such access. The following methods are available:

    AcquireCredentialsHandle—This method acquires a handle to the reference credentials.

    FreeCredentialsHandle—This method releases a credential handle and associated resources.

    QueryCredentialAttributes—This method allows queries on various credential attributes like associated name, domain name, and so on.

  • Context-management interfaces—Provide methods for creating and using security contexts. The contexts are created on both the client and the server side of a communication link. These contexts can then be used later with the message support interfaces. The following methods are available:

    InitializeSecurityContext—Initiates a security context by generating an opaque message (security token) that can be passed to the server.

    AcceptSecurityContext—Creates a security context, using the opaque message received from the client.

    DeleteSecurityContext—Frees a security context and associated resources.

    QueryContextAttributes—Allows queries on various context attributes.

    ApplyControlToken—Applies a supplemental security message to an existing security context.

    CompleteAuthToken—Completes an authentication token, since some protocols, like DCE RPC, need to revise the security information once the transport has updated some message fields.

    ImpersonateSecurityContext—Attaches the client's security context as an impersonation token to the calling thread.

    RevertSecurityContext—Ceases impersonation and defaults the calling thread to its primary token.

  • Message-support interfaces—Provide communication integrity and privacy services, based on a security context. The following methods are available:

    MakeSignature—Generates a secure signature, based on a message and a security context.

    VerifySignature—Verifies that the signature matches a received message.

  • Package-management interfaces—Provide services for various security packages that the security provider supports. The following methods are available:

    EnumerateSecurityPackages—Lists available security packages and their capabilities.

    QuerySecurityPackageInfo—Queries an individual security package for its capabilities.

The SSPI does not currently provide any public interfaces for encryption or decryption. Future versions of the SSPI will include message-support routines for encryption.

A security provider is a dynamic-link library that implements the Security Support Provider Interface and makes one or more security packages available to applications. A security package maps the SSPI functions to an implementation of the security protocol specific to that package, such as NTLM, Kerberos, or SSL. Security packages are sometimes referred to as SSPs. The name of the security package is used in the initialization step to identify a specific package.

The SSPI allows an application to use any of the available security packages on a system without changing the interface to use security services. The SSPI does not establish logon credentials because that is generally a privileged operation handled by the operating system.

An application can use the package-management functions to list the security packages available and select one to support its needs. The application then uses the credential-management functions to obtain a handle to the credentials of the user on whose behalf they are executing. With this handle, the application can use the context-management functions to create a security context to a service. A security context is an opaque data structure that contains the security data relevant to a connection, such as a session key, the duration of the session, and so on. Finally, the application uses the security context with the message-support functions to ensure message integrity and privacy during the connection.

Security Package Capabilities

The capabilities of the security package determine what services it provides to the application. These capabilities include, for example, support for client-only authentication or mutual authentication, or support for message integrity and message privacy. In addition, some packages are designed for use only on reliable transport protocols and are not designed for use on datagram transports.

The security-package capabilities available from a specific package are obtained using the QuerySecurityPackageInfo function. The following lists show the security-package capabilities.

  • Authentication-related capabilities:

    • Client-only authentication

    • Multileg authentication required

    • Supports Windows NT impersonation

  • Transport-related capabilities:

    • Datagram-style transports

    • Connection-oriented transports

    • Data-stream connection semantics

  • Message-related capabilities

    • Supports message integrity

    • Supports message privacy

In most cases, applications select security packages, based on the type of security capabilities available to meet the application needs. For more information on security package capabilities, see the section below on Security Context Semantics.

Initializing the Security Provider

This section describes how application-level protocols initialize and use the Security Support Provider Interface. It describes various stages of a secure network connection setup. The stages include:

  • Initializing the SSPI

  • Establishing an authenticated connection

  • Ensuring communication integrity during message exchange

  • Security quality of service to service a client request

These stages are described in the following sections.

Initializing the SSPI

Both the client and server use the same sequence of operations to initialize the security provider and select the appropriate security package.

Initializing the security interface involves the following steps:

  • Loading the security provider DLL

  • Getting a pointer to the provider initialization function

  • Using the initialization function to get a reference to the provider's security function table

  • Getting specific information about the security package, such as the maximum token size

The security function table contains the SSPI entry points for the security package. The function table is used to invoke the calls implemented by the security package.

Loading the Security Provider DLL

To initialize security, you need to load the provider. Throughout this paper, it is assumed that the client side of the provider is a DLL.

The provider is loaded by calling the LoadLibrary function, as shown in the example below:

void * DllHandle;
//loading NTLM SSP
DllHandle = (void *)LoadLibrary(TEXT("security.dll");
if(!DllHandle)
{
// 
// DLL did not get loaded.
//
Status = GetLastError();
return Status;
}
//
// DllHandle is valid
//

In Windows NT, a single system DLL implements the security support provider. This provider supports all available security packages available on the system. The name of the Windows NT security provider DLL is Security.dll. After loading the Windows NT security provider, multiple security packages are available, including:

  • NTLM

  • MSN

  • Schannel (SSL/ Private Communications Technology [PCT])

  • Kerberos (in Windows 2000)

In Windows 95, the security provider DLL is Secur32.dll. The differences in provider DLL file names are due to the organization of the system root directory on Windows 95. All security packages for Windows 95 are available through this provider.

The Kerberos security package is not currently available for Windows 95, but will be made available in a future update for that environment.

Provider Initialization

Once the provider has been loaded successfully, you must perform some setup to use the security interface conveniently in the rest of the application. First, get a pointer to the initialization function for the provider. Then use the initialization function to get a reference to the provider's security function table. Finally, get information from the provider about the security packages, or protocols, supported by this security provider. Each security package has unique capabilities of interest to the application. However, in most cases, applications use security packages that support default or common capabilities.

The example below shows how to initialize the security provider.

//
// Initial provider setup.
//
INIT_SECURITY_INTERFACE  InitSecurityInterface;
PSecurityFunctionTable  SecurityInterface = 0;
SecPkgInfo  PAPI *    SecurityPackages;
DWORD    NumOfPkgs;
SECURITY_PROVIDER_INFO PAPI * List;
InitSecurityInterface = GetProcAddress(DllHandle, SECURITY_ENDPOINT);
if(!InitSecurityInterface)
{
//
// Something is amiss..
//
}
//
// We got the InitSecurityInterface!
// Now use it to get the function table.
//
SecurityInterface = (*InitSecurityInterface)();
if(!SecurityInterface)
{
//
// we have a problem…
//
}
//
// Lets find out the security packages supported by the provider.
//
Status = (*SecurityInterface->EnumerateSecurityPackages)( &NumOfPkgs, &SecurityPackages);
//
// Now using the capabilities information figure out which package you
// want to use.
//
PkgToUseIndex = -1;
for(I=0;I<NumOfPackages;I++)
{
//
// for example, if app needs integrity & privacy on messages, it 
// checks
//
if(SecurityPackages[I].fCapabilities & (SECPKG_FLAG_INTEGRITY | SECPKG_FLAG_PRIVACY))
{
PkgToUseIndex = I;
break;
}
}
if(PkgToUseIndex > 0)
{
//
// Find out the maximum token size for this package
//
g_MaxToken = SecurityPackages[I].cbMaxToken;
}

Both the client and the server must agree on the security package they will use before the SSPI initialization steps shown above. Windows 2000 supports security package negotiation that is conducted automatically by the security provider.

At this point, the application has successfully initialized a security support provider and chosen a security package with sufficient capabilities for the application protocol. The SecurityInterface points to an array of function pointers, as defined by SSPI.

The call to the EnumerateSecurityPackages function initializes the reference pointer SecurityPackages with return data. Some SSPI functions have return output parameters, such as security package information. For the output data parameters, the caller passes in a pointer to a pointer to the return structure type, and the security provider allocates memory and returns the data to the caller by assigning the address of the return data buffer to the argument. The convention used by SSPI to return data is the following: The security package allocates, and the caller frees*.* Therefore, the calling program uses the FreeContextBuffer function to free the memory containing data allocated by the security provider when it has finished referencing the data. The examples below continue to reference SecurityPackages information, so it must be freed later.

Security Function Table

The Security Function Table is an array of function pointers that are declared in the Sspi.h header file. The function names correspond to the interface specification for SSPI.

The definition of the Security Function Table is shown below:

typedef struct _SECURITY_FUNCTION_TABLE_W {
unsigned long      dwVersion;
ENUMERATE_SECURITY_PACKAGES_FN_W EnumerateSecurityPackagesW;
void SEC_FAR *      Reserved1;
// QUERY_CREDENTIALS_ATTRIBUTES_FN_W QueryCredentialsAttributesW;
ACQUIRE_CREDENTIALS_HANDLE_FN_W  AcquireCredentialsHandleW;
FREE_CREDENTIALS_HANDLE_FN   FreeCredentialHandle;
void SEC_FAR *      Reserved2;
INITIALIZE_SECURITY_CONTEXT_FN_W InitializeSecurityContextW;
ACCEPT_SECURITY_CONTEXT_FN   AcceptSecurityContext;
COMPLETE_AUTH_TOKEN_FN    CompleteAuthToken;
DELETE_SECURITY_CONTEXT_FN   DeleteSecurityContext;
APPLY_CONTROL_TOKEN_FN    ApplyControlToken;
QUERY_CONTEXT_ATTRIBUTES_FN_W  QueryContextAttributesW;
IMPERSONATE_SECURITY_CONTEXT_FN  ImpersonateSecurityContext;
REVERT_SECURITY_CONTEXT_FN   RevertSecurityContext;
MAKE_SIGNATURE_FN     MakeSignature;
VERIFY_SIGNATURE_FN     VerifySignature;
FREE_CONTEXT_BUFFER_FN    FreeContextBuffer;
QUERY_SECURITY_PACKAGE_INFO_FN_W QuerySecurityPackageInfoW;
void SEC_FAR *      Reserved3;
void SEC_FAR *      Reserved4;
QUERY_SECURITY_CONTEXT_TOKEN_FN  QuerySecurityContextToken;
} SecurityFunctionTableW, SEC_FAR * PSecurityFunctionTableW;

Windows NT 4.0 does not support the QueryCredentialsAttributes function and, therefore, is commented out of the function table. This function will be supported in a future release.

SSPI in Windows NT 4.0 does not provide encryption and decryption interfaces because of legal restrictions on such interfaces. Microsoft is planning to extend the interface to provide encryption interfaces in a future release.

Memory Use, Security Buffers, and Descriptor

Most of the SSPI functions have variable length arguments for the caller (application) to provide message data to the security package and for the security package to return security data to the caller. SSPI APIs use a parameter type, BufferDescriptor, to define the size and location of the variable length data. The caller can use security buffers, for example, to pass message data to the security package, or to receive an output security token.

Security buffers can be passed in as an array of buffers. The security buffer descriptor identifies the number of buffers and the starting address of the buffer array. Each security buffer also has a buffer type field to identify the contents of the buffer.

The definition of security buffers, buffer descriptors, and buffer data types from Sspi.h are shown below:

//
// SecBuffer
//
// Generic memory descriptors for buffers passed in to the security
// API
//
typedef struct _SecBuffer {
unsigned long cbBuffer;    // Size of the buffer, in bytes
unsigned long BufferType;   // Type of the buffer (below)
void SEC_FAR * pvBuffer;   // Pointer to the buffer
} SecBuffer, SEC_FAR * PSecBuffer;
typedef struct _SecBufferDesc {
unsigned long ulVersion;   // Version number
unsigned long cBuffers;    // Number of buffers
#ifdef MIDL_PASS
[size_is(cBuffers)]
#endif
PSecBuffer pBuffers;    // Pointer to array of buffers
} SecBufferDesc, SEC_FAR * PSecBufferDesc;
#define SECBUFFER_VERSION   0
#define SECBUFFER_EMPTY    0 // Undefined, replaced by provider
#define SECBUFFER_DATA    1 // Packet data
#define SECBUFFER_TOKEN    2 // Security token
#define SECBUFFER_PKG_PARAMS  3 // Package specific parameters
#define SECBUFFER_MISSING   4 // Missing Data indicator
#define SECBUFFER_EXTRA    5 // Extra data
#define SECBUFFER_STREAM_TRAILER 6 // Security Trailer
#define SECBUFFER_STREAM_HEADER  7 // Security Header
#define SECBUFFER_ATTRMASK   0xF0000000
#define SECBUFFER_READONLY   0x80000000 // Buffer is read-only

Each time a security API is called that takes a SecBufferDesc parameter, it should be setup with one or more SecBuffers. For example, there can be two security buffers, one that contains input message data and the other for the output opaque security token returned by the security package. The order of security buffers in the security buffer descriptor is not important, but they should be tagged with the appropriate type. An input buffer that can not be modified by the security package should also be tagged as read-only.

The size of the output buffer that is expected to contain the security token is important. An application can find the maximum token size for a security package during initial setup. The call to the EnumerateSecurityPackages function returns an array of pointers to security package information. The security package information structure contains maximum token size value. In the example code, the information is in SecPkgInfo.cbMaxToken. It can also be obtained later by using the QuerySecurityPackageInfo function.

The application initializes the buffer pointers and sizes in the buffer description to indicate where message data and other information may be found.

The example below shows how to initialize an array of security buffers. This particular case shows how input security buffers are initialized by the server-side of a connection in a call to the AcceptSecurityContext function. The last buffer contains the opaque security token received by the client, and the SECBUFFER_READONLY flag is also set.

SecBuffer  Buffers[3];
SecBufferDesc BufferDesc;
...
BufferDesc.ulVersion = SECBUFFER_VERSION;
BufferDesc.cBuffers = 3;
BufferDesc.pBuffers = &Buffers;
Buffers[0].cbBuffer = sizeof(Protocol_Header);
Buffers[0].BufferType = SECBUFFER_READONLY | SECBUFFER_DATA;
Buffers[0].pvBuffer = pHeader;
Buffers[1].cbBuffer = pHeader->MessageSize;
Buffers[1].BufferType = SECBUFFER_DATA;
Buffers[1].pvBuffer = pMessage;
Buffers[2].cbBuffer = pHeader->TrailerSize;
Buffers[2].BufferType = SECBUFFER_READONLY | SECBUFFER_TOKEN;
Buffers[2].pvBuffer = pSecurityTrailer;

Establishing an Authenticated Connection

In a client/server application protocol, a server typically binds to a well known communication port (for example, a socket, RPC interface, and so forth) and waits for clients to connect and request service. The role of security at connection setup is twofold:

  • The server should be able to authenticate the client.

  • The client should be able to authenticate the server.

Associated with these two basic requirements are other security issues, such as, the authentication information should not be prone to replay, corruption, and so on. The application does not need to worry about how these are handled. It can simply request it from the chosen provider, which will encapsulate the underlying security protocol.

The protocol used to establish an authenticated connection involves the exchange of one or more "security tokens" between the security providers on each side. These tokens are sent as "opaque" messages by the two sides along with any other application protocol specific information. The application level protocol strips the security token out of the received message and passes on to the security package on their side to figure out if authentication is complete or if further exchange of tokens is required. Theoretically, the exchange of security tokens can continue ad infinitum, however, in practice it contains one to three legs of message exchange.

For example, NTLM authentication is based on the challenge/response scheme, and uses three legs to authenticate a client to the server, as shown in the figure below.

Figure 2: Using NTLM Challenge Response Authentication Protocol via SSPI

Figure 2: Using NTLM Challenge Response Authentication Protocol via SSPI

Client Context Initialization

To establish a secure connection, the client needs to acquire an outbound credentials handle so that it can send over an authentication request to the server. The server creates a security context for the client from the authentication request. There are two client-side SSPI functions involved in authentication setup:

  • AcquireCredentialsHandle to obtain a reference to previously obtained logon credentials

  • InitializeSecurityContext to create the initial authentication request security tokens

Using the reference to the Security Function Table initialized during the security provider setup stage, the client calls AcquireCredentialsHandle as follows:

//
// Acquire an out-bound Credentials handle using the chosen security package.
//
SecurityStatus = (*SecurityInterface->AcquireCredentialsHandle)(
0,
SecurityPackages[PkgToUseIndex].Name,
SECPKG_CRED_OUTBOUND,
0,
0,
0,
0,
&Credentials,
&TimeStamp
);

The arguments to AcquireCredentialHandle are the following:

  • Arg1 = Principal Name, set to NULL so that the security package uses the default.

  • Arg2 = Security Package Name, set to the one that was selected during package setup.

  • Arg3 = Type of credential, the client will use outbound credentials.

  • Arg4 = Pointer to LogonID, set to NULL so that the security package uses default.

  • Arg5 =AuthIdentity, set to NULL to use the process's default credentials. This parameter may be used to provide. package specific data. For an NTLM security package it may contain a pointer to the SEC_WINNT_AUTH_IDENTIY structure that contains the user name, domain name, and password. The file system redirector can use this feature to allow users to specify an alternate account name than the one they are currently logged in as when connecting to a remote file server.

  • Arg6 = GetKey function, set to NULL, not used.

  • Arg7 = Any argument to the GetKey function, also set to NULL.

  • Arg8 = returned Credentials handle, used for additional SSPI calls.

  • Arg9 = returned TimeStamp, which indicates the life span of the credentials handle.

Once the client has acquired an outbound credentials handle, it is ready to start the authentication protocol to establish a connection with the server. The application client calls the security package again to initialize the security context.

To initiate the first leg of the authentication, the client calls InitializeSecurityContext to obtain an initial security token that will be sent in a connection request message to the server.

The example of the client call to InitializeSecurityContext is shown below:

//
// Set up the Buffer Descriptor.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;
OutSecBuffer.cbBuffer = BufferLen;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = Buffer;
//
// Lets get the authentication token from the security package
// to send to the server to request an authenticated connection.
//
SecurityStatus = (*SecurityInterface->InitializeSecurityContext(
Credentials,
0,
ServerPrincipalName,
ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE |
ISC_REQ_MUTUAL_AUTH |ISC_REQ_REPLAY_DETECT |
ISC_REQ_SEQUENCE_DETECT |ISC_REQ_CONFIDENTIALITY |
ISC_REQ_CONNECTION,
0,
0,
0,
0,
&SecurityContext,
BufferDescriptor,
&ContextAttributes,
&TimeStamp
);

The arguments to InitializeSecurityContext are the following:

  • Arg1 = Credentials handle received from AcquireCredentialsHandle call.

  • Arg2 = Old Context handle if any.

  • Arg3 = Target server name, which is ignored by NTLM SSP.

  • Arg4 = Context Attributes Requested (See SSPI.H for valid values).

  • Arg5 = Reserved parameter.

  • Arg6 = Data Representation (see SSPI.H for valid values).

  • Arg7 = Input Buffer Descriptor (if there is one received from the server).

  • Arg8 = Reserved parameter.

  • Arg9 = New Context Handle.

  • Arg10 = Output Buffer Descriptor (contains what will be sent to the server).

  • Arg11 = Context Attributes that are supported by the provider.

  • Arg12 = TimeStamp for the life span of context validity.

The client then uses the security token information received in the output buffer descriptor to generate a message to send to the server. The construction of the message in terms of placement of various buffers and so forth, is part of the application protocol and should be understood between the two parties.

The client checks the return status from InitializeSecurityContext to see if authentication will complete in a single call. Otherwise it expects to receive a server-side authentication token in a response message to continue the security protocol. The return status SEC_I_CONTINUE_NEEDED, indicates the security protocol requires multiple authentication messages.

Server Context Initialization

To establish an authenticated connection, the server needs to acquire a credentials handle so that it can receive an incoming authentication request from the client. The server's credentials may be used to authenticate the server in security protocols that support server authentication or mutual authentication. When a connection request is received, the server creates a local security context to represent the client. The server uses the security context to carry out future requests by the same client.

First, the server obtains a handle to its credentials, which may be defined by the service account used to start the server. It does so by calling AcquireCredentialsHandle as follows:

//
// Acquire an out-bound Credentials handle using the chosen security package.
//
SecurityStatus = (*SecurityInterface->AcquireCredentialsHandle)(
0,
SecurityPackages[PkgToUseIndex].Name,
SECPKG_CRED_INBOUND,
0,
0,
0,
0,
&Credentials,
&TimeStamp
);

The arguments to the server-side call to AcquireCredentialHandle are as follows:

  • Arg1 = Principal Name, set to NULL here to let the security package use the default

  • Arg2 = Security Package Name, set to the one that was selected at initialization

  • Arg3 = Type of credentials, inbound for a server, use SECPKG_CRED_BOTH if this server is going to be a client to another server.

  • Arg4 = Pointer to LogonID (set to NULL to let the security package use the default).

  • Arg5 = AuthIdentity Package specific authentication data. Since NTLM does not support server authentication, this can be NULL. For other security providers, this can be server authentication data, such as public key credentials.

  • Arg6 = GetKey function (set to NULL)

  • Arg7 = Any argument to the GetKey function (also set to NULL)

  • Arg8 = Returned Credentials handle.

  • Arg9 = Returned TimeStamp which indicates the life span of the credentials handle.

The returned Credentials handle should be assigned to a global variable that is used for the lifetime of the server process. The returned TimeStamp is a temporary variable.

The server can wait (in a listen state) until a connection request arrives before acquiring an inbound credentials handle or it may acquire the handle and then go into a listen state.

When the server receives a connection request message from a client, it creates a security context for the client using AcceptSecurityContext. The server initializes the SecurityBufferDescriptors to refer to sections of the data message received, rather than copying data to an alternate buffer.

The following example shows the call to AcceptSecurityContext.

//
// Set up the Input and OutputBuffer Descriptor using the information from message received
// from the client.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;
OutSecBuffer.cbBuffer = BufferLen;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = Buffer;
InBufferDesc.ulVersion = 0;
InBufferDesc.cBuffers = 1;
InBufferDesc.pBuffers = &InSecBuffer;
InSecBuffer.cbBuffer = InBufferLen;
InSecBuffer.BufferType = SECBUFFER_TOKEN;
InSecBuffer.pvBuffer = InBuffer;
//
// Lets initialize client's context from the SSP and see if 
// we need to send anything back to the client
//
SecurityStatus = (*SecurityInterface->AcceptSecurityContext(
Credentials,
0,
InputBufferDescriptor,
ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE |
ISC_REQ_MUTUAL_AUTH |ISC_REQ_REPLAY_DETECT |
ISC_REQ_SEQUENCE_DETECT |ISC_REQ_CONFIDENTIALITY |
ISC_REQ_CONNECTION,
DataRepresentation,
&SecurityContext,
OutputBufferDescriptor,
&ContextAttributes,
&TimeStamp
);

The arguments to AcceptSecurityContext are as follows:

  • Arg1 = Credentials handle returned from the AcquireCredentialsHandle call.

  • Arg2 = Old Context handle if any.

  • Arg3 = Input Buffer Descriptor (if there is one received from client).

  • Arg4 = Context Attributes Requested (See the Security Context Details section below for for more information).

  • Arg5 = Data Representation (see SSPI.H for valid values).

  • Arg6 = New Context handle.

  • Arg7 = Output Buffer Descriptor, containing what will be sent back to the client.

  • Arg8 = Context Attributes that are supported by the provider.

  • Arg9 = TimeStamp for the life span of context validity.

The server checks the return status and output buffer descriptor to ensure there are no errors so far, otherwise it rejects the connection request. If there is information in the output buffer it bundles it into a response message to the client as per the application protocol.

If the return status requires the protocol to continue (SEC_I_CONTINUE_NEEDED or SEC_I_COMPLETE_AND_CONTINUE), then another message exchange with the client is required. Otherwise the authentication is complete. For third leg, the server waits for the client to respond with another message. Note that this wait maybe timed out so as to avoid a denial of service attack (a malicious client may never respond hanging this server thread, and soon it will hang all server threads!!).

Client Continuation

On receipt of the response from the server, the client decomposes the message and, using the continue status from the previous call, it calls InitializeSecurityContext again:

if(SecurityStatus == SEC_I_CONTINUE_NEEDED || SecurityStatus = SEC_I_COMPLETE_AND_CONTINUE)
{
//
// Set up the Input and OutputBuffer Descriptor using the information from message 
// received from the server.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;
OutSecBuffer.cbBuffer = BufferLen;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = Buffer;
InBufferDesc.ulVersion = 0;
InBufferDesc.cBuffers = 1;
InBufferDesc.pBuffers = &InSecBuffer;
InSecBuffer.cbBuffer = InBufferLen;
InSecBuffer.BufferType = SECBUFFER_TOKEN;
InSecBuffer.pvBuffer = InBuffer;
//
// 
SecurityStatus = (*SecurityInterface->InitializeSecurityContext(
0,
&SecurityContext,
0,
0,
0,
DataRepresentation,
InputBufferDescriptor,
0,
&SecurityContext,
OutputBufferDescriptor,
&ContextAttributes,
&TimeStamp
);
}

The client checks the return status from this call and may be required to continue for another leg. It uses the information in the OutputBufferDescriptor to construct a message, and sends it to the server.

Server Continuation

The server should be waiting for the response based on the return code from previous call to AcquireSecurityContext. To continue the authentication protocol, the server also calls AcceptSecurityContext again.

if(SecurityStatus = SEC_I_CONTINUE_NEEDED || SecurityStatus = SEC_I_COMPLETE_AND_CONTINUE)
{
//
// Set up the Input and OutputBuffer Descriptor using the information from message
// receivedfrom the client.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;
OutSecBuffer.cbBuffer = BufferLen;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = Buffer;
InBufferDesc.ulVersion = 0;
InBufferDesc.cBuffers = 1;
InBufferDesc.pBuffers = &InSecBuffer;
InSecBuffer.cbBuffer = InBufferLen;
InSecBuffer.BufferType = SECBUFFER_TOKEN;
InSecBuffer.pvBuffer = InBuffer;
//
// Lets do the next leg of client's context initialization from the
// security package and see if we need
// to send anything back to the client
//
SecurityStatus = (*SecurityInterface->AcceptSecurityContext(
0,
&SecurityContext,
InputBufferDescriptor,
0,
DataRepresentation,
&SecurityContext,
OutputBufferDescriptor,
&ContextAttributes,
&TimeStamp
);
}

The return status is checked to see if the server needs to wait for another leg from the client. In most existing authentication protocols this is the maximum even for mutual authentication. NTLM security package performs client authentication and Kerberos security package does mutual authentication in three legs.

Secure Message Exchange

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:

  • MakeSignature—Generates a secure signature based on a message and a security context.

  • VerifySignature—Verifies that the signature matches a received message.

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.

Sender

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:

  • Arg1 = Context Handle for the active security context

  • Arg2 = Quality of protection

  • Arg3 = Buffer descriptor containing the message for signing.

  • Arg4 = Sequence number of the message if sequence detection is on.

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.

Receiver

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:

  • Arg1 = Context Handle for the active context

  • Arg2 = Buffer descriptor containing received message

  • Arg3 = Sequence number expected for the received message

  • Arg4 = Quality of protection on the message (if any)

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.

Message Privacy

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.

Windows 2000 is expected to expose message encryption routines. The SSPI function entry points for message privacy will be:

  • EncryptMessage

  • DecryptMessage

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 Platform SDK.

Impersonation

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 that 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:

  1. Anonymous—the server is not allowed to find any information on the client's security context.

  2. Identification—the server can only authenticate the client but not use the client's security context for access checks.

  3. 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.

  4. 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 Windows 2000 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 the 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.

Using Delegation in Kerberos

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.

Bb742535.sspiz203(en-us,TechNet.10).gif

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.
//

SDK Examples

The code segments in the sections above show how SSPI APIs can be used for connection authentication, message integrity, and impersonation. The Platform SDK includes two samples that use SSPI to obtain security services; they are the 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.

  • SockAuth Sample

    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 Sample

    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 the InitAuthorizationHeader, TerminiateAuthorizationHeader, IsInAuthorization-Sequence, ValidateAuthenticationMethods, AddAuthorizationHeader, AuthInit, AuthTerminate, AuthConverse, uuencode, and uudecode functions.

Security Context Details

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.

Connection-Oriented Contexts

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 Contexts

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 reestablished 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

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, the last item 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

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."

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.

This section examines the development of secure distributed applications using security features of higher-level application interfaces that are layered above SSPI.

Using DCOM Security

DCOM can make distributed applications secure without any security specific coding or design in either the client or the component. Just like the DCOM programming model hides a component's location, it also hides the security requirements of a component. The same (preexisting or off-the-shelf) binary code that works in a single-machine environment, where security may be of no concern, can be used in a distributed environment in a secure fashion.

For more information about DCOM in general, see the DCOM Technical Overview and DCOM Architecture white papers. This section attempts to summarize how DCOM security features are layered on the Windows SSPI.

There are two distinguishable categories of security provided by Distributed COM. The first form is termed Activation Security , and it controls which objects a client is allowed to instantiate. The second form is Call Security , which dictates how security operates at the per-call level between an established connection from a client to an object (server). Activation security controls which classes a client is allowed to launch and retrieve objects from.

DCOM provides two mechanisms to secure calls. The first mechanism DCOM provides is APIs and interfaces that applications may use to perform their own security checking. The second mechanism is done automatically by the DCOM infrastructure. An important item to note is the automatic mechanism does security checking for the process, not for individual objects or methods.

Since DCOM security is layered on secure RPC, there are frequent references to constants defined for RPC-level interfaces. The section "Using Secure RPC," below, shows how the RPC constants map to SSPI security provider features.

Client Security

In a typical situation, the client queries an existing object for IClientSecurity, which is implemented locally by the interface remoting layer. The client uses IClientSecurity to control the security of individual interface proxies on the object prior to making a call on one of the interfaces.

An example illustrating a specific use of the IClientSecurity interface would be to ensure the integrity and privacy of a credit card transaction. You may want to use the IClientSecurity interface to escalate the RPC authentication level to include packet encryption (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY).

Although DCOM provides default security levels for authentication, the client requests additional features using IClientSecurity::SetBlanket to define the security level required. The definition of IClientSecurity::SetBlanket is shown below.

HRESULT IClientSecurity::SetBlanket(pProxy, dwAuthnSvc,
dwAuthzSvc, pServerPrincName, dwAuthnLevel, 
dwImpLevel, pAuthInfo, dwCapabilities );

This method sets the authentication information that will be used to make calls on the specified proxy. The values specified here override the values chosen by automatic security. Calling this method changes the security values for all other users of the specified proxy.

The arguments for IClientSecurity::SetBlanket are the following:

Argument

Type

Description

pProxy

void*

This parameter indicates the proxy to set.

dwAuthnSvc

DWORD

A single value from the list of RPC_C_AUTHN constants indicating the authentication service to use. It may be RPC_C_AUTHN_NONE if no authentication is required.

dwAuthzSvc

DWORD

A single value from the list of RPC_C_AUTHZ constants indicating the authorization service to use. If you are using the Windows NT authentication service, use RPC_C_AUTHZ_NONE.

PserverPrincName

WCHAR*

Indicates the server principal name to use with the authentication service. If you are using RPC_C_AUTHN_WINNT, the principal name will be ignored.

dwAuthnLevel

DWORD

A single value from the list of RPC_C_AUTHN_LEVEL constants indicating the authentication level to use.

dwImpLevel

DWORD

A single value from the list of RPC_C_IMP constants indicating the impersonation level to use. Currently, only RPC_C_IMP_LEVEL_IMPERSONATE is supported.

pAuthInfo

RPC_AUTH_IDENTITY_HANDLE*

Establishes the identity of the client. It is authentication service specific. Some authentication services allow the application to pass in a different user name and password. COM keeps a pointer to the memory passed in until COM is uninitialized or a new value is set. If NULL is specified COM uses the current identity (whether it is the process token or impersonation token).

dwCapabilities

DWORD

Flags to establish indicating the further capabilities of this proxy. Currently, no capability flags are defined.

Returns

S_OK

Success.

E_INVALIDARG

One or more arguments is invalid.

 

By default, DCOM chooses the first available authentication service and authorization service available on both the client and server machines and the principal name which the server registered for that authentication service. Currently, DCOM will not try another authentication service if the first fails.

The arguments allow the client application to select a specific authentication service and authentication level. The available values for these arguments are shown below in the tables, "RPC Authentication Service Identifiers," and "RPC Authentication Levels and SSP Services." The default Windows NT authentication service uses the NTLM security support provider. Future versions of Windows will make available additional authentication services based on Kerberos and SSL.

Server Security

When a call arrives at the server, the server may call CoGetCallContext to retrieve a IServerSecurity interface which allows the server to check the client's authentication and to impersonate the client, if needed. The IServerSecurity object is valid for the duration of the call. CoInitializeSecurity allows the client to establish default call security for the process, avoiding the use of IClientSecurity on individual proxies. CoInitializeSecurity allows a server to register automatic authentication services for the process.

The IServerSecurity interface is used by a server to help identify the client and is also used to impersonate the client during a call. By calling IServerSecurity::ImpersonateClient, the server can impersonate a client for the duration of the call. Note that CoImpersonateClient is a helper function that calls CoCallGetContext to retrieve an IServerSecurity interface pointer, and then it calls IServerSecurity::ImpersonateClient.

The IServerSecurity::ImpersonateClient maps through the RPC run-time layer to the SSPI call ImpersonateSecurityContext.

  • HRESULT IServerSecurity::ImpersonateClient();

    This method allows a server to impersonate a client for the duration of a call. The server may impersonate the client on any secure call at the identify, impersonate, or delegate level. At the identify level, the server may only find out the clients name and perform ACL checks; it may not access system objects as the client. At delegate level the server may make off machine calls while impersonating the client. The impersonation information only lasts until the end of the current method call. At that time IServerSecurity::RevertToSelf will automatically be called, if necessary.

  • HRESULT IServerSecurity::RevertToSelf();

    This method restores the authentication information on a thread to the process's identity.

The server may impersonate the client on any secure call at the identify, impersonate, or delegate level. At the identify level, the server may only find out the clients name and perform Access Control List (ACL) verification; it may not access system objects as the client. At the delegate level the server may make service calls on behalf of the client to servers on different machines. The impersonation information only lasts until the end of the current method call. At that time IServerSecurity::RevertToSelf will automatically be called if necessary.

The following helper routines are available to support DCOM server impersonation.

  • HRESULT CoImpersonateClient();

    Allows the server to impersonate the client of the current call for the duration of the call. This function will fail unless the object is being called with RPC_C_IMP_LEVEL_IMPERSONATE or higher impersonation in effect. This function encapsulates the following sequence of common calls (error handling excluded):

CoGetCallContext(IID_IServerSecurity, (void**)&pss); pss->ImpersonateClient(); pss->Release();

  • HRESULT CoRevertToSelf();

    Restores the authentication information on a thread of execution to its previous identity. This function encapsulates the following sequence of common calls (error handling excluded):

CoGetCallContext(IID_IServerSecurity, (void**)&pss); pss->RevertToSelf(); pss->Release();

DCOM Secure Sample

The Platform SDK contains a sample program that demonstrates the use of DCOM call security features. (The location of the DCOM secure example program in the SDK is c:\mstools\samples\ole\dcom\secure.) The SECURE sample consists of a client portion and a server portion. The purpose of the sample is to demonstrate client-side and server-side security features.

The server application, SECSVR.EXE, demonstrates the packaging options available to server writers and the call-security capabilities. It implements the following server capabilities:

  • A free-threaded server implemented as a service. The application implements the class "LocalService" (CLSID_SecureObjectService).

  • A free-threaded server implemented as a standard executable. The application implements the class "LocalServer32" (CLSID_SecureObject).

  • An apartment-threaded server implemented as a standard executable. The application implements the class "LocalServer32" (CLSID_SecureObject).

The client application, SECCLNT.EXE, allows you to select security settings for the client application's connections to the server application.

DCOM uses the authentication, authorization, and message integrity capabilities of authenticated RPC. The next section looks at secure RPC services, and the APIs available to integrate security features at that level.

Using Secure RPC

Any RPC application can turn security on by using simple security enabling RPC APIs. This section presents the APIs, various associated parameters, and then discusses where to use each API to get security services.

RPC Security Interfaces

The security related RPC interfaces are:

RPC_STATUS RPC_ENTRY
RpcBindingInqAuthInfoW (
IN RPC_BINDING_HANDLE Binding,
OUT RPC_CHAR PAPI * PAPI * ServerPrincName, OPTIONAL
OUT unsigned long PAPI * AuthnLevel, OPTIONAL
OUT unsigned long PAPI * AuthnSvc, OPTIONAL
OUT RPC_AUTH_IDENTITY_HANDLE PAPI * AuthIdentity, OPTIONAL
OUT unsigned long PAPI * AuthzSvc OPTIONAL
)
RPC_STATUS RPC_ENTRY
RpcBindingInqAuthInfoExW (
IN RPC_BINDING_HANDLE Binding,
OUT RPC_CHAR PAPI * PAPI * ServerPrincName, OPTIONAL
OUT unsigned long PAPI * AuthnLevel, OPTIONAL
OUT unsigned long PAPI * AuthnSvc, OPTIONAL
OUT RPC_AUTH_IDENTITY_HANDLE PAPI * AuthIdentity, OPTIONAL
OUT unsigned long PAPI * AuthzSvc, OPTIONAL
IN unsigned long RpcSecurityQosVersion,
OUT RPC_SECURITY_QOS * SecurityQos
)

RpcBindingInqAuthInfo is a client-side API that can be used to inquire about the authentication information set on the supplied binding handle. Ex-counterpart provides additional information on the security quality of service setting on the binding handle.

RPC_STATUS RPC_ENTRY
RpcBindingSetAuthInfoW (
IN RPC_BINDING_HANDLE Binding,
IN RPC_CHAR PAPI * ServerPrincName,
IN unsigned long AuthnLevel,
IN unsigned long AuthnSvc,
IN RPC_AUTH_IDENTITY_HANDLE AuthIdentity, OPTIONAL
IN unsigned long AuthzSvc
)
RPC_STATUS RPC_ENTRY
RpcBindingSetAuthInfoExW (
IN RPC_BINDING_HANDLE Binding,
IN RPC_CHAR PAPI * ServerPrincName,
IN unsigned long AuthnLevel,
IN unsigned long AuthnSvc,
IN RPC_AUTH_IDENTITY_HANDLE AuthIdentity, OPTIONAL
IN unsigned long AuthzSvc,
IN RPC_SECURITY_QOS *SecurityQOS
)

RpcBindingSetAuthInfo is a client-side API that can be used by to set authentication information on the supplied binding handle. This involves setting up authentication level, authentication service, and so forth. Ex-counterpart provides additional capability to set security quality of service information on the binding handle. The mapping of various RPC security constants is described in subsections later.

RPC_STATUS RPC_ENTRY
RpcMgmtInqDefaultProtectLevel(
IN unsigned long AuthnSvc,
OUT unsigned long PAPI *AuthnLevel
)

RpcMgmtInqDefaultProtectLevel returns the mapping for RPC_C_AUTHN_LEVEL_DEFAULT for the supplied authentication service.

RPC_STATUS RPC_ENTRY
RpcServerRegisterAuthInfoW (
IN RPC_CHAR PAPI * ServerPrincName,
IN unsigned long AuthnSvc,
IN RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn, OPTIONAL
IN void PAPI * Arg OPTIONAL
)

RpcServerRegisterAuthInfo is the server-side API to turn on security for the various server interfaces that are registered. It sets up the server, principal name, authentication service to use, any key retrieval function (default is supplied by authentication service) in the RPC_SERVER object. The authentication service to use is specified by a constant. The mapping of these constants to packages is discussed in a subsection later in this paper.

RPC_STATUS RPC_ENTRY
RpcBindingInqAuthClientW (
IN RPC_BINDING_HANDLE ClientBinding, OPTIONAL
OUT RPC_AUTHZ_HANDLE PAPI * Privs,
OUT RPC_CHAR PAPI * PAPI * ServerPrincName, OPTIONAL
OUT unsigned long PAPI * AuthnLevel, OPTIONAL
OUT unsigned long PAPI * AuthnSvc, OPTIONAL
OUT unsigned long PAPI * AuthzSvc OPTIONAL
)

The server-side application can use RpcBindingInqAuthClient to obtain authorization information about the client making the authenticated RPC.

RPC_STATUS RPC_ENTRY
RpcImpersonateClient (
IN RPC_BINDING_HANDLE ClientBinding OPTIONAL
)
RPC_STATUS RPC_ENTRY
RpcRevertToSelfEx (
IN RPC_BINDING_HANDLE ClientBinding OPTIONAL
)
RPC_STATUS RPC_ENTRY
RpcRevertToSelf (
)

The server thread uses RpcImpersonateClient to impersonate the client whose request it is servicing. The impersonation level is set according to the one specified by the client when making the connection.

RevertToSelf is used to return the default security context.

RPC_STATUS RPC_ENTRY
RpcMgmtInqServerPrincNameW (
IN RPC_BINDING_HANDLE Binding,
IN unsigned long AuthnSvc,
OUT RPC_CHAR __RPC_FAR * __RPC_FAR * ServerPrincName
)
RPC_STATUS RPC_ENTRY
RpcServerInqDefaultPrincNameW (
IN unsigned long AuthnSvc,
OUT RPC_CHAR __RPC_FAR * __RPC_FAR * PrincName
)

RpcMgmtInqServerPrincName is used by the server application to inquire aboutthe server's principal name corresponding to the supplied binding handle and the authentication service.

RpcServerInqDefaultPrincName returns the default principal name for the server for the supplied authentication service.

RPC Authentication Service Constants

Authentication service is provided by security support packages. RPC defines six authentication service identifiers. Security support packages are loaded in the system as part of the security provider DLL. Each provider DLL can support one or more security packages. RPC loads the provider DLLs and then calls EnumerateSecurityPackages in the DLL to get the security packages in the DLL. Each security package information contains "wRPCID" which has to be one of the six RPC identifiers. This information is used to select the security package which corresponds to the authentication service requested by the application during the security initialization calls.

RPC Authentication Service Identifiers.

RPC Authentication Service

Security Support Package

RPC_C_AUTHN_NONE

no authentication package to use, that is, no security.

RPC_C_AUTHN_DCE_PRIVATE

not currently supported

RPC_C_AUTHN_DCE_PUBLIC

not currently supported

RPC_C_AUTHN_DEC_PUBLIC

not currently supported

RPC_C_AUTHN_WINNT

NTLM SSP in security.dll (Windows NT 4.0)

RPC_C_AUTHN_DEFAULT

NTLM SSP in security.dll (Windows NT 4.0)

RPC Authentication Level Constants

RPC provides security by "levels." The authentication levels are:

  • Default ( RPC_C_AUTHN_LEVEL_DEFAULT) == Per Connection Authentication

  • No Authentication (RPC_C_AUTHN_LEVEL_NONE)

  • Per Connection Authentication (RPC_C_AUTHN_LEVEL_CONNECT)

  • Per Call Authentication (RPC_C_AUTHN_LEVEL_CALL)

  • Per Packet Authentication (RPC_C_AUTHN_LEVEL_PKT)

  • Per Packet Authentication with Packet Integrity (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)

  • Per Packet Authentication with Packet Integrity and Privacy (RPC_C_AUTHN_LEVEL_PKT_PRIVACY).

Whether a particular authentication level is available is decided by the security package being used. If a security package does not support the requested level it may upgrade it to the next highest level it supports (if available) or return an error during server initialization.

RPC Authentication Levels and SSP Services

RPC Authentication Level

SSP Service Level

RPC_C_AUTHN_LEVEL_CONNECT

ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH

RPC_C_AUTHN_LEVEL_CALL

bumped to RPC_C_AUTHN_LEVEL_PKT

RPC_C_AUTHN_LEVEL_PKT

ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH | ISC_REQ_REPLAY_DETECT

RPC_C_AUTHN_LEVEL_PKT_ INTEGRITY

ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT

RPC_C_AUTHN_LEVEL_PKT_ PRIVACY

ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH | ISC_REQ_REPLAY_DETECT |ISC_REQ_SEQUENCE_DETECT| ISC_REQ_CONFIDENTIALITY

The mapping of RPC authentication levels to SSPI's capabilities is constant. It does not change with the change of authentication package. It is up to the authentication package to ignore or return an error on some of these depending on the capabilities it supports.

RPC Impersonation Levels

RPC is designed to provide all the impersonation levels defined by a native Windows NT-based system:

  • Anonymous (RPC_C_IMP_LEVEL_ANONYMOUS)

  • Identify (RPC_C_IMP_LEVEL_IDENTIFY)

  • Impersonate (RPC_C_IMP_LEVEL_IMPERSONATE)

  • Delegate (RPC_C_IMP_LEVEL_DELEGATE)

The delegation impersonation level is not supported by Windows NT natively, but the security package may have the ability to provide it. In fact, in the current implementation, RPC always requests delegation level security context from the security package for nonauthenticated transports. It maps the requested level to the native level for authenticated transports (Namedpipes and LPC). Impersonation levels are also associated with context tracking modes. If the tracking is static, the security context is created only once and is never revised during the entire communication, even if the client side changes it. In the case of dynamic tracking, the context is revised whenever the LogonId in the client's token is changed.

NTLM security package, the default security package in Windows NT 4.0, does not currently support delegation and ignores the RPC's request.

RPC Authentication Identity

During client initialization, client process can supply a client identity handle in the RpcBindingSetAuthInfo call. This authentication identity handle points to a structure which contains the username, domain name, and plain text password of the user. This information is not required, and if not present, the process's default identity is picked from the attached token by the security package.

RPC stores this information with the binding and passes it on to the security support package when acquiring security credentials.

RPC Authorization Service Constants

RPC currently defines three authorization service constants:

  • RPC_C_AUTHZ_NONE

  • RPC_C_AUTHZ_NAME

  • RPC_C_AUTHZ_DCE

RPC currently does not use this information. It corresponds to authorization service setting by the security package. The default security package in Windows NT 4.0 (NTLM security package) sets this to 0.

RPC Client and Server Security Initialization

The RPC server initializes security by calling RpcServerRegisterAuthInfo as follows:

//
// Request Security…
//
Status = RpcServerRegisterAuthInfo(
NULL,
RPC_C_AUTHN_WINNT,
NULL,
NULL
);
//
// where:
// Arg1 = ServerPrincipalName (ignored by NTLM SSP).
// Arg2 = The SSP to use based on RPC settings => default NTLM SSP
// Arg3/Arg4 = GetKey function and its argument, not used.
// 
if(Status)
{
// Oops.. we have a problem…
}
//
// Standard RPC calls to register the interface, set up the endpoint, etc.
// and then go into Listen…
//

The RPC client initializes security by calling RpcBindingSetAuthInfo(Ex) as follows:

//
// Do the binding etc.
//
// 
// Request Security…
//
Status = RpcBindingSetAuthInfoEx(
RpcInterfaceHandle,
NULL,
AuthnLevel,
RPC_C_AUTHN_WINNT,
NULL,
AuthzSvc,
SecurityQos
);
//
// where:
// Arg1 = Binding Handle just established.
// Arg2 = ServerPrincipalName (NULL as it is ignored by NTLM SSP).
// Arg3 = Authentication Leve (see above for valid values).
// Arg4 = Auth service which be default selects NTLM SSP for WinNT.
// Arg5 = Authentication Identity which can be SEC_WINNT_AUTH_IDENTITY structure
// specifying Username, DomainName and password. NULL means use default.
// Arg6 = Autorization Service (see above for valid values).
// Arg7 = Impersonation Level, see above for valid values.
//

RPC Invocations and Executions.

Once the client and server sides have initialized security, the client can go ahead and start making remote procedure calls without worrying about security. Underlying RPC run time encapsulates all of the SSPI interactions to establish the authenticated connection and secure message exchange.

On the server side, when a procedure is invoked for execution, it can use RpcImpersonateClient() API to impersonate the client before servicing the request, if so required. It can then revert to original security context using RpcRevertToSelf().

Using Secure WinSock

WinSock 2.0 extends the Windows Socket interface to allow the underlying transport provider to expose security features that may be built into the provider. It is conceivable that a TCP/IP provider could use SSPI to encapsulate security functionality such that it can be invoked by a WinSock application. This is similar to how RPC encapsulates this functionality, and all the application needs to do is to request it. Note that such a provider does not currently exist for Windows NT 4.0. Microsoft is implementing a WinSock 2.0 provider for SSL 3.0 which will be available soon. A general purpose SSPI-TCP/IP transport provider is not currently implemented, but Microsoft will make one available in a future release.

Using the TCP/IP and SSL security provider as an example, all that an application needs to do to use SSPI services is the following:

  • Enumerate the available transport providers

  • Create a socket in which to use the transport, and initialize the necessary callback functions

  • Initialize the SSL security provider options using WSAIoctl calls

  • Connect to the server

The following example is taken from the Microsoft Internet Security Schannel examples. The example below shows how to use SSL 3.0 and a TCP/IP provider from WinSock 2.0.

//
// Initialize the WinSock 2 subsystem.
//
err = WSAStartup(MAKEWORD(2,2), &WsaData);
if(err != 0)
{
printf("**** Error initializing WinSock!\n");
return;
}
//
// Choose a WinSock 2 provider that supports both TCP/IP and the
// chosen security scheme.
//
cProtocols = WSAEnumProtocols(NULL,
NULL,
&cbProtocolBuffer);
if(cProtocols == SOCKET_ERROR && WSAGetLastError() != WSAENOBUFS)
{
printf("**** Error %d during WSAEnumProtocols\n", WSAGetLastError());
return;
}
// Allocate memory
pProtocolBuffer = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, cbProtocolBuffer);
if(pProtocolBuffer == NULL) return FALSE;
// Read array of WSAPROTOCOL_INFO structures.
cProtocols = WSAEnumProtocols(NULL,
pProtocolBuffer,
&cbProtocolBuffer);
if(cProtocols == SOCKET_ERROR)
{
LocalFree(pProtocolBuffer);
return;
}
// Search for SSL protocol
for(i = 0, fFound = FALSE ; i < cProtocols ; i++)
{
if(pProtocolBuffer[i].iProtocol == IPPROTO_TCP &&
pProtocolBuffer[i].iSecurityScheme == SECURITY_PROTOCOL_SSL)
{
*pProtocolInfo = pProtocolBuffer[i];
fFound = TRUE;
break;
}
}
if(!fFound)
{
LocalFree(pProtocolBuffer);
return;
}
// Free memory
LocalFree(pProtocolBuffer);

Once you have selected the WinSock provider of choice, you can go ahead and create a socket and initialize security on it.

//
// Create socket
//
Socket = WSASocket(FROM_PROTOCOL_INFO,  // address family
FROM_PROTOCOL_INFO,  // type
FROM_PROTOCOL_INFO,  // protocol
pProtocolInfo,   // protocol info
0,      // group
0);      // flags
if(Socket == INVALID_SOCKET)
{
printf("**** Error creating socket!\n");
return FALSE;
}
else
{
*pSocket = Socket;
}
//
// Set the socket's SSL flags.
//
{
DWORD dwFlags;
dwFlags = SSL_FLAG_ENABLE;
err = WSAIoctl(Socket,
SO_SSL_SET_FLAGS,
(LPVOID)&dwFlags,
sizeof(dwFlags),
NULL,     // output buffer
0,      // output buffer size
NULL,     // # bytes returned
NULL,     // overlapped structure
NULL);     // completion routine
if(err == SOCKET_ERROR)
{
printf("**** Error setting SSL flags!\n");
return FALSE;
}
}
//
// Set the socket's SSL client parameters.
//
{
SSLCLIENTOPTS SslClientOpts;
DWORD cbBytesRead;
// Read the default client parameters.
err = WSAIoctl(Socket,     // socket
SO_SSL_GET_CLIENT_OPTS, // dwIoControlCode
NULL,     // lpvInBuffer
0,      // cbInBuffer
(LPVOID)&SslClientOpts, // lpvOutBuffer
sizeof(SslClientOpts), // cbOutBuffer
&cbBytesRead,   // lpcbBytesReturned
NULL,     // lpOverlapped
NULL);     // lpCompletionRoutine
if(err == SOCKET_ERROR)
{
printf("**** Error reading default client options!\n");
return FALSE;
}
// Set the session cache timeout period to 10 minutes. Leave
// everything else at the default value.
SslClientOpts.CacheTimeout = 600;
// Set the client parameters.
err = WSAIoctl(Socket,     // socket
SO_SSL_SET_CLIENT_OPTS, // dwIoControlCode
(LPVOID)&SslClientOpts, // lpvInBuffer
sizeof(SslClientOpts), // cbInBuffer
NULL,     // lpvOutBuffer
0,      // cbOutBuffer
NULL,     // lpcbBytesReturned
NULL,     // lpOverlapped
NULL);     // lpCompletionRoutine
if(err == SOCKET_ERROR)
{
printf("**** Error setting client options!\n");
return FALSE;
}
}
//
// We could additionally set up authentication call back functions, etc.
// using WSAIoctl API, depending on what else may be involved in setting
// up a secure socket.
//

Note: The code above is for demonstration purposes only; no such WinSock provider is currently available from Microsoft.

Once the socket is set up, the application can use WSAConnect API to connect to the server and the underlying provider will use the socket security settings to authenticate the connection using an appropriate security package, and so forth. From then on, all the communication can occur transparent of any message signing and sealing that may be done by the provider via SSPI.

Using Secure WinInet

Win32 Internet APIs, also known as WinInet, is another way of building secure distributed applications without having to deal with SSPI directly. Please refer to the WinInet documentation in the SDK for details. This paper provides a simple example that uses Secure HTTP via WinInet APIs. Currently the security provider interfaced with WinInet is SCHANNEL.DLL that uses the SSL/PCT authentication protocol. Windows 2000 may allow other security flags to WinInet that will allow you to develop applications using the Kerberos security.

After initializing the WinInet API, you call the InternetConnect API to create a session and all that is needed to turn on security is INTERNET_FLAG_SECURE. The WinInet.Dll under the cover talks to SSPI and gets an authenticated connection using an appropriate provider, which by default is the SSL-based Schannel.Dll at present but can easily be extended to give users a choice of providers by adding flags like INTERNET_FLAG_SECURITY_SSL or INTERNET_FLAG_SECURITY_ KERBEROS.

//
// Create an HTTPS session. Note that this differs from creating a
// normal HTTP session in that the INTERNET_FLAG_SECURE flag is specified.
//
hConnect = InternetConnect(
hOpen,       // hInternetSession
szHostName,      // lpszServerName
INTERNET_INVALID_PORT_NUMBER, // nServerPort
"",        // lpszUsername
"",        // lpszPassword
INTERNET_SERVICE_HTTP,   // dwService
INTERNET_FLAG_SECURE,   // dwFlags
0);        // dwContext
if(hConnect == 0)
{
ErrorOut(GetLastError(), "InternetConnect");
return 0;
}
//
// Create an HTTPS request handle. Note that this example specifies two
// security-related flags:
//
// INTERNET_FLAG_SECURE
//  Use the SSL/PCT security protocol.
//
// INTERNET_FLAG_CERT_CN_INVALID
//  During the SSL/PCT handshake phase, the server authenticates itself
//  by presenting a certificate. Within the certificate, the subject's
//  common name field, must equal the server's host name. Setting this
//  flag turns off the automatic checking that HTTPSendRequest normally
//  does. Instead, this example validates the host name manually.
//  (Actually, this automatic check seems to be broken in the current
//  version of Wininet.dll).
//
hReq = HttpOpenRequest(
hConnect,       // hHttpSession
"GET",        // lpszVerb
"",         // lpszObjectName
HTTP_VERSION,      // lpszVersion
"",         // lpszReferer
NULL,        // lpszAcceptTypes
INTERNET_FLAG_RELOAD   |
INTERNET_FLAG_NO_CACHE_WRITE |
INTERNET_FLAG_SECURE   | // dwFlags
INTERNET_FLAG_IGNORE_CERT_CN_INVALID,
0);         // dwContext
if(hReq == 0)
{
ErrorOut (GetLastError(), "HttpOpenRequest");
return 0;
}

Security in Other Application Protocols

The DCOM, RPC, WinSock, and WinInet examples above illustrate an important concept with respect to SSPI. It does not matter which underlying protocol the distributed application is built on, as long as the application protocol defines security requirements for connection authentication, message integrity, message privacy, and security quality of service, SSPI can be used as the common abstraction layer to obtain these services. Therefore, other protocols like POP3, LDAP, IRC/MIC, IMAP, and so on. can be implemented using SSPI to take advantage of the integrated security services available in Windows NT.

Summary

Microsoft's Security Support Provider Interface provides an interface for integrating Windows NT authentication and security into distributed applications. The SSPI APIs consist of functionality to use security providers in the areas of:

  • Credential management

  • Context management

  • Message support

  • Security package management

SSPI is designed to support the semantics of connection-oriented transports, as well as datagram and stream transports.

The major feature of SSPI is that applications have a common API to use different security packages, including Windows NTLM authentication, SSL/PCT public key cryptography providers, and in Windows 2000, a Kerberos authentication security provider.

Application developers have the option to call SSPI functions directly to integrate Windows NT security or use higher-level application interfaces based on DCOM, authenticated RPC, or WinSock 2.0. Microsoft continues to support SSPI by developing new security packages based on the interface specification. Microsoft encourages all Win32 application developers to use the integrated security features of SSPI for secure distributed application development.

For More Information

For the latest information on Windows NT Server and Windows 2000, check out Microsoft TechNet or our World Wide Web site at https://www.microsoft.com/ntserver, or the Windows NT Server Forum on the Microsoft Network (GO WORD: MSNTS).

SUMMARY

(or Conclusion)

For More Information

For the latest information on Windows 2000, check out our World Wide Web site at https://www.microsoft.com/ntserver/, the Windows NT Server Forum on MSN™, and The Microsoft Network online service (GO WORD: MSNTS).© 1999 Microsoft Corporation. All rights reserved.