I created a HTTP wcf service that will be consumed by a windows client and utilized. I had no issue until I was using HTTP. Now my customer wants to change the site to HTTPS. So for development purpose I have used IIS express certificate and setup the service. Now my service is up and running in IIS with iisexpress self signed certificate. I am able to browse my new https service via browser as well as wcf test client tool.
But when I try invoke the method from my windows application to new https wcf service. My service instance is created but only during invoking a method I get following error:
System.ServiceModel.ActionNotSupportedException was unhandled by user code
HResult=-2146233087
Message=The message with Action 'http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequence' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).
In service my binding config (config 3 is being used at https mode and config 2 was used during http mode) are as follows:
<bindings>
<wsHttpBinding>
<binding name="wsHttpBinding_IPGPService">
<!--config test 1 - start -->
<!--<security mode="None">
<transport clientCredentialType="None" />
<message establishSecurityContext="false" />
</security>-->
<!--config test 1 - end -->
<!--config test 2 - start -->
<!--<security mode="None" />
<reliableSession enabled="true" />-->
<!--config test 2 - end -->
<!--config test 3 - start -->
<!--<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None"/>
<message clientCredentialType="Certificate" algorithmSuite="Default" />
</security>-->
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
<!--config test 3 - end -->
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="PGPService.PGPService">
<endpoint address="" binding="wsHttpBinding" contract="PGPService.IPGPService" bindingConfiguration="wsHttpBinding_IPGPService" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<!--<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/> -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<!--<serviceDebug includeExceptionDetailInFaults="false"/>-->
<!--test config - start-->
<serviceDebug includeExceptionDetailInFaults="True" />
<!--test config - end-->
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
In client we use custom binding to create service instance.
url = "https://localhost:550/TestService/TestService.svc";
customBinding = new CustomBinding();
customBinding.Elements.Add(new ReliableSessionBindingElement());
customBinding.Elements.Add(new HttpsTransportBindingElement());
EndpointAddress endpointAdress = new EndpointAddress(url);
pgpServiceClientInstance = new PGPServiceClient(customBinding, endpointAdress);
pgpServiceClientInstance.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.Root,
X509FindType.FindByThumbprint,
"03815c894b62dcf2d17336ade2d9ca61ddb7f92c");
After adding service reference the app.config generated on my Windows application is as follows:
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IPGPService">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://localhost:550/TestService/TestService.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IPGPService"
contract="PGPServiceReference.IPGPService" name="WSHttpBinding_IPGPService" />
</client>
So now I can see my service instance is getting successfully created. but on the time of invoking a method I get above error.
There is no code changed before and after moving to https on service end. It is the same service code. It was completely working fine on http but is broken on https.
I have completely removed the service reference and added it newly after making my wcf service https. IIS express self signed certificate is installed and available already on my system. Both service and client are running in same system as of now since it is development
I am suspecting I am having a configuration problem....but with less experience I could not figure out.
Actually it was an additional binding during service instantiation that was causing the problem. Got it sorted by commenting below line in instantiation part.
url = "https://localhost:550/TestService/TestService.svc";
customBinding = new CustomBinding();
//customBinding.Elements.Add(new ReliableSessionBindingElement());
customBinding.Elements.Add(new HttpsTransportBindingElement());
This helped me to proceed with developing the entire service and client.
So that concludes my fix for above issue.
Further down the lane since i am a newbie to wcf service i faced following problems which i faced and solved. Someone who is struggling like me can use below step if you are facing my errors
Next my service had problems accessing folder
Fix: add seperate application pool with an service or administrator account with enough privileges.
To add wcf session to existing code.
Fix: This is where i am facing problem because i was using wshttpbinding with transport security. Then after browsing several materials and codes understood i have to make my config to custombinding with reliablesession and httpstransport to proceed further with development. so my config roughly appears as
<customBinding abc of wcf>
<reliableSession/>
<httpsTransport/>
<\customBinding>
And also uncommented above line of service instantiation after understanding its real purpose.No need for any values in reliable session tag and httpsTransport for basic support of security.
This solved my second issue.
However i am facing wsdl import error in wcf client test tool now which i am struggling now to solve. svcutil proxy generation option or add service refrence with uncheked assembly option doesnt seem to be working. Also i noticed that even if add my service refrence freshly to client there is no configurations generated to my app.config file surprisingly.....and above mentioned options dont help.....looking for experts ideas....
Fix : ??? Expolring....
Souls who read this are welcome to give answer/suggestion.
Im trying to pass windows credentials into WCF service that requires windows authentication but it seems like my credentials are not making it into the service. My service does not throw any errors but when I check either of the 2 below they are empty.
var user = WindowsIdentity.GetCurrent().User;
var callerUserName = ServiceSecurityContext.Current.WindowsIdentity.User;
Here is my client side code
ServiceReference1.DispatchServiceClient service = new DispatchServiceClient();
service.ClientCredentials.Windows.ClientCredential = ServiceCredentialsManager.GetNetworkCredentials();
service.ClientCredentials.UserName.UserName= ServiceCredentialsManager.GetNetworkCredentials().UserName;
service.ClientCredentials.UserName.Password = ServiceCredentialsManager.GetNetworkCredentials().Password;
Client config -
<basicHttpBinding>
<binding name="BasicHttpsBinding_IDispatchService">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
<basicHttpsBinding>
<binding name="basicHttpsBindingMax" maxBufferSize="999999999"
maxReceivedMessageSize="999999999">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpsBinding>
</bindings>
Service config -
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicHttpsBinding">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
Please note that in order to use Windows Authentication both service and the client application has to be run in the same Windows domain.
Please also make sure that that in the client the values You assign are not empty. Usually, the password isn't accessible from code when Windows authentication is used.
If the service client is authenticated using Windows Authentication, You probably shouldn't manually pass the credentials to the service. The authentication process should be handled automatically by the WCF and doesn't rely simply on sending the credentials, but for example it can use Kerberos ticket instead.
Please take a look here for some description and code samples for client and service:
https://msdn.microsoft.com/en-us/library/ms734673%28v=vs.110%29.aspx
https://msdn.microsoft.com/en-us/library/ms733089%28v=vs.110%29.aspx
Update
After some research I've found several sources suggesting that setting credentials appropriately in the client code may enable WCF to authenticate from outside the domain:
https://devdump.wordpress.com/2009/04/29/wcf-windows-authentication-and-external-users/
http://subbusspace.blogspot.com/2009/10/accessing-wshttp-wcf-service-from.html
The code samples suggested in those articles are similar, but slightly different to the one You've posted in the question. I haven't tested those methods and they may not work in all scenarios.
My project used visual studio 2010 and is a web application project with c#. I add web referrence for the web service.
I'm having could not establish secure channel for SSL/TLS with authority when i try to access third party web service with certificate in my UAT server. The certificate was expired. I've already add the trust root cert and personal cert for local computer and current user. It works when i call with web service application but not with web application
Below code I used to add the certificate when calling web service and bypass the certificate error.
AServiceReference.AServiceClient client = new AServiceReference.AServiceClient();
X509Certificate2 cert = new X509Certificate2("CERTIFICATE","PASSWORD");
client.ClientCredentials.ClientCertificate.Certificate = cert;
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Add this in your web.config file and you will be fine probably:
<bindings>
<basicHttpBinding>
<binding name="xxxBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
When I was tasked with attaching a client cert I was able to do it in one of two ways. It doesn't look like you're actually attaching the client cert (if you are using one) anywhere.
1: through code like you've been doing
proxyClient.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindByThumbprint,
"6D0DBF387484B25A16D0E3E53DBB178A366DA954");
2: through configuration in the web/app.config file.
<behaviors>
<endpointBehaviors>
<behavior name="ohBehave">
<clientCredentials useIdentityConfiguration="false">
<clientCertificate findValue="c6dafea24197cd6a6f13e846ffcdf70220d23ec2" storeLocation="CurrentUser"
x509FindType="FindByThumbprint" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="https://myservice.ca/SubmitService/Submit.svc"
behaviorConfiguration="ohBehave" binding="customBinding" bindingConfiguration="SubmitBinding"
contract="SubmitService.Submit" name="SubmitDev" />
</client>
As long as the cert is in the store specified it should be getting attached.
I also had to use a customBinding in my .config file since we wanted to pass credentials as well (note the httpsTransport node for client certs):
<binding name="SubmitBinding">
<security defaultAlgorithmSuite="Default" authenticationMode="UserNameOverTransport"
requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
<localClientSettings detectReplays="false" />
<localServiceSettings detectReplays="false" />
</security>
<textMessageEncoding messageVersion="Soap11">
<readerQuotas maxDepth="32" maxStringContentLength="200000000"
maxArrayLength="200000000" maxBytesPerRead="200000000" />
</textMessageEncoding>
<httpsTransport maxBufferPoolSize="200000000" maxReceivedMessageSize="200000000"
maxBufferSize="200000000" requireClientCertificate="true" />
</binding>
I am trying to make a WCF service over basicHttpBinding to be used over https. Here's my web.config:
<!-- language: xml -->
<service behaviorConfiguration="MyServices.PingResultServiceBehavior"
name="MyServices.PingResultService">
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="defaultBasicHttpBinding"
contract="MyServices.IPingResultService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
...
<bindings>
<basicHttpBinding>
<binding name="defaultBasicHttpBinding">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
...
<behaviors>
<serviceBehaviors>
<behavior name="MyServices.UpdateServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
I am connecting using WCFStorm which is able to retrieve all the meta data properly, but when I call the actual method I get:
The provided URI scheme 'https' is invalid; expected 'http'. Parameter
name: via
Try adding message credentials on your app.config like:
<bindings>
<basicHttpBinding>
<binding name="defaultBasicHttpBinding">
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="Certificate" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
Adding this as an answer, just since you can't do much fancy formatting in comments.
I had the same issue, except I was creating and binding my web service client entirely in code.
Reason is the DLL was being uploaded into a system, which prohibited the use of config files.
Here is the code as it needed to be updated to communicate over SSL...
Public Function GetWebserviceClient() As WebWorker.workerSoapClient
Dim binding = New BasicHttpBinding()
binding.Name = "WebWorkerSoap"
binding.CloseTimeout = TimeSpan.FromMinutes(1)
binding.OpenTimeout = TimeSpan.FromMinutes(1)
binding.ReceiveTimeout = TimeSpan.FromMinutes(10)
binding.SendTimeout = TimeSpan.FromMinutes(1)
'// HERE'S THE IMPORTANT BIT FOR SSL
binding.Security.Mode = BasicHttpSecurityMode.Transport
Dim endpoint = New EndpointAddress("https://myurl/worker.asmx")
Return New WebWorker.workerSoapClient(binding, endpoint)
End Function
Change
from
<security mode="None">
to
<security mode="Transport">
in your web.config file. This change will allow you to use https instead of http
Are you running this on the Cassini (vs dev server) or on IIS with a cert installed? I have had issues in the past trying to hook up secure endpoints on the dev web server.
Here is the binding configuration that has worked for me in the past. Instead of basicHttpBinding, it uses wsHttpBinding. I don't know if that is a problem for you.
<!-- Binding settings for HTTPS endpoint -->
<binding name="WsSecured">
<security mode="Transport">
<transport clientCredentialType="None" />
<message clientCredentialType="None"
negotiateServiceCredential="false"
establishSecurityContext="false" />
</security>
</binding>
and the endpoint
<endpoint address="..." binding="wsHttpBinding"
bindingConfiguration="WsSecured" contract="IYourContract" />
Also, make sure you change the client configuration to enable Transport security.
I had same exception in a custom binding scenario. Anybody using this approach, can check this too.
I was actually adding the service reference from a local WSDL file. It got added successfully and required custom binding was added to config file. However, the actual service was https; not http. So I changed the httpTransport elemet as httpsTransport. This fixed the problem
<system.serviceModel>
<bindings>
<customBinding>
<binding name="MyBindingConfig">
<textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
messageVersion="Soap11" writeEncoding="utf-8">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
</textMessageEncoding>
<!--Manually changed httpTransport to httpsTransport-->
<httpsTransport manualAddressing="false" maxBufferPoolSize="524288"
maxReceivedMessageSize="65536" allowCookies="false" authenticationScheme="Anonymous"
bypassProxyOnLocal="false"
decompressionEnabled="true" hostNameComparisonMode="StrongWildcard"
keepAliveEnabled="true" maxBufferSize="65536"
proxyAuthenticationScheme="Anonymous"
realm="" transferMode="Buffered" unsafeConnectionNtlmAuthentication="false"
useDefaultWebProxy="true" />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="https://mainservices-certint.mycompany.com/Services/HRTest"
binding="customBinding" bindingConfiguration="MyBindingConfig"
contract="HRTest.TestWebserviceManagerImpl" name="TestWebserviceManagerImpl" />
</client>
</system.serviceModel>
References
WCF with custombinding on both http and https
I had the EXACT same issue as the OP. My configuration and situation were identical. I finally narrowed it down to being an issue in WCFStorm after creating a service reference in a test project in Visual Studio and confirming that the service was working. In Storm you need to click on the "Config" settings option (NOT THE "Client Config"). After clicking on that, click on the "Security" tab on the dialog that pops up. Make sure "Authentication Type" is set to "None" (The default is "Windows Authentication"). Presto, it works! I always test out my methods in WCFStorm as I'm building them out, but have never tried using it to connect to one that has already been set up on SSL. Hope this helps someone!
Ran into the same issue, this is how my solution turned out at the end:
<basicHttpsBinding>
<binding name="VerificationServicesPasswordBinding">
<security mode="Transport">
</security>
</binding>
<binding name="VerificationServicesPasswordBinding1" />
</basicHttpsBinding>
I basically replaced every occurrence of Http with Https. You can try adding both of them if you prefer.
If you do this programatically and not in web.config its:
new WebHttpBinding(WebHttpSecurityMode.Transport)
Its a good to remember that config files can be split across secondary files to make config changes easier on different servers (dev/demo/production etc), without having to recompile code/app etc.
For example we use them to allow onsite engineers to make endpoint changes without actually touching the 'real' files.
First step is to move the bindings section out of the WPF App.Config into it's own separate file.
The behaviours section is set to allow both http and https (doesn't seem to have an affect on the app if both are allowed)
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="true" />
And we move the bindings section out to its own file;
<bindings configSource="Bindings.config" />
In the bindings.config file we switch the security based on protocol
<!-- None = http:// -->
<!-- Transport = https:// -->
<security mode="None" >
Now the on site engineers only need to change the Bindings.Config file and the Client.Config where we store the actual URL for each endpoint.
This way we can change the endpoint from http to https and back again to test the app without having to change any code.
Hope this helps.
To re-cap the question in the OP:
I am connecting [to a WCF service] using WCFStorm which is able to retrieve all the meta data properly, but when I call the actual method I get:
The provided URI scheme 'https' is invalid; expected 'http'. Parameter name: via
The WCFStorm tutorials addresses this issue in Working with IIS and SSL.
Their solution worked for me:
To fix the error, generate a client config that matches the wcf service configuration. The easiest way to do this is with Visual Studio.
Open Visual Studio and add a service reference to the service. VS will generate an app.config file that matches the service
Edit the app.config file so that it can be read by WCFStorm. Please see Loading Client App.config files. Ensure that the endpoint/#name and endpoint/#contract attributes match the values in wcfstorm.
Load the modified app.config to WCFStorm [using the Client Config toobar button].
Invoke the method. This time the method invocation will no longer fail
Item (1) last bullet in effect means to remove the namespace prefix that VS prepends to the endpoint contract attribute, by default "ServiceReference1"
<endpoint ... contract="ServiceReference1.ListsService" ... />
so in the app.config that you load into WCFStorm you want for ListsService:
<endpoint ... contract="ListsService" ... />
I needed the following bindings to get mine to work:
<binding name="SI_PurchaseRequisition_ISBindingSSL">
<security mode="Transport">
<transport clientCredentialType="Basic" proxyCredentialType="None" realm="" />
</security>
</binding>
wsHttpBinding is a problem because silverlight doesn't support it!
I've added a "Connected Service" to our project by Visual Studio which generated a default method to create Client.
var client = new MyWebService.Client(MyWebService.Client.EndpointConfiguration.MyPort, _endpointUrl);
This constructor inherits ClientBase and behind the scene is creating Binding by using its own method Client.GetBindingForEndpoint(endpointConfiguration):
public Client(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(Client.GetBindingForEndpoint(endpointConfiguration),
new System.ServiceModel.EndpointAddress(remoteAddress))
This method has different settings for https service and http service.
When you want get data from http, you should use TransportCredentialOnly:
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.TransportCredentialOnly;
For https you should use Transport:
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
In my case in web.config I had to change binding="basicHttpsBinding" to binding="basicHttpBinding" in the endpoint definition and copy the relative bindingConfiguration to basicHttpBinding section
<!-- Binding settings for HTTPS endpoint -->
<binding name="yourServiceName">
<security mode="Transport">
<transport clientCredentialType="None" />
<!-- Don't use message -->
</security>
</binding>
My solution, having encountered the same error message, was even simpler than the ones above, I just updated the to basicHttpsBinding>
<bindings>
<basicHttpsBinding>
<binding name="ShipServiceSoap" maxBufferPoolSize="512000" maxReceivedMessageSize="512000" />
</basicHttpsBinding>
</bindings>
And the same in the section below:
<client>
<endpoint address="https://s.asmx" binding="basicHttpsBinding" bindingConfiguration="ShipServiceSoap" contract="..ServiceSoap" name="ShipServiceSoap" />
</client>
I've been trying to get WCF security working for my project, and have had little luck. I'm trying to create a service that uses net.tcp as the binding, and does both message and transport security. Message security is done using username and password and transport security is done (supposedly!) using certificates.
For my development testing, I created my own certificate authority and placed this certificate in my computer's trusted store (LocalMachine). I then created two certificates, each signed by my certificate authority, one for the service to use, and one for the client app to use. I placed both of these in the Personal store (My) in LocalMachine. Then, for testing I created a random certificate that wasn't signed by my certificate authority (and therefore is untrusted) and placed that in the Personal store in LocalMachine. I used makecert to create these certificates.
I then configured the client app that connects to the service to use the invalid untrusted certificate as its client certificate. The service is set (supposedly) to check the client certificates using chain trust. However, this client is able to connect and successfully talk to the service! It should be being rejected, because its certificate is untrusted!
I don't know what's causing this behaviour, so I submit the issue to you guys to see what you make of it. Here are my WCF configurations:
Service conf:
<system.serviceModel>
<services>
<service behaviorConfiguration="DHTestBehaviour" name="DigitallyCreated.DHTest.Business.DHTestBusinessService">
<endpoint address="" binding="netTcpBinding" contract="DigitallyCreated.DHTest.Business.IDHTestBusinessService" bindingConfiguration="DHTestNetTcpBinding" bindingNamespace="http://www.digitallycreated.net/DHTest/v1" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8090/"/>
<add baseAddress="http://localhost:8091/"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DHTestBehaviour">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="DHTestMembershipProvider"/>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" findValue="CN=business.dhtestDHTest.com" />
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" trustedStoreLocation="LocalMachine" revocationMode="NoCheck" />
</clientCertificate>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="DHTestRoleProvider" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="DHTestNetTcpBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign"/>
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
Client Conf:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IDHTestBusinessService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288"
maxBufferSize="65536" maxConnections="10" maxReceivedMessageSize="65536">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" />
<message clientCredentialType="UserName" />
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="DHTestBusinessServiceEndpointConf">
<clientCredentials>
<clientCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" findValue="CN=invalid"/>
<serviceCertificate>
<authentication revocationMode="NoCheck" trustedStoreLocation="LocalMachine"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="net.tcp://phoenix-iv:8090/" binding="netTcpBinding"
behaviorConfiguration="DHTestBusinessServiceEndpointConf"
bindingConfiguration="NetTcpBinding_IDHTestBusinessService"
contract="DHTest.NetTcp.Business.IDHTestBusinessService"
name="NetTcpBinding_IDHTestBusinessService">
<identity>
<dns value="business.dhtest.com" />
</identity>
</endpoint>
</client>
</system.serviceModel>
The client username/password auth code:
DHTestBusinessServiceClient client = new DHTestBusinessServiceClient();
client.ClientCredentials.UserName.UserName = "ratfink";
client.ClientCredentials.UserName.Password = "testpassword";
Thank you for your help in advance.
EDIT (2009/06/01):
One of my friends pointed me towards a blog that answers the question as to why this is occurring. Apparently, when you specify TransportWithMessageCredential is means exactly that: Transport with Message Credentials only. This is why my certificates are being ignored on the transport level.
However, I don't consider the issue complete and closed, because I still want to do this. :) I'm going to look into custom certificate validators that I think I can plug in and see if that works. I'll get back to you all with the results.
EDIT (2009/06/08):
No, custom certificate validators don't work either. WCF simply doesn't call them.
I found a solution to my problem, however, it turned out to be a lot nastier than I expected.
Basically, to achieve both Transport and Message credential checking you need to define a custom binding. (I found info to this effect here).
I found the easiest way to do this is to continue doing your configuration in the XML, but at runtime copy and slightly modify the netTcp binding from the XML configuration. There is literally one switch you need to enable. Here's the code on the service side and on the client side:
Service Side
ServiceHost businessHost = new ServiceHost(typeof(DHTestBusinessService));
ServiceEndpoint endpoint = businessHost.Description.Endpoints[0];
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>();
sslElement.RequireClientCertificate = true; //Turn on client certificate validation
CustomBinding newBinding = new CustomBinding(bindingElements);
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding;
newBinding.Namespace = oldBinding.Namespace;
endpoint.Binding = newBinding;
Client Side
DHTestBusinessServiceClient client = new DHTestBusinessServiceClient();
ServiceEndpoint endpoint = client.Endpoint;
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>();
sslElement.RequireClientCertificate = true; //Turn on client certificate validation
CustomBinding newBinding = new CustomBinding(bindingElements);
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding;
newBinding.Namespace = oldBinding.Namespace;
endpoint.Binding = newBinding;
You'd think that'd be it, but you'd be wrong! :) This is where it gets extra lame. I was attributing my concrete service methods with PrincipalPermission to restrict access based on roles of the service user like this:
[PrincipalPermission(SecurityAction.Demand, Role = "StandardUser")]
This started failing as soon as I applied the above changes. The reason was because the
OperationContext.Current.ServiceSecurityContext.PrimaryIdentity
was ending up being an unknown, username-less, unauthenticated IIdentity. This was because there are actually two identities representing the user: one for the X509 certificate used to authenticate over Transport, and one for the username and password credentials used to authenticate at Message level. When I reverse engineered the WCF binaries to see why it wasn't giving me my PrimaryIdentity I found that it has an explicit line of code that causes it to return that empty IIdentity if it finds more than one IIdentity. I guess it's because it's got no way to figure out which one is the primary one.
This means using the PrincipalPermission attribute is out the window. Instead, I wrote a method to mimic its functionality that can deal with multiple IIdentities:
private void AssertPermissions(IEnumerable<string> rolesDemanded)
{
IList<IIdentity> identities = OperationContext.Current.ServiceSecurityContext.AuthorizationContext.Properties["Identities"] as IList<IIdentity>;
if (identities == null)
throw new SecurityException("Unauthenticated access. No identities provided.");
foreach (IIdentity identity in identities)
{
if (identity.IsAuthenticated == false)
throw new SecurityException("Unauthenticated identity: " + identity.Name);
}
IIdentity usernameIdentity = identities.Where(id => id.GetType().Equals(typeof(GenericIdentity))).SingleOrDefault();
string[] userRoles = Roles.GetRolesForUser(usernameIdentity.Name);
foreach (string demandedRole in rolesDemanded)
{
if (userRoles.Contains(demandedRole) == false)
throw new SecurityException("Access denied: authorisation failure.");
}
}
It's not pretty (especially the way I detect the username/password credential IIdentity), but it works! Now, at the top of my service methods I need to call it like this:
AssertPermissions(new [] {"StandardUser"});
check out the Codeplex - Intranet Section that provides checklists for different scenarios. The other thing to mention is that netTcpBindings are not supported for use within IIS5.0 and IIS6.0 - see 3rd paragraph here, only IIS7.0+.