Федеративная безопасность — отдельные сертификаты SSL и RP (.NET 4.5 и WIF)

#.net #wcf #wif #net.tcp #sts-securitytokenservice

#.net #wcf #wif #net.tcp #sts-securitytokenservice

Вопрос:

В настоящее время я работаю над решением, использующим STS, клиент и службу WCF, которая используется клиентом. В настоящее время все это выполняется с помощью конфигурации, при которой клиент успешно извлекает токен и передает его службе WCF.

Проблема возникает с сертификатами, мы используем привязку net.tcp, защищенную транспортной безопасностью, а также токеном безопасности, и в качестве требования для этого нам требуется сертификат SSL. Этот сертификат настроен следующим образом (я удалил ненужный xml):

 <behavior name="Federated">
  <serviceAuthorization principalPermissionMode="Always" />
  <serviceCredentials  useIdentityConfiguration="true">
    <serviceCertificate findValue="CN=SSLCert" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" />
  </serviceCredentials>
</behavior>
  

Проблема в том, что указанный здесь сертификат службы также является сертификатом, который WIF использует для расшифровки токена, который он получает, поскольку проверяющая сторона в этом случае распределена по нескольким машинам, при этом токены передаются между ними, недопустимо использовать SSL-сертификат в качестве сертификата шифрования (RP).

Есть ли способ указать отдельный сертификат SSL и сертификат шифрования для привязки net.tcp или они всегда должны быть одинаковыми?

Просто для повторной итерации поток токена выглядит следующим образом:

sts * (зашифрованный) * > клиент * (зашифрованный) * > dmz-broker * (требуется расшифровка) * > внутренний сервер * (требуется расшифровка)*

Я попытался изменить сертификат службы на сертификат шифрования, но затем он использует его для SSL и завершается неудачей. Я также попытался установить идентификатор конечной точки, указав сертификаты и значения DNS, но все безуспешно.

Заранее спасибо за любую помощь.

Ответ №1:

