Как избежать проверки сертификата при каждом вызове службы с помощью настраиваемой привязки WCF и настраиваемого валидатора

У меня есть настроенная служба WCF с пользовательской привязкой и пользовательским средством проверки сертификата.

Валидатор сертификата определяется следующим образом. Это будет расширено позже, но в настоящее время выполняется базовая проверка.

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");
        }
    }
}

Мой web.config настроен следующим образом. Цель состоит в том, чтобы использовать безопасность на транспорте и использовать сеансы. Я хочу, чтобы сертификат был проверен один раз, когда создается сеанс. Тем не менее, во время входа в систему проверки сертификатов я вижу, что проверка выполняется для каждого вызова службы, который выполняет клиент при использовании существующего открытого клиентского прокси WCF.

Я убедился, что мой экземпляр службы WCF создается один раз за сеанс (вход в конструктор вызывается один раз за сеанс). Но валидатор сертификата вызывается при каждом вызове службы. Как я могу заставить валидатор сертификата вызываться только в начале сеанса?

Учитывая, что он, похоже, использует сеансы, я предположил, что проверка сертификата будет полной сессией, и вызывался только один раз за сеанс. Я просмотрел документацию по конфигурации WCF на MSDN и не вижу способа дальнейшей настройки тега надежного сеанса или чего-либо, связанного с безопасностью, для выполнения того, что я хочу.

Вот web.config и определение сервиса

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

0 ответов

AFAIK, для этого нет подходящего решения для настройки WCF, но вы можете реализовать какой-то кеш внутри Validate метод, используя Thumbprint свойство сертификата (Thumbprint на самом деле хэш тела сертификата):

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; 
    }
}

Я предполагаю, что продолжительность сеанса намного меньше, чем срок действия сертификата, и в случае отзыва сертификата (ов) у вас есть другие средства для завершения сеансов:)

Чтобы улучшить ситуацию, вы можете добавить временную метку последнего вызова проверки и повторно подтвердить сертификат, если истек разумный срок (скажем, 30 минут):

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();
    }
}
Другие вопросы по тегам