1 package org.neuclear.xml.c14;
2
3 /***
4 * (C) 2003 Antilles Software Ventures SA
5 * User: pelleb
6 * Date: Feb 3, 2003
7 * Time: 5:56:42 AM
8 * $Id: Canonicalizer.java,v 1.13 2004/03/08 23:51:02 pelle Exp $
9 * $Log: Canonicalizer.java,v $
10 * Revision 1.13 2004/03/08 23:51:02 pelle
11 * More improvements on the XMLSignature. Now uses the Transforms properly, References properly.
12 * All the major elements have been refactored to be cleaner and more correct.
13 *
14 * Revision 1.12 2004/03/03 23:23:24 pelle
15 * Interops with enveloped signatures.
16 *
17 * Revision 1.11 2004/03/02 23:50:45 pelle
18 * minor changes.
19 * receiver didnt get checked in by idea in recent refactoring.
20 *
21 * Revision 1.10 2004/03/02 23:30:43 pelle
22 * Renamed SignatureInfo to SignedInfo as that is the name of the Element.
23 * Made some changes in the Canonicalizer to make all the output verify in Aleksey's xmlsec library.
24 * Unfortunately this breaks example 3 of merlin-eight's canonicalization interop tests, because dom4j afaik
25 * can't tell the difference between <test/> and <test xmlns=""/>.
26 * Changed XMLSignature it is now has less repeated code.
27 *
28 * Revision 1.9 2004/02/19 19:37:33 pelle
29 * At times IntelliJ IDEA can cause some real hassle. On my last checkin it optimized away all of the dom4j and command line imports.
30 * We'll now, Ive added them all back.
31 *
32 * Revision 1.8 2004/02/19 15:30:08 pelle
33 * Various cleanups and corrections
34 *
35 * Revision 1.7 2004/02/19 00:27:59 pelle
36 * Discovered several incompatabilities with the xmlsig implementation. Have been working on getting it working.
37 * Currently there is still a problem with enveloping signatures and it seems enveloped signatures done via signers.
38 *
39 * Revision 1.6 2004/01/14 16:34:27 pelle
40 * New model of references and signatures now pretty much works.
41 * I am still not 100% sure on the created enveloping signatures. I need to do more testing.
42 *
43 * Revision 1.5 2004/01/14 06:42:37 pelle
44 * Got rid of the verifyXXX() methods
45 *
46 * Revision 1.4 2003/12/10 23:57:04 pelle
47 * Did some cleaning up in the builders
48 * Fixed some stuff in IdentityCreator
49 * New maven goal to create executable jarapp
50 * We are close to 0.8 final of ID, 0.11 final of XMLSIG and 0.5 of commons.
51 * Will release shortly.
52 *
53 * Revision 1.3 2003/11/21 04:44:30 pelle
54 * EncryptedFileStore now works. It uses the PBECipher with DES3 afair.
55 * Otherwise You will Finaliate.
56 * Anything that can be final has been made final throughout everyting. We've used IDEA's Inspector tool to find all instance of variables that could be final.
57 * This should hopefully make everything more stable (and secure).
58 *
59 * Revision 1.2 2003/11/11 21:18:07 pelle
60 * Further vital reshuffling.
61 * org.neudist.crypto.* and org.neudist.utils.* have been moved to respective areas under org.neuclear.commons
62 * org.neuclear.signers.* as well as org.neuclear.passphraseagents have been moved under org.neuclear.commons.crypto as well.
63 * Did a bit of work on the Canonicalizer and changed a few other minor bits.
64 *
65 * Revision 1.1.1.1 2003/11/11 16:33:22 pelle
66 * Moved over from neudist.org
67 * Moved remaining common utilities into commons
68 *
69 * Revision 1.12 2003/10/29 21:15:53 pelle
70 * Refactored the whole signing process. Now we have an interface called Signer which is the old SignerStore.
71 * To use it you pass a byte array and an alias. The sign method then returns the signature.
72 * If a Signer needs a passphrase it uses a PassPhraseAgent to present a dialogue box, read it from a command line etc.
73 * This new Signer pattern allows us to use secure signing hardware such as N-Cipher in the future for server applications as well
74 * as SmartCards for end user applications.
75 *
76 * Revision 1.11 2003/09/29 23:44:54 pelle
77 * Trying to tweak Canonicalizer to function better.
78 * Apparently the built in Sun JCE doesnt like the Keysizes of NSROOT
79 * So now CryptoTools forces the use of BouncyCastle
80 *
81 * Revision 1.10 2003/02/24 13:32:23 pelle
82 * Final doc changes for 0.8
83 *
84 * Revision 1.9 2003/02/24 00:40:57 pelle
85 * Cleaned up a lot of code for new fixed processing model.
86 * It all still work as before, but will be easier to modify the Reference processing model which is the only
87 * main thing todo besides X509 and HMAC-SHA1 support.
88 *
89 * Revision 1.8 2003/02/22 16:54:29 pelle
90 * Major structural changes in the whole processing framework.
91 * Verification now supports Enveloping and detached signatures.
92 * The reference element is a lot more important at the moment and handles much of the logic.
93 * Replaced homegrown Base64 with Blackdowns.
94 * Still experiencing problems with decoding foreign signatures. I reall dont understand it. I'm going to have
95 * to reread the specs a lot more and study other implementations sourcecode.
96 *
97 * Revision 1.7 2003/02/18 00:03:32 pelle
98 * Moved the Signer classes from neuclearframework into neuclear-xmlsig
99 *
100 * Revision 1.6 2003/02/11 14:47:03 pelle
101 * Added benchmarking code.
102 * DigestValue is now a required part.
103 * If you pass a keypair when you sign, you get the PublicKey included as a KeyInfo block within the signature.
104 *
105 * Revision 1.5 2003/02/08 20:55:07 pelle
106 * Some documentation changes.
107 * Major reorganization of code. The code is slowly being cleaned up in such a way that we can
108 * get rid of the org.neuclear.utils package and split out the org.neuclear.xml.soap package.
109 * Got rid of tons of unnecessary dependencies.
110 *
111 * Revision 1.4 2003/02/08 18:48:07 pelle
112 * The Signature phase has been rewritten.
113 * There now is a new Class called QuickEmbeddedSignature which is more in line with my original idea for this library.
114 * It simply has a template of the xml and signs it in a standard way.
115 * The original XMLSignature class is still used for verification and will in the future handle more thoroughly
116 * all the various flavours of XMLSig.
117 * XMLSecTools has got different flavours of canonicalize now. Including one where you can pass it a Canonicaliser to use.
118 * Of the new Canonicalizer's are CanonicalizerWithComments, which I accidently left out of the last commit.
119 * And CanonicalizerWithoutSignature which leaves out the Signature in the Canonicalization phase and is thus
120 * a lot more efficient than the previous approach.
121 *
122 * Revision 1.3 2003/02/08 17:06:45 pelle
123 * Added support for Canonicalized XML Subsets.
124 * Only problem is it doesnt work. This should hopefully be fixed soon.
125 * There is a test case which has been temporarily disabled.
126 * Message has been sent to dom4j mailing list. I'm not sure if it's something I've done wrong or if it's a bug
127 * in Jaxen (the XPath implementation used by Dom4j)
128 * I've also added Canonicalizer with comments.
129 *
130 * Revision 1.2 2003/02/07 22:33:11 pelle
131 * Compliance mostly working.
132 * Merlin's Example 7 hasn't been implemented, but mainly because we havent written the test case yet.
133 * A few of the example c14n files had trailing new lines. I'm not sure what the spec says about that, but I
134 * got rid of them as my implementation doesnt support those. Who is right?
135 * Example 4 has problems with the final element. This seems to be causing problems for lots of people.
136 * To workaround it, I've removed the offending lines from all the files.
137 * TBH I dont understand whats going on with it. Will put it on the back burner and come back.
138 *
139 * Revision 1.1 2003/02/07 21:14:45 pelle
140 * Much improved Canonicalizer and Test Suite.
141 * I've added the merlin-xmldsig-eight Canonicalization test suite.
142 * All tests still dont work.
143 *
144 */
145
146 import org.dom4j.*;
147 import org.dom4j.tree.NamespaceStack;
148 import org.neuclear.commons.Utility;
149 import org.neuclear.xml.ElementProxy;
150 import org.neuclear.xml.XMLTools;
151 import org.neuclear.xml.transforms.TransformerFactory;
152 import org.neuclear.xml.transforms.XPathTransform;
153 import org.neuclear.xml.xmlsec.XMLSecurityException;
154
155 import java.io.*;
156 import java.nio.charset.Charset;
157 import java.util.*;
158
159 /***
160 * XML Canonicalizer.
161 * This "mostly" supports the <a href="http://www.w3c.org">W3C</a>
162 * <a href="http://www.w3.org/TR/2000/WD-xml-c14n-20001011">Canonical XML</a> standard.
163 * <p/>
164 * Usage:<br>
165 * <pre>Canonicalizer canon=new Canonicalizer(writer);//writer is a preinitialized instance of a java.io.Writer
166 * canon.canonicalize(doc); // Canonicalizes the document or element and outputs it to the writer
167 * </pre>
168 */
169 public class Canonicalizer extends XPathTransform {
170
171 public Canonicalizer() {
172 this(ALGORITHM, XPATH_WO_COMMENTS);
173 }
174
175 protected Canonicalizer(final String algorithm, final String xpath) {
176 super(algorithm, xpath);
177
178 }
179
180 private void init() {
181 try {
182 bos = new ByteArrayOutputStream();
183 writer = new BufferedWriter(new OutputStreamWriter(bos, "UTF-8"));
184 } catch (UnsupportedEncodingException e) {
185 System.out.println("Strange... we do not have UTF-8");
186 throw new RuntimeException(e.getLocalizedMessage());
187 }
188 notfirst = false;
189 namespaceStack = new NamespaceStack();
190
191 }
192
193
194 /***
195 * Canonicalizes a node and outputs it to the writer
196 *
197 * @param node
198 */
199 public final byte[] canonicalize(final Object node) throws XMLSecurityException {
200 try {
201 init();
202 write(node);
203 return getBytes();
204 } catch (IOException e) {
205 throw new XMLSecurityException(e);
206 }
207 }
208
209 /***
210 * Creates a subset of a node based on it's xpath and outputs it to the writer
211 * Note, for this to return wellformed xml the xpath most be formatted to produce it.
212 *
213 * @param node node
214 * @param xpath subset expression
215 * @throws IOException
216 */
217 public final byte[] canonicalizeSubset(final Node node, final String xpath) throws IOException {
218 init();
219 final XPath xpathSelector = DocumentHelper.createXPath(xpath);
220 final Map nsmap = new HashMap();
221 nsmap.put("ietf", "http://www.ietf.org");
222 xpathSelector.setNamespaceURIs(nsmap);
223 final List nl = xpathSelector.selectNodes(node);
224 write(nl);
225 return getBytes();
226 }
227
228 private byte[] getBytes() throws IOException {
229 writer.close();
230 bos.close();
231 return bos.toByteArray();
232 }
233
234 private void write(final Object obj) throws IOException {
235 if (obj instanceof Node)
236 writeNode((Node) obj);
237 else if (obj instanceof ElementProxy) {
238 writeNode(((ElementProxy) obj).getElement());
239 } else if (obj instanceof List) {
240 final Iterator iter = ((List) obj).iterator();
241 while (iter.hasNext()) {
242 final Node node = (Node) iter.next();
243 write(node);
244 }
245 } else {
246 writer.write(obj.toString());
247 }
248 }
249
250 private void writeNode(final Node node) throws IOException {
251 if (!matches(node))
252 return;
253
254 final int nodeType = node.getNodeType();
255
256 switch (nodeType) {
257 case Node.ELEMENT_NODE:
258 writeElement((Element) node);
259 break;
260 case Node.ATTRIBUTE_NODE:
261 writeAttribute((Attribute) node);
262 break;
263 case Node.TEXT_NODE:
264 case Node.CDATA_SECTION_NODE:
265 writeEscapedString(node.getText());
266 break;
267 case Node.ENTITY_REFERENCE_NODE:
268 writeEntity((Entity) node);
269 break;
270 case Node.PROCESSING_INSTRUCTION_NODE:
271 writeProcessingInstruction((ProcessingInstruction) node);
272 break;
273 case Node.COMMENT_NODE:
274 writeComment((Comment) node);
275 break;
276 case Node.DOCUMENT_NODE:
277 writeDocument((Document) node);
278 break;
279 case Node.DOCUMENT_TYPE_NODE:
280 writeDocType((DocumentType) node);
281 break;
282 case Node.NAMESPACE_NODE:
283 // Will be output with attributes
284 //write((Namespace) node);
285 break;
286 default:
287 throw new IOException("Invalid node type: " + node);
288
289 }
290
291 notfirst = true;
292 }
293
294 private void outputLF(final Node node) throws IOException {
295 if (node.getParent() == null && notfirst) {
296 writer.write(LF);
297 }
298 }
299
300 private void writeDocType(final DocumentType documentType) {
301 }
302
303 private void writeDocument(final Document document) throws IOException {
304 for (int i = 0, size = document.nodeCount(); i < size; i++) {
305 final Node node = document.node(i);
306 write(node);
307 }
308 }
309
310 private void writeComment(final Comment comment) throws IOException {
311 outputLF(comment);
312 writer.write("<!--");
313 writer.write(comment.getText());
314 writer.write("-->");
315 }
316
317 private void writeProcessingInstruction(final ProcessingInstruction processingInstruction) throws IOException {
318 outputLF(processingInstruction);
319 writer.write("<?");
320 writer.write(processingInstruction.getName());
321 if (!XMLTools.isEmpty(processingInstruction.getText())) {
322 writer.write(" ");
323 writer.write(processingInstruction.getText());
324 }
325 writer.write("?>");
326 }
327
328 private void writeEntity(final Entity entity) throws IOException {
329 final String text = entity.getText();
330 final int hashLoc = text.indexOf('#');
331 writer.write(entity.getText()); //TODO entities need to be expanded
332 }
333
334 private void writeAttribute(final Attribute attribute) throws IOException {
335 writer.write(" ");
336 writer.write(attribute.getQualifiedName());
337 writer.write("=\"");
338 writeEscapedAttributeString(attribute.getValue());
339 // System.out.println(attribute.getQualifiedName()+"="+attribute.getValue());
340 writer.write("\"");
341
342 }
343
344 private void writeElement(final Element element) throws IOException {
345 outputLF(element);
346
347 final String qualifiedName = element.getQualifiedName();
348 writer.write("<");
349 writer.write(qualifiedName);
350
351 final int previouslyDeclaredNamespaces = namespaceStack.size();
352 final TreeMap sorted = new TreeMap();
353
354 if (previouslyDeclaredNamespaces == 0) {
355 writeParentNS(element, sorted);
356 }
357 final Namespace ns = element.getNamespace();
358 if (isNamespaceDeclaration(ns)) {
359 namespaceStack.push(ns);
360 writeNamespace(ns, sorted);
361 } else if (ns.getURI() != null &&
362 ns.getURI().equals("") &&
363 element.getParent() != null &&
364 element.getParent().getNamespaceURI() != null
365 && !element.getParent().getNamespaceURI().equals("")
366 ) {
367 writeNamespace(ns, sorted);
368 }
369
370 final Iterator nsiter = element.additionalNamespaces().iterator();
371 while (nsiter.hasNext()) {
372 final Namespace namespace = (Namespace) nsiter.next();
373 if (!namespaceStack.contains(namespace)) {
374 writeNamespace(namespace, sorted);
375 namespaceStack.push(namespace);
376 }
377 }
378 writeAttributes(element, sorted);
379
380 writer.write(">");
381 for (int i = 0, size = element.nodeCount(); i < size; i++) {
382 final Node node = element.node(i);
383 write(node);
384 }
385 writer.write("</");
386 writer.write(qualifiedName);
387 writer.write(">");
388
389 // remove declared namespaceStack from stack
390 while (namespaceStack.size() > previouslyDeclaredNamespaces) {
391 namespaceStack.pop();
392 }
393
394 // if (element.getSignatory()==null)
395 // writer.write(LF);
396 }
397
398 private void writeParentNS(final Element element, final TreeMap sorted) throws IOException {
399 Element parent = element.getParent();
400 if (parent != null) {
401 final Namespace ns = parent.getNamespace();
402 if (isNamespaceDeclaration(ns)) {
403 namespaceStack.push(ns);
404 writeNamespace(ns, sorted);
405 }
406 writeParentNS(parent, sorted);
407 }
408 }
409
410 private void writeNamespace(final Namespace namespace, final TreeMap sorted) throws IOException {
411 if (namespace != null) {
412 sorted.put("0" + namespace.getPrefix(), namespace);
413 }
414 }
415
416 private void writeAttributes(final Element element, final TreeMap sorted) throws IOException {
417
418 // I do not yet handle the case where the same prefix maps to
419 // two different URIs. For attributes on the same element
420 // this is illegal; but as yet we don't throw an exception
421 // if someone tries to do this
422 //List attr=element.attributes();
423
424
425 for (int i = 0, size = element.attributeCount(); i < size; i++) {
426 final Attribute attribute = element.attribute(i);
427 sorted.put(attribute.getNamespaceURI() + ":" + attribute.getName(), attribute);
428 final Namespace ns = attribute.getNamespace();
429 if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) {
430 final String prefix = ns.getPrefix();
431 final String uri = namespaceStack.getURI(prefix);
432 if (!ns.getURI().equals(uri)) { // output a new namespace declaration
433 writeNamespace(ns, sorted);
434 namespaceStack.push(ns);
435 }
436 }
437
438 }
439 final Iterator iter = sorted.keySet().iterator();
440 while (iter.hasNext()) {
441 final String name = (String) iter.next();
442 final Node node = (Node) sorted.get(name);
443 if (node instanceof Attribute)
444 writeAttribute((Attribute) node);
445 else if (node instanceof Namespace) {
446 writeNSAttribute((Namespace) node);
447
448 }
449 }
450 }
451
452 private void writeNSAttribute(final Namespace namespace) throws IOException {
453 final String prefix = namespace.getPrefix();
454
455 // TODO This breaks example 3 from the Merlin Eight, but I'm not sure how to go about fixing it, due to DOM4J's
456 // nondifferentiation between <test/> and <test xmlns=""/>
457 if (Utility.isEmpty(prefix) && Utility.isEmpty(namespace.getURI()))
458 return;
459 writer.write(" xmlns");
460 if (prefix != null && prefix.length() > 0) {
461 writer.write(":");
462 writer.write(prefix);
463 }
464 writer.write("=\"");
465 writer.write(namespace.getURI());
466 writer.write("\"");
467 }
468
469 private boolean isNamespaceDeclaration(final Namespace ns) {
470 if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) {
471 final String uri = ns.getURI();
472 if (uri != null) {//&& uri.length() > 0 ) {
473 if (!namespaceStack.contains(ns)) {
474 return true;
475
476 }
477 }
478 }
479 return false;
480 }
481
482 /***
483 * This will take the pre-defined entities in XML 1.0 and
484 * convert their character representation to the appropriate
485 * entity reference, suitable for XML attributes.
486 */
487 private void writeEscapedString(final String text) throws IOException {
488 int i;
489 final int size = text.length();
490
491 for (i = 0; i < size; i++) {
492 switch (text.charAt(i)) {
493 case '<':
494 writer.write("<");
495 break;
496 case '>':
497 writer.write(">");
498 break;
499 case '&':
500 writer.write("&");
501 break;
502 case 0x0d:
503 writer.write("
");
504 break;
505 default :
506
507 writer.write(text.charAt(i));
508
509 }
510 }
511 }
512
513 /***
514 * This will take the pre-defined entities in XML 1.0 and
515 * convert their character representation to the appropriate
516 * entity reference, suitable for XML attributes.
517 */
518 private void writeEscapedAttributeString(final String text) throws IOException {
519 int i;
520 final int size = text.length();
521 for (i = 0; i < size; i++) {
522 switch (text.charAt(i)) {
523 case '<':
524 writer.write("<");
525 break;
526 // case '>' :
527 // writer.write(">");
528 // break;
529 case '&':
530 writer.write("&");
531 break;
532 case '"':
533 writer.write(""");
534 break;
535 case 0x0d:
536 writer.write("
");
537 break;
538 case 0x09:
539 writer.write(" ");
540 break;
541 case 0x0a:
542 writer.write("
");
543 break;
544 // case '\'' :
545 // writer.write("'");
546 // break;
547 default :
548 writer.write(text.charAt(i));
549 }
550 }
551 }
552
553 private Writer writer;
554 private ByteArrayOutputStream bos;
555 private boolean notfirst = false;
556 private NamespaceStack namespaceStack = new NamespaceStack();
557
558 public static final String XPATH_WO_COMMENTS = "(//. | //@* | //namespace::*| self::processing-instruction())[not(self::comment())]";
559 public static final String LF = new String(new byte[]{10});
560 public final static int C14NTYPE_NORMAL = 0;
561 public final static int C14NTYPE_WITH_COMMENTS = 1;
562 private final static Charset UTF8 = Charset.forName("UTF-8");
563
564 public static final String ALGORITHM = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
565
566 {
567 TransformerFactory.registerTransformer(ALGORITHM, Canonicalizer.class);
568 }
569
570
571 }
This page was automatically generated by Maven