intermittent error could not establish secure channel for ssl/tls - wcf

My WPF(desktop) application is calling two type of services - one WCF service which is hosted in our internal environment and other one is a 3rd party web service.
My application is using .Net framework 4.0. Due to some recent windows update applied on organization, my 3rd party web service stop working and I start getting this error
"System.ServiceModel.Security.SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority 'abc.net'. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel. "
In order to resolve that I had added this line before calling the 3rd party web service.
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
And, it start working perfectly. However, it impacted my internally hosted WCF service. I start getting the same error for my WCF service. Strange part is that, i am not getting is error every time, it occurs all of a sudden and when we restart the application, WCF service call again start working fine.
Error:
"System.ServiceModel.Security.SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority 'abc.net'. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel."
Some of WCF service config details :
<bindings>
<wsHttpBinding>
<binding maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
----
<serviceBehaviors>
<behavior name="WcfServerSyncService.LocalDataCacheSyncServiceBehavior">
<serviceMetadata httpsGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="True" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfServerSyncService.CustomValidator,WcfServerSyncService" />
</serviceCredentials>
</behavior>
<serviceBehaviors>
And, one more thing only specific set of users are getting this error and not all. But, for these users this work sometime and sometime not.
Any suggestion ?

TLS1.2 requires the support of the OS and the SDK, therefore, I suggest you install the DotNet framework and target high version of DotNet Runtime.
https://learn.microsoft.com/en-us/dotnet/framework/network-programming/tls
Based on the best practice of transport layer security, we had better not specify the TLSversion while calling the WCF service protected by an SSL certificate. OS will decide on the TLS version.
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls13 | System.Net.SecurityProtocolType.Tls;
In addition, WCF server-side hosted in the WPF shall not produce a communication error, which only occurs on the client-side. The only error might be the account running WPF doesn't have permission to access the certificate. The transport layer security requires binding a certificate to the service port that is used to publish service.
At last, since the WCF server authenticates the client with custom validation, this will inevitably lead to certain errors.
Feel free to let me know if there is anything I can help with.

Related

How do I configure a WCF web service for Basic authentication, but expose metadata with anonymous authentication

