Are OneWay Operations in WCF compatible with ClientCertificateMappingAuthorization?

249 Views Asked by At

I made a WCF with one void method and deployed it on IIS; it works fine (the service response is code 202) until I put it under SSL with Client Certificate Authentication: in this case the code behind the operation is not executed and the server response is 200. No Exception seems to be raised (No Failed Request is traced, no Errors on Events Viewer) but I can't get the execution of called method

Here's the WCF implementation and configuration:

namespace WcfTestService
{
    [ServiceContract]
    public interface IWcfTestService
    {
        [OperationContract(IsOneWay =true)]
        void OneWay(int value);

    }

    [ServiceBehavior]
    public class Service1 : IWcfTestService
    {
        [OperationBehavior]
        public void OneWay(int value)
        {
            Trace.TraceInformation(DateTime.Now.ToString() + " Oneway method invoked!" );
        }
    }
}


<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="WcfTestBinding" messageEncoding="Text" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxBufferPoolSize="50000000" maxReceivedMessageSize="50000000">
                    <readerQuotas maxDepth="500000000" maxStringContentLength="500000000" maxArrayLength="500000000" maxBytesPerRead="500000000" maxNameTableCharCount="500000000" />
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <services>
            <service name="WcfTestService.Service1" behaviorConfiguration="WcfTestBehaviors">
                <endpoint address="" binding="basicHttpBinding" bindingConfiguration="WcfTestBinding" contract="WcfTestService.IWcfTestService" />
                <endpoint contract="IMetadataExchange" binding="mexHttpsBinding" address="mex" />
            </service>
        </services>
        <protocolMapping>
            <add binding="wsHttpBinding" scheme="https" />
        </protocolMapping>

        <behaviors>
            <serviceBehaviors>
                <behavior name="WcfTestBehaviors">
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="true" />
                    <serviceThrottling maxConcurrentCalls="500" maxConcurrentInstances="500" maxConcurrentSessions="500" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
        <directoryBrowse enabled="true" />
        <security>
            <authentication>
                <anonymousAuthentication enabled="true" />
                <iisClientCertificateMappingAuthentication enabled="true">
                    <oneToOneMappings>
                        <clear />
                    </oneToOneMappings>
                </iisClientCertificateMappingAuthentication>
            </authentication>
            <authorization>
                <remove users="*" roles="" verbs="" />
                <add accessType="Allow" users="" roles="Users" />
            </authorization>
        </security>
        <tracing>
            <traceFailedRequests>
                <remove path="*" />
                <add path="*">
                    <traceAreas>
                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI" verbosity="Verbose" />
                    </traceAreas>
                    <failureDefinitions timeTaken="00:00:00" statusCodes="401.2,202" />
                </add>
            </traceFailedRequests>
        </tracing>
    </system.webServer>
    <system.diagnostics>
        <switches>
            <add name="DataMessagesSwitch" value="1" />
            <add name="TraceLevelSwitch" value="4" />
        </switches>
        <trace autoflush="true" indentsize="4">
            <listeners>
                <add name="WcfTestServiceTraceListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="logs\WcfTestService.txt" />
                <remove name="Default" />
            </listeners>
        </trace>
    </system.diagnostics>
</configuration>

The client code and its configuration:

static bool Test()
        {
            string certPath = @"C:\myCertName.pfx";
            string pwdValue = "myPassword";
            bool res = false;
            EndpointAddress newEP = new EndpointAddress("https://myservername/WcfTestService");
            BasicHttpsBinding newBind = new BasicHttpsBinding();

            newBind.Security.Mode = BasicHttpsSecurityMode.Transport;
            newBind.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

            srvRefTest.WcfTestServiceClient myWS = new srvRefTest.WcfTestServiceClient(newBind,newEP);

            System.Net.ServicePointManager.ServerCertificateValidationCallback +=
                            (se, cert, chain, sslerror) =>
                            {
                                return true;
                            };

            X509Certificate2 ccert = new X509Certificate2(certPath, pwdValue);
            myWS.ClientCredentials.ClientCertificate.Certificate = ccert;
            myWS.OneWay(1);
            return res;
        }

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IWcfTestService">
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate" />
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://myservername/WcfTestService/WcfTestService.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IWcfTestService"
                contract="srvRefTest.IWcfTestService" name="BasicHttpBinding_IWcfTestService" />
        </client>
    </system.serviceModel>
</configuration>        

On server logs I can see this call:

2019-01-11 14:45:01 W3SVC1 10.0.0.4 POST /WcfTestService - 443 SDI_user 130.0.139.146 - 200 0 0 187

