| /* |
| * 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.WSSecurityException; |
| 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.xml.security.c14n.CanonicalizationException; |
| import org.apache.xml.security.c14n.Canonicalizer; |
| import org.apache.xml.security.c14n.InvalidCanonicalizerException; |
| import org.apache.xml.security.signature.XMLSignatureInput; |
| import org.apache.xml.security.transforms.TransformSpi; |
| import org.apache.ws.security.util.Base64; |
| import org.apache.xml.security.utils.XMLUtils; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Text; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.security.cert.CertificateEncodingException; |
| 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 static String XMLNS = "xmlns="; |
| |
| 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 particular 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 by the c14n |
| * that is specified in the argument element of the STRTransform |
| * element. |
| * |
| * First step: Get the required c14n argument and get the specified |
| * Canonicalizer |
| */ |
| |
| 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); |
| |
| ByteArrayOutputStream bos = null; |
| byte[] buf = null; |
| if (doDebug) { |
| buf = input.getBytes(); |
| bos = new ByteArrayOutputStream(buf.length); |
| bos.write(buf, 0, buf.length); |
| log.debug("canon bos: " + bos.toString()); |
| } |
| |
| /* |
| * Get the input (node) to transform. Currently we support only an |
| * Element as input format. If other formats are required we must |
| * get it as bytes and probably reparse it into a DOM tree (How to |
| * work with nodesets? how to select the right node from a nodeset?) |
| */ |
| Element str = null; |
| if (input.isElement()) { |
| str = (Element) input.getSubNode(); |
| } else { |
| throw (new CanonicalizationException( |
| "Wrong input format - only element input supported")); |
| } |
| |
| if (doDebug) { |
| log.debug("STR: " + str.toString()); |
| } |
| /* |
| * The element to transform MUST be a SecurityTokenReference |
| * element. |
| */ |
| SecurityTokenReference secRef = new SecurityTokenReference(str); |
| /* |
| * Third and forth step are performed by derefenceSTR() |
| */ |
| Element dereferencedToken = dereferenceSTR(thisDoc, secRef); |
| /* |
| * C14n with specified algorithm. According to WSS Specification. |
| */ |
| buf = canon.canonicalizeSubtree(dereferencedToken, "#default"); |
| if (doDebug) { |
| bos = new ByteArrayOutputStream(buf.length); |
| bos.write(buf, 0, buf.length); |
| log.debug("after c14n: " + bos.toString()); |
| } |
| |
| /* |
| * Alert: Hacks ahead According to WSS spec an Apex node must |
| * contain a default namespace. If none is availabe in the first |
| * node of the c14n output (this is the apex element) then we do |
| * some editing to insert an empty default namespace |
| * |
| * TODO: Rework theses hacks after c14n was updated and can be |
| * instructed to insert empty default namespace if required |
| */ |
| // If the problem with c14n method is solved then just do: |
| // return new XMLSignatureInput(buf); |
| |
| // start of HACK |
| StringBuffer bf = new StringBuffer(new String(buf)); |
| String bf1 = bf.toString(); |
| |
| /* |
| * Find start and end of first element <....>, this is the Apex node |
| */ |
| int gt = bf1.indexOf(">"); |
| /* |
| * Lookup the default namespace |
| */ |
| int idx = bf1.indexOf(XMLNS); |
| /* |
| * If none found or if it is outside of this (Apex) element look for |
| * first blank in, insert default namespace there (this is the |
| * correct place according to c14n specification) |
| */ |
| if (idx < 0 || idx > gt) { |
| idx = bf1.indexOf(" "); |
| bf.insert(idx + 1, "xmlns=\"\" "); |
| bf1 = bf.toString(); |
| } |
| if (doDebug) { |
| log.debug("last result: "); |
| log.debug(bf1); |
| } |
| return new XMLSignatureInput(bf1.getBytes()); |
| } |
| // End of HACK |
| catch (WSSecurityException ex) { |
| throw (new CanonicalizationException("WS Security Exception", ex)); |
| |
| } |
| } |
| |
| private Element dereferenceSTR(Document doc, SecurityTokenReference secRef) |
| throws WSSecurityException { |
| |
| /* |
| * 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. |
| * |
| */ |
| 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, null); |
| } |
| /* |
| * second case: IssuerSerial, lookup in keystore, wrap in BST according |
| * to specification |
| */ |
| else if (secRef.containsX509Data() || secRef.containsX509IssuerSerial()) { |
| if (doDebug) { |
| log.debug("STR: IssuerSerial"); |
| } |
| X509Certificate cert = null; |
| X509Certificate[] certs = secRef.getX509IssuerSerial(wsDocInfo |
| .getCrypto()); |
| if (certs == null || certs.length == 0 || certs[0] == null) { |
| throw new WSSecurityException(WSSecurityException.FAILED_CHECK); |
| } |
| cert = certs[0]; |
| tokElement = createBSTX509(doc, cert, secRef.getElement()); |
| } |
| /* |
| * third case: KeyIdentifier, must be SKI, 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; |
| X509Certificate[] certs = secRef.getKeyIdentifier(wsDocInfo |
| .getCrypto()); |
| if (certs == null || certs.length == 0 || certs[0] == null) { |
| throw new WSSecurityException(WSSecurityException.FAILED_CHECK); |
| } |
| cert = certs[0]; |
| tokElement = createBSTX509(doc, cert, secRef.getElement()); |
| } |
| return (Element) tokElement; |
| } |
| |
| private Element createBSTX509(Document doc, X509Certificate cert, |
| Element secRefE) throws WSSecurityException { |
| |
| byte data[]; |
| try { |
| data = cert.getEncoded(); |
| } catch (CertificateEncodingException e) { |
| throw new WSSecurityException( |
| WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError", null, e |
| ); |
| } |
| 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", ""); |
| if (cert.getVersion() == 1) { |
| elem.setAttributeNS(null, "ValueType", X509Security.X509_V1_TYPE); |
| } else { |
| elem.setAttributeNS(null, "ValueType", X509Security.X509_V3_TYPE); |
| } |
| Text certText = doc.createTextNode(Base64.encode(data)); // no line |
| // wrap |
| elem.appendChild(certText); |
| return elem; |
| } |
| } |