/*
 * 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;

import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.axiom.om.OMNamespace;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.rampart.policy.RampartPolicyData;
import org.apache.rampart.policy.SupportingPolicyData;
import org.apache.rampart.util.RampartUtil;
import org.apache.ws.secpolicy.model.*;
import org.apache.wss4j.policy.SPConstants;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.CryptoType;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.WSEncryptionPart;
import org.apache.wss4j.common.WSSPolicyException;
import org.apache.wss4j.dom.engine.WSSecurityEngine;
import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
import org.apache.wss4j.dom.handler.WSHandlerResult;
import org.apache.wss4j.dom.message.token.Timestamp;
import org.apache.wss4j.dom.util.WSSecurityUtil;
import org.apache.wss4j.common.util.XMLUtils;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.WSDataRef;
import org.apache.wss4j.dom.SOAP11Constants;
import org.apache.wss4j.dom.SOAP12Constants;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.jaxen.XPath;
import org.jaxen.JaxenException;

import javax.xml.namespace.QName;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.util.*;
import java.time.Instant;

public class PolicyBasedResultsValidator implements ExtendedPolicyValidatorCallbackHandler {
   
    private static Log log = LogFactory.getLog(PolicyBasedResultsValidator.class);

    public void validate(ValidatorData data, Vector results)
    throws RampartException {
        List<WSSecurityEngineResult> resultsList = new ArrayList<WSSecurityEngineResult>(results);
        this.validate(data, resultsList);
    }
   
    /**
     * {@inheritDoc}
     */
    public void validate(ValidatorData data, List<WSSecurityEngineResult> results)
    throws RampartException {
       
        RampartMessageData rmd = data.getRampartMessageData();
       
        RampartPolicyData rpd = rmd.getPolicyData();
       
        //If there's Security policy present and no results
        //then we should throw an error
        if(rpd != null && results == null) {
            throw new RampartException("noSecurityResults");
        }
       
        //Check presence of timestamp
        WSSecurityEngineResult tsResult = null;
        if(rpd != null &&  rpd.isIncludeTimestamp()) {
            tsResult = fetchActionResult(results, WSConstants.TS);
            if(tsResult == null && !rpd.isIncludeTimestampOptional()) {
                throw new RampartException("timestampMissing");
            }
           
        }
       
        //sig/encr
        List<WSEncryptionPart> encryptedParts = RampartUtil.getEncryptedParts(rmd);
        if(rpd != null && rpd.isSignatureProtection() && isSignatureRequired(rmd)) {
           
            String sigId = RampartUtil.getSigElementId(rmd);

            encryptedParts.add(RampartUtil.createEncryptionPart(WSConstants.SIG_LN, sigId, WSConstants.SIG_NS,
                    RampartConstants.XML_ENCRYPTION_MODIFIER_ELEMENT));
        }
       
        List<WSEncryptionPart> signatureParts = RampartUtil.getSignedParts(rmd);

        //Timestamp is not included in sig parts
        if (rpd != null) {
            if (tsResult != null || !rpd.isIncludeTimestampOptional()) {
                if (rpd.isIncludeTimestamp()
                        && !rpd.isTransportBinding()) {
                    signatureParts.add(RampartUtil.createEncryptionPart(WSConstants.TIMESTAMP_TOKEN_LN, "timestamp"));
                }
            }
        }

        if(!rmd.isInitiator()) {
                       
            //Just an indicator for EndorsingSupportingToken signature
            SupportingToken endSupportingToken = null;
            if (rpd != null) {
                endSupportingToken = rpd.getEndorsingSupportingTokens();
            }

            if(endSupportingToken !=  null && !endSupportingToken.isOptional()) {
                SignedEncryptedParts endSignedParts = endSupportingToken.getSignedParts();
                if((endSignedParts != null && !endSignedParts.isOptional() &&
                        (endSignedParts.isBody() ||
                                endSignedParts.getHeaders().size() > 0)) ||
                                rpd.isIncludeTimestamp()) {

                    signatureParts.add(RampartUtil.createEncryptionPart("EndorsingSupportingTokens",
                            "EndorsingSupportingTokens"));
                }
            }
            //Just an indicator for SignedEndorsingSupportingToken signature
            SupportingToken sgndEndSupportingToken = null;
            if (rpd != null) {
                sgndEndSupportingToken = rpd.getSignedEndorsingSupportingTokens();
            }
            if(sgndEndSupportingToken != null && !sgndEndSupportingToken.isOptional()) {
                SignedEncryptedParts sgndEndSignedParts = sgndEndSupportingToken.getSignedParts();
                if((sgndEndSignedParts != null && !sgndEndSignedParts.isOptional() &&
                        (sgndEndSignedParts.isBody() ||
                                sgndEndSignedParts.getHeaders().size() > 0)) ||
                                rpd.isIncludeTimestamp()) {

                    signatureParts.add(RampartUtil.createEncryptionPart("SignedEndorsingSupportingTokens",
                            "SignedEndorsingSupportingTokens"));
                }
            }

            if (rpd != null) {
                List<SupportingToken> supportingToks = rpd.getSupportingTokensList();
                for (SupportingToken supportingToken : supportingToks) {
                    if (supportingToken != null && !supportingToken.isOptional()) {
                        SupportingPolicyData policyData = new SupportingPolicyData();
                        policyData.build(supportingToken);
                        encryptedParts.addAll(RampartUtil.getSupportingEncryptedParts(rmd, policyData));
                        signatureParts.addAll(RampartUtil.getSupportingSignedParts(rmd, policyData));
                    }
                }
            }
        }
       
        validateEncrSig(data,encryptedParts, signatureParts, results);

        if(rpd != null && !rpd.isTransportBinding()) {
            validateProtectionOrder(data, results);
        }

        validateEncryptedParts(data, encryptedParts, results);

        validateSignedPartsHeaders(data, signatureParts, results);
       
        validateRequiredElements(data);

        //Supporting tokens
        if(!rmd.isInitiator()) {
            validateSupportingTokens(data, results);
        }
       
        /*
         * Now we can check the certificate used to sign the message. In the
         * following implementation the certificate is only trusted if either it
         * itself or the certificate of the issuer is installed in the keystore.
         *
         * Note: the method verifyTrust(X509Certificate) allows custom
         * implementations with other validation algorithms for subclasses.
         */

        // Extract the signature action result from the action vector

        WSSecurityEngineResult actionResult = fetchActionResult(
                results, WSConstants.SIGN);
        if (actionResult != null) {
            X509Certificate returnCert = (X509Certificate) actionResult
                    .get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);

            if (returnCert != null) {
                if (!verifyTrust(returnCert, rmd)) {
                    throw new RampartException ("trustVerificationError");
                }
            }
        }
       
        /*
         * Perform further checks on the timestamp that was transmitted in the
         * header.
         * In the following implementation the timestamp is valid if :
         * Timestamp->Created < 'now' < Timestamp->Expires.
         * (Last test handled by WSS4J also if timeStampStrict enabled)
         *
         * Note: the method verifyTimestamp(Timestamp) allows custom
         * implementations with other validation algorithms for subclasses.
         */

        // Extract the timestamp action result from the action vector
        actionResult = fetchActionResult(results, WSConstants.TS);

        if (actionResult != null) {
            Timestamp timestamp = (Timestamp) actionResult
                    .get(WSSecurityEngineResult.TAG_TIMESTAMP);

            if (timestamp != null) {
                if (!verifyTimestamp(timestamp, rmd)) {
                    throw new RampartException("cannotValidateTimestamp");
                }
            }
        }
    }
   
    /**
     * @param data Validator data
     * @param encryptedParts optionally can be size of zero
     * @param signatureParts optionally can be size of zero
     * @param results used for getting SigEncrActions
     * @throws RampartException If an errors during validation
     */
    protected void validateEncrSig(ValidatorData data,List<WSEncryptionPart> encryptedParts,
                                   List<WSEncryptionPart> signatureParts, List<WSSecurityEngineResult> results)
    throws RampartException {
        List<Integer> actions = getSigEncrActions(results);
        boolean sig = false;
        boolean encr = false;
        for (Object action : actions) {
            Integer act = (Integer) action;
            if (act == WSConstants.SIGN) {
                sig = true;
            } else if (act == WSConstants.ENCR) {
                encr = true;
            }
        }
       
        RampartPolicyData rpd = data.getRampartMessageData().getPolicyData();
       
        SupportingToken sgndSupTokens = rpd.getSignedSupportingTokens();
        SupportingToken sgndEndorSupTokens = rpd.getSignedEndorsingSupportingTokens();
       
        if(sig && signatureParts.size() == 0
                && (sgndSupTokens == null || sgndSupTokens.getTokens().size() == 0)
                 && (sgndEndorSupTokens == null || sgndEndorSupTokens.getTokens().size() == 0)) {
           
            //Unexpected signature
            throw new RampartException("unexprectedSignature");
        } else if(!sig && signatureParts.size() > 0) {
           
            //required signature missing
            throw new RampartException("signatureMissing");
        }
       
        if(encr && encryptedParts.size() == 0) {
           
            //Check whether its just an encrypted key
            List<WSSecurityEngineResult> list = this.getResults(results, WSConstants.ENCR);

            boolean encrDataFound = false;
            for (WSSecurityEngineResult result : list) {
            	Object resultElement = result.get(WSSecurityEngineResult.TAG_DATA_REF_URIS);
            	if (resultElement instanceof ArrayList) {
	                ArrayList dataRefURIs = (ArrayList) resultElement;
	                if (dataRefURIs != null && dataRefURIs.size() != 0) {
	                    encrDataFound = true;
	                }
            	}
            }
            //TODO check whether the encrptedDataFound is an UsernameToken
            if(encrDataFound && !isUsernameTokenPresent(data)) {
                //Unexpected encryption
                throw new RampartException("unexprectedEncryptedPart");
            }
        } else if(!encr && encryptedParts.size() > 0) {
           
            //required signature missing
            throw new RampartException("encryptionMissing");
        }
    }

    /**
     * @param data contains RampartMessageData
     * @param results Used to handle SupportingTokens
     * @throws RampartException If an errors during validation
     */
    protected void validateSupportingTokens(ValidatorData data, List<WSSecurityEngineResult> results)
    throws RampartException {
       
        //Check for UsernameToken
        RampartPolicyData rpd = data.getRampartMessageData().getPolicyData();
        RampartMessageData rmd = data.getRampartMessageData();
        List<SupportingToken> supportingTokens = rpd.getSupportingTokensList();
        for (SupportingToken suppTok : supportingTokens) {
            handleSupportingTokens(rmd, results, suppTok);
        }
        SupportingToken signedSuppToken = rpd.getSignedSupportingTokens();
        handleSupportingTokens(rmd, results, signedSuppToken);
        SupportingToken signedEndSuppToken = rpd.getSignedEndorsingSupportingTokens();
        handleSupportingTokens(rmd, results, signedEndSuppToken);
        SupportingToken endSuppToken = rpd.getEndorsingSupportingTokens();
        handleSupportingTokens(rmd, results, endSuppToken);
    }

    /**
     * @param rmd Rampart configuration Object
     * @param results unused
     * @param suppTok Defines an ArrayList of tokens
     * @throws RampartException If an errors during processing
     */
    protected void handleSupportingTokens(RampartMessageData rmd, List<WSSecurityEngineResult> results, SupportingToken suppTok) throws RampartException {
       
        if(suppTok == null) {
            return;
        }

        ArrayList tokens = suppTok.getTokens();
        for (Object objectToken : tokens) {
            Token token = (Token) objectToken;
            if (token instanceof UsernameToken) {
                UsernameToken ut = (UsernameToken) token;
                //Check presence of a UsernameToken
                WSSecurityEngineResult utResult = fetchActionResult(results, WSConstants.UT);
               
                if (utResult == null && !ut.isOptional()) {
                    throw new RampartException("usernameTokenMissing");
                }
               
                org.apache.wss4j.dom.message.token.UsernameToken wssUt = (org.apache.wss4j.dom.message.token.UsernameToken) utResult.get(WSSecurityEngineResult.TAG_USERNAME_TOKEN);
               
                if(ut.isNoPassword() && wssUt.getPassword() != null) {
                throw new RampartException("invalidUsernameTokenType");
                }
               
            if(ut.isHashPassword() && !wssUt.isHashed()) {
                throw new RampartException("invalidUsernameTokenType");
                } else if (!ut.isHashPassword() && (wssUt.getPassword() == null ||
                        !wssUt.getPasswordType().equals(WSConstants.PASSWORD_TEXT))) {
                throw new RampartException("invalidUsernameTokenType");
                }
               
               

            } else if (token instanceof IssuedToken) {
                WSSecurityEngineResult samlResult = fetchActionResult(results, WSConstants.ST_SIGNED);
                // Then check for unsigned saml tokens
                if (samlResult == null) {
                    log.debug("No signed SAMLToken found. Looking for unsigned SAMLTokens");
                    samlResult = fetchActionResult(results, WSConstants.ST_UNSIGNED);
                }
                if (samlResult == null) {
                    throw new RampartException("samlTokenMissing");
                }
            } else if (token instanceof X509Token) {
                X509Token x509Token = (X509Token) token;
                WSSecurityEngineResult x509Result = fetchActionResult(results, WSConstants.BST);
                if (x509Result == null && !x509Token.isOptional()) {
                    throw new RampartException("binaryTokenMissing");
                }
            }
        }
    }
   
   
   

    /**
     * @param data contains RampartMessageData
     * @param results Used to get SigEncrActions
     * @throws RampartException If an errors during validation
     */
    protected void validateProtectionOrder(ValidatorData data, List<WSSecurityEngineResult> results)
    throws RampartException {
       
        String protectionOrder = data.getRampartMessageData().getPolicyData().getProtectionOrder();
        List<Integer> sigEncrActions = this.getSigEncrActions(results);
       
        if(sigEncrActions.size() < 2) {
            //There are no results to COMPARE
            return;
        }
       
        boolean sigNotPresent = true;
        boolean encrNotPresent = true;

        for (Object sigEncrAction : sigEncrActions) {
            Integer act = (Integer) sigEncrAction;
            if (act == WSConstants.SIGN) {
                sigNotPresent = false;
            } else if (act == WSConstants.ENCR) {
                encrNotPresent = false;
            }
        }
       
        // Only one action is present, so there is no order to check
        if ( sigNotPresent || encrNotPresent ) {
            return;
        }
       
       
        boolean done = false;
        if(SPConstants.SIGN_BEFORE_ENCRYPTING.equals(protectionOrder)) {
                       
            boolean sigFound = false;
            for (Iterator iter = sigEncrActions.iterator();
                iter.hasNext() || !done;) {
                Integer act = (Integer) iter.next();
                if(act == WSConstants.ENCR && ! sigFound ) {
                    // We found ENCR and SIGN has not been found - break and fail
                    break;
                }
                if(act == WSConstants.SIGN) {
                    sigFound = true;
                } else if(sigFound) {
                    //We have an ENCR action after sig
                    done = true;
                }
            }
           
        } else {
            boolean encrFound = false;
            for (Object sigEncrAction : sigEncrActions) {
                Integer act = (Integer) sigEncrAction;
                if (act == WSConstants.SIGN && !encrFound) {
                    // We found SIGN and ENCR has not been found - break and fail
                    break;
                }
                if (act == WSConstants.ENCR) {
                    encrFound = true;
                } else if (encrFound) {
                    //We have an ENCR action after sig
                    done = true;
                }
            }
        }
       
        if(!done) {
            throw new RampartException("protectionOrderMismatch");
        }
    }


    protected List<Integer> getSigEncrActions(List<WSSecurityEngineResult> results) {
        List<Integer> sigEncrActions = new ArrayList<Integer>();
        for (WSSecurityEngineResult result : results) {
            Integer action = (Integer) (result)
                    .get(WSSecurityEngineResult.TAG_ACTION);

            if (WSConstants.SIGN == action || WSConstants.ENCR == action) {
                sigEncrActions.add(action);
            }

        }
        return sigEncrActions;
    }

    protected void validateEncryptedParts(ValidatorData data,
                                          List<WSEncryptionPart> encryptedParts, List<WSSecurityEngineResult> results)
                                                                                throws RampartException {
       
        RampartMessageData rmd = data.getRampartMessageData();
       
        ArrayList encrRefs = getEncryptedReferences(results);
       
        RampartPolicyData rpd = rmd.getPolicyData();

        // build the list of encrypted nodes based on the dataRefs xpath expressions
        SOAPEnvelope envelope = rmd.getMsgContext().getEnvelope();
        Set namespaces = RampartUtil.findAllPrefixNamespaces(envelope,
                                                             rpd.getDeclaredNamespaces());

        Map decryptedElements = new HashMap();
        for (Object encrRef : encrRefs) {
            WSDataRef dataRef = (WSDataRef) encrRef;

            if (dataRef == null || dataRef.getXpath() == null) {
                continue;
            }

            try {
                XPath xp = new AXIOMXPath(dataRef.getXpath());

                for (Object namespaceObject : namespaces) {
                    OMNamespace tmpNs = (OMNamespace) namespaceObject;
                    xp.addNamespace(tmpNs.getPrefix(), tmpNs.getNamespaceURI());
                }

                for (Object o : xp.selectNodes(envelope)) {
                    decryptedElements.put(o, dataRef.isContent());
                }


            } catch (JaxenException e) {
                // This has to be changed to propagate an instance of a RampartException up
                throw new RampartException("An error occurred while searching for decrypted elements.", e);
            }

        }

        //Check for encrypted body
        if(rpd.isEncryptBody()&& !rpd.isEncryptBodyOptional()) {
           
            if( !isRefIdPresent(encrRefs, data.getBodyEncrDataId())){
                throw new RampartException("encryptedPartMissing",
                        new String[]{data.getBodyEncrDataId()});
            }
        }

        for (WSEncryptionPart encryptedPart : encryptedParts) {

            //This is the encrypted Body and we already checked encrypted body
            if (encryptedPart.getName().equals(WSConstants.ELEM_BODY)) {
                continue;
            }

            if ((WSConstants.SIG_LN.equals(encryptedPart.getName()) &&
                    WSConstants.SIG_NS.equals(encryptedPart.getNamespace()))
                    || encryptedPart.getEncModifier().equals(WSConstants.ELEM_HEADER)) {
                if (!isRefIdPresent(encrRefs, new QName(encryptedPart.getNamespace(), encryptedPart.getName()))) {
                    throw new RampartException("encryptedPartMissing",
                            new String[]{encryptedPart.getNamespace() + ":" + encryptedPart.getName()});
                }
                continue;
            }

            // it is not a header or body part... verify encrypted xpath elements
            String xpath = encryptedPart.getXpath();
            boolean found = false;
            try {
                XPath xp = new AXIOMXPath(xpath);

                for (Object namespaceObject : namespaces) {
                    OMNamespace tmpNs = (OMNamespace) namespaceObject;
                    xp.addNamespace(tmpNs.getPrefix(), tmpNs.getNamespaceURI());
                }

                for (Object o : xp.selectNodes(envelope)) {
                    Object result = decryptedElements.get(o);
                    if (result != null &&
                            ("Element".equals(encryptedPart.getEncModifier())
                                    ^ (Boolean) result)) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    throw new RampartException("encryptedPartMissing",
                            new String[]{xpath});
                }


            } catch (JaxenException e) {
                // This has to be changed to propagate an instance of a RampartException up
                throw new RampartException("An error occurred while searching for decrypted elements.", e);
            }

        }
       
    }
   
    public void validateRequiredElements(ValidatorData data) throws RampartException {
       
        RampartMessageData rmd = data.getRampartMessageData();
       
        RampartPolicyData rpd = rmd.getPolicyData();
       
        SOAPEnvelope envelope = rmd.getMsgContext().getEnvelope();

        for (String expression : rpd.getRequiredElements()) {

            if (!RampartUtil.checkRequiredElements(envelope, rpd.getDeclaredNamespaces(), expression)) {
                throw new RampartException("requiredElementsMissing", new String[]{expression});
            }
        }
       
    }

    protected void validateSignedPartsHeaders(ValidatorData data, List<WSEncryptionPart> signatureParts,
                                              List<WSSecurityEngineResult> results)
    throws RampartException {
       
        RampartMessageData rmd = data.getRampartMessageData();
        Node envelope = rmd.getDocument().getFirstChild();
       
        WSSecurityEngineResult[] actionResults = fetchActionResults(results, WSConstants.SIGN);

        // Find elements that are signed
        List<QName> actuallySigned = new ArrayList<QName>();
        if (actionResults != null) {
            for (WSSecurityEngineResult actionResult : actionResults) {

                List wsDataRefs = (List) actionResult.get(WSSecurityEngineResult.TAG_DATA_REF_URIS);

                // if header was encrypted before it was signed, protected
                // element is 'EncryptedHeader.' the actual element is
                // first child element

                for (Object objectDataReference : wsDataRefs) {
                    WSDataRef wsDataRef = (WSDataRef) objectDataReference;
                    Element protectedElement = wsDataRef.getProtectedElement();
                    if (protectedElement.getLocalName().equals("EncryptedHeader")) {
                        NodeList nodeList = protectedElement.getChildNodes();
                        for (int x = 0; x < nodeList.getLength(); x++) {
                            if (nodeList.item(x).getNodeType() == Node.ELEMENT_NODE) {
                                String ns = (nodeList.item(x)).getNamespaceURI();
                                String ln = (nodeList.item(x)).getLocalName();
                                actuallySigned.add(new QName(ns, ln));
                                break;
                            }
                        }
                    } else {
                        String ns = protectedElement.getNamespaceURI();
                        String ln = protectedElement.getLocalName();
                        actuallySigned.add(new QName(ns, ln));
                    }
                }

            }
        }

        for (WSEncryptionPart wsep : signatureParts) {
            if (wsep.getName().equals(WSConstants.ELEM_BODY)) {

                QName bodyQName;

                if (WSConstants.URI_SOAP11_ENV.equals(envelope.getNamespaceURI())) {
                    bodyQName = new SOAP11Constants().getBodyQName();
                } else {
                    bodyQName = new SOAP12Constants().getBodyQName();
                }

                if (!actuallySigned.contains(bodyQName) && !rmd.getPolicyData().isSignBodyOptional()) {
                    // soap body is not signed
                    throw new RampartException("bodyNotSigned");
                }

            } else if (wsep.getName().equals(WSConstants.ELEM_HEADER) ||
                    wsep.getXpath() != null) {
                // TODO earlier this was wsep.getType() == WSConstants.PART_TYPE_ELEMENT
                // This means that encrypted element of an XPath expression type. Therefore we are checking
                // now whether an XPath expression exists. - Verify

                Element element = XMLUtils.findElement(
                        envelope, wsep.getName(), wsep.getNamespace());

                if (element == null) {
                    // The signedpart header or element we are checking is not present in
                    // soap envelope - this is allowed
                    continue;
                }

                // header or the element present in soap envelope - verify that it is part of signature
                if (actuallySigned.contains(new QName(element.getNamespaceURI(), element.getLocalName()))) {
                    continue;
                }

                String msg = wsep.getXpath() != null ?
                        "signedPartHeaderNotSigned" : "signedElementNotSigned";

                // header or the element defined in policy is present but not signed
                throw new RampartException(msg, new String[]{wsep.getNamespace() + ":" + wsep.getName()});

            }
        }
    }

   
    protected boolean isSignatureRequired(RampartMessageData rmd) {
        RampartPolicyData rpd = rmd.getPolicyData();
        return (rpd.isSymmetricBinding() && rpd.getSignatureToken() != null) ||
                (!rpd.isSymmetricBinding() && !rpd.isTransportBinding() &&
                        ((rpd.getInitiatorToken() != null && rmd.isInitiator())
                                || rpd.getRecipientToken() != null && !rmd.isInitiator()));
    }


    /*
    * Verify whether timestamp of the message is valid.
    * If timeStampStrict is enabled in rampartConfig; testing of timestamp has not expired
    * ('now' is before ts->Expires) is also handled earlier by WSS4J without timeskew.
    * TODO must write unit tests
    */
    protected boolean verifyTimestamp(Timestamp timestamp, RampartMessageData rmd) throws RampartException {

        long maxSkew = RampartUtil.getTimestampMaxSkew(rmd);

        //Verify that ts->Created is before 'now'
        Instant createdTime = timestamp.getCreated();
        if (createdTime != null) {
            long now = Instant.now().toEpochMilli();

            //calculate the tolerance limit for timeskew of the 'Created' in timestamp
            if (maxSkew > 0) {
                now += (maxSkew * 1000);
            }

            // fail if ts->Created is after 'now'
            if (createdTime.toEpochMilli() > now) {
                return false;
            }
        }

        //Verify that ts->Expires is after now.
        Instant expires = timestamp.getExpires();

        if (expires != null) {
            long now = Instant.now().toEpochMilli();
            //calculate the tolerance limit for timeskew of the 'Expires' in timestamp
            if (maxSkew > 0) {
                now -= (maxSkew * 1000);
            }
            //fail if ts->Expires is before 'now'
            if (expires.toEpochMilli() < now) {
                return false;
            }
        }

        return true;
    }
   
    /**
     * Evaluate whether a given certificate should be trusted.
     * Hook to allow subclasses to implement custom validation methods however they see fit.
     * <p>
     * Policy used in this implementation:
     * 1. Search the keystore for the transmitted certificate
     * 2. Search the keystore for a connection to the transmitted certificate
     * (that is, search for certificate(s) of the issuer of the transmitted certificate
     * 3. Verify the trust path for those certificates found because the search for the issuer might be fooled by a phony DN (String!)
     *
     * @param cert the certificate that should be validated against the keystore
     * @param rmd To get signature keystore information.
     * @return true if the certificate is trusted, false if not (AxisFault is thrown for exceptions during CertPathValidation)
     * @throws RampartException If an error occurred during validation.
     */
    protected boolean verifyTrust(X509Certificate cert, RampartMessageData rmd) throws RampartException {

        // If no certificate was transmitted, do not trust the signature
        if (cert == null) {
            return false;
        }

        Crypto crypto = RampartUtil.getSignatureCrypto(
                    rmd.getPolicyData().getRampartConfig(),
                    rmd.getCustomClassLoader());


        // TODO removing this with WSS4J 1.6 migration. We do not have a way to get alias
        // Therefore cannot set alias to message context. What will be affected from this ?
        // rmd.getMsgContext().setProperty(RampartMessageData.SIGNATURE_CERT_ALIAS, alias);

        // TODO this validation we are doing in SignatureProcessor.handleToken (WSS4J) So why we need to do again ?
        // investigate

        return isCertificateTrusted(cert, crypto);

    }


    /**
     * TODO - This is directly copied from WSS4J (SignatureTrustValidator).
     * We need to use to Validators instead of following code. REFACTOR later.
     *
     * Evaluate whether a given certificate should be trusted.
     *
     * Policy used in this implementation:
     * 1. Search the keystore for the transmitted certificate
     * 2. Search the keystore for a connection to the transmitted certificate
     * (that is, search for certificate(s) of the issuer of the transmitted certificate
     * 3. Verify the trust path for those certificates found because the search for the issuer
     * might be fooled by a phony DN (String!)
     *
     * @param cert the certificate that should be validated against the keystore
     * @param crypto A crypto instance to use for trust validation
     * @return true if the certificate is trusted, false if not
     * @throws RampartException  If an error occurred during validation.
     */
    protected boolean isCertificateTrusted(
        X509Certificate cert,
        Crypto crypto
    ) throws RampartException {
        String subjectString = cert.getSubjectX500Principal().getName();
        String issuerString = cert.getIssuerX500Principal().getName();
        BigInteger issuerSerial = cert.getSerialNumber();

        if (log.isDebugEnabled()) {
            log.debug("Transmitted certificate has subject " + subjectString);
            log.debug(
                "Transmitted certificate has issuer " + issuerString + " (serial "
                + issuerSerial + ")"
            );
        }

        //
        // FIRST step - Search the keystore for the transmitted certificate
        //
        if (isCertificateInKeyStore(crypto, cert)) {
            return true;
        }

        //
        // SECOND step - Search for the issuer cert (chain) of the transmitted certificate in the
        // keystore or the truststore
        //
        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.SUBJECT_DN);
        cryptoType.setSubjectDN(issuerString);
        X509Certificate[] foundCerts = new X509Certificate[0];
        try {
            foundCerts = crypto.getX509Certificates(cryptoType);
        } catch (WSSecurityException e) {
            throw new RampartException("noCertForSubject", e);
        }

        // If the certs have not been found, the issuer is not in the keystore/truststore
        // As a direct result, do not trust the transmitted certificate
        if (foundCerts == null || foundCerts.length < 1) {
            if (log.isDebugEnabled()) {
                log.debug(
                    "No certs found in keystore for issuer " + issuerString
                    + " of certificate for " + subjectString
                );
            }
            return false;
        }

        //
        // THIRD step
        // Check the certificate trust path for the issuer cert chain
        //
        if (log.isDebugEnabled()) {
            log.debug(
                "Preparing to validate certificate path for issuer " + issuerString
            );
        }
        //
        // Form a certificate chain from the transmitted certificate
        // and the certificate(s) of the issuer from the keystore/truststore
        //
        X509Certificate[] x509certs = new X509Certificate[foundCerts.length + 1];
        x509certs[0] = cert;
        for (int j = 0; j < foundCerts.length; j++) {
            x509certs[j + 1] = (X509Certificate)foundCerts[j];
        }

        //
        // Use the validation method from the crypto to check whether the subjects'
        // certificate was really signed by the issuer stated in the certificate
        //
        // TODO we need to configure enable revocation ...
        try {
            crypto.verifyTrust(x509certs, false, null, null);
            if (log.isDebugEnabled()) {
                log.debug(
                    "Certificate path has been verified for certificate with subject "
                     + subjectString
                );
            }
            return true;
        } catch (WSSecurityException e) {
            log.error(
                "Certificate path could not be verified for certificate with subject "
                + subjectString
            );
            throw new RampartException("certPathVerificationFailed", e);
        }

    }

    /**
     * Check to see if the certificate argument is in the keystore
     * TODO Directly copied from WSS4J (SignatureTrustValidator) - Optimize later
     * @param crypto A Crypto instance to use for trust validation
     * @param cert The certificate to check
     * @return true if cert is in the keystore
     * @throws RampartException If certificates are not found for given issuer and serial number.
     */
    protected boolean isCertificateInKeyStore(
        Crypto crypto,
        X509Certificate cert
    ) throws RampartException {
        String issuerString = cert.getIssuerX500Principal().getName();
        BigInteger issuerSerial = cert.getSerialNumber();

        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
        cryptoType.setIssuerSerial(issuerString, issuerSerial);
        X509Certificate[] foundCerts = new X509Certificate[0];
        try {
            foundCerts = crypto.getX509Certificates(cryptoType);
        } catch (WSSecurityException e) {
            throw new RampartException("noCertificatesForIssuer", new String[]{issuerString,
                    issuerSerial.toString()}, e);
        }

        //
        // If a certificate has been found, the certificates must be compared
        // to ensure against phony DNs (compare encoded form including signature)
        //
        if (foundCerts != null && foundCerts[0] != null && foundCerts[0].equals(cert)) {
            if (log.isDebugEnabled()) {
                log.debug(
                        "Direct trust for certificate with " + cert.getSubjectX500Principal().getName()
                );
            }
            return true;
        }
        if (log.isDebugEnabled()) {
            log.debug(
                    "No certificate found for subject from issuer with " + issuerString
                            + " (serial " + issuerSerial + ")"
            );
        }
        return false;
    }

   
    protected ArrayList getEncryptedReferences(List<WSSecurityEngineResult> results) {
       
        //there can be multiple ref lists
        List<WSSecurityEngineResult> encrResults = getResults(results, WSConstants.ENCR);
       
        ArrayList refs = new ArrayList();

        for (WSSecurityEngineResult engineResult : encrResults) {
            // EmptyList rather than null is passed back from result creation so need to use List rather than ArrayList
            List dataRefUris = (List) engineResult
                    .get(WSSecurityEngineResult.TAG_DATA_REF_URIS);

            //take only the ref list processing results
            if (dataRefUris != null) {
                for (Iterator iterator = dataRefUris.iterator(); iterator
                        .hasNext(); ) {
                    WSDataRef uri = (WSDataRef) iterator.next();
                    refs.add(uri);
                }
            }
        }
       
        return refs;
    }
   
   
   
    protected List<WSSecurityEngineResult> getResults(List<WSSecurityEngineResult> results, int action) {
       
        List<WSSecurityEngineResult> list = new ArrayList<WSSecurityEngineResult>();

        for (WSSecurityEngineResult result : results) {
            // Check the result of every action whether it matches the given
            // action
            Integer actInt = (Integer) result.get(WSSecurityEngineResult.TAG_ACTION);
            if (actInt == action) {
                list.add(result);
            }
        }
       
        return list;
    }
   
    protected boolean isUsernameTokenPresent(ValidatorData data) {
       
        //TODO This can be integrated with supporting token processing
        // which also checks whether Username Tokens present
       
        RampartPolicyData rpd = data.getRampartMessageData().getPolicyData();
       
        List<SupportingToken> supportingToks = rpd.getSupportingTokensList();
        for (SupportingToken suppTok : supportingToks) {
            if (isUsernameTokenPresent(suppTok)) {
                return true;
            }
        }
       
        SupportingToken signedSuppToken = rpd.getSignedSupportingTokens();
        if(isUsernameTokenPresent(signedSuppToken)) {
            return true;
        }
       
        SupportingToken signedEndSuppToken = rpd.getSignedEndorsingSupportingTokens();
        if(isUsernameTokenPresent(signedEndSuppToken)) {
            return true;
        }
       
        SupportingToken endSuppToken = rpd.getEndorsingSupportingTokens();
        return isUsernameTokenPresent(endSuppToken);


    }
   
    protected boolean isUsernameTokenPresent(SupportingToken suppTok) {
       
        if(suppTok == null) {
            return false;
        }
       
        ArrayList tokens = suppTok.getTokens();
        for (Iterator iter = tokens.iterator(); iter.hasNext();) {
            Token token = (Token) iter.next();
            if(token instanceof UsernameToken) {
                return true;
            }
        }
       
        return false;
    }
   
    private boolean isRefIdPresent(ArrayList refList , String id) {

        if(id != null && id.charAt(0) == '#') {
           id = id.substring(1);
        }

        for (Object aRefList : refList) {
            WSDataRef dataRef = (WSDataRef) aRefList;

            //ArrayList can contain null elements
            if (dataRef == null) {
                continue;
            }
            //Try to get the wsuId of the decrypted element
            String dataRefUri = dataRef.getWsuId();
            //If not found, try the reference Id of encrypted element ( we set the same Id when we
            // decrypted element in WSS4J)  
            // TODO wsu id must present. We need to find the scenario where it is not set
            // if (dataRefUri == null) {
            //    dataRefUri = dataRef.getProtectedElement().getAttribute("Id"); // TODO check whether this is correct
                // earlier it was dataRefUri = dataRef.getDataref();
            //}
            if (dataRefUri != null && dataRefUri.equals(id)) {
                return true;
            }
        }
       
        return false;
       
    }
   
    public static WSSecurityEngineResult[] fetchActionResults(List<WSSecurityEngineResult> wsSecurityEngineResults, int action) {
        List<WSSecurityEngineResult> wsResult = new ArrayList<WSSecurityEngineResult>();

        // Find the part of the security result that matches the given action
        for (WSSecurityEngineResult wsSecurityEngineResult : wsSecurityEngineResults) {
            // Check the result of every action whether it matches the given action
            WSSecurityEngineResult result = (WSSecurityEngineResult) wsSecurityEngineResult;
            int resultAction = (Integer) result.get(WSSecurityEngineResult.TAG_ACTION);
            if (resultAction == action) {
                wsResult.add(wsSecurityEngineResult);
            }
        }

        return wsResult.toArray(new WSSecurityEngineResult[wsResult
                .size()]);
    }
   
    // Just copied fetchActionResult from older WSSec as it's been removed - it's a very simple filter
    public static WSSecurityEngineResult fetchActionResult(
            List<WSSecurityEngineResult> resultList,
            int action
        ) {
   
        WSSecurityEngineResult returnResult = null;
       
        for (WSSecurityEngineResult result : resultList) {
            //
            // Check the result of every action whether it matches the given action
            //
            int resultAction =
                ((java.lang.Integer)result.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
            if (resultAction == action) {
                returnResult = result;
            }
        }

        return returnResult;
    }
   
    private boolean isRefIdPresent(ArrayList refList , QName qname) {

        for (Object aRefList : refList) {
            WSDataRef dataRef = (WSDataRef) aRefList;

            //ArrayList can contain null elements
            if (dataRef == null) {
                continue;
            }
            //QName of the decrypted element
            QName dataRefQName = dataRef.getName();

            if (dataRefQName != null && dataRefQName.equals(qname)) {
                return true;
            }

        }
       
        return false;
       
    }

   
}