/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.handler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSEncryptionPart;
import org.apache.ws.security.WSPasswordCallback;
import org.apache.ws.security.WSSConfig;
import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.action.Action;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.token.SignatureConfirmation;
import org.apache.ws.security.message.token.Timestamp;
import org.apache.ws.security.util.Loader;
import org.apache.ws.security.util.StringUtil;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.ws.security.util.XmlSchemaDateFormat;
import org.w3c.dom.Document;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;

import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;


/**
 * Extracted from WSDoAllReceiver and WSDoAllSender
 * Extended to all passwordless UsernameTokens and configurable identities.
 *
 * @author Davanum Srinivas (dims@yahoo.com).
 * @author Werner Dittmann (Werner.Dittmann@t-online.de).
 * @author Marcel Ammerlaan (marcel.ammerlaan@gmail.com).
 */
public abstract class WSHandler {
    public static String DONE = "done";
    private static final Log log = LogFactory.getLog(WSHandler.class.getName());
    protected static final WSSecurityEngine secEngine = WSSecurityEngine.getInstance();
    protected static Hashtable cryptos = new Hashtable(5);

    private boolean doDebug = log.isDebugEnabled();

    /**                                                             
     * Performs all defined security actions to set-up the SOAP request.
     * 
     * 
     * @param doAction a set defining the actions to do 
     * @param doc   the request as DOM document 
     * @param reqData a data storage to pass values around between methods
     * @param actions a vector holding the actions to do in the order defined
     *                in the deployment file or property
     * @throws WSSecurityException
     */
    protected void doSenderAction(
            int doAction, 
            Document doc,
            RequestData reqData, 
            Vector actions, 
            boolean isRequest
    ) throws WSSecurityException {

        boolean mu = decodeMustUnderstand(reqData);

        WSSConfig wssConfig = reqData.getWssConfig();
        if (wssConfig == null) {
            wssConfig = secEngine.getWssConfig();
        }

        boolean enableSigConf = decodeEnableSignatureConfirmation(reqData);
        wssConfig.setEnableSignatureConfirmation(
            enableSigConf || ((doAction & WSConstants.SC) != 0)
        );
        wssConfig.setPasswordsAreEncoded(decodeUseEncodedPasswords(reqData));

        wssConfig.setPrecisionInMilliSeconds(
            decodeTimestampPrecision(reqData)
        );
        reqData.setWssConfig(wssConfig);

        Object mc = reqData.getMsgContext();
        String actor = getString(WSHandlerConstants.ACTOR, mc);
        reqData.setActor(actor);

        WSSecHeader secHeader = new WSSecHeader(actor, mu);
        secHeader.insertSecurityHeader(doc);

        reqData.setSecHeader(secHeader);
        reqData.setSoapConstants(
            WSSecurityUtil.getSOAPConstants(doc.getDocumentElement())
        );
        /*
         * Here we have action, username, password, and actor, mustUnderstand.
         * Now get the action specific parameters.
         */
        if ((doAction & WSConstants.UT) == WSConstants.UT) {
            decodeUTParameter(reqData);
        }
        /*
         * Here we have action, username, password, and actor, mustUnderstand.
         * Now get the action specific parameters.
         */
        if ((doAction & WSConstants.UT_SIGN) == WSConstants.UT_SIGN) {
            decodeUTParameter(reqData);
            decodeSignatureParameter(reqData);
        }
        /*
         * Get and check the Signature specific parameters first because they
         * may be used for encryption too.
         */
        if ((doAction & WSConstants.SIGN) == WSConstants.SIGN) {
            reqData.setSigCrypto(loadSignatureCrypto(reqData));
            decodeSignatureParameter(reqData);
        }
        /*
         * If we need to handle signed SAML token then we may need the
         * Signature parameters. The handle procedure loads the signature crypto
         * file on demand, thus don't do it here.
         */
        if ((doAction & WSConstants.ST_SIGNED) == WSConstants.ST_SIGNED) {
            decodeSignatureParameter(reqData);
        }
        /*
         * Set and check the encryption specific parameters, if necessary take
         * over signature parameters username and crypto instance.
         */
        if ((doAction & WSConstants.ENCR) == WSConstants.ENCR) {
            reqData.setEncCrypto(loadEncryptionCrypto(reqData));
            decodeEncryptionParameter(reqData);
        }
        /*
         * If after all the parsing no Signature parts defined, set here a
         * default set. This is necessary because we add SignatureConfirmation
         * and therefore the default (Body) must be set here. The default setting
         * in WSSignEnvelope doesn't work because the vector is not empty anymore.
         */
        if (reqData.getSignatureParts().isEmpty()) {
            WSEncryptionPart encP = new WSEncryptionPart(reqData.getSoapConstants()
                    .getBodyQName().getLocalPart(), reqData.getSoapConstants()
                    .getEnvelopeURI(), "Content");
            reqData.getSignatureParts().add(encP);
        }
        /*
         * If SignatureConfirmation is enabled and this is a response then
         * insert SignatureConfrmation elements, note their wsu:id in the signature
         * parts. They will be signed automatically during a (probably) defined
         * SIGN action.
         */
        if (wssConfig.isEnableSignatureConfirmation() && !isRequest) {
            String done = (String) 
                getProperty(reqData.getMsgContext(), WSHandlerConstants.SIG_CONF_DONE);
            if (!DONE.equals(done)
                && (getProperty(reqData.getMsgContext(), WSHandlerConstants.RECV_RESULTS)) 
                    != null) {
                wssConfig.getAction(WSConstants.SC).execute(this, WSConstants.SC, doc, reqData);
            }
        }
        /*
         * Here we have all necessary information to perform the requested
         * action(s).
         */
        for (int i = 0; i < actions.size(); i++) {

            int actionToDo = ((Integer) actions.get(i)).intValue();
            if (doDebug) {
                log.debug("Performing Action: " + actionToDo);
            }

            switch (actionToDo) {
            case WSConstants.UT:
            case WSConstants.ENCR:
            case WSConstants.SIGN:
            case WSConstants.ST_SIGNED:
            case WSConstants.ST_UNSIGNED:
            case WSConstants.TS:
            case WSConstants.UT_SIGN:
                wssConfig.getAction(actionToDo).execute(this, actionToDo, doc, reqData);
                break;
            case WSConstants.NO_SERIALIZE:
                reqData.setNoSerialization(true);
                break;
                //
                // Handle any "custom" actions, similarly,
                // but to preserve behavior from previous
                // versions, consume (but log) action lookup failures.
                //
            default:
                Action doit = null;
            try {
                doit = wssConfig.getAction(actionToDo);
            } catch (final WSSecurityException e) {
                log.warn(
                        "Error trying to locate a custom action (" + actionToDo + ")", 
                        e
                );
            }
            if (doit != null) {
                doit.execute(this, actionToDo, doc, reqData);
            }
            }
        }
        
        /*
         * If this is a request then store all signature values. Add ours to
         * already gathered values because of chained handlers, e.g. for
         * other actors.
         */
        if (wssConfig.isEnableSignatureConfirmation() 
                && isRequest
                && reqData.getSignatureValues().size() > 0) {
            Vector sigv = (Vector) 
            getProperty(reqData.getMsgContext(), WSHandlerConstants.SEND_SIGV);
            if (sigv == null) {
                sigv = new Vector();
                setProperty(reqData.getMsgContext(),
                        WSHandlerConstants.SEND_SIGV, sigv);
            }
            // sigv.add(reqData.getSignatureValues());
            sigv.addAll(reqData.getSignatureValues());
        }
    }



