WWSAPI to WCF interop 4: WSHttpBinding with username over transport security

WWSAPI doesn’t support full message mode security (where security negotiation happens at SOAP message level and parts of the envelope are signed and encrypted using XML signature and XML encryption) in Win7 time frame. This means the default WSHttpBinding is not interoperable with the WWSAPI’s security offering as the WSHttpBinding defaults to use full message mode security with secure conversation. WWSAPI supports mixed mode security that provides message integrity and confidentiality at transport level (e.g. through https). In mixed mode security, the client is authenticated through a token carried in the SOAP message’s security header. Client credential types supported by WWSAPI in Win7 include username/password token and Kerberos AP-REQ token. Now I’ll show how to use username/password token over https transport.

 

As explained in my previous post, WS_SECURITY_DESCRIPTION contains security bindings that describe the security mechanism to be applied to the message or channel. For https, you need to use WS_SSL_TRANSPORT_SECURITY_BINDING. To use username/password token on top of it, WS_USERNAME_MESSAGE_SECURITY_BINDING needs to be included as well. Inside WS_USERNAME_MESSAGE_SECURITY_BINDING, the binding usage should be WS_SUPPORTING_MESSAGE_SECURITY_USAGE, which means the binding is for client authentication only, not message protection. Then a username/password credential needs to be set in the binding so that it could be included in the messages. Here is how the WS_SECURITY_DESCRIPTION can be filled for this scenario.

 

    // declare and initialize a username credential

    WS_STRING_USERNAME_CREDENTIAL usernameCredential = {}; // zero out the struct

    WS_STRING userName = WS_STRING_VALUE(L"usr1");

    WS_STRING passWord = WS_STRING_VALUE(L"pwd1");

    usernameCredential.credential.credentialType = WS_STRING_USERNAME_CREDENTIAL_TYPE; // set the credential type

    usernameCredential.username = userName;

  usernameCredential.password = passWord;

   

    // declare and initialize a username message security binding

    WS_USERNAME_MESSAGE_SECURITY_BINDING usernameBinding = {}; // zero out the struct

    usernameBinding.binding.bindingType = WS_USERNAME_MESSAGE_SECURITY_BINDING_TYPE; // set the binding type

    usernameBinding.bindingUsage = WS_SUPPORTING_MESSAGE_SECURITY_USAGE; // set the binding usage

    usernameBinding.clientCredential = &usernameCredential.credential;

   

    // declare and initialize an SSL transport security binding

    WS_SSL_TRANSPORT_SECURITY_BINDING sslBinding = {}; // zero out the struct

    sslBinding.binding.bindingType = WS_SSL_TRANSPORT_SECURITY_BINDING_TYPE; // set the binding type

   

    // declare and initialize the array of all security bindings

    WS_SECURITY_BINDING* securityBindings[2] = { &sslBinding.binding, &usernameBinding.binding };

   

    // declare and initialize the security description

    WS_SECURITY_DESCRIPTION securityDescription = {}; // zero out the struct

    securityDescription.securityBindings = securityBindings;

    securityDescription.securityBindingCount = WsCountOf(securityBindings);

 

Then you pass the WS_SECURITY_DESCRIPTION structure to WsCreateServiceProxy:

    // Create the proxy

    hr = WsCreateServiceProxy(

            WS_CHANNEL_TYPE_REQUEST,

            WS_HTTP_CHANNEL_BINDING,

            &securityDescription, // security description

            NULL, // proxy properties

            0, // proxy property count

            NULL, // channel properties

            0, // channel property count

            &proxy,

            error);

    if (FAILED(hr))

    {

        goto Exit;

    }

 

Note: the corresponding WSHttpBinding object is created this way:

    WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);

    binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

    binding.Security.Message.EstablishSecurityContext = false;

    binding.Security.Message.NegotiateServiceCredential = false;

 

In config file, it’s presented in the following element:

      <wsHttpBinding>

        <binding name="UPOverHttps">

          <security mode="TransportWithMessageCredential">

            <message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false" />

          </security>

        </binding>

      </wsHttpBinding>