0

I've got some sample code I've written trying to get a WCF Client talking to a WCF Server (not hosted in IIS) in a request / reply scenario. But I need to do it using certificates for authentication instead of user name / password. My test works fine when the computers have the same user name and password, but fails when I change the passwords to be different.

I've tried several different ideas, and none have worked. Can someone please tell me what I am doing wrong.

Here is my sample code: The WCF request reply library (Service1.cs):

using System;
namespace Test2ServLib
{
    public class Service1 : IService1
    {
        public CompositeTypeRsp GetDataUsingDataContract(CompositeTypeReq composite)
        {
            CompositeTypeRsp Rsp = new CompositeTypeRsp();
            if (composite == null)
            {
                Rsp.Value = "Hello {null} from " + Environment.MachineName;
                return Rsp;
            }
            Rsp.Value = "Hello " + composite.Value + " from " + Environment.MachineName;
            return Rsp;
        }
    }
}

Interface contract (IService1.cs):

using System;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace Test2ServLib
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        CompositeTypeRsp GetDataUsingDataContract(CompositeTypeReq composite);
    }

    [DataContract]
    public class CompositeTypeReq
    {
        string stringValue = "Hello ";
        [DataMember]
        public string Value
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }
    [DataContract]
    public class CompositeTypeRsp
    {
        string stringValue = "Hello ";
        [DataMember]
        public string Value
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }
}

Test application (Can be both server and client, Hoster.cs)

using System;
using System.ServiceModel;
using System.Threading;
using System.Windows.Forms;
using Test2ServLib;

namespace Test2HostApp
{
    public partial class Hoster : Form
    {
        public Hoster()
        {
            InitializeComponent();
            textBox1.Text = Environment.MachineName;
        }

        private void StartServer_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(runHost);
        }
        private static bool bRunning = false;
        private static object locker = new object();
        private static string proto = "https";// <-- this line changes between tests
        private void runHost(object state)
        {
            lock (locker)
            {
                if (bRunning)
                {
                    MessageBox.Show("Host already running");
                    return;
                }
                bRunning = true;
            }
            Uri baseAddress = new Uri($"{proto}://localhost:2202/Test2ServLib/Service1/");
            WSHttpBinding binding = new WSHttpBinding();
//            binding.Security.Mode = SecurityMode.Transport;
//            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
            try
            {
                using (ServiceHost host = new ServiceHost(typeof(Service1), baseAddress))
                {
                    host.Open();
                    MessageBox.Show("Host started");
                    while (bRunning == true)
                    {
                        Thread.Sleep(100);
                    }
                    host.Close();
                    MessageBox.Show("Host stopped");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex}");
            }
        }

        private void StopServer_Click(object sender, EventArgs e)
        {
            lock (locker)
            {
                bRunning = false;
                Thread.Sleep(100); // wait for the host to close
            }
        }

        private void ClientReq_Click(object sender, EventArgs e)
        {
            try
            {
                Uri baseAddress = new Uri($"{proto}://{textBox1.Text}:2202/Test2ServLib/Service1/");
                EndpointAddress address = new EndpointAddress(baseAddress);
                ChannelFactory<IService1> factory =
                            new ChannelFactory<IService1>("Test2ServLib.IService1", address); 
                var patientSvc = factory.CreateChannel();
                var rsp = patientSvc.GetDataUsingDataContract(new CompositeTypeReq { Value = "me" } );
                MessageBox.Show(rsp.Value);
            }
            catch(Exception ex)
            {
                MessageBox.Show($"error-{ex}");
            }
        }
    }
}

The designer code is:

