具有特定命名空间前缀 ("ds:") 且没有 X509Data 的 .NET SignedXml
.NET SignedXml with a Specific Namespace Prefix ("ds:") and without X509Data
我在尝试在 Microsoft .NET 中对肥皂信封进行数字签名时遇到了困难。 Web 服务拒绝了我的 .NET 签名请求,说“签名无效”。在这种情况下,webservice 是由第三方在 Java 中编写的,因此我无法在服务器端进行任何更改。
服务器端需要带有 ds 前缀的签名元素。默认情况下,SignedXml class 不会为签名元素和子元素生成带有 ds 前缀的 xml。另一个问题与 ds:KeyInfo 有关,它应该有 KeyValue 和 X509IssuerSerial 元素——默认情况下在 .NET 中它是 X509Data 元素。所以消息的结构应该是这样的,服务器接受请求:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#ea43a55321b243c082dadae4f53f32b5">
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>.........</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>.......</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>.......</ds:Modulus>
<ds:Exponent>....</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
<ds:X509IssuerSerial>
<ds:X509IssuerName>.......</ds:X509IssuerName>
<ds:X509SerialNumber>.......</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body ds:id="ea43a55321b243c082dadae4f53f32b5" xmlns:ds="http://schemas.xmlsoap.org/soap/security/2000-12">
.................
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
我想分享我的解决方案。也许这对其他人有帮助,正在为类似的问题而苦苦挣扎。
我是用 .NET 3.5 创建的,并且使用的是 Microsoft Web Services Enhancements (WSE) 3.0。
所以我覆盖了默认的 XmlDocument 以具有 SetPrefix 和 GetPrefix 方法:
public class XmlDsigDocument : XmlDocument
{
// Constants
public const string XmlDsigNamespacePrefix = "ds";
/// <summary>
/// Override CreateElement function as it is extensively used by SignedXml
/// </summary>
/// <param name="prefix"></param>
/// <param name="localName"></param>
/// <param name="namespaceURI"></param>
/// <returns></returns>
public override XmlElement CreateElement(string prefix, string localName, string namespaceURI)
{
// CAntonio. If this is a Digital signature security element, add the prefix.
if (string.IsNullOrEmpty(prefix))
{
// !!! Note: If you comment this line, you'll get a valid signed file! (but without ds prefix)
// !!! Note: If you uncomment this line, you'll get an invalid signed file! (with ds prefix within 'Signature' object)
//prefix = GetPrefix(namespaceURI);
// The only way to get a valid signed file is to prevent 'Prefix' on 'SignedInfo' and descendants.
List<string> SignedInfoAndDescendants = new List<string>();
SignedInfoAndDescendants.Add("SignedInfo");
SignedInfoAndDescendants.Add("CanonicalizationMethod");
SignedInfoAndDescendants.Add("InclusiveNamespaces");
SignedInfoAndDescendants.Add("SignatureMethod");
SignedInfoAndDescendants.Add("Reference");
SignedInfoAndDescendants.Add("Transforms");
SignedInfoAndDescendants.Add("Transform");
SignedInfoAndDescendants.Add("InclusiveNamespaces");
SignedInfoAndDescendants.Add("DigestMethod");
SignedInfoAndDescendants.Add("DigestValue");
if (!SignedInfoAndDescendants.Contains(localName))
{
prefix = GetPrefix(namespaceURI);
}
}
return base.CreateElement(prefix, localName, namespaceURI);
}
/// <summary>
/// Select the standar prefix for the namespaceURI provided
/// </summary>
/// <param name="namespaceURI"></param>
/// <returns></returns>
public static string GetPrefix(string namespaceURI)
{
if (namespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
return "ec";
else if (namespaceURI == SignedXml.XmlDsigNamespaceUrl)
return "ds";
return string.Empty;
}
/// <summary>
/// Set the prefix to this and all its descendants.
/// </summary>
/// <param name="prefix"></param>
/// <param name="node"></param>
/// <returns></returns>
public static XmlNode SetPrefix(string prefix, XmlNode node)
{
foreach (XmlNode n in node.ChildNodes)
{
SetPrefix(prefix, n);
}
if (node.NamespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
node.Prefix = "ec";
else if ((node.NamespaceURI == SignedXmlWithId.xmlDSignSecurityUrl) || (string.IsNullOrEmpty(node.Prefix)))
node.Prefix = prefix;
return node;
}
}
然后覆盖 SignedXml class 以支持 body 元素中命名空间 http://schemas.xmlsoap.org/soap/security/2000-12 的 id 属性
internal sealed class SignedXmlWithId : SignedXml
{
public SignedXmlWithId()
: base()
{
}
public SignedXmlWithId(XmlDocument doc)
: base(doc)
{
}
public SignedXmlWithId(XmlElement elem)
: base(elem)
{
}
public const string xmlSoapEnvelopeUrl = "http://schemas.xmlsoap.org/soap/envelope/";
public const string xmlDSignSecurityUrl = "http://www.w3.org/2000/09/xmldsig#";
public const string xmlBodyIDNamespaceUrl = "http://schemas.xmlsoap.org/soap/security/2000-12";
public const string xmlOasisWSSSecurityExtUrl = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
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)
{
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("ds", "http://schemas.xmlsoap.org/soap/security/2000-12");
idElem = doc.SelectSingleNode("//*[@ds:id=\"" + id + "\"]", nsManager) as XmlElement;
}
return idElem;
}
}
然后有点乱,但是可以用代码签名 xml 文档:
public class SignatureHelper
{
public XmlDsigDocument SignSoapBody(XmlDsigDocument xmlDoc, X509Certificate2 cert)
{
XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
XmlElement body = xmlDoc.DocumentElement.SelectSingleNode(@"//SOAP-ENV:Body", ns) as XmlElement;
if (body == null)
throw new Exception("No body tag found");
string bodyId = Guid.NewGuid().ToString().Replace("-", "");
body.SetAttribute("id", "http://schemas.xmlsoap.org/soap/security/2000-12", bodyId);
SignedXmlWithId signedXml = new SignedXmlWithId(xmlDoc);
signedXml.SigningKey = cert.PrivateKey;
string mySerialNumber = "";
string[] subjectArray = cert.Subject.Split(',');
for (int i = 0; i < subjectArray.Length; i++)
{
if (subjectArray[i].StartsWith("SERIALNUMBER="))
{
mySerialNumber = subjectArray[i].Replace("SERIALNUMBER=", "");
break;
}
}
RSAKeyValue rsa = new RSAKeyValue((System.Security.Cryptography.RSA)cert.PublicKey.Key);
XmlElement rsaElem = rsa.GetXml();
KeyInfo keyInfo = new KeyInfo();
XmlDsigDocument doc = new XmlDsigDocument();
doc.LoadXml("<x>" + rsaElem.OuterXml + "<X509IssuerSerial><X509IssuerName>" + cert.Issuer + "</X509IssuerName><X509SerialNumber>" + mySerialNumber + "</X509SerialNumber></X509IssuerSerial></x>");
keyInfo = Microsoft.Web.Services3.Security.KeyInfoHelper.LoadXmlKeyInfo(doc.DocumentElement); //Microsoft WSE 3.0
signedXml.KeyInfo = keyInfo;
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigC14NWithCommentsTransformUrl;
Reference reference = new Reference();
reference.Uri = "#" + bodyId;
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement signedElement = signedXml.GetXml();
signedElement.Prefix = "ds";
for (int i = 0; i < signedElement.ChildNodes.Count; i++)
{
signedElement.ChildNodes[i].Prefix = "ds";
for (int k = 0; k < signedElement.ChildNodes[i].ChildNodes.Count; k++)
{
signedElement.ChildNodes[i].ChildNodes[k].Prefix = "ds";
for (int m = 0; m < signedElement.ChildNodes[i].ChildNodes[k].ChildNodes.Count; m++)
{
signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].Prefix = "ds";
for (int n = 0; n < signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].ChildNodes.Count; n++)
{
signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].ChildNodes[n].Prefix = "ds";
}
}
}
}
XmlElement soapSignature = xmlDoc.CreateElement("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
soapSignature.Prefix = "wsse";
soapSignature.SetAttribute("mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
signedElement.ChildNodes[1].ChildNodes[0].Value = Chunks(signedElement.ChildNodes[1].ChildNodes[0].Value);
signedElement.ChildNodes[2].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Value = Chunks(signedElement.ChildNodes[2].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Value);
soapSignature.AppendChild(signedElement);
XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP-ENV:Header", ns) as XmlElement;
if (soapHeader == null)
{
soapHeader = xmlDoc.CreateElement("Header", "http://schemas.xmlsoap.org/soap/envelope/");
soapHeader.Prefix = "SOAP-ENV";
xmlDoc.DocumentElement.InsertBefore(soapHeader, xmlDoc.DocumentElement.ChildNodes[0]);
}
soapHeader.AppendChild(soapSignature);
string xmlContent = xmlDoc.OuterXml;
xmlContent = xmlContent.Replace("X509IssuerSerial", "ds:X509IssuerSerial");
xmlContent = xmlContent.Replace("X509IssuerName", "ds:X509IssuerName");
xmlContent = xmlContent.Replace("X509SerialNumber", "ds:X509SerialNumber");
XmlDsigDocument xmlDocResult = new XmlDsigDocument();
xmlDocResult.LoadXml(xmlContent);
xmlDocResult = GenerateSignatureValue(xmlDocResult, cert);
return xmlDocResult;
}
private string Chunks(string str)
{
byte[] bytes = Convert.FromBase64String(str);
return Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks);
}
private XmlDsigDocument GenerateSignatureValue(XmlDsigDocument xmlDoc, X509Certificate2 cert)
{
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
SignedXmlWithId signedXml = new SignedXmlWithId(xmlDoc);
signedXml.SignedInfo.CanonicalizationMethod = SignedXmlWithId.XmlDsigC14NWithCommentsTransformUrl;
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("ds:Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
signedXml.SigningKey = privateKey;
signedXml.ComputeSignature();
XmlElement signedElement = signedXml.GetXml();
bool ok = signedXml.CheckSignature();
if(!ok)
{
throw new Exception("Invalid signature");
}
xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].RemoveChild(xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].ChildNodes[0]);
XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP-ENV:Header", ns) as XmlElement;
if (soapHeader != null)
soapHeader.ChildNodes[0].AppendChild(signedElement);
return xmlDoc;
}
}
在我的例子中,传递给方法 SignSoapBody 的输入 XmlDocument 如下所示:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
..............
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
希望这对某人有所帮助...
p.s. If i try to sign with .NET 4.0 signature becomes invalid, so for
that reason i use older .NET 3.5 (.NET 2.0 is working fine as well).
The thing is that in .NET 4.0 System.Security.dll version has changed
and i think for that reason it makes invalid signature values which is
not acceptable for server side i need to communicate with.
我在尝试在 Microsoft .NET 中对肥皂信封进行数字签名时遇到了困难。 Web 服务拒绝了我的 .NET 签名请求,说“签名无效”。在这种情况下,webservice 是由第三方在 Java 中编写的,因此我无法在服务器端进行任何更改。
服务器端需要带有 ds 前缀的签名元素。默认情况下,SignedXml class 不会为签名元素和子元素生成带有 ds 前缀的 xml。另一个问题与 ds:KeyInfo 有关,它应该有 KeyValue 和 X509IssuerSerial 元素——默认情况下在 .NET 中它是 X509Data 元素。所以消息的结构应该是这样的,服务器接受请求:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#ea43a55321b243c082dadae4f53f32b5">
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>.........</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>.......</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>.......</ds:Modulus>
<ds:Exponent>....</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
<ds:X509IssuerSerial>
<ds:X509IssuerName>.......</ds:X509IssuerName>
<ds:X509SerialNumber>.......</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body ds:id="ea43a55321b243c082dadae4f53f32b5" xmlns:ds="http://schemas.xmlsoap.org/soap/security/2000-12">
.................
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
我想分享我的解决方案。也许这对其他人有帮助,正在为类似的问题而苦苦挣扎。
我是用 .NET 3.5 创建的,并且使用的是 Microsoft Web Services Enhancements (WSE) 3.0。
所以我覆盖了默认的 XmlDocument 以具有 SetPrefix 和 GetPrefix 方法:
public class XmlDsigDocument : XmlDocument
{
// Constants
public const string XmlDsigNamespacePrefix = "ds";
/// <summary>
/// Override CreateElement function as it is extensively used by SignedXml
/// </summary>
/// <param name="prefix"></param>
/// <param name="localName"></param>
/// <param name="namespaceURI"></param>
/// <returns></returns>
public override XmlElement CreateElement(string prefix, string localName, string namespaceURI)
{
// CAntonio. If this is a Digital signature security element, add the prefix.
if (string.IsNullOrEmpty(prefix))
{
// !!! Note: If you comment this line, you'll get a valid signed file! (but without ds prefix)
// !!! Note: If you uncomment this line, you'll get an invalid signed file! (with ds prefix within 'Signature' object)
//prefix = GetPrefix(namespaceURI);
// The only way to get a valid signed file is to prevent 'Prefix' on 'SignedInfo' and descendants.
List<string> SignedInfoAndDescendants = new List<string>();
SignedInfoAndDescendants.Add("SignedInfo");
SignedInfoAndDescendants.Add("CanonicalizationMethod");
SignedInfoAndDescendants.Add("InclusiveNamespaces");
SignedInfoAndDescendants.Add("SignatureMethod");
SignedInfoAndDescendants.Add("Reference");
SignedInfoAndDescendants.Add("Transforms");
SignedInfoAndDescendants.Add("Transform");
SignedInfoAndDescendants.Add("InclusiveNamespaces");
SignedInfoAndDescendants.Add("DigestMethod");
SignedInfoAndDescendants.Add("DigestValue");
if (!SignedInfoAndDescendants.Contains(localName))
{
prefix = GetPrefix(namespaceURI);
}
}
return base.CreateElement(prefix, localName, namespaceURI);
}
/// <summary>
/// Select the standar prefix for the namespaceURI provided
/// </summary>
/// <param name="namespaceURI"></param>
/// <returns></returns>
public static string GetPrefix(string namespaceURI)
{
if (namespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
return "ec";
else if (namespaceURI == SignedXml.XmlDsigNamespaceUrl)
return "ds";
return string.Empty;
}
/// <summary>
/// Set the prefix to this and all its descendants.
/// </summary>
/// <param name="prefix"></param>
/// <param name="node"></param>
/// <returns></returns>
public static XmlNode SetPrefix(string prefix, XmlNode node)
{
foreach (XmlNode n in node.ChildNodes)
{
SetPrefix(prefix, n);
}
if (node.NamespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
node.Prefix = "ec";
else if ((node.NamespaceURI == SignedXmlWithId.xmlDSignSecurityUrl) || (string.IsNullOrEmpty(node.Prefix)))
node.Prefix = prefix;
return node;
}
}
然后覆盖 SignedXml class 以支持 body 元素中命名空间 http://schemas.xmlsoap.org/soap/security/2000-12 的 id 属性
internal sealed class SignedXmlWithId : SignedXml
{
public SignedXmlWithId()
: base()
{
}
public SignedXmlWithId(XmlDocument doc)
: base(doc)
{
}
public SignedXmlWithId(XmlElement elem)
: base(elem)
{
}
public const string xmlSoapEnvelopeUrl = "http://schemas.xmlsoap.org/soap/envelope/";
public const string xmlDSignSecurityUrl = "http://www.w3.org/2000/09/xmldsig#";
public const string xmlBodyIDNamespaceUrl = "http://schemas.xmlsoap.org/soap/security/2000-12";
public const string xmlOasisWSSSecurityExtUrl = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
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)
{
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("ds", "http://schemas.xmlsoap.org/soap/security/2000-12");
idElem = doc.SelectSingleNode("//*[@ds:id=\"" + id + "\"]", nsManager) as XmlElement;
}
return idElem;
}
}
然后有点乱,但是可以用代码签名 xml 文档:
public class SignatureHelper
{
public XmlDsigDocument SignSoapBody(XmlDsigDocument xmlDoc, X509Certificate2 cert)
{
XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
XmlElement body = xmlDoc.DocumentElement.SelectSingleNode(@"//SOAP-ENV:Body", ns) as XmlElement;
if (body == null)
throw new Exception("No body tag found");
string bodyId = Guid.NewGuid().ToString().Replace("-", "");
body.SetAttribute("id", "http://schemas.xmlsoap.org/soap/security/2000-12", bodyId);
SignedXmlWithId signedXml = new SignedXmlWithId(xmlDoc);
signedXml.SigningKey = cert.PrivateKey;
string mySerialNumber = "";
string[] subjectArray = cert.Subject.Split(',');
for (int i = 0; i < subjectArray.Length; i++)
{
if (subjectArray[i].StartsWith("SERIALNUMBER="))
{
mySerialNumber = subjectArray[i].Replace("SERIALNUMBER=", "");
break;
}
}
RSAKeyValue rsa = new RSAKeyValue((System.Security.Cryptography.RSA)cert.PublicKey.Key);
XmlElement rsaElem = rsa.GetXml();
KeyInfo keyInfo = new KeyInfo();
XmlDsigDocument doc = new XmlDsigDocument();
doc.LoadXml("<x>" + rsaElem.OuterXml + "<X509IssuerSerial><X509IssuerName>" + cert.Issuer + "</X509IssuerName><X509SerialNumber>" + mySerialNumber + "</X509SerialNumber></X509IssuerSerial></x>");
keyInfo = Microsoft.Web.Services3.Security.KeyInfoHelper.LoadXmlKeyInfo(doc.DocumentElement); //Microsoft WSE 3.0
signedXml.KeyInfo = keyInfo;
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigC14NWithCommentsTransformUrl;
Reference reference = new Reference();
reference.Uri = "#" + bodyId;
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement signedElement = signedXml.GetXml();
signedElement.Prefix = "ds";
for (int i = 0; i < signedElement.ChildNodes.Count; i++)
{
signedElement.ChildNodes[i].Prefix = "ds";
for (int k = 0; k < signedElement.ChildNodes[i].ChildNodes.Count; k++)
{
signedElement.ChildNodes[i].ChildNodes[k].Prefix = "ds";
for (int m = 0; m < signedElement.ChildNodes[i].ChildNodes[k].ChildNodes.Count; m++)
{
signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].Prefix = "ds";
for (int n = 0; n < signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].ChildNodes.Count; n++)
{
signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].ChildNodes[n].Prefix = "ds";
}
}
}
}
XmlElement soapSignature = xmlDoc.CreateElement("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
soapSignature.Prefix = "wsse";
soapSignature.SetAttribute("mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
signedElement.ChildNodes[1].ChildNodes[0].Value = Chunks(signedElement.ChildNodes[1].ChildNodes[0].Value);
signedElement.ChildNodes[2].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Value = Chunks(signedElement.ChildNodes[2].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Value);
soapSignature.AppendChild(signedElement);
XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP-ENV:Header", ns) as XmlElement;
if (soapHeader == null)
{
soapHeader = xmlDoc.CreateElement("Header", "http://schemas.xmlsoap.org/soap/envelope/");
soapHeader.Prefix = "SOAP-ENV";
xmlDoc.DocumentElement.InsertBefore(soapHeader, xmlDoc.DocumentElement.ChildNodes[0]);
}
soapHeader.AppendChild(soapSignature);
string xmlContent = xmlDoc.OuterXml;
xmlContent = xmlContent.Replace("X509IssuerSerial", "ds:X509IssuerSerial");
xmlContent = xmlContent.Replace("X509IssuerName", "ds:X509IssuerName");
xmlContent = xmlContent.Replace("X509SerialNumber", "ds:X509SerialNumber");
XmlDsigDocument xmlDocResult = new XmlDsigDocument();
xmlDocResult.LoadXml(xmlContent);
xmlDocResult = GenerateSignatureValue(xmlDocResult, cert);
return xmlDocResult;
}
private string Chunks(string str)
{
byte[] bytes = Convert.FromBase64String(str);
return Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks);
}
private XmlDsigDocument GenerateSignatureValue(XmlDsigDocument xmlDoc, X509Certificate2 cert)
{
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
SignedXmlWithId signedXml = new SignedXmlWithId(xmlDoc);
signedXml.SignedInfo.CanonicalizationMethod = SignedXmlWithId.XmlDsigC14NWithCommentsTransformUrl;
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("ds:Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
signedXml.SigningKey = privateKey;
signedXml.ComputeSignature();
XmlElement signedElement = signedXml.GetXml();
bool ok = signedXml.CheckSignature();
if(!ok)
{
throw new Exception("Invalid signature");
}
xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].RemoveChild(xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].ChildNodes[0]);
XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP-ENV:Header", ns) as XmlElement;
if (soapHeader != null)
soapHeader.ChildNodes[0].AppendChild(signedElement);
return xmlDoc;
}
}
在我的例子中,传递给方法 SignSoapBody 的输入 XmlDocument 如下所示:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
..............
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
希望这对某人有所帮助...
p.s. If i try to sign with .NET 4.0 signature becomes invalid, so for that reason i use older .NET 3.5 (.NET 2.0 is working fine as well). The thing is that in .NET 4.0 System.Security.dll version has changed and i think for that reason it makes invalid signature values which is not acceptable for server side i need to communicate with.