2010-03-09 225 views
12

我的團隊正在爲第三方胖客戶端應用程序開發許多WPF插件。 WPF插件使用WCF來使用由多個TIBCO服務發佈的Web服務。胖客戶端應用程序維護一個單獨的中央數據存儲並使用專有API訪問數據存儲。胖客戶端和WPF插件將被部署到10,000個工作站上。我們的客戶希望將胖客戶使用的證書保存在中央數據存儲中,以便他們無需擔心重新頒發證書(目前的重新發布週期大約需要3個月),並且有機會授權證書的使用。所提出的體系結構在中央數據存儲和TIBCO服務之間提供了一種共享祕密/身份驗證的形式。沒有證書存儲的WCF證書

雖然我不一定同意建議的架構,但我們的團隊無法對其進行更改,並且必須使用所提供的內容。

基本上,我們的客戶希望我們能夠在我們的WPF插件中構建一個機制,該機制從中央數據存儲(將根據數據存儲中的角色允許或拒絕)檢索證書,然後使用該證書創建TIBCO服務的SSL連接。不允許使用本地機器的證書存儲,並且在每個會話結束時丟棄內存版本。

所以問題是有人知道是否有可能將內存中的證書傳遞給WCF(.NET 3.5)服務進行SSL傳輸級加密?

注意:我已經提出了類似的問題(here),但之後就刪除了它並重新提供了更多信息。

回答

13

這是可能的。我們採用類似於相互證書認證的服務 - 服務證書,在某些情況下,客戶端證書作爲自動發現/單點登錄機制的一部分從中央管理機構獲取。

在什麼情況下使用證書並不完全清楚,但在所有情況下,您需要做的是定義您自己的行爲和行爲元素,這些行爲和行爲元素來自獲取證書的System.ServiceModel.Description命名空間中的特定行爲/元素。我暫時假定它是一個客戶端證書。首先,你必須寫行爲,這是這樣的:

public class MyCredentials : ClientCredentials 
{ 
    public override void ApplyClientBehavior(ServiceEndpoint endpoint, 
     ClientRuntime behavior) 
    { 
     // Assuming GetCertificateFromNetwork retrieves from CDS 
     ClientCertificate.Certificate = GetCertificateFromNetwork(); 
    } 

    protected override ClientCredentials CloneCore() 
    { 
     // ... 
    } 
} 

現在,你需要創建一個可以去的XML配置元素:

public class MyCredentialsExtensionElement : ClientCredentialsElement 
{ 
    protected override object CreateBehavior() 
    { 
     return new MyCredentials(); 
    } 

    public override Type BehaviorType 
    { 
     get { return typeof(MyCredentials); } 
    } 

    // Snip other overrides like Properties 
} 

在這之後,你可以添加策略你的WCF配置:

<behaviors> 
    <endpointBehaviors> 
     <behavior name="MyEndpointBehavior"> 
      <myCredentials/> 
     </behavior> 
    </endpointBehaviors> 
</behaviors> 

編輯:差點忘了提,你需要註冊擴展:

<system.serviceModel> 
    <extensions> 
     <behaviorExtensions> 
      <add name="myCredentials" 
       type="MyAssembly.MyCredentialsExtensionElement, MyAssembly, 
         Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 
     </behaviorExtensions> 
    </extensions> 
</system.serviceModel> 

希望有幫助。如果您需要更多關於所有這些課程安排的細節以及幕後發生的情況,請嘗試閱讀Extending WCF with Custom Behaviors

+0

非常感謝你的建議。我會讓你知道我如何繼續。 – Kane 2010-03-09 10:42:47

+0

不好意思問這麼晚。爲什麼直接分配客戶端證書而不是調用'SetCertificate()'?有區別嗎? – ashes999 2012-07-09 19:43:50

+0

@ ashes999:該方法沒有一個需要實際的'X509Certificate'的重載。只有當您的證書已經在本地商店中的某個商店時纔有用,這裏明顯不是這種情況。 – Aaronaught 2012-07-10 00:09:14

4