namespace Test2HostApp
{
    partial class Hoster
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be     disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {    
            this.btnStart = new System.Windows.Forms.Button();
            this.btnStop = new System.Windows.Forms.Button();
            this.btnSend = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.btnStart.Location = new System.Drawing.Point(12, 12);
            this.btnStart.Name = "btn_Start";
            this.btnStart.Size = new System.Drawing.Size(75, 23);
            this.btnStart.TabIndex = 0;
            this.btnStart.Text = "Start";
            this.btnStart.UseVisualStyleBackColor = true;
            this.btnStart.Click += new System.EventHandler(this.StartServer_Click);
            // 
            // button2
            // 
            this.btnStop.Location = new System.Drawing.Point(12, 41);
            this.btnStop.Name = "btn_Stop";
            this.btnStop.Size = new System.Drawing.Size(75, 23);
            this.btnStop.TabIndex = 1;
            this.btnStop.Text = "Stop";
            this.btnStop.UseVisualStyleBackColor = true;
            this.btnStop.Click += new System.EventHandler(this.StopServer_Click);
            // 
            // button3
            // 
            this.btnSend.Location = new System.Drawing.Point(12, 70);
            this.btnSend.Name = "btn_Send";
            this.btnSend.Size = new System.Drawing.Size(75, 23);
            this.btnSend.TabIndex = 2;
            this.btnSend.Text = "Send";
            this.btnSend.UseVisualStyleBackColor = true;
            this.btnSend.Click += new System.EventHandler(this.ClientReq_Click);
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(93, 70);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(100, 22);
            this.textBox1.TabIndex = 3;
            // 
            // Hoster
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(282, 255);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.btnSend);
            this.Controls.Add(this.btnStop);
            this.Controls.Add(this.btnStart);
            this.Name = "Hoster";
            this.Text = "Hoster";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button btnStart;
        private System.Windows.Forms.Button btnStop;
        private System.Windows.Forms.Button btnSend;
        private System.Windows.Forms.TextBox textBox1;
    }
}

My first test, my protocol was set to "net.tcp" (the marked line in hoster.cs) My app.config file was the following:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="announcementBehavior" 
               name="Test2ServLib.Service1">
        <endpoint binding="netTcpBinding" 
                  bindingConfiguration="RequestReplyNetTcpBinding"
                  contract="Test2ServLib.IService1" />
        <host>
        </host>
      </service>
    </services>
    <client>
      <endpoint binding="netTcpBinding" 
                bindingConfiguration="StandardNetTcpBinding" 
                contract="Test2ServLib.IService1" 
                name="Test2ServLib.IService1" 
                behaviorConfiguration="LargeEndpointBehavior">
      </endpoint>
    </client>
    <behaviors>
      <serviceBehaviors>
        <behavior name="announcementBehavior">
          <!--The following behavior attribute is required to enable WCF serialization of large data sets -->
          <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
          <serviceDiscovery>
            <announcementEndpoints>
              <endpoint kind="announcementEndpoint"
                        address="net.tcp://localhost:8005/Announcement"
                        binding="netTcpBinding"
                        bindingConfiguration="RequestReplyNetTcpBinding"/>
            </announcementEndpoints>
          </serviceDiscovery>
          <serviceThrottling maxConcurrentCalls="1500"
                             maxConcurrentSessions="1500"
                             maxConcurrentInstances="1500"/>
          <serviceCredentials>
            <clientCertificate>
              <authentication certificateValidationMode="PeerTrust"/>
            </clientCertificate>
            <serviceCertificate findValue="WCfServer"
                                storeLocation="CurrentUser"
                                storeName="My"
                                x509FindType="FindBySubjectName" />
          </serviceCredentials>
        </behavior>

        <behavior name="discoveryBehavior">
          <serviceDiscovery>
          </serviceDiscovery>
          <serviceCredentials>
            <clientCertificate>
              <authentication certificateValidationMode="PeerTrust"/>
            </clientCertificate>
            <serviceCertificate findValue="WCfServer"
                                storeLocation="CurrentUser"
                                storeName="My"
                                x509FindType="FindBySubjectName" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="LargeEndpointBehavior">
          <!--The behavior is required to enable WCF deserialization of large data sets -->
          <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
          <clientCredentials>
            <clientCertificate findValue="WcfClient" 
                               x509FindType="FindBySubjectName"
                               storeLocation="CurrentUser" 
                               storeName="My" />
            <serviceCertificate>
              <authentication certificateValidationMode="PeerTrust"/>
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <netTcpBinding>
        <binding  name="StandardNetTcpBinding"
                 receiveTimeout="05:00:00"
                 openTimeout="00:00:59"
                 closeTimeout="00:00:59"
                 maxBufferPoolSize="524288"
                 maxBufferSize="25000000"
                 maxConnections="50"
                 maxReceivedMessageSize="25000000"
                 listenBacklog="1500"
                 sendTimeout="00:05:00">
          <security>
            <message clientCredentialType="Certificate"/>
          </security>

          <reliableSession ordered="false" 
                           inactivityTimeout="00:01:00" 
                           enabled="true" />
          <readerQuotas maxDepth="2147483647" 
                        maxStringContentLength="2147483647"
                        maxArrayLength="2147483647" 
                        maxBytesPerRead="2147483647"
                        maxNameTableCharCount="2147483647" />
        </binding>
        <binding  name="RequestReplyNetTcpBinding"
                 receiveTimeout="05:00:00"
                 openTimeout="00:00:59"
                 closeTimeout="00:00:59"
                 maxBufferPoolSize="524288"
                 maxBufferSize="25000000"
                 maxConnections="50"
                 maxReceivedMessageSize="25000000"
                 sendTimeout="00:05:00"
                 listenBacklog="1500">
          <reliableSession ordered="false" 
                           inactivityTimeout="00:01:00" 
                           enabled="true" />
          <readerQuotas maxDepth="2147483647" 
                        maxStringContentLength="2147483647"
                        maxArrayLength="2147483647" 
                        maxBytesPerRead="2147483647"
                        maxNameTableCharCount="2147483647" />
          <security >
            <message clientCredentialType="Certificate"/>
          </security>
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

