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 two fold:

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

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:

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:

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:

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:

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:

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.