    protected void doReceiverAction(int doAction, RequestData reqData)
        throws WSSecurityException {

        WSSConfig wssConfig = reqData.getWssConfig();
        if (wssConfig == null) {
            wssConfig = secEngine.getWssConfig();
        }
        boolean enableSigConf = decodeEnableSignatureConfirmation(reqData);
        wssConfig.setEnableSignatureConfirmation(
            enableSigConf || ((doAction & WSConstants.SC) != 0)
        );
        wssConfig.setTimeStampStrict(decodeTimestampStrict(reqData));
        wssConfig.setHandleCustomPasswordTypes(decodeCustomPasswordTypes(reqData));
        wssConfig.setPasswordsAreEncoded(decodeUseEncodedPasswords(reqData));
        wssConfig.setAllowNamespaceQualifiedPasswordTypes(
            decodeNamespaceQualifiedPasswordTypes(reqData)
        );
        wssConfig.setSecretKeyLength(reqData.getSecretKeyLength());
        reqData.setWssConfig(wssConfig);

        if ((doAction & WSConstants.SIGN) == WSConstants.SIGN) {
            decodeSignatureParameter2(reqData);
        }
        
        if ((doAction & WSConstants.ST_SIGNED) == WSConstants.ST_SIGNED) {
            decodeSignatureParameter2(reqData);
        }
        
        if ((doAction & WSConstants.ENCR) == WSConstants.ENCR) {
            decodeDecryptionParameter(reqData);
        }
        if ((doAction & WSConstants.NO_SERIALIZE) == WSConstants.NO_SERIALIZE) {
            reqData.setNoSerialization(true);
        }
    }

    protected boolean checkReceiverResults(Vector wsResult, Vector actions) {
        int resultActions = wsResult.size();
        int size = actions.size();

        int ai = 0;
        for (int i = 0; i < resultActions; i++) {
            final Integer actInt = (Integer) ((WSSecurityEngineResult) wsResult
                    .get(i)).get(WSSecurityEngineResult.TAG_ACTION);
            int act = actInt.intValue();
            if (act == WSConstants.SC || act == WSConstants.BST) {
                continue;
            }
            if (ai >= size || ((Integer) actions.get(ai++)).intValue() != act) {
                return false;
            }
        }

        if (ai != size) {
            return false;
        }

        return true;
    }
    
    protected boolean checkReceiverResultsAnyOrder(Vector wsResult, Vector actions) {

        java.util.List recordedActions = new Vector(actions.size());
        for (int i = 0; i < actions.size(); i++) {
            Integer action = (Integer)actions.get(i);
            recordedActions.add(action);
        }
        
        for (int i = 0; i < wsResult.size(); i++) {
            final Integer actInt = (Integer) ((WSSecurityEngineResult) wsResult
                    .get(i)).get(WSSecurityEngineResult.TAG_ACTION);
            int act = actInt.intValue();
            if (act == WSConstants.SC || act == WSConstants.BST) {
                continue;
            }
            
            if (!recordedActions.remove(actInt)) {
                return false;
            }
        }

        if (!recordedActions.isEmpty()) {
            return false;
        }

        return true;
    }

