Authenticating a Connection

In a typical client/server application protocol, the server waits for the client to connect and request service. Upon connection, the server must be able to authenticate the client and the client must be able to authenticate the server. The protocol used to establish an authentic connection involves the exchange of one or more security tokens between the client and server SSP. The tokens are sent as messages by the client and the server and include protocol-specific data.

When a message is received, the application protocol reads the token from the message and passes it to its security provider to ensure authentication is complete or to determine if further exchange of tokens is required. The client and the server continue to exchange messages until either the authentication is successfully established or an error occurs.

To begin the authentication process, the client needs to obtain an outbound credentials handle so that it can send an authentication request to the server. This is accomplished by calling the AcquireCredentialsHandle function.

Using a reference to the SecurityFunctionTable function initialized during the SSPI initialization procedure, the following code example shows how the client obtains an outbound credentials handle.

// Get an outbound credentials handle.
status = (*pSecurityInterface->AcquireCredentialsHandle)(
                                  NULL,
                                  pSecurityPackages[dwPkgToUse].Name,
                                  SECPKG_CRED_OUTBOUND,
                                  NULL,
                                  NULL,
                                  NULL,
                                  NULL,
                                  &hCredential,
                                  &tsExpiry);

When the client accesses credential data, it starts the authentication protocol to establish a connection with the server. The client calls the InitializeSecurityContext function to obtain a security token to send a connection request message to the server.

The following code example shows how the client calls InitializeSecurityContext.

// Initialize the OutSecBuffer structure.
OutSecBuffer.cbBuffer = BUFFERLEN;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = pszOutBuffer;

// Initialize the OutBufferDesc structure.
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;

ulContextReq = ISC_REQ_MUTUAL_AUTH | ISC_REQ_CONNECTION |
               ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | 
               ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY;

// Assign the target server name.
// wcscpy (szTargetName, TEXT("..."));

// Get the authentication token from the security package
// to pass to the server to request an authenticated token.
status = (*pSecurityInterface->InitializeSecurityContext)(
                                          &hCredential,
                                          NULL,
                                          szTargetName,
                                          ulContextReq,
                                          0,
                                          SECURITY_NATIVE_DREP,
                                          NULL,
                                          0,
                                          &hNewContext,
                                          &OutBufferDesc,
                                          &ulContextAttributes,
                                          &tsExpiry); 

The client uses the security token data 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, is decided by the application protocol and is adhered to by both client and server.

Most SSPI functions have variable length arguments that enable an application to provide message data to a security package and enable a security package to return security data, such as a token, to the application. These functions use a parameter type, called a buffer descriptor, to define the size and location of the variable length data. For more information on buffers, see Memory Use and Buffers.

When the client sends a message to the server, it checks the return status from the call to InitializeSecurityContext to see if authentication is complete. If not, it expects to receive an authentication token from the server in a response message to continue the security protocol. The return status SEC_I_CONTINUE_NEEDED, indicates that the security protocol requires additional authentication messages.

To establish an authenticated connection, the server must also obtain a handle to its credentials by calling the AcquireCredentialsHandle function. When received, you should assign a global variable to the handle to use for the duration of the server process.

When the server receives a connection request from the client, it calls the AcceptSecurityContext function, passing the data that it received from the client as input. The server initializes the security buffer descriptors to refer to specific sections of the data, rather than copying the data to an alternate buffer.

The server checks the return status and output buffer descriptor to ensure that no errors exist. If errors exist, it rejects the connection request. If no errors exist, it checks the output buffer for data. If data is present in the buffer, the server bundles the data according to the application protocol and places it into a response message to the client.

If the return status is SEC_I_CONTINUE_NEEDED or SEC_I_COMPLETE_AND_CONTINUE, another message exchange with the client is required. Otherwise, authentication is complete. If authentication must continue, the server waits for the client to respond with another message. Place a time out on this waiting period in case the client does not respond with a message. This will avoid the possibility of hanging this and, subsequently, all server threads.

When the client receives a reply from the server, it deconstructs the message and calls InitializeSecurityContext again; be sure to use the continue status from the previous call. Depending on the security package and the context requirements, the transmission of messages between the client and the server can go on indefinitely, but is usually limited to three attempts.