I have a problem with getting a MVC web app with HTTPS to call a WCF Service that also uses HTTPS. The MVC web app must authenticate with a certificate to the WCF Service.
I'm using Windows 7, IIS 7.5, VS 2010 with C# and .NET Framework 4.0.
I created a Certificate Authority called 'Development Authority':
makecert -pe -n "CN=Development Authority" -ss my -sr LocalMachine -a sha1 -sky signature -r "c:\Development Authority.cer"
I imported the newly created certificate with the MMC tool in the 'Trusted Root Certification Authorities' folder and in the 'Third-Party Root Certification Authorities' folder (Local Computer). This certificate can also be found in the 'Personal' folder.
I saw that if I import the CA only in the 'Trusted Root CA' it will still give a 'not trusted' warning. After importing the CA in the 'Third-Party Root CA' folder, the warning dissappeared.
I created two other certificates (signed by the newly created CA), using the following command :
makecert -pe -n "CN=mvc.localhost" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in "Development Authority" -is MY -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 c:\mvc.localhost.cer
In this way I created 'mvc.localhost' and 'wcf.localhost' certificates. I imported them into the 'Personal' folder (for Local Computer), using the MMC tool.
In my Windows hosts files I created two aliases for the two applications that are going to be hosted with IIS (MVC app and WCF Service app). I updated my hosts file with the following lines:
#development
127.0.0.1 mvc.localhost
127.0.0.1 wcf.localhost
Under IIS, I mapped a new website 'mvc.localhost' to a very simple MVC web app. I also mapped a new website 'wcf.localhost' to a basic newly created WCF Service.
Application pool: .NET Framework 4.0 running using 'ApplicationPoolIdentity'.
Anonymous Authentication: enabled and set to 'Specific User' : 'IUSR'.
Bindings:
HTTP (hostname 'mvc.localhost', port 80) -> can access with 'http://mvc.localhost/'
HTTPS (hostname 'mvc.localhost', port 1234) -> can access with 'https://mvc.localhost:1234/'. Added with the 'mvc.localhost' certificate.
Application pool: .NET Framework 4.0 running using 'ApplicationPoolIdentity'.
Anonymous Authentication: enabled and set to 'Specific User' : 'IUSR'.
Bindings:
HTTP (hostname 'wcf.localhost', port 80) -> can access with 'http://wcf.localhost/'
HTTPS (hostname 'wcf.localhost', port 443) -> can access with 'https://wcf.localhost/'. Added with the 'wcf.localhost' certificate.
To modify the hostnames for the HTTPS bindings I used the following command:
c:\Windows\System32\inetsrv>appcmd set site /site.name:"wcf.localhost" /bindings.[protocol='https',bindingInformation='*:443:'].bindingInformation:*:443:wcf.localhost
I tested the two websites separately with HTTPS and they worked without problems (except Firefox that has a strict policy when I comes to local certificates created for testing purposes and gives you a 'sec_error_unknown_issuer' warning, but this is not my problem for now).
WCF Service configuration for certificate authentication:
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpointBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="wcf.localhost.WelcomeService" behaviorConfiguration="wcf.localhost.WelcomeServiceBehavior">
<endpoint address="https://wcf.localhost/WelcomeService.svc" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" contract="wcf.localhost.IWelcomeService">
</endpoint>
<!--<endpoint address="mex" binding="mexHttpsBinding"
name="mexEndpoint" contract="IMetadataExchange"/>-->
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="wcf.localhost.WelcomeServiceBehavior">
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<clientCertificate>
<authentication revocationMode="NoCheck" certificateValidationMode="PeerOrChainTrust" />
</clientCertificate>
<serviceCertificate findValue="<<wcf.localhost Certificate Thumbprint inserted here>>" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
In IIS, for SSL Settings I set 'Require SSL' with the 'Require' option.
The service WelcomeService exposes a method called 'SayHello(string)' that concatenates 'Hello' to the input parameter and returns the new string.
I can reach 'https://wcf.localhost/welcomeservice.svc' without problems.
MVC web app configuration:
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IWelcomeService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Transport">
<transport clientCredentialType="Certificate"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://wcf.localhost/WelcomeService.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IWelcomeService"
contract="WelcomeService.IWelcomeService" name="WSHttpBinding_IWelcomeService" behaviorConfiguration="CustomBehavior">
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="CustomBehavior" >
<clientCredentials>
<clientCertificate findValue="<<mvc.localhost Certificate Thumbprint inserted here>>"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindByThumbprint"/>
<serviceCertificate>
<authentication certificateValidationMode="PeerOrChainTrust" revocationMode="NoCheck"></authentication>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
Now for the problem : After using the MVC app to call the WCF service I get the following error :
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Net.WebException: The remote server returned an error: (403) Forbidden.
Source Error:
Line 48:
Line 49: public void SayHello(string name) {
Line 50: base.Channel.SayHello(name);
Line 51: }
Line 52: }
Source File: C:\Projects\ssl.research\mvc.localhost\Service References\WelcomeService\Reference.cs Line: 50
Stack Trace:
[WebException: The remote server returned an error: (403) Forbidden.]
System.Net.HttpWebRequest.GetResponse() +7859156
System.ServiceModel.Channels.HttpChannelRequest.WaitForReply(TimeSpan timeout) +99
[MessageSecurityException: The HTTP request was forbidden with client authentication scheme 'Anonymous'.]
System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +4727747
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +1725
mvc.localhost.WelcomeService.IWelcomeService.SayHello(String name) +0
mvc.localhost.WelcomeService.WelcomeServiceClient.SayHello(String name) in C:\Projects\ssl.research\mvc.localhost\Service References\WelcomeService\Reference.cs:50
mvc.localhost.Controllers.HomeController.CallService(HomeModel model) in C:\Projects\ssl.research\mvc.localhost\Controllers\HomeController.cs:33
lambda_method(Closure , ControllerBase , Object[] ) +127
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +264
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +39
System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +129
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +826410
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +314
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +825632
System.Web.Mvc.Controller.ExecuteCore() +159
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +335
System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +62
System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +20
System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +54
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +469
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +375
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.237
On the client side I also tried:
ServicePointManager.ServerCertificateValidationCallback += customXertificateValidation ... private static bool customXertificateValidation(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error) { return true; }
Nothing worked.
I used SSL Diagnostics 1.1 to verify the two web applications and their certificates. No errors were shown. The SSL Handshake Simulation went fine, without errors.
I feel that I am quite close to finishing the test project (having resolved other issues along the way).
Searching the web, I could find only HTTP WCF services or WCF services that didn't use a certificate for authentication or WCF service that use certificate authentication but are HTTP.
I'm trying to have a HTTPS running MVC web app call a HTTPS running WCF Service that requires a certificate for authentication.
Many of the great articles out there helped me with other problems with this project.
Now I'm stuck and I cannot seem to find a solution.
Any suggestion is appreciated.
Thank you, Bogdan.
I noticed that both of my certificates have as their purpose(s): 'Ensures the identity of a remote computer' (serverAuth) but I don't see anything about the clientAuth.
Also, Chrome browser prompts me to select a certificate when I access the MVC web app. In the certificates list I cannot see the two certificates that I created.
I can see only one certificate that has as one of its purposes (but is a different certificate from the ones that I plan on using!) : 'Proves your identity to a remote computer'
Maybe this is why I get the error? Maybe I cannot use either of my newly created certificates to authenticate the client to the server?
Any thoughts? If I'm right, how can I add clientAuth purpose to any of my certificates? Thanks
When using certificates for authentication you need to make sure that the certificates are placed in the appropriate store.
You need to have the client certificate as follows:
On the client machine :
Current User --> Personal folder should have client certificate mvc.localhost.pfx installed
On Server machines:
Local Machine --> TrusterPeople should have mvc.localhost.cer installed
The 403 forbidden error is because the server machine is not able to validate the client with the certificate it is recieveing as it might not be in its trusted store.
If the client and server are the same machine just follow make sure that the certificates are installed as said. In your case you need to have the certificates as below:
Local Machine --> Personal store needs to have the server certificate (wcf.localhost)
Local Machine --> Trusted People store to have the client certificate (mvc.localhost)
Current User --> Personal store to have the client certificate (mvc.localhost)
Current User --> Trusted root certificate Authority store to have server certifcate (wcf.localhost)
If you have teh following then try to browse to the service from IE and you should be able to see the service and its wsdl.
Also go to IE and then Tools --> Internet Options --> Security --> Internet --> Custom Level
Now scroll down to Misc section to find the option "Dont Prompt for client certificate selection when no certificate is present or only one certificate is present" to Diable.
Now restart IE and browse to the service and IE should ask you to select a client certificate from the personal store and you need to select mvc.localhost.
If mvc.localhost is not visible then your client certificate is not in the appropriate store.