    protected void checkSignatureConfirmation(RequestData reqData,
            Vector wsResult) throws WSSecurityException{
        if (doDebug) {
            log.debug("Check Signature confirmation");
        }

        /*
         * First get all Signature values stored during sending the request
         */
        Vector sigv = (Vector) getProperty(reqData.getMsgContext(),
                WSHandlerConstants.SEND_SIGV);
        /*
         * Now get all results that hold a SignatureConfirmation element from
         * the current run of receiver (we can have more than one run: if we
         * have several security header blocks with different actors/roles)
         */
        Vector sigConf = new Vector();
        WSSecurityUtil.fetchAllActionResults(wsResult, WSConstants.SC, sigConf);
        /*
         * now loop over all SignatureConfirmation results and check:
         * - if there is a signature value and no Signature value generated in request: error
         * - if there is a signature value and no matching Signature value found: error
         * 
         *  If a matching value found: remove from vector of stored signature values
         */
        for (int i = 0; i < sigConf.size(); i++) {
            WSSecurityEngineResult result = 
                (WSSecurityEngineResult)sigConf.get(i);
            SignatureConfirmation sc = 
                (SignatureConfirmation)result.get(WSSecurityEngineResult.TAG_SIGNATURE_CONFIRMATION);

            byte[] sigVal = sc.getSignatureValue();
            if (sigVal != null) {
                if (sigv == null || sigv.size() == 0) {
                    // If there are no stored signature values
                    if (sigVal.length != 0) {
                        // If there's no value in the case where there are no
                        // stored SV it is valid. Therefore if there IS a value 
                        // in the sig confirmation element
                        throw new WSSecurityException(
                            "WSHandler: Check Signature confirmation: got a SC element, "
                            + "but no stored SV"
                        );
                    }
                } else {
                    //If we have stored signature values
                    boolean found = false;
                    for (int ii = 0; ii < sigv.size(); ii++) {
                        byte[] storedValue = (byte[]) sigv.get(ii);
                        if (Arrays.equals(sigVal, storedValue)) {
                            found = true;
                            sigv.remove(ii);
                            break;
                        }
                    }
                    if (!found) {
                        throw new WSSecurityException(
                            "WSHandler: Check Signature confirmation: got SC element, "
                            + "but no matching SV"
                        );
                    } 
                }
            }
        }

        /*
         * This indicates this is the last handler: the vector holding the
         * stored Signature values must be empty, otherwise we have an error
         */
        if (!reqData.isNoSerialization()) {
            log.debug("Check Signature confirmation - last handler");
            if (sigv != null && !sigv.isEmpty()) {
                throw new WSSecurityException(
                    "WSHandler: Check Signature confirmation: stored SV vector not empty"
                );
            }
        }
    }
    
    /**
     * Hook to allow subclasses to load their Signature Crypto however they see
     * fit.
     */
    public Crypto loadSignatureCrypto(RequestData reqData) 
        throws WSSecurityException {
        Crypto crypto = null;
        /*
         * Get crypto property file for signature. If none specified throw
         * fault, otherwise get a crypto instance.
         */
        String sigPropFile = 
            getString(WSHandlerConstants.SIG_PROP_FILE, reqData.getMsgContext());
        if (sigPropFile != null) {
            crypto = (Crypto) cryptos.get(sigPropFile);
            if (crypto == null) {
                crypto = 
                    CryptoFactory.getInstance(
                        sigPropFile, this.getClassLoader(reqData.getMsgContext())
                    );
                cryptos.put(sigPropFile, crypto);
            }
        } else if (getString(WSHandlerConstants.SIG_PROP_REF_ID, reqData.getMsgContext()) != null) {
            //
            // If the property file is missing then look for the Properties object 
            //
            String refId = 
                getString(WSHandlerConstants.SIG_PROP_REF_ID, reqData.getMsgContext());
            if (refId != null) {
                Object propObj = getProperty(reqData.getMsgContext(), refId);
                if (propObj instanceof Properties) {
                    crypto = (Crypto) cryptos.get(refId);
                    if (crypto == null) {
                        crypto = CryptoFactory.getInstance((Properties)propObj);
                        cryptos.put(refId, crypto);
                    }
                }
            }
        }
        
        return crypto;
    }

    /**
     * Hook to allow subclasses to load their Encryption Crypto however they
     * see fit.
     */
    protected Crypto loadEncryptionCrypto(RequestData reqData) 
        throws WSSecurityException {
        Crypto crypto = null;
        /*
         * Get encryption crypto property file. If non specified take crypto
         * instance from signature, if that fails: throw fault
         */
        String encPropFile = 
            getString(WSHandlerConstants.ENC_PROP_FILE, reqData.getMsgContext());
        if (encPropFile != null) {
            crypto = (Crypto) cryptos.get(encPropFile);
            if (crypto == null) {
                crypto = 
                    CryptoFactory.getInstance(
                        encPropFile, this.getClassLoader(reqData.getMsgContext())
                    );
                cryptos.put(encPropFile, crypto);
            }
        } else if (getString(WSHandlerConstants.ENC_PROP_REF_ID, reqData.getMsgContext()) != null) {
            //
            // If the property file is missing then look for the Properties object 
            //
            String refId = 
                getString(WSHandlerConstants.ENC_PROP_REF_ID, reqData.getMsgContext());
            if (refId != null) {
                Object propObj = getProperty(reqData.getMsgContext(), refId);
                if (propObj instanceof Properties) {
                    crypto = (Crypto) cryptos.get(refId);
                    if (crypto == null) {
                        crypto = CryptoFactory.getInstance((Properties)propObj);
                        cryptos.put(refId, crypto);
                    }
                }
            }
        } else if (reqData.getSigCrypto() != null) {
            //
            // Default to the signature crypto
            //
            crypto = reqData.getSigCrypto();
        }
        
        return crypto;
    }

    protected void decodeUTParameter(RequestData reqData) 
        throws WSSecurityException {
        Object mc = reqData.getMsgContext();

        String type = getString(WSHandlerConstants.PASSWORD_TYPE, mc);
        if (type != null) {
            if (WSConstants.PW_TEXT.equals(type)) {
                reqData.setPwType(WSConstants.PASSWORD_TEXT);
            } else if (WSConstants.PW_DIGEST.equals(type)) {
                reqData.setPwType(WSConstants.PASSWORD_DIGEST);
            } else if (WSConstants.PW_NONE.equals(type)) {
                // No password requested.
                reqData.setPwType(null);
            } else {
                throw new WSSecurityException("Unknown password type encoding: " + type);
            }
        }

        String add = getString(WSHandlerConstants.ADD_UT_ELEMENTS, mc);
        if (add != null) {
            reqData.setUtElements(StringUtil.split(add, ' '));
        }
        
        String derived = getString(WSHandlerConstants.USE_DERIVED_KEY, mc);
        boolean useDerivedKey = Boolean.valueOf(derived).booleanValue();
        if (useDerivedKey) {
            reqData.setUseDerivedKey(useDerivedKey);
        }
        
        String derivedMAC = getString(WSHandlerConstants.USE_DERIVED_KEY, mc);
        boolean useDerivedKeyForMAC = Boolean.valueOf(derivedMAC).booleanValue();
        if (useDerivedKeyForMAC) {
            reqData.setUseDerivedKeyForMAC(useDerivedKeyForMAC);
        }
        
        String iterations = getString(WSHandlerConstants.DERIVED_KEY_ITERATIONS, mc);
        if (iterations != null) {
            int iIterations = Integer.parseInt(iterations);
            reqData.setDerivedKeyIterations(iIterations);
        }
    }

