Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XMLSignature created in Java is different from C#

Tags:

java

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 ???

like image 388
sbraconnier Avatar asked Dec 08 '14 09:12

sbraconnier


People also ask

How to Create XML Signature in Java?

In order to create an XML Digital Signature, use the following procedure: Generate a pair of Keys called Private Key and Pubic Key. Obtain the original XML document. Sign the original XML document using both Private and Public key by Java API and generate another document that has a XML Digital Signature.

How does XML signature work?

An XML digital signature (XML DSIG) is an electronic, encrypted, stamp of authentication on digital information such as messages. The digital signature confirms that the information originated from the signer and was not altered in transmission.

What is digest value in XML Signature?

The default digest method is the SHA1 algorithm. The <DigestValue> element contains a hash value based on a single message property preceded by a keyword indicating the message property used to calculate it with the hashing algorithm specified in the <DigestMethod> element.


1 Answers

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.

like image 191
Aaryn Tonita Avatar answered Sep 25 '22 06:09

Aaryn Tonita