2012-05-31 46 views
0

我有这个WCF webservice,它提供了一个非常基本的查找服务(所以没有数据在服务器端更改)。这项服务提供的信息是保密的,所以我必须采取适当的安全措施。WCF唯一标识证书问题的客户端

要做的标准事情是通过在服务绑定中要求有效的客户端证书来提供传输和消息安全性。我创建并颁发这些证书,以便控制谁可以使用此服务。但是(根据我的说法,请在这里证明我是错的)没有任何东西阻止客户与其他客户(我没有给予我的服务)交换客户证书。只要提供的客户端证书有效,服务就会接受连接。一个添加的用户名/密码机制不会改变这一点,因为这些凭证也可以交换。

您可能会说客户在法律上有义务将证书保留给自己,但这并不能为我提供足够的安全保障。有没有一种方法可以专门控制谁与我的服务连接,而不必诉诸于基于IP的访问控制(因为我的客户端使用DHCP发布的IP地址,无法将IP与客户端/证书耦合)。

我最初的问题是以某种方式使用由客户端生成的唯一标识符,该客户端在其证书存储区中安装客户端证书(如GUID)并在服务器端注册该证书,以便只有证书主题和该唯一标识符可以访问该服务。但我不知道是否有机制来做到这一点。

编辑:由于我无法控制客户端,我无法通过更改客户端上的代码轻松解决此问题。

我真的很感谢任何指针/答案!

回答

2

我使用IDispatchMessageInspector为此。虽然不确定这是否是一种性能最佳的方法,但它已证明工作得很好。

我的客户使用邮件头来发送其唯一的GUID。 每个GUID对应于每个客户端证书,并且这些都存储在Windows注册表中。 GUID由客户端生成(我使用UuidCreateSequential()

我将与您分享我的解决方案。这是我的服务代码:

public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) 
    { 
     IList<IIdentity> identities = OperationContext.Current.ServiceSecurityContext.AuthorizationContext.Properties["Identities"] as IList<IIdentity>; 
     string clientGuid = request.Headers.GetHeader<string>("Guid", "MyNamespace"); 
     if (clientGuid == null) 
      throw new FaultException("Client GUID not sent!"); 
     Guid testGuid = Guid.Empty; 

     if (!Guid.TryParse(clientGuid, out testGuid)) 
     { 
      throw new FaultException(string.Format("The format of the GUID '{0}' is not valid!", clientGuid)); 
     } 

     IIdentity X509Identity = identities.Where(x => x.AuthenticationType == "X509").SingleOrDefault(); 
     RegistryKey identityKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\MySoftware\\Identities"); 
     string storedSubjectName = (string)identityKey.GetValue(clientGuid); 

     if (storedSubjectName == null) 
     { 
      string[] valueNames = identityKey.GetValueNames(); 
      for (int idx = 0; idx < valueNames.Count(); idx++) 
      { 
       string testCN = (string)identityKey.GetValue(valueNames[idx]); 
       if (testCN == X509Identity.Name) 
       { 
        throw new FaultException(string.Format("Certificate '{0}' has already been registered!", X509Identity.Name)); 
       } 
      } 
      identityKey.SetValue(clientGuid, X509Identity.Name, RegistryValueKind.String); 
     } 
     else 
     { 
      if (storedSubjectName != X509Identity.Name) 
       throw new FaultException(string.Format("Certificate '{0}' used by the user registered with the certificate '{0}'", X509Identity.Name, storedSubjectName)); 
     } 

     identityKey.Close(); 

     return null; 
    } 

客户端上的代码:
应用程序启动时

 RegistryKey guidKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\MySoftware\\Settings"); 
     string clientGuid = (string)guidKey.GetValue("Guid"); 
     if (clientGuid == null) 
     { 
      clientGuid = UUID.mGetNewGUID().ToString(); 
      guidKey.SetValue("Guid", clientGuid, RegistryValueKind.String); 
     } 

一个辅助类独特的UUID感谢去this article

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Data; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 

static class UUID 
{ 

    // ========================================================== 
    // GUID Creation 
    // ========================================================== 
    private const int RPC_S_OK = 0; 
    private const int RPC_S_OUT_OF_MEMORY = 14; 
    private const int RPC_S_UUID_NO_ADDRESS = 1739; 

    private const int RPC_S_UUID_LOCAL_ONLY = 1824; 
    [DllImport("rpcrt4.dll", SetLastError = true)] 
    public static extern int UuidCreateSequential(ref Guid guid); 

    [DllImport("rpcrt4.dll", SetLastError = true)] 
    public static extern int UuidCreate(ref Guid guid); 

    /// <summary> 
    /// Creates the machine GUID. 
    /// </summary> 
    public static Guid mGetNewGUID() 
    { 
     Guid guidMachineGUID = default(Guid); 
     int intReturnValue = UuidCreateSequential(ref guidMachineGUID); 
     switch (intReturnValue) 
     { 
      case RPC_S_OK: 
       return guidMachineGUID; 

      case RPC_S_OUT_OF_MEMORY: 
       throw new Exception("UuidCreate returned RPC_S_OUT_OF_MEMORY"); 
      case RPC_S_UUID_NO_ADDRESS: 
       throw new Exception("UuidCreate returned RPC_S_UUID_NO_ADDRESS"); 
      case RPC_S_UUID_LOCAL_ONLY: 
       throw new Exception("UuidCreate returned RPC_S_UUID_LOCAL_ONLY"); 
      default: 
       throw new Exception("UuidCreate returned an unexpected value = " + intReturnValue.ToString()); 
     } 
    } 

} 

BeforeSendRequestIClientMessageInspector

 var guidHeader = new MessageHeader<string>(Service.ClientGuid); 
     var untypedGuid = guidHeader.GetUntypedHeader("Guid", "MyNamespace"); 
     request.Headers.Add(untypedGuid); 
+0

谢谢您的回复!它看起来很有希望,但我有一个问题:GUID如何生成并存储在客户端上?是否有数据库参与? – jscheppers

+0

@jscheppers我已更新我的回答 –

+0

谢谢!这看起来非常好!它并不完全排除证书交换的选项,因为客户端仍然可以从注册表中提取GUID,但当然会提高吧!;) – jscheppers