1

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?

Nikunj Kakadiya
  • 2,689
  • 2
  • 20
  • 35
user1804079
  • 11
  • 1
  • 2
  • Here is a sample of my code : https://stackoverflow.com/questions/46722997/saml-assertion-in-a-xml-using-c-sharp/46724392 – jdweng Jan 30 '21 at 10:12
  • The end tag name is optional to your Example just has the backslash instead of the name /SOAP-SEC:Signature – jdweng Jan 30 '21 at 10:14
  • Hi @jdweng , thank for reply , i', actually just success to build whole soap request using back my function, but how to add "#Body" to uri in Reference? – user1804079 Jan 30 '21 at 12:12
  • You have a MermoryStream stream. The entire request is in the stream. So set the position of the stream to zero. Then you can read the stream into your request. – jdweng Jan 30 '21 at 15:15
  • @jdweng i get "Unable to resolve Uri Body" message when trying to put as : reference = new Reference { Uri = "#Body" }; when sign my cert – user1804079 Jan 30 '21 at 15:54
  • You body is already in the SOAP. Either you add the entire SOAP to your request, or add each piece (header, body, attachments) individually. Don't add the body twice. – jdweng Jan 30 '21 at 15:59

0 Answers0