WWSAPI (Windows Web Services API) HTTP Kerberos Client

Further adventures with the WWSAPI Beta. If you find this useful please take the time to Jot me a quick comment or note!

I decided to investigate these API's and make a simple call to the same WebService in my previous blog post using Kerberos.  I first copied the WebService inside my network so I could use Kerberos and configured the IIS 6 WebSite to use Windows Integrated authentication only.

There are several examples of using Negotiate with SSL but I did not want to use SSL.  I figured all I needed to do was remove the SSL bindings from the samples...  WRONG!

Here are the other steps I took and the challenges I faced:

// use the default client credential if the thread opening the channel/proxy is impersonating,
// the thread token will be used; otherwise, the process token will be used
WS_DEFAULT_WINDOWS_INTEGRATED_AUTH_CREDENTIAL defaultCred = {}; // zero out the struct

defaultCred.credential.credentialType = WS_DEFAULT_WINDOWS_INTEGRATED_AUTH_CREDENTIAL_TYPE;

// declare and initialize properties to set the authentication scheme to Negotiate
// Since the default scheme for WWSAPI header authentication is Negotiate, this property
// may be omitted but included for clarity.
ULONG scheme = WS_HTTP_HEADER_AUTH_SCHEME_NEGOTIATE;

 

WS_SECURITY_BINDING_PROPERTY headerAuthBindingProperties [1] =
{
    { WS_SECURITY_BINDING_PROPERTY_HTTP_HEADER_AUTH_SCHEME, &scheme, sizeof(scheme) }
};

// declare and initialize an header authentication security binding
WS_HTTP_HEADER_AUTH_SECURITY_BINDING headerAuthBinding = {}; // zero out the struct

headerAuthBinding.binding.bindingType = WS_HTTP_HEADER_AUTH_SECURITY_BINDING_TYPE;
headerAuthBinding.binding.properties = headerAuthBindingProperties;
headerAuthBinding.binding.propertyCount = WsCountOf(headerAuthBindingProperties);
headerAuthBinding.clientCredential = &defaultCred.credential;

// declare and initialize the array of all security bindings
WS_SECURITY_BINDING* securityBindings[1] = { &headerAuthBinding.binding };

// declare and initialize the security description
WS_SECURITY_DESCRIPTION securityDescription = {}; // zero out the struct
securityDescription.securityBindings = securityBindings;
securityDescription.securityBindingCount = WsCountOf(securityBindings);
securityDescription.properties = securityProperties;
securityDescription.propertyCount = WsCountOf(securityProperties);

// Create the proxy

hr = WsCreateServiceProxy(
WS_CHANNEL_TYPE_REQUEST,
WS_HTTP_CHANNEL_BINDING,
&securityDescription, // added for Negotiate
NULL,
0,
NULL,
0,
&serviceProxy,
error);

This looked good but failed with an error when I called the WebService method:

E_INVALIDARG.

 

Further investigation revealed that I needed to set the WS_PROTECTION_LEVEL appropriately. Since the message is NOT using SSL we need to set this property as follows:

// set the protection level to none - you have to do this to avoid the error: E_INVALIDARG when you call a SOAP method
WS_PROTECTION_LEVEL protectionLevel = WS_PROTECTION_LEVEL_NONE;
WS_SECURITY_PROPERTY securityProperties[1] = {
{WS_SECURITY_PROPERTY_TRANSPORT_PROTECTION_LEVEL, &protectionLevel, sizeof(protectionLevel)}
};

and then set these on the securityDescription like this:

securityDescription.properties = securityProperties;
securityDescription.propertyCount = WsCountOf(securityProperties);

OK, home free right?  No...  Now I was getting this error from the call to _HelloWorld.

  hr 0x803d0000 The input data was not in the expected format or did not have the expected value.  

Since the 'input data' seemed to be the issue I took a Network Monitor trace and found that the difference between my anonymous test and this new Kerberos Test (other than the Negotiate headers etc...) was that the SOAP envelope looked totally different!  In the Kerberos case I found this:

