Okay, I think I've got this figured. Everybody annoyed by all the
clutter in their inbox can rest easy, it's almost all over.
The problem is namespaces vanishing. The cause is something in
XMLSignature not playing nicely with the internal DOM state. The
workaround is as follows.
Firstly, it is not necessary to manually create "xmlns" attributes. Raul
is quite correct in saying that createElementNS doesn't create them for
you, but that function *does* do something useful that results in the
xmlns information propagating out when you serialize.
The important point is that this *only* happens when you serialize. If
you hand-assemble a DOM using createElementNS and pass it off to
XMLSignature, it just won't work. XMLSignature won't see the namespaces.
I would go and poke at the code to work this out, but I have a
presentation to do on Saturday that I can only now start working on :)
So, the easy workaround that keeps XMLSignature happy and your method
calls sane involves serializing and re-parsing before signing.
Specifically, build your DOM in the intuitive way:
Document dom = yourBuilder.newDocument();
Element root = dom.createElementNS(namespace, prefix + ":" + "rootTag");
dom.appendChild(root);
Element blah = dom.createElementNS(namespace, prefix + ":" + "blah");
root.appendChild(blah);
(repeat ad nauseam)
You now have a pretty document which, when serialized, will have a
single xmlns attribute at the top and an inherited namespace all the way
down. Nice and clean.
Now, before you sign it, you have to do:
OutputFormat of = new OutputFormat(thisDocument, "UTF-8", false);
StringWriter sw = new StringWriter();
XMLSerializer xs = new XMLSerializer(sw, of);
xs.setNamespaces(true);
try {
xs.asDOMSerializer();
xs.serialize(thisDocument.getDocumentElement());
} catch(IOException e) {
sw.write("IOException " + e.getMessage());
}
Document signingDoc = yourBuilder.parse(sw.toString());
That 'false' flag in the OutputFormat constructor is critically
important. Without it, you'll get superfluous whitespace text nodes that
you might spend an hour writing XPath expressions to remove like an
idiot before you realize what's going on. Ahem.
So, you now have a signingDoc that theoretically is exactly the same as
your original document, but actually has the consistent backend magic
that keeps XMLSignature happy. Mine looked something like this:
<frog:solicitation Id="solicitation-0"
NS1:schemaLocation="urn:frog http://xml.rcpt.to/mikolaj/default"
xmlns:NS1="http://www.w3.org/2001/XMLSchema-instance"
xmlns:frog="urn:frog">
<frog:DNS>www.rcpt.to</frog:DNS>
..etc..
You can now sign signingDoc with XMLSignature without any dramas. You
get a result that looks like:
<?xml version="1.0" encoding="UTF-8"?>
<frog:solicitation Id="solicitation-0"
NS1:schemaLocation="urn:frog http://xml.rcpt.to/mikolaj/default"
xmlns="" xmlns:NS1="http://www.w3.org/2001/XMLSchema-instance"
xmlns:frog="urn:frog">
<frog:DNS xmlns=""
xmlns:NS1="http://www.w3.org/2001/XMLSchema-instance"
xmlns:frog="urn:frog">www.rcpt.to</frog:DNS>
This is functional and you can serialize and re-parse and it will all
verify happily. However, you'll note that XMLSignature has crapped all
over the element attributes, inserting superfluous namespace
declarations all over the place. Luckily, you still have your original
pre-signature document, so you can pluck out the Signature element from
your signed document, import it into your original document, and have
the best of both worlds: clean namespace declarations and a working
Signature.
m.
PS: Mind you, I have no idea whether it will inter-operate with any
other xmlsig implementation at this point. *shrug*