Platform SDK: Active Directory, ADSI, and Directory Services |
This topic shows the code that a client program uses to compose an SPN for a service. The client binds to the service's service connection point (SCP) to retrieve the information needed to connect to the service. The SCP also contains information the client can use to compose the service's SPN. For sample code that binds to the SCP and retrieves the necessary properties, see How Clients Find and Use a Service Connection Point.
This topic also shows how a client uses an SSPI security package and the service's SPN to establish a mutually authenticated connection to the Windows Sockets service. Note that this code is almost identical to the code required in Microsoft Windows NTŪ 4.0 and earlier just to authenticate the client to the server. The only difference is that the client must supply the SPN and specify the ISC_REQ_MUTUAL_AUTH flag.
// Initialize these strings by querying the service's SCP. TCHAR szDn[MAX_PATH], // DN of the service's SCP szServer[MAX_PATH], // DNS name of the service's server szClass[MAX_PATH]; // Service class TCHAR szSpn[MAX_PATH]; // Buffer for SPN SOCKET sockServer; // Socket connected to service DWORD dwRes, dwLen; . . . // Compose the SPN for the service using the DN, Class, and Server // returned by ScpLocate dwLen = sizeof(szSpn); dwRes = DsMakeSpn( szClass, // Service class szDn, // DN of the service's SCP szServer, // DNS name of the server on which service is running 0, // No port component in SPN NULL, // No referrer &dwLen, // Size of szSpn buffer szSpn); // Buffer to receive the SPN if (!DoAuthentication (sockServer, szSpn)) { closesocket (sockServer); return(FALSE); } . . .
This code sample consists of two routines: DoAuthentication and GenClientContext. After calling DsMakeSpn to compose an SPN for the service, the client passes the SPN to the DoAuthentication routine, whichcalls the GenClientContext to generate the initial buffer to send to the service. DoAuthentication uses the socket handle to send the buffer and receive the service's response, which is passed to the SSPI package by another call to GenClientContext. This loop is repeated until the authentication fails or GenClientContext sets a flag that indicates the authentication was successful.
The GenClientContext routine interacts with the SSPI package to generate the authentication information to send to the service and process the information received from the service. The key components of the authentication information provided by the client are the following:
// Structure for storing the state of the authentication sequence. typedef struct _AUTH_SEQ { BOOL _fNewConversation; CredHandle _hcred; BOOL _fHaveCredHandle; BOOL _fHaveCtxtHandle; struct _SecHandle _hctxt; } AUTH_SEQ, *PAUTH_SEQ; /***************************************************************/ // DoAuthentication routine for the client. // // Manages the client's authentication conversation with the service // using the supplied socket handle. // // Returns TRUE if the mutual authentication is successful. // Otherwise, it returns FALSE. // /***************************************************************/ BOOL DoAuthentication ( SOCKET s, LPTSTR szSpn) { BOOL done = FALSE; DWORD cbOut, cbIn; // Call the security package to generate the initial buffer of // authentication information to send to the service. cbOut = g_cbMaxMessage; if (!GenClientContext (s, NULL, 0, g_pOutBuf, &cbOut, &done, szSpn)) return(FALSE); if (!SendMsg (s, g_pOutBuf, cbOut)) return(FALSE); // Pass the service's response back to the security package, and send // the package's output back to the service. Repeat until done. while (!done) { if (!ReceiveMsg (s, g_pInBuf, g_cbMaxMessage, &cbIn)) return(FALSE); cbOut = g_cbMaxMessage; if (!GenClientContext (s, g_pInBuf, cbIn, g_pOutBuf, &cbOut, &done, szSpn)) return(FALSE); if (!SendMsg (s, g_pOutBuf, cbOut)) return(FALSE); } return(TRUE); } /***************************************************************/ // GenClientContext routine // // Handles the client's interactions with the security package. // Optionally takes an input buffer coming from the service // and generates a buffer of information to send back to the // service. Also returns an indication when the authentication // is complete. // // Returns TRUE if the mutual authentication is successful. // Otherwise, it returns FALSE. // /***************************************************************/ BOOL GenClientContext ( DWORD dwKey, // socket handle used as key BYTE *pIn, DWORD cbIn, BYTE *pOut, DWORD *pcbOut, BOOL *pfDone, LPTSTR szSpn) { SECURITY_STATUS ssStatus; TimeStamp Lifetime; SecBufferDesc OutBuffDesc; SecBuffer OutSecBuff; SecBufferDesc InBuffDesc; SecBuffer InSecBuff; ULONG ContextAttributes; PAUTH_SEQ pAS; // structure to store state of authentication // Get structure containing the state of the authentication sequence. if (!GetEntry (dwKey, (PVOID*) &pAS)) return(FALSE); if (pAS->_fNewConversation) { ssStatus = g_pFuncs->AcquireCredentialsHandle ( NULL, // principal PACKAGE_NAME, SECPKG_CRED_OUTBOUND, NULL, // LOGON id NULL, // authentication data NULL, // get key function NULL, // get key argument &pAS->_hcred, &Lifetime ); if (SEC_SUCCESS (ssStatus)) pAS->_fHaveCredHandle = TRUE; else { fprintf (stderr, "AcquireCredentialsHandle failed: %u\n", ssStatus); return(FALSE); } } // Prepare output buffer OutBuffDesc.ulVersion = 0; OutBuffDesc.cBuffers = 1; OutBuffDesc.pBuffers = &OutSecBuff; OutSecBuff.cbBuffer = *pcbOut; OutSecBuff.BufferType = SECBUFFER_TOKEN; OutSecBuff.pvBuffer = pOut; // Prepare input buffer if (!pAS->_fNewConversation) { InBuffDesc.ulVersion = 0; InBuffDesc.cBuffers = 1; InBuffDesc.pBuffers = &InSecBuff; InSecBuff.cbBuffer = cbIn; InSecBuff.BufferType = SECBUFFER_TOKEN; InSecBuff.pvBuffer = pIn; } _tprintf(TEXT("InitializeSecurityContext: pszTarget=%s\n"),szSpn); ssStatus = g_pFuncs->InitializeSecurityContext ( &pAS->_hcred, pAS->_fNewConversation ? NULL : &pAS->_hctxt, szSpn, ISC_REQ_MUTUAL_AUTH, // Context requirements 0, // reserved1 SECURITY_NATIVE_DREP, pAS->_fNewConversation ? NULL : &InBuffDesc, 0, // reserved2 &pAS->_hctxt, &OutBuffDesc, &ContextAttributes, &Lifetime ); if (!SEC_SUCCESS (ssStatus)) { fprintf (stderr, "init context failed: %X\n", ssStatus); return FALSE; } pAS->_fHaveCtxtHandle = TRUE; // Complete token -- if applicable if ( (SEC_I_COMPLETE_NEEDED == ssStatus) || (SEC_I_COMPLETE_AND_CONTINUE == ssStatus)) { if (g_pFuncs->CompleteAuthToken) { ssStatus = g_pFuncs->CompleteAuthToken (&pAS->_hctxt, &OutBuffDesc); if (!SEC_SUCCESS(ssStatus)) { fprintf (stderr, "complete failed: %u\n", ssStatus); return FALSE; } } else { fprintf (stderr, "Complete not supported.\n"); return FALSE; } } *pcbOut = OutSecBuff.cbBuffer; if (pAS->_fNewConversation) pAS->_fNewConversation = FALSE; *pfDone = !((SEC_I_CONTINUE_NEEDED == ssStatus) || (SEC_I_COMPLETE_AND_CONTINUE == ssStatus)); // Check for the ISC_RET_MUTUAL_AUTH flag to verify that // mutual authentication was performed. if (*pfDone && !(ContextAttributes && ISC_RET_MUTUAL_AUTH) ) _tprintf(TEXT("Mutual Auth not set in returned context.\n")); return TRUE; }