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
DEF
HIJ
</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
DEF
GHI
</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
DEF
GHI
</content></inbound></Body>
I really don't get it. Why the CR are replaced by 
instead of LF like when the document is built from the parsing of an input String ???
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With