我是誰得到凱恩(我們的SO走狗!)問原來的問題。我認爲我最終會創建一個帳戶,併發布我們的發現/結果/經驗,以答覆Aaronaught發佈的答案(所以上面的任何信用)。

我們試圖添加自定義行爲上面,並設置端點配置元素的behaviourConfiguration使用它的建議。我們無法獲得代碼,因此最終只能採用編程方法。

,因爲我們有一個包裝類設置爲打造我們用我們現有的創作功能建設ClientBase的所有其他部分之後添加行爲的ClientBase對象。

我們碰到了這樣做也,即一個ClientCredentials行爲已經被我們ClientBase用戶名和密碼,而不是我們的證書+用戶名和密碼驗證定義的幾個問題。因此,我們在添加基於證書的新行爲(通過註冊用戶名和密碼)作爲測試的臨時措施之前,以編程方式刪除了現有行爲。仍然沒有骰子,正在構建我們的行爲和ApplyClientBehavior被解僱了,但服務仍然翻倒調用被調用時(我們從來沒有得到真正的例外,由於一堆使用難以重構出報表)。

然後我們決定不要刪除現有的ClientCredentials行爲,我們只需將我們的證書注入到其中,然後讓所有批處理照常進行。第三次是一種魅力,現在全部結束了。

我想感謝Aaronaught(如果可以的話,我會投票贊成的!),以便讓我們走上正確的道路並提供一個深思熟慮並且有用的答案。

下面有它的啓動和運行(使用試驗.CRT文件)小代碼段。

 protected override ClientBase<TChannel> CreateClientBase(string endpointConfigurationName) 
    { 
     ClientBase<TChannel> clientBase = new ClientBase<TChannel>(endpointConfigurationName); // Construct yours however you want here 

     // ... 

     ClientCredentials credentials = clientBase.Endpoint.Behaviors.Find<ClientCredentials>(); 

     X509Certificate2 certificate = new X509Certificate2(); 
     byte[] rawCertificateData = File.ReadAllBytes(@"C:\Path\To\YourCert.crt"); 
     certificate.Import(rawCertificateData); 

     credentials.ClientCertificate.Certificate = certificate; 

     return clientBase; 
    } 

至於另一個方面說明,因爲測試中,我們移除了本地計算機存儲我們的所有證書的一部分,這種使用招實際上造成了問題。 Fiddler沒有檢測到我們的客戶端證書,因爲它純粹是在內存中,而不是在可信存儲中。如果我們將它重新添加到可信商店,那麼Fiddler又開始好起來了。

再次感謝。

+0

謝謝你的答案jules :) – Kane 2010-03-10 08:37:33

+0

嗯,非常困惑,你不能得到的行爲運行。你有沒有忘記配置端點的行爲?無論如何,看起來你們都找到了解決方法,所以一切都很好! – Aaronaught 2010-03-15 02:36:22

+0

是的,我們將端點配置爲使用該行爲,但只有在以編程方式添加它時才能觸發它。這有點奇怪,但我們沒有進一步追求,因爲我們稍微改變了方向。 – 2010-03-15 03:25:22

0

Aaronaught有正確的想法,但我不得不做出一些修改,以得到它的工作。我使用的實現如下。我增加了一些功能,可以從嵌入式資源獲取證書。

using System.IO; 
using System.Linq; 
using System.Reflection; 
using System.Security.Cryptography.X509Certificates; 
using System.ServiceModel.Configuration; 
using System.Configuration; 
using System.ServiceModel.Description; 

namespace System.ServiceModel.Description 
{ 
    /// <summary> 
    /// Uses a X509 certificate from disk as credentials for the client. 
    /// </summary> 
    public class ClientCertificateCredentialsFromFile : ClientCredentials 
    { 
     public ClientCertificateCredentialsFromFile(CertificateSource certificateSource, string certificateLocation) 
     { 
      if (!Enum.IsDefined(typeof(CertificateSource), certificateSource)) { throw new ArgumentOutOfRangeException(nameof(certificateSource), $"{nameof(certificateSource)} contained an unexpected value."); } 
      if (string.IsNullOrWhiteSpace(certificateLocation)) { throw new ArgumentNullException(nameof(certificateLocation)); } 

      _certificateSource = certificateSource; 
      _certificateLocation = certificateLocation; 

      ClientCertificate.Certificate = certificateSource == CertificateSource.EmbeddedResource ? 
       GetCertificateFromEmbeddedResource(certificateLocation) 
       : GetCertificateFromDisk(certificateLocation); 
     } 

