none
Can Windows Web Services API handle https with both server and client certificates? RRS feed

All replies

  • The answer is yes, WWSAPI can handle https with both server and client certificates.

    Hints: See the definitions:

     WS_HTTP_SSL_BINDING_TEMPLATE httpsBindingTemplate;
     WS_CUSTOM_CERT_CREDENTIAL  customCertCredential;
     WS_HTTP_SSL_POLICY_DESCRIPTION sslPolicyDescription;

      Use this:
    hr= ::WsCreateServiceProxyFromTemplate(
            WS_CHANNEL_TYPE_REQUEST,
            NULL, 0,
            WS_HTTP_SSL_BINDING_TEMPLATE_TYPE, //
            &httpsBindingTemplate, sizeof(WS_HTTP_SSL_BINDING_TEMPLATE),
            &sslPolicyDescription, sizeof(WS_HTTP_SSL_POLICY_DESCRIPTION),
            &serviceProxy, // Out
            error);

     Get and pass the certificate from a .P12 with:

    For a P12 that contains a client certificate and private key:

     HCERTSTORE WwsGenericClient::PFXImportCertStoreTCHAR( CRYPT_DATA_BLOB *PFX, const TCHAR * password )
     {
    #if defined UNICODE || defined _UNICODE
      return ::PFXImportCertStore( PFX, password, 0 );
    #endif
      // Have to convert char * password to wchar_t *
      size_t sizeInWords= 999;
      wchar_t wcstr[999];
    #ifdef UNICODE  
      size_t count= wcslen( password );
    #else
      size_t count= strlen( password );
    #endif  
      size_t returnValue;
      errno_t err= mbstowcs_s( &returnValue, wcstr, sizeInWords, (const char *)password, count );
      return ::PFXImportCertStore( PFX, wcstr, 0 );
     }

     

     // Use a .pfx certificate and private key
     // Note that the certificate must be associated with a private key to avoid a ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY error.
     //
     PCCERT_CONTEXT WwsGenericClient::getClientCertificateContext(
      const std::vector< unsigned char > & clientPfx,
      const TCHAR * szPassword )
     {
      // Convert a .pfx or .p12 file image to a Certificate store
      CRYPT_DATA_BLOB PFX;
      PFX.pbData= (BYTE *)&clientPfx[0];
      PFX.cbData= clientPfx.size();

      HCERTSTORE pfxStore=  PFXImportCertStoreTCHAR( &PFX, szPassword );
      if ( NULL == pfxStore ) throw std::pair<int,int>( __LINE__, ::GetLastError() );


      // Extract the certificate from the store and pass it to WinHttp
      PCCERT_CONTEXT  pcontext= NULL;
      return::CertEnumCertificatesInStore( pfxStore, pcontext );
      // NULL if No certificates in the store created from the pfx/p12
     }

    void getClientCertificateCallback(
    void* getCertCallbackState, // Pass a CERT_CONTEXT here
    const WS_ENDPOINT_ADDRESS* targetAddress, const WS_STRING* viaUri,
    const struct _CERT_CONTEXT** cert,
    WS_ERROR* error
    )
    {
    *cert= (CERT_CONTEXT *)getCertCallbackState; // Just use the certificate passed.
    }

    Tuesday, February 22, 2011 6:57 PM
  • Hello Andrew,

    Our Web Service client requirement is to present Client Certificate ( even 0 byte is fine) to Web Service .

    From your response I can see you are saying it is possible to support Server and client Certificates in Windows Web Services API's.

    Can you please point to code example / Segregate code snippet for supporting client certificate in we service client? In WWS API documentation I cloud not see any example giving this information.

    also in above example their no mention of property values to be used for WS_HTTP_SSL_BINDING_TEMPLATE, WS_CUSTOM_CERT_CREDENTIAL ?

    Thanks In Advance

    Wednesday, January 30, 2019 12:59 PM
  • You must have a valid certificate and associated private key for client certificate to work.   You can't fake it with dummy bytes.

    See https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_handshake   Section "Client-authenticated TLS handshake" to help you understand that the protocol uses the client certificate.  For example, in this step: 

    "The client sends a CertificateVerifymessage, which is a signature over the previous handshake messages using the client's certificate's private key. This signature can be verified by using the client's certificate's public key. This lets the server know that the client has access to the private key of the certificate and thus owns the certificate."

    I have not looked at this in years, so no guarantees at all.   But here is a class that I found.  Good luck.

    The "WwsGenericClient.h" file:

    #pragma once

    #pragma comment( lib, "webservices.lib" )
    #pragma comment( lib, "crypt32.lib" )
    //Include the header for Windows Web Service API
    #include "WebServices.h"
    #include <vector>
    #include <sstream>
    #include <string>
    #include <tchar.h>
    typedef HRESULT Specific_CreateServiceProxy( WS_HTTP_BINDING_TEMPLATE* templateValue, const WS_PROXY_PROPERTY* proxyProperties, const ULONG proxyPropertyCount, WS_SERVICE_PROXY** _serviceProxy, WS_ERROR* error);
    #include <stdio.h>
    //---------------------------

    // GET_CERT_CALLBACK
    // http://msdn.microsoft.com/en-us/library/dd401891(VS.85).aspx
    //
    void getClientCertificateCallback(
      void* getCertCallbackState,
      const WS_ENDPOINT_ADDRESS* targetAddress, const WS_STRING* viaUri, 
      const struct _CERT_CONTEXT** cert,
      WS_ERROR* error
            );
    //---------------------------
    // WwsGenericClient
    class WwsGenericClient
    {
    public:
     WwsGenericClient( Specific_CreateServiceProxy specific,
          TCHAR * urlOfService
          );
     WwsGenericClient( TCHAR * urlOfService,
           const std::vector< unsigned char > & clientPfx,
           const TCHAR * szPassword,
           WS_ENVELOPE_VERSION soapVersionIn = WS_ENVELOPE_VERSION_SOAP_1_1 );
           // Use WS_ENVELOPE_VERSION_SOAP_1_1 if the XML namespace is: 'http://schemas.xmlsoap.org/soap/envelope/'.
           // Use WS_ENVELOPE_VERSION_SOAP_1_2 if the XML namespace is: 'http://www.w3.org/2003/05/soap-envelope'.
     ~WwsGenericClient(void);
     std::vector<unsigned char> getVect( const WS_BYTES & bytesStore );
     WS_SERVICE_PROXY* getServiceProxy() { return serviceProxy; };
     WS_HEAP* getHeap() { return heap; };
     WS_ERROR* getError() { return error; };
     void setDateTime( WS_DATETIME * dt );
     void decodeError(HRESULT errorCode, WS_ERROR* error);
     std::wstring getStatus() { return status.str(); };
     bool serverTrial( WS_SERVICE_CONTRACT * serviceContract );
    protected:
     WS_SERVICE_PROXY* serviceProxy;
    private: // functions
     void createEndpointAddress( WS_ENDPOINT_ADDRESS & address, TCHAR * urlOfService, wchar_t wcstr[999] );
     void createErrorHeap();
     void createChannelProperties(  WS_CHANNEL_PROPERTIES & channelProperties, bool specifyEnvelopeVersion );
     void createSecurityProperties( WS_SECURITY_PROPERTIES & securityProperties );
     void createTransportSecurityBinding( WS_SSL_TRANSPORT_SECURITY_BINDING_TEMPLATE & security_binding_template,
               bool isClient, PCCERT_CONTEXT myCert  );
     void createServerTransportSecurityBinding( WS_SECURITY_BINDING_PROPERTIES & security_binding_properties );
     void createClientTransportSecurityBinding( WS_SECURITY_BINDING_PROPERTIES & security_binding_properties );
     int createServiceProxy();
     // Use a .pfx certificate and private key
     // Note that the certificate must be associated with a private key to avoid a ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY error.
     //
     PCCERT_CONTEXT getClientCertificateContext(
      const std::vector< unsigned char > & clientPfx,
      const TCHAR * szPassword );
     HCERTSTORE PFXImportCertStoreTCHAR( CRYPT_DATA_BLOB *PFX, const TCHAR * password );
     // Convert tchars into the equivalent wcstr
     //
     size_t WwsGenericClient::to_wchar_t( wchar_t wcstr[999], char * characters );

    private: // variables
     std::wstringstream status;
     HRESULT hr;
     WS_ERROR* error;
     WS_HEAP* heap;
     WS_HTTP_BINDING_TEMPLATE  httpBindingTemplate;
     WS_HTTP_SSL_BINDING_TEMPLATE httpsBindingTemplate;
     WS_CUSTOM_CERT_CREDENTIAL  customCertCredential;
     std::vector<WS_CHANNEL_PROPERTY> channelPropertiesVector;
     std::vector<WS_SECURITY_PROPERTY> securityPropertiesVector;
     std::vector<WS_SECURITY_BINDING_PROPERTY> securityBindingPropertiesVector;

     ULONG ignoreThese;
     BOOL requireClientCert;
     ULONG level;
     ULONG maxMessageSize;
     WS_ENVELOPE_VERSION soapVersion;
     WS_ADDRESSING_VERSION addressingVersion;
    };

    //--------------------------------------------

    The .CPP file

    #pragma once
    #pragma comment( lib, "webservices.lib" )

    // WwsGenericClient
    #include "WwsGenericClient.h"

    // GET_CERT_CALLBACK
    // http://msdn.microsoft.com/en-us/library/dd401891(VS.85).aspx
    //
    void getClientCertificateCallback(
      void* getCertCallbackState,   // Passes a CERT_CONTEXT
      const WS_ENDPOINT_ADDRESS* targetAddress, const WS_STRING* viaUri, 
      const struct _CERT_CONTEXT** cert,
      WS_ERROR* error
            )
    {
     *cert= (CERT_CONTEXT *)getCertCallbackState; // Just use the certificate passed.
    }
    //---------------------------

    // Create with the generated function and the URL.
    //
    WwsGenericClient::WwsGenericClient( Specific_CreateServiceProxy specific, TCHAR * urlOfService )
    {
     hr = S_OK;
     error = NULL;
     heap = NULL;
     serviceProxy = NULL;
     createErrorHeap();
     
     createChannelProperties( httpBindingTemplate.channelProperties, false );
     // creating Service Proxy using the changed binding template
     hr = specific(  &httpBindingTemplate, // WSHttpBinding_IArraySort_CreateServiceProxy
         NULL, 0,
         &serviceProxy,
         error);
     if (FAILED(hr)) {
      status << L"Failed to create Service Proxy\n";
      decodeError( hr, error );
      throw std::wstring( status.str() );
     }
     // Setting address to the service
     WS_ENDPOINT_ADDRESS address; wchar_t wcstr[999];
     createEndpointAddress( address, urlOfService, wcstr );

     // Opening Service Proxy with address choosen
     hr = WsOpenServiceProxy(serviceProxy, &address, NULL, error);
     if (FAILED(hr)) {
      status << L"Failed to open Service Proxy\n";
      decodeError( hr, error );
      throw std::wstring( status.str() );
     }

     if ( WS_ENVELOPE_VERSION_SOAP_1_1 == soapVersion ) {
      addressingVersion= WS_ADDRESSING_VERSION_1_0;
     }
     if ( WS_ENVELOPE_VERSION_SOAP_1_2 == soapVersion ) {
      addressingVersion= WS_ADDRESSING_VERSION_TRANSPORT;
     }
    }

    // Create with a client certificate
    //
    WwsGenericClient::WwsGenericClient( TCHAR * urlOfService,
             const std::vector< unsigned char > & clientPfx,
             const TCHAR * szPassword,
             WS_ENVELOPE_VERSION soapVersionIn )
     : soapVersion( soapVersionIn )
    {
     hr = S_OK;
     error = NULL;
     heap = NULL;
     serviceProxy = NULL;
     createErrorHeap();
     
     createChannelProperties( httpsBindingTemplate.channelProperties, true );
     createSecurityProperties( httpsBindingTemplate.securityProperties );
     createTransportSecurityBinding( httpsBindingTemplate.sslTransportSecurityBinding, true, getClientCertificateContext( clientPfx, szPassword ) );
     
     // Setting address to the service
     WS_ENDPOINT_ADDRESS address; wchar_t wcstr[999];
     createEndpointAddress( address, urlOfService, wcstr );
     WS_HTTP_SSL_POLICY_DESCRIPTION sslPolicyDescription;
     sslPolicyDescription.channelProperties= httpsBindingTemplate.channelProperties;  // WS_CHANNEL_PROPERTIES
     sslPolicyDescription.securityProperties= httpsBindingTemplate.securityProperties; // WS_SECURITY_PROPERTIES
     sslPolicyDescription.sslTransportSecurityBinding.securityBindingProperties=   // WS_SSL_TRANSPORT_SECURITY_BINDING_POLICY_DESCRIPTION
      httpsBindingTemplate.sslTransportSecurityBinding.securityBindingProperties;  
     sslPolicyDescription.channelProperties.propertyCount= 0;
     sslPolicyDescription.securityProperties.propertyCount= 0;
     sslPolicyDescription.sslTransportSecurityBinding.securityBindingProperties.propertyCount= 0;
     
     hr= ::WsCreateServiceProxyFromTemplate(
            WS_CHANNEL_TYPE_REQUEST,
            NULL, 0,
      WS_HTTP_SSL_BINDING_TEMPLATE_TYPE, // NEW
            &httpsBindingTemplate, sizeof(WS_HTTP_SSL_BINDING_TEMPLATE),
            &sslPolicyDescription, sizeof(WS_HTTP_SSL_POLICY_DESCRIPTION),
            &serviceProxy, // Out
            error);
     if (FAILED(hr)) {
      status << L"Failed to create Service Proxy\n";
      decodeError( hr, error );
      throw std::wstring( status.str() );
     }

     // Opening Service Proxy with address choosen
     hr = WsOpenServiceProxy(serviceProxy, &address, NULL, error);
     if (FAILED(hr)) {
      status << L"Failed to open Service Proxy\n";
      decodeError( hr, error );
      throw std::wstring( status.str() );
     }
    }
    void WwsGenericClient::createEndpointAddress( WS_ENDPOINT_ADDRESS & address, TCHAR * urlOfService, wchar_t wcstr[999] )
    {
     address.url.length = _tcslen( urlOfService );
    #if defined UNICODE || defined _UNICODE
     address.url.chars = urlOfService;
    #else
     // Have to convert char * password to wchar_t *
     size_t count= to_wchar_t( wcstr, (char *)urlOfService );
     address.url.chars = wcstr;
    #endif
     address.extensions= NULL;
     address.headers= NULL;
     address.identity= NULL;
    }
     // WS_STRING url = WS_STRING_VALUE( urlOfService ); // example: L"http://localhost:8080/FastSortService"
    //----------------------
    #define CHANGE_CHANNEL_PROPERTIES

    // SOAP 1.2 uses content type application/soap+xml, whereas SOAP 1.1 uses content type text/xml.
    // If you get use the wrong one, you get: Failure errorCode=0x803d0013 "The body of the received message contained a fault."
    //
    void WwsGenericClient::createChannelProperties( WS_CHANNEL_PROPERTIES & channelProperties, bool specifyEnvelopeVersion )
    {
     channelProperties.propertyCount = 0;
     WS_CHANNEL_PROPERTY prop;
     // defining the desirable maximum size for messages
     maxMessageSize = 2147483647;
     //changing a channel property
     prop.id = WS_CHANNEL_PROPERTY_MAX_BUFFERED_MESSAGE_SIZE;
     prop.value = &maxMessageSize;
     prop.valueSize = sizeof(maxMessageSize);
     channelPropertiesVector.push_back( prop );
     if ( specifyEnvelopeVersion ) {
      // Change the default soap version per "soapVersion" set by the constructor
      // WS_ENVELOPE_VERSION_SOAP_1_1  The XML namespace for this version is: 'http://schemas.xmlsoap.org/soap/envelope/'.
      // WS_ENVELOPE_VERSION_SOAP_1_2  The XML namespace for this version is: 'http://www.w3.org/2003/05/soap-envelope'.
      //
      prop.id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION; // 5
      prop.value = &soapVersion;      
      prop.valueSize = sizeof(soapVersion);
      channelPropertiesVector.push_back( prop );
      // Can't specify WS_CHANNEL_PROPERTY id '5' in both user template and policy description.
      // WS_ADDRESSING_VERSION_0_9 or WS_ADDRESSING_VERSION_1_0 or WS_ADDRESSING_VERSION_TRANSPORT
      addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;     //
      prop.id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
      prop.value = &addressingVersion;      
      prop.valueSize = sizeof(WS_ADDRESSING_VERSION);
      channelPropertiesVector.push_back( prop );
     }
     channelProperties.properties = channelPropertiesVector.data();
     channelProperties.propertyCount= channelPropertiesVector.size();
    }
    void WwsGenericClient::createSecurityProperties( WS_SECURITY_PROPERTIES & securityProperties )
    {
     //changing the security properties
     WS_SECURITY_PROPERTY prop;
     level= WS_PROTECTION_LEVEL_SIGN_AND_ENCRYPT;
     prop.id = WS_SECURITY_PROPERTY_TRANSPORT_PROTECTION_LEVEL;
     prop.value = &level; // http://blogs.msdn.com/b/haoxu/archive/2009/10/22/common-wwsapi-errors-property-value-set-incorrectly.aspx
     prop.valueSize = sizeof(level);
     securityPropertiesVector.push_back( prop );
     
     securityProperties.properties= securityPropertiesVector.data();
     securityProperties.propertyCount = securityPropertiesVector.size();
    }
    // Server side
    //
    void WwsGenericClient::createServerTransportSecurityBinding( WS_SECURITY_BINDING_PROPERTIES & security_binding_properties )
    {
     WS_SECURITY_BINDING_PROPERTY prop;
     requireClientCert= TRUE;
     prop.id= WS_SECURITY_BINDING_PROPERTY_REQUIRE_SSL_CLIENT_CERT;
     prop.value= &requireClientCert;
     prop.valueSize= sizeof(BOOL);
     securityBindingPropertiesVector.push_back( prop );
     security_binding_properties.properties= securityBindingPropertiesVector.data();
     security_binding_properties.propertyCount= securityBindingPropertiesVector.size();
    }

    // Client side
    //
    void WwsGenericClient::createClientTransportSecurityBinding( WS_SECURITY_BINDING_PROPERTIES & security_binding_properties )
    {
     WS_SECURITY_BINDING_PROPERTY prop;
     ignoreThese= WS_CERT_FAILURE_CN_MISMATCH | WS_CERT_FAILURE_INVALID_DATE | WS_CERT_FAILURE_UNTRUSTED_ROOT
          | WS_CERT_FAILURE_WRONG_USAGE | WS_CERT_FAILURE_REVOCATION_OFFLINE; // WS_CERT_FAILURE enum
     prop.id= WS_SECURITY_BINDING_PROPERTY_CERT_FAILURES_TO_IGNORE;
     prop.value= &ignoreThese;
     prop.valueSize= sizeof(ULONG);
     securityBindingPropertiesVector.push_back( prop );
     security_binding_properties.properties= securityBindingPropertiesVector.data();
     security_binding_properties.propertyCount= securityBindingPropertiesVector.size();
    }
    void WwsGenericClient::createTransportSecurityBinding(
        WS_SSL_TRANSPORT_SECURITY_BINDING_TEMPLATE & security_binding_template,
        bool isClient, PCCERT_CONTEXT myCert )
    {
     if ( isClient ) {
      createClientTransportSecurityBinding( security_binding_template.securityBindingProperties );
      // The local certificate credential to be used with this security binding.
      // Server side: When SSL is used for transport security with WS_HTTP_CHANNEL_BINDING,
      // the server certificate must be registered by the application using the HttpCfg.exe
      // and this field must be set to NULL. In all other cases, the server SSL certificate must be specified using this field.
      // Client side: If a client certificate is to be used with SSL, it must be specified using "localCertCredential".
      // If no client certificate is to be used, this field must be set to NULL.
      // Trick: Have a WS_CERT_CREDENTIAL inside a WS_CUSTOM_CERT_CREDENTIAL
      // Then use the .credentialType to know to cast to WS_CUSTOM_CERT_CREDENTIAL *
      security_binding_template.localCertCredential= &customCertCredential.credential;
      customCertCredential.credential.credentialType= WS_CUSTOM_CERT_CREDENTIAL_TYPE;
      customCertCredential.getCertCallback= ( WS_GET_CERT_CALLBACK) &getClientCertificateCallback; // The Callback to get the certificate
      customCertCredential.getCertCallbackState= (void *)myCert;   // The state to be passed when invoking the callback.
      customCertCredential.certIssuerListNotificationCallback= NULL;
      customCertCredential.certIssuerListNotificationCallbackState= NULL;
     } else {
      // Server side
      createServerTransportSecurityBinding( security_binding_template.securityBindingProperties );
     }
    }
    WwsGenericClient::~WwsGenericClient(void)
    {
     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;
     }
    }

    void WwsGenericClient::createErrorHeap()
    {
     // Creating error object
     hr = WsCreateError(NULL, 0, &error);
     if (FAILED(hr))
     {
      status << L"Failed to create Error object\n";
      throw std::wstring( status.str() );
     }

     //  Creating heap  handle
     hr = WsCreateHeap(10000000, 0, NULL, 0, &heap, error);
     if (FAILED(hr))
     {
      status << L"Failed to create Heap object\n";
      decodeError(hr, error);
      throw std::wstring( status.str() );
     }
    }

    // Set timestamp
    //
    void WwsGenericClient::setDateTime( WS_DATETIME * dt )
    {
      FILETIME ft;
      ::GetSystemTimeAsFileTime( & ft );
      WsFileTimeToDateTime( &ft, dt, error );
    }

    // Print out rich error info
    //
    void WwsGenericClient::decodeError( HRESULT errorCode, WS_ERROR* error )
    {
     status << L"Failure errorCode=0x" << std::hex << errorCode << std::dec << std::endl << std::endl;
     switch( errorCode ) {
     case WS_E_INVALID_FORMAT: status << L"The input data was not in the expected format or did not have the expected value." << std::endl;
      // -typically means that certain contract (API contract, network protocol contract, etc.) is not followed.
      break;
     case WS_E_INVALID_OPERATION: status << L"The operation is not allowed due to the current state of the object." << std::endl;
      break;
     case WS_E_QUOTA_EXCEEDED: status << L"A quota was exceeded." << std::endl;    
      break;
     case WS_E_OPERATION_ABANDONED: status << L"The operation was abandoned." << std::endl;
      break;
     case WS_E_OPERATION_TIMED_OUT: status << L"The operation did not complete within the time allotted." << std::endl;
      break;
     case E_OUTOFMEMORY: status << L"Insufficient memory to complete the operation." << std::endl;
      break;
     case E_INVALIDARG: status << L"One or more arguments are invalid." << std::endl;
      break;
     }
     status << std::endl;
     
        HRESULT hr = NOERROR;
        if (error != NULL)
        {
            ULONG errorCount;
            hr = WsGetErrorProperty(error, WS_ERROR_PROPERTY_STRING_COUNT, &errorCount, sizeof(errorCount));
            if ( ! FAILED(hr)) {
       for (ULONG i = 0; i < errorCount; i++) {
        WS_STRING string;
        hr = WsGetErrorString(error, i, &string);
        if (FAILED(hr)) break;
        std::wstring errmsg( string.chars, string.length );
        status << errmsg << std::endl << std::endl;
       }
            }
     }
        if (FAILED(hr))
        {
      status << L"Could not get error string (errorCode=0x" << std::hex << hr << std::dec << std::endl << std::endl;
        }
    }

    std::vector<unsigned char> WwsGenericClient::getVect( const WS_BYTES & bytesStore )
    {
     return std::vector<unsigned char>( bytesStore.bytes, &bytesStore.bytes[ bytesStore.length ] );
    }

     // Convert characters into the equivalent wchar_t array
     //
     size_t WwsGenericClient::to_wchar_t( wchar_t wcstr[999], char * characters )
     {
      size_t sizeInWords= 999;
      size_t count= strlen( characters );
      size_t errcode;
      errno_t err= mbstowcs_s( &errcode, wcstr, sizeInWords, characters, count );
      return count;
     }

     HCERTSTORE WwsGenericClient::PFXImportCertStoreTCHAR( CRYPT_DATA_BLOB *PFX, const TCHAR * password )
     {
    #if defined UNICODE || defined _UNICODE
      return ::PFXImportCertStore( PFX, password, 0 );
    #else
      // Have to convert char * password to wchar_t *
      wchar_t wcstr[999];
      size_t count= to_wchar_t( wcstr, (char *)password );
      return ::PFXImportCertStore( PFX, wcstr, 0 );
    #endif
     }
     // Use a .pfx certificate and private key
     // Note that the certificate must be associated with a private key to avoid a ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY error.
     //
     PCCERT_CONTEXT WwsGenericClient::getClientCertificateContext(
      const std::vector< unsigned char > & clientPfx,
      const TCHAR * szPassword )
     {
      // Convert a .pfx or .p12 file image to a Certificate store
      CRYPT_DATA_BLOB PFX;
      PFX.pbData= (BYTE *)&clientPfx[0];
      PFX.cbData= clientPfx.size();
      HCERTSTORE pfxStore=  PFXImportCertStoreTCHAR( &PFX, szPassword );
      if ( NULL == pfxStore ) throw std::pair<int,int>( __LINE__, ::GetLastError() );

      // Extract the certificate from the store and pass it to WinHttp
      PCCERT_CONTEXT  pcontext= NULL;
      return::CertEnumCertificatesInStore( pfxStore, pcontext );
      // NULL if No certificates in the store created from the pfx/p12
     }

    /*

     // Part 3 Step 2 - Point out implementation of operations for this service
     WSHttpBinding_IArraySortFunctionTable arraySortFunctions = {
       QuickSort,
       InsertionSort };
    */

    bool WwsGenericClient::serverTrial( WS_SERVICE_CONTRACT * serviceContract )
    {
    /*
     // Part 3 Step 2 - Create an error object for storing rich error information
     WS_ERROR* error = NULL;
     HRESULT hr = WsCreateError( NULL, 0, &error);
     if (FAILED(hr))
     {
      wprintf(L"Failed to create an error object\n");
      return -1;
     }
     
     // Part 3 Step 2 - Creating a heap object to help with data serialization
     WS_HEAP* heap = NULL;
     hr = WsCreateHeap( 100000, 0, NULL, 0, &heap, error);
     if (FAILED(hr))
     {
      wprintf(L"Failed to create an heap object\n");
      decodeError( hr, error );
      WsFreeError(error);
      return false;
     }

     // Part 3 Step 2 - Specifying the address where service will be located
     WS_STRING url = WS_STRING_VALUE(L"http://localhost:8080/FastSortService");
     // Part 8 Step 2 - Increase maximum size of messages the service accepts
     WS_HTTP_BINDING_TEMPLATE templateValue = {};
     ULONG maxMessageSize = 100000;
     WS_CHANNEL_PROPERTY channelProperty[1];
     channelProperty[0].id = WS_CHANNEL_PROPERTY_MAX_BUFFERED_MESSAGE_SIZE;
     channelProperty[0].value = &maxMessageSize;
     channelProperty[0].valueSize = sizeof(maxMessageSize);
     WS_CHANNEL_PROPERTIES channelProperties;
     channelProperties.properties = channelProperty;
     channelProperties.propertyCount = 1;
     templateValue.channelProperties = channelProperties;
     SIZE_T maxHeapSize = 200000;
     WS_SERVICE_ENDPOINT_PROPERTY endpointProperty[1];
     endpointProperty[0].id = WS_SERVICE_ENDPOINT_PROPERTY_BODY_HEAP_MAX_SIZE;
     endpointProperty[0].value = &maxHeapSize;
     endpointProperty[0].valueSize = sizeof(maxHeapSize);
     // Part 3 Step 1 - Create endpoint using pregenerated template
     WS_SERVICE_ENDPOINT* serviceEndpoint;
     hr = WSHttpBinding_IArraySort_CreateServiceEndpoint(&templateValue,
                  &url,
                  &arraySortFunctions ,
                  NULL,
                  endpointProperty, 1,
                  heap,
                  &serviceEndpoint,
                  error);

     hr= ::WsCreateServiceEndpointFromTemplate(
            WS_CHANNEL_TYPE_REPLY,
            endpointProperties,
            endpointPropertyCount,
            address,
            &serviceContract,
            authorizationCallback,
            heap,
            WS_HTTP_BINDING_TEMPLATE_TYPE,
            templateValue,
            templateValue == NULL ? 0 : sizeof(WS_HTTP_BINDING_TEMPLATE),
            &sortservice_wsdl.policies.WSHttpBinding_IArraySort,
            sizeof(sortservice_wsdl.policies.WSHttpBinding_IArraySort),
            serviceEndpoint,
            error);

     if (FAILED(hr))
     {
      wprintf(L"Failed to create endpoints on the service\n");
      decodeError( hr, error );
      WsFreeHeap(heap);
      WsFreeError(error);
      return false;
     }
     // Part 4 Step 1 - Creating Service Host for this service
     WS_SERVICE_HOST* host = NULL;
     const WS_SERVICE_ENDPOINT* serviceEndpoints[1];
     serviceEndpoints[0]= serviceEndpoint;
     hr = WsCreateServiceHost( serviceEndpoints, 1,
            NULL, 0,
            &host,
            error);
     if (FAILED(hr))
     {
      wprintf(L"Failed to create Service Host\n");
      decodeError( hr, error );
      WsFreeHeap(heap);
      WsFreeError(error);
      return false;
     }
     // Part 4 Step 2 - Start listeners on the service host
     hr = WsOpenServiceHost(host, NULL, error);
     if (FAILED(hr))
     {
      wprintf(L"Failed to open the service host\n");
      decodeError( hr, error );
      WsFreeServiceHost(host);
      WsFreeHeap(heap);
      WsFreeError(error);
      return -1;
     }
     // Running service until user stops it
     wprintf(L"SortService using WWSAPI is up and running...\n");
     wprintf(L"Press Enter to exit the service...\n");
     _getch();
     // Part 4 Step 3 - Closing and freeing handles
     WsCloseServiceHost(host, NULL, error);
     WsFreeServiceHost(host);
     WsFreeHeap(heap);
     WsFreeError(error);
    */
     return 0;
    }

    /*
    struct WS_HTTP_SSL_BINDING_TEMPLATE {
      WS_CHANNEL_PROPERTIES                      channelProperties;
      WS_SECURITY_PROPERTIES                     securityProperties;
      WS_SSL_TRANSPORT_SECURITY_BINDING_TEMPLATE sslTransportSecurityBinding;
    };
    struct WS_SSL_TRANSPORT_SECURITY_BINDING_TEMPLATE {
      WS_SECURITY_BINDING_PROPERTIES securityBindingProperties;
      WS_CERT_CREDENTIAL*            localCertCredential;
    };

    struct WS_CERT_CREDENTIAL {
      WS_CERT_CREDENTIAL_TYPE credentialType;
    };
    enum WS_CERT_CREDENTIAL_TYPE {
      WS_SUBJECT_NAME_CERT_CREDENTIAL_TYPE   = 1,
      WS_THUMBPRINT_CERT_CREDENTIAL_TYPE     = 2,
      WS_CUSTOM_CERT_CREDENTIAL_TYPE         = 3
    };
    enum WS_CERT_CREDENTIAL_TYPE {
      WS_SUBJECT_NAME_CERT_CREDENTIAL_TYPE   = 1,
         struct WS_SUBJECT_NAME_CERT_CREDENTIAL {
           WS_CERT_CREDENTIAL credential;
           ULONG              storeLocation;
           WS_STRING          storeName;
           WS_STRING          subjectName;
         };
      WS_THUMBPRINT_CERT_CREDENTIAL_TYPE     = 2,
      WS_CUSTOM_CERT_CREDENTIAL_TYPE         = 3
         struct WS_CUSTOM_CERT_CREDENTIAL {
           WS_CERT_CREDENTIAL   credential;
           WS_GET_CERT_CALLBACK getCertCallback;
           void*                getCertCallbackState;
         };

    };

    struct WS_SECURITY_BINDING_PROPERTIES {
      WS_SECURITY_BINDING_PROPERTY* properties;
      ULONG                         propertyCount;
    };
    struct WS_SECURITY_BINDING_PROPERTY {
      WS_SECURITY_BINDING_PROPERTY_ID id;       // See http://msdn.microsoft.com/en-us/library/dd323386(v=VS.85).aspx
      void*                           value;
      ULONG                           valueSize;
    };
    Helpful URLS
    http://blogs.msdn.com/b/nikolad/archive/2009/04/06/connecting-c-c-and-web-services.aspx
    http://blogs.msdn.com/b/haoxu/archive/2009/04/30/one-time-set-up-for-wwsapi-security-examples.aspx
    http://blogs.msdn.com/b/haoxu/archive/2008/11/18/wwsapi-to-wcf-interop-4-wshttpbinding-with-username-over-transport-security.aspx
    http://blogs.msdn.com/b/haoxu/archive/2008/12/02/wwsapi-to-wcf-interop-nettcpbinding-with-transport-security.aspx
    http://blogs.msdn.com/b/haoxu/archive/2009/04/09/403-forbidden-due-to-client-certificate-issue.aspx

    HttpClientWithSslExample  http://msdn.microsoft.com/en-us/library/dd323351(v=VS.85).aspx
    Policy Support     http://msdn.microsoft.com/en-us/library/dd815308(VS.85).aspx
    */

    Thursday, January 31, 2019 12:34 AM
  • Hello Andrew,

    Thanks for providing sample code for Generic class . I am able to compile this code successfully.

    I can see their is constructor as below provided by WwsGenericClient class which creates client certificate however I will need to understand more how to pass these parameters like clientPfx etc.

    // Create with a client certificate
    //
    WwsGenericClient::WwsGenericClient(TCHAR * urlOfService,
     const std::vector< unsigned char > & clientPfx,
     const TCHAR * szPassword,
     WS_ENVELOPE_VERSION soapVersionIn)

    Do you have any sample which uses this ( i.e. main file) generic client class to connect to any https  web service using client certificate?

    Monday, February 4, 2019 12:40 PM