where SDI_user is the name of the user specified in the section of manyToOneMapping in IIS as shown in picture: (https://drive.google.com/open?id=1gGN6HrIDC9u160FuWx6MBwgi7MW7ppRS)

3

There are 3 best solutions below

1
On

the best thing to do is to add WCF tracing both on the server and in the client side https://learn.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing this should give you details of what is happening. If needed post here the svclog file

1
On

I reviewed your configuration and wanted to know why not using client certificate in a standard way - didn't understand security mode - transport doing both transport and message security I suggest you follow the example from: https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication Use wsHttpBinding look here for comparison: https://www.codeproject.com/Articles/36396/Difference-between-BasicHttpBinding-and-WsHttpBind

0
On

thanks to everyone (expecially oshvartz); I just found the cause: in the client code I had to specify the endpoint address complete of .svc file. Here's the full code and configuration.

WCF implementation and configuration:

namespace WcfTestService
{
    [ServiceContract]
    public interface IWcfTestService
    {
        [OperationContract(IsOneWay =true)]
        [XmlSerializerFormat()]
        void OneWay(int value);
    }
}

namespace WcfTestService
{
     [ServiceBehavior]
    public class Service1 : IWcfTestService
    {

        [OperationBehavior]
        public void OneWay(int value)
        {
            Trace.TraceInformation(DateTime.Now.ToString() + " " + "Oneway method invoked! Parameter= " + value.ToString());
            Trace.Flush();
        }
    }
}

WCF Configuration:

<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WcfTestBinding" messageEncoding="Mtom" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxBufferPoolSize="50000000" maxReceivedMessageSize="50000000">
                    <readerQuotas maxDepth="500000000" maxStringContentLength="500000000" maxArrayLength="500000000" maxBytesPerRead="500000000" maxNameTableCharCount="500000000" />
                    <security mode="TransportWithMessageCredential">
                        <message clientCredentialType="Certificate" />
                        <transport clientCredentialType="Certificate" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <services>
            <service name="WcfTestService.Service1" behaviorConfiguration="WcfTestBehaviors">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="WcfTestBinding" contract="WcfTestService.IWcfTestService" />
                <endpoint contract="IMetadataExchange" binding="mexHttpsBinding" address="mex" />
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="WcfTestBehaviors">
                    <serviceMetadata httpsGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    </system.serviceModel>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
        <directoryBrowse enabled="true" />
        <security>
                        <authentication>
                <anonymousAuthentication enabled="true" />
            </authentication>
            <authorization>
                <remove users="*" roles="" verbs="" />
                <add accessType="Allow" users="myuser" />
            </authorization>
        </security>
        <tracing>
            <traceFailedRequests>
                <remove path="*" />
                <add path="*">
                    <traceAreas>
                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI" verbosity="Verbose" />
                    </traceAreas>
                    <failureDefinitions timeTaken="00:00:00" statusCodes="401-500" />
                </add>
            </traceFailedRequests>
        </tracing>
    </system.webServer>
   <system.diagnostics>
        <switches>
            <add name="DataMessagesSwitch" value="1" />
            <add name="TraceLevelSwitch" value="4" />
        </switches>
        <trace autoflush="true" indentsize="4">
            <listeners>
                <add name="WCFConsumerTraceListener" type="System.Diagnostics.TextWriterTraceListener" 
                     initializeData="logs\WcfTestService.txt" />
            </listeners>
        </trace>
    </system.diagnostics>
</configuration>

Client implementation:

static void Test1Remoto()
        {
            EndpointAddress newEP = new EndpointAddress("https://mydomain/WcfTestService/WcfTestService.svc");
            BasicHttpsBinding newBind = new BasicHttpsBinding();

            newBind.Security.Mode = BasicHttpsSecurityMode.Transport;
            newBind.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

            srvRefTest.WcfTestServiceClient myWS = new srvRefTest.WcfTestServiceClient(newBind,newEP);

            System.Net.ServicePointManager.ServerCertificateValidationCallback +=
                            (se, cert, chain, sslerror) =>
                            {
                                return true;
                            };

            X509Certificate2 ccert = new X509Certificate2(certPath, pwdValue);
            myWS.ClientCredentials.ClientCertificate.Certificate = ccert;
            myWS.OneWay(1);
            myWS.Close();
        }

Client configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IWcfTestService" messageEncoding="Mtom">
                    <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="None" />
                        <message clientCredentialType="Windows" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://mydomain/WcfTestService/WcfTestService.svc"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IWcfTestService"
                contract="srvRefTest.IWcfTestService" name="WSHttpBinding_IWcfTestService" />
        </client>
    </system.serviceModel>
</configuration>