Безопасность транспорта WCF с использованием сертификатов игнорирует доверие цепи - PullRequest
6 голосов
/ 30 мая 2009

Я пытался заставить безопасность WCF работать на мой проект, и мне немного не повезло. Я пытаюсь создать службу, которая использует net.tcp в качестве привязки и обеспечивает безопасность сообщений и транспорта. Защита сообщений осуществляется с использованием имени пользователя и пароля, а защита транспорта (предположительно!) - с использованием сертификатов.

Для тестирования в процессе разработки я создал свой собственный центр сертификации и поместил этот сертификат в доверенное хранилище моего компьютера (LocalMachine). Затем я создал два сертификата, каждый из которых был подписан центром сертификации, один для службы, а другой для клиентского приложения. Я поместил оба из них в Личный магазин (Мой) в LocalMachine. Затем для тестирования я создал случайный сертификат, который не был подписан моим центром сертификации (и, следовательно, не заслуживает доверия), и поместил его в личное хранилище в LocalMachine. Я использовал makecert для создания этих сертификатов.

Затем я настроил клиентское приложение, которое подключается к службе, для использования недействительного ненадежного сертификата в качестве своего клиентского сертификата. Служба настроена (предположительно) для проверки клиентских сертификатов с использованием цепного доверия. Тем не менее, этот клиент может подключиться и успешно общаться с сервисом! Он должен быть отклонен, потому что его сертификат не заслуживает доверия!

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

Сервисный конф .:

<system.serviceModel>
    <services>
        <service behaviorConfiguration="DHTestBehaviour" name="DigitallyCreated.DHTest.Business.DHTestBusinessService">
            <endpoint address="" binding="netTcpBinding" contract="DigitallyCreated.DHTest.Business.IDHTestBusinessService" bindingConfiguration="DHTestNetTcpBinding" bindingNamespace="http://www.digitallycreated.net/DHTest/v1" />

            <host>
                <baseAddresses>
                    <add baseAddress="net.tcp://localhost:8090/"/>
                    <add baseAddress="http://localhost:8091/"/>
                </baseAddresses>
            </host>
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="DHTestBehaviour">
                <serviceMetadata httpGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="true"/>
                <serviceCredentials>
                    <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="DHTestMembershipProvider"/>
                    <serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" findValue="CN=business.dhtestDHTest.com" />
                    <clientCertificate>
                        <authentication certificateValidationMode="ChainTrust" trustedStoreLocation="LocalMachine" revocationMode="NoCheck" />
                    </clientCertificate>
                </serviceCredentials>
                <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="DHTestRoleProvider" />
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <bindings>
        <netTcpBinding>
            <binding name="DHTestNetTcpBinding">
                <security mode="TransportWithMessageCredential">
                    <message clientCredentialType="UserName"/>
                    <transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign"/>
                </security>
            </binding>
        </netTcpBinding>
    </bindings>
</system.serviceModel>

Клиент Conf:

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="NetTcpBinding_IDHTestBusinessService" closeTimeout="00:01:00"
             openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
             transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
             hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288"
             maxBufferSize="65536" maxConnections="10" maxReceivedMessageSize="65536">
                <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                 maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                <reliableSession ordered="true" inactivityTimeout="00:10:00"
                 enabled="false" />
                <security mode="TransportWithMessageCredential">
                    <transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" />
                    <message clientCredentialType="UserName" />
                </security>
            </binding>
        </netTcpBinding>
    </bindings>
    <behaviors>
        <endpointBehaviors>
            <behavior name="DHTestBusinessServiceEndpointConf">
                <clientCredentials>
                    <clientCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" findValue="CN=invalid"/>
                    <serviceCertificate>
                        <authentication revocationMode="NoCheck" trustedStoreLocation="LocalMachine"/>
                    </serviceCertificate>
                </clientCredentials>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <client>
        <endpoint address="net.tcp://phoenix-iv:8090/" binding="netTcpBinding"
         behaviorConfiguration="DHTestBusinessServiceEndpointConf"
         bindingConfiguration="NetTcpBinding_IDHTestBusinessService"
         contract="DHTest.NetTcp.Business.IDHTestBusinessService"
         name="NetTcpBinding_IDHTestBusinessService">
            <identity>
                <dns value="business.dhtest.com" />
            </identity>
        </endpoint>
    </client>
</system.serviceModel>

Имя пользователя / код авторизации пароля:

DHTestBusinessServiceClient client = new DHTestBusinessServiceClient();
client.ClientCredentials.UserName.UserName = "ratfink";
client.ClientCredentials.UserName.Password = "testpassword";