    protected void decodeSignatureParameter(RequestData reqData) 
        throws WSSecurityException {
        Object mc = reqData.getMsgContext();
        String signatureUser = getString(WSHandlerConstants.SIGNATURE_USER, mc);

        if (signatureUser != null) {
            reqData.setSignatureUser(signatureUser);
        } else {
            reqData.setSignatureUser(reqData.getUsername());
        }
        
        String keyId = getString(WSHandlerConstants.SIG_KEY_ID, mc);
        if (keyId != null) {
            Integer id = (Integer) WSHandlerConstants.keyIdentifier.get(keyId);
            if (id == null) {
                throw new WSSecurityException(
                    "WSHandler: Signature: unknown key identification"
                );
            }
            int tmp = id.intValue();
            if (!(tmp == WSConstants.ISSUER_SERIAL
                    || tmp == WSConstants.BST_DIRECT_REFERENCE
                    || tmp == WSConstants.X509_KEY_IDENTIFIER
                    || tmp == WSConstants.SKI_KEY_IDENTIFIER
                    || tmp == WSConstants.THUMBPRINT_IDENTIFIER
                    || tmp == WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER)) {
                throw new WSSecurityException(
                    "WSHandler: Signature: illegal key identification"
                );
            }
            reqData.setSigKeyId(tmp);
        }
        String algo = getString(WSHandlerConstants.SIG_ALGO, mc);
        reqData.setSigAlgorithm(algo);
        
        String digestAlgo = getString(WSHandlerConstants.SIG_DIGEST_ALGO, mc);
        reqData.setSigDigestAlgorithm(digestAlgo);

        String parts = getString(WSHandlerConstants.SIGNATURE_PARTS, mc);
        if (parts != null) {
            splitEncParts(parts, reqData.getSignatureParts(), reqData);
        }
        
        String secretKeyLength = getString(WSHandlerConstants.WSE_SECRET_KEY_LENGTH, mc);
        if (secretKeyLength != null) {
            int iSecretKeyLength = Integer.parseInt(secretKeyLength);
            reqData.setSecretKeyLength(iSecretKeyLength);
        }
        
        boolean useSingleCert = decodeUseSingleCertificate(reqData);
        reqData.setUseSingleCert(useSingleCert);
    }

    protected void decodeEncryptionParameter(RequestData reqData) 
        throws WSSecurityException {
        Object mc = reqData.getMsgContext();
        String encUser = getString(WSHandlerConstants.ENCRYPTION_USER, mc);

        if (encUser != null) {
            reqData.setEncUser(encUser);
        } else {
            reqData.setEncUser(reqData.getUsername());
        }
        if (reqData.getEncUser() == null) {
            throw new WSSecurityException("WSHandler: Encryption: no username");
        }
        /*
         * String msgType = msgContext.getCurrentMessage().getMessageType(); if
         * (msgType != null && msgType.equals(Message.RESPONSE)) {
         * handleSpecialUser(encUser); }
         */
        handleSpecialUser(reqData);

        /*
         * If the following parameters are no used (they return null) then the
         * default values of WSS4J are used.
         */
        String encKeyId = getString(WSHandlerConstants.ENC_KEY_ID, mc);
        if (encKeyId != null) {
            Integer id = (Integer) WSHandlerConstants.keyIdentifier.get(encKeyId);
            if (id == null) {
                throw new WSSecurityException(
                    "WSHandler: Encryption: unknown key identification"
                );
            }
            int tmp = id.intValue();
            reqData.setEncKeyId(tmp);
            if (!(tmp == WSConstants.ISSUER_SERIAL
                    || tmp == WSConstants.X509_KEY_IDENTIFIER
                    || tmp == WSConstants.SKI_KEY_IDENTIFIER
                    || tmp == WSConstants.BST_DIRECT_REFERENCE
                    || tmp == WSConstants.EMBEDDED_KEYNAME
                    || tmp == WSConstants.THUMBPRINT_IDENTIFIER
                    || tmp == WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER)) {
                throw new WSSecurityException(
                    "WSHandler: Encryption: illegal key identification"
                );
            }
        }
        String encSymAlgo = getString(WSHandlerConstants.ENC_SYM_ALGO, mc);
        reqData.setEncSymmAlgo(encSymAlgo);

        String encKeyTransport = 
            getString(WSHandlerConstants.ENC_KEY_TRANSPORT, mc);
        reqData.setEncKeyTransport(encKeyTransport);
        
        String encSymEncKey = getString(WSHandlerConstants.ENC_SYM_ENC_KEY, mc);
        if (encSymEncKey != null) {
            boolean encSymEndKeyBoolean = Boolean.valueOf(encSymEncKey).booleanValue();
            reqData.setEncryptSymmetricEncryptionKey(encSymEndKeyBoolean);
        }

        String encParts = getString(WSHandlerConstants.ENCRYPTION_PARTS, mc);
        if (encParts != null) {
            splitEncParts(encParts, reqData.getEncryptParts(), reqData);
        }
    }

    protected boolean decodeMustUnderstand(RequestData reqData) 
        throws WSSecurityException {
        String mu = 
            getString(WSHandlerConstants.MUST_UNDERSTAND, reqData.getMsgContext());

        if (mu == null) {
            return true;
        }
        if ("0".equals(mu) || "false".equals(mu)) {
            return false;
        } 
        if ("1".equals(mu) || "true".equals(mu)) {
            return true;
        }

        throw new WSSecurityException(
            "WSHandler: illegal mustUnderstand parameter"
        );
    }

