View Javadoc
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