MVC web app (https) calling a WCF Service (https) that also requires a SSL certificate for authentication

10,677

Solution 1

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.

Solution 2

Together with Rajesh I could find the solution.

The problem was with the client certificate (mvc.localhost) that was generated using the "-eku" attribute which gave the certificate the purpose "Ensures the identity of a remote computer" (serverAuth) but not the needed purpose of "Client Authentication".

You can verify a certificate purpose on the "General" tab on its details.

So, I had to generate a new client certificate, eliminating the "-eku" attribute, which gave the certificate "All application policies" purpose (including Client Authentication).

I did this with the following command :

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC>makecert -sr LocalMachine
 -ss my  -a sha1 -n "CN=mvc.localhost" -sky exchange -pe -in "Development Author
ity" -is MY -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider"
 -sy 12 c:\mvc.localhost.cer
Succeeded

Be careful! After generating the certificates using the makecert tool, they will appear available in the "Local Machine/Personal" store. Whenever you have to copy/import a certain certificate in a different store, be sure to first export the original certificate ("Local Machine/Personal" store) as a .pfx file. Then you can import the .pfx file in the other stores and not the .cer file!!

After this, just add the two certificates to the right stores :

Local Machine --> Personal store --> has the two certificates (automatically added when the certificates were created with the makecert tool). IIS picks the certificates from this store (in order to use one when creating a https binding) so I don't recommend deleting the client certificate.

Local Machine --> Trusted People --> imported mvc.localhost.pfx

Current User --> Personal store --> imported mvc.localhost.pfx (this also imported the mvc.localhost CA in this folder)

Current User --> Trusted root certificate Authority --> imported wcf.localhost.pfx

Thanks Rajesh.

PS: you can also check the solution here : WCF Forum Thread

Share:
10,677
Bogdan
Author by

Bogdan

Updated on June 24, 2022

Comments

  • Bogdan
    Bogdan almost 2 years

    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.

    Here is what I've done so far:

    1. 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.

    2. 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.

    3. 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 
      
    4. 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.

      mcv.localhost:

      • 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.

      wcf.localhost:

      • 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
      
    5. 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).

    6. 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.

    7. 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>
      

    8. 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;
          }
    • Using the service certificate thumbprint to authenticate with the service.
    • Different certificateValidationMode values.

    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.

    UPDATE

    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