/*
 * Copyright 2004,2005 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.rampart.builder;

import org.apache.axiom.om.OMElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.rahas.TrustException;
import org.apache.rampart.RampartConstants;
import org.apache.rampart.RampartException;
import org.apache.rampart.RampartMessageData;
import org.apache.rampart.policy.RampartPolicyData;
import org.apache.rampart.policy.SupportingPolicyData;
import org.apache.rampart.policy.model.RampartConfig;
import org.apache.rampart.util.RampartUtil;
import org.apache.ws.secpolicy.model.AlgorithmSuite;
import org.apache.ws.secpolicy.model.SupportingToken;
import org.apache.ws.secpolicy.model.Token;
import org.apache.ws.secpolicy.model.X509Token;

import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.WSEncryptionPart;
import org.apache.wss4j.common.util.KeyUtils;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import org.apache.wss4j.dom.handler.WSHandlerResult;
import org.apache.wss4j.dom.message.WSSecDKEncrypt;
import org.apache.wss4j.dom.message.WSSecDKSign;
import org.apache.wss4j.dom.message.WSSecEncrypt;
import org.apache.wss4j.dom.message.WSSecEncryptedKey;
import org.apache.wss4j.dom.message.WSSecHeader;
import org.apache.wss4j.dom.message.WSSecSignature;
import org.apache.wss4j.dom.WSConstants;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.xml.crypto.dsig.Reference;
import java.util.*;

public class AsymmetricBindingBuilder extends BindingBuilder {

    private static Log log = LogFactory.getLog(AsymmetricBindingBuilder.class);
    private static Log tlog = LogFactory.getLog(RampartConstants.TIME_LOG);

    private WSSecEncryptedKey encrKey;
    
    private String encryptedKeyId;
    
    private byte[] encryptedKeyValue;

    private List<byte[]> signatureValues = new ArrayList<byte[]>();

    private Element encrTokenElement;
    
    private Element sigDKTElement;
    
    private Element encrDKTElement;

    private List<WSEncryptionPart> sigParts = new ArrayList<WSEncryptionPart>();
    
    private Element signatureElement;

    private Element refList;

    
    public void build(RampartMessageData rmd) throws RampartException {
        log.debug("AsymmetricBindingBuilder build invoked");

        RampartPolicyData rpd = rmd.getPolicyData();
        if (rpd.isIncludeTimestamp()) {
            this.addTimestamp(rmd);
        }

        if (RampartUtil.encryptFirst(rpd)) {
            this.doEncryptBeforeSig(rmd);
        } else {
            this.doSignBeforeEncrypt(rmd);
        }

        log.debug("AsymmetricBindingBuilder build invoked : DONE");
    }

    private void doEncryptBeforeSig(RampartMessageData rmd)
            throws RampartException {
    	
    	long t0 = 0, t1 = 0, t2 = 0;
    	if(tlog.isDebugEnabled()){
    		t0 = System.currentTimeMillis();
    	}
        RampartPolicyData rpd = rmd.getPolicyData();
        Document doc = rmd.getDocument();
        RampartConfig config = rpd.getRampartConfig();

        /*
         * We need to hold on to these elements to use them as reference in the
         * case of encrypting the signature
         */
        Element encrDKTokenElem = null;
        WSSecEncrypt encr = null;
        refList = null;
        WSSecDKEncrypt dkEncr = null;
        SecretKey symmetricKey = null;

        /*
         * We MUST use keys derived from the same token
         */
        Token encryptionToken = null;
        if(rmd.isInitiator()) {
            encryptionToken = rpd.getRecipientToken();
        } else {
            encryptionToken = rpd.getInitiatorToken();
        }
        List<WSEncryptionPart> encrParts = RampartUtil.getEncryptedParts(rmd);
        
        //Signed parts are determined before encryption because encrypted signed  headers
        //will not be included otherwise
        this.sigParts = RampartUtil.getSignedParts(rmd);
        
        if(encryptionToken == null && encrParts.size() > 0) {
            throw new RampartException("encryptionTokenMissing");
        }
        
        if (encryptionToken != null && encrParts.size() > 0) {
            
            //Check for RampartConfig assertion
            if(rpd.getRampartConfig() == null) {
                //We'er missing the extra info rampart needs
                throw new RampartException("rampartConigMissing");
            }
            
            if (encryptionToken.isDerivedKeys()) {
                try {
                    this.setupEncryptedKey(rmd, encryptionToken);
                    // Create the DK encryption builder
                    dkEncr = new WSSecDKEncrypt(doc);
                    dkEncr.setTokenIdentifier(this.encryptedKeyId);
                    dkEncr.setDerivedKeyLength(rpd.getAlgorithmSuite().getEncryptionDerivedKeyLength()/8);
                    dkEncr.prepare(this.encryptedKeyValue);

                    // Get and add the DKT element
                    this.encrDKTElement = dkEncr.getdktElement();
                    encrDKTokenElem = RampartUtil.appendChildToSecHeader(rmd, this.encrDKTElement);

                    refList = dkEncr.encryptForExternalRef(null, encrParts);

                } catch (WSSecurityException e) {
                    throw new RampartException("errorCreatingEncryptedKey", e);
                } catch (Exception e) {
                    throw new RampartException("errorInDKEncr", e);
                }
            } else {
                try {
                    WSSecHeader secHeader = new WSSecHeader(doc);
                    secHeader.insertSecurityHeader();
            
                    encr = new WSSecEncrypt(secHeader);

                    KeyGenerator keyGen = KeyUtils.getKeyGenerator(rpd.getAlgorithmSuite().getEncryption());
                    symmetricKey = keyGen.generateKey();
            
                    //Element refs = encr.encryptForRef(null, encrParts, symmetricKey);
                    //encr.addInternalRefElement(refs);


                    RampartUtil.setEncryptionUser(rmd, encr);
                    encr.setSymmetricEncAlgorithm(rpd.getAlgorithmSuite().getEncryption());
                    RampartUtil.setKeyIdentifierType(rmd, encr, encryptionToken);
                    encr.setKeyEncAlgo(rpd.getAlgorithmSuite().getAsymmetricKeyWrap());
                    encr.prepare(RampartUtil.getEncryptionCrypto(config, rmd.getCustomClassLoader()), symmetricKey);

                    Element bstElem = encr.getBinarySecurityTokenElement();
                    if (bstElem != null) {
                        RampartUtil.appendChildToSecHeader(rmd, bstElem);
                    }

                    this.encrTokenElement = encr.getEncryptedKeyElement();
                    this.encrTokenElement = RampartUtil.appendChildToSecHeader(rmd,
                            encrTokenElement);

                    refList = encr.encryptForRef(null, encrParts, symmetricKey);

                } catch (WSSecurityException e) {
                    throw new RampartException("errorInEncryption", e);
                }
            }

            refList = RampartUtil.appendChildToSecHeader(rmd, refList);
            
            if(tlog.isDebugEnabled()){
            	t1 = System.currentTimeMillis();
            }

            this.setInsertionLocation(encrTokenElement);

            RampartUtil.handleEncryptedSignedHeaders(encrParts, this.sigParts, doc);

            // TODO may contain deifferent types of objects as values, therefore cannot use strongly type maps
            // need to figure out a way
            HashMap<Token,Object> sigSuppTokMap = null;
            HashMap<Token,Object> endSuppTokMap = null;
            HashMap<Token,Object> sgndEndSuppTokMap = null;
            HashMap<Token,Object> sgndEncSuppTokMap = null;
            HashMap<Token,Object> endEncSuppTokMap = null;
            HashMap<Token,Object> sgndEndEncSuppTokMap = null;
            
            if(this.timestampElement != null){
            	sigParts.add(RampartUtil.createEncryptionPart(WSConstants.TIMESTAMP_TOKEN_LN,
                            RampartUtil.addWsuIdToElement((OMElement) this.timestampElement)));
            }
            
            if (rmd.isInitiator()) {

                // Now add the supporting tokens
                SupportingToken sgndSuppTokens = rpd.getSignedSupportingTokens();
                sigSuppTokMap = this.handleSupportingTokens(rmd, sgndSuppTokens);           
                
                SupportingToken endSuppTokens = rpd.getEndorsingSupportingTokens();
                endSuppTokMap = this.handleSupportingTokens(rmd, endSuppTokens);
                
                SupportingToken sgndEndSuppTokens = rpd.getSignedEndorsingSupportingTokens();           
                sgndEndSuppTokMap = this.handleSupportingTokens(rmd, sgndEndSuppTokens);
                
                SupportingToken sgndEncryptedSuppTokens = rpd.getSignedEncryptedSupportingTokens();
                sgndEncSuppTokMap = this.handleSupportingTokens(rmd, sgndEncryptedSuppTokens);
                
                SupportingToken endorsingEncryptedSuppTokens = rpd.getEndorsingEncryptedSupportingTokens();
                endEncSuppTokMap = this.handleSupportingTokens(rmd, endorsingEncryptedSuppTokens);
                
                SupportingToken sgndEndEncSuppTokens = rpd.getSignedEndorsingEncryptedSupportingTokens();           
                sgndEndEncSuppTokMap = this.handleSupportingTokens(rmd, sgndEndEncSuppTokens);
                
                List<SupportingToken> supportingToks = rpd.getSupportingTokensList();
                for (SupportingToken supportingTok : supportingToks) {
                    this.handleSupportingTokens(rmd, supportingTok);
                } 
                
                SupportingToken encryptedSupportingToks = rpd.getEncryptedSupportingTokens();
                this.handleSupportingTokens(rmd, encryptedSupportingToks);
        
                //Setup signature parts
                sigParts = addSignatureParts(sigSuppTokMap, sigParts);
                sigParts = addSignatureParts(sgndEncSuppTokMap, sigParts);
                sigParts = addSignatureParts(sgndEndSuppTokMap, sigParts);
                sigParts = addSignatureParts(sgndEndEncSuppTokMap, sigParts);
                
            } else {
                addSignatureConfirmation(rmd, sigParts);
            }
            
            if(( sigParts.size() > 0 &&
                    rmd.isInitiator() && rpd.getInitiatorToken() != null) || 
                    (!rmd.isInitiator() && rpd.getRecipientToken() != null)) {
                this.doSignature(rmd);
            }

            if (rmd.isInitiator()) {

                if (endSuppTokMap != null) {
                    endSuppTokMap.putAll(endEncSuppTokMap);
                }
                // Do endorsed signatures
                List<byte[]> endSigVals = this.doEndorsedSignatures(rmd,
                        endSuppTokMap);
                for (byte[] endSigVal : endSigVals) {
                    signatureValues.add(endSigVal);
                }

                sgndEndSuppTokMap.putAll(sgndEndEncSuppTokMap);
                // Do signed endorsing signatures
                List<byte[]> sigEndSigVals = this.doEndorsedSignatures(rmd,
                        sgndEndSuppTokMap);
                for (byte[] sigEndSigVal : sigEndSigVals) {
                    signatureValues.add(sigEndSigVal);
                }
            }
            
            if(tlog.isDebugEnabled()){
            	t2 = System.currentTimeMillis();
            	tlog.debug("Encryption took :" + (t1 - t0)
            				+", Signature tool :" + (t2 - t1) );
            }

            // Check for signature protection
            if (rpd.isSignatureProtection() && this.mainSigId != null) {
            	long t3 = 0, t4 = 0;
            	if(tlog.isDebugEnabled()){
            		t3 = System.currentTimeMillis();
            	}

                List<WSEncryptionPart> secondEncrParts = new ArrayList<WSEncryptionPart>();

                // Now encrypt the signature using the above token
                secondEncrParts.add(new WSEncryptionPart(this.mainSigId,
                        "Element"));
                
                if(rmd.isInitiator()) {
                    for (String anEncryptedTokensIdList : encryptedTokensIdList) {
                        secondEncrParts.add(new WSEncryptionPart(anEncryptedTokensIdList, "Element"));
                    }
                }

                Element secondRefList = null;

                if (encryptionToken.isDerivedKeys()) {
                    try {

                        secondRefList = dkEncr.encryptForExternalRef(null,
                                secondEncrParts);
                        RampartUtil.insertSiblingAfter(rmd, encrDKTokenElem,
                                secondRefList);

                    } catch (WSSecurityException e) {
                        throw new RampartException("errorCreatingEncryptedKey",
                                e);
                    }
                } else {
                    try {
                        // Encrypt, get hold of the ref list and add it
                        secondRefList = encr.encryptForRef(null,
                                secondEncrParts, symmetricKey);

                        // Insert the ref list after the encrypted key elem
                        this.setInsertionLocation(RampartUtil
                                .insertSiblingAfter(rmd, encrTokenElement,
                                        secondRefList));
                    } catch (WSSecurityException e) {
                        throw new RampartException("errorInEncryption", e);
                    }
                }
                if(tlog.isDebugEnabled()){
            		t4 = System.currentTimeMillis();
            		tlog.debug("Signature protection took :" + (t4 - t3));
            	}
            }
        }
        
        

    }

    private void doSignBeforeEncrypt(RampartMessageData rmd)
            throws RampartException {
    	
    	long t0 = 0, t1 = 0, t2 = 0;
    	        
        RampartPolicyData rpd = rmd.getPolicyData();
        Document doc = rmd.getDocument();

        HashMap sigSuppTokMap = null;
        HashMap endSuppTokMap = null;
        HashMap sgndEndSuppTokMap = null;
        HashMap sgndEncSuppTokMap = null;
        HashMap endEncSuppTokMap = null;
        HashMap sgndEndEncSuppTokMap = null;
        
        sigParts = RampartUtil.getSignedParts(rmd);
        
        //Add timestamp
        if(this.timestampElement != null){
        	sigParts.add(new WSEncryptionPart(RampartUtil
                .addWsuIdToElement((OMElement) this.timestampElement)));
        }else{
        	this.setInsertionLocation(null);
        }
        
        if(tlog.isDebugEnabled()){
    		t0 = System.currentTimeMillis();
    	}
        
        if (rmd.isInitiator()) {
           
            //      Now add the supporting tokens
            SupportingToken sgndSuppTokens = rpd.getSignedSupportingTokens();
            sigSuppTokMap = this.handleSupportingTokens(rmd, sgndSuppTokens);           
            
            SupportingToken endSuppTokens = rpd.getEndorsingSupportingTokens();
            endSuppTokMap = this.handleSupportingTokens(rmd, endSuppTokens);
            
            SupportingToken sgndEndSuppTokens = rpd.getSignedEndorsingSupportingTokens();           
            sgndEndSuppTokMap = this.handleSupportingTokens(rmd, sgndEndSuppTokens);
            
            SupportingToken sgndEncryptedSuppTokens = rpd.getSignedEncryptedSupportingTokens();
            sgndEncSuppTokMap = this.handleSupportingTokens(rmd, sgndEncryptedSuppTokens);
            
            SupportingToken endorsingEncryptedSuppTokens = rpd.getEndorsingEncryptedSupportingTokens();
            endEncSuppTokMap = this.handleSupportingTokens(rmd, endorsingEncryptedSuppTokens);
            
            SupportingToken sgndEndEncSuppTokens = rpd.getSignedEndorsingEncryptedSupportingTokens();           
            sgndEndEncSuppTokMap = this.handleSupportingTokens(rmd, sgndEndEncSuppTokens);
            
            List<SupportingToken> supportingToks = rpd.getSupportingTokensList();
            for (SupportingToken supportingTok : supportingToks) {
                this.handleSupportingTokens(rmd, supportingTok);
            } 
            
            SupportingToken encryptedSupportingToks = rpd.getEncryptedSupportingTokens();
            this.handleSupportingTokens(rmd, encryptedSupportingToks);
    
            //Setup signature parts
            sigParts = addSignatureParts(sigSuppTokMap, sigParts);
            sigParts = addSignatureParts(sgndEncSuppTokMap, sigParts);
            sigParts = addSignatureParts(sgndEndSuppTokMap, sigParts);
            sigParts = addSignatureParts(sgndEndEncSuppTokMap, sigParts);
            
        } else {
            addSignatureConfirmation(rmd, sigParts);
        }

        if( sigParts.size() > 0 && 
                ((rmd.isInitiator() && rpd.getInitiatorToken() != null) || 
                (!rmd.isInitiator() && rpd.getRecipientToken() != null))) {
            // Do signature
            this.doSignature(rmd);
        }
        
        List<SupportingPolicyData> supportingToks = rpd.getSupportingPolicyData();
        for (SupportingPolicyData policyData : supportingToks) {
            if (policyData != null) { // TODO do we need this null check ?
                List<WSEncryptionPart> supportingSigParts = RampartUtil.getSupportingSignedParts(rmd,
                        policyData);

                if (supportingSigParts.size() > 0
                        && ((rmd.isInitiator() && rpd.getInitiatorToken() != null) || (!rmd
                        .isInitiator() && rpd.getRecipientToken() != null))) {
                    // Do signature for policies defined under SupportingToken.
                    this.doSupportingSignature(rmd, supportingSigParts, policyData);
                }
            }
        }
        
        //Do endorsed signature

        if (rmd.isInitiator()) {
            
            // Adding the endorsing encrypted supporting tokens to endorsing supporting tokens
            if (endSuppTokMap != null) {
                endSuppTokMap.putAll(endEncSuppTokMap);
            }
            // Do endorsed signatures
            List<byte[]> endSigVals = this.doEndorsedSignatures(rmd,
                    endSuppTokMap);
            for (byte[] endSigVal : endSigVals) {
                signatureValues.add(endSigVal);
            }

            //Adding the signed endorsed encrypted tokens to signed endorsed supporting tokens
            if (sgndEndSuppTokMap != null) {
                sgndEndSuppTokMap.putAll(sgndEndEncSuppTokMap);
            }
            // Do signed endorsing signatures
            List<byte[]> sigEndSigVals = this.doEndorsedSignatures(rmd,
                    sgndEndSuppTokMap);
            for (byte[] sigEndSigVal : sigEndSigVals) {
                signatureValues.add(sigEndSigVal);
            }
        }
        
        if(tlog.isDebugEnabled()){
    		t1 = System.currentTimeMillis();
    	}
             
        List<WSEncryptionPart> encrParts = RampartUtil.getEncryptedParts(rmd);
        
        //Check for signature protection
        if(rpd.isSignatureProtection() && this.mainSigId != null) {
            encrParts.add(new WSEncryptionPart(RampartUtil.addWsuIdToElement((OMElement)this.signatureElement), "Element"));
        }
        
        if(rmd.isInitiator()) {
            for (String anEncryptedTokensIdList : encryptedTokensIdList) {
                encrParts.add(new WSEncryptionPart(anEncryptedTokensIdList, "Element"));
            }
        }

        //Do encryption
        Token encrToken;
        if (rmd.isInitiator()) {
            encrToken = rpd.getRecipientToken();
        } else {
            encrToken = rpd.getInitiatorToken();
        }

        if(encrToken != null && encrParts.size() > 0) {
            Element refList = null;
            AlgorithmSuite algorithmSuite = rpd.getAlgorithmSuite();
            if(encrToken.isDerivedKeys()) {
                
                try {
                    WSSecDKEncrypt dkEncr = new WSSecDKEncrypt(rmd.getSecHeader());
                    
                    if(this.encrKey == null) {
                        this.setupEncryptedKey(rmd, encrToken);
                    }
                    
                    dkEncr.setTokenIdentifier(this.encryptedKeyId);
                    dkEncr.setCustomValueType(WSConstants.SOAPMESSAGE_NS11 + "#"
                            + WSConstants.ENC_KEY_VALUE_TYPE);
                    dkEncr.setSymmetricEncAlgorithm(algorithmSuite.getEncryption());
                    dkEncr.setDerivedKeyLength(algorithmSuite.getEncryptionDerivedKeyLength()/8);
                    dkEncr.prepare(this.encryptedKeyValue);
                    
                    
                    if(this.encrTokenElement != null) {
                        this.encrDKTElement = RampartUtil.insertSiblingAfter(
                                rmd, this.encrTokenElement, dkEncr.getdktElement());
                    } else {
                        this.encrDKTElement = RampartUtil.insertSiblingBefore(
                                rmd, this.sigDKTElement, dkEncr.getdktElement());
                    }
                    
                    refList = dkEncr.encryptForExternalRef(null, encrParts);
                    
                    RampartUtil.insertSiblingAfter(rmd, 
                                                    this.encrDKTElement, 
                                                    refList);
                                                    
                } catch (WSSecurityException e) {
                    throw new RampartException("errorInDKEncr", e);
                } catch (Exception e) {
                    throw new RampartException("errorInDKEncr", e);
                }
            } else {
                try {
                    
                    WSSecEncrypt encr = new WSSecEncrypt(doc);
                    
                    RampartUtil.setKeyIdentifierType(rmd, encr, encrToken);
                    
                    RampartUtil.setEncryptionUser(rmd, encr);
                    encr.setSymmetricEncAlgorithm(algorithmSuite.getEncryption());
                    encr.setKeyEncAlgo(algorithmSuite.getAsymmetricKeyWrap());
                    KeyGenerator keyGen = KeyUtils.getKeyGenerator(rpd.getAlgorithmSuite().getEncryption());
                    SecretKey symmetricKey = keyGen.generateKey();
                    encr.prepare(RampartUtil.getEncryptionCrypto(rpd.getRampartConfig(), rmd.getCustomClassLoader()), symmetricKey);
                    
                    if(this.timestampElement != null){
                    	this.setInsertionLocation(this.timestampElement);
                    }else{
                    	this.setInsertionLocation(null);
                    }
                    
                    if(encr.getBSTTokenId() != null) {
                        this.setInsertionLocation(RampartUtil
                                .insertSiblingAfterOrPrepend(rmd,
                                        this.getInsertionLocation(),
                                        encr.getBinarySecurityTokenElement()));
                    }
                    
                    
                    Element encryptedKeyElement = encr.getEncryptedKeyElement();
                                       
                    //Encrypt, get hold of the ref list and add it
                    refList = encr.encryptForRef(null, encrParts, symmetricKey);
                    
                    //Add internal refs
                    encryptedKeyElement.appendChild(refList);
                    
                    this.setInsertionLocation(RampartUtil
                            .insertSiblingAfterOrPrepend(rmd,
                                    this.getInsertionLocation(),
                                    encryptedKeyElement)); 

//                    RampartUtil.insertSiblingAfter(rmd,
//                                                    this.getInsertionLocation(),
//                                                    refList);
                } catch (WSSecurityException e) {
                    throw new RampartException("errorInEncryption", e);
                }    
            }
        }
        
        List<SupportingPolicyData> supportingTokens = rpd.getSupportingPolicyData();
        for (SupportingPolicyData policyData : supportingTokens) {
            if (policyData != null) { // TODO do we need this null check ?
                Token supportingEncrToken = policyData.getEncryptionToken();
                List<WSEncryptionPart> supoortingEncrParts = RampartUtil.getSupportingEncryptedParts(rmd,
                        policyData);

                if (supportingEncrToken != null && supoortingEncrParts.size() > 0) {
                    doEncryptionWithSupportingToken(rpd, rmd, supportingEncrToken, doc,
                            supoortingEncrParts);
                }
            }
        }
        
        if(tlog.isDebugEnabled()){
    		t2 = System.currentTimeMillis();
    		tlog.debug("Signature took :" + (t1 - t0)
    				+", Encryption took :" + (t2 - t1) );
    	}
        
    }
    
    private void doSupportingSignature(RampartMessageData rmd, List<WSEncryptionPart> supportingSigParts,
            SupportingPolicyData supportingData) throws RampartException {

        Token supportingSigToken;
        WSSecSignature supportingSig;
        Element supportingSignatureElement;

        long t0 = 0, t1 = 0;
        if (tlog.isDebugEnabled()) {
            t0 = System.currentTimeMillis();
        }

        supportingSigToken = supportingData.getSignatureToken();

        if (!(supportingSigToken instanceof X509Token)) {
            return;
        }
        supportingSig = this.getSignatureBuilder(rmd, supportingSigToken,
                ((X509Token) supportingSigToken).getUserCertAlias());
        Element bstElem = supportingSig.getBinarySecurityTokenElement();
        if (bstElem != null) {
            bstElem = RampartUtil.insertSiblingAfter(rmd, this.getInsertionLocation(), bstElem);
            this.setInsertionLocation(bstElem);
        }

        if (rmd.getPolicyData().isTokenProtection() && supportingSig.getBSTTokenId() != null) {
            supportingSigParts.add(new WSEncryptionPart(supportingSig.getBSTTokenId()));
        }

        try {
        	supportingSig.setDigestAlgo(rmd.getPolicyData().getAlgorithmSuite().getDigest());

            List<Reference> referenceList
                    = supportingSig.addReferencesToSign(supportingSigParts);

            /**
             * Before migration it was - this.setInsertionLocation(RampartUtil.insertSiblingAfter(rmd, this
             *       .getInsertionLocation(), supportingSignatureElement));
             *
             * In this case we need to append <Signature>..</Signature> element to
             * current insertion location
             */
            supportingSig.computeSignature(referenceList, false, this.getInsertionLocation());

        } catch (WSSecurityException e) {
            throw new RampartException("errorInSignatureWithX509Token", e);
        }

        signatureValues.add(supportingSig.getSignatureValue());

        if (tlog.isDebugEnabled()) {
            t1 = System.currentTimeMillis();
            tlog.debug("Signature took :" + (t1 - t0));
        }

    }

    private void doSignature(RampartMessageData rmd) throws RampartException {

        RampartPolicyData rpd = rmd.getPolicyData();
        Document doc = rmd.getDocument();
        
        long t0 = 0, t1 = 0;
        if(tlog.isDebugEnabled()){
    		t0 = System.currentTimeMillis();
    	}
        Token sigToken;
        if(rmd.isInitiator()) {
            sigToken = rpd.getInitiatorToken();
        } else {
            sigToken = rpd.getRecipientToken();
        }

        /**
         * Note : It doesn't make sense to use Derived Keys in an Asymmetric binding environment to sign messages.
         * In asymmetric binding environment we always sign the message using sender's private key. We do *not*
         * use a session/ephemeral key to sign the message. We always use PKC keys to sign and verify messages.
         * Therefore we do not need to have following code segment.
         * TODO Confirm and remove.
         */
        if (sigToken.isDerivedKeys()) {
            // Set up the encrypted key to use
            if(this.encrKey == null) {
                setupEncryptedKey(rmd, sigToken);
            }
            
            WSSecDKSign dkSign = new WSSecDKSign(rmd.getSecHeader());

            dkSign.setTokenIdentifier(this.encryptedKeyId);

            // Set the algo info
            dkSign.setSignatureAlgorithm(rpd.getAlgorithmSuite()
                    .getSymmetricSignature());
            dkSign.setDerivedKeyLength(rpd.getAlgorithmSuite()
                    .getSignatureDerivedKeyLength() / 8);
            dkSign.setCustomValueType(WSConstants.SOAPMESSAGE_NS11 + "#"
                    + WSConstants.ENC_KEY_VALUE_TYPE);
            try {
                dkSign.prepare(this.encryptedKeyValue);

                if (rpd.isTokenProtection()) {
                    sigParts.add(new WSEncryptionPart(encrKey.getId()));
                }

                List<Reference> referenceList = dkSign.addReferencesToSign(sigParts);

                 /**
                 * Add <wsc:DerivedKeyToken>..</wsc:DerivedKeyToken> to security
                 * header. We need to add this just after Encrypted Key and just before <Signature>..</Signature>
                 * elements. (As a convention)
                 */

                if (refList == null) {
                    //dkSign.appendDKElementToHeader(rmd.getSecHeader());
                    this.sigDKTElement = RampartUtil.insertSiblingAfter(rmd,
                            this.getInsertionLocation(), dkSign.getdktElement());
                    this.setInsertionLocation(this.sigDKTElement);
                     // Do signature
                    /**
                     * Create and prepend signature
                     */
                    dkSign.computeSignature(referenceList, false, this.getInsertionLocation());
                } else {
                    this.sigDKTElement = RampartUtil.insertSiblingBefore(rmd, refList, dkSign.getdktElement());
                    this.setInsertionLocation(this.sigDKTElement);

                    // Do signature
                    /**
                     * Create and append signature
                     */
                    dkSign.computeSignature(referenceList, true, this.getInsertionLocation());
                }

                if (RampartUtil.encryptFirst(rpd)) {
                    // always add encrypt elements after signature. Because we need to first verify the signature
                    // and decrypt at receiver end.
                    this.setInsertionLocation(dkSign.getSignatureElement());
                } else {
                    // append timestamp element as next insertion location. Cos in sign and encrypt case the
                    // receiver first need to decrypt the message => The decryption keys should appear first.
                    this.setInsertionLocation(this.timestampElement);
                }

                this.mainSigId = RampartUtil
                        .addWsuIdToElement((OMElement) dkSign
                                .getSignatureElement());

                signatureValues.add(dkSign.getSignatureValue());
                
                signatureElement = dkSign.getSignatureElement();
            } catch (WSSecurityException e) {
                throw new RampartException("errorInDerivedKeyTokenSignature", e);
            } catch (Exception e) {
                throw new RampartException("errorInDerivedKeyTokenSignature", e);
            }

        } else {
            WSSecSignature sig = this.getSignatureBuilder(rmd, sigToken);
            Element bstElem = sig.getBinarySecurityTokenElement();
            if(bstElem != null) {
                bstElem = RampartUtil.insertSiblingAfter(rmd, this
                                        .getInsertionLocation(), bstElem);
                this.setInsertionLocation(bstElem);
            }
            
            if (rmd.getPolicyData().isTokenProtection()
                    && sig.getBSTTokenId() != null) {
                sigParts.add(new WSEncryptionPart(sig.getBSTTokenId()));
            }

            try {
            	sig.setDigestAlgo(rpd.getAlgorithmSuite().getDigest());

                List<Reference> referenceList
                        = sig.addReferencesToSign(sigParts);

                // Do signature
                if (this.refList == null) {
                    /**
                     * If <ReferenceData>..</ReferenceData> is null append <Signature>..</Signature> element
                     * to current insertion location.
                     */
                    sig.computeSignature(referenceList, false, this.getInsertionLocation());
                } else {
                    /**
                     * If <ReferenceData>..</ReferenceData> is not null prepend <Signature>..</Signature> element
                     * to reference data.
                     */
                    sig.computeSignature(referenceList, true, this.refList);
                }

                signatureElement = sig.getSignatureElement();
                
                this.setInsertionLocation(signatureElement);

                this.mainSigId = RampartUtil.addWsuIdToElement((OMElement) signatureElement);
            } catch (WSSecurityException e) {
                throw new RampartException("errorInSignatureWithX509Token", e);
            }
            signatureValues.add(sig.getSignatureValue());
        }
        
        if(tlog.isDebugEnabled()){
    		t1 = System.currentTimeMillis();
    		tlog.debug("Signature took :" + (t1 - t0));
    	}

    }
    
    private void doEncryptionWithSupportingToken(RampartPolicyData rpd, RampartMessageData rmd,
            Token encrToken, Document doc, List<WSEncryptionPart> encrParts) throws RampartException {
        Element refList = null;
        try {
            if (!(encrToken instanceof X509Token)) {
                return;
            }

            WSSecEncrypt encr = new WSSecEncrypt(doc);

            RampartUtil.setKeyIdentifierType(rmd, encr, encrToken);

            RampartUtil.setEncryptionUser(rmd, encr, ((X509Token) encrToken).getEncryptionUser());
            encr.setSymmetricEncAlgorithm(rpd.getAlgorithmSuite().getEncryption());
            encr.setKeyEncAlgo(rpd.getAlgorithmSuite().getAsymmetricKeyWrap());
            KeyGenerator keyGen = KeyUtils.getKeyGenerator(rpd.getAlgorithmSuite().getEncryption());
            SecretKey symmetricKey = keyGen.generateKey();
            encr.prepare(RampartUtil.getEncryptionCrypto(rpd.getRampartConfig(), rmd.getCustomClassLoader()), symmetricKey);

            if (this.timestampElement != null) {
                this.setInsertionLocation(this.timestampElement);
            } else {
                this.setInsertionLocation(null);
            }

            if (encr.getBSTTokenId() != null) {
                this.setInsertionLocation(RampartUtil.insertSiblingAfterOrPrepend(rmd, this
                        .getInsertionLocation(), encr.getBinarySecurityTokenElement()));
            }

            Element encryptedKeyElement = encr.getEncryptedKeyElement();

            // Encrypt, get hold of the ref list and add it
            refList = encr.encryptForRef(null, encrParts, symmetricKey);

            // Add internal refs
            encryptedKeyElement.appendChild(refList);

            this.setInsertionLocation(RampartUtil.insertSiblingAfterOrPrepend(rmd, this
                    .getInsertionLocation(), encryptedKeyElement));

        } catch (WSSecurityException e) {
            throw new RampartException("errorInEncryption", e);
        }
    }


    /**
     * @param rmd
     * @throws RampartException
     */
    private void setupEncryptedKey(RampartMessageData rmd, Token token) 
    throws RampartException {
        if(!rmd.isInitiator() && token.isDerivedKeys()) {
                
                //If we already have them, simply return
                if(this.encryptedKeyId != null && this.encryptedKeyValue != null) {
                    return;
                }
                
                //Use the secret from the incoming EncryptedKey element
                List<WSHandlerResult> resultsObj
                        = (List<WSHandlerResult>)rmd.getMsgContext().getProperty(WSHandlerConstants.RECV_RESULTS);
                if(resultsObj != null) {
                    encryptedKeyId = RampartUtil.getRequestEncryptedKeyId(resultsObj);
                    encryptedKeyValue = RampartUtil.getRequestEncryptedKeyValue(resultsObj);
                    
                    //In the case where we don't have the EncryptedKey in the 
                    //request, for the control to have reached this state,
                    //the scenario MUST be a case where this is the response
                    //message by a listener created for an async client
                    //Therefor we will create a new EncryptedKey
                    if(encryptedKeyId == null && encryptedKeyValue == null) {
                        createEncryptedKey(rmd, token);
                    }
                } else {
                    throw new RampartException("noSecurityResults");
                }
        } else {
            createEncryptedKey(rmd, token);
        }
       
    }

    /**
     * Create an encrypted key element
     * @param rmd
     * @param token
     * @throws RampartException
     */
    private void createEncryptedKey(RampartMessageData rmd, Token token) throws RampartException {
        //Set up the encrypted key to use
        KeyGenerator keyGen;
        try {
            keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_128);
        } catch (WSSecurityException e) {
            e.printStackTrace();
            return;
        }
        SecretKey symmetricKey = keyGen.generateKey();
        encrKey = this.getEncryptedKeyBuilder(rmd, token, symmetricKey);

        Element bstElem = encrKey.getBinarySecurityTokenElement();
        if (bstElem != null) {
            // If a BST is available then use it
            RampartUtil.appendChildToSecHeader(rmd, bstElem);
        }
        
        // Add the EncryptedKey
        encrTokenElement = encrKey.getEncryptedKeyElement();
        this.encrTokenElement = RampartUtil.appendChildToSecHeader(rmd,
                encrTokenElement);
        encryptedKeyValue = symmetricKey.getEncoded();
        encryptedKeyId = encrKey.getId();

        //Store the token for client - response verification 
        // and server - response creation
        try {
            org.apache.rahas.Token tok = new org.apache.rahas.Token(
                    encryptedKeyId, (OMElement)encrTokenElement , null, null);
            tok.setSecret(encryptedKeyValue);
            rmd.getTokenStorage().add(tok);
        } catch (TrustException e) {
            throw new RampartException("errorInAddingTokenIntoStore", e);
        }
    }
}
