Dan*_*ers 6 .net c# security wcf certificate
我一直在努力让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>
Run Code Online (Sandbox Code Playgroud)
客户会议:
<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>
Run Code Online (Sandbox Code Playgroud)
客户端用户名/密码验证码:
DHTestBusinessServiceClient client = new DHTestBusinessServiceClient();
client.ClientCredentials.UserName.UserName = "ratfink";
client.ClientCredentials.UserName.Password = "testpassword";
Run Code Online (Sandbox Code Playgroud)
提前谢谢你的帮助.
编辑(2009/06/01):
我的一位朋友向我指出了一个博客,回答了为什么会发生这种情况的问题.显然,当你指定TransportWithMessageCredential是指恰好是:交通运输与消息凭据只.这就是我的证书在传输级别被忽略的原因.
但是,我不认为这个问题是完整和封闭的,因为我仍然想这样做.:)我将调查自定义证书验证器,我认为我可以插入,看看是否有效.结果我会全部回复你.
编辑(2009/06/08):
不,自定义证书验证程序也不起作用.WCF根本不会调用它们.
我找到了解决我的问题的方法,但结果证明它比我预期的要糟糕得多。
基本上,要实现传输和消息凭据检查,您需要定义自定义绑定。(我在这里找到了这种效果的信息)。
我发现最简单的方法是继续在 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;
Run Code Online (Sandbox Code Playgroud)
客户端
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;
Run Code Online (Sandbox Code Playgroud)
你会认为是这样,但你错了!:) 这是它变得特别蹩脚的地方。我将我的具体服务方法归因于 PrincipalPermission 以限制基于服务用户角色的访问,如下所示:
[PrincipalPermission(SecurityAction.Demand, Role = "StandardUser")]
Run Code Online (Sandbox Code Playgroud)
一旦我应用了上述更改,这就会开始失败。原因是因为
OperationContext.Current.ServiceSecurityContext.PrimaryIdentity
Run Code Online (Sandbox Code Playgroud)
最终成为一个未知的、无用户名的、未经身份验证的 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.");
}
}
Run Code Online (Sandbox Code Playgroud)
这不是很好(尤其是我检测用户名/密码凭证 IIdentity 的方式),但它有效!现在,在我的服务方法的顶部,我需要像这样调用它:
AssertPermissions(new [] {"StandardUser"});
Run Code Online (Sandbox Code Playgroud)