I need to assign digital signature on header for my soap request, here are the example of the SOAP request.
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:SOAP-SEC="http://schemas.xmlsoap.org/soap/security/2000-12" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<SOAP-SEC:Signature soapenv:actor="" soapenv:mustUnderstand="0">
<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"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#Body">
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
</ds:SignatureValue>
</ds:Signature>
</SOAP-SEC:Signature>
</soapenv:Header>
<soapenv:Body Id="Body">
<CalFireRequest soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><DETAFFReq href="#id0"/></CalFireRequest>
<multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:DETAFFServices" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns1:CalFireRequest">
<inputFieldName xsi:type="soapenc:string">
</inputFieldName>
<inputFieldValue xsi:type="soapenc:string">
</inputFieldValue>
<usrId xsi:type="soapenc:string"></usrId>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>
Then I decided to generated the request by using XML Writer which show as below:
private static string BuildEnvelope(X509Certificate2 certificate)
{
string envelope = null;
// note - lots of bits here specific to my thirdparty
using (var stream = new MemoryStream())
{
Encoding utf8 = new UTF8Encoding(false); // omit BOM
using (var writer = new XmlTextWriter(stream, utf8))
{
// soap envelope
writer.WriteStartDocument();
writer.WriteStartElement("soapenv:Envelope");
writer.WriteAttributeString("xmlns", "SOAP-SEC", null, "http://schemas.xmlsoap.org/soap/security/2000-12");
writer.WriteAttributeString("xmlns", "soapenv", null, "http://schemas.xmlsoap.org/soap/envelope/");
writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
writer.WriteStartElement("soapenv", "Header", null);
writer.WriteStartElement("SOAP-SEC","Signature",null);
writer.WriteAttributeString("soapenv", "actor", null, "");
writer.WriteAttributeString("soapenv", "mustUnderstand", null, "0");
writer.WriteEndElement(); //Security
writer.WriteEndElement(); //Header
////<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
//writer.WriteStartElement("soapenv", "Body", null);
//writer.WriteAttributeString(null , "Id", null, "Body");
//writer.WriteStartElement("CalFireRequest", null, null);
//writer.WriteAttributeString("soapenv", "encodingStyle", null, "http://schemas.xmlsoap.org/soap/encoding/");
//writer.WriteStartElement("DETAFFReq", null, null);
//writer.WriteAttributeString("null", "href", null, "#id0");
//writer.WriteEndElement(); // CalFireRequest
//// your 3rd-party soap payload goes here
//writer.WriteStartElement("???", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
//// ...
//writer.WriteEndElement(); //
//writer.WriteEndElement(); // Body
writer.WriteEndElement(); //Envelope
}
// signing pass
var signable = Encoding.UTF8.GetString(stream.ToArray());
XmlDocument doc = new XmlDocument();
doc.LoadXml(signable);
// see https://stackoverflow.com/a/6467877
var signedXml = new SignedXml(doc);
RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)certificate.PrivateKey;
signedXml.SigningKey = rsaKey;
// these values may not be supported by your 3rd party - they may use e.g. SHA256 miniumum
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
//
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data x509data = new KeyInfoX509Data(certificate);
keyInfo.AddClause(x509data);
signedXml.KeyInfo = keyInfo;
// 3rd party wants us to only sign the timestamp fragment- ymmv
Reference reference0 = new Reference();
reference0.Uri = "BODY";
var t0 = new XmlDsigExcC14NTransform();
reference0.AddTransform(t0);
reference0.DigestMethod = SignedXml.XmlDsigSHA1Url;
signedXml.AddReference(reference0);
// etc
// get the sig fragment
signedXml.ComputeSignature();
XmlElement xmlDigitalSignature = signedXml.GetXml();
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("o", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
nsmgr.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
var security_node = doc.SelectSingleNode("/soapenv:Envelope/soapenv:Header/SOAP-SEC:Signature", nsmgr);
security_node.AppendChild(xmlDigitalSignature);
envelope = doc.OuterXml;
}
return envelope;
}
When I run the program I found out that, this part the tag SOAP-SEC can't be close using writer.WriteEndElement()
writer.WriteStartElement("soapenv", "Header", null);
writer.WriteStartElement("SOAP-SEC","Signature",null);
writer.WriteAttributeString("soapenv", "actor", null, "");
writer.WriteAttributeString("soapenv", "mustUnderstand", null, "0");
writer.WriteEndElement(); //Security
writer.WriteEndElement(); //Header
Example of XML result generate
<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:SOAP-SEC="http://schemas.xmlsoap.org/soap/security/2000-12" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<SOAP-SEC:Signature soapenv:actor="" soapenv:mustUnderstand="0" />
</soapenv:Header>
</soapenv:Envelope>
Is there anything I am missing? or can anyone provide a better solution on the SOAP request?