    public int decodeTimeToLive(RequestData reqData) {
        String ttl = 
            getString(WSHandlerConstants.TTL_TIMESTAMP, reqData.getMsgContext());
        int ttl_i = 0;
        if (ttl != null) {
            try {
                ttl_i = Integer.parseInt(ttl);
            } catch (NumberFormatException e) {
                ttl_i = reqData.getTimeToLive();
            }
        }
        if (ttl_i <= 0) {
            ttl_i = reqData.getTimeToLive();
        }
        return ttl_i;
    }

    protected boolean decodeEnableSignatureConfirmation(RequestData reqData) throws WSSecurityException {

        String value = getString(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION,
                reqData.getMsgContext());

        if (value == null) {
            return true;
        }
        if ("0".equals(value) || "false".equals(value)) {
            return false;
        } 
        if ("1".equals(value) || "true".equals(value)) {
            return true;
        }

        throw new WSSecurityException(
            "WSHandler: illegal enableSignatureConfirmation parameter"
        );
    }

    protected boolean decodeTimestampPrecision(RequestData reqData) 
        throws WSSecurityException {
        String value = getString(WSHandlerConstants.TIMESTAMP_PRECISION,
                reqData.getMsgContext());

        if (value == null) {
            return true;
        }
        if ("0".equals(value) || "false".equals(value)) {
            return false;
        } 
        if ("1".equals(value) || "true".equals(value)) {
            return true;
        }

        throw new WSSecurityException(
            "WSHandler: illegal precisionInMilliSeconds parameter"
        );
    }

    protected boolean decodeCustomPasswordTypes(RequestData reqData) 
        throws WSSecurityException {
        String value = getString(
                WSHandlerConstants.HANDLE_CUSTOM_PASSWORD_TYPES,
                reqData.getMsgContext()
        );

        if (value == null) {
            return false;
        }
        if ("0".equals(value) || "false".equals(value)) {
            return false;
        } 
        if ("1".equals(value) || "true".equals(value)) {
            return true;
        }

        throw new WSSecurityException(
            "WSHandler: illegal handleCustomPasswordTypes parameter"
        );
    }
    
    protected boolean decodeUseEncodedPasswords(RequestData reqData) 
        throws WSSecurityException {
        String value = getString(
            WSHandlerConstants.USE_ENCODED_PASSWORDS,
            reqData.getMsgContext()
        );
    
        if (value == null) {
            return false;
        }
        if ("0".equals(value) || "false".equals(value)) {
            return false;
        } 
        if ("1".equals(value) || "true".equals(value)) {
            return true;
        }
    
        throw new WSSecurityException(
            "WSHandler: illegal useEncodedPasswords parameter"
        );
    }
    
    protected boolean decodeNamespaceQualifiedPasswordTypes(RequestData reqData) 
        throws WSSecurityException {
        String value = getString(
            WSHandlerConstants.ALLOW_NAMESPACE_QUALIFIED_PASSWORD_TYPES,
            reqData.getMsgContext()
        );
    
        if (value == null) {
            return false;
        }
        if ("0".equals(value) || "false".equals(value)) {
            return false;
        } 
        if ("1".equals(value) || "true".equals(value)) {
            return true;
        }
    
        throw new WSSecurityException(
            "WSHandler: illegal allowNamespaceQualifiedPasswordTypes parameter"
        );
    }

    protected boolean decodeTimestampStrict(RequestData reqData) 
        throws WSSecurityException {
        String value = getString(WSHandlerConstants.TIMESTAMP_STRICT,
                reqData.getMsgContext());

        if (value == null) {
            return true;
        }
        if ("0".equals(value) || "false".equals(value)) {
            return false;
        } 
        if ("1".equals(value) || "true".equals(value)) {
            return true;
        }

        throw new WSSecurityException(
            "WSHandler: illegal timestampStrict parameter"
        );
    }
    
    protected boolean decodeUseSingleCertificate(RequestData reqData) 
        throws WSSecurityException {
        String useSingleCert = 
            getString(WSHandlerConstants.USE_SINGLE_CERTIFICATE, reqData.getMsgContext());
    
        if (useSingleCert == null) {
            return true;
        }
        if ("0".equals(useSingleCert) || "false".equals(useSingleCert)) {
            return false;
        } 
        if ("1".equals(useSingleCert) || "true".equals(useSingleCert)) {
            return true;
        }
    
        throw new WSSecurityException(
            "WSHandler: illegal useSingleCert parameter"
        );
    }

    /**
     * Get a password to construct a UsernameToken or sign a message.
     * <p/>
     * Try all possible sources to get a password.
     */
    public WSPasswordCallback getPassword(String username,
            int doAction,
            String clsProp,
            String refProp,
            RequestData reqData
    ) throws WSSecurityException {
        WSPasswordCallback pwCb = null;
        Object mc = reqData.getMsgContext();
        String callback = getString(clsProp, mc);
        
        if (callback != null) { 
            // we have a password callback class
            pwCb = readPwViaCallbackClass(callback, username, doAction, reqData);
        } else {
            // Try to obtain a password callback class from the message context or handler options
            CallbackHandler cbHandler = (CallbackHandler) getOption(refProp);
            if (cbHandler == null) {
                cbHandler = (CallbackHandler) getProperty(mc, refProp);
            }
            if (cbHandler != null) {
                pwCb = performCallback(cbHandler, username, doAction);
            } else {
                //
                // If a callback isn't configured then try to get the password
                // from the message context
                //
                String password = getPassword(mc);
                if (password == null) {
                    String err = "provided null or empty password";
                    throw new WSSecurityException("WSHandler: application " + err);
                }
                pwCb = constructPasswordCallback(username, doAction);
                pwCb.setPassword(password);
            }
        }
        
        return pwCb;
    }