I have a WCF SOAP-1.2 web service hosted in IIS that is using HTTP Basic Auth via a customBinding specification. In dev environments, it uses only HTTP. In QA, it uses HTTP and HTTPS. In prod, it uses HTTPS transport only.
Right now the WSDL is exposed by a serviceBehavior tag, rather simply, like this (using httpsGetEnabled as appropriate):
<serviceMetadata httpGetEnabled="true"/>
I would like to enable anonymous access to the WSDL/schemas only, as they currently require Basic Auth as does the actual service. How does one do that? I've dug around on MSDN, and found some resources pointing to use of a webHttpBinding for the metadata specifically, but I can't seem to get it to forget about Basic Auth:
<serviceMetadata httpGetEnabled="true" httpGetBinding="webHttpBinding" httpGetBindingConfiguration="metadatabinding" />
...
<bindings>
<webHttpBinding>
<binding name="metadatabinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="None" proxyCredentialType="None"/>
</security>
</binding>
</webHttpBinding>
...
</bindings>
In using the above tweaked metadata tags, I am prompted for Basic credentials in the browser when pulling up http://someserver/service.svc?wsdl (and those names have been sanitized).
In IIS, I have enabled anonymous and Basic auth for the site/application, such that the bindings ultimately control the credential requirements.
Oops, I actually did not change the IIS application configuration as I stated that I did in the question. To make the second, expanded web.config above work properly, you need to enable Anonymous and Basic Auth inside of IIS in the "Authentication" section of either a site or application so that at the application level, both are available. By using a binding for the actual service which has an authenticationScheme="Basic", the service is authenticated while the metadata is not.
I'm surprised this is not as directly documented; most helpful tips that I could find on other social sites or SO has suggested using a separate application or static resources for WSDLs and schemas, as opposed to the loosening access to the WCF generated metadata.
The authenticationScheme attribute change did the trick for me as well (from #Greg's answer).
However, I have a self-hosted service, so I added it to the App.config file instead.
This defines both HTTPS and Basic Authentication to the serviceMetaData endpoint:
<behaviors>
<serviceBehaviors>
<behavior name="HttpsAndBasicAuthentication" >
<serviceMetadata httpsGetEnabled="true" httpsGetUrl="https://localhost:8000/CalculatorService" />
<serviceAuthenticationManager authenticationSchemes="Basic"/>
</behavior>
</serviceBehaviors>
</behaviors>
Note that this behavior has to be referenced in the <service> element using the behaviorConfiguration attribute.

Configure a WCF service to authenticate the client by using X509 certificates but without server credentials

I have developed a ASP.NET MVC website that also hosts a WCF 4 service; and I have created a .NET Windows application that complements the website and interacts with it by consuming that web service via the internet. Both programs were created using .NET 4 in Visual Studio 2010. The binding used with the WCF service is a WsHttpBinding.
My only security requirement is that the WCF service is not consumed by some unknown party, as that would contaminate the information in my website's database. I have no need for any privacy: I don't care if some third party gets to see the messages I am sending to the web service (i.e., no need for encryption)
Considering that single requirement, and as far as my knowledge about security in web services goes, the best way to implement that security scenario would be for the client to sign each SOAP message with a X509 Certificate, and have the web service trust any message signed with a certificate present in its "trusted people" store. Since certificates use Public/Private key pairs, even if the message is not encrypted it cannot be reproduced by an unauthorized third party without the private key used to sign the message. This would also assure me message integrity.
Considering that decision, I have created a test certificate using makecert.exe. I installed the version with both the private and public key in the client machine (the one with the windows application), in the "Current User" "My" certificate store. Then, I exported the certificate (without the private key), and installed it in "Trusted People" of "Local Machine" certificate store of the web server.
My (unsuccessful) attempt to configure that scenario so far goes like this:
Binding is configured to use Message security, where the Message expects client credentials of the certificate type. Also, the server is configured to use PeerTrust instead of ChainTrust, since the test certificate is not emitted by a trusted CA:
<bindings>
<wsHttpBinding>
<binding>
<security mode="Message">
<transport clientCredentialType="None" proxyCredentialType="None" />
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="Standard">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode = "PeerTrust" trustedStoreLocation="LocalMachine"/>
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
The configuration on the client windows application sets the certificate as its credential. The binding configuration is identical to the one in the server.
<behaviors>
<endpointBehaviors>
<behavior name="CertificateCredential">
<clientCredentials>
<clientCertificate findValue="ErrEye"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
The problem is that with this configuration, when I try to check if the Web Service host was started successfully, I get the following error:
The service certificate is not provided. Specify a service certificate in ServiceCredentials.
My problem is, I don't WANT to specify a "service certificate". As far as I understand, the service certificate would allow the client to authenticate the service, but I don't need that; I just want the service to authenticate the client and that's it. I do understand this means that if someone impersonates the web server (by tampering the DNS settings of my client computer, for example) it would receive the messages from my client and the client would not know it is not communicating with its intended recipient: I don't mind if that happens.
In summary, my question is: How do I configure in WCF the security scenario I've described?
Thanks in advance for your help.
As long as its .net to .net (and remains so) - add this
negotiateServiceCredential=true
to:
<message clientCredentialType="Certificate" />
But beware, you will lose interoperability with non .net clients.

WCF - SSL Service

I have a service that uses SSL. When I try to browse to the service I get the following error:
The SSL settings for the service 'SslRequireCert' does not match those of the IIS 'None'.
My config is as follows:
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
I am using IIS Express and I have "Enable SSL" checked (verified in WebMatrix).
Any ideas what else I need to do?
Ok. It looks like I needed to update the applicationhost.config file (in IIS Express). I added a location for my site and set sslFlags="Ssl, SslAcceptCert, SslRequireCert". This allowed me to enable SSL authentication. Then when I tried to access the service with a client I got a message that there was an error establishing a trust relationship. This was because I did not have the server's certificate in my trusted people folder (or it might have been the client, I'm not sure as the service and client is on my local PC). After doing that, the service will work, however we do not want to have to give our certificate to the client, so I ended up using the TransportWithMessageCredential security mode.
Modify your behaviour to include SSL as below;
<behaviors>
<serviceBehaviors>
<behavior name="MyService">
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
and remove
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
This is implying that you will be using certificate authentication
This has absolutely nothing to do with the absence of the mex line, unless it's configured incorrectly in which case removing it will have an effect but IIS still might give the same error.
Make sure IIS has SSL Settings of 'Require SSL' and 'Accept' (easier) or 'Require' user certificates - then RESTART IIS. Although IIS says changes are applied - im my experience they are not always - or at least not done immediately.

connecting to wcf service hosted on domain from a client that is not on the domain

