5

Setup IIS to require client certificate and to use anonymous authentication

I have a WCF web service for our customers to use. I want to protect this using client certificates. I will also use the client certificate to identify the customer.

I've made the identification part work, but I cannot make make the IIS require client certificates.

If I set the IIS to accept client certificates, the communication works and I can get the client identity using:

ServiceSecurityContext.Current.PrimaryIdentity.Name

But I can also access the site without a client certificate. I'm not sure if those without can do anything else than read the WSDL, but I don't want anyone without a trusted certificate to be able to get any information at all.

If I set the IIS to require client certificate, my test client who should have access gets the error:

The HTTP request was forbidden with client authentication scheme 'Anonymous'.

I want to allow access only to those who have a client certificate trusted by the server. Anyone else shall be rejected.

Server WCF configuration:

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="DefaultBehavior">
        <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
        <serviceCredentials>
          <clientCertificate>
            <authentication certificateValidationMode="ChainTrust" />
          </clientCertificate>
          <serviceCertificate findValue="64343ee2c8338518e78ba698f3936dc92c90db57" x509FindType="FindByThumbprint" />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <wsHttpBinding>
      <binding name="DefaultBinding">
        <security mode="TransportWithMessageCredential">
          <transport clientCredentialType="Certificate" />
          <message clientCredentialType="Certificate" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <services>
    <service name="WebService.Service" behaviorConfiguration="DefaultBehavior">
      <endpoint address="" binding="wsHttpBinding" bindingConfiguration="DefaultBinding" contract="WebService.IService" />
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    </service>
  </services>
</system.serviceModel>

Client WCF configuration.

<system.serviceModel>
  <behaviors>
    <endpointBehaviors>
      <behavior name="DefaultBehavior">
        <clientCredentials>
          <clientCertificate storeLocation="LocalMachine" findValue="d084c91a8f81878cd4dd991bbab348235f0fd1a3" x509FindType="FindByThumbprint" />
          <serviceCertificate>
            <authentication certificateValidationMode="ChainTrust" />
          </serviceCertificate>
        </clientCredentials>
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <bindings>
    <wsHttpBinding>
      <binding name="WSHttpBinding_IService">
        <security mode="TransportWithMessageCredential">
          <transport clientCredentialType="Certificate" />
          <message clientCredentialType="Certificate" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <client>
    <endpoint address="https://host/WebService/Service.svc"
      behaviorConfiguration="DefaultBehavior" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService"
      contract="WebService.IService" name="WSHttpBinding_IService">
    </endpoint>
  </client>
</system.serviceModel>
Palpie
  • 235
  • 2
  • 4
  • 11

1 Answers1

1