    private WSPasswordCallback readPwViaCallbackClass(String callback,
            String username,
            int doAction,
            RequestData requestData
    ) throws WSSecurityException {

        Class cbClass = null;
        CallbackHandler cbHandler = null;
        try {
            cbClass = 
                Loader.loadClass(getClassLoader(requestData.getMsgContext()), callback);
        } catch (ClassNotFoundException e) {
            throw new WSSecurityException(
                "WSHandler: cannot load password callback class: " + callback, e
            );
        }
        try {
            cbHandler = (CallbackHandler) cbClass.newInstance();
        } catch (Exception e) {
            throw new WSSecurityException(
                "WSHandler: cannot create instance of password callback: " + callback, e
            );
        }
        return performCallback(cbHandler, username, doAction);
    }

    /**
     * Perform a callback to get a password.
     * <p/>
     * The called back function gets an indication why to provide a password:
     * to produce a UsernameToken, Signature, or a password (key) for a given
     * name.
     */
    private WSPasswordCallback performCallback(CallbackHandler cbHandler,
            String username,
            int doAction
    ) throws WSSecurityException {

        WSPasswordCallback pwCb = constructPasswordCallback(username, doAction);
        Callback[] callbacks = new Callback[1];
        callbacks[0] = pwCb;
        /*
         * Call back the application to get the password
         */
        try {
            cbHandler.handle(callbacks);
        } catch (Exception e) {
            throw new WSSecurityException("WSHandler: password callback failed", e);
        }
        return pwCb;
    }

    private WSPasswordCallback constructPasswordCallback(
            String username,
            int doAction
    ) throws WSSecurityException {

        int reason = WSPasswordCallback.UNKNOWN;

        switch (doAction) {
        case WSConstants.UT:
        case WSConstants.UT_SIGN:
            reason = WSPasswordCallback.USERNAME_TOKEN;
            break;
        case WSConstants.SIGN:
            reason = WSPasswordCallback.SIGNATURE;
            break;
        case WSConstants.ENCR:
            reason = WSPasswordCallback.KEY_NAME;
            break;
        }
        return new WSPasswordCallback(username, reason);
    }

    private void splitEncParts(String tmpS, Vector parts, RequestData reqData)
        throws WSSecurityException {
        WSEncryptionPart encPart = null;
        String[] rawParts = StringUtil.split(tmpS, ';');

        for (int i = 0; i < rawParts.length; i++) {
            String[] partDef = StringUtil.split(rawParts[i], '}');

            if (partDef.length == 1) {
                if (doDebug) {
                    log.debug("single partDef: '" + partDef[0] + "'");
                }
                encPart =
                    new WSEncryptionPart(partDef[0].trim(),
                            reqData.getSoapConstants().getEnvelopeURI(),
                            "Content");
            } else if (partDef.length == 3) {
                String mode = partDef[0].trim();
                if (mode.length() <= 1) {
                    mode = "Content";
                } else {
                    mode = mode.substring(1);
                }
                String nmSpace = partDef[1].trim();
                if (nmSpace.length() <= 1) {
                    nmSpace = reqData.getSoapConstants().getEnvelopeURI();
                } else {
                    nmSpace = nmSpace.substring(1);
                    if (nmSpace.equals(WSConstants.NULL_NS)) {
                        nmSpace = null;
                    }
                }
                String element = partDef[2].trim();
                if (doDebug) {
                    log.debug(
                        "partDefs: '" + mode + "' ,'" + nmSpace + "' ,'" + element + "'"
                    );
                }
                encPart = new WSEncryptionPart(element, nmSpace, mode);
            } else {
                throw new WSSecurityException("WSHandler: wrong part definition: " + tmpS);
            }
            parts.add(encPart);
        }
    }

