简体   繁体   中英

How to avoid cert validation on every service call with a WCF custom binding and a custom validator

I have a WCF servcie set up with custom binding and a custom cert validator.

THe cert validator is defined as follows. It will be expanded later, but is just doing a basic verification currently.

public class MyX509CertificateValidator : X509CertificateValidator
{
    private static readonly ILog Logger = LogManager.GetLogger(typeof(MyX509CertificateValidator));

    public MyX509CertificateValidator()
    {
        Logger.Info("certval - Constructor ");
    }

    public override void Validate(X509Certificate2 certificate)
    {
        Logger.Info("certval - Validate(). Calling Cert.validate()");
        bool verifyResult = certificate.Verify();
        Logger.Info("verify result: " + verifyResult);
        if (!verifyResult)
        {
            throw new SecurityTokenValidationException("cert had some bad juju");
        }
    }
}

My web.config is set up as follows. The goal is to use Transport security and use sessions. I want the cert to be validated once, when the session is being created. However, I can see through logging in the cert validator, that the validation takes place for every service call that a client makes, when using an existing open WCF client proxy.

I've verified that my WCF service instance is created once per session (logging in the constructor is being called once per session). But, the cert validator is being called every single service calls. How can I get the cert validator to be called only at the start of a session?

Given that it appears to be using sessions, I assumed that the cert verification would be sesssion-full, and invoked just once per session. I've perused the WCF configuration documentation on MSDN and do not see a way to further customize reliableSession tag, or anything related to Security to do what I wish.

Here's the web.config and the service definition

    [ServiceBehavior(AutomaticSessionShutdown = true,
        InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class WcfBasicService : IWcfBasicService
    {

...

  <system.serviceModel>
    <bindings>

        <customBinding>  
            <binding name="reliableSessionOverHttps">  
                <reliableSession/>  
                <security authenticationMode="CertificateOverTransport"/>
                <httpsTransport />  
            </binding>  
        </customBinding>  
    </bindings>

    <services>
      <service name="WcfServiceLibrary1.WcfBasicService">
        <endpoint address="" binding="customBinding" contract="WcfServiceLibrary1.IWcfBasicService" name="mainEndpoint" 
            bindingConfiguration="reliableSessionOverHttps">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>

        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceCredentials>
            <clientCertificate>
              <authentication certificateValidationMode="Custom" customCertificateValidatorType="WcfServiceLibrary1.MyX509CertificateValidator, WcfServiceLibrary1" />
            </clientCertificate>
          </serviceCredentials>

          <!-- To avoid disclosing metadata information, 
          set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
          <!-- To receive exception details in faults for debugging purposes, 
          set the value below to true.  Set to false before deployment 
          to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

AFAIK there is no any good WCF configuration solution for that, but you may implement some sort of cache inside Validate method, using Thumbprint property of the certificate ( Thumbprint is actually hash of the certificate body):

public class MyX509CertificateValidator : X509CertificateValidator
{
    private static readonly ILog Logger = LogManager.GetLogger(typeof(MyX509CertificateValidator));
    private string lastValidCertTumbprint = null;

    public MyX509CertificateValidator()
    {
        Logger.Info("certval - Constructor ");
    }

    public override void Validate(X509Certificate2 certificate)
    {
        if ((lastValidCertTumbprint != null) && (certificate.Tumbprint == lastValidCertTumbprint)) 
        { 
          return; // Fast track
        } 

        Logger.Info("certval - Validate(). Calling Cert.validate()");
        bool verifyResult = certificate.Verify();
        Logger.Info("verify result: " + verifyResult);
        if (!verifyResult)
        {
            throw new SecurityTokenValidationException("cert had some bad juju");
        }

        // The cert valid, save this fact into fast track cache
        lastValidCertTumbprint = certificate.Tumbprint; 
    }
}

I assume that session duration is far less then certificate lifetime and in case certificate(s) revoked, you have other means to terminate the sessions :)

To made things better, you may add some sort of timestamp of the last validation call and re-validate certificate if reasonable timeout (say, 30min) expired:

public class MyX509CertificateValidator : X509CertificateValidator
{
    private static readonly ILog Logger = LogManager.GetLogger(typeof(MyX509CertificateValidator));

    private string    lastValidCertTumbprint = null;
    private Stopwatch lastValidCertTimeMarker = new Stopwatch();
    private const int VALIDATION_CACHE_LIFETIME = 30*60*1000; // in ms // 30min

    public MyX509CertificateValidator()
    {
        Logger.Info("certval - Constructor ");
    }

    public override void Validate(X509Certificate2 certificate)
    {
        if ((lastValidCertTumbprint != null) 
            && (certificate.Tumbprint == lastValidCertTumbprint)
            && (lastValidCertTimeMarker.ElapsedMilliseconds < VALIDATION_CACHE_LIFETIME))
        { 
            return;  // Fast track
        }

        lastValidCertTumbprint = null;

        Logger.Info("certval - Validate(). Calling Cert.validate()");
        bool verifyResult = certificate.Verify();
        Logger.Info("verify result: " + verifyResult);
        if (!verifyResult)
        {
            throw new SecurityTokenValidationException("cert had some bad juju");
        }

        // The cert valid, save this fact into fast track cache and save timestamp
        lastValidCertTumbprint = certificate.Tumbprint;
        lastValidCertTimeMarker.Reset();
        lastValidCertTimeMarker.Start();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM