使用从WCF服务到Java Webservice的X.509证书对SOAP消息进行签名

这是我在网上的第一个问题。 希望它有意义。

我在网上看到了几个与此问题相关的博客,我尝试了一些没有成功的想法。 这是我的情况:

我有一个Web应用程序调用WCF Web服务,然后调用Java Web服务。 它们都在不同的服务器上。 WCF Web服务与java Web服务之间的调用未通过https,因为证书足以识别调用者(因此消息安全性)。

  • Java Web服务(黑盒子)

Java Web服务需要收到已签名的消息,并按以下方式工作:
在处理每个请求之前,处理程序拦截所有传入的消息并执行以下validation规则:
1.邮件是否包含安全标头
2.邮件是否包含正确的安全标头ID
3.邮件是否已正确签名
4.邮件是否包含KeyInfo x.509证书
5.证书是否来自可信CA-配置
6.证书是否有效(未过期,已撤销)
7.证书是否包含正确的策略OID

确认所有这些步骤后,可以处理该消息,如果任何步骤失败,则将返回soap消息exception。

SOAP安全标头应根据xxx … w3.org/TR/SOAP-dsig/数字签名规范进行validation。

最完整的描述可以在这里找到xxx … ibm.com/developerworks/webservices/library/ws-security.html这篇IBM文章列出了每个WS-Security标头的详细信息,另外还提供了一个示例签名的SOAP消息。

签署SOAP消息时,还必须将x.509证书添加到消息KeyInfo中,这是证书validation所必需的。

SOAP请求应该是这样的:

            soe1PnaGXVGrsauC61JSHD+uqGw=    Y9SRPQ9TcDu+GazO3LFwodEdhaA=   jBX/8XkY2aCte7qgXEp1sbNWmQcK/90iVL58sAvwYAEcBABGzOk2agxR0HvWrNa6ixkocAQ205lggwOxnxZJvoVozVYAAjcLtayPBOUYrnSEBFrwKWP/vxgvUDRIdXeIuw5GLY87NrTQMm1Ehf/HvMX9hTBJn4Nm8RdDiUmPcIo=   MIIEbZCCA1WgAwIBAgIES1XpMjANBgkqhkiG9w0BAQUFADBYMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFzAVBgoJkiaJk/IsZAEZFgdlbnRydXN0MRIwEAYDVQQDEwllbnRydXN0U00xEjAQBgNVBAMTCWVudHJ1c3RDQTAeFw0xMDA0MjIxMDQ4MDBaFw0xMzA0MjIxMTE4MDBaMGoxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEXMBUGCgmSJomT8ixkARkWB2VudHJ1c3QxEjAQBgNVBAMTCWVudHJ1c3RTTTESMBAGA1UEAxMJZW50cnVzdENBMRAwDgYDVQQDEwdSYnMgUmJzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMf88L2JjLPG1hNmTA/KBiC53WVwS2WU9Jh3lC1Rob6RMzOojomZ/dNrvSRB6nzWeXJpZXwik4XFrsAq24By2SZpLTO4p8Vcq71mTAfDu33cnO49Au2pwNvcMn5qIKBk1Xx+oVb4fzK9ncTRu7bW46HsIYth+qkGhbI2JEHwr/zwIDAQABo4IBrzCCAaswCwYDVR0PBAQDAgeAMCsGA1UdEAQkMCKADzIwMTAwNDIyMTA0ODAwWoEPMjAxMjA1MjgxNTE4MDBaMCMGA1UdIAQcMBowCwYJYIZIAYb6awoEMAsGCSqGSIb2fQdLAzAbBgNVHQkEFDASMBAGCSqGSIb2fQdEHTEDAgEBMIHGBgNVHR8Egb4wgbswb6BtoGukaTBnMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFzAVBgoJkiaJk/IsZAEZFgdlbnRydXN0MRIwEAYDVQQDEwllbnRydXN0U00xEjAQBgNVBAMTCWVudHJ1c3RDQTENMAsGA1UEAxMEQ1JMMTBIoEagRIZCZmlsZTovLy8vTVNJREhVLTQ0NUE0RkVFL0NSTC9lbnRydXN0Y2FfZW50cnVzdHNtX2xvY2FsX2NybGZpbGUuY3JsMB8GA1UdIwQYMBaAFBvSL6cPz8L5shubV58yf0pczKzuMB0GA1UdDgQWBBT1/j6OSS8FTjwqluvew16sv7h+VzAJBgNVHRMEAjAAMBkGCSqGSIb2fQdBAAQMMAobBFY4LjADAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQBXxRIA4HUvGSw4L+4uaR51pY4ISjUQWo2Fh7FYBMt29NsKCTdur1OWVVdndt1yjXP4yWXxoAhHtvZL+XNALUFlR2HAWiXuL1nRcxHkB98N5gPqQzW/lJk9cLtL4hVp28EiEpgmKT3I3NP2Pdb2G5MMOdvQ/GFb2y6OwblR8ViPQ8B2aHWzXMrH+0qadPAuBhXyAohwb+mMuYT/ms6xpGi1NMYuYMf6XONz9GkZgnGnMwa+9CCQws1HNz8WYHtmFIxLsVuEWc/0a1vg4IYX1Ds/ttyhJGTVXOSJSkBz8kRyj1pNBDdc1KeG8M++O8m8VgRTJvYaPc7NMiclISukGpea       0061020051    
  • WCF Web服务

我有一个服务器证书(来自可靠CA的p7b格式),我安装在我的WCF Web服务工作站(dev)使用mmc Certificate Snap-in(目前cert在受信任的发布者中)。 我不认为我需要在Java服务器上使用另一个证书,因为响应应该是明确的(既没有签名也没有加密)。 我对这个证书仍然有点困惑 – 一般都是证书 – 因为它似乎只持有一个公钥。