Заранее благодарю за помощь.

РЕДАКТИРОВАТЬ (2009/06/01):

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

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

РЕДАКТИРОВАТЬ (2009/06/08):

Нет, специальные валидаторы сертификатов тоже не работают. WCF просто не звонит им.

Ответы [ 2 ]

4 голосов
/ 08 июня 2009

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

По сути, для выполнения проверки учетных данных как транспорта, так и сообщения необходимо определить пользовательскую привязку. (Я нашел информацию на этот счет здесь ).

Я обнаружил, что самый простой способ сделать это - продолжить выполнять настройку в XML, но во время выполнения скопировать и немного изменить привязку netTcp из конфигурации XML. Существует буквально один переключатель, который нужно включить. Вот код на стороне обслуживания и на стороне клиента:

Сервисная сторона

ServiceHost businessHost = new ServiceHost(typeof(DHTestBusinessService));
ServiceEndpoint endpoint = businessHost.Description.Endpoints[0];
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>();
sslElement.RequireClientCertificate = true; //Turn on client certificate validation
CustomBinding newBinding = new CustomBinding(bindingElements);
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding;
newBinding.Namespace = oldBinding.Namespace;
endpoint.Binding = newBinding;

Клиентская сторона

DHTestBusinessServiceClient client = new DHTestBusinessServiceClient();
ServiceEndpoint endpoint = client.Endpoint;
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>();
sslElement.RequireClientCertificate = true; //Turn on client certificate validation
CustomBinding newBinding = new CustomBinding(bindingElements);
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding;
newBinding.Namespace = oldBinding.Namespace;
endpoint.Binding = newBinding;

Вы бы подумали, что так оно и будет, но вы ошибаетесь! :) Вот где он становится лишним. Я приписывал свои конкретные методы обслуживания с PrincipalPermission, чтобы ограничить доступ на основе ролей пользователя службы следующим образом:

[PrincipalPermission(SecurityAction.Demand, Role = "StandardUser")]

Это начало сбой, как только я применил вышеуказанные изменения. Причина была в том, что

OperationContext.Current.ServiceSecurityContext.PrimaryIdentity

заканчивал тем, что был неизвестным, без имени пользователя, без аутентификации IIdentity. Это было связано с тем, что на самом деле существует две удостоверения, представляющие пользователя: одна для сертификата X509, используемая для проверки подлинности по протоколу Transport, и одна для учетных данных имени пользователя и пароля, используемых для проверки подлинности на уровне сообщений. Когда я пересмотрел двоичные файлы WCF, чтобы понять, почему он не дал мне мой PrimaryIdentity, я обнаружил, что у него есть явная строка кода, которая заставляет его возвращать этот пустой IIdentity, если он находит более одного IIdentity. Я предполагаю, что это потому, что у него нет никакого способа выяснить, какой из них является первичным одним.

Это означает, что использование атрибута PrincipalPermission находится вне окна. Вместо этого я написал метод, имитирующий его функциональность, который может иметь дело с несколькими IIdentities:

private void AssertPermissions(IEnumerable<string> rolesDemanded)
{
    IList<IIdentity> identities = OperationContext.Current.ServiceSecurityContext.AuthorizationContext.Properties["Identities"] as IList<IIdentity>;
    if (identities == null)
        throw new SecurityException("Unauthenticated access. No identities provided.");

    foreach (IIdentity identity in identities)
    {
        if (identity.IsAuthenticated == false)
            throw new SecurityException("Unauthenticated identity: " + identity.Name);
    }

    IIdentity usernameIdentity = identities.Where(id => id.GetType().Equals(typeof(GenericIdentity))).SingleOrDefault();
    string[] userRoles = Roles.GetRolesForUser(usernameIdentity.Name);

    foreach (string demandedRole in rolesDemanded)
    {
        if (userRoles.Contains(demandedRole) == false)
            throw new SecurityException("Access denied: authorisation failure.");
    }
}

Это не красиво (особенно то, как я определяю имя пользователя / пароль IIdentity), но оно работает! Теперь, в верхней части моих методов обслуживания, мне нужно назвать его так:

AssertPermissions(new [] {"StandardUser"});
0 голосов
/ 04 июня 2009

посмотрите Codeplex - Intranet Section , который предоставляет контрольные списки для различных сценариев. Еще одна вещь, которую стоит упомянуть, это то, что netTcpBindings не поддерживаются для использования в IIS5.0 и IIS6.0 - см. 3-й абзац здесь , только IIS7.0 +.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...