2012-05-31 48 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