With this, I can communication from one system to another, but both systems have to have the same user name and passwords. If I change the password on one system, I get:

error-System.ServiceModel.Security.SecurityNegotiationException: The server has rejected the client credentials. ---> System.Security.Authentication.InvalidCredentialException: The server has rejected the client credentials. ---> System.ComponentModel.Win32Exception: The logon attempt failed

So it looks like it's still using user name and password authentication. So I've tried it with https. I changed the protocol to "https", and changed my app config to the following:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
       <wsHttpBinding>
        <binding name="CertificateWithTransport" messageEncoding="Mtom">
         <security mode="TransportWithMessageCredential">
           <transport clientCredentialType="None" />
           <message clientCredentialType="Certificate" />
         </security>
        </binding>
        <binding name="CertificateWithTransportService" messageEncoding="Mtom" >
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="Certificate" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="Test2ServLib.Service1"
               behaviorConfiguration="credentialConfigService" >
        <endpoint address=""
                  binding="wsHttpBinding"
                  bindingConfiguration="CertificateWithTransportService"
                  contract="Test2ServLib.IService1" />
      </service>
    </services>
    <client>
      <endpoint address=""
                behaviorConfiguration="credentialConfigurationClient"
                binding="wsHttpBinding"
                bindingConfiguration="CertificateWithTransport"
                contract="Test2ServLib.IService1"
                name="Test2ServLib.IService1" />
    </client>
    <behaviors>
      <serviceBehaviors>
        <behavior name="credentialConfigService">
           <serviceCredentials>
             <clientCertificate>
               <authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" />
             </clientCertificate>
           </serviceCredentials>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="credentialConfigurationClient">
          <clientCredentials>
            <clientCertificate findValue="WCfServer"
                                storeLocation="CurrentUser"
                                storeName="My"
                                x509FindType="FindBySubjectName" />
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Now, which this change I can't even get the client to talk to the server when they are running on the same computer with the same account, I get:

error-System.ServiceModel.CommunicationException: An error occurred while making the HTTP request to https://vm-pjh-135-2c:2202/Test2ServLib/Service1/. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

What do I need to do to get either (or both) of these solutions working?

Paul Hurt
  • 1
  • 2
  • Side note, I created my certs using "makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=WCFServer -sky exchange -pe" And copied them around as needed. If I need to create the cert some other way, please let me know. This is Proof of Concept code, so self signed works for me. – Paul Hurt Sep 10 '18 at 16:23
  • Are you using WsHttp for the service binding and then netTcp for the client code? – mahlatse Sep 17 '18 at 13:16
  • Possible duplicate of [WCF Error "This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case"](https://stackoverflow.com/questions/2013880/wcf-error-this-could-be-due-to-the-fact-that-the-server-certificate-is-not-conf) – kenorb Oct 02 '18 at 16:32

0 Answers0