这是我的测试项目的app.config:

   <!--  -->                          

当我运行一个简单的测试时:
WebAS entrustService = new WebAS();
ActivationCodes certCodes = entrustService.createUser(“testNomad”);
我有错误:
失败:System.Web.Services.Protocols.SoapException:javax.xml.soap.SOAPException:肥皂消息中找不到签名元素

我如何强制每条消息的签名过程? 我以为我可以很容易地通过WCF配置来实现。 任何帮助将不胜感激 !

好。 经过几次尝试和错误,这里是使用SignedXml和IClientMessageInspector / BeforeSendRequest模式的解决方案。 非常感谢Yaron Naveh提出的相关建议。

 // Sign an XML request and return it public static string SignRequest(string request, string SubjectName, string Signature, string keyInfoRefId) { if (string.IsNullOrEmpty(request)) throw new ArgumentNullException("request"); if (string.IsNullOrEmpty(SubjectName)) throw new ArgumentNullException("SubjectName"); // Load the certificate from the certificate store. X509Certificate2 cert = GetCertificateBySubject(SubjectName); // Create a new XML document. XmlDocument doc = new XmlDocument(); // Format the document to ignore white spaces. doc.PreserveWhitespace = false; // Load the passed XML doc.LoadXml(request); // Add the declaration as per Entrust sample provided -don't think it's necessary though if (!(doc.FirstChild is XmlDeclaration)) { XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "UTF-8", string.Empty); doc.InsertBefore(declaration, doc.FirstChild); } // Remove the Action (MustUnderstand). // TODO: Need to find a more elegant way to do so XmlNode headerNode = null; XmlNodeList nodeList = doc.GetElementsByTagName("Action"); if (nodeList.Count > 0) { headerNode = nodeList[0].ParentNode; headerNode.RemoveChild(nodeList[0]); } // Set the body id - not in used but could be useful at a later stage of this project XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable); ns.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/"); XmlElement body = doc.DocumentElement.SelectSingleNode(@"//s:Body", ns) as XmlElement; if (body == null) throw new ApplicationException("No body tag found"); body.RemoveAllAttributes(); // no need to have namespace body.SetAttribute("Id", "ABC"); // Body Id could be passed as a param // Create a custom SignedXml object so that we could sign the keyinfo CustomSignedXml signedXml = new CustomSignedXml(doc); // Add the key to the SignedXml document. signedXml.SigningKey = cert.PrivateKey; // Create a new KeyInfo object. KeyInfo keyInfo = new KeyInfo(); keyInfo.Id = keyInfoRefId; // Load the certificate into a KeyInfoX509Data object // and add it to the KeyInfo object. KeyInfoX509Data keyInfoData = new KeyInfoX509Data(); keyInfoData.AddCertificate(cert); keyInfo.AddClause(keyInfoData); // Add the KeyInfo object to the SignedXml object. signedXml.KeyInfo = keyInfo; // Create a reference to be signed. Reference reference = new Reference(); reference.Uri = ""; // Add an enveloped transformation to the reference. XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(); reference.AddTransform(env); // Add the reference to the SignedXml object. signedXml.AddReference(reference); Reference reference2 = new Reference(); reference2.Uri = "#" + keyInfoRefId; signedXml.AddReference(reference2); // Add the Signature Id signedXml.Signature.Id = Signature; // Compute the signature. signedXml.ComputeSignature(); // Get the XML representation of the signature and save // it to an XmlElement object. XmlElement xmlDigitalSignature = signedXml.GetXml(); // Append the Signature element to the XML document. if (headerNode != null) { headerNode.AppendChild(doc.ImportNode(xmlDigitalSignature, true)); } return doc.InnerXml; } public static X509Certificate2 GetCertificateBySubject(string CertificateSubject) { // Check the args. if (string.IsNullOrEmpty(CertificateSubject)) throw new ArgumentNullException("CertificateSubject"); // Load the certificate from the certificate store. X509Certificate2 cert = null; X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); try { // Open the store. store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); // Find the certificate with the specified subject. cert = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateSubject, false)[0]; // Throw an exception of the certificate was not found. if (cert == null) { throw new CryptographicException("The certificate could not be found."); } } finally { // Close the store even if an exception was thrown. store.Close(); } return cert; } 

和CustomSignedXml类:

 public class CustomSignedXml : SignedXml { public CustomSignedXml(XmlDocument doc) : base(doc) { return; } public override XmlElement GetIdElement(XmlDocument doc, string id) { // see if this is the key info being referenced, otherwise fall back to default behavior if (String.Compare(id, this.KeyInfo.Id, StringComparison.OrdinalIgnoreCase) == 0) return this.KeyInfo.GetXml(); else return base.GetIdElement(doc, id); } } 

您可以捕获WCF服务发送的消息吗? 顺便说一句。 是WSDL中描述的Java服务使用的消息安全性 – 这将使事情变得更加容易。

根据您的描述,我认为您的配置是错误的,因为使用证书客户端凭据时,您需要两个证书 – 带有公钥和私钥的客户端证书以及带有公钥的服务器证书。

它也可能在您的要求中描述:

该消息是否包含KeyInfo x.509证书

证书是否从受信任的CA – 基于配置发出

为什么需要发送已安装在该服务器上的服务证书? 为什么服务检查其证书是否来自可信CA? 我想这些要求表明您必须为您的客户创建新证书。

但这些只是假设,因为实际需求通常用共享语言描述 – WSDL + WS-Security断言。

可以在多个级别上控制签名和加密。 首先,每个ServiceContractMessageContract都有属性ProtectionLevel,默认情况下为EncryptAndSign 。 您可以将其更改为Sign