<s:Envelope xmlns:a="https://www.w3.org/2005/08/addressing" xmlns:s="https://www.w3.org/2003/05/soap-envelope"><s:Header><a:Action s:mustUnderstand="1">https://tempuri.org/HelloWorld</a:Action><a:MessageID>urn:uuid:ba48938e-97a2-4dba-9e3f-cf9378533f8f</a:MessageID><a:To s:mustUnderstand="1">https://jsandershv2003/WebSite/MyWebService.asmx</a:To></s:Header><s:Body><HelloWorld xmlns="https://tempuri.org/"/></s:Body></s:Envelope>

 

Error:

 

System.Web.Services.Protocols.SoapHeaderException: SOAP header Action was not understood...  

at System.Web.Services.Protocols.SoapHeaderHandling.SetHeaderMembers(SoapHeaderCollection headers,

Object target,

SoapHeaderMapping[] mappings,

SoapHeaderDirection direction, Boolean client)..  

at System.Web.Services.Protocols.SoapServerProtocol.CreateServerInstance()..  

at System.Web.Services.Protocols.WebServiceHandler.Invoke().. 

 at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()

 

After investigation I found that to get rid of the Messaging headers I needed to set some channel properties:

WS_CHANNEL_PROPERTY channelProperties[1];
ULONG channelPropertyCount = 0;
WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
channelProperties[channelPropertyCount].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
channelProperties[channelPropertyCount].value = &addressingVersion ;
channelProperties[channelPropertyCount].valueSize = sizeof(addressingVersion );
channelPropertyCount++; // in this case only one property

// Then you pass the channelProperties and channelPropertyCount to WsCreateServiceProxy (or WsCreateChannel if you are working at channel layer).

hr = WsCreateServiceProxy(
WS_CHANNEL_TYPE_REQUEST,
WS_HTTP_CHANNEL_BINDING,
&securityDescription,

// added for Negotiate
NULL,
0,
channelProperties, // channel properties
channelPropertyCount, // channel property count
&serviceProxy,
error);

Success!

Now the complete code listing for your enjoyment (Copy Code):

// WWSAPITest.cpp : Defines the entry point for the console application.
//

#include

