Search code examples
wcfiis-7wcf-bindingwcf-securityx509certificate

WCF message authentication with both username and certificate


Long story short:

My WCF clients should be able to provide both username and certificate to a service hosted in IIS, where I should use that information to validate requests using a custom policies.

Complete story:

I have the need to authenticate some WCF clients to verify if they can execute operations.

We have two kinds of clients: WPF applications and a web application. We would like to do the following:

  • The web application uses a certificate trusted by the service so that it is recognized as a special user with all permissions (the web application already verifies permissions by itself and we wouldn't like to touch it by now)
  • The WPF clients authenticate themselves with username/password provided by the user

In the implementation of the operations, I would like to verify if the certificate was provided (then I recognize the "super user"), otherwise fallback to username/password authentication.

Services are hosted in IIS 7 and we need to use NetTcpBinding. I was able to implement the username validation, but the problem is that the AuthorizationContext inspected by the service contains only identity information, and not the certificate. The following code is used on the client side to initialize the creation of channels (from a spike I'm using to test the solution):

var factory = new ChannelFactory<T>(this.Binding, address);

        var defaultCredentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
        factory.Endpoint.Behaviors.Remove(defaultCredentials);

        var loginCredentials = new ClientCredentials();
        loginCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
                     X509CertificateValidationMode.None;
        loginCredentials.UserName.UserName = username;
        loginCredentials.UserName.Password = password;
        if (useCertificate)
        {
            loginCredentials.SetCertificate();
        }

        factory.Endpoint.Behaviors.Add(loginCredentials);
        return factory.CreateChannel();

With the SetCertificate extension being implemented like this:

public static void SetCertificate(this ClientCredentials loginCredentials)
    {
        loginCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SecureWcfClient");
    }

This is the configuration of the web application hosting the services:

<system.serviceModel>
<behaviors>
  <serviceBehaviors>
    <behavior name="SecureBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true" />
      <serviceCredentials>
        <serviceCertificate findValue="Test"
              storeLocation="LocalMachine"
              storeName="My"
              x509FindType="FindBySubjectName" />
        <clientCertificate>
          <authentication certificateValidationMode="Custom" customCertificateValidatorType="AuthenticationProtectedService.Security.CertificateValidator, AuthenticationProtectedService.Security"/>
        </clientCertificate>
        <userNameAuthentication userNamePasswordValidationMode="Custom"
         customUserNamePasswordValidatorType="AuthenticationProtectedService.Security.UserNamePassValidator, AuthenticationProtectedService.Security" />
      </serviceCredentials>
      <serviceAuthorization serviceAuthorizationManagerType="AuthenticationProtectedService.Security.CertificateAuthorizationManager, AuthenticationProtectedService.Security"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
<bindings>
  <netTcpBinding>
    <binding>
      <security mode="None"/>
    </binding>
    <binding name="SecureNetTcp">
      <security mode="Message">
        <message clientCredentialType="UserName"/>
      </security>
    </binding>
  </netTcpBinding>
</bindings>
  <service
  name="AuthenticationProtectedService.Services.OneWayServiceB"
  behaviorConfiguration="SecureBehavior">
    <endpoint
        address=""
        binding="wsHttpBinding"
        contract="AuthenticationProtectedService.ServiceModel.IOneWayServiceB">
    </endpoint>
  </service>
  <service
  name="AuthenticationProtectedService.Services.DuplexServiceB" behaviorConfiguration="SecureBehavior">
    <endpoint
        address=""
        binding="netTcpBinding"
        bindingConfiguration="SecureNetTcp"
        contract="AuthenticationProtectedService.ServiceModel.IDuplexServiceB">
    </endpoint>
    <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
  </service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

Finally, this is the implementation of the custom authorization manager (I also tried with a custom certificate validator but the function was never run)

public class CertificateAuthorizationManager : ServiceAuthorizationManager
{
    protected override bool CheckAccessCore(OperationContext operationContext)
    {
        if (!base.CheckAccessCore(operationContext))
        {
            return false;
        }

        string thumbprint = GetCertificateThumbprint(operationContext);

        // I'd need to verify the thumbprint, but it is always null
        return true;
    }

    private string GetCertificateThumbprint(OperationContext operationContext)
    {
        foreach (var claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
        {
            foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Thumbprint, Rights.Identity))
            {
                string tb = BitConverter.ToString((byte[])claim.Resource);
                tb = tb.Replace("-", "");
                return tb;
            }
        }

        return null;
    }
}

I think that the problem could be in the clientCredentialType property of the nettcpbinding.Security.Message node on the service configuration, but I don't see the option to use both Certificate and Username withing the Message security.

Any help appreciated, thanks

Remark: a specific goal of the project is to have very low level impact on server setup and in general in the system, so also SSL should be avoided if possible.


Solution

  • try out this link http://msdn.microsoft.com/en-us/library/ms733099.aspx ...it might resolve your issue where in you can have different binding configuration for same binding type and associate the same to different endpoints as per your need.