blob: 370e787b77bda1c119bf4c0a6578749e1db2649f [file] [log] [blame]
/*
* Copyright 2003-2006 The Apache Software Foundation, or their licensors, as
* appropriate.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ws.security.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.SOAP11Constants;
import org.apache.ws.security.SOAP12Constants;
import org.apache.ws.security.SOAPConstants;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.handler.WSHandlerResult;
import org.apache.xml.security.algorithms.JCEMapper;
import org.apache.xml.security.signature.XMLSignature;
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;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.namespace.QName;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
/**
* WS-Security Utility methods. <p/>
*
* @author Davanum Srinivas (dims@yahoo.com).
*/
public class WSSecurityUtil {
private static Log log = LogFactory.getLog(WSSecurityUtil.class);
/**
* A cached pseudo-random number generator
* NB. On some JVMs, caching this random number
* generator is required to overcome punitive
* overhead.
*/
private static SecureRandom random = null;
/**
* Returns the first WS-Security header element for a given actor. Only one
* WS-Security header is allowed for an actor.
*
* @param doc
* @param actor
* @return the <code>wsse:Security</code> element or <code>null</code>
* if not such element found
*/
public static Element getSecurityHeader(Document doc, String actor,
SOAPConstants sc) {
Element soapHeaderElement = (Element) getDirectChild(doc
.getDocumentElement(), sc.getHeaderQName().getLocalPart(), sc
.getEnvelopeURI());
if (soapHeaderElement == null) { // no SOAP header at all
return null;
}
// get all wsse:Security nodes
NodeList list = null;
int len = 0;
list = soapHeaderElement.getElementsByTagNameNS(WSConstants.WSSE_NS,
WSConstants.WSSE_LN);
if (list == null) {
return null;
} else {
len = list.getLength();
}
Element elem;
Attr attr;
String hActor;
for (int i = 0; i < len; i++) {
elem = (Element) list.item(i);
attr = elem.getAttributeNodeNS(sc.getEnvelopeURI(), sc
.getRoleAttributeQName().getLocalPart());
hActor = (attr != null) ? attr.getValue() : null;
if (WSSecurityUtil.isActorEqual(actor, hActor)) {
return elem;
}
}
return null;
}
/**
* Compares two actor strings and returns true if these are equal. Takes
* care of the null length strings and uses ignore case.
*
* @param actor
* @param hActor
* @return TODO
*/
public static boolean isActorEqual(String actor, String hActor) {
if ((((hActor == null) || (hActor.length() == 0)) && ((actor == null) || (actor
.length() == 0)))
|| ((hActor != null) && (actor != null) && hActor
.equalsIgnoreCase(actor))) {
return true;
} else {
return false;
}
}
/**
* Gets a direct child with specified localname and namespace. <p/>
*
* @param fNode
* the node where to start the search
* @param localName
* local name of the child to get
* @param namespace
* the namespace of the child to get
* @return the node or <code>null</code> if not such node found
*/
public static Node getDirectChild(Node fNode, String localName,
String namespace) {
for (Node currentChild = fNode.getFirstChild(); currentChild != null; currentChild = currentChild
.getNextSibling()) {
if (localName.equals(currentChild.getLocalName())
&& namespace.equals(currentChild.getNamespaceURI())) {
return currentChild;
}
}
return null;
}
/**
* return the first soap "Body" element. <p/>
*
* @param doc
* @return the body element or <code>null</code> if document does not
* contain a SOAP body
*/
public static Element findBodyElement(Document doc, SOAPConstants sc) {
Element soapBodyElement = (Element) WSSecurityUtil.getDirectChild(doc
.getFirstChild(), sc.getBodyQName().getLocalPart(), sc
.getEnvelopeURI());
return soapBodyElement;
}
/**
* Returns the first element that matches <code>name</code> and
* <code>namespace</code>. <p/> This is a replacement for a XPath lookup
* <code>//name</code> with the given namespace. It's somewhat faster than
* XPath, and we do not deal with prefixes, just with the real namespace URI
*
* @param startNode
* Where to start the search
* @param name
* Local name of the element
* @param namespace
* Namespace URI of the element
* @return The found element or <code>null</code>
*/
public static Node findElement(Node startNode, String name, String namespace) {
/*
* Replace the formely recursive implementation with a depth-first-loop
* lookup
*/
if (startNode == null) {
return null;
}
Node startParent = startNode.getParentNode();
Node processedNode = null;
while (startNode != null) {
// start node processing at this point
if (startNode.getNodeType() == Node.ELEMENT_NODE
&& startNode.getLocalName().equals(name)) {
String ns = startNode.getNamespaceURI();
if (ns != null && ns.equals(namespace)) {
return startNode;
}
if ((namespace == null || namespace.length() == 0)
&& (ns == null || ns.length() == 0)) {
return startNode;
}
}
processedNode = startNode;
startNode = startNode.getFirstChild();
// no child, this node is done.
if (startNode == null) {
// close node processing, get sibling
startNode = processedNode.getNextSibling();
}
// no more siblings, get parent, all children
// of parent are processed.
while (startNode == null) {
processedNode = processedNode.getParentNode();
if (processedNode == startParent) {
return null;
}
// close parent node processing (processed node now)
startNode = processedNode.getNextSibling();
}
}
return null;
}
/**
* Returns the single element that containes an Id with value
* <code>uri</code> and <code>namespace</code>. <p/> This is a
* replacement for a XPath Id lookup with the given namespace. It's somewhat
* faster than XPath, and we do not deal with prefixes, just with the real
* namespace URI
*
* If there are multiple elements, we log a warning and return null as this
* can be used to get around the signature checking.
*
* @param startNode
* Where to start the search
* @param value
* Value of the Id attribute
* @param namespace
* Namespace URI of the Id
* @return The found element if there was exactly one match, or
* <code>null</code> otherwise
*/
public static Element findElementById(Node startNode, String value,
String namespace) {
Element foundElement = null;
/*
* Replace the formerly recursive implementation with a depth-first-loop
* lookup
*/
if (startNode == null) {
return null;
}
Node startParent = startNode.getParentNode();
Node processedNode = null;
while (startNode != null) {
// start node processing at this point
if (startNode.getNodeType() == Node.ELEMENT_NODE) {
Element se = (Element) startNode;
if (se.hasAttributeNS(namespace, "Id")
&& value.equals(se.getAttributeNS(namespace, "Id"))) {
if (foundElement == null) {
foundElement = se; // Continue searching to find
// duplicates
} else {
log
.warn("Multiple elements with the same 'Id' attribute value!");
return null;
}
}
}
processedNode = startNode;
startNode = startNode.getFirstChild();
// no child, this node is done.
if (startNode == null) {
// close node processing, get sibling
startNode = processedNode.getNextSibling();
}
// no more siblings, get parent, all children
// of parent are processed.
while (startNode == null) {
processedNode = processedNode.getParentNode();
if (processedNode == startParent) {
return foundElement;
}
// close parent node processing (processed node now)
startNode = processedNode.getNextSibling();
}
}
return foundElement;
}
/**
* set the namespace if it is not set already. <p/>
*
* @param element
* @param namespace
* @param prefix
* @return TODO
*/
public static String setNamespace(Element element, String namespace,
String prefix) {
String pre = getPrefixNS(namespace, element);
if (pre != null) {
return pre;
}
element.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:" + prefix,
namespace);
return prefix;
}
/*
* ** The following methods were copied over from aixs.utils.XMLUtils and
* adapted
*/
public static String getPrefixNS(String uri, Node e) {
while (e != null && (e.getNodeType() == Element.ELEMENT_NODE)) {
NamedNodeMap attrs = e.getAttributes();
for (int n = 0; n < attrs.getLength(); n++) {
Attr a = (Attr) attrs.item(n);
String name;
if ((name = a.getName()).startsWith("xmlns:")
&& a.getNodeValue().equals(uri)) {
return name.substring(6);
}
}
e = e.getParentNode();
}
return null;
}
public static String getNamespace(String prefix, Node e) {
while (e != null && (e.getNodeType() == Node.ELEMENT_NODE)) {
Attr attr = null;
if (prefix == null) {
attr = ((Element) e).getAttributeNode("xmlns");
} else {
attr = ((Element) e).getAttributeNodeNS(WSConstants.XMLNS_NS,
prefix);
}
if (attr != null)
return attr.getValue();
e = e.getParentNode();
}
return null;
}
/**
* Return a QName when passed a string like "foo:bar" by mapping the "foo"
* prefix to a namespace in the context of the given Node.
*
* @return a QName generated from the given string representation
*/
public static QName getQNameFromString(String str, Node e) {
return getQNameFromString(str, e, false);
}
/**
* Return a QName when passed a string like "foo:bar" by mapping the "foo"
* prefix to a namespace in the context of the given Node. If default
* namespace is found it is returned as part of the QName.
*
* @return a QName generated from the given string representation
*/
public static QName getFullQNameFromString(String str, Node e) {
return getQNameFromString(str, e, true);
}
private static QName getQNameFromString(String str, Node e,
boolean defaultNS) {
if (str == null || e == null)
return null;
int idx = str.indexOf(':');
if (idx > -1) {
String prefix = str.substring(0, idx);
String ns = getNamespace(prefix, e);
if (ns == null)
return null;
return new QName(ns, str.substring(idx + 1));
} else {
if (defaultNS) {
String ns = getNamespace(null, e);
if (ns != null)
return new QName(ns, str);
}
return new QName("", str);
}
}
/**
* Return a string for a particular QName, mapping a new prefix if
* necessary.
*/
public static String getStringForQName(QName qname, Element e) {
String uri = qname.getNamespaceURI();
String prefix = getPrefixNS(uri, e);
if (prefix == null) {
int i = 1;
prefix = "ns" + i;
while (getNamespace(prefix, e) != null) {
i++;
prefix = "ns" + i;
}
e.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:" + prefix, uri);
}
return prefix + ":" + qname.getLocalPart();
}
/* ** up to here */
/**
* Search for an element given its wsu:id. <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param id
* the Id of the element
* @return the found element or null if no element with the Id exists
*/
public static Element getElementByWsuId(Document doc, String id) {
if (id == null) {
return null;
}
id = getIDFromReference(id);
return WSSecurityUtil.findElementById(doc.getDocumentElement(), id,
WSConstants.WSU_NS);
}
/**
* Turn a reference (eg "#5") into an ID (eg "5").
*
* @param ref
* @return ref trimmed and with the leading "#" removed, or null if not
* correctly formed
*/
public static String getIDFromReference(String ref) {
String id = ref.trim();
if ((id.length() == 0) || (id.charAt(0) != '#')) {
return null;
}
return id.substring(1);
}
/**
* Turn a reference (eg "#5") into an ID (eg "5").
*
* @param ref
* @return ref trimmed and with the leading "#" removed, or null if not
* correctly formed
* @deprecated use getIDFromReference instead
*/
public static String getIDfromReference(String ref) {
return getIDFromReference(ref);
}
/**
* Search for an element given its generic id. <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param id
* the Id of the element
* @return the found element or null if no element with the Id exists
*/
public static Element getElementByGenId(Document doc, String id) {
if (id == null) {
return null;
}
id = id.trim();
if ((id.length() == 0) || (id.charAt(0) != '#')) {
return null;
}
id = id.substring(1);
return WSSecurityUtil.findElementById(doc.getDocumentElement(), id,
null);
}
/**
* create a new element in the same namespace <p/>
*
* @param parent
* for the new element
* @param localName
* of the new element
* @return the new element
*/
private static Element createElementInSameNamespace(Element parent,
String localName) {
String qName = localName;
String prefix = parent.getPrefix();
if (prefix != null && prefix.length() > 0) {
qName = prefix + ":" + localName;
}
String nsUri = parent.getNamespaceURI();
return parent.getOwnerDocument().createElementNS(nsUri, qName);
}
/**
* find a child element with given namespace and local name <p/>
*
* @param parent
* the node to start the search
* @param namespaceUri
* of the element
* @param localName
* of the element
* @return the found element or null if the element does not exist
*/
private static Element findChildElement(Element parent,
String namespaceUri, String localName) {
NodeList children = parent.getChildNodes();
int len = children.getLength();
for (int i = 0; i < len; i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element elementChild = (Element) child;
if (namespaceUri.equals(elementChild.getNamespaceURI())
&& localName.equals(elementChild.getLocalName())) {
return elementChild;
}
}
}
return null;
}
/**
* append a child element <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param parent
* element of this child element
* @param child
* the element to append
* @return the child element
*/
public static Element appendChildElement(Document doc, Element parent,
Element child) {
Node whitespaceText = doc.createTextNode("\n");
parent.appendChild(whitespaceText);
parent.appendChild(child);
return child;
}
/**
* prepend a child element <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param parent
* element of this child element
* @param child
* the element to append
* @param addWhitespace
* if true prepend a newline before child
* @return the child element
*/
public static Element prependChildElement(Document doc, Element parent,
Element child, boolean addWhitespace) {
Node firstChild = parent.getFirstChild();
if (firstChild == null) {
parent.appendChild(child);
} else {
parent.insertBefore(child, firstChild);
}
if (addWhitespace) {
Node whitespaceText = doc.createTextNode("\n");
parent.insertBefore(whitespaceText, child);
}
return child;
}
/**
* find the first ws-security header block <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param envelope
* the SOAP envelope
* @param doCreate
* if true create a new WSS header block if none exists
* @return the WSS header or null if none found and doCreate is false
*/
public static Element findWsseSecurityHeaderBlock(Document doc,
Element envelope, boolean doCreate) {
return findWsseSecurityHeaderBlock(doc, envelope, null, doCreate);
}
/**
* find a ws-security header block for a given actor <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param envelope
* the SOAP envelope
* @param actor
* the actor (role) name of the WSS header
* @param doCreate
* if true create a new WSS header block if none exists
* @return the WSS header or null if none found and doCreate is false
*/
public static Element findWsseSecurityHeaderBlock(Document doc,
Element envelope, String actor, boolean doCreate) {
SOAPConstants sc = getSOAPConstants(envelope);
Element wsseSecurity = getSecurityHeader(doc, actor, sc);
if (wsseSecurity != null) {
return wsseSecurity;
}
Element header = findChildElement(envelope, sc.getEnvelopeURI(), sc
.getHeaderQName().getLocalPart());
if (header == null && doCreate) {
header = createElementInSameNamespace(envelope, sc
.getHeaderQName().getLocalPart());
header = prependChildElement(doc, envelope, header, true);
}
if (doCreate) {
wsseSecurity = header.getOwnerDocument().createElementNS(
WSConstants.WSSE_NS, "wsse:Security");
wsseSecurity.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:wsse",
WSConstants.WSSE_NS);
return prependChildElement(doc, header, wsseSecurity, true);
}
return null;
}
/**
* create a base64 test node <p/>
*
* @param doc
* the DOM document (SOAP request)
* @param data
* to encode
* @return a Text node containing the base64 encoded data
*/
public static Text createBase64EncodedTextNode(Document doc, byte data[]) {
return doc.createTextNode(Base64.encode(data));
}
public static SecretKey prepareSecretKey(String symEncAlgo, byte[] rawKey) {
SecretKeySpec keySpec = new SecretKeySpec(rawKey, JCEMapper
.getJCEKeyAlgorithmFromURI(symEncAlgo));
return (SecretKey) keySpec;
}
public static SOAPConstants getSOAPConstants(Element startElement) {
Document doc = startElement.getOwnerDocument();
String ns = doc.getDocumentElement().getNamespaceURI();
if (WSConstants.URI_SOAP12_ENV.equals(ns)) {
return new SOAP12Constants();
} else {
return new SOAP11Constants();
}
}
public static Cipher getCipherInstance(String cipherAlgo)
throws WSSecurityException {
Cipher cipher = null;
try {
if (cipherAlgo.equalsIgnoreCase(WSConstants.KEYTRANSPORT_RSA15)) {
cipher = Cipher.getInstance("RSA/NONE/PKCS1PADDING");
} else if (cipherAlgo
.equalsIgnoreCase(WSConstants.KEYTRANSPORT_RSAOEP)) {
cipher = Cipher.getInstance("RSA/NONE/OAEPPADDING");
} else {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM,
"unsupportedKeyTransp", new Object[] { cipherAlgo });
}
} catch (NoSuchPaddingException ex) {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM, "unsupportedKeyTransp",
new Object[] { "No such padding: " + cipherAlgo }, ex
);
} catch (NoSuchAlgorithmException ex) {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM, "unsupportedKeyTransp",
new Object[] { "No such algorithm: " + cipherAlgo }, ex
);
}
return cipher;
}
/**
* Fetch the result of a given action from a given result vector <p/>
*
* @param wsResultVector
* The result vector to fetch an action from
* @param action
* The action to fetch
* @return The result fetched from the result vector, null if the result
* could not be found
*/
public static WSSecurityEngineResult fetchActionResult(
Vector wsResultVector, int action) {
WSSecurityEngineResult wsResult = null;
// Find the part of the security result that matches the given action
for (int i = 0; i < wsResultVector.size(); i++) {
// Check the result of every action whether it matches the given
// action
WSSecurityEngineResult result =
(WSSecurityEngineResult) wsResultVector.get(i);
int resultAction =
((java.lang.Integer)result.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
if (resultAction == action) {
wsResult = (WSSecurityEngineResult) wsResultVector.get(i);
}
}
return wsResult;
}
/**
* Fetch the result of a given action from a given result vector <p/>
*
* @param wsResultVector
* The result vector to fetch an action from
* @param action
* The action to fetch
* @param results
* where to store the found results data for the action
* @return The result fetched from the result vector, null if the result
* could not be found
*/
public static Vector fetchAllActionResults(Vector wsResultVector,
int action, Vector results) {
// Find the parts of the security result that matches the given action
for (int i = 0; i < wsResultVector.size(); i++) {
// Check the result of every action whether it matches the given
// action
WSSecurityEngineResult result =
(WSSecurityEngineResult) wsResultVector.get(i);
int resultAction =
((java.lang.Integer)result.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
if (resultAction == action) {
results.add(wsResultVector.get(i));
}
}
return results;
}
static public int decodeAction(String action, Vector actions)
throws WSSecurityException {
int doAction = 0;
if (action == null) {
return doAction;
}
String single[] = StringUtil.split(action, ' ');
for (int i = 0; i < single.length; i++) {
if (single[i].equals(WSHandlerConstants.NO_SECURITY)) {
doAction = WSConstants.NO_SECURITY;
return doAction;
} else if (single[i].equals(WSHandlerConstants.USERNAME_TOKEN)) {
doAction |= WSConstants.UT;
actions.add(new Integer(WSConstants.UT));
} else if (single[i].equals(WSHandlerConstants.SIGNATURE)) {
doAction |= WSConstants.SIGN;
actions.add(new Integer(WSConstants.SIGN));
} else if (single[i].equals(WSHandlerConstants.ENCRYPT)) {
doAction |= WSConstants.ENCR;
actions.add(new Integer(WSConstants.ENCR));
} else if (single[i].equals(WSHandlerConstants.SAML_TOKEN_UNSIGNED)) {
doAction |= WSConstants.ST_UNSIGNED;
actions.add(new Integer(WSConstants.ST_UNSIGNED));
} else if (single[i].equals(WSHandlerConstants.SAML_TOKEN_SIGNED)) {
doAction |= WSConstants.ST_SIGNED;
actions.add(new Integer(WSConstants.ST_SIGNED));
} else if (single[i].equals(WSHandlerConstants.TIMESTAMP)) {
doAction |= WSConstants.TS;
actions.add(new Integer(WSConstants.TS));
} else if (single[i].equals(WSHandlerConstants.NO_SERIALIZATION)) {
doAction |= WSConstants.NO_SERIALIZE;
actions.add(new Integer(WSConstants.NO_SERIALIZE));
} else if (single[i].equals(WSHandlerConstants.SIGN_WITH_UT_KEY)) {
doAction |= WSConstants.UT_SIGN;
actions.add(new Integer(WSConstants.UT_SIGN));
} else {
throw new WSSecurityException(
"WSDoAllSender: Unknown action defined" + single[i]);
}
}
return doAction;
}
/**
* Returns the length of the key in # of bytes
*
* @param algorithm
* @return the key length
*/
public static int getKeyLength(String algorithm) throws WSSecurityException {
if (algorithm.equals(WSConstants.TRIPLE_DES)) {
return 24;
} else if (algorithm.equals(WSConstants.AES_128)) {
return 16;
} else if (algorithm.equals(WSConstants.AES_192)) {
return 24;
} else if (algorithm.equals(WSConstants.AES_256)) {
return 32;
} else if (XMLSignature.ALGO_ID_MAC_HMAC_SHA1.equals(algorithm)) {
return 20;
} else if (XMLSignature.ALGO_ID_MAC_HMAC_SHA256.equals(algorithm)) {
return 32;
} else if (XMLSignature.ALGO_ID_MAC_HMAC_SHA384.equals(algorithm)) {
return 48;
} else if (XMLSignature.ALGO_ID_MAC_HMAC_SHA512.equals(algorithm)) {
return 64;
} else if (XMLSignature.ALGO_ID_MAC_HMAC_NOT_RECOMMENDED_MD5
.equals(algorithm)) {
return 16;
} else {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, null);
}
}
/**
* Generate a nonce of the given length
*
* @return a nonce of the given length
* @throws Exception
*/
public static byte[] generateNonce(int length) throws WSSecurityException {
try {
final SecureRandom r = resolveSecureRandom();
if (r == null) {
throw new WSSecurityException("Random generator is not initialzed.");
}
byte[] temp = new byte[length];
r.nextBytes(temp);
return temp;
} catch (Exception e) {
throw new WSSecurityException(
"Error in generating nonce of length " + length, e);
}
}
/**
* Search through a WSS4J results vector for a single signature covering all
* these elements.
*
* NOTE: it is important that the given elements are those that are
* referenced using wsu:Id. When the signed element is referenced using a
* transformation such as XPath filtering the validation is carried out
* in signature verification itself.
*
* @param results
* results (e.g., as stored as WSHandlerConstants.RECV_RESULTS on
* an Axis MessageContext)
* @param elements
* the elements to check
* @return the identity of the signer
* @throws WSSecurityException
* if no suitable signature could be found or if any element
* didn't have a wsu:Id attribute
*/
public static X509Certificate ensureSignedTogether(Iterator results,
Element[] elements) throws WSSecurityException {
log.debug("ensureSignedTogether()");
if (results == null)
throw new IllegalArgumentException("No results vector");
if (elements == null || elements.length == 0)
throw new IllegalArgumentException("No elements to check!");
// Turn the list of required elements into a list of required wsu:Id
// strings
String[] requiredIDs = new String[elements.length];
for (int i = 0; i < elements.length; i++) {
Element e = (Element) elements[i];
if (e == null) {
throw new IllegalArgumentException("elements[" + i
+ "] is null!");
}
requiredIDs[i] = e.getAttributeNS(WSConstants.WSU_NS, "Id");
if (requiredIDs[i] == null) {
throw new WSSecurityException(WSSecurityException.FAILED_CHECK,
"requiredElementNoID", new Object[] { e.getNodeName() });
}
log.debug("Required element " + e.getNodeName() + " has wsu:Id "
+ requiredIDs[i]);
}
WSSecurityException fault = null;
// Search through the results for a SIGN result
while (results.hasNext()) {
WSHandlerResult result = (WSHandlerResult) results.next();
Iterator actions = result.getResults().iterator();
while (actions.hasNext()) {
WSSecurityEngineResult resultItem =
(WSSecurityEngineResult) actions.next();
int resultAction =
((java.lang.Integer)resultItem.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
if (resultAction == WSConstants.SIGN) {
try {
checkSignsAllElements(resultItem, requiredIDs);
return
(X509Certificate)resultItem.get(
WSSecurityEngineResult.TAG_X509_CERTIFICATE
);
} catch (WSSecurityException ex) {
// Store the exception but keep going... there may be a
// better signature later
log.debug("SIGN result does not sign all required elements", ex);
fault = ex;
}
}
}
}
if (fault != null)
throw fault;
throw new WSSecurityException(WSSecurityException.FAILED_CHECK, "noSignResult");
}
/**
* Ensure that this signature covers all required elements (identified by
* their wsu:Id attributes).
*
* @param resultItem
* the signature to check
* @param requiredIDs
* the list of wsu:Id values that must be covered
* @throws WSSecurityException
* if any required element is not included
*/
private static void checkSignsAllElements(
WSSecurityEngineResult resultItem, String[] requiredIDs)
throws WSSecurityException {
int resultAction =
((java.lang.Integer)resultItem.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
if (resultAction != WSConstants.SIGN) {
throw new IllegalArgumentException("Not a SIGN result");
}
Set sigElems = (Set)resultItem.get(WSSecurityEngineResult.TAG_SIGNED_ELEMENT_IDS);
if (sigElems == null) {
throw new RuntimeException(
"Missing signedElements set in WSSecurityEngineResult!");
}
log.debug("Found SIGN result...");
for (Iterator i = sigElems.iterator(); i.hasNext();) {
Object sigElement = i.next();
if(sigElement instanceof String) {
log.debug("Signature includes element with ID " + sigElement);
} else {
log.debug("Signature includes element with null uri " +
sigElement.toString());
}
}
log.debug("Checking required elements are in the signature...");
for (int i = 0; i < requiredIDs.length; i++) {
if (!sigElems.contains(requiredIDs[i])) {
throw new WSSecurityException(WSSecurityException.FAILED_CHECK,
"requiredElementNotSigned",
new Object[] { requiredIDs[i] });
}
log.debug("Element with ID " + requiredIDs[i]
+ " was correctly signed");
}
log.debug("All required elements are signed");
}
/**
* @return a SecureRandom instance initialized with the "SHA1PRNG"
* algorithm identifier
*/
public static SecureRandom
resolveSecureRandom() throws NoSuchAlgorithmException {
return resolveSecureRandom("SHA1PRNG");
}
/**
* @param algorithm
*
*
* @return a SecureRandom instance initialize with the identifier
* specified in algorithm
*/
public synchronized static SecureRandom
resolveSecureRandom(
final String algorithm
) throws NoSuchAlgorithmException {
if (random == null) {
random = SecureRandom.getInstance(algorithm);
random.setSeed(System.currentTimeMillis());
}
return random;
}
}