     /// <summary> 
     /// Retrieves a certificate from an embedded resource. 
     /// </summary> 
     /// <param name="certificateLocation">The certificate location and assembly information. Example: The.Namespace.certificate.cer, Assembly.Name</param> 
     /// <returns>A new instance of the embedded certificate.</returns> 
     private static X509Certificate2 GetCertificateFromEmbeddedResource(string certificateLocation) 
     { 
      X509Certificate2 result = null; 

      string[] parts = certificateLocation.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 
      if (parts.Length < 2) { throw new ArgumentException($"{certificateLocation} was expected to have a format of namespace.resource.extension, assemblyName"); } 
      string assemblyName = string.Join(",", parts.Skip(1)); 

      var assembly = Assembly.Load(assemblyName); 
      using (var stream = assembly.GetManifestResourceStream(parts[0])) 
      { 
       var bytes = new byte[stream.Length]; 
       stream.Read(bytes, 0, bytes.Length); 
       result = new X509Certificate2(bytes); 
      } 

      return result; 
     } 

     /// <summary> 
     /// Retrieves a certificate from disk. 
     /// </summary> 
     /// <param name="certificateLocation">The file path to the certificate.</param> 
     /// <returns>A new instance of the certificate from disk</returns> 
     private static X509Certificate2 GetCertificateFromDisk(string certificateLocation) 
     { 
      if (!File.Exists(certificateLocation)) { throw new ArgumentException($"File {certificateLocation} not found."); } 
      return new X509Certificate2(certificateLocation); 
     } 


     /// <summary> 
     /// Used to keep track of the source of the certificate. This is needed when this object is cloned. 
     /// </summary> 
     private readonly CertificateSource _certificateSource; 

     /// <summary> 
     /// Used to keep track of the location of the certificate. This is needed when this object is cloned. 
     /// </summary> 
     private readonly string _certificateLocation; 

     /// <summary> 
     /// Creates a duplicate instance of this object. 
     /// </summary> 
     /// <remarks> 
     /// A new instance of the certificate is created.</remarks> 
     /// <returns>A new instance of <see cref="ClientCertificateCredentialsFromFile"/></returns> 
     protected override ClientCredentials CloneCore() 
     { 
      return new ClientCertificateCredentialsFromFile(_certificateSource, _certificateLocation); 
     } 
    } 
} 


namespace System.ServiceModel.Configuration 
{ 
    /// <summary> 
    /// Configuration element for <see cref="ClientCertificateCredentialsFromFile"/> 
    /// </summary> 
    /// <remarks> 
    /// When configuring the behavior an extension has to be registered first. 
    /// <code> 
    /// <![CDATA[ 
    /// <extensions> 
    ///  <behaviorExtensions> 
    ///   <add name = "clientCertificateCredentialsFromFile" 
    ///    type="System.ServiceModel.Configuration.ClientCertificateCredentialsFromFileElement, Assembly.Name" /> 
    ///  </behaviorExtensions> 
    /// </extensions> 
    /// ]]> 
    /// </code> 
    /// Once the behavior is registered it can be used as follows. 
    /// <code> 
    /// <![CDATA[ 
    /// <behaviors> 
    ///  <endpointBehaviors> 
    ///   <behavior name = "BehaviorConfigurationName" > 
    ///    <clientCertificateCredentialsFromFile fileLocation="C:\certificates\paypal_cert.cer" /> 
    ///   </behavior> 
    ///  </endpointBehaviors> 
    /// </behaviors> 
    /// <client> 
    ///  <endpoint address="https://endpoint.domain.com/path/" behaviorConfiguration="BehaviorConfigurationName" ... /> 
    /// </client> 
    /// ]]> 
    /// </code> 
    /// </remarks> 
    public class ClientCertificateCredentialsFromFileElement : BehaviorExtensionElement 
    { 
     /// <summary> 
     /// Creates a new <see cref="ClientCertificateCredentialsFromFile"/> from this configuration element. 
     /// </summary> 
     /// <returns>The newly configured <see cref="ClientCertificateCredentialsFromFile"/></returns> 
     protected override object CreateBehavior() 
     { 
      return new ClientCertificateCredentialsFromFile(Source, Location); 
     } 

