没有证书存储的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。
我是谁得到凯恩(我们的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又开始好起来了。
再次感谢。
谢谢你的答案jules :) – Kane 2010-03-10 08:37:33
嗯,非常困惑,你不能得到的行为运行。你有没有忘记配置端点的行为?无论如何,看起来你们都找到了解决方法,所以一切都很好! – Aaronaught 2010-03-15 02:36:22
是的,我们将端点配置为使用该行为,但只有在以编程方式添加它时才能触发它。这有点奇怪,但我们没有进一步追求,因为我们稍微改变了方向。 – 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>
非常感谢你的建议。我会让你知道我如何继续。 – Kane 2010-03-09 10:42:47
不好意思问这么晚。为什么直接分配客户端证书而不是调用'SetCertificate()'?有区别吗? – ashes999 2012-07-09 19:43:50
@ ashes999:该方法没有一个需要实际的'X509Certificate'的重载。只有当您的证书已经在本地商店中的某个商店时才有用,这里明显不是这种情况。 – Aaronaught 2012-07-10 00:09:14