001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the License). You may not use this file except in
005: * compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * https://glassfish.dev.java.net/public/CDDLv1.0.html.
009: * See the License for the specific language governing
010: * permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL
013: * Header Notice in each file and include the License file
014: * at https://glassfish.dev.java.net/public/CDDLv1.0.html.
015: * If applicable, add the following below the CDDL Header,
016: * with the fields enclosed by brackets [] replaced by
017: * you own identifying information:
018: * "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Copyright 2006 Sun Microsystems Inc. All Rights Reserved
021: */
022:
023: package com.sun.xml.wss.impl;
024:
025: import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
026: import com.sun.xml.wss.logging.LogDomainConstants;
027: import java.io.BufferedReader;
028: import java.io.ByteArrayInputStream;
029: import java.io.FileInputStream;
030: import java.io.FileNotFoundException;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.InputStreamReader;
034: import java.io.Reader;
035: import java.io.StringReader;
036: import java.io.UnsupportedEncodingException;
037: import java.net.URL;
038: import java.security.KeyFactory;
039: import java.security.cert.X509Certificate;
040: import java.security.spec.DSAPublicKeySpec;
041: import java.security.spec.RSAPublicKeySpec;
042: import java.util.ArrayList;
043: import java.util.List;
044: import java.util.logging.Level;
045: import java.util.logging.Logger;
046:
047: import javax.xml.namespace.QName;
048: import javax.xml.parsers.DocumentBuilder;
049: import javax.xml.parsers.DocumentBuilderFactory;
050: import javax.xml.parsers.ParserConfigurationException;
051: import javax.xml.soap.SOAPElement;
052: import javax.xml.soap.SOAPException;
053: import javax.xml.soap.SOAPFactory;
054: import javax.xml.transform.TransformerException;
055:
056: import org.w3c.dom.Attr;
057: import org.w3c.dom.DOMException;
058: import org.w3c.dom.Document;
059: import org.w3c.dom.Element;
060: import org.w3c.dom.NamedNodeMap;
061: import org.w3c.dom.Node;
062: import org.w3c.dom.NodeList;
063: import org.xml.sax.InputSource;
064: import org.xml.sax.helpers.DefaultHandler;
065:
066: import com.sun.org.apache.xml.internal.security.keys.content.X509Data;
067: import com.sun.org.apache.xml.internal.security.keys.content.keyvalues.DSAKeyValue;
068: import com.sun.org.apache.xml.internal.security.keys.content.keyvalues.RSAKeyValue;
069: import com.sun.xml.wss.impl.misc.Base64;
070: import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
071: import com.sun.org.apache.xpath.internal.XPathAPI;
072: import com.sun.xml.wss.*;
073:
074: //introducing a SAAJ impl dependency
075: import com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl;
076: import java.net.MalformedURLException;
077:
078: public class XMLUtil {
079:
080: /**
081: * This is a custom XML handler to load the dtds from the classpath
082: * This should be used by all the xml parsing document builders to
083: * set the default entity resolvers. This will avoid to have the
084: * dtds having specified in a fixed directory that will get replaced
085: * during installation
086: * This will need to specify the dtds as follows
087: * jar://com/sun/identity/sm/sms.dtd
088: * Bundle all the dtds along with the jar files and
089: */
090:
091: protected static final Logger logger = Logger.getLogger(
092: LogDomainConstants.WSS_API_DOMAIN,
093: LogDomainConstants.WSS_API_DOMAIN_BUNDLE);
094:
095: static class XMLHandler extends DefaultHandler {
096:
097: /**
098: * This method reads the resource from a reader
099: */
100: String read(Reader aReader) {
101: StringBuffer sb = new StringBuffer();
102:
103: try {
104: BufferedReader bReader = new BufferedReader(aReader);
105: char[] data = new char[2048];
106: int count = 0;
107:
108: while ((count = bReader.read(data)) != -1) {
109: sb.append(data, 0, count);
110: }
111:
112: //while loop
113: bReader.close();
114: aReader.close();
115: } catch (IOException e) {
116: }
117:
118: //try/catch
119: return sb.toString();
120: }
121:
122: /**
123: * This method reads the resource from the classloader which load this class
124: */
125: String read(String fileName) {
126: return read(fileName, XMLUtil.class);
127: }
128:
129: /**
130: * Reads the resource from a class loader.
131: *
132: * @param fileName resource to be read.
133: * @param cl class which delegates the classloader functionality.
134: * @return resource value.
135: */
136: String read(String fileName, Class cl) {
137: String data = "";
138:
139: try {
140: InputStream in = cl.getResourceAsStream(fileName);
141:
142: //may be absoulte file path is given
143: if (in == null) {
144: try {
145: //works well if the user has given absolute path
146: in = new FileInputStream(fileName);
147: } catch (FileNotFoundException e) {
148: //works well if the user has given the relative path to the
149: //location of class file
150: String directoryURL = cl.getProtectionDomain()
151: .getCodeSource().getLocation()
152: .toString();
153: String fileURL = directoryURL + fileName;
154: URL url = new URL(fileURL);
155: in = url.openStream();
156: }
157: }
158:
159: //if
160: data = read(new InputStreamReader(in));
161: in.close();
162: } catch (MalformedURLException e) {
163: //ignoring this as well
164: } catch (IOException e) {
165: //ignore
166: }
167:
168: //try/catch
169: return data;
170: }
171:
172: public InputSource resolveEntity(String aPublicID,
173: String aSystemID) {
174: String sysid = aSystemID.trim();
175:
176: if (sysid.toLowerCase().startsWith("jar://")) {
177: String dtdname = sysid.substring(5);
178: String dtdValue = read(dtdname).trim();
179:
180: return new InputSource(new StringReader(dtdValue));
181: }
182:
183: return null;
184: }
185: }
186:
187: protected static SOAPFactory soapFactory;
188: static {
189: try {
190: soapFactory = SOAPFactory.newInstance();
191: } catch (SOAPException e) {
192:
193: }
194: }
195:
196: private static boolean validating = false;
197:
198: /**
199: * convertToSoapElement
200: *
201: * @param doc the Owner Soap Document of the SOAPElement to be returned
202: * @param elem the DOM Element to be converted to SOAPElement
203: * @return a SOAPElement whose parent node is null
204: * @throws DOMException
205: */
206: public static SOAPElement convertToSoapElement(Document doc,
207: Element elem) throws DOMException, ClassCastException {
208: if (elem instanceof SOAPElement)
209: return (SOAPElement) elem;
210: return (SOAPElement) doc.importNode(elem, true);
211: }
212:
213: /**
214: * This method searches children of Element element for element with tagName
215: * and namespaceURI nsName. It searchs one level down only.
216: * @param element The root element
217: * @param nsName NamespaceURI
218: * @param tagName A String representing the name of the tag to be searched
219: * for.
220: * @return A List of elements that meet the criterial.
221: */
222: public static List getElementsByTagNameNS1(Element element,
223: String nsName, String tagName) {
224: List list = new ArrayList();
225:
226: if (element != null) {
227: NodeList nl = element.getChildNodes();
228: int length = nl.getLength();
229: Node child = null;
230: String childName;
231: String childNS;
232:
233: for (int i = 0; i < length; i++) {
234: child = nl.item(i);
235: childName = child.getLocalName();
236: childNS = child.getNamespaceURI();
237:
238: if ((childName != null) && (childName.equals(tagName))
239: && (childNS != null)
240: && (childNS.equals(nsName))) {
241: list.add(child);
242: }
243: }
244: }
245:
246: return list;
247: }
248:
249: public static String getFullTextFromChildren(Element element) {
250: if (element == null) {
251: return null;
252: }
253:
254: StringBuffer sb = new StringBuffer(1000);
255: NodeList nl = element.getChildNodes();
256: Node child = null;
257: int length = nl.getLength();
258:
259: for (int i = 0; i < length; i++) {
260: child = nl.item(i);
261:
262: if (child.getNodeType() == Node.TEXT_NODE) {
263: sb.append(child.getNodeValue());
264: }
265: }
266:
267: return sb.toString().trim();
268: }
269:
270: public static boolean inEncryptionNS(SOAPElement element) {
271: return element.getNamespaceURI().equals(
272: MessageConstants.XENC_NS);
273: }
274:
275: public static boolean inSamlNSv1_0(SOAPElement element) {
276: return element.getNamespaceURI().equals(
277: MessageConstants.SAML_v1_0_NS);
278: }
279:
280: public static boolean inSamlNSv2_0(SOAPElement element) {
281: return element.getNamespaceURI().equals(
282: MessageConstants.SAML_v2_0_NS);
283: }
284:
285: public static boolean inSamlNSv1_1(SOAPElement element) {
286: return element.getNamespaceURI().equals(
287: MessageConstants.SAML_v1_1_NS);
288: }
289:
290: public static boolean inSignatureNS(SOAPElement element) {
291: return element.getNamespaceURI().equals(
292: MessageConstants.DSIG_NS);
293: }
294:
295: public static boolean inWsseNS(SOAPElement element) {
296: return element.getNamespaceURI().equals(
297: MessageConstants.WSSE_NS);
298: }
299:
300: public static boolean inWsscNS(SOAPElement element) {
301: return element.getNamespaceURI().equals(
302: MessageConstants.WSSC_NS);
303: }
304:
305: public static boolean inWsse11NS(SOAPElement element) {
306: return element.getNamespaceURI().equals(
307: MessageConstants.WSSE11_NS);
308: }
309:
310: public static boolean inWSS11_NS(SOAPElement element) {
311: return element.getNamespaceURI().equals(
312: MessageConstants.WSS11_SPEC_NS);
313: }
314:
315: public static boolean inWsuNS(SOAPElement element) {
316: return element.getNamespaceURI()
317: .equals(MessageConstants.WSU_NS);
318: }
319:
320: public static String resolveXPath(Node element) throws Exception {
321: if (element.getOwnerDocument() == null) {
322: logger.log(Level.SEVERE,
323: "WSS0424.null.OwnerDocument.element");
324: throw new Exception(
325: "Element does not have an owner document");
326: }
327: StringBuffer xpath = new StringBuffer();
328: String prefix = element.getPrefix();
329: String lcname = element.getLocalName();
330: String lxpath = prefix + ":" + lcname;
331: xpath.append(lxpath);
332: Node parentNode = element.getParentNode();
333: while (parentNode != null
334: && parentNode.getNodeType() != Node.DOCUMENT_NODE) {
335: prefix = parentNode.getPrefix();
336: lcname = parentNode.getLocalName();
337: lxpath = prefix + ":" + lcname + "/";
338: xpath.insert(0, lxpath);
339: parentNode = parentNode.getParentNode();
340: }
341: xpath.insert(0, "./");
342: return xpath.toString();
343: }
344:
345: public static Element prependChildElement(Element parent,
346: Element child, boolean addWhitespace, Document doc) {
347:
348: Node firstChild = parent.getFirstChild();
349: if (firstChild == null) {
350: parent.appendChild(child);
351: } else {
352: parent.insertBefore(child, firstChild);
353: }
354:
355: if (addWhitespace) {
356: Node whitespaceText = doc.createTextNode("\n");
357: parent.insertBefore(whitespaceText, child);
358: }
359: return child;
360: }
361:
362: public static Element prependChildElement(Element parent,
363: Element child, Document doc) {
364: return prependChildElement(parent, child, true, doc);
365: }
366:
367: /**
368: * Print a Node tree recursively.
369: * @param node A DOM tree Node
370: * @return An xml String representation of the DOM tree.
371: */
372: public static String print(Node node) {
373: if (node == null) {
374: return null;
375: }
376:
377: StringBuffer xml = new StringBuffer(100);
378: int type = node.getNodeType();
379:
380: switch (type) {
381: // print element with attributes
382: case Node.ELEMENT_NODE: {
383: xml.append('<');
384: xml.append(node.getNodeName());
385:
386: NamedNodeMap attrs = node.getAttributes();
387: int length = attrs.getLength();
388: ;
389:
390: for (int i = 0; i < length; i++) {
391: Attr attr = (Attr) attrs.item(i);
392: xml.append(' ');
393: xml.append(attr.getNodeName());
394: xml.append("=\"");
395:
396: //xml.append(normalize(attr.getNodeValue()));
397: xml.append(attr.getNodeValue());
398: xml.append('"');
399: }
400:
401: xml.append('>');
402:
403: NodeList children = node.getChildNodes();
404:
405: if (children != null) {
406: int len = children.getLength();
407:
408: for (int i = 0; i < len; i++) {
409: xml.append(print(children.item(i)));
410: }
411: }
412:
413: break;
414: }
415:
416: // handle entity reference nodes
417: case Node.ENTITY_REFERENCE_NODE: {
418: NodeList children = node.getChildNodes();
419:
420: if (children != null) {
421: int len = children.getLength();
422:
423: for (int i = 0; i < len; i++) {
424: xml.append(print(children.item(i)));
425: }
426: }
427:
428: break;
429: }
430:
431: // print cdata sections
432: case Node.CDATA_SECTION_NODE: {
433: xml.append("<![CDATA[");
434: xml.append(node.getNodeValue());
435: xml.append("]]>");
436:
437: break;
438: }
439:
440: // print text
441: case Node.TEXT_NODE: {
442: //xml.append(normalize(node.getNodeValue()));
443: xml.append(node.getNodeValue());
444:
445: break;
446: }
447:
448: // print processing instruction
449: case Node.PROCESSING_INSTRUCTION_NODE: {
450: xml.append("<?");
451: xml.append(node.getNodeName());
452:
453: String data = node.getNodeValue();
454:
455: if ((data != null) && (data.length() > 0)) {
456: xml.append(' ');
457: xml.append(data);
458: }
459:
460: xml.append("?>");
461:
462: break;
463: }
464: }
465:
466: if (type == Node.ELEMENT_NODE) {
467: xml.append("</");
468: xml.append(node.getNodeName());
469: xml.append('>');
470: }
471:
472: return xml.toString();
473: }
474:
475: public static Node selectSingleNode(Node contextNode, String xpath,
476: Element nsContext) throws XWSSecurityException {
477:
478: try {
479: return XPathAPI.selectSingleNode(contextNode, xpath,
480: nsContext);
481: } catch (TransformerException e) {
482: logger.log(Level.SEVERE, "WSS0425.unableto.resolve.xpath",
483: e);
484: throw new XWSSecurityException("Unable to resolve XPath", e);
485: }
486: }
487:
488: public static void setWsuIdAttr(Element element, String wsuId) {
489: element.setAttributeNS(MessageConstants.NAMESPACES_NS, "xmlns:"
490: + MessageConstants.WSU_PREFIX, MessageConstants.WSU_NS);
491: element.setAttributeNS(MessageConstants.WSU_NS,
492: MessageConstants.WSU_ID_QNAME, wsuId);
493: }
494:
495: public static void setIdAttr(Element element, String Id) {
496: element.setAttribute("Id", Id);
497: element.setIdAttribute("Id", true);
498: }
499:
500: /**
501: * Converts the XML document from an input stream to DOM Document format.
502: *
503: * @param is is the InputStream that contains XML document
504: * @return Document is the DOM object obtained by parsing the input stream.
505: * Returns null if there are any parser errores.
506: */
507: public static Document toDOMDocument(InputStream is) {
508: /* Constructing a DocumentBuilderFactory for every call is less expensive than a
509: synchronizing a single instance of the factory and obtaining the builder
510: */
511: DocumentBuilderFactory dbFactory = null;
512:
513: try {
514: // Assign new debug object
515: dbFactory = new com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl();
516: dbFactory.setValidating(validating);
517: dbFactory.setNamespaceAware(true);
518: } catch (Exception e) {
519: }
520:
521: try {
522: DocumentBuilder documentBuilder = dbFactory
523: .newDocumentBuilder();
524:
525: if (documentBuilder == null) {
526: return null;
527: }
528:
529: documentBuilder.setEntityResolver(new XMLHandler());
530: //XMLHandler());
531:
532: return documentBuilder.parse(is);
533: } catch (Exception e) {
534: // Since there may potentially be several invalid XML documents
535: // that are mostly client-side errors, only a warning is logged for
536: // efficiency reasons.
537: return null;
538: }
539: }
540:
541: /**
542: * Converts the XML document from a String format to DOM Document format.
543: *
544: * @param xmlString is the XML document in a String format
545: * @return Document is the DOM object obtained by converting the String XML
546: * Returns null if xmlString is null.
547: * Returns null if there are any parser errores.
548: */
549: public static Document toDOMDocument(String xmlString) {
550: if (xmlString == null) {
551: return null;
552: }
553:
554: try {
555: ByteArrayInputStream is = new ByteArrayInputStream(
556: xmlString.getBytes("UTF-8"));
557:
558: return toDOMDocument(is);
559: } catch (UnsupportedEncodingException uee) {
560: return null;
561: }
562: }
563:
564: /**
565: * Obtains a new instance of a DOM Document object
566: * @return a new instance of a DOM Document object
567: * @exception Exception if an error occurs while constructing a new
568: * document
569: */
570: public static Document newDocument()
571: throws ParserConfigurationException {
572: DocumentBuilderFactory dbFactory = new com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl();
573: dbFactory.setNamespaceAware(true);
574: dbFactory.setValidating(validating);
575:
576: return dbFactory.newDocumentBuilder().newDocument();
577: }
578:
579: /**
580: * Checks if a node has a child of ELEMENT type.
581: * @param node a node
582: * @return true if the node has a child of ELEMENT type
583: */
584: public static boolean hasElementChild(Node node) {
585: NodeList nl = node.getChildNodes();
586: Node child = null;
587: int length = nl.getLength();
588:
589: for (int i = 0; i < length; i++) {
590: child = nl.item(i);
591:
592: if (child.getNodeType() == Node.ELEMENT_NODE) {
593: return true;
594: }
595: }
596:
597: return false;
598: }
599:
600: public static DSAKeyValue getDSAKeyValue(Document doc,
601: X509Certificate cert) throws XWSSecurityException {
602: try {
603: KeyFactory keyFactory = KeyFactory.getInstance("DSA");
604: DSAPublicKeySpec dsaPkSpec = (DSAPublicKeySpec) keyFactory
605: .getKeySpec(cert.getPublicKey(),
606: DSAPublicKeySpec.class);
607: return new DSAKeyValue(doc, dsaPkSpec.getP(), dsaPkSpec
608: .getQ(), dsaPkSpec.getG(), dsaPkSpec.getY());
609:
610: } catch (Exception e) {
611: logger.log(Level.SEVERE, "WSS0426.failed.DSAKeyValue", e);
612: throw new XWSSecurityException(e);
613: }
614: }
615:
616: public static RSAKeyValue getRSAKeyValue(Document doc,
617: X509Certificate cert) throws XWSSecurityException {
618: try {
619: KeyFactory keyFactory = KeyFactory.getInstance("RSA");
620: RSAPublicKeySpec rsaPkSpec = (RSAPublicKeySpec) keyFactory
621: .getKeySpec(cert.getPublicKey(),
622: RSAPublicKeySpec.class);
623: return new RSAKeyValue(doc, rsaPkSpec.getModulus(),
624: rsaPkSpec.getPublicExponent());
625:
626: } catch (Exception e) {
627: logger.log(Level.SEVERE, "WSS0293.failed.RSAKeyValue", e);
628: throw new XWSSecurityException(e);
629: }
630: }
631:
632: // The X509Certificate element, which contains
633: // a base64-encoded [X509v3] certificate is added into the X509Data
634: public static X509Data getX509Data(Document doc,
635: X509Certificate cert) throws XWSSecurityException {
636: try {
637: X509Data x509Data = new X509Data(doc);
638: x509Data.addCertificate(cert);
639: return x509Data;
640: } catch (Exception e) {
641: logger.log(Level.SEVERE, "WSS0294.failed.X509Data", e);
642: throw new XWSSecurityException(e);
643: }
644: }
645:
646: /**
647: * Looks up elements with wsu:Id or Id in xenc or dsig namespace.
648: *
649: * @param doc
650: * @param id
651: *
652: * @return element
653: *
654: * @throws TransformerException
655: */
656: public static Element getElementById(Document doc, String id)
657: throws TransformerException {
658:
659: Element selement = doc.getElementById(id);
660: if (selement != null) {
661: return selement;
662: }
663:
664: Document sdoc = null;
665: if (doc instanceof SOAPDocumentImpl) {
666: sdoc = ((SOAPDocumentImpl) doc).getSOAPPart();
667: }
668: if (sdoc == null) {
669: sdoc = doc;
670: }
671:
672: Element nscontext = XMLUtils.createDSctx(sdoc, "wsu",
673: MessageConstants.WSU_NS);
674: Element element = (Element) XPathAPI.selectSingleNode(sdoc,
675: "//*[@wsu:Id='" + id + "']", nscontext);
676:
677: if (element == null) {
678: NodeList elems = XPathAPI.selectNodeList(sdoc, "//*[@Id='"
679: + id + "']", nscontext);
680: for (int i = 0; i < elems.getLength(); i++) {
681: Element elem = (Element) elems.item(i);
682: String namespace = elem.getNamespaceURI();
683: if (namespace.equals(MessageConstants.DSIG_NS)
684: || namespace.equals(MessageConstants.XENC_NS)
685: || namespace.equals(MessageConstants.WSSE11_NS)) {
686: element = elem;
687: break;
688: }
689: }
690: }
691:
692: // look for SAML AssertionID
693: if (element == null) {
694:
695: /*NodeList assertions =
696: sdoc.getElementsByTagNameNS(MessageConstants.SAML_v1_0_NS,
697: MessageConstants.SAML_ASSERTION_LNAME);*/
698: NodeList assertions = sdoc
699: .getElementsByTagName(MessageConstants.SAML_ASSERTION_LNAME);
700: int len = assertions.getLength();
701: if (len > 0) {
702: for (int i = 0; i < len; i++) {
703: SOAPElement elem = (SOAPElement) assertions.item(i);
704: String assertionId = elem
705: .getAttribute(MessageConstants.SAML_ASSERTIONID_LNAME);
706: String saml20Id = elem
707: .getAttribute(MessageConstants.SAML_ID_LNAME);
708: if (id.equals(assertionId) || id.equals(saml20Id)) {
709: element = elem;
710: break;
711: }
712: }
713: }
714: }
715:
716: return element;
717: }
718:
719: public static String convertToXpath(String qname) {
720: QName name = QName.valueOf(qname);
721: if ("".equals(name.getNamespaceURI())) {
722: return "//" + name.getLocalPart();
723: } else {
724: return "//*[local-name()='" + name.getLocalPart()
725: + "' and namespace-uri()='"
726: + name.getNamespaceURI() + "']";
727: }
728: }
729:
730: public static byte[] getDecodedBase64EncodedData(String encodedData)
731: throws XWSSecurityException {
732: try {
733: return Base64.decode(encodedData);
734: } catch (Base64DecodingException e) {
735: logger.log(Level.SEVERE, "WSS0427.unableto.decode.base64",
736: e);
737: throw new XWSSecurityException(
738: "Unable to decode Base64 encoded data", e);
739: }
740: }
741:
742: public static Document getOwnerDocument(Node node) {
743: if (node.getNodeType() == Node.DOCUMENT_NODE) {
744: return (Document) node;
745: } else {
746: return node.getOwnerDocument();
747: }
748: }
749:
750: public static Element getFirstChildElement(Node node) {
751: Node child = node.getFirstChild();
752: while (child != null
753: && child.getNodeType() != Node.ELEMENT_NODE) {
754: child = child.getNextSibling();
755: }
756: return (Element) child;
757: }
758:
759: public static Element createElement(Document doc, String tag,
760: String nsURI, String prefix) {
761: String qName = prefix == null ? tag : prefix + ":" + tag;
762: return doc.createElementNS(nsURI, qName);
763: }
764:
765: }
|