    private void handleSpecialUser(RequestData reqData) {
        if (!WSHandlerConstants.USE_REQ_SIG_CERT.equals(reqData.getEncUser())) {
            return;
        }
        Vector results = 
            (Vector) getProperty(reqData.getMsgContext(), WSHandlerConstants.RECV_RESULTS);
        if (results == null) {
            return;
        }
        /*
         * Scan the results for a matching actor. Use results only if the
         * receiving Actor and the sending Actor match.
         */
        for (int i = 0; i < results.size(); i++) {
            WSHandlerResult rResult =
                (WSHandlerResult) results.get(i);
            String hActor = rResult.getActor();
            if (!WSSecurityUtil.isActorEqual(reqData.getActor(), hActor)) {
                continue;
            }
            Vector wsSecEngineResults = rResult.getResults();
            /*
             * Scan the results for the first Signature action. Use the
             * certificate of this Signature to set the certificate for the
             * encryption action :-).
             */
            for (int j = 0; j < wsSecEngineResults.size(); j++) {
                WSSecurityEngineResult wser =
                    (WSSecurityEngineResult) wsSecEngineResults.get(j);
                int wserAction = 
                    ((java.lang.Integer)wser.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
                if (wserAction == WSConstants.SIGN) {
                    X509Certificate cert = 
                        (X509Certificate)wser.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
                    reqData.setEncCert(cert);
                    return;
                }
            }
        }
    }

    /**
     * Hook to allow subclasses to load their Decryption Crypto however they 
     * see fit.
     */
    protected Crypto loadDecryptionCrypto(RequestData reqData) 
        throws WSSecurityException {

        Crypto crypto = null;
        String decPropFile = 
            getString(WSHandlerConstants.DEC_PROP_FILE, reqData.getMsgContext());
        if (decPropFile != null) {
            crypto = (Crypto) cryptos.get(decPropFile);
            if (crypto == null) {
                crypto = 
                    CryptoFactory.getInstance(
                        decPropFile, this.getClassLoader(reqData.getMsgContext())
                    );
                cryptos.put(decPropFile, crypto);
            }
        } else if (getString(WSHandlerConstants.DEC_PROP_REF_ID, reqData.getMsgContext()) != null) {
            //
            // If the property file is missing then look for the Properties object 
            //
            String refId = 
                getString(WSHandlerConstants.DEC_PROP_REF_ID, reqData.getMsgContext());
            if (refId != null) {
                Object propObj = getProperty(reqData.getMsgContext(), refId);
                if (propObj instanceof Properties) {
                    crypto = (Crypto) cryptos.get(refId);
                    if (crypto == null) {
                        crypto = CryptoFactory.getInstance((Properties)propObj);
                        cryptos.put(refId, crypto);
                    }
                }
            }
        } else if (reqData.getSigCrypto() != null) {
            //
            // Default to the signature crypto
            //
            crypto = reqData.getSigCrypto();
        }
        
        return crypto;
    }

    protected void decodeSignatureParameter2(RequestData reqData) 
        throws WSSecurityException {
        reqData.setSigCrypto(loadSignatureCrypto(reqData));
        /* There are currently no other signature parameters that need 
         * to be handled here, but we call the load crypto hook rather 
         * than just changing the visibility
         * of this method to maintain parity with WSDoAllSender.
         */
    }

    /*
     * Set and check the decryption specific parameters, if necessary
     * take over signature crypto instance.
     */
    protected void decodeDecryptionParameter(RequestData reqData) 
        throws WSSecurityException {
        reqData.setDecCrypto(loadDecryptionCrypto(reqData));
        /* There are currently no other decryption parameters that need 
         * to be handled here, but we call the load crypto hook rather 
         * than just changing the visibility
         * of this method to maintain parity with WSDoAllSender.
         */
    }

    /**
     * Get the password callback class and get an instance
     * <p/>
     */
    protected CallbackHandler getPasswordCB(RequestData reqData) 
        throws WSSecurityException {

        Object mc = reqData.getMsgContext();
        CallbackHandler cbHandler = null;
        String callback = getString(WSHandlerConstants.PW_CALLBACK_CLASS, mc);
        if (callback != null) {
            Class cbClass = null;
            try {
                cbClass = 
                    Loader.loadClass(getClassLoader(reqData.getMsgContext()), callback);
            } catch (ClassNotFoundException e) {
                throw new WSSecurityException(
                    "WSHandler: cannot load password callback class: " + callback, e
                );
            }
            try {
                cbHandler = (CallbackHandler) cbClass.newInstance();
            } catch (java.lang.Exception e) {
                throw new WSSecurityException(
                    "WSHandler: cannot create instance of password callback: " + callback, e
                );
            }
        } else {
            cbHandler = 
                (CallbackHandler) getProperty(mc, WSHandlerConstants.PW_CALLBACK_REF);
            if (cbHandler == null) {
                throw new WSSecurityException(
                    "WSHandler: no reference in callback property"
                );
            }
        }
        return cbHandler;
    }

    /**
     * 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
     * @return true if the certificate is trusted, false if not (AxisFault is thrown for exceptions
     * during CertPathValidation)
     * @throws WSSecurityException
     */
    protected boolean verifyTrust(X509Certificate cert, RequestData reqData) 
        throws WSSecurityException {

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

        String[] aliases = null;
        String alias = null;
        X509Certificate[] certs;

        String subjectString = cert.getSubjectX500Principal().getName();
        String issuerString = cert.getIssuerX500Principal().getName();
        BigInteger issuerSerial = cert.getSerialNumber();

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

        // FIRST step
        // Search the keystore for the transmitted certificate

        // Search the keystore for the alias of the transmitted certificate
        try {
            alias = reqData.getSigCrypto().getAliasForX509Cert(issuerString, issuerSerial);
        } catch (WSSecurityException ex) {
            throw new WSSecurityException(
                "WSHandler: Could not get alias for certificate with " + subjectString, ex
            );
        }

        if (alias != null) {
            // Retrieve the certificate for the alias from the keystore
            try {
                certs = reqData.getSigCrypto().getCertificates(alias);
            } catch (WSSecurityException ex) {
                throw new WSSecurityException(
                    "WSHandler: Could not get certificates for alias " + alias, ex
                );
            }

            // If certificates have been found, the certificates must be compared
            // to ensure against phony DNs (compare encoded form including signature)
            if (certs != null && certs.length > 0 && cert.equals(certs[0])) {
                if (doDebug) {
                    log.debug("Direct trust for certificate with " + subjectString);
                }
                return true;
            }
        } else {
            if (doDebug) {
                log.debug(
                    "No alias found for subject from issuer with " + issuerString 
                    + " (serial " + issuerSerial + ")"
                );
            }
        }

        // SECOND step
        // Search for the issuer of the transmitted certificate in the keystore

        // Search the keystore for the alias of the transmitted certificates issuer
        try {
            aliases = reqData.getSigCrypto().getAliasesForDN(issuerString);
        } catch (WSSecurityException ex) {
            throw new WSSecurityException(
                "WSHandler: Could not get alias for certificate with " + issuerString, ex
            );
        }

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

        // THIRD step
        // Check the certificate trust path for every alias of the issuer found in the keystore
        for (int i = 0; i < aliases.length; i++) {
            alias = aliases[i];

            if (doDebug) {
                log.debug(
                    "Preparing to validate certificate path with alias " + alias 
                    + " for issuer " + issuerString
                );
            }

            // Retrieve the certificate(s) for the alias from the keystore
            try {
                certs = reqData.getSigCrypto().getCertificates(alias);
            } catch (WSSecurityException ex) {
                throw new WSSecurityException(
                    "WSHandler: Could not get certificates for alias " + alias, ex
                );
            }

            // If no certificates have been found, there has to be an error:
            // The keystore can find an alias but no certificate(s)
            if (certs == null || certs.length < 1) {
                throw new WSSecurityException(
                    "WSHandler: Could not get certificates for alias " + alias
                );
            }

            // Form a certificate chain from the transmitted certificate
            // and the certificate(s) of the issuer from the keystore
            // First, create new array
            X509Certificate[] x509certs = new X509Certificate[certs.length + 1];
            // Then add the first certificate ...
            x509certs[0] = cert;
            // ... and the other certificates
            for (int j = 0; j < certs.length; j++) {
                x509certs[j + 1] = certs[j];
            }
            certs = x509certs;

            // Use the validation method from the crypto to check whether the subjects' 
            // certificate was really signed by the issuer stated in the certificate
            try {
                if (reqData.getSigCrypto().validateCertPath(certs)) {
                    if (doDebug) {
                        log.debug(
                            "WSHandler: Certificate path has been verified for certificate "
                            + "with subject " + subjectString
                        );
                    }
                    return true;
                }
            } catch (WSSecurityException ex) {
                throw new WSSecurityException(
                    "WSHandler: Certificate path verification failed for certificate "
                    + "with subject " + subjectString, ex
                );
            }
        }

        if (doDebug) {
            log.debug(
                "WSHandler: Certificate path could not be verified for "
                + "certificate with subject " + subjectString
            );
        }
        return false;
    }
    
    /**
     * Evaluate whether the given certificate chain should be trusted.
     * 
     * @param certificates the certificate chain that should be validated against the keystore
     * @return true if the certificate chain is trusted, false if not
     * @throws WSSecurityException
     */
    protected boolean verifyTrust(X509Certificate[] certificates, RequestData reqData) 
        throws WSSecurityException {
        
        // If no certificate was transmitted, do not trust the signature
        if (certificates == null) {
            return false;
        }
        
        String subjectString = certificates[0].getSubjectX500Principal().getName();
        //
        // Use the validation method from the crypto to check whether the subjects' 
        // certificate was really signed by the issuer stated in the certificate
        //
        try {
            if (certificates != null && certificates.length > 1
                && reqData.getSigCrypto().validateCertPath(certificates)) {
                if (log.isDebugEnabled()) {
                    log.debug(
                        "Certificate path has been verified for certificate with subject " 
                        + subjectString
                    );
                }
                return true;
            }
        } catch (WSSecurityException ex) {
            throw new WSSecurityException(
                "WSHandler: Certificate path verification failed for certificate "
                + "with subject " + subjectString, ex
            );
        }
        
        if (log.isDebugEnabled()) {
            log.debug(
                "Certificate path could not be verified for certificate with subject " 
                + subjectString
            );
        }
            
        return false;
    }

    /**
     * Evaluate whether a timestamp is considered valid on the receivers' side. Hook to
     * allow subclasses to implement custom validation methods however they see fit.
     * 
     * Policy used in this implementation:
     * 
     * 1. The receiver can set its own time to live (besides from that set on
     * sender side)
     * 
     * 2. If the message was created before (now-ttl) the message is rejected
     * 
     * @param timestamp
     *            the timestamp that is validated
     * @param timeToLive
     *            the limit on the receivers' side, that the timestamp is validated against
     * @return true if the timestamp is before (now-timeToLive), false otherwise
     * @throws WSSecurityException
     */
    protected boolean verifyTimestamp(Timestamp timestamp, int timeToLive) throws WSSecurityException {

        // Calculate the time that is allowed for the message to travel
        Calendar validCreation = Calendar.getInstance();
        Calendar cre = timestamp.getCreated();
        if (cre != null && cre.after(validCreation)) {
            if (doDebug) {
                log.debug("Validation of Timestamp: The message was created in the future!");
            }
            return false;
        }
        long currentTime = validCreation.getTime().getTime();
        currentTime -= timeToLive * 1000;
        validCreation.setTime(new Date(currentTime));

        if (doDebug) {
            log.debug("Preparing to verify the timestamp");
            DateFormat zulu = new XmlSchemaDateFormat();
            log.debug("Validation of Timestamp: Current time is "
                    + zulu.format(Calendar.getInstance().getTime()));
            log.debug("Validation of Timestamp: Valid creation is "
                    + zulu.format(validCreation.getTime()));
            if (timestamp.getCreated() != null) {
                log.debug("Validation of Timestamp: Timestamp created is "
                        + zulu.format(timestamp.getCreated().getTime()));
            }
        }
        // Validate the time it took the message to travel
        // if (timestamp.getCreated().before(validCreation) ||
        // !timestamp.getCreated().equals(validCreation)) {
        if (cre != null && !cre.after(validCreation)) {
            if (doDebug) {
                log.debug("Validation of Timestamp: The message was created too long ago");
            }
            return false;
        }

        if (doDebug) {
            log.debug("Validation of Timestamp: Everything is ok");
        }
        return true;
    }

    /**
     * Looks up key first via {@link #getOption(String)} and if not found
     * there, via {@link #getProperty(Object, String)}
     *
     * @param key the key to search for. May not be null.
     * @param mc the message context to search. 
     * @return the value found.
     * @throws IllegalArgumentException if <code>key</code> is null.
     */
    public String getString(String key, Object mc) { 
        if (key == null) {
            throw new IllegalArgumentException("Key cannot be null");
        }
        String s = getStringOption(key);
        if (s != null) {
            return s;
        }
        if (mc == null) {
            throw new IllegalArgumentException("Message context cannot be null");
        }
        return (String) getProperty(mc, key);
    }


    /**
     * Returns the option on <code>name</code>.
     *
     * @param key the non-null key of the option.
     * @return the option on <code>key</code> if <code>key</code>
     *  exists and is of type java.lang.String; otherwise null.
     */
    public String getStringOption(String key) {
        Object o = getOption(key);
        if (o instanceof String){
            return (String) o;
        } else {
            return null;
        }
    }

    /**
     * Returns the classloader to be used for loading the callback class
     * @param msgCtx The MessageContext 
     * @return class loader
     */
    public ClassLoader getClassLoader(Object msgCtx) {
        try {
            return Loader.getTCL();
        } catch (Throwable t) {
            return null;
        }
    }

    public abstract Object getOption(String key);
    public abstract Object getProperty(Object msgContext, String key);

    public abstract void setProperty(Object msgContext, String key, 
            Object value);


    public abstract String getPassword(Object msgContext);

    public abstract void setPassword(Object msgContext, String password);
}