I would like an example or explanation of how to connect a client to a wcf service when the client is not on the domain.
I imagine there is a way to specify domain credentials with the client and the wcf service could talk to the authority (dc) to see if the client is secure.
I followed the examples on the msdn and can connect to see the metadata (methods available) but when using wshttpbinding I get "An unsecured or incorrectly secured fault was received from the other party".
Thanks in advance!
By default, wsHttpBinding will use Windows credentials - this only works if both your service and your calling client are member of the same domain (or member of domains with a mutual trust relationship).
If you want to authenticate using username/password, there's a number of things you need to do:
the service needs a certificate to authenticate itself to the caller, and to provide an encryption mechanism for the exchange of username/passwords and messages. So you will need to create a security certificate and install it on the server machine, and configure it:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Internet">
<serviceCredentials
findValue="MyServiceCertificate"
storeLocation="LocalMachine"
storeName="My"
X509FindType="FindBySubjectName" />
</behavior>
<serviceBehaviors>
<behaviors>
<services>
<service name="MyService" behaviorConfiguration="Internet">
......
</service>
</services>
</system.serviceModel>
the client needs to set up a config that defines wsHttpBinding with message security, and username/password client credentials
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="UserNameWS">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
<wsHttpBinding>
<bindings>
<client>
<endpoint name="Default"
address="........."
binding="wsHttpBinding" bindingConfiguration="UserNameWS"
contract="........." />
</client>
</system.serviceModel>
on the server side, you need to set up a mechanism to authenticate those username/passwords - typically, the easiest way is to use the ASP.NET membership system
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Internet">
<userNameAuthentication
userNamePasswordValidationMode="MembershipProvider" />
<serviceCredentials
.....
</system.serviceModel>
before each call from the client, you need to set the username/password on your client-side proxy (this is one of the few things you cannot do in config - works only in code).
proxy.ClientCredentials.UserName.UserName = "YourUserName";
proxy.ClientCredentials.UserName.Password = "Top$Secret";
Read all about WCF security at the WCF Security Guidance site on Codeplex.
The error message "An unsecured or incorrectly secured fault was received from the other party" is a rather misleading one. A common cause is a difference in the bindings configuration between the client and the server. Check the system.serviceModel section of the web.config at the service side, and modify your client settings to match.
The reason why you can access metadata and cannot call service is that you are using WsHttpBinding probably with default configuration. It uses message security wich is involved only for service usage - not service metadata. It uses Windows credentials and Windows security to encrypt and sign message. Because of Windows security it works only when both client and server are on the same domain.
Your client is not part of domain - you can send windows credentials either with message security or transport security. In case of message security you will have to use clientCredentialType="UserName", default password validator and you will have to configure X509 certificate in service behavior to support encryption and signing. In case of transport security will either use HTTPS (X509 certificate configured in http.sys/IIS) or TransportCredentialOnly mode which will send windows user name and password as a plain text over HTTP (this is bad solution). In case of transport security set clientCredentialType="Basic".

Securing WCF hosted inside public MVC2 App

I've got a WCF service that is to be called by an application hosted on the web server (for the short-medium term, we'll only need a single web server so disregard scalability issues)
The web server serves a public website. at example.com
The WCF service exposes calls which amongst other things run jobs and provide certain admin functionality not supported by the web model eg long running database operations.
The WCF service has to be hosted inside the web site as it uses compatibility mode to take advantage of the Asp.Net http(s) pipeline - specifically, the service can generate emails and the emails are templated using MVC. One side-effect of this is that the call has to use the publicly visible hostname eg https://example.com/JobService.svc so that links in emails point to example.com as opposed to localhost or similar.
Obviously, I don't want the general public to be able to kick off jobs/admin tasks so I want to secure the WCF service.
I can only use https as opposed to net.tcp or similar for the binding thanks to relying on the Asp.net http pipeline.
I have to bind to the publicly accessible IP address to be able to use the proper hostname (unless someone knows a way around this?)
I can't use kerberos/NTLM as the server isn't on a domain (and NTLM is weak anyway)
I can't use certificates as it complains:
The SSL settings for the service 'SslRequireCert' does not match those of the IIS 'None'.
NB: I don't quite understand this as the website itself is only served via https. http simply returns a redirect to the same page via https.
(An interesting issue I'm having is that although the mex is served via https, the URLs inside the WSDL use http. I'm assuming this is a side-effect of not being able to set up TLS properly on my service so it thinks it's http even though it also responds on https)
So, I'm running out of ideas for how to secure my service. I could, of course, from within the service itself examine the request and determine if it comes from an IP used by the current server - but this feels very nasty and I'm effectively ignoring the work of experts and trying to put something in its place - Not a very good place to start.
Can anyone suggest a way to limit access to this service to processes on the local machine?
I've attached my current config below. (This is currently giving me the certificate error mentioned above)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="WebJobServiceHTTPBinding" openTimeout="00:10:00"
sendTimeout="00:10:00">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"
aspNetCompatibilityEnabled="true">
<serviceActivations>
<add relativeAddress="WebJob.svc"
service="MyApp.WebJobService"
factory="MyApp.WCFDIServiceHostFactory" />
</serviceActivations>
</serviceHostingEnvironment>
<services>
<service behaviorConfiguration="WebJobServiceBehavior" name="MyApp.WebJobService">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="WebJobServiceHTTPBinding"
name="HTTPEndpoint" contract="MyApp.JobService.Common.IWebJobService" />
</service>
</services>
<standardEndpoints>
<mexEndpoint>
<standardEndpoint name="WebJobServiceMex" />
</mexEndpoint>
</standardEndpoints>
<behaviors>
<serviceBehaviors>
<behavior name="WebJobServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="[Thumbprint of x509 cert used by website for SSL]"
storeName="Root" x509FindType="FindByThumbprint" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
"Can anyone suggest a way to limit access to this service to processes on the local machine?"
Run your service in a different web site in IIS, if you're not already.
You could bind your service in IIS to the internal network IP address which would allow internal LAN clients to access the service but not external clients.
Another binding option is to bind to a port that is not open on your firewall in order to allow access from internal clients only. Even better, bind to a port that is not open on your firewall, and bind to the internal LAN IP.
You could also try binding to IP address 127.0.0.1.
In the end, I was forced to implement my own Authentication system. This was relatively simple as authenticatio implied authorization - ie no permission levels. That said, I'm still unhappy at the solution and will change it if another option presents itself.