"stdafx.h"
#include <WebServices.h>
#include ".\wsdl\MyWebService.wsdl.h"
int mainHttpKerb(int argc, _TCHAR* argv[])
{
HRESULT hr;
WS_ERROR* error;
WS_HEAP* heap;
WS_SERVICE_PROXY* serviceProxy;
hr = S_OK;
error = NULL;
heap = NULL;
serviceProxy = NULL;
    // Creating error object
    hr = WsCreateError(NULL, 0, &error);
    if (FAILED(hr))
{
wprintf (L"Failed to create Error object\n");
        return -1;
}
    // Creating heap handle
    hr = WsCreateHeap(10000000, 0, NULL, 0, &heap, error);
    if (FAILED(hr))
{
wprintf (L"Failed to create Heap object\n");
        if (heap != NULL)
{
WsFreeHeap(heap);
heap = NULL;
}
        if (error != NULL)
{
WsFreeError(error);
error = NULL;
}
        return -1;
    }
    ////////////////kerberos
    //// declare and initialize a windows credential
    // use the default client credential if the thread opening the channel/proxy is impersonating,
    // the thread token will be used; otherwise, the process token will be used
    WS_DEFAULT_WINDOWS_INTEGRATED_AUTH_CREDENTIAL defaultCred = {}; // zero out the struct
    defaultCred.credential.credentialType = WS_DEFAULT_WINDOWS_INTEGRATED_AUTH_CREDENTIAL_TYPE;
    // declare and initialize properties to set the authentication scheme to Negotiate
    // Since the default scheme for WWSAPI header authentication is Negotiate, this property
    // may be omitted.
    ULONG scheme = WS_HTTP_HEADER_AUTH_SCHEME_NEGOTIATE;
    // set the protection level to none - you have to do this to avoid the error: E_INVALIDARG when you call a SOAP method
    WS_PROTECTION_LEVEL protectionLevel = WS_PROTECTION_LEVEL_NONE;
WS_SECURITY_PROPERTY securityProperties[1] = {
{WS_SECURITY_PROPERTY_TRANSPORT_PROTECTION_LEVEL, &protectionLevel, sizeof(protectionLevel)}
};

    WS_SECURITY_BINDING_PROPERTY headerAuthBindingProperties [1] =
{
{ WS_SECURITY_BINDING_PROPERTY_HTTP_HEADER_AUTH_SCHEME, &scheme, sizeof(scheme) }
};
    // declare and initialize an header authentication security binding
    WS_HTTP_HEADER_AUTH_SECURITY_BINDING headerAuthBinding = {}; // zero out the struct
    headerAuthBinding.binding.bindingType = WS_HTTP_HEADER_AUTH_SECURITY_BINDING_TYPE;
headerAuthBinding.binding.properties = headerAuthBindingProperties;
headerAuthBinding.binding.propertyCount = WsCountOf(headerAuthBindingProperties);
headerAuthBinding.clientCredential = &defaultCred.credential;
    // declare and initialize the array of all security bindings
    WS_SECURITY_BINDING* securityBindings[1] = { &headerAuthBinding.binding };
    // declare and initialize the security description
    WS_SECURITY_DESCRIPTION securityDescription = {}; // zero out the struct
    securityDescription.securityBindings = securityBindings;
securityDescription.securityBindingCount = WsCountOf(securityBindings);
securityDescription.properties = securityProperties;
securityDescription.propertyCount = WsCountOf(securityProperties);
    // size this array to the number of properties you will have
    WS_CHANNEL_PROPERTY channelProperties[1];
ULONG channelPropertyCount = 0;
WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
channelProperties[channelPropertyCount].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
channelProperties[channelPropertyCount].value = &addressingVersion ;
channelProperties[channelPropertyCount].valueSize = sizeof(addressingVersion );
channelPropertyCount++;

    // add more channel properties here
    /*WS_ENVELOPE_VERSION soapVersion = WS_ENVELOPE_VERSION_SOAP_1_1;
channelProperties[channelPropertyCount].id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION;
channelProperties[channelPropertyCount].value = &soapVersion;
channelProperties[channelPropertyCount].valueSize = sizeof(soapVersion);
channelPropertyCount++;*/

    // Then you pass the channelProperties and channelPropertyCount to WsCreateServiceProxy (or WsCreateChannel if you are working at channel layer).
    // Create the proxy
    hr = WsCreateServiceProxy(
WS_CHANNEL_TYPE_REQUEST,
WS_HTTP_CHANNEL_BINDING,
&securityDescription, // added for Negotiate
        NULL,
0,
channelProperties, // channel properties
        channelPropertyCount, // channel property count
        &serviceProxy,
error);

if (FAILED(hr))
{
WsFreeHeap(heap);
WsFreeError(error);
        return -1;
}
WS_ENDPOINT_ADDRESS address = {};

WS_STRING Url = WS_STRING_VALUE(L

"https://jsandershv2003/WebSite/MyWebService.asmx");

address.url = Url;
hr = WsOpenServiceProxy(serviceProxy, &address, NULL, error);

    if (FAILED(hr))
{
WsFreeServiceProxy(serviceProxy);
WsFreeHeap(heap);
WsFreeError(error);
     return -1;
}

WCHAR *aResult = NULL;

hr = WebServiceSoap12_HelloWorld(
serviceProxy,
&aResult,
heap,
NULL, 0,
NULL,
error);

    if (SUCCEEDED(hr))
    {
        wprintf(L"%s\n", aResult);
}
    else 
{
        wprintf(L"failed\n");
}

    if (serviceProxy != NULL)
{
WsCloseServiceProxy(serviceProxy, NULL, error);
WsFreeServiceProxy(serviceProxy);
serviceProxy = NULL;
}

    if (heap != NULL)
{
WsFreeHeap(heap);
heap = NULL;
}
    if (error != NULL)
{
WsFreeError(error);
error = NULL;
}

return 0;
}

int

_tmain(int argc, _TCHAR* argv[])
{   
    return mainHttpKerb(argc,argv);
}