使用证书的 WCF 传输安全性忽略链信任

Posted

技术标签:

【中文标题】使用证书的 WCF 传输安全性忽略链信任【英文标题】:WCF Transport Security using Certificates is ignoring chain trust 【发布时间】:2010-10-30 02:01:38 【问题描述】:

我一直在尝试让 WCF 安全性为我的项目工作,但运气不佳。我正在尝试创建一个使用 net.tcp 作为绑定的服务,并同时提供消息和传输安全性。消息安全性使用用户名和密码完成,传输安全性(据说!)使用证书完成。

为了我的开发测试,我创建了自己的证书颁发机构并将此证书放在我计算机的受信任存储区 (LocalMachine) 中。然后我创建了两个证书,每个都由我的证书颁发机构签名,一个供服务使用,一个供客户端应用程序使用。我将这两个都放在 LocalMachine 的个人商店 (My) 中。然后,为了测试,我创建了一个未经我的证书颁发机构签名的随机证书(因此不受信任),并将其放在 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>

客户配置:

<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):

我的一个朋友将我指向a blog,它回答了为什么会发生这种情况的问题。显然,当您指定 TransportWithMessageCredential 时,恰好意味着:使用消息凭据进行传输。这就是为什么我的证书在传输级别被忽略的原因。

但是,我不认为该问题已完成并已关闭,因为我仍然想这样做。 :) 我将研究我认为可以插入的自定义证书验证器,看看它是否有效。我会把结果回复给大家。

编辑(2009/06/08):

不,自定义证书验证器也不起作用。 WCF 根本不调用它们。

【问题讨论】:

【参考方案1】:

我找到了解决问题的方法,但是结果比我预期的要糟糕得多。

基本上,要实现传输和消息凭据检查,您需要定义自定义绑定。 (我找到了这方面的信息here)。

我发现最简单的方法是继续在 XML 中进行配置,但在运行时复制并稍微修改 XML 配置中的 netTcp 绑定。实际上,您需要启用一个开关。这是服务端和客户端的代码:

服务端

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 证书,用于通过传输进行身份验证,另一种用于用户名和密码凭据,用于在消息级别进行身份验证。当我对 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");

【讨论】:

+1 我知道它很旧但做得很好,它看起来比我的解决方案好得多(或者我应该称之为 hack):***.com/questions/9111361/…【参考方案2】:

查看Codeplex - Intranet Section,它提供了针对不同场景的清单。另一件要提的是,在 IIS5.0 和 IIS6.0 中不支持使用 netTcpBindings - 请参阅 3rd paragraph here,仅支持 IIS7.0+。

【讨论】:

以上是关于使用证书的 WCF 传输安全性忽略链信任的主要内容,如果未能解决你的问题,请参考以下文章

WCF:无法为 SSL/TLS 安全通道建立信任关系,出现权限错误

如何设置火狐浏览器忽略安全证书

WCF 服务无法为具有权限的 SSL/TLS 安全通道建立信任关系

WCF 无法为具有权限的 SSL/TLS 安全通道建立信任关系

WCF 自签名证书在客户端上不受信任

WCF、安全和证书