Ok we have done the same as you. We worked the other way around. We first secured IIS with the client & server certificate. We did this on IIS Express (still in development while I'm posting this). We allowed in IIS express applicationhost.config to overwrite specific parts of the web.config. I.e.: <section name="windowsAuthentication" overrideModeDefault="Allow" />

Server side Config :

<sytem.serviceModel>
<bindings>
      <wsHttpBinding>
        <binding name="ClientCert">
          <security mode="Transport">
            <transport clientCredentialType="Certificate" />
          </security>
        </binding>
      </wsHttpBinding>
</bindings>
<behaviors>
<!--We have a custom service behavior for claim based security -->
        <behavior name="wsHttpCertificateBehavior">
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
          <serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
          <serviceAuthorization serviceAuthorizationManagerType="MyNamespace.AdamAuthorizationManager, MyAssembly">
            <authorizationPolicies>
              <add policyType="MyNamespace.AdamAuthorizationPolicy, MyAssembly" />
            </authorizationPolicies>
          </serviceAuthorization>
          <serviceCredentials>
            <serviceCertificate findValue="YourServerCertificateNameWithoutCN=" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
            <clientCertificate>
              <authentication revocationMode="NoCheck" mapClientCertificateToWindowsAccount="true" />
            </clientCertificate>
          </serviceCredentials>
        </behavior>
</behaviors>
<services>
      <service name="MyNamespace.OrderService" behaviorConfiguration="wsHttpCertificateBehavior">
        <endpoint address="https://iisurl/OrderService.svc/ClientCert" contract="wsHttpCertificateBehavior.IOrderService" binding="wsHttpBinding" bindingConfiguration="ClientCert">
        </endpoint>
      </service>
</services>
</sytem.serviceModel>
<system.webServer>
<security>
      <authentication>
        <windowsAuthentication enabled="true" />
        <anonymousAuthentication enabled="true" />
        <iisClientCertificateMappingAuthentication defaultLogonDomain="YourDomain" enabled="true" oneToOneCertificateMappingsEnabled="true">
          <oneToOneMappings>
            <add enabled="true" certificate="Base64HashOfTheCertificate" userName="YourUserName" password="YourPassword" />
          </oneToOneMappings>
        </iisClientCertificateMappingAuthentication>
      </authentication>
      <authorization>
        <add users="*" accessType="Allow" />
      </authorization>
      <!--Require SSL *AND* require a client certificate -->
      <access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
    </security>
</system.WebServer>

On the client:

<system.serviceModel>
      <wsHttpBinding>
        <binding name="ClientCertificate">
          <security mode="Transport">
            <transport clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding>
    <behaviors>
      <endpointBehaviors>
        <behavior name="wsHttpCertificateBehavior">
          <clientCredentials>

            <clientCertificate findValue="YourClientCertificateNameWithoutCN=" storeLocation="CurrentUser" storeName="My" x509FindType="FindBySubjectName"/>
            <serviceCertificate>
              <authentication revocationMode="NoCheck"/>
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <client>

      <endpoint name="ClientCertificate" address="https://iisurl/OrderService.svc/ClientCert" contract="MyNamespace.IOrderService" binding="wsHttpBinding" bindingConfiguration="ClientCertificate" behaviorConfiguration="wsHttpCertificateBehavior">
      </endpoint>
    </client>
</system.serviceModel>

What helped us a lot was enable Tracing, Logging in the service and the custom authorization policy and the IIS Trace Logs.

We have iisurl mapped to 127.0.0.1 in our host file, so we have trusted certifcates. For the iisClientCertificationMapping check this out.

Don't know if your ssl setup is correct. We have a powershell script for that. Some parts of it:

Generating the root certificate (powershell)

Invoke-Command -ScriptBlock{ . "C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\makecert.exe" -n "CN=YourRootCA" -r -sv YourRootCA.pvk YourRootCA.cer}


    $certFile = get-childitem $exPath | where {$_.FullName -match "GlobalVisionServicesRootCA.cer"} 
    if ($certFile -ne $NULL) { 
        echo "Discovered the YourRootCA.cer in the same folder as this script, installing it in the LocalMachine\Root certificate store.." 
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certFile.FullName)
        $rootStore = new-object system.security.cryptography.x509certificates.x509Store 'Root','LocalMachine'
        $rootStore.Open('ReadWrite')
        $rootStore.Add($cert) 
        $rootStore.Close() 
    }

Generating the server certificate (command line):

makecert.exe  -sk YourDevSrvCert -iv YourRootCA.pvk -n "CN=iisurl" -ic YourRootCA.cer -sr localmachine -ss my -sky exchange -pe yourservercertificate.cer

Generating the server client (command line):

makecert.exe  -sk ClientDevSrvCert -iv YourRootCA.pvk -n "CN=iisurl" -ic GlobalVisionServicesRootCA.cer -sr localmachine -ss my -sky exchange -pe iisurl.cer

Binding the certificates to IIS (command line, XP specific):

httpcfg.exe delete ssl -i "0.0.0.0:443"
httpcfg.exe" delete urlacl url="https://iisurl:443/"

httpcfg.exe set urlacl url="https://iisurl:443/" user=Everyone
httpcfg.exe" set ssl -i "0.0.0.0:443" -h ThumpPrint

change the ThumpPrint to the ThumpPrint of the certificate with subject name iisurl. I recommend you fully automate this with powershell, we have this to, so we can develop on multiple machines. But i can't past all of it here.

I hope this helps you. With this config, if you browse over https to the url iisurl/OrderService It asks you for a client certificate. (In IE)

You can also watch this log: C:\WINDOWS\system32\Logfiles\HTTPERR