C# 在对SOAP请求进行数字签名时指定#Body的引用URI-使用WCF
我正在使用WCF客户端与非WCF web服务进行对话 此web服务要求对SOAP消息体进行签名,但是,我在生成有效的SOAP请求时遇到了问题 我实现了一个ClientMessageInspector,它继承自IClientMessageInspector,在这里我修改了BeforeSendRequest方法中的消息以添加XML数字签名。我使用SignedXML类来实现这一点 我正在使用ibmwebservicesvalidationtoolforwsdl和SOAP来检查我的数字签名是否能够验证 我的问题是,当我在引用URI中指定一个完整的名称空间引用时,我正在使用的IBM工具会说我有一个有效的签名。根据XML数字签名规范,我应该只能够引用属性,而不需要名称空间,但是,当我这样做时,我没有得到有效的数字签名 这是我当前生成的SOAP请求,我的工具说它有一个有效的签名,但web服务不喜欢它:C# 在对SOAP请求进行数字签名时指定#Body的引用URI-使用WCF,c#,wcf,soap,C#,Wcf,Soap,我正在使用WCF客户端与非WCF web服务进行对话 此web服务要求对SOAP消息体进行签名,但是,我在生成有效的SOAP请求时遇到了问题 我实现了一个ClientMessageInspector,它继承自IClientMessageInspector,在这里我修改了BeforeSendRequest方法中的消息以添加XML数字签名。我使用SignedXML类来实现这一点 我正在使用ibmwebservicesvalidationtoolforwsdl和SOAP来检查我的数字签名是否能够验证
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<s:Header>
<soapsec:Signature>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="http://schemas.xmlsoap.org/soap/security/2000-12#Body">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate>
</X509Data>
<KeyValue>
<RSAKeyValue>
<Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</soapsec:Signature>
</s:Header>
<s:Body soapsec:id="Body">
.... SOAP Body Here ...
</s:Body>
</s:Envelope>
有人知道我如何将引用URI设置为#Body,但也可以使用有效的XML签名吗?因为我试图为SOAP请求生成签名,所以我通过对SignedXml进行子类化,覆盖GetIdElement来解决这个问题,这样我就可以返回我要查找的任何元素。在这种情况下,id元素将属于名称空间 我还意识到,使用IBMWebServicesValidationToolforWSDL和SOAP等工具来验证SOAP请求的数字签名是行不通的。相反,我使用以下方法来验证签名:
public static bool verifyDigitalSignatureForString(string msgAsString)
{
XmlDocument verifyDoc = new XmlDocument();
verifyDoc.PreserveWhitespace = true;
verifyDoc.LoadXml(msgAsString);
SignedXmlWithId verifyXml = new SignedXmlWithId(verifyDoc);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = verifyDoc.GetElementsByTagName("Signature");
// Load the signature node.
verifyXml.LoadXml((XmlElement)nodeList[0]);
if (verifyXml.CheckSignature())
{
Console.WriteLine("Digital signature is valid");
return true;
}
else
{
Console.WriteLine("Digital signature is not valid");
return false;
}
}
请问您为什么要手动生成签名,而不是使用WCF安全功能为您生成签名?您是否发现任何不允许您使用WCF安全性的内容?顺便问一句,您是否有带有WS-Policy的WSDL或来自某个现有客户机的有效服务请求?这些通常是构建工作客户端所必需的构件。不幸的是,web服务没有WSDL。我首先尝试使用WCF安全性来实现这一点,但遇到了我描述的问题。建议的解决方案不起作用,所以我开始尝试一种不同的策略,这就是我现在的处境。我已经要求该服务的主机向我发送一个有效的请求,但我还没有。我所说的服务不是WCF,因此我认为这使得使用WCF OOTB变得更加困难。不过,我确实为该服务提供了XSD文件,我使用XSD.exe从这些文件创建了C#类,并且在我的服务接口上设置了XmlSerializerFormat标志,以允许我使用生成的类。
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(request.ToString());
// Add the required namespaces to the SOAP Envelope element, if I don't do this, the web service I'm calling returns an error
string soapSecNS = "http://schemas.xmlsoap.org/soap/security/2000-12";
string soapEnvNS = "http://www.w3.org/2003/05/soap-envelope";
//Get the header element, so that we can add the digital signature to it
XmlNode headerNode = doc.GetElementsByTagName("Header", soapEnvNS)[0];
// Set the ID attribute on the body element, so that we can reference it later
XmlNode bodyNode = doc.GetElementsByTagName("Body", soapEnvNS)[0];
((XmlElement)bodyNode).RemoveAllAttributes();
((XmlElement)bodyNode).SetAttribute("id", soapSecNS, "Body");
XmlWriterSettings settings2 = new XmlWriterSettings();
settings2.Encoding = new System.Text.UTF8Encoding(false);
// Load the certificate we want to use for signing
SignedXmlWithId signedXml = new SignedXmlWithId(doc);
X509Certificate2 cert = new X509Certificate2("C:\\myCertificate.pfx", "myPassword");
signedXml.SigningKey = cert.PrivateKey;
//Populate the KeyInfo element correctly, with the public cert and public key
Signature sigElement = signedXml.Signature;
KeyInfoX509Data x509Data = new KeyInfoX509Data(cert);
sigElement.KeyInfo.AddClause(x509Data);
RSAKeyValue rsaKeyValue = new RSAKeyValue((RSA)cert.PublicKey.Key);
sigElement.KeyInfo.AddClause(rsaKeyValue);
// Create a reference to be signed, only sign the body of the SOAP request, which we have given an
// ID attribute to, in order to reference it correctly here
Reference reference = new Reference();
reference.Uri = soapSecNS + "#Body";
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
XmlElement soapSignature = doc.CreateElement("Signature", soapSecNS);
soapSignature.Prefix = "soapsec";
soapSignature.AppendChild(xmlDigitalSignature);
headerNode.AppendChild(soapSignature);
// Make sure the byte order mark doesn't get written out
XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas();
Encoding encoderWithoutBOM = new System.Text.UTF8Encoding(false);
System.IO.MemoryStream ms = new System.IO.MemoryStream(encoderWithoutBOM.GetBytes(doc.InnerXml));
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(ms, encoderWithoutBOM, quotas, null);
//Create the new message, that has the digital signature in the header
Message newMessage = Message.CreateMessage(xdr, System.Int32.MaxValue, request.Version);
request = newMessage;
return null;
}
public class SignedXmlWithId : SignedXml
{
public SignedXmlWithId(XmlDocument xml)
: base(xml)
{
}
public SignedXmlWithId(XmlElement xmlElement)
: base(xmlElement)
{
}
public override XmlElement GetIdElement(XmlDocument doc, string id)
{
// check to see if it's a standard ID reference
XmlElement idElem = base.GetIdElement(doc, id);
if (idElem == null)
{
// I've just hardcoded it for the time being, but should be using an XPath expression here, and the id that is passed in
idElem = (XmlElement)doc.GetElementsByTagName("Body", "http://schemas.xmlsoap.org/soap/security/2000-12")[0];
}
return idElem;
}
}
public static bool verifyDigitalSignatureForString(string msgAsString)
{
XmlDocument verifyDoc = new XmlDocument();
verifyDoc.PreserveWhitespace = true;
verifyDoc.LoadXml(msgAsString);
SignedXmlWithId verifyXml = new SignedXmlWithId(verifyDoc);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = verifyDoc.GetElementsByTagName("Signature");
// Load the signature node.
verifyXml.LoadXml((XmlElement)nodeList[0]);
if (verifyXml.CheckSignature())
{
Console.WriteLine("Digital signature is valid");
return true;
}
else
{
Console.WriteLine("Digital signature is not valid");
return false;
}
}