blob: 8de9f65a174c3fb7d20c7f0b43fbdd4e6de5401c [file] [log] [blame]
/*
* Copyright 2003-2004 The Apache Software Foundation.
*
* 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.transform;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSDocInfo;
import org.apache.ws.security.WSDocInfoStore;
import org.apache.ws.security.WSSConfig;
import org.apache.ws.security.message.token.SecurityTokenReference;
import org.apache.ws.security.message.token.X509Security;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.ws.security.util.Base64;
import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.transforms.TransformSpi;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.cert.X509Certificate;
/**
* Class STRTransform
*
* @author Werner Dittmann (Werner.Dittmann@siemens.com)
* @version 1.0
*/
public class STRTransform extends TransformSpi {
/**
* Field implementedTransformURI
*/
public static final String implementedTransformURI =
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#STR-Transform";
private static Log log = LogFactory.getLog(STRTransform.class.getName());
private static boolean doDebug = false;
private WSDocInfo wsDocInfo = null;
public boolean wantsOctetStream() {
return false;
}
public boolean wantsNodeSet() {
return true;
}
public boolean returnsOctetStream() {
return true;
}
public boolean returnsNodeSet() {
return false;
}
/**
* Method engineGetURI
*/
protected String engineGetURI() {
return STRTransform.implementedTransformURI;
}
/**
* Method enginePerformTransform
*
* @param input
* @throws CanonicalizationException
* @throws InvalidCanonicalizerException
*/
protected XMLSignatureInput enginePerformTransform(XMLSignatureInput input)
throws IOException,
CanonicalizationException,
InvalidCanonicalizerException {
doDebug = log.isDebugEnabled();
if (doDebug) {
log.debug("Beginning STRTransform..." + input.toString());
}
try {
/*
* Get the main document, that is the complete SOAP
* request document
*/
Document thisDoc = this._transformObject.getDocument();
int docHash = thisDoc.hashCode();
if (doDebug) {
log.debug("doc: " + thisDoc.toString() + ", " + docHash);
}
/*
* Here we get some information about the document that is being
* processed, in partucular the crypto implementation, and already
* detected BST that may be used later during dereferencing.
*/
wsDocInfo = WSDocInfoStore.lookup(docHash);
if (wsDocInfo == null) {
throw (new CanonicalizationException("no WSDocInfo found"));
}
/*
* According to the OASIS WS Specification
* "Web Services Security: SOAP Message Security 1.0"
* Monday, 19 January 2004, chapter 8.3 describes that
* the input node set must be processed bythe c14n that
* is specified in the argument element of the STRTransform
* element.
*
* First step: Get the required c14n argument. After that, get
* the c14n, feed the node set into c14n and get back the byte[].
* The byte[] contains the XML doc part to be
* signed or verified. Then reparse the byte[] to get the DOM.
*/
String canonAlgo = null;
if (this._transformObject.length(WSConstants.WSSE_NS,
"TransformationParameters") == 1) {
Element tmpE = XMLUtils.selectNode(
this._transformObject.getElement().getFirstChild(),
WSConstants.WSSE_NS, "TransformationParameters", 0);
Element canonElem = (Element) WSSecurityUtil.getDirectChild(
tmpE, "CanonicalizationMethod", WSConstants.SIG_NS);
canonAlgo = canonElem.getAttribute("Algorithm");
if (doDebug) {
log.debug("CanonAlgo: " + canonAlgo);
}
}
Canonicalizer canon = Canonicalizer.getInstance(canonAlgo);
byte buf[] = canon.canonicalizeXPathNodeSet(input.getNodeSet());
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.length);
bos.write(buf, 0, buf.length);
if (doDebug) {
log.debug("canon bos: " + bos.toString());
}
DocumentBuilderFactory dfactory = DocumentBuilderFactory
.newInstance();
dfactory.setValidating(false);
dfactory.setNamespaceAware(true);
DocumentBuilder db = dfactory.newDocumentBuilder();
Document doc = db
.parse(new ByteArrayInputStream(bos.toByteArray()));
/*
* Second step: find the STR element inside the resulting XML doc,
* check if STR contains some reference to an security token.
*/
NodeList nodeList =
doc.getElementsByTagNameNS(WSConstants.WSSE_NS,
"SecurityTokenReference");
Element str = null;
Element tmpEl = (Element) nodeList.item(0);
if (doDebug) {
log.debug("STR: " + tmpEl.toString());
}
/*
* Third and forth step are performed by derefenceSTR()
*/
SecurityTokenReference secRef = new SecurityTokenReference(
WSSConfig.getDefaultWSConfig(), tmpEl);
str = dereferenceSTR(thisDoc, secRef);
/*
* Keep in mind: the returned element belong to "thisDoc", thus
* import it to "doc" before replacing it.
*
* Fifth step: replace the STR with the above created/copied BST, feed
* this result in the specified c14n method and return this to
* the caller.
*
*/
str = (Element) doc.importNode(str, true);
Node parent = tmpEl.getParentNode(); // point to document node
// parent.replaceChild(str, tmpEl); // replace STR with new node
//
/*
* Alert: Hacks ahead
*
* TODO: Rework theses hacks after c14n was updated.
*/
/*
* HACK 1:
* Create a top level fake element with a defined default namespace
* setting (urn:X). Replace the STR node with this fake element that
* is now the top level element. Append our result element to the
* fake element, then call c14n. This forces the c14n to insert
* xmlns="" if necessary. However, before we handover the result we
* have to remove the fake element. See string buffer operation
* below.
*/
Element tmpEl1 = doc.createElement("temp");
tmpEl1.setAttributeNS(WSConstants.XMLNS_NS, "xmlns", "urn:X");
parent.replaceChild(tmpEl1, tmpEl); // replace STR with new node
tmpEl1.appendChild(str);
// End of HACK 1
// XMLUtils.circumventBug2650(doc); // No longer needed???
/*
* C14n with specified algorithm. According to WSS Specification.
*/
buf = canon.canonicalizeSubtree(doc, "#default");
// If the problem with c14n method is solved then just do:
/* return new XMLSignatureInput(buf); */
/*
* HACK 2
*/
bos = new ByteArrayOutputStream(buf.length);
bos.write(buf, 0, buf.length);
if (doDebug) {
log.debug("after c14n: " + bos.toString());
}
/*
* Here we delete the previously inserted fake element from the
* serialized XML.
*/
StringBuffer bf = new StringBuffer(bos.toString());
String bf1 = bf.substring("<temp xmlns=\"urn:X\">".length(), bf.length() - "</temp>".length());
if (doDebug) {
log.debug("last result: ");
log.debug(bf1.toString());
}
return new XMLSignatureInput(bf1.getBytes());
// End of HACK 2
} catch (IOException ex) {
throw new CanonicalizationException("empty", ex);
} catch (ParserConfigurationException ex) {
throw new CanonicalizationException("empty", ex);
} catch (XMLSecurityException ex) {
throw new CanonicalizationException("empty", ex);
} catch (SAXException ex) {
throw new CanonicalizationException("empty", ex);
} catch (TransformerException ex) {
throw new CanonicalizationException("empty", ex);
} catch (Exception ex) {
throw new CanonicalizationException("empty", ex);
}
}
private Element dereferenceSTR(Document doc, SecurityTokenReference secRef)
throws Exception {
/*
* Third step: locate the security token referenced by the STR
* element. Either the Token is contained in the document as a
* BinarySecurityToken or stored in some key storage.
*
* Forth step: after security token was located, prepare it. If its
* reference via a direct reference, i.e. a relative URI that references
* the BST directly in the message then just return that element.
* Otherwise wrap the located token in a newly created BST element
* as described in WSS Specification.
*
* Note: every element (also newly created elements) belong to the
* document defined by the doc parameter. This is the main SOAP document
* (thisDoc) and _not_ the document part that is to be signed/verified. Thus
* the caller must import the returned element into the document
* part that is signed/verified.
*
*/
Element tokElement = null;
/*
* First case: direct reference, according to chap 7.2 of OASIS
* WS specification (main document). Only in this case return
* a true reference to the BST. Copying is done by the caller.
*/
if (secRef.containsReference()) {
if (doDebug) {
log.debug("STR: Reference");
}
tokElement = secRef.getTokenElement(doc, wsDocInfo);
if (tokElement == null) {
throw new CanonicalizationException("empty");
}
}
/*
* second case: IssuerSerial, first try to get embedded
* certificate, if that fails, lookup in keystore, wrap
* in BST according to specification
*/
else if (secRef.containsX509IssuerSerial()) {
if (doDebug) {
log.debug("STR: IssuerSerial");
}
X509Certificate cert = null;
X509Security x509token = null;
// Disable check for embedded, always get from store (comment from Merlin,
// Betrust)
// x509token = secRef.getEmbeddedTokenFromIS(doc, wsDocInfo.getCrypto());
if (x509token != null) {
cert = x509token.getX509Certificate(wsDocInfo.getCrypto());
} else {
X509Certificate[] certs = secRef.getX509IssuerSerial(wsDocInfo.getCrypto());
if (certs == null || certs.length == 0 || certs[0] == null) {
throw new CanonicalizationException("empty");
}
cert = certs[0];
}
tokElement = createBST(doc, cert, secRef.getElement());
}
/*
* third case: KeyIdentifier, must be SKI, first try to get embedded
* certificate, if that fails, lookup in keystore, wrap
* in BST according to specification. No other KeyIdentifier
* type handled here - just SKI
*/
else if (secRef.containsKeyIdentifier()) {
if (doDebug) {
log.debug("STR: KeyIdentifier");
}
X509Certificate cert = null;
X509Security x509token = null;
// Disable check for embedded, always get from store (comment from Merlin,
// Betrust)
// x509token = secRef.getEmbeddedTokenFromSKI(doc, wsDocInfo.getCrypto());
if (x509token != null) {
cert = x509token.getX509Certificate(wsDocInfo.getCrypto());
} else {
X509Certificate[] certs = secRef.getKeyIdentifier(wsDocInfo.getCrypto());
if (certs == null || certs.length == 0 || certs[0] == null) {
throw new CanonicalizationException("empty");
}
cert = certs[0];
}
tokElement = createBST(doc, cert, secRef.getElement());
}
return (Element) tokElement;
}
private Element createBST(Document doc,
X509Certificate cert,
Element secRefE)
throws Exception {
byte data[] = cert.getEncoded();
String prefix = WSSecurityUtil.getPrefixNS(WSConstants.WSSE_NS, secRefE);
Element elem =
doc.createElementNS(WSConstants.WSSE_NS,
prefix + ":BinarySecurityToken");
WSSecurityUtil.setNamespace(elem, WSConstants.WSSE_NS, prefix);
elem.setAttributeNS(WSConstants.XMLNS_NS, "xmlns", "");
elem.setAttributeNS(null, "ValueType", X509Security.getType(WSSConfig.getDefaultWSConfig()));
Text certText = doc.createTextNode(Base64.encode(data));
elem.appendChild(certText);
return elem;
}
}