sbraconnier
sbraconnier

Reputation: 465

XMLSignature created in Java is different from C#

This problem is driving me crazy. I have to sign a SOAPMessage using Java that is verified by a dotNet endpoint application. The vendor has given us a sample dotNet client application as an example so I have been inspired by this sample application, and several links on the internet, like these:

http://www.java2s.com/Tutorial/Java/0410__Web-Services-SOA/SignSOAPmessage.htm

Java equivalent of C# XML signing method

The structure of the body (which is the signed content) is:

<Body>
    <inbound>
        <content>...text content...</content>
    </inbound>
</Body>

So far I have been able to produce identical signatures (using my code and the sample one provided by the vendor) only if the text content doesn't contain any line breaks. As soon as my text contains a line delimiter, the signature value is not the same anymore.

After several days of searching, I think the problem is related to the Canonicalization.

In Java, the following code:

...
Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
Element body = getNextSiblingElement(header);
byte outputBytes[] = c14n.canonicalizeSubtree(body);
FileUtils.writeByteArrayToFile(new File("C:\\tmp\\Canonicalized_java.xml"),outputBytes);
...

Produces:

<s:Body xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" Id="uuid"><inbound><content>ABC&#xD;DEF&#xD;HIJ&#xD;</content></inbound></s:Body>

And in dotNet (c#), the following code:

...
using (MemoryStream nodeStream = new MemoryStream())
{
    XmlWriterSettings ws = new XmlWriterSettings();
    ws.NewLineHandling = NewLineHandling.None;
    using (XmlWriter xw = XmlWriter.Create(nodeStream, ws))
    {
        soapRequest.GetElementsByTagName("Body")[0].WriteTo(xw);
        xw.Flush();
    }
    nodeStream.Position = 0;

    XmlDsigExcC14NTransform transform = new XmlDsigExcC14NTransform();
    transform.LoadInput(nodeStream);
    using (MemoryStream outputStream = (MemoryStream)transform.GetOutput(typeof(Stream)))
    {
        File.WriteAllBytes("C:\\tmp\\Canonicalized_dotNet.xml", outputStream.ToArray());
    }
}
...

Produces:

<s:Body xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" Id="uuid"><inbound><content>ABC
DEF
HIJ
</content></inbound></s:Body>

Where the line breaks are LF only. (Note that the original content contains only CR as line break).

I can provide more source code if needed, but maybe someone knows exactly what is going on here. Any help would be very very appreciated. Note that I cannot change the way the dotNet endpoint application verify the XML Signature value.

EDIT Here's how the Body of the message is built. I don't understand why this doesn't produce the same result as Aaryn's anwser:

public static void main(String[] args) throws Exception { 
    com.sun.org.apache.xml.internal.security.Init.init();
    SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
    SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
    SOAPBody soapBody = soapEnvelope.getBody();
    SOAPElement inboundMessage = soapBody.addChildElement("inbound");
    SOAPElement payload = inboundMessage.addChildElement("content");
    payload.addTextNode("ABC\rDEF\rGHI\r");
    Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    byte outputBytes[] = c14n.canonicalizeSubtree(soapBody);
    System.out.println(new String(outputBytes));
}

output:

<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><inbound><content>ABC&#xD;DEF&#xD;GHI&#xD;</content></inbound></SOAP-ENV:Body>

EDIT 2 I did some more testing, trying to build the XML at runtime instead of parsing an input String. Here's my last trial:

public static void main(String[] args) throws Exception {
    com.sun.org.apache.xml.internal.security.Init.init();
    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    Element body = doc.createElement("Body");
    Element inbound = doc.createElement("inbound");
    Element content = doc.createElement("content");
    content.appendChild(doc.createTextNode("ABC\rDEF\rGHI\r"));
    doc.appendChild(body);
    body.appendChild(inbound);
    inbound.appendChild(content);
    Canonicalizer c14n =   Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    byte outputBytes[] = c14n.canonicalizeSubtree(body);
    System.out.println(new String(outputBytes));
}

And the output is still

<Body><inbound><content>ABC&#xD;DEF&#xD;GHI&#xD;</content></inbound></Body>

I really don't get it. Why the CR are replaced by &#xD; instead of LF like when the document is built from the parsing of an input String ???

Upvotes: 8

Views: 2441

Answers (1)

Aaryn Tonita
Aaryn Tonita

Reputation: 490

There may be problems with both of your canonicalizations if the document you post is your actual input. In that case, they are not preserving the leading whitespace between opening and closing tags. Are you doing any sort of processing on the document before canonicalizing it? In a simple test program:

public static String test = "<Body>\n"
    + "    <inbound>\n"
    + "        <content>...text\r content\n...</content>\n"
    + "    </inbound>\n"
    + "</Body>";

public static void main(String[] args) throws Exception {
    com.sun.org.apache.xml.internal.security.Init.init();
    Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    Element body = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(test.getBytes())).getDocumentElement();
    byte outputBytes[] = c14n.canonicalizeSubtree(body);
    System.out.println(new String(outputBytes));
}

the output of the whitespace is preserved as it should be (subject to line delimiter normalization). I believe you are doing something in the code preceding your canonicalization in Java which is escaping your carriage returns. The whitespace preservation of XML canonicalization is surprising to most people, see W3C Canonicalization Specs. It definitely tripped me up the first time I had to deal with it.

Are you using XML Digital signatures or custom signatures? If W3C, you ought to be able to use native Digital Signature libraries.

In response to edit 1&2

The Document.createTextNode(String data) method escapes data for you. This is to prevent you from creating invalid xml documents (it will also escape >, < and other characters). Instead of performing line delimiter normalization as defined by XML canonicalization, it escapes characters so that the original text can be recovered. Since you appear to want to perform line delimiter normalization yourself you should perform this sanitization on input strings manually:

    String text = "ABC\rDEF\rGHI\r";
    payload.addTextNode(text.replace("\r", "\n"));

Using CDATA sections might also be a solution for you.

Upvotes: 4

Related Questions