没有证书存储的WCF证书

没有证书存储的WCF证书

问题描述:

我的团队正在为第三方胖客户端应用程序开发许多WPF插件。 WPF插件使用WCF来使用由多个TIBCO服务发布的Web服务。胖客户端应用程序维护一个单独的中央数据存储并使用专有API访问数据存储。胖客户端和WPF插件将被部署到10,000个工作站上。我们的客户希望将胖客户使用的证书保存在中央数据存储中,以便他们无需担心重新颁发证书(目前的重新发布周期大约需要3个月),并且有机会授权证书的使用。所提出的体系结构在中央数据存储和TIBCO服务之间提供了一种共享秘密/身份验证的形式。没有证书存储的WCF证书

虽然我不一定同意建议的架构,但我们的团队无法对其进行更改,并且必须使用所提供的内容。

基本上,我们的客户希望我们能够在我们的WPF插件中构建一个机制,该机制从中央数据存储(将根据数据存储中的角色允许或拒绝)检索证书,然后使用该证书创建TIBCO服务的SSL连接。不允许使用本地机器的证书存储,并且在每个会话结束时丢弃内存版本。

所以问题是有人知道是否有可能将内存中的证书传递给WCF(.NET 3.5)服务进行SSL传输级加密?

注意:我已经提出了类似的问题(here),但之后就删除了它并重新提供了更多信息。

这是可能的。我们采用类似于相互证书认证的服务 - 服务证书,在某些情况下,客户端证书作为自动发现/单点登录机制的一部分从中央管理机构获取。

在什么情况下使用证书并不完全清楚,但在所有情况下,您需要做的是定义您自己的行为和行为元素,这些行为和行为元素来自获取证书的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

我是谁得到凯恩(我们的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

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>