I'm developping a proof of concept for a WCF web service using SSL and certificates for mutual authentication.
So, I have 2 certificates both provided by a valid certification authority (these are production certificates, not development). Here are the chains and the store locations for the certificates :
Server Certificate Chain
- Issuer Root CA
- Intermediate 1 CA
- Server Authentication certificate
I don't know if this detail is important or not : server certificate is a wildcard certificate for the domain (*.mydomain.com)
Client Certificate Chain
- Issuer Root CA
- Intermediate 2 CA
- Client Authentication certificate
Issuer Root CA is common root CA for both certificates.
Intermediates certificates are differents.
Store Location
Issuer Root CA have been imported into Trusted Root CA on both server and client machines Intermediate CA 1 & 2 have been imported into Intermediate CA on both server and client Issuer and intermediates certificates have both public keys only.
Server certificate have been imported into Personal on server machine. This certificate have a private key. Server certificate have been imported into Personal on client machine. This certificate have a public key only. Client authentication certificate have been imported into Personal on both server and clients machines. These certificates have both private keys.
I created a simple WCF application project hosted in IIS 8.5 with framework C# 4.0. I use the example classes provided by default at the project creation, I have just renamed it into DemoService.svc. Then, I created the client (I use a winform application for target users to have a graphical interface to view results) and add the web service reference.
Then, I modified the service configuration to set up mutual authentication. All is done via web.config :
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="demoServiceBehavior">
<serviceAuthorization principalPermissionMode="UseWindowsGroups"></serviceAuthorization>
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" findValue="*.mydomain.com"/>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" mapClientCertificateToWindowsAccount="true"
trustedStoreLocation="LocalMachine"/>
<certificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"
findValue="myservice.mydomain.com"/>
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="demoServiceBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true">
<serviceActivations>
<add service="WcfMutualAuthenticationServiceDemo.DemoService" relativeAddress="DemoService.svc" />
</serviceActivations>
</serviceHostingEnvironment>
<services>
<service name="WcfMutualAuthenticationServiceDemo.DemoService" behaviorConfiguration="demoServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="https://myservice.mydomain.com/"/>
</baseAddresses>
</host>
<endpoint name="demoServiceEndpoint"
address=""
binding="basicHttpBinding"
bindingConfiguration="demoServiceBinding"
contract="WcfMutualAuthenticationServiceDemo.IDemoService"></endpoint>
</service>
</services>
</system.serviceModel>
I modified the client configuration to set up mutual authentication too :
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="demoClientBehavior">
<clientCredentials>
<clientCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"
findValue="myservice.dekra-automotivesolutions.com"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="demoClientBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://myservice.mydomain.com/DemoService.svc"
behaviorConfiguration="demoClientBehavior"
binding="basicHttpBinding"
bindingConfiguration="demoClientBinding"
contract="DemoServiceValidReference.IDemoService"
name="demoServiceEndpoint" />
</client>
</system.serviceModel>
When I call the web service via the client, it return an exception :
System.ServiceModel.Security.SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority 'myservice.mydomain.com'. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel. at System.Net.HttpWebRequest.GetResponse() at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout) --- End of inner exception stack trace ---
After investigation, I found an entry in the server's event viewer :
Handling an exception. Exception details: System.IdentityModel.Tokens.SecurityTokenValidationException: The X.509 certificate CN=myservice.mydomain chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. A certification chain processed correctly, but one of the CA certificates is not trusted by the policy provider.
at System.IdentityModel.Selectors.X509CertificateChain.Build(X509Certificate2 certificate) at System.IdentityModel.Selectors.X509CertificateValidator.ChainTrustValidator.Validate(X509Certificate2 certificate) at System.IdentityModel.Selectors.X509SecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token) at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token) at System.ServiceModel.Channels.HttpsChannelListener
1.CreateSecurityProperty(X509Certificate2 certificate, WindowsIdentity identity, String authType) at System.ServiceModel.Channels.HttpsChannelListener
1.ProcessAuthentication(IHttpAuthenticationContext authenticationContext) at System.ServiceModel.Activation.HostedHttpContext.OnProcessAuthentication() at System.ServiceModel.Channels.HttpRequestContext.ProcessAuthentication() at System.ServiceModel.Channels.HttpChannelListener1.HttpContextReceivedAsyncResult
1.Authenticate() at System.ServiceModel.Channels.HttpChannelListener1.HttpContextReceivedAsyncResult
1.ProcessHttpContextAsync()
Regarding this exception, I found that the issue is on the chain validation of the client authentication certificate, but I don't know why. At this point, I'm stuck ! I don't know what's wrong with my certificates, and I don't know how to find a solution.
I truly hope that someone could help me fixing this issue.
Edit : We have tested with another client certificate for the certification chain to be the same on server and client certificates, it don't change anything.
Solution was found today : configuration is OK on client and server. It was a misconfiguration on the IIS server certificate mapping, the functionnality was not configured but not enabled.