     /// <summary> 
     /// Returns <code>typeof(<see cref="ClientCertificateCredentialsFromFile"/>);</code> 
     /// </summary> 
     public override Type BehaviorType 
     { 
      get 
      { 
       return typeof(ClientCertificateCredentialsFromFile); 
      } 
     } 

     /// <summary> 
     /// An attribute used to configure the file location of the certificate to use for the client's credentials. 
     /// </summary> 
     [ConfigurationProperty("location", IsRequired = true)] 
     public string Location 
     { 
      get 
      { 
       return this["location"] as string; 
      } 
      set 
      { 
       this["location"] = value; 
      } 
     } 

     /// <summary> 
     /// An attribute used to configure where the certificate should should be loaded from. 
     /// </summary> 
     [ConfigurationProperty("source", IsRequired = true)] 
     public CertificateSource Source 
     { 
      get 
      { 
       return (CertificateSource)this["source"]; 
      } 
      set 
      { 
       this["source"] = value; 
      } 
     } 
    } 

    /// <summary> 
    /// Used to declare the source of a certificate. 
    /// </summary> 
    public enum CertificateSource 
    { 
     FileOnDisk, 
     EmbeddedResource 
    } 
} 

使用上面的代碼,我就能夠配置我的客戶如下

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <system.serviceModel> 
     <extensions> 
      <behaviorExtensions> 
       <add name="clientCertificateCredentialsFromFile" 
        type="System.ServiceModel.Configuration.ClientCertificateCredentialsFromFileElement, My.Project.PayPal" /> 
      </behaviorExtensions> 
     </extensions> 

     <bindings> 
      <basicHttpBinding> 
       <binding name="PayPalAPISoapBinding"> 
        <security mode="Transport"> 
         <transport clientCredentialType="Certificate" /> 
        </security> 
       </binding> 
       <binding name="PayPalAPIAASoapBinding"> 
        <security mode="Transport"> 
         <transport clientCredentialType="Certificate" /> 
        </security> 
       </binding> 
      </basicHttpBinding> 
     </bindings> 
     <behaviors> 
      <endpointBehaviors> 
       <behavior name="PayPalAPICredentialBehavior"> 
        <clientCertificateCredentialsFromFile source="EmbeddedResource" location="My.Project.PayPal.Test.Integration.paypal_cert.cer, My.Project.PayPal.Test.Integration" /> 
       </behavior> 
       <behavior name="PayPalAPIAACredentialBehavior"> 
        <clientCertificateCredentialsFromFile source="EmbeddedResource" location="My.Project.PayPal.Test.Integration.paypal_cert.cer, My.Project.PayPal.Test.Integration" /> 
       </behavior> 
      </endpointBehaviors> 
     </behaviors> 
     <client> 
      <endpoint 
       address="https://api.sandbox.paypal.com/2.0/" 
       behaviorConfiguration="PayPalAPICredentialBehavior" 
       binding="basicHttpBinding" 
       bindingConfiguration="PayPalAPISoapBinding" 
       contract="My.Project.PayPal.Proxy.PayPalAPIInterface" 
       name="PayPalAPI" /> 
      <endpoint 
       address="https://api-aa.sandbox.paypal.com/2.0/" 
       behaviorConfiguration="PayPalAPIAACredentialBehavior" 
       binding="basicHttpBinding" 
       bindingConfiguration="PayPalAPIAASoapBinding" 
       contract="My.Project.PayPal.Proxy.PayPalAPIAAInterface" 
       name="PayPalAPIAA" /> 
     </client> 
    </system.serviceModel> 
</configuration>