Platform SDK: Active Directory, ADSI, and Directory Services

How a Client Authenticates an RpcNs Service

The following sample code shows the RPC client's side of a mutually authenticated connection. The client connects to the RPC name service to enumerate the bindings that match the RPC service's interface specification. For each binding handle in the enumeration, the client must call the DsMakeSpn function to compose the corresponding SPN.

void main(void) 
{
RPC_STATUS     rpcstatus;
boolean        bResult;
ULONG          ulCode;
TCHAR          szEntryName[] = TEXT("/.:/RpcExampleServiceEntry");
TCHAR          szDsEntryName[MAX_PATH];
BOOL           done=FALSE;
RPC_NS_HANDLE  hNs;  // Context for import operations
TCHAR         *pszBind;
int           ilen;
 
// Items for ADSI
 
IADs    *pRoot=NULL;
VARIANT varDSRoot;
HRESULT hr;
 
// Items for Mutual Auth, SPN
 
TCHAR    szServiceClass[]=TEXT("RpcExample");
TCHAR    szServiceInstance[MAX_PATH];
TCHAR    szSpn[MAX_PATH];
ULONG    ulSpn = sizeof(szSpn);
 
RPC_SECURITY_QOS    qos;
 
// To build an SPN for the RPC service, the DN of the 
// service's entry in the RpcServices container is needed. To build 
// that DN, retrieve the default naming context and combine it with
// the known strings for the RpcServices container and the service's
// entry in that container.
hr = CoInitialize(NULL);
 
hr = ADsGetObject(TEXT("LDAP://RootDSE"),
                  IID_IADs,
                  (void**)&pRoot);
 
hr = pRoot->Get(TEXT("defaultNamingContext"),&varDSRoot);
_tprintf(TEXT("\nDS Root :%s\n"),varDSRoot.bstrVal);
    
if (pRoot)
    pRoot->Release();
 
CoUninitialize();
 
// Compose the DN of the RPC service's connection point using 
// "RpcExampleServiceEntry", which is the name of this service's 
// entry in the RpcServices container.
_tcscpy(szDsEntryName,
        TEXT("cn=RpcExampleServiceEntry,cn=RpcServices,cn=System,"));
_tcscat(szDsEntryName,varDSRoot.bstrVal);
 
// Use the service's RPC name service entry to get a handle to 
// enumerate the bindings that match our interface specification.
rpcstatus = RpcNsBindingImportBegin(RPC_C_NS_SYNTAX_DCE,
                                     (TCHAR *)&szEntryName,
                                     RpcExample_v1_0_c_ifspec,
                                     NULL,
                                     &hNs);
if (rpcstatus != RPC_S_OK)
    return;
 
// Loop through the bindings and try each handle until one works.
// In this example, implicit handles are used: the implicit handle
// "RpcExample_IfHandle" is generated by MIDL and defined in the 
// MIDL-generated include file RpcExample.H.
while (rpcstatus != RPC_S_NO_MORE_BINDINGS) 
{
    rpcstatus = RpcNsBindingImportNext(hNs,
                                       &RpcExample_IfHandle);
    if (rpcstatus != RPC_S_OK)
        continue;
 
    // Convert the binding to a string.
    RpcBindingToStringBinding(RpcExample_IfHandle,&pszBind);
    _tprintf(TEXT("String Binding:%s\n"),pszBind);
 
    // Extract the service name, the host, in this case.
    // Note that the DNS host name is used to compose the SPN. 
 
    ilen=_tcscspn((const TCHAR *)pszBind,TEXT(":"));
    _tcscpy(szServiceInstance,(const TCHAR *)(pszBind+ilen+1));
    RpcStringFree(&pszBind);
 
    // Set up the authentication info for the call
    //
    // First make the SPN, which is composed of the service class, 
    // the DN of the RPC Service object in the DS, and the service 
    // instance name, which is the DNS name of the host, 
    // which was extracted from the RPC binding string.
    rpcstatus = DsMakeSpn(
        szServiceClass,
        szDsEntryName,     // DN of the entry in RpcServices container
        szServiceInstance, // DNS name of the host for the service
        0,                 // Use the default port
        NULL,              // No referral host
        &ulSpn,            // Size, in bytes, of the szSpn buffer
        szSpn              // buffer to receive the SPN
        );
        
    _tprintf(TEXT("Client will present SPN %s\n"),szSpn);
 
    // Set up the RPC_SECURITY_QOS struct for mutual authentication.
    qos.Version             = RPC_C_SECURITY_QOS_VERSION;
    qos.Capabilities        = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
    qos.IdentityTracking    = RPC_C_QOS_IDENTITY_STATIC;
    qos.ImpersonationType   = RPC_C_IMP_LEVEL_IMPERSONATE;
 
    // Set the authentication information for this binding handle.
    // Specify the service principal name and the QOS information.
    // Ask for PKT_INTEGRITY to ensure that no-one has tampered 
    // with the traffic between the client and the authenticated
    // service. Failure to ask for at least PKT_INTEGRITY renders the 
    // mutual authentication effectively worthless because an attacker 
    // can steal and re-issue compromised packets. 
    // For greater security, request PKT_PRIVACY which 
    // also encrypts the traffic. PKT_INTEGRITY is a good compromise 
    // between security and performance - an attacker can see the 
    // traffic but not tamper with it.
    rpcstatus = RpcBindingSetAuthInfoEx(
                       RpcExample_IfHandle,
                       (TCHAR *)szSpn,
                       RPC_C_AUTHN_LEVEL_PKT_INTEGRITY,
                       RPC_C_AUTHN_GSS_NEGOTIATE,
                       NULL,
                       NULL,
                       &qos);
 
    // Now that mutual authentication parameters have been set, 
    // make RPC calls. 
    RpcTryExcept 
    {
        bResult = Shutdown();
        if (bResult) 
            _tprintf(TEXT("Shutdown: Service accepted call\n"));
        else
            _tprintf(TEXT("Shutdown: Service rejected call\n"));
 
        // Stop looping through the bindings.
        rpcstatus  = RPC_S_NO_MORE_BINDINGS; 
    } 
    RpcExcept(1) 
    {
        ulCode = RpcExceptionCode();
        _tprintf(TEXT("RPC exception 0x%lx = %ld\n"), ulCode, ulCode);
    }
    RpcEndExcept;
 
    // Free the imported binding
    RpcBindingFree(&RpcExample_IfHandle);
} 
 
// Discard the Import handle.
rpcstatus = RpcNsBindingImportDone(&hNs);
 
return;
}