title: Error when using transport security with certificate in my self-hosted WCF service when using basicHttpBinding
body: <p>I have a self-hosted WCF service that uses net.tcp, but now, to support a legacy application we had to open a basicHttpBinding. The security is done through transport using certificates and it works well through net.tcp. The configuration for the service is done through its App.config:</p>
  
  <pre><code>  &lt;system.serviceModel&gt;
      &lt;services&gt;
        &lt;service name="MyServiceHost" behaviorConfiguration="ServiceBehavior"&gt;
      &lt;endpoint address="net.tcp://localhost:1234/MyService" binding="netTcpBinding" bindingConfiguration="CertificateCredentialsServiceBinding" name="MyService_Tcp_Certificate" contract="Wcf.MyService" /&gt;
      &lt;endpoint address="https://localhost:2234/MyService" binding="basicHttpBinding" bindingConfiguration="HttpCertificateCredentialsServiceBinding" name="MyService_BasicHttp_Certificate" contract="Wcf.MyService" /&gt;
      &lt;endpoint address="MyServiceMex" contract="IMetadataExchange" binding="mexTcpBinding" /&gt;
      &lt;host&gt;
        &lt;baseAddresses&gt;
          &lt;add baseAddress="http://localhost:2235/"/&gt;
          &lt;add baseAddress="net.tcp://localhost:1235/" /&gt;
        &lt;/baseAddresses&gt;
      &lt;/host&gt;
        &lt;/service&gt;
      &lt;/services&gt;
      &lt;bindings&gt;
        &lt;netTcpBinding&gt;
      &lt;binding name="CertificateCredentialsServiceBinding" receiveTimeout="00:30:00" sendTimeout="00:15:00"&gt;
        &lt;readerQuotas maxArrayLength="2147483647" /&gt;
        &lt;reliableSession inactivityTimeout="3.00:00:00" enabled="true" /&gt;
        &lt;security mode="Transport"&gt;
          &lt;transport clientCredentialType="Certificate" /&gt;
        &lt;/security&gt;
      &lt;/binding&gt;
        &lt;/netTcpBinding&gt;
        &lt;basicHttpBinding&gt;
      &lt;binding name="HttpCertificateCredentialsServiceBinding" receiveTimeout="00:30:00" sendTimeout="00:15:00"&gt;
        &lt;security mode="Transport"&gt;
          &lt;transport clientCredentialType="Certificate" /&gt;
        &lt;/security&gt;
      &lt;/binding&gt;
        &lt;/basicHttpBinding&gt;
      &lt;/bindings&gt;
      &lt;behaviors&gt;
        &lt;serviceBehaviors&gt;
      &lt;behavior name="ServiceBehavior"&gt;
        &lt;serviceTimeouts transactionTimeout="00:15:00" /&gt;
        &lt;serviceMetadata httpGetEnabled="true" httpGetUrl="" /&gt;
        &lt;serviceCredentials&gt;
          &lt;clientCertificate&gt;
            &lt;authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" trustedStoreLocation="LocalMachine" /&gt;
          &lt;/clientCertificate&gt;
          &lt;serviceCertificate storeLocation="LocalMachine" findValue="my-certificate-thumprint-here" x509FindType="FindByThumbprint" /&gt;
        &lt;/serviceCredentials&gt;
      &lt;/behavior&gt;
        &lt;/serviceBehaviors&gt;
      &lt;/behaviors&gt;
    &lt;/system.serviceModel&gt;
  </code></pre>
  
  <p>When the client connects using their own client certificate and NET.TCP, all works fine and the client credentials are validated correctly. This is the (simplified) code for that on the client side:</p>
  
  <pre><code>        MyService myService;
          Uri serviceUri = new Uri("net.tcp://localhost:1234/MyService");
  
          NetTcpBinding netTcpBinding = new NetTcpBinding(SecurityMode.Transport);
          netTcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
          netTcpBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
  
          Uri netTcpUri = serviceUri;
          EndpointAddress netTcpEndpoint = new EndpointAddress(netTcpUri, EndpointIdentity.CreateDnsIdentity("MyServer"));
  
          ChannelFactory&lt;MyService&gt; tempServicesFactory = null;
          ChannelFactory&lt;MyService&gt; servicesFactory = null;
  
          servicesFactory = new ChannelFactory&lt;MyService&gt;(netTcpBinding, netTcpEndpoint);
  
          servicesFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "my-client-certificate-thumprint-here");
          servicesFactory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
  
          myService = servicesFactory.CreateChannel();
  </code></pre>
  
  <p>All working well until here. Trying to connect to the Basic Http binding though fails miserably. This is the code in the client side:</p>
  
  <pre><code>        MyService myService;
          Uri serviceUri = new Uri("https://localhost:2234/MyService");
  
          BasicHttpBinding httpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
          httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
  
          Uri httpUri = serviceUri;
          EndpointAddress httpEndpoint = new EndpointAddress(httpUri, EndpointIdentity.CreateDnsIdentity("MyServer"));
  
          ChannelFactory&lt;MyService&gt; tempServicesFactory = null;
          ChannelFactory&lt;MyService&gt; servicesFactory = null;
  
          servicesFactory = new ChannelFactory&lt;MyService&gt;(httpBinding, httpEndpoint);
  
          servicesFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "my-client-certificate-thumprint-here");
          servicesFactory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
  
          myService = servicesFactory.CreateChannel();
  </code></pre>
  
  <p>With this code, when I try to make a call to the service (in this case a "GetAllProducts" method), I get the following exception:</p>
  
  <pre><code>System.ServiceModel.Security.SecurityNegotiationException was caught
    HResult=-2146233087
    Message=Could not establish trust relationship for the SSL/TLS secure channel with authority 'localhost:2234'.
    Source=mscorlib
    StackTrace:
      Server stack trace: 
         at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
         at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
         at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
         at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
         at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
         at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
         at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
      Exception rethrown at [0]: 
         at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
         at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&amp; msgData, Int32 type)
         at Wcf.MyService.GetAllProducts()
         at TesterApp.Initialise() in CODEFILE :line 159
    InnerException: System.Net.WebException
         HResult=-2146233079
         Message=The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
         Source=System
         StackTrace:
          at System.Net.HttpWebRequest.GetResponse()
          at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
         InnerException: System.Security.Authentication.AuthenticationException
          HResult=-2146233087
          Message=The remote certificate is invalid according to the validation procedure.
          Source=System
          StackTrace:
           at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, Exception exception)
           at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
           at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
           at System.Net.TlsStream.CallProcessAuthentication(Object state)
           at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
           at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
           at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
           at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)
           at System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)
           at System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size)
           at System.Net.ConnectStream.WriteHeaders(Boolean async)
          InnerException: 
  </code></pre>
  
  <p>Even though it looks like the certificate is invalid, I am sure it is valid since it works well with NET.TCP.</p>
  
  <p>Oh, and of course, before all of this worked I had to set up these with netsh.exe:</p>
  
  <pre><code>netsh.exe http add urlacl url=https://+:2234/ user=MYDOMAIN\MyUserName
  netsh.exe http add sslcert ipport=0.0.0.0:2234 certhash=my-certificate-thumprint-here appid={my-service-app-guid-here}
  </code></pre>
  
  <p>Any ideas of what the problem may be and how to work it out?</p>
  
  <p>Thanks!</p>
  
  <p><strong>UPDATE:</strong></p>
  
  <p>I have now managed to get rid of this first error, it was a case of changing all addresses to match the certificate name (MyServer) instead of using localhost. Unfortunately it looks like setting the DNS identity with <code>EndpointIdentity.CreateDnsIdentity("MyServer")</code> does not work for HTTP bindings even though it does work for NET.TCP.</p>
  
  <p>I am now getting a 403 (Forbidden) error when making a call to the service from the client. The client certificate is being set as you can see in my code above, but it is as if it wasn't. If I remove the configuration that expects client credential type to be certificate from my configuration (<code>&lt;transport clientCredentialType="Certificate" /&gt;</code> to <code>&lt;transport clientCredentialType="None" /&gt;</code>) then it all works with transport security, but I still need the client to provide credentials through a client certificate just like the NET.TCP solution.</p>
  
  <p>Any ideas?</p>
  
  <p>Thanks again!</p>