XML Digital Signatures and JAXB: The Less DOMinant Approach
Working with the XML Digital Signature Standard should be a straight-forward affair, but if you're using JAXB instead of DOM, what can you do? This article details my journey of trying to get these two technologies to play nicely together.
Join the DZone community and get the full member experience.Join For Free
I recently had the opportunity to deal with the legacy XML Digital Signature Standard from our good friends over at the W3C as part of a project I'm working on that requires sending signed XML (SOAP) documents over the wire.
This project also entails assembling an involved SOAP message that has a very large schema, which was provided to me by the receiving party. One look at said schema and I knew I was not about to spend my time using straight DOM manipulation to create these documents, so I turned to my old friend, JAXB, given that Java was the platform being used.
No problem, right?
Why wrong? Because the usual examples of the XML DSig library in Java make use of DOM libraries, which I was looking to avoid. Figures. Not to mention that the location of the signature was a non-standard location; at least, non-standard as far as the DSig libraries were concerned. The signature block had to be contained within the SOAP Header element, within a SOAP Security element, as opposed to being enveloped or enveloping with the signed document itself.
(Side note: If anyone knows how to customize the above, I'm all ears.)
In my specific case, only the SOAP Body element needed to be signed.
This article details my journey in trying to get this all to work.
First Pass: Manual Signature Generation
Assembling the body of the document and the overall SOAP message was a piece of cake using JAXB. JAXB's Maven plugin made it easy to generate all of the classes I needed to create the document. And given that I had the DSig spec handy, I figured it would just be a matter of following some instructions to sign the document.
Boy was I wrong.
The biggest issue with this approach was validating the hashes for the digest and the signature. While I did have a sample valid signed document, there was no way to systematically determine what any discrepancies were, other than following a "trial and error" method.
That "method" quickly wore thin.
The spec, while detailed, does not exactly make it easy to follow and comprehend unambiguously—Should the digest include the starting and ending elements, before canonicalization? Etc.
Having had enough of this approach, it was time to move on to the second one.
Second Pass: Integrate the Standard Java XML DSig Library
I wondered if there was a way to combine the JAXB approach with the standard XML DSig library without needing to use the DOM.
So I set forth and tried to make this idea happen.
I figured out that I could marshal my SOAP envelope (generated by JAXB) into a DOMResult, so I would only need to briefly work with the DOM in order to accomplish my goals.
Awesome, I can handle that!
And then, like on the bridge of the Starship Enterprise, red alerts went off.
(Or at least my JUnit test bar turned red. Close enough.)
Since I had a self-signed certificate, I had been loading it in via an InputStream in a typical fashion, and then handing it off to the DSig library to deal with it.
As it turns out, I encountered an extremely weird exception; namely, that as the library was attempting to transform the certificate data into an X509Data element, it was throwing a ClassCastException. Looking into it after digging around online for the source and confirming by decompiling the class in question, I discovered that, for some very odd (and still-unexplained at the time of writing) reason, what I had passed in was not being detected as being a descendent of X509Certificate, when it very clearly was. In fact, after copying the offending code into my own project and running it there, the exception was never hit.
So it seemed to me that whatever the code throwing the exception was expecting, it had to somehow be getting an actual X509Certificate and not some descendent.
To side-step this issue, I acquiesced to Oracle's example code and loaded the certificate/key pair thusly.
The big catch was that I had to now convert my certificate/key into a JKS-formatted keystore. That part actually wasn't so bad after I did some digging online.
With this fix, my digest hash was now passing.
But I was faced with a few new challenges.
Third Pass: Fixing Bugs and Outputting the Result
With the signed document, I still had to get the DOM-based result into the original SOAP envelope, which, as you will recall, is a JAXB-based structure.
After puzzling over this for a short while, I tried the following steps:
Transform the appropriate portion of the DOM (i.e. the one containing the Signature) into an OutputStream.
Pipe the OutputStream from above into an InputStream.
Unmarshal the InputStream from above into a JAXB-based Signature class.
Insert the Signature class from above into the appropriate place in the original SOAP envelope.
Turns out this worked out just fine!
As for bugs, I'll just list them off here to save some time:
Unable to find internal reference. It is possible to reference a specific section within your original XML document that is to be signed, via XPath or even using the "id" attribute. When using the latter, it seems that JAXB does not preserve the fact that an "id" attribute has a specific meaning with the DOM during marshaling. This can be solved with the following code:
// replace the first line below with the path to the element within the document you wish to refer to
Element el = (Element)doc.getFirstChild().getFirstChild().getNextSibling();
RSA Signature Did Not Verify. This can be a pain, but assuming your certificate/key pair is setup and verified, it is more than likely an issue of the signed block not matching what actually appears in your submitted document. In my case, because I had to move the signed document out of the DOM and back into my JAXB structure, JAXB, by default, includes the namespace prefixes as part of the element names when marshaling, whereas the DOM object, by default, does not. By editing the appropriate package-info.java file (protip: Keep your DSig namespace in its own package), I was able to suppress the namespace prefix and voila!
With these issues solved, I finally had a working implementation!
Working with this spec wasn't easy, but really only because I had chosen to use JAXB instead of a more traditional DOM-based approach.
I didn't find a lot of help online specifically detailing how to work with the DSig library and JAXB—at least none that helped me get to where I had to go.
I hope this article helps out some other souls who encounter this situation. Good luck!
Opinions expressed by DZone contributors are their own.