package org.cagrid.gaards.saml.encoding; import gov.nih.nci.cagrid.common.Utils; import gov.nih.nci.cagrid.opensaml.SAMLAssertion; import gov.nih.nci.cagrid.opensaml.SAMLAttribute; import gov.nih.nci.cagrid.opensaml.SAMLAttributeStatement; import gov.nih.nci.cagrid.opensaml.SAMLException; import gov.nih.nci.cagrid.opensaml.XML; import java.io.ByteArrayInputStream; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.xml.namespace.QName; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * @author <A href="mailto:langella@bmi.osu.edu">Stephen Langella </A> * @author <A href="mailto:oster@bmi.osu.edu">Scott Oster </A> * @author <A href="mailto:hastings@bmi.osu.edu">Shannon Hastings </A> * @version $Id: ArgumentManagerTable.java,v 1.2 2004/10/15 16:35:16 langella * Exp $ */ public class SAMLUtils { public final static String XMLSEC_IGNORE_LINE_BREAK = "org.apache.xml.security.ignoreLineBreaks"; public final static String XMLNS_PREFIX = "xmlns"; public final static String XMLSIG_NS_PREFIX = "ds"; public final static QName ASSERTION_QNAME = new QName(XML.SAML_NS, "Assertion"); public final static QName SIGNATURE_QNAME = new QName(XML.XMLSIG_NS, "Signature"); public final static QName SIGNED_INFO_QNAME = new QName(XML.XMLSIG_NS, "SignedInfo"); public final static QName TRANSFORM_QNAME = new QName(XML.XMLSIG_NS, "Transform"); public final static QName INCLUSIVE_NAMESPACES_QNAME = new QName( "http://www.w3.org/2001/10/xml-exc-c14n#", "InclusiveNamespaces"); public final static QName PREFIX_LIST_QNAME = new QName(null, "PrefixList"); private final static Set<QName> _WHITESPACE_EXCLUSIONS = new HashSet<QName>(); public final static Set<QName> WHITESPACE_EXCLUSIONS = Collections .unmodifiableSet(_WHITESPACE_EXCLUSIONS); static { _WHITESPACE_EXCLUSIONS.add(TRANSFORM_QNAME); } public static SAMLAssertion domToSAMLAssertion(Element dom) throws Exception { SAMLAssertion saml = new SAMLAssertion(dom); return saml; } public static SAMLAssertion stringToSAMLAssertion(String str) throws Exception { SAMLAssertion saml = new SAMLAssertion(new ByteArrayInputStream( str.getBytes())); return saml; } public static String samlAssertionToString(SAMLAssertion saml) throws Exception { String xml = saml.toString(); return xml; } public static Element samlAssertionToDOM(SAMLAssertion saml) throws Exception { return (Element) saml.toDOM(); } public static String getAttributeValue(SAMLAssertion saml, String namespace, String name) { Iterator itr = saml.getStatements(); while (itr.hasNext()) { Object o = itr.next(); if (o instanceof SAMLAttributeStatement) { SAMLAttributeStatement att = (SAMLAttributeStatement) o; Iterator attItr = att.getAttributes(); while (attItr.hasNext()) { SAMLAttribute a = (SAMLAttribute) attItr.next(); if ((a.getNamespace().equals(namespace)) && (a.getName().equals(name))) { Iterator vals = a.getValues(); while (vals.hasNext()) { String val = Utils.clean((String) vals.next()); if (val != null) { return val; } } } } } } return null; } public static SAMLAssertion canonicalizeSAMLAssertion(SAMLAssertion saml0) throws SAMLException { Element element = (Element) saml0.toDOM(); canonicalizeAssertion(element); SAMLAssertion saml = new SAMLAssertion(element); return saml; } public static void canonicalizeAssertion(Element element) { Element assertionElement = findElement(element, ASSERTION_QNAME); if (assertionElement == null) return; // Remove any 'ds' namespace declaration from assertion. removeNamespaceDeclartion(assertionElement.getAttributes(), XML.XMLSIG_NS); // Remove all whitespace-only text nodes. removeWhitespace(assertionElement); // Nothing more to do if no signature element. Element signatureElement = findElement(assertionElement, SIGNATURE_QNAME); if (signatureElement == null) return; // Ensure signature element has namespace declaration. ensureNamespaceDeclartion(signatureElement.getOwnerDocument(), signatureElement.getAttributes(), XML.XMLSIG_NS, XMLSIG_NS_PREFIX); // Add whitespace in the signature element. Element signedInfoElement = findElement(signatureElement, SIGNATURE_QNAME); if (signedInfoElement == null) return; // If org.apache.xml.security.ignoreLineBreaks is true don't add // whitespace if (!Boolean.valueOf(System.getProperty(XMLSEC_IGNORE_LINE_BREAK))) addWhitespace(signedInfoElement, _WHITESPACE_EXCLUSIONS); // Force the digital signature namespace prefix to 'ds'. String currentPrefix = signatureElement.getPrefix(); boolean replacePrefix = !XMLSIG_NS_PREFIX.equals(currentPrefix); if (replacePrefix) setPrefix(signatureElement, SIGNATURE_QNAME.getNamespaceURI(), XMLSIG_NS_PREFIX); // In all inclusive-namespaces elements: // 1) Remove any 'ds' namespace declaration // 2) Replace the prefix in the PrefixList attribute NodeList inclusiveNamespaceElements = signedInfoElement .getElementsByTagNameNS( INCLUSIVE_NAMESPACES_QNAME.getNamespaceURI(), INCLUSIVE_NAMESPACES_QNAME.getLocalPart()); for (int i = 0, n = inclusiveNamespaceElements.getLength(); i < n; i++) { Node inclusiveNamespaceNode = inclusiveNamespaceElements.item(i); NamedNodeMap attrs = inclusiveNamespaceNode.getAttributes(); removeNamespaceDeclartion(attrs, XML.XMLSIG_NS); if (replacePrefix) { Attr prefixAttr = (Attr) attrs.getNamedItemNS( PREFIX_LIST_QNAME.getNamespaceURI(), PREFIX_LIST_QNAME.getLocalPart()); if (prefixAttr == null) continue; String attrValue = prefixAttr.getValue(); attrValue = replacePrefixInList(attrValue, currentPrefix, XMLSIG_NS_PREFIX); prefixAttr.setValue(attrValue); } } } public static Element findElement(Element element, QName qName) { if (qName.getNamespaceURI().equals(element.getNamespaceURI()) && qName.getLocalPart().equals(element.getLocalName())) return element; else { NodeList children = element.getChildNodes(); for (int i = 0, n = children.getLength(); i < n; i++) { Node child = children.item(i); if (child instanceof Element) { element = findElement((Element) child, qName); if (element != null) return element; } } } return null; } public static void removeWhitespace(Element element) { NodeList children = element.getChildNodes(); for (int i = children.getLength() - 1; i >= 0; i--) { Node child = children.item(i); if (child instanceof Text) { String data = ((Text) child).getData().trim(); if (data.isEmpty()) element.removeChild(child); } else if (child instanceof Element) removeWhitespace((Element) child); } } public static void addWhitespace(Element element, Set<QName> exemptions) { QName qName = new QName(element.getNamespaceURI(), element.getLocalName()); if (exemptions.contains(qName)) return; NodeList children = element.getChildNodes(); int n = children.getLength(); if (n == 0) return; Node firstChild = element.getFirstChild(); if ((n == 1) && (firstChild.getNodeType() == Node.TEXT_NODE)) return; Document doc = element.getOwnerDocument(); Text whitespace = doc.createTextNode("\n"); element.insertBefore(whitespace, null); for (int i = n - 1; i >= 0; i--) { Node child = children.item(i); whitespace = doc.createTextNode("\n"); element.insertBefore(whitespace, child); if (child instanceof Element) addWhitespace((Element) child, exemptions); } } public static void removeNamespaceDeclartion(NamedNodeMap attrs, String namespaceURI) { for (int i = 0, n = attrs.getLength(); i < n; i++) { Attr attr = (Attr) attrs.item(i); if (XML.XMLNS_NS.equals(attr.getNamespaceURI()) && namespaceURI.equals(attr.getValue())) { attrs.removeNamedItemNS(XML.XMLNS_NS, attr.getLocalName()); break; } } } public static void ensureNamespaceDeclartion(Document doc, NamedNodeMap attrs, String namespaceURI, String localName) { Attr attr = doc.createAttributeNS(XML.XMLNS_NS, XMLNS_PREFIX + ":" + localName); attr.setValue(namespaceURI); attrs.setNamedItemNS(attr); } public static void setPrefix(Element element, String namespaceURI, String prefix) { if (namespaceURI.equals(element.getNamespaceURI())) element.setPrefix(prefix); NodeList children = element.getChildNodes(); for (int i = 0, n = children.getLength(); i < n; i++) { Node child = children.item(i); if (child instanceof Element) setPrefix((Element) child, namespaceURI, prefix); } } public static String replacePrefixInList(String list, String oldPrefix, String newPrefix) { int listLength = list.length(); int oldPrefixLength = oldPrefix.length(); int ix = 0; int iy = oldPrefixLength; while (true) { ix = list.indexOf(oldPrefix, iy); if (ix < 0) break; if ((ix > 0) && !Character.isWhitespace(list.charAt(ix - 1))) continue; iy = ix + oldPrefixLength; if ((iy < listLength) && !Character.isWhitespace(list.charAt(iy))) continue; list = list.substring(0, ix) + newPrefix + list.substring(iy); break; } return list; } }