В конечном итоге мне удалось решить эту проблему, используя пользовательский распознаватель SecurityToken. Это включало копирование SimpleTokenResolver, который является стандартным классом .NET (http://referencesource.microsoft.com/#System .IdentityModel/System/IdentityModel/Selectors/SecurityTokenResolver.cs), а затем создает его, передавая токен безопасности, который относится к сертификату, используемому для расшифровки токена.

Мы можем видеть в исходном коде .NET 4.5, что при инициализации WIF создается распознаватель токенов, передающий сертификат службы в качестве токена:

  SecurityTokenResolver serviceCertificateResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(new ReadOnlyCollection<SecurityToken>(
                                                  new SecurityToken[] { new X509SecurityToken(this.ServiceCertificate) }), false);
  

Это означает, что платформа по умолчанию создает распознаватель, который расшифровывает с использованием точно такого же сертификата, который вы указываете для SSL.

К сожалению, SimpleTokenResolver, который используется внутри метода CreateDefaultSecurityTokenResolver, является закрытым и не может быть унаследован или переопределен, однако, взяв код по ссылке выше и передав правильный сертификат в конструкторе (который можно прочитать из настроек приложения), вы можете добавить свой собственный распознаватель.

 public CustomSecurityTokenResolver()
            : this(new ReadOnlyCollection<SecurityToken>(new SecurityToken[] { new X509SecurityToken(CertificateHelper.GetFromAppSetting("EncryptionCertificate")) }), false)
{

}
  

Затем этот распознаватель токенов может быть указан в конфигурации следующим образом:

 <system.identityModel>
  <identityConfiguration>
    <securityTokenHandlers>
      <securityTokenHandlerConfiguration>
        <serviceTokenResolver type="MySecurity.CustomSecurityTokenResolver, MySecurity">
        </serviceTokenResolver>
      </securityTokenHandlerConfiguration>
    </securityTokenHandlers>
  </identityConfiguration>
</system.identityModel>
  

Обратите внимание, что другой распознаватель все еще добавлен в коллекцию распознавателей токенов безопасности, и этот будет выбран после значения по умолчанию, созданного платформой.

Код для всего пользовательского преобразователя показан ниже:

 public class CustomSecurityTokenResolver: SecurityTokenResolver
{
    ReadOnlyCollection<SecurityToken> tokens;
        bool canMatchLocalId;


    public CustomSecurityTokenResolver()
            : this(new ReadOnlyCollection<SecurityToken>(new SecurityToken[] { new X509SecurityToken(CertificateHelper.GetFromAppSetting("EncryptionCertificate")) }), false)
    {

    }

    public CustomSecurityTokenResolver(ReadOnlyCollection<SecurityToken> tokens, bool canMatchLocalId)
    {
        this.tokens = tokens;
        this.canMatchLocalId = canMatchLocalId;
    }

    protected override bool TryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key)
    {


        key = null;
        for (int i = 0; i < this.tokens.Count;   i)
        {
            SecurityKey securityKey = this.tokens[i].ResolveKeyIdentifierClause(keyIdentifierClause);
            if (securityKey != null)
            {
                key = securityKey;
                return true;
            }
        }

        if (keyIdentifierClause is EncryptedKeyIdentifierClause)
        {
            EncryptedKeyIdentifierClause keyClause = (EncryptedKeyIdentifierClause)keyIdentifierClause;
            SecurityKeyIdentifier keyIdentifier = keyClause.EncryptingKeyIdentifier;
            if (keyIdentifier != null amp;amp; keyIdentifier.Count > 0)
            {
                for (int i = 0; i < keyIdentifier.Count; i  )
                {
                    SecurityKey unwrappingSecurityKey = null;
                    if (TryResolveSecurityKey(keyIdentifier[i], out unwrappingSecurityKey))
                    {
                        byte[] wrappedKey = keyClause.GetEncryptedKey();
                        string wrappingAlgorithm = keyClause.EncryptionMethod;
                        byte[] unwrappedKey = unwrappingSecurityKey.DecryptKey(wrappingAlgorithm, wrappedKey);
                        key = new InMemorySymmetricSecurityKey(unwrappedKey, false);
                        return true;
                    }
                }
            }
        }

        return key != null;
    }

    protected override bool TryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token)
    {
        token = null;
        for (int i = 0; i < keyIdentifier.Count;   i)
        {

            SecurityToken securityToken = ResolveSecurityToken(keyIdentifier[i]);
            if (securityToken != null)
            {
                token = securityToken;
                break;
            }
        }

            return (token != null);
    }

    protected override bool TryResolveTokenCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityToken token)
    {

        token = null;

        SecurityToken securityToken = ResolveSecurityToken(keyIdentifierClause);
        if (securityToken != null)
            token = securityToken;

        return (token != null);
    }

    SecurityToken ResolveSecurityToken(SecurityKeyIdentifierClause keyIdentifierClause)
    {

        if (!this.canMatchLocalId amp;amp; keyIdentifierClause is LocalIdKeyIdentifierClause)
            return null;

        for (int i = 0; i < this.tokens.Count;   i)
        {
            if (this.tokens[i].MatchesKeyIdentifierClause(keyIdentifierClause))
                return this.tokens[i];
        }

        return null;
    }
}
  

Комментарии:

1. CustomSecurityTokenResolver также может быть задано программно: msdn.microsoft.com/en-us/library/ee748480.aspx

Ответ №2:

Это можно исправить без создания пользовательского ServiceTokenResolver .

.NET 4.5 : использовать System.IdentityModel.ServiceConfiguration

 public class Service1 : IService1
{
    public static void Configure(ServiceConfiguration config)
    {
        config.IdentityConfiguration.SecurityTokenHandlers.Configuration.ServiceTokenResolver =
          SecurityTokenResolver.CreateDefaultSecurityTokenResolver(new ReadOnlyCollection<SecurityToken>(
              new SecurityToken[]
              {
                  new X509SecurityToken(Util.GetEncryptionCert())
              }), false);
    }
}
  

https://learn.microsoft.com/en-us/dotnet/framework/wcf/configuring-wcf-services-in-code

До версии 4.5: используйте Microsoft.IdentityModel.Configuration.ServiceConfiguration

 using (ServiceHost host = new ServiceHost(typeof(HelloWorldService), baseAddress))
{
    var config = new Microsoft.IdentityModel.Configuration.ServiceConfiguration();

    config.SecurityTokenHandlers.Configuration.ServiceTokenResolver =
        SecurityTokenResolver.CreateDefaultSecurityTokenResolver(new ReadOnlyCollection<SecurityToken>(
        new SecurityToken[]
        {
            new X509SecurityToken(Util.GetEncryptionCert())
        }), false);

    FederatedServiceCredentials.ConfigureServiceHost(serviceHost, config);

    host.Open();

    // Close the ServiceHost.
    host.Close();
}
  

https://msdn.microsoft.com/en-us/library/microsoft.identitymodel.tokens.federatedservicecredentials.configureservicehost.aspx