// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package org.dogtagpki.server.tks.servlet;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.MGF1ParameterSpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.StringTokenizer;

import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dogtagpki.server.authorization.AuthzToken;
import org.dogtagpki.server.connector.IRemoteRequest;
import org.dogtagpki.server.tks.TKSEngine;
import org.dogtagpki.server.tks.TKSEngineConfig;
import org.dogtagpki.server.tks.TPSConnectorConfig;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.NotInitializedException;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.crypto.KeyWrapAlgorithm;
import org.mozilla.jss.crypto.KeyWrapper;
import org.mozilla.jss.crypto.SymmetricKey;
import org.mozilla.jss.crypto.X509Certificate;
import org.mozilla.jss.pkcs11.PK11SymKey;

import com.netscape.certsrv.authentication.IAuthToken;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.IConfigStore;
import com.netscape.certsrv.base.SessionContext;
import com.netscape.certsrv.logging.AuditEvent;
import com.netscape.certsrv.logging.ILogger;
import com.netscape.certsrv.logging.event.ComputeRandomDataRequestProcessedEvent;
import com.netscape.certsrv.logging.event.ComputeSessionKeyRequestProcessedEvent;
import com.netscape.certsrv.logging.event.DiversifyKeyRequestProcessedEvent;
import com.netscape.certsrv.logging.event.EncryptDataRequestProcessedEvent;
import com.netscape.cms.servlet.base.CMSServlet;
import com.netscape.cms.servlet.common.CMSRequest;
import com.netscape.cmscore.apps.CMS;
import com.netscape.cmscore.cert.PrettyPrintFormat;
import com.netscape.cmscore.security.JssSubsystem;
import com.netscape.cmsutil.crypto.CryptoUtil;
import com.netscape.symkey.SessionKey;

/**
 * A class representings an administration servlet for Token Key
 * Service Authority. This servlet is responsible to serve
 * tks administrative operation such as configuration
 * parameter updates.
 *
 * @version $Revision$, $Date$
 */
public class TokenServlet extends CMSServlet {

    public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(TokenServlet.class);

    private static final long serialVersionUID = 8687436109695172791L;
    protected static final String PROP_ENABLED = "enabled";
    protected static final String TRANSPORT_KEY_NAME = "sharedSecret";
    private final static String INFO = "TokenServlet";
    public static int ERROR = 1;
    String mKeyNickName = null;
    String mNewKeyNickName = null;
    String mCurrentUID = null;
    PrettyPrintFormat pp = new PrettyPrintFormat(":");

    // Derivation Constants for SCP02
    public final static byte[] C_MACDerivationConstant = { (byte) 0x01, (byte) 0x01 };
    public final static byte[] ENCDerivationConstant = { 0x01, (byte) 0x82 };
    public final static byte[] DEKDerivationConstant = { 0x01, (byte) 0x81 };
    public final static byte[] R_MACDerivationConstant = { 0x01, 0x02 };

    /**
     * Constructs tks servlet.
     */
    public TokenServlet() {
        super();

    }

    public static String trim(String a) {
        StringBuffer newa = new StringBuffer();
        StringTokenizer tokens = new StringTokenizer(a, "\n");
        while (tokens.hasMoreTokens()) {
            newa.append(tokens.nextToken());
        }
        return newa.toString();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    /**
     * Returns serlvet information.
     *
     * @return name of this servlet
     */
    @Override
    public String getServletInfo() {
        return INFO;
    }

    /**
     * Process the HTTP request.
     *
     * @param s The URL to decode.
     */
    protected String URLdecode(String s) {
        if (s == null)
            return null;
        ByteArrayOutputStream out = new ByteArrayOutputStream(s.length());

        for (int i = 0; i < s.length(); i++) {
            int c = s.charAt(i);

            if (c == '+') {
                out.write(' ');
            } else if (c == '%') {
                int c1 = Character.digit(s.charAt(++i), 16);
                int c2 = Character.digit(s.charAt(++i), 16);

                out.write((char) (c1 * 16 + c2));
            } else {
                out.write(c);
            }
        } // end for
        return out.toString();
    }

    private void setDefaultSlotAndKeyName(HttpServletRequest req) {
        try {

            String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
            if (keySet == null || keySet.equals("")) {
                keySet = "defKeySet";
            }
            logger.debug("keySet selected: " + keySet);

            TKSEngine engine = TKSEngine.getInstance();
            TKSEngineConfig config = engine.getConfig();
            String masterKeyPrefix = config.getString("tks.master_key_prefix", null);
            String temp = req.getParameter(IRemoteRequest.TOKEN_KEYINFO); //#xx#xx
            String keyInfoMap = "tks." + keySet + ".mk_mappings." + temp;
            String mappingValue = config.getString(keyInfoMap, null);
            if (mappingValue != null) {
                StringTokenizer st = new StringTokenizer(mappingValue, ":");
                int tokenNumber = 0;
                while (st.hasMoreTokens()) {

                    String currentToken = st.nextToken();
                    if (tokenNumber == 1)
                        mKeyNickName = currentToken;
                    tokenNumber++;

                }
            }
            if (req.getParameter(IRemoteRequest.TOKEN_NEW_KEYINFO) != null) // for diversification
            {
                temp = req.getParameter(IRemoteRequest.TOKEN_NEW_KEYINFO); //#xx#xx
                String newKeyInfoMap = "tks." + keySet + ".mk_mappings." + temp;
                String newMappingValue = config.getString(newKeyInfoMap, null);
                if (newMappingValue != null) {
                    StringTokenizer st = new StringTokenizer(newMappingValue, ":");
                    int tokenNumber = 0;
                    while (st.hasMoreTokens()) {
                        String currentToken = st.nextToken();
                        if (tokenNumber == 1)
                            mNewKeyNickName = currentToken;
                        tokenNumber++;

                    }
                }
            }

            logger.debug("Setting masteter keky prefix to: " + masterKeyPrefix);

            SecureChannelProtocol.setDefaultPrefix(masterKeyPrefix);
            /*SessionKey.SetDefaultPrefix(masterKeyPrefix);*/

        } catch (Exception e) {
            logger.warn("Exception in TokenServlet::setDefaultSlotAndKeyName: " + e.getMessage(), e);
        }

    }

    // AC: KDF SPEC CHANGE - read new setting value from config file
    // (This value allows configuration of which master keys use the NIST SP800-108 KDF and which use the original KDF for backwards compatibility)
    // CAREFUL:  Result returned may be negative due to java's lack of unsigned types.
    //           Negative values need to be treated as higher key numbers than positive key numbers.
    private static byte read_setting_nistSP800_108KdfOnKeyVersion(String keySet) throws Exception {
        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        String nistSP800_108KdfOnKeyVersion_map = "tks." + keySet + ".nistSP800-108KdfOnKeyVersion";
        // KDF phase1: default to 00
        String nistSP800_108KdfOnKeyVersion_value =
                config.getString(nistSP800_108KdfOnKeyVersion_map, "00" /*null*/);
        short nistSP800_108KdfOnKeyVersion_short = 0;
        // if value does not exist in file
        if (nistSP800_108KdfOnKeyVersion_value == null) {
            // throw
            //  (we want admins to pay attention to this configuration item rather than guessing for them)
            throw new Exception("Required configuration value \"" + nistSP800_108KdfOnKeyVersion_map
                    + "\" missing from configuration file.");
        }
        // convert setting value (in ASCII-hex) to short
        try {
            nistSP800_108KdfOnKeyVersion_short = Short.parseShort(nistSP800_108KdfOnKeyVersion_value, 16);
            if ((nistSP800_108KdfOnKeyVersion_short < 0) || (nistSP800_108KdfOnKeyVersion_short > (short) 0x00FF)) {
                throw new Exception("Out of range.");
            }
        } catch (Throwable t) {
            throw new Exception("Configuration value \"" + nistSP800_108KdfOnKeyVersion_map
                    + "\" is in incorrect format. " +
                    "Correct format is \"" + nistSP800_108KdfOnKeyVersion_map
                    + "=xx\" where xx is key version specified in ASCII-HEX format.", t);
        }
        // convert to byte (anything higher than 0x7F is represented as a negative)
        byte nistSP800_108KdfOnKeyVersion_byte = (byte) nistSP800_108KdfOnKeyVersion_short;
        return nistSP800_108KdfOnKeyVersion_byte;
    }

    // AC: KDF SPEC CHANGE - read new setting value from config file
    // (This value allows configuration of the NIST SP800-108 KDF:
    //   If "true" we use the CUID parameter within the NIST SP800-108 KDF.
    //   If "false" we use the KDD parameter within the NIST SP800-108 KDF.
    private static boolean read_setting_nistSP800_108KdfUseCuidAsKdd(String keySet) throws Exception {
        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        String setting_map = "tks." + keySet + ".nistSP800-108KdfUseCuidAsKdd";
        // KDF phase1: default to "false"
        String setting_str = config.getString(setting_map, "false" /*null*/);
        boolean setting_boolean = false;
        // if value does not exist in file
        if (setting_str == null) {
            // throw
            //  (we want admins to pay attention to this configuration item rather than guessing for them)
            throw new Exception("Required configuration value \"" + setting_map + "\" missing from configuration file.");
        }
        // convert setting value to boolean
        try {
            setting_boolean = Boolean.parseBoolean(setting_str);
        } catch (Throwable t) {
            throw new Exception("Configuration value \"" + setting_map
                    + "\" is in incorrect format.  Should be either \"true\" or \"false\".", t);
        }
        return setting_boolean;
    }

    // AC: KDF SPEC CHANGE - Audit logging helper functions.
    // Converts a byte array to an ASCII-hex string.
    //   We implemented this ourselves rather than using this.pp.toHexArray() because
    //   the team preferred CUID and KDD strings to be without ":" separators every byte.
    final char[] bytesToHex_hexArray = "0123456789ABCDEF".toCharArray();

    private String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int i = 0; i < bytes.length; i++) {
            int thisChar = bytes[i] & 0x000000FF;
            hexChars[i * 2] = bytesToHex_hexArray[thisChar >>> 4]; // div 16
            hexChars[i * 2 + 1] = bytesToHex_hexArray[thisChar & 0x0F];
        }
        return new String(hexChars);
    }

    // AC: KDF SPEC CHANGE - Audit logging helper functions.
    // Safely converts a keyInfo byte array to a Key version hex string in the format: 0xa
    // Since key version is always the first byte, this function returns the unsigned hex string representation of parameter[0].
    //   Returns "null" if parameter is null.
    //   Returns "invalid" if parameter.length < 1
    private String log_string_from_keyInfo(byte[] xkeyInfo) {
        return (xkeyInfo == null) ? "null" : (xkeyInfo.length < 1 ? "invalid" : "0x"
                + Integer.toHexString((xkeyInfo[0]) & 0x000000FF));
    }

    // AC: KDF SPEC CHANGE - Audit logging helper functions.
    // Safely converts a byte array containing specialDecoded information to an ASCII-hex string.
    // Parameters:
    //   specialDecoded - byte array containing data.  May be null.
    // Returns:
    //   if specialDecoded is blank, returns "null"
    //   if specialDecoded != null, returns <ASCII-HEX string representation of specialDecoded>
    private String log_string_from_specialDecoded_byte_array(byte[] specialDecoded) {
        if (specialDecoded == null) {
            return "null";
        } else {
            return bytesToHex(specialDecoded);
        }
    }

    /* Compute Session Key for SCP02
     *  For simplicity compute just one session key,unless it is the DEK key case.
     */

    private void processComputeSessionKeySCP02(HttpServletRequest req, HttpServletResponse resp) throws EBaseException {

        logger.debug("TokenServlet.processComputeSessionKeySCP02 entering..");
        String auditMessage = null;
        String errorMsg = "";
        String badParams = "";
        String transportKeyName = "";
        boolean missingParam = false;
        String selectedToken = null;
        String keyNickName = null;
        byte[] drm_trans_wrapped_desKey = null;

        byte[] xKDD = null;
        byte nistSP800_108KdfOnKeyVersion = (byte) 0xff;
        boolean nistSP800_108KdfUseCuidAsKdd = false;

        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();

        boolean isCryptoValidate = false;
        byte[] keyInfo, xCUID = null, session_key = null;

        Exception missingSettingException = null;

        String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);

        String rKDD = req.getParameter(IRemoteRequest.TOKEN_KDD);

        String rKeyInfo = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);

        if ((rKeyInfo == null) || (rKeyInfo.equals(""))) {
            badParams += " KeyInfo,";
            logger.debug("TokenServlet: processComputeSessionKeySCP02(): missing request parameter: key info");
            missingParam = true;
        }

        keyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);

        String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);

        if (keySet == null || keySet.equals("")) {
            keySet = "defKeySet";
        }
        logger.debug("TokenServlet.processComputeSessionKeySCP02: keySet selected: " + keySet + " keyInfo: " + rKeyInfo);

        boolean serversideKeygen = false;

        String rDerivationConstant = req.getParameter(IRemoteRequest.DERIVATION_CONSTANT);
        String rSequenceCounter = req.getParameter(IRemoteRequest.SEQUENCE_COUNTER);

        if ((rDerivationConstant == null) || (rDerivationConstant.equals(""))) {
            badParams += " derivation_constant,";
            logger.debug("TokenServlet.processComputeSessionKeySCP02(): missing request parameter: derivation constant.");
            missingParam = true;
        }

        if ((rSequenceCounter == null) || (rSequenceCounter.equals(""))) {
            badParams += " sequence_counter,";
            logger.debug("TokenServlet.processComputeSessionKeySCP02(): missing request parameter: sequence counter.");
            missingParam = true;
        }

        SessionContext sContext = SessionContext.getContext();

        String agentId = "";
        if (sContext != null) {
            agentId =
                    (String) sContext.get(SessionContext.USER_ID);
        }

        auditMessage = CMS.getLogMessage(
                AuditEvent.COMPUTE_SESSION_KEY_REQUEST,
                rCUID,
                rKDD, // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                ILogger.SUCCESS,
                agentId);

        audit(auditMessage);

        if (!missingParam) {
            xCUID = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rCUID);

            if (xCUID == null || xCUID.length != 10) {
                badParams += " CUID length,";
                logger.debug("TokenServlet.processCompureSessionKeySCP02: Invalid CUID length");
                missingParam = true;
            }

            if ((rKDD == null) || (rKDD.length() == 0)) {
                logger.debug("TokenServlet.processComputeSessionKeySCP02(): missing request parameter: KDD");
                badParams += " KDD,";
                missingParam = true;
            }

            xKDD = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKDD);
            if (xKDD == null || xKDD.length != 10) {
                badParams += " KDD length,";
                logger.debug("TokenServlet.processComputeSessionKeySCP02: Invalid KDD length");
                missingParam = true;
            }

            keyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);
            if (keyInfo == null || keyInfo.length != 2) {
                badParams += " KeyInfo length,";
                logger.debug("TokenServlet.processComputeSessionKeySCP02: Invalid key info length.");
                missingParam = true;
            }

            try {
                nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
                nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);

                // log settings read in to debug log along with xkeyInfo
                logger.debug("TokenServlet: ComputeSessionKeySCP02():  keyInfo[0] = 0x"
                        + Integer.toHexString((keyInfo[0]) & 0x0000000FF)
                        + ",  xkeyInfo[1] = 0x"
                        + Integer.toHexString((keyInfo[1]) & 0x0000000FF)
                        );
                logger.debug("TokenServlet: ComputeSessionKeySCP02():  Nist SP800-108 KDF will be used for key versions >= 0x"
                        + Integer.toHexString((nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
                        );
                if (nistSP800_108KdfUseCuidAsKdd == true) {
                    logger.debug("TokenServlet: ComputeSessionKeySCP02():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
                } else {
                    logger.debug("TokenServlet: ComputeSessionKeySCP02():  Nist SP800-108 KDF (if used) will use KDD.");
                }
                // conform to the set-an-error-flag mentality
            } catch (Exception e) {
                missingSettingException = e;
                logger.warn("TokenServlet: Exception reading Nist SP800-108 KDF config values: " + e.getMessage(), e);
            }

        }

        String keyInfoMap = "tks." + keySet + ".mk_mappings." + rKeyInfo; //#xx#xx
        String mappingValue = config.getString(keyInfoMap, null);
        if (mappingValue == null) {
            selectedToken =
                    config.getString("tks.defaultSlot", CryptoUtil.INTERNAL_TOKEN_NAME);
            keyNickName = rKeyInfo;
        } else {
            StringTokenizer st = new StringTokenizer(mappingValue, ":");
            if (st.hasMoreTokens())
                selectedToken = st.nextToken();
            if (st.hasMoreTokens())
                keyNickName = st.nextToken();
        }

        keyInfoMap = "tks." + keySet + ".mk_mappings." + rKeyInfo; //#xx#xx
        try {
            mappingValue = config.getString(keyInfoMap, null);
        } catch (EBaseException e1) {
            logger.warn("TokenServlet: " + e1.getMessage(), e1);
        }
        if (mappingValue == null) {
            try {
                selectedToken =
                        config.getString("tks.defaultSlot", CryptoUtil.INTERNAL_TOKEN_NAME);
            } catch (EBaseException e) {
                logger.warn("TokenServlet: " + e.getMessage(), e);
            }
            keyNickName = rKeyInfo;
        } else {
            StringTokenizer st = new StringTokenizer(mappingValue, ":");
            if (st.hasMoreTokens())
                selectedToken = st.nextToken();
            if (st.hasMoreTokens())
                keyNickName = st.nextToken();
        }

        logger.debug("TokenServlet: processComputeSessionKeySCP02(): final keyNickname: " + keyNickName);
        String useSoftToken_s = null;
        try {
            useSoftToken_s = config.getString("tks.useSoftToken", "true");
        } catch (EBaseException e1) {
            logger.warn("TokenServlet: " + e1.getMessage(), e1);
        }
        if (!useSoftToken_s.equalsIgnoreCase("true"))
            useSoftToken_s = "false";

        String rServersideKeygen = req.getParameter(IRemoteRequest.SERVER_SIDE_KEYGEN);
        if (rServersideKeygen.equals("true")) {
            logger.debug("TokenServlet.processComputeSessionKeySCP02: serversideKeygen requested");
            serversideKeygen = true;
        } else {
            logger.debug("TokenServlet.processComputeSessionKeySCP02: serversideKeygen not requested");
        }

        transportKeyName = null;
        try {
            transportKeyName = getSharedSecretName(config);
        } catch (EBaseException e1) {
            logger.warn("TokenServlet: Can't find transport key name: " + e1.getMessage(), e1);
        }

        logger.debug("TokenServlet: processComputeSessionKeySCP02(): tksSharedSymKeyName: " + transportKeyName);

        try {
            isCryptoValidate = config.getBoolean("cardcryptogram.validate.enable", true);
        } catch (EBaseException eee) {
        }

        byte macKeyArray[] = null;
        byte sequenceCounter[] = null;
        byte derivationConstant[] = null;

        boolean errorFound = false;

        String dek_wrapped_desKeyString = null;
        String keycheck_s = null;

        if (selectedToken != null && keyNickName != null && transportKeyName != null && missingSettingException == null) {
            try {
                macKeyArray = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                        + keySet + ".mac_key"));

                sequenceCounter = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rSequenceCounter);
                derivationConstant = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rDerivationConstant);

                //Use old style for the moment.
                //ToDo: We need to use the nistXP800 params we have collected and send them down to symkey
                //Perform in next ticket to fully implement nistXP800

                session_key = SessionKey.ComputeSessionKeySCP02(
                        selectedToken, keyNickName,
                        keyInfo,
                        nistSP800_108KdfOnKeyVersion, // AC: KDF SPEC CHANGE - pass in configuration file value
                        nistSP800_108KdfUseCuidAsKdd, xCUID, xKDD, macKeyArray, sequenceCounter, derivationConstant,
                        useSoftToken_s, keySet,
                        transportKeyName);

                if (session_key == null) {
                    logger.warn("TokenServlet.computeSessionKeySCP02:Tried ComputeSessionKey, got NULL ");
                    throw new EBaseException("Can't compute session key for SCP02!");

                }

                //Only do this for the dekSessionKey and if we are in the server side keygen case.
                if (derivationConstant[0] == DEKDerivationConstant[0]
                        && derivationConstant[1] == DEKDerivationConstant[1] && serversideKeygen == true) {

                    logger.debug("TokenServlet.computeSessionKeySCP02: We have the server side keygen case while generating the dek session key, wrap and return symkeys for the drm and token.");

                    /**
                     * 0. generate des key
                     * 1. encrypt des key with dek key
                     * 2. encrypt des key with DRM transport key
                     * These two wrapped items are to be sent back to
                     * TPS. 2nd item is to DRM
                     **/

                    PK11SymKey desKey = null;
                    PK11SymKey dekKey = null;

                    /*generate it on whichever token the master key is at*/
                    if (useSoftToken_s.equals("true")) {
                        logger.debug("TokenServlet.computeSessionKeySCP02: key encryption key generated on internal");

                        desKey = SessionKey.GenerateSymkey(CryptoUtil.INTERNAL_TOKEN_NAME);

                    } else {
                        logger.debug("TokenServlet.computeSessionKeySCP02: key encryption key generated on "
                                + selectedToken);
                        desKey = SessionKey.GenerateSymkey(selectedToken);
                    }
                    if (desKey != null)
                        logger.debug("TokenServlet.computeSessionKeySCP02: key encryption key generated for " + rCUID);
                    else {
                        logger.error("TokenServlet.computeSessionKeySCP02: key encryption key generation failed for "
                                + rCUID);
                        throw new EBaseException(
                                "TokenServlet.computeSessionKeySCP02: can't generate key encryption key");
                    }

                    CryptoToken token = null;
                    if (useSoftToken_s.equals("true")) {
                        token = CryptoUtil.getCryptoToken(null);
                    } else {
                        token = CryptoUtil.getCryptoToken(selectedToken);
                    }

                    //Now we have to create a sym key object for the wrapped session_key (dekKey)
                    // session_key wrapped by the shared Secret

                    PK11SymKey sharedSecret = getSharedSecretKey();

                    if (sharedSecret == null) {
                        throw new EBaseException(
                                "TokenServlet.computeSessionKeySCP02: Can't find share secret sym key!");
                    }

                    dekKey = SessionKey.UnwrapSessionKeyWithSharedSecret(token.getName(), sharedSecret,
                            session_key);

                    if (dekKey == null) {
                        throw new EBaseException(
                                "TokenServlet.computeSessionKeySCP02: Can't unwrap DEK key onto the token!");
                    }

                    /*
                     * ECBencrypt actually takes the 24 byte DES2 key
                     * and discard the last 8 bytes before it encrypts.
                     * This is done so that the applet can digest it
                     */
                    byte[] encDesKey =
                            SessionKey.ECBencrypt(dekKey,
                                    desKey);

                    if (encDesKey == null) {
                        throw new EBaseException("TokenServlet.computeSessionKeySCP02: Can't encrypt DEK key!");
                    }

                    dek_wrapped_desKeyString =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(encDesKey);

                    byte[] keycheck =
                            SessionKey.ComputeKeyCheck(desKey);

                    if (keycheck == null) {
                        throw new EBaseException(
                                "TokenServlet.computeSessionKeySCP02: Can't compute key check for encrypted DEK key!");
                    }

                    keycheck_s =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(keycheck);

                    //use DRM transport cert to wrap desKey
                    String drmTransNickname = config.getString("tks.drm_transport_cert_nickname", "");

                    if ((drmTransNickname == null) || (drmTransNickname == "")) {
                        logger.error("TokenServlet.computeSessionKeySCP02:did not find DRM transport certificate nickname");
                        throw new EBaseException("can't find DRM transport certificate nickname");
                    } else {
                        logger.debug("TokenServlet.computeSessionKeySCP02:drmtransport_cert_nickname=" + drmTransNickname);
                    }

                    X509Certificate drmTransCert = null;
                    drmTransCert = CryptoManager.getInstance().findCertByNickname(drmTransNickname);
                    // wrap kek session key with DRM transport public key

                    PublicKey pubKey = drmTransCert.getPublicKey();
                    String pubKeyAlgo = pubKey.getAlgorithm();

                    KeyWrapper keyWrapper = null;
                    //For wrapping symmetric keys don't need IV, use ECB
                    if (pubKeyAlgo.equals("EC")) {
                        keyWrapper = token.getKeyWrapper(KeyWrapAlgorithm.AES_ECB);
                        keyWrapper.initWrap(pubKey, null);
                    } else {
                        boolean useOAEP = config.getBoolean("keyWrap.useOAEP",false);
                        KeyWrapAlgorithm wrapAlg = KeyWrapAlgorithm.RSA;
                        if(useOAEP == true) {
                            wrapAlg = KeyWrapAlgorithm.RSA_OAEP;
                        }
                        keyWrapper = token.getKeyWrapper(wrapAlg);

                        OAEPParameterSpec params =  null;
                        if(useOAEP) {
                            params = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
                        }
                        keyWrapper.initWrap(pubKey, params);
                    }

                    drm_trans_wrapped_desKey = keyWrapper.wrap(desKey);
                    logger.debug("computeSessionKey.computeSessionKeySCP02:desKey wrapped with drm transportation key.");

                    logger.debug("computeSessionKey.computeSessionKeySCP02:desKey: Just unwrapped the dekKey onto the token to be wrapped on the way out.");

                }

            } catch (Exception e) {
                logger.warn("TokenServlet.computeSessionKeySCP02 Computing Session Key: " + e.getMessage(), e);
                errorFound = true;

            }

        }

        String status = "0";
        String value = "";
        String outputString = "";

        boolean statusDeclared = false;

        if (session_key != null && session_key.length > 0 && errorFound == false) {
            outputString =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(session_key);
        } else {

            status = "1";
            statusDeclared = true;
        }

        if (selectedToken == null || keyNickName == null) {
            if (!statusDeclared) {
                status = "4";
                statusDeclared = true;
            }
        }

        if (missingSettingException != null) {
            if (!statusDeclared) {
                status = "6";
                statusDeclared = true;
            }
        }

        if (missingParam) {
            status = "3";
        }

        String drm_trans_wrapped_desKeyString = null;

        if (!status.equals("0")) {
            if (status.equals("1")) {
                errorMsg = "Problem generating session key info.";
            }

            if (status.equals("4")) {
                errorMsg = "Problem obtaining token information.";
            }

            if (status.equals("3")) {
                if (badParams.endsWith(",")) {
                    badParams = badParams.substring(0, badParams.length() - 1);
                }
                errorMsg = "Missing input parameters :" + badParams;
            }

            if (status.equals("6")) {
                errorMsg = "Problem reading required configuration value.";
            }

        } else {

            if (serversideKeygen == true) {

                if (drm_trans_wrapped_desKey != null && drm_trans_wrapped_desKey.length > 0) {
                    drm_trans_wrapped_desKeyString =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(drm_trans_wrapped_desKey);
                }

                StringBuffer sb = new StringBuffer();
                sb.append(IRemoteRequest.RESPONSE_STATUS + "=0&");
                sb.append(IRemoteRequest.TKS_RESPONSE_SessionKey + "=");
                sb.append(outputString);

                //Now add the trans wrapped des key

                if (drm_trans_wrapped_desKeyString != null) {
                    sb.append("&" + IRemoteRequest.TKS_RESPONSE_DRM_Trans_DesKey + "=");
                    sb.append(drm_trans_wrapped_desKeyString);
                }

                if (dek_wrapped_desKeyString != null) {
                    sb.append("&" + IRemoteRequest.TKS_RESPONSE_KEK_DesKey + "=");
                    sb.append(dek_wrapped_desKeyString);
                }

                if (keycheck_s != null) {
                    sb.append("&" + IRemoteRequest.TKS_RESPONSE_KeyCheck + "=");
                    sb.append(keycheck_s);
                }

                value = sb.toString();
            } else {
                StringBuffer sb = new StringBuffer();
                sb.append(IRemoteRequest.RESPONSE_STATUS + "=0&");
                sb.append(IRemoteRequest.TKS_RESPONSE_SessionKey + "=");
                sb.append(outputString);
                value = sb.toString();
            }

        }

        //logger.debug("TokenServlet:outputString.encode " + value);

        try {
            resp.setContentLength(value.length());
            logger.debug("TokenServlet:outputString.length " + value.length());
            OutputStream ooss = resp.getOutputStream();
            ooss.write(value.getBytes());
            ooss.flush();
            mRenderResult = false;
        } catch (IOException e) {
            logger.warn("TokenServlet: " + e.getMessage(), e);
        }

        if (status.equals("0")) {

            ComputeSessionKeyRequestProcessedEvent event = ComputeSessionKeyRequestProcessedEvent.success(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    isCryptoValidate ? "true" : "false", // IsCryptoValidate
                    serversideKeygen ? "true" : "false", // IsServerSideKeygen
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(keyInfo), // KeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd) // NistSP800_108KdfUseCuidAsKdd
            );

            signedAuditLogger.log(event);;

        } else {

            ComputeSessionKeyRequestProcessedEvent event = ComputeSessionKeyRequestProcessedEvent.failure(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    isCryptoValidate ? "true" : "false", // IsCryptoValidate
                    serversideKeygen ? "true" : "false", // IsServerSideKeygen
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(keyInfo), // KeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd), // NistSP800_108KdfUseCuidAsKdd
                    errorMsg // Error
            );

            signedAuditLogger.log(event);
        }
    }

    private void processComputeSessionKey(HttpServletRequest req,
            HttpServletResponse resp) throws EBaseException {
        byte[] card_challenge, host_challenge, keyInfo, xCUID, session_key, xKDD; // AC: KDF SPEC CHANGE: removed duplicative 'CUID' variable and added xKDD

        // AC: KDF SPEC CHANGE - new config file values (needed for symkey)
        byte nistSP800_108KdfOnKeyVersion = (byte) 0xff;
        boolean nistSP800_108KdfUseCuidAsKdd = false;

        byte[] card_crypto, host_cryptogram, input_card_crypto;
        byte[] xcard_challenge, xhost_challenge;
        byte[] enc_session_key, xkeyInfo;
        String auditMessage = null;
        String errorMsg = "";
        String badParams = "";
        String transportKeyName = "";
        String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);

        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
        String rKDD = req.getParameter("KDD");
        if ((rKDD == null) || (rKDD.length() == 0)) {
            // KDF phase1: default to rCUID if not present
            logger.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
            rKDD = rCUID;
        }

        String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
        if (keySet == null || keySet.equals("")) {
            keySet = "defKeySet";
        }
        logger.debug("keySet selected: " + keySet);

        boolean serversideKeygen = false;
        byte[] drm_trans_wrapped_desKey = null;
        SymmetricKey desKey = null;
        //        PK11SymKey kek_session_key;
        SymmetricKey kek_key;

        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        boolean isCryptoValidate = true;
        boolean missingParam = false;

        // AC: KDF SPEC CHANGE - flag for if there is an error reading our new setting
        Exception missingSetting_exception = null;

        session_key = null;
        card_crypto = null;
        host_cryptogram = null;
        enc_session_key = null;
        //        kek_session_key = null;

        SessionContext sContext = SessionContext.getContext();

        String agentId = "";
        if (sContext != null) {
            agentId =
                    (String) sContext.get(SessionContext.USER_ID);
        }

        // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID
        auditMessage = CMS.getLogMessage(
                AuditEvent.COMPUTE_SESSION_KEY_REQUEST,
                rCUID,
                rKDD, // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                ILogger.SUCCESS,
                agentId);

        audit(auditMessage);

        String kek_wrapped_desKeyString = null;
        String keycheck_s = null;

        logger.debug("processComputeSessionKey:");
        String useSoftToken_s = config.getString("tks.useSoftToken", "true");
        if (!useSoftToken_s.equalsIgnoreCase("true"))
            useSoftToken_s = "false";

        String rServersideKeygen = req.getParameter(IRemoteRequest.SERVER_SIDE_KEYGEN);
        if (rServersideKeygen.equals("true")) {
            logger.debug("TokenServlet: serversideKeygen requested");
            serversideKeygen = true;
        } else {
            logger.debug("TokenServlet: serversideKeygen not requested");
        }

        try {
            isCryptoValidate = config.getBoolean("cardcryptogram.validate.enable", true);
        } catch (EBaseException eee) {
        }

        transportKeyName = getSharedSecretName(config);

        String rcard_challenge = req.getParameter(IRemoteRequest.TOKEN_CARD_CHALLENGE);
        String rhost_challenge = req.getParameter(IRemoteRequest.TOKEN_HOST_CHALLENGE);
        String rKeyInfo = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
        String rcard_cryptogram = req.getParameter(IRemoteRequest.TOKEN_CARD_CRYPTOGRAM);
        if ((rCUID == null) || (rCUID.equals(""))) {
            logger.debug("TokenServlet: ComputeSessionKey(): missing request parameter: CUID");
            badParams += " CUID,";
            missingParam = true;
        }

        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
        if ((rKDD == null) || (rKDD.length() == 0)) {
            logger.debug("TokenServlet: ComputeSessionKey(): missing request parameter: KDD");
            badParams += " KDD,";
            missingParam = true;
        }

        if ((rcard_challenge == null) || (rcard_challenge.equals(""))) {
            badParams += " card_challenge,";
            logger.debug("TokenServlet: ComputeSessionKey(): missing request parameter: card challenge");
            missingParam = true;
        }

        if ((rhost_challenge == null) || (rhost_challenge.equals(""))) {
            badParams += " host_challenge,";
            logger.debug("TokenServlet: ComputeSessionKey(): missing request parameter: host challenge");
            missingParam = true;
        }

        if ((rKeyInfo == null) || (rKeyInfo.equals(""))) {
            badParams += " KeyInfo,";
            logger.debug("TokenServlet: ComputeSessionKey(): missing request parameter: key info");
            missingParam = true;
        }

        String selectedToken = null;
        String keyNickName = null;
        boolean sameCardCrypto = true;

        // AC: KDF SPEC CHANGE
        xCUID = null; // avoid errors about non-initialization
        xKDD = null; // avoid errors about non-initialization
        xkeyInfo = null; // avoid errors about non-initialization

        if (!missingParam) {

            xCUID = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rCUID);
            if (xCUID == null || xCUID.length != 10) {
                badParams += " CUID length,";
                logger.debug("TokenServlet: Invalid CUID length");
                missingParam = true;
            }

            // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
            xKDD = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKDD);
            if (xKDD == null || xKDD.length != 10) {
                badParams += " KDD length,";
                logger.debug("TokenServlet: Invalid KDD length");
                missingParam = true;
            }

            xkeyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);
            if (xkeyInfo == null || xkeyInfo.length != 2) {
                badParams += " KeyInfo length,";
                logger.debug("TokenServlet: Invalid key info length.");
                missingParam = true;
            }
            xcard_challenge =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rcard_challenge);
            if (xcard_challenge == null || xcard_challenge.length != 8) {
                badParams += " card_challenge length,";
                logger.debug("TokenServlet: Invalid card challenge length.");
                missingParam = true;
            }

            xhost_challenge = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rhost_challenge);
            if (xhost_challenge == null || xhost_challenge.length != 8) {
                badParams += " host_challenge length,";
                logger.debug("TokenServlet: Invalid host challenge length");
                missingParam = true;
            }

        }

        if (!missingParam) {
            card_challenge =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rcard_challenge);

            host_challenge = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rhost_challenge);
            keyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);

            // AC: KDF SPEC CHANGE - read new config file values (needed for symkey)
            //ToDo: Will use these values after completing next ticket
            try {
                nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
                nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);

                // log settings read in to debug log along with xkeyInfo
                logger.debug("TokenServlet: ComputeSessionKey():  xkeyInfo[0] = 0x"
                        + Integer.toHexString((xkeyInfo[0]) & 0x0000000FF)
                        + ",  xkeyInfo[1] = 0x"
                        + Integer.toHexString((xkeyInfo[1]) & 0x0000000FF)
                        );
                logger.debug("TokenServlet: ComputeSessionKey():  Nist SP800-108 KDF will be used for key versions >= 0x"
                        + Integer.toHexString((nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
                        );
                if (nistSP800_108KdfUseCuidAsKdd == true) {
                    logger.debug("TokenServlet: ComputeSessionKey():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
                } else {
                    logger.debug("TokenServlet: ComputeSessionKey():  Nist SP800-108 KDF (if used) will use KDD.");
                }
                // conform to the set-an-error-flag mentality
            } catch (Exception e) {
                missingSetting_exception = e;
                logger.warn("TokenServlet: Exception reading Nist SP800-108 KDF config values: " + e.getMessage(), e);
            }

            String keyInfoMap = "tks." + keySet + ".mk_mappings." + rKeyInfo; //#xx#xx
            String mappingValue = config.getString(keyInfoMap, null);
            if (mappingValue == null) {
                selectedToken =
                        config.getString("tks.defaultSlot", CryptoUtil.INTERNAL_TOKEN_NAME);
                keyNickName = rKeyInfo;
            } else {
                StringTokenizer st = new StringTokenizer(mappingValue, ":");
                if (st.hasMoreTokens())
                    selectedToken = st.nextToken();
                if (st.hasMoreTokens())
                    keyNickName = st.nextToken();
            }

            if (selectedToken != null && keyNickName != null
                    // AC: KDF SPEC CHANGE - check for error flag
                    && missingSetting_exception == null) {

                try {

                    byte macKeyArray[] =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                    + keySet + ".mac_key"));
                    logger.debug("TokenServlet about to try ComputeSessionKey selectedToken="
                            + selectedToken + " keyNickName=" + keyNickName);

                    SecureChannelProtocol protocol = new SecureChannelProtocol();
                    SymmetricKey macKey = protocol.computeSessionKey_SCP01(SecureChannelProtocol.macType,
                            selectedToken,
                            keyNickName, card_challenge,
                            host_challenge, keyInfo, nistSP800_108KdfOnKeyVersion, nistSP800_108KdfUseCuidAsKdd, xCUID,
                            xKDD, macKeyArray, useSoftToken_s, keySet, transportKeyName);

                    session_key = protocol.wrapSessionKey(selectedToken, macKey, null);

                    if (session_key == null) {
                        logger.error("TokenServlet:Tried ComputeSessionKey, got NULL ");
                        throw new Exception("Can't compute session key!");

                    }

                    byte encKeyArray[] =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                    + keySet + ".auth_key"));
                    SymmetricKey encKey = protocol.computeSessionKey_SCP01(SecureChannelProtocol.encType,
                            selectedToken,
                            keyNickName, card_challenge, host_challenge, keyInfo, nistSP800_108KdfOnKeyVersion,
                            nistSP800_108KdfUseCuidAsKdd, xCUID, xKDD, encKeyArray, useSoftToken_s, keySet,
                            transportKeyName);

                    enc_session_key = protocol.wrapSessionKey(selectedToken, encKey, null);

                    if (enc_session_key == null) {
                        logger.error("TokenServlet:Tried ComputeEncSessionKey, got NULL ");
                        throw new Exception("Can't compute enc session key!");

                    }

                    if (serversideKeygen == true) {

                        /**
                         * 0. generate des key
                         * 1. encrypt des key with kek key
                         * 2. encrypt des key with DRM transport key
                         * These two wrapped items are to be sent back to
                         * TPS. 2nd item is to DRM
                         **/
                        logger.debug("TokenServlet: calling ComputeKekKey");

                        byte kekKeyArray[] =
                                org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                        + keySet + ".kek_key"));

                        kek_key = protocol.computeKEKKey_SCP01(selectedToken,
                                keyNickName,
                                keyInfo, nistSP800_108KdfOnKeyVersion, nistSP800_108KdfUseCuidAsKdd,
                                xCUID,
                                xKDD, kekKeyArray, useSoftToken_s, keySet, transportKeyName);

                        logger.debug("TokenServlet: called ComputeKekKey");

                        if (kek_key == null) {
                            logger.error("TokenServlet:Tried ComputeKekKey, got NULL ");
                            throw new Exception("Can't compute kek key!");

                        }
                        // now use kek key to wrap kek session key..
                        logger.debug("computeSessionKey:kek key len =" +
                                kek_key.getLength());

                        // (1) generate DES key
                        /* applet does not support DES3
                        org.mozilla.jss.crypto.KeyGenerator kg =
                            internalToken.getKeyGenerator(KeyGenAlgorithm.DES3);
                            desKey = kg.generate();*/

                        /*
                         * GenerateSymkey firt generates a 16 byte DES2 key.
                         * It then pads it into a 24 byte key with last
                         * 8 bytes copied from the 1st 8 bytes.  Effectively
                         * making it a 24 byte DES2 key.  We need this for
                         * wrapping private keys on DRM.
                         */
                        /*generate it on whichever token the master key is at*/
                        if (useSoftToken_s.equals("true")) {
                            logger.debug("TokenServlet: key encryption key generated on internal");
                            //cfu audit here? sym key gen

                            desKey = protocol.generateSymKey(CryptoUtil.INTERNAL_TOKEN_NAME);
                            //cfu audit here? sym key gen done
                        } else {
                            logger.debug("TokenServlet: key encryption key generated on " + selectedToken);
                            desKey = protocol.generateSymKey(selectedToken);
                        }
                        if (desKey != null) {
                            // AC: KDF SPEC CHANGE - Output using CUID and KDD
                            logger.debug("TokenServlet: key encryption key generated for CUID=" +
                                    trim(pp.toHexString(xCUID)) +
                                    ", KDD=" +
                                    trim(pp.toHexString(xKDD)));
                        } else {
                            // AC: KDF SPEC CHANGE - Output using CUID and KDD
                            logger.error("TokenServlet: key encryption key generation failed for CUID=" +
                                    trim(pp.toHexString(xCUID)) +
                                    ", KDD=" +
                                    trim(pp.toHexString(xKDD)));

                            throw new Exception("can't generate key encryption key");
                        }

                        /*
                         * ECBencrypt actually takes the 24 byte DES2 key
                         * and discard the last 8 bytes before it encrypts.
                         * This is done so that the applet can digest it
                         */

                        byte[] encDesKey = protocol.ecbEncrypt(kek_key, desKey, selectedToken);

                        /*
                        logger.debug("computeSessionKey:encrypted desKey size = "+encDesKey.length);
                        logger.debug(encDesKey);
                        */

                        kek_wrapped_desKeyString =
                                org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(encDesKey);

                        // get keycheck

                        byte[] keycheck = protocol.computeKeyCheck(desKey, selectedToken);
                        /*
                        logger.debug("computeSessionKey:keycheck size = "+keycheck.length);
                        logger.debug(keycheck);
                        */
                        keycheck_s =
                                org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(keycheck);

                        //use DRM transport cert to wrap desKey
                        String drmTransNickname = config.getString("tks.drm_transport_cert_nickname", "");

                        if ((drmTransNickname == null) || (drmTransNickname == "")) {
                            logger.error("TokenServlet:did not find DRM transport certificate nickname");
                            throw new Exception("can't find DRM transport certificate nickname");
                        } else {
                            logger.debug("TokenServlet:drmtransport_cert_nickname=" + drmTransNickname);
                        }

                        X509Certificate drmTransCert = null;
                        drmTransCert = CryptoManager.getInstance().findCertByNickname(drmTransNickname);
                        // wrap kek session key with DRM transport public key
                        CryptoToken token = null;
                        if (useSoftToken_s.equals("true")) {
                            token = CryptoUtil.getCryptoToken(null);
                        } else {
                            token = CryptoUtil.getCryptoToken(selectedToken);
                        }
                        PublicKey pubKey = drmTransCert.getPublicKey();
                        String pubKeyAlgo = pubKey.getAlgorithm();
                        logger.debug("Transport Cert Key Algorithm: " + pubKeyAlgo);
                        KeyWrapper keyWrapper = null;
                        //For wrapping symmetric keys don't need IV, use ECB
                        if (pubKeyAlgo.equals("EC")) {
                            keyWrapper = token.getKeyWrapper(KeyWrapAlgorithm.AES_ECB);
                            keyWrapper.initWrap(pubKey, null);
                        } else {

                            boolean useOAEP = config.getBoolean("keyWrap.useOAEP",false);
                            KeyWrapAlgorithm wrapAlg = KeyWrapAlgorithm.RSA;
                            if(useOAEP == true) {
                                wrapAlg = KeyWrapAlgorithm.RSA_OAEP;
                            }

                            keyWrapper = token.getKeyWrapper(wrapAlg);
                            OAEPParameterSpec params = null;
                            if(useOAEP == true) {
                                params =new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
                            }
                            keyWrapper.initWrap(pubKey, params);
                        }
                        logger.debug("desKey token " + desKey.getOwningToken().getName() + " token: " + token.getName());
                        drm_trans_wrapped_desKey = keyWrapper.wrap(desKey);
                        logger.debug("computeSessionKey:desKey wrapped with drm transportation key.");

                    } // if (serversideKeygen == true)

                    byte authKeyArray[] =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                    + keySet + ".auth_key"));

                    host_cryptogram = protocol.computeCryptogram_SCP01(selectedToken, keyNickName, card_challenge,
                            host_challenge,
                            xkeyInfo, nistSP800_108KdfOnKeyVersion, nistSP800_108KdfUseCuidAsKdd, xCUID, xKDD, SecureChannelProtocol.HOST_CRYPTOGRAM,
                            authKeyArray, useSoftToken_s, keySet, transportKeyName);

                    if (host_cryptogram == null) {
                        logger.error("TokenServlet:Tried ComputeCryptogram, got NULL ");
                        throw new Exception("Can't compute host cryptogram!");

                    }

                    card_crypto = protocol.computeCryptogram_SCP01(selectedToken, keyNickName, card_challenge,
                            host_challenge, xkeyInfo, nistSP800_108KdfOnKeyVersion, nistSP800_108KdfUseCuidAsKdd,
                            xCUID, xKDD, SecureChannelProtocol.CARD_CRYPTOGRAM, authKeyArray, useSoftToken_s, keySet, transportKeyName);

                    if (card_crypto == null) {
                        logger.error("TokenServlet:Tried ComputeCryptogram, got NULL ");
                        throw new Exception("Can't compute card cryptogram!");

                    }

                    if (isCryptoValidate) {
                        if (rcard_cryptogram == null) {
                            logger.error("TokenServlet: ComputeCryptogram(): missing card cryptogram");
                            throw new Exception("Missing card cryptogram");
                        }
                        input_card_crypto =
                                org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rcard_cryptogram);

                        //SecureChannelProtocol.debugByteArray(input_card_crypto, "input_card_crypto");
                        //SecureChannelProtocol.debugByteArray(card_crypto, "card_crypto");

                        if (card_crypto.length == input_card_crypto.length) {
                            for (int i = 0; i < card_crypto.length; i++) {
                                if (card_crypto[i] != input_card_crypto[i]) {
                                    sameCardCrypto = false;
                                    break;
                                }
                            }
                        } else {
                            // different length; must be different
                            sameCardCrypto = false;
                        }
                    }

                    // AC: KDF SPEC CHANGE - print both KDD and CUID
                    logger.info("processComputeSessionKey for CUID=" +
                                    trim(pp.toHexString(xCUID)) +
                                    ", KDD=" +
                                    trim(pp.toHexString(xKDD)));
                } catch (Exception e) {
                    logger.warn("TokenServlet Computing Session Key: " + e.getMessage(), e);
                    if (isCryptoValidate)
                        sameCardCrypto = false;
                }
            }
        } // ! missingParam

        String value = "";

        resp.setContentType("text/html");

        String outputString = "";
        String encSessionKeyString = "";
        String drm_trans_wrapped_desKeyString = "";
        String cryptogram = "";
        String status = "0";
        if (session_key != null && session_key.length > 0) {
            outputString =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(session_key);
        } else {

            status = "1";
        }

        if (enc_session_key != null && enc_session_key.length > 0) {
            encSessionKeyString =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(enc_session_key);
        } else {
            status = "1";
        }

        if (serversideKeygen == true) {
            if (drm_trans_wrapped_desKey != null && drm_trans_wrapped_desKey.length > 0)
                drm_trans_wrapped_desKeyString =
                        org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(drm_trans_wrapped_desKey);
            else {
                status = "1";
            }
        }

        if (host_cryptogram != null && host_cryptogram.length > 0) {
            cryptogram =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(host_cryptogram);
        } else {
            // AC: Bugfix: Don't override status's value if an error was already flagged
            if (status.equals("0") == true) {
                status = "2";
            }
        }

        if (selectedToken == null || keyNickName == null) {
            // AC: Bugfix: Don't override status's value if an error was already flagged
            if (status.equals("0") == true) {
                status = "4";
            }
        }

        if (!sameCardCrypto) {
            // AC: Bugfix: Don't override status's value if an error was already flagged
            if (status.equals("0") == true) {
                // AC: Bugfix: Don't mis-represent host cryptogram mismatch errors as TPS parameter issues
                status = "5";
            }
        }

        // AC: KDF SPEC CHANGE - check for settings file issue (flag)
        if (missingSetting_exception != null) {
            // AC: Intentionally override previous errors if config file settings were missing.
            status = "6";
        }

        if (missingParam) {
            // AC: Intentionally override previous errors if parameters were missing.
            status = "3";
        }

        if (!status.equals("0")) {

            if (status.equals("1")) {
                errorMsg = "Problem generating session key info.";
            }

            if (status.equals("2")) {
                errorMsg = "Problem creating host_cryptogram.";
            }

            // AC: Bugfix: Don't mis-represent card cryptogram mismatch errors as TPS parameter issues
            if (status.equals("5")) {
                errorMsg = "Card cryptogram mismatch. Token likely has incorrect keys.";
            }

            if (status.equals("4")) {
                errorMsg = "Problem obtaining token information.";
            }

            // AC: KDF SPEC CHANGE - handle missing configuration item
            if (status.equals("6")) {
                errorMsg = "Problem reading required configuration value.";
            }

            if (status.equals("3")) {
                if (badParams.endsWith(",")) {
                    badParams = badParams.substring(0, badParams.length() - 1);
                }
                errorMsg = "Missing input parameters :" + badParams;
            }

            value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        } else {
            if (serversideKeygen == true) {
                StringBuffer sb = new StringBuffer();
                sb.append(IRemoteRequest.RESPONSE_STATUS + "=0&");
                sb.append(IRemoteRequest.TKS_RESPONSE_SessionKey + "=");
                sb.append(outputString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_HostCryptogram + "=");
                sb.append(cryptogram);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_EncSessionKey + "=");
                sb.append(encSessionKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_KEK_DesKey + "=");
                sb.append(kek_wrapped_desKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_KeyCheck + "=");
                sb.append(keycheck_s);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_DRM_Trans_DesKey + "=");
                sb.append(drm_trans_wrapped_desKeyString);
                value = sb.toString();
            } else {

                StringBuffer sb = new StringBuffer();
                sb.append(IRemoteRequest.RESPONSE_STATUS + "=0&");
                sb.append(IRemoteRequest.TKS_RESPONSE_SessionKey + "=");
                sb.append(outputString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_HostCryptogram + "=");
                sb.append(cryptogram);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_EncSessionKey + "=");
                sb.append(encSessionKeyString);
                value = sb.toString();
            }

        }
        //logger.debug("TokenServlet:outputString.encode " + value);

        try {
            resp.setContentLength(value.length());
            logger.debug("TokenServlet:outputString.length " + value.length());
            OutputStream ooss = resp.getOutputStream();
            ooss.write(value.getBytes());
            ooss.flush();
            mRenderResult = false;
        } catch (IOException e) {
            logger.warn("TokenServlet: " + e.getMessage(), e);
        }

        if (status.equals("0")) {
            // AC: KDF SPEC CHANGE - Log both CUID and KDD.
            //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
            ComputeSessionKeyRequestProcessedEvent event = ComputeSessionKeyRequestProcessedEvent.success(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    isCryptoValidate ? "true" : "false", // IsCryptoValidate
                    serversideKeygen ? "true" : "false", // IsServerSideKeygen
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // KeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd) // NistSP800_108KdfUseCuidAsKdd
            );

            signedAuditLogger.log(event);

        } else {
            // AC: KDF SPEC CHANGE - Log both CUID and KDD
            //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
            ComputeSessionKeyRequestProcessedEvent event = ComputeSessionKeyRequestProcessedEvent.failure(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    isCryptoValidate ? "true" : "false", // IsCryptoValidate
                    serversideKeygen ? "true" : "false", // IsServerSideKeygen
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // KeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd), // NistSP800_108KdfUseCuidAsKdd
                    errorMsg // Error
            );

            signedAuditLogger.log(event);
        }
    }

    // This method will return the shared secret name.  In new 10.1 subsystems, this
    // name will be stored in tps.X.nickname.
    //
    // Until multiple TKS/TPS connections is fully supported, this method will just
    // return the first shared secret nickname found, on the assumption that only
    // one nickname will be configured.  This will have to be changed to return the correct
    // key based on some parameter in the request in future.
    //
    // On legacy systems, this method just returns what was previously returned.
    private String getSharedSecretName(TKSEngineConfig cs) throws EBaseException {
        boolean useNewNames = cs.getBoolean("tks.useNewSharedSecretNames", false);

        if (useNewNames) {
            Collection<String> tpsList = cs.getTPSConnectorIDs();
            String firstSharedSecretName = null;
            if (!tpsList.isEmpty()) {
                for (String tpsID : tpsList) {
                    TPSConnectorConfig tpsConfig = cs.getTPSConnectorConfig(tpsID);
                    String sharedSecretName = tpsConfig.getNickname();

                    // This one will be a fall back in case we can't get a specific one
                    if (firstSharedSecretName == null) {
                        firstSharedSecretName = sharedSecretName;
                    }

                    if (!sharedSecretName.isEmpty()) {
                        if (mCurrentUID != null) {
                            String csUid = tpsConfig.getUserID();

                            if (mCurrentUID.equalsIgnoreCase(csUid)) {
                                logger.debug("TokenServlet.getSharedSecretName: found a match of the user id! " + csUid);
                                return sharedSecretName;
                            }
                        }
                    }
                }

                if (firstSharedSecretName != null) {
                    //Return the first in the list if we couldn't isolate one
                    return firstSharedSecretName;
                }
            }
            logger.error("getSharedSecretName: no shared secret has been configured");
            throw new EBaseException("No shared secret has been configured");
        }

        // legacy system - return as before
        return cs.getString("tks.tksSharedSymKeyName", TRANSPORT_KEY_NAME);
    }

    //Accepts protocol param and supports scp03.
    private void processDiversifyKey(HttpServletRequest req,
            HttpServletResponse resp) throws EBaseException {

        String method = "TokenServlet.processDiversifyKey: ";
        byte[] KeySetData, xCUID, xKDD; // AC: KDF SPEC CHANGE: removed duplicative 'CUID' variable and added xKDD

        // AC: BUGFIX:  Record the actual parameters to DiversifyKey in the audit log.
        String oldKeyNickName = null;
        String newKeyNickName = null;

        // AC: KDF SPEC CHANGE - new config file values (needed for symkey)
        byte nistSP800_108KdfOnKeyVersion = (byte) 0xff;
        boolean nistSP800_108KdfUseCuidAsKdd = false;

        // AC: BUGFIX for key versions higher than 09:  We need to initialize these variables in order for the compiler not to complain when we pass them to DiversifyKey.
        byte[] xkeyInfo = null, xnewkeyInfo = null;

        // AC: KDF SPEC CHANGE - flag for if there is an error reading our new setting
        Exception missingSetting_exception = null;

        boolean missingParam = false;
        String errorMsg = "";
        String badParams = "";
        byte[] xWrappedDekKey = null;

        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        String rnewKeyInfo = req.getParameter(IRemoteRequest.TOKEN_NEW_KEYINFO);
        String newMasterKeyName = req.getParameter(IRemoteRequest.TOKEN_NEW_KEYINFO);
        String oldMasterKeyName = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
        String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);

        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
        String rKDD = req.getParameter("KDD");
        if ((rKDD == null) || (rKDD.length() == 0)) {
            // temporarily make it friendly before TPS change
            logger.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
            rKDD = rCUID;
        }

        String rProtocol = req.getParameter(IRemoteRequest.CHANNEL_PROTOCOL);
        String rWrappedDekKey = req.getParameter(IRemoteRequest.WRAPPED_DEK_SESSION_KEY);

        logger.debug(method + "rWrappedDekKey: " + rWrappedDekKey);

        int protocol = 1;
        String auditMessage = "";

        String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
        if (keySet == null || keySet.equals("")) {
            keySet = "defKeySet";
        }
        logger.debug("keySet selected: " + keySet);

        SessionContext sContext = SessionContext.getContext();

        String agentId = "";
        if (sContext != null) {
            agentId =
                    (String) sContext.get(SessionContext.USER_ID);
        }

        // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID
        auditMessage = CMS.getLogMessage(
                AuditEvent.DIVERSIFY_KEY_REQUEST,
                rCUID,
                rKDD, // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                ILogger.SUCCESS,
                agentId,
                oldMasterKeyName,
                newMasterKeyName);

        audit(auditMessage);

        if ((rCUID == null) || (rCUID.equals(""))) {
            badParams += " CUID,";
            logger.debug("TokenServlet: processDiversifyKey(): missing request parameter: CUID");
            missingParam = true;
        }

        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
        if ((rKDD == null) || (rKDD.length() == 0)) {
            logger.debug("TokenServlet: processDiversifyKey(): missing request parameter: KDD");
            badParams += " KDD,";
            missingParam = true;
        }

        if ((rnewKeyInfo == null) || (rnewKeyInfo.equals(""))) {
            badParams += " newKeyInfo,";
            logger.debug("TokenServlet: processDiversifyKey(): missing request parameter: newKeyInfo");
            missingParam = true;
        }
        if ((oldMasterKeyName == null) || (oldMasterKeyName.equals(""))) {
            badParams += " KeyInfo,";
            logger.debug("TokenServlet: processDiversifyKey(): missing request parameter: KeyInfo");
            missingParam = true;
        }

        // AC: KDF SPEC CHANGE
        xCUID = null; // avoid errors about non-initialization
        xKDD = null; // avoid errors about non-initialization
        xkeyInfo = null; // avoid errors about non-initialization
        xnewkeyInfo = null; // avoid errors about non-initialization

        if (!missingParam) {
            xkeyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(oldMasterKeyName);
            if (xkeyInfo == null || (xkeyInfo.length != 2 && xkeyInfo.length != 3)) {
                badParams += " KeyInfo length,";
                logger.debug("TokenServlet: Invalid key info length");
                missingParam = true;
            }
            xnewkeyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(newMasterKeyName);
            if (xnewkeyInfo == null || (xnewkeyInfo.length != 2 && xnewkeyInfo.length != 3)) {
                badParams += " NewKeyInfo length,";
                logger.debug("TokenServlet: Invalid new key info length");
                missingParam = true;
            }

            if (rProtocol != null) {
                try {
                    protocol = Integer.parseInt(rProtocol);
                } catch (NumberFormatException e) {
                    protocol = 1;
                }
            }
            logger.debug("process DiversifyKey: protocol value: " + protocol);

            if (protocol == 2) {
                if ((rWrappedDekKey == null) || (rWrappedDekKey.equals(""))) {
                    badParams += " WrappedDekKey,";
                    logger.debug("TokenServlet: processDiversifyKey(): missing request parameter: WrappedDekKey, with SCP02.");
                    missingParam = true;
                } else {

                    logger.debug("process DiversifyKey: wrappedDekKey value: " + rWrappedDekKey);
                    xWrappedDekKey = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rWrappedDekKey);
                }

            }
        }
        String useSoftToken_s = config.getString("tks.useSoftToken", "true");
        if (!useSoftToken_s.equalsIgnoreCase("true"))
            useSoftToken_s = "false";

        KeySetData = null;
        if (!missingParam) {
            xCUID = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rCUID);
            if (xCUID == null || xCUID.length != 10) {
                badParams += " CUID length,";
                logger.debug("TokenServlet: Invalid CUID length");
                missingParam = true;
            }

            // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
            xKDD = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKDD);
            if (xKDD == null || xKDD.length != 10) {
                badParams += " KDD length,";
                logger.debug("TokenServlet: Invalid KDD length");
                missingParam = true;
            }
        }
        if (!missingParam) {
            // CUID = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rCUID); // AC: KDF SPEC CHANGE: Removed duplicative variable/processing.

            // AC: KDF SPEC CHANGE - read new config file values (needed for symkey)

            //ToDo: Refactor this, this same block occurs several times in the file
            try {
                nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
                nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);

                // log settings read in to debug log along with xkeyInfo and xnewkeyInfo
                logger.debug("TokenServlet: processDiversifyKey():  xkeyInfo[0] (old) = 0x"
                        + Integer.toHexString((xkeyInfo[0]) & 0x0000000FF)
                        + ",  xkeyInfo[1] (old) = 0x"
                        + Integer.toHexString((xkeyInfo[1]) & 0x0000000FF)
                        + ",  xnewkeyInfo[0] = 0x"
                        + Integer.toHexString((xnewkeyInfo[0]) & 0x000000FF)
                        + ",  xnewkeyInfo[1] = 0x"
                        + Integer.toHexString((xnewkeyInfo[1]) & 0x000000FF)
                        );
                logger.debug("TokenServlet: processDiversifyKey():  Nist SP800-108 KDF will be used for key versions >= 0x"
                        + Integer.toHexString((nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
                        );
                if (nistSP800_108KdfUseCuidAsKdd == true) {
                    logger.debug("TokenServlet: processDiversifyKey():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
                } else {
                    logger.debug("TokenServlet: processDiversifyKey():  Nist SP800-108 KDF (if used) will use KDD.");
                }
                // conform to the set-an-error-flag mentality
            } catch (Exception e) {
                missingSetting_exception = e;
                logger.warn("TokenServlet: Exception reading Nist SP800-108 KDF config values: " + e.getMessage(), e);
            }

            if (mKeyNickName != null)
                oldMasterKeyName = mKeyNickName;
            if (mNewKeyNickName != null)
                newMasterKeyName = mNewKeyNickName;

            String tokKeyInfo =  req.getParameter(IRemoteRequest.TOKEN_KEYINFO);

            // Get the first 6 characters, since scp03 gives us extra characters.
            tokKeyInfo = tokKeyInfo.substring(0,6);
            String oldKeyInfoMap = "tks." + keySet + ".mk_mappings." + tokKeyInfo; //#xx#xx
            logger.debug(method + " oldKeyInfoMap: " + oldKeyInfoMap);
            String oldMappingValue = config.getString(oldKeyInfoMap, null);
            String oldSelectedToken = null;
            if (oldMappingValue == null) {
                oldSelectedToken = config.getString("tks.defaultSlot", CryptoUtil.INTERNAL_TOKEN_NAME);
                oldKeyNickName = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
            } else {
                StringTokenizer st = new StringTokenizer(oldMappingValue, ":");
                oldSelectedToken = st.nextToken();
                oldKeyNickName = st.nextToken();
            }


            String newKeyInfoMap = "tks.mk_mappings." + rnewKeyInfo.substring(0,6); //#xx#xx
            logger.debug(method + " newKeyInfoMap: " + newKeyInfoMap);
            String newMappingValue = config.getString(newKeyInfoMap, null);
            String newSelectedToken = null;
            if (newMappingValue == null) {
                newSelectedToken = config.getString("tks.defaultSlot", CryptoUtil.INTERNAL_TOKEN_NAME);
                newKeyNickName = rnewKeyInfo;
            } else {
                StringTokenizer st = new StringTokenizer(newMappingValue, ":");
                newSelectedToken = st.nextToken();
                newKeyNickName = st.nextToken();
            }

            logger.debug("process DiversifyKey for oldSelectedToke=" +
                    oldSelectedToken + " newSelectedToken=" + newSelectedToken +
                    " oldKeyNickName=" + oldKeyNickName + " newKeyNickName=" +
                    newKeyNickName);

            byte kekKeyArray[] = getDeveKeyArray("kek_key", config, keySet);
            byte macKeyArray[] = getDeveKeyArray("auth_key", config, keySet);
            byte encKeyArray[] = getDeveKeyArray("mac_key", config, keySet);

            //        org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(sconfig.getString("tks." + keySet + ".kek_key"));

            //GPParams for scp03 right now, reads some scp03 specific values from the config of a given keyset
            // passed down to the SecureChannelProtocol functions that deal with SCP03

            GPParams gp3Params = readGPSettings(keySet);

            SecureChannelProtocol secProtocol = new SecureChannelProtocol(protocol);
            // AC: KDF SPEC CHANGE - check for error reading settings
            if (missingSetting_exception == null) {
                if (protocol == 1 || protocol == 3) {
                   KeySetData = secProtocol.diversifyKey(oldSelectedToken,
                            newSelectedToken, oldKeyNickName,
                            newKeyNickName,
                            xkeyInfo, // AC: KDF SPEC CHANGE - pass in old key info so symkey can make decision about which KDF version to use
                            xnewkeyInfo, // AC: BUGFIX for key versions higher than 09:  We need to specialDecode keyInfo parameters before sending them into symkey!  This means the parameters must be byte[]
                            nistSP800_108KdfOnKeyVersion, // AC: KDF SPEC CHANGE - pass in configuration file value
                            nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE - pass in configuration file value
                            xCUID, // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
                            xKDD, // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
                            kekKeyArray,encKeyArray,macKeyArray, useSoftToken_s, keySet, (byte) protocol,gp3Params);

                } else if (protocol == 2) {
                    KeySetData = SessionKey.DiversifyKey(oldSelectedToken, newSelectedToken, oldKeyNickName,
                            newKeyNickName, xkeyInfo,
                            xnewkeyInfo, nistSP800_108KdfOnKeyVersion, nistSP800_108KdfUseCuidAsKdd, xCUID, xKDD,
                            (protocol == 2) ? xWrappedDekKey : kekKeyArray, useSoftToken_s, keySet, (byte) protocol);
                }
                //SecureChannelProtocol.debugByteArray(KeySetData, " New keyset data: ");
                logger.debug("TokenServlet.processDiversifyKey: New keyset data obtained");

                if (KeySetData == null || KeySetData.length <= 1) {
                    logger.info("process DiversifyKey: Missing MasterKey in Slot");
                }

                logger.info("process DiversifyKey for CUID=" +
                                trim(pp.toHexString(xCUID)) + // AC: KDF SPEC CHANGE:  Log both CUID and KDD
                                ", KDD=" +
                                trim(pp.toHexString(xKDD))
                                + ";from oldMasterKeyName=" + oldSelectedToken + ":" + oldKeyNickName
                                + ";to newMasterKeyName=" + newSelectedToken + ":" + newKeyNickName);

                resp.setContentType("text/html");

            } // AC: KDF SPEC CHANGE - endif no error reading settings from settings file

        } // ! missingParam

        String value = "";
        String status = "0";

        if (KeySetData != null && KeySetData.length > 1) {
            value = IRemoteRequest.RESPONSE_STATUS + "=0&" + IRemoteRequest.TKS_RESPONSE_KeySetData + "=" +
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(KeySetData);
            //logger.debug("TokenServlet:process DiversifyKey.encode " + value);
            logger.debug("TokenServlet:process DiversifyKey.encode returning KeySetData");
            // AC: KDF SPEC CHANGE - check for settings file issue (flag)
        } else if (missingSetting_exception != null) {
            status = "6";
            errorMsg = "Problem reading required configuration value.";
            value = "status=" + status;
        } else if (missingParam) {
            status = "3";
            if (badParams.endsWith(",")) {
                badParams = badParams.substring(0, badParams.length() - 1);
            }
            errorMsg = "Missing input parameters: " + badParams;
            value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        } else {
            errorMsg = "Problem diversifying key data.";
            status = "1";
            value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        }

        resp.setContentLength(value.length());
        logger.debug("TokenServlet:outputString.length " + value.length());

        try {
            OutputStream ooss = resp.getOutputStream();
            ooss.write(value.getBytes());
            ooss.flush();
            mRenderResult = false;
        } catch (Exception e) {
            logger.warn("TokenServlet:process DiversifyKey: " + e.getMessage(), e);
        }

        if (status.equals("0")) {

            // AC: KDF SPEC CHANGE - Log both CUID and KDD
            //                       Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
            DiversifyKeyRequestProcessedEvent event = DiversifyKeyRequestProcessedEvent.success(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID

                    // AC: BUGFIX:  Record the actual parameters to DiversifyKey in the audit log.
                    oldKeyNickName, // oldMasterKeyName
                    newKeyNickName, // newMasterKeyName

                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // OldKeyInfo_KeyVersion
                    log_string_from_keyInfo(xnewkeyInfo), // NewKeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd) // NistSP800_108KdfUseCuidAsKdd
            );

            signedAuditLogger.log(event);

        } else {
            // AC: KDF SPEC CHANGE - Log both CUID and KDD
            //                       Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
            DiversifyKeyRequestProcessedEvent event = DiversifyKeyRequestProcessedEvent.failure(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID

                    // AC: BUGFIX:  Record the actual parameters to DiversifyKey in the audit log.
                    oldKeyNickName, // oldMasterKeyName
                    newKeyNickName, // newMasterKeyName

                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // OldKeyInfo_KeyVersion
                    log_string_from_keyInfo(xnewkeyInfo), // NewKeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd), // NistSP800_108KdfUseCuidAsKdd
                    errorMsg // Error
            );

            signedAuditLogger.log(event);
        }
    }

    private void processEncryptData(HttpServletRequest req,
            HttpServletResponse resp) throws EBaseException {
        byte[] keyInfo, xCUID, encryptedData, xkeyInfo, xKDD; // AC: KDF SPEC CHANGE: removed duplicative 'CUID' variable and added xKDD

        // AC: KDF SPEC CHANGE - new config file values (needed for symkey)
        byte nistSP800_108KdfOnKeyVersion = (byte) 0xff;
        boolean nistSP800_108KdfUseCuidAsKdd = false;

        // AC: KDF SPEC CHANGE - flag for if there is an error reading our new setting
        Exception missingSetting_exception = null;

        boolean missingParam = false;
        byte[] data = null;
        boolean isRandom = true; // randomly generate the data to be encrypted

        String errorMsg = "";
        String badParams = "";

        TKSEngine engine = TKSEngine.getInstance();
        JssSubsystem jssSubsystem = engine.getJSSSubsystem();

        TKSEngineConfig config = engine.getConfig();
        encryptedData = null;
        String rdata = req.getParameter(IRemoteRequest.TOKEN_DATA);
        String rKeyInfo = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
        String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);

        String protocolValue = req.getParameter(IRemoteRequest.CHANNEL_PROTOCOL);

        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
        String rKDD = req.getParameter("KDD");
        if ((rKDD == null) || (rKDD.length() == 0)) {
            // temporarily make it friendly before TPS change
            logger.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
            rKDD = rCUID;
        }

        String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
        if (keySet == null || keySet.equals("")) {
            keySet = "defKeySet";
        }

        SessionContext sContext = SessionContext.getContext();

        String agentId = "";
        if (sContext != null) {
            agentId =
                    (String) sContext.get(SessionContext.USER_ID);
        }

        logger.debug("keySet selected: " + keySet);

        String s_isRandom = config.getString("tks.EncryptData.isRandom", "true");
        if (s_isRandom.equalsIgnoreCase("false")) {
            logger.debug("TokenServlet: processEncryptData(): Random number not to be generated");
            isRandom = false;
        } else {
            logger.debug("TokenServlet: processEncryptData(): Random number generation required");
            isRandom = true;
        }

        // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID
        String auditMessage = CMS.getLogMessage(
                AuditEvent.ENCRYPT_DATA_REQUEST,
                rCUID,
                rKDD, // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                ILogger.SUCCESS,
                agentId,
                s_isRandom);
        audit(auditMessage);

        GPParams gp3Params = readGPSettings(keySet);

        if (isRandom) {
            if ((rdata == null) || (rdata.equals(""))) {
                logger.debug("TokenServlet: processEncryptData(): no data in request.  Generating random number as data");
            } else {
                logger.debug("TokenServlet: processEncryptData(): contain data in request, however, random generation on TKS is required. Generating...");
            }
            try {
                SecureRandom random = jssSubsystem.getRandomNumberGenerator();
                data = new byte[16];
                random.nextBytes(data);
            } catch (Exception e) {
                logger.warn("TokenServlet: processEncryptData():" + e.getMessage(), e);
                badParams += " Random Number,";
                missingParam = true;
            }
        } else if ((!isRandom) && (((rdata == null) || (rdata.equals(""))))) {
            logger.debug("TokenServlet: processEncryptData(): missing request parameter: data.");
            badParams += " data,";
            missingParam = true;
        }

        if ((rCUID == null) || (rCUID.equals(""))) {
            badParams += " CUID,";
            logger.debug("TokenServlet: processEncryptData(): missing request parameter: CUID");
            missingParam = true;
        }

        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
        if ((rKDD == null) || (rKDD.length() == 0)) {
            logger.debug("TokenServlet: processDiversifyKey(): missing request parameter: KDD");
            badParams += " KDD,";
            missingParam = true;
        }

        if ((rKeyInfo == null) || (rKeyInfo.equals(""))) {
            badParams += " KeyInfo,";
            logger.debug("TokenServlet: processEncryptData(): missing request parameter: key info");
            missingParam = true;
        }

        // AC: KDF SPEC CHANGE
        xCUID = null; // avoid errors about non-initialization
        xKDD = null; // avoid errors about non-initialization
        xkeyInfo = null; // avoid errors about non-initialization

        if (!missingParam) {
            xCUID = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rCUID);
            if (xCUID == null || xCUID.length != 10) {
                badParams += " CUID length,";
                logger.debug("TokenServlet: Invalid CUID length");
                missingParam = true;
            }

            // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
            xKDD = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKDD);
            if (xKDD == null || xKDD.length != 10) {
                badParams += " KDD length,";
                logger.debug("TokenServlet: Invalid KDD length");
                missingParam = true;
            }

            xkeyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);
            if (xkeyInfo == null || (xkeyInfo.length != 2 && xkeyInfo.length != 3)) {
                badParams += " KeyInfo length,";
                logger.debug("TokenServlet: Invalid key info length");
                missingParam = true;
            }
        }

        String useSoftToken_s = config.getString("tks.useSoftToken", "true");
        if (!useSoftToken_s.equalsIgnoreCase("true"))
            useSoftToken_s = "false";

        String selectedToken = null;
        String keyNickName = null;
        if (!missingParam) {

            // AC: KDF SPEC CHANGE - read new config file values (needed for symkey
            try {
                nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
                nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);

                // log settings read in to debug log along with xkeyInfo
                logger.debug("TokenServlet: processEncryptData():  xkeyInfo[0] = 0x"
                        + Integer.toHexString((xkeyInfo[0]) & 0x0000000FF)
                        + ",  xkeyInfo[1] = 0x"
                        + Integer.toHexString((xkeyInfo[1]) & 0x0000000FF)
                        );
                logger.debug("TokenServlet: processEncryptData():  Nist SP800-108 KDF will be used for key versions >= 0x"
                        + Integer.toHexString((nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
                        );
                if (nistSP800_108KdfUseCuidAsKdd == true) {
                    logger.debug("TokenServlet: processEncryptData():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
                } else {
                    logger.debug("TokenServlet: processEncryptData():  Nist SP800-108 KDF (if used) will use KDD.");
                }
                // conform to the set-an-error-flag mentality
            } catch (Exception e) {
                missingSetting_exception = e;
                logger.warn("TokenServlet: Exception reading Nist SP800-108 KDF config values: " + e.getMessage(), e);
            }

            if (!isRandom)
                data = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rdata);
            keyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);

            String keyInfoMap = "tks." + keySet + ".mk_mappings." +  rKeyInfo.substring(0,6);
            String mappingValue = config.getString(keyInfoMap, null);
            if (mappingValue == null) {
                selectedToken = config.getString("tks.defaultSlot", CryptoUtil.INTERNAL_TOKEN_NAME);
                keyNickName = rKeyInfo;
            } else {
                StringTokenizer st = new StringTokenizer(mappingValue, ":");
                selectedToken = st.nextToken();
                keyNickName = st.nextToken();
            }


            //calculate the protocol

            int protocolInt = SecureChannelProtocol.PROTOCOL_ONE;
            try
            {
                 protocolInt = Integer.parseInt(protocolValue);
            }
            catch (NumberFormatException nfe)
            {
                protocolInt = SecureChannelProtocol.PROTOCOL_ONE;
            }

            logger.debug( "TokenServerlet.encryptData: protocol input: " + protocolInt);

            //Check for reasonable sanity, leave room for future versions
            if(protocolInt <= 0 || protocolInt > 20) {
                logger.debug( "TokenServerlet.encryptData: unfamliar protocl, assume default of 1.");
                protocolInt = 1;

            }

            byte kekKeyArray[] =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks." + keySet + ".kek_key"));
            // AC: KDF SPEC CHANGE - check for error reading settings
            if (missingSetting_exception == null) {


                SecureChannelProtocol protocol = new SecureChannelProtocol(protocolInt);

                if (protocolInt != SecureChannelProtocol.PROTOCOL_THREE) {

                    encryptedData = protocol.encryptData(
                            selectedToken, keyNickName, data, keyInfo,
                            nistSP800_108KdfOnKeyVersion, // AC: KDF SPEC CHANGE - pass in configuration file value
                            nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE - pass in configuration file value
                            xCUID, // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
                            xKDD, // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
                            kekKeyArray, useSoftToken_s, keySet);

                } else {

                    encryptedData = protocol.encryptData_SCP03(selectedToken, keyNickName, data, xkeyInfo,
                            nistSP800_108KdfOnKeyVersion, nistSP800_108KdfUseCuidAsKdd, xCUID, xKDD, kekKeyArray,
                            useSoftToken_s, keySet,gp3Params);

                }

                //SecureChannelProtocol.debugByteArray(encryptedData, "New Encrypt Data: ");

                // AC: KDF SPEC CHANGE - Log both CUID and KDD

                logger.info("process EncryptData for CUID=" +
                                trim(pp.toHexString(xCUID)) +
                                ", KDD=" +
                                trim(pp.toHexString(xKDD)));

            } // AC: KDF SPEC CHANGE - endif no error reading settings from settings file

        } // !missingParam

        resp.setContentType("text/html");

        String value = "";
        String status = "0";
        if (encryptedData != null && encryptedData.length > 0) {
            // sending both the pre-encrypted and encrypted data back
            value = IRemoteRequest.RESPONSE_STATUS + "=0&"
                    + IRemoteRequest.TOKEN_DATA + "=" +
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(data) +
                    "&" + IRemoteRequest.TKS_RESPONSE_EncryptedData + "=" +
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(encryptedData);
            // AC: KDF SPEC CHANGE - check for settings file issue (flag)
        } else if (missingSetting_exception != null) {
            status = "6";
            errorMsg = "Problem reading required configuration value.";
            value = "status=" + status;
        } else if (missingParam) {
            if (badParams.endsWith(",")) {
                badParams = badParams.substring(0, badParams.length() - 1);
            }
            errorMsg = "Missing input parameters: " + badParams;
            status = "3";
            value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        } else {
            errorMsg = "Problem encrypting data.";
            status = "1";
            value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        }

        //logger.debug("TokenServlet:process EncryptData.encode " + value);

        try {
            resp.setContentLength(value.length());
            logger.debug("TokenServlet:outputString.lenght " + value.length());

            OutputStream ooss = resp.getOutputStream();
            ooss.write(value.getBytes());
            ooss.flush();
            mRenderResult = false;
        } catch (Exception e) {
            logger.warn("TokenServlet: " + e.getMessage(), e);
        }

        if (status.equals("0")) {
            // AC: KDF SPEC CHANGE - Log both CUID and KDD
            //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
            EncryptDataRequestProcessedEvent event = EncryptDataRequestProcessedEvent.success(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    s_isRandom, // isRandom
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // KeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd) // NistSP800_108KdfUseCuidAsKdd
            );

            signedAuditLogger.log(event);

        } else {
            // AC: KDF SPEC CHANGE - Log both CUID and KDD
            //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
            EncryptDataRequestProcessedEvent event = EncryptDataRequestProcessedEvent.failure(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    s_isRandom, // isRandom
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // KeyInfo_KeyVersion
                    "0x" + Integer.toHexString(nistSP800_108KdfOnKeyVersion & 0x000000FF), // NistSP800_108KdfOnKeyVersion
                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd), // NistSP800_108KdfUseCuidAsKdd
                    errorMsg // Error
            );

            signedAuditLogger.log(event);
        }
    }

    /*
     *   For EncryptData:
     *   data=value1
     *   CUID=value2 // missing from RA
     *   versionID=value3  // missing from RA
     *
     *   For ComputeSession:
     *   card_challenge=value1
     *   host_challenge=value2

     *   For DiversifyKey:
     *   new_master_key_index
     *   master_key_index
     */

    private void processComputeRandomData(HttpServletRequest req,
            HttpServletResponse resp) throws EBaseException {

        byte[] randomData = null;
        String status = "0";
        String errorMsg = "";
        String badParams = "";
        boolean missingParam = false;
        int dataSize = 0;

        logger.debug("TokenServlet::processComputeRandomData");

        SessionContext sContext = SessionContext.getContext();

        TKSEngine engine = TKSEngine.getInstance();
        JssSubsystem jssSubsystem = engine.getJSSSubsystem();

        String agentId = "";
        if (sContext != null) {
            agentId =
                    (String) sContext.get(SessionContext.USER_ID);
        }

        String sDataSize = req.getParameter(IRemoteRequest.TOKEN_DATA_NUM_BYTES);

        if (sDataSize == null || sDataSize.equals("")) {
            logger.debug("TokenServlet::processComputeRandomData missing param dataNumBytes");
            badParams += " Random Data size, ";
            missingParam = true;
            status = "1";
        } else {
            try {
                dataSize = Integer.parseInt(sDataSize.trim());
            } catch (NumberFormatException nfe) {
                logger.warn("TokenServlet::processComputeRandomData invalid data size input: " + nfe.getMessage(), nfe);
                badParams += " Random Data size, ";
                missingParam = true;
                status = "1";
            }

        }

        logger.debug("TokenServlet::processComputeRandomData data size requested: " + dataSize);

        String auditMessage = CMS.getLogMessage(
                AuditEvent.COMPUTE_RANDOM_DATA_REQUEST,
                ILogger.SUCCESS,
                agentId);

        audit(auditMessage);

        if (!missingParam) {
            try {
                SecureRandom random = jssSubsystem.getRandomNumberGenerator();
                randomData = new byte[dataSize];
                random.nextBytes(randomData);
            } catch (Exception e) {
                logger.warn("TokenServlet::processComputeRandomData:" + e.getMessage(), e);
                errorMsg = "Can't generate random data!";
                status = "2";
            }
        }

        String randomDataOut = "";
        if (status.equals("0")) {
            if (randomData != null && randomData.length == dataSize) {
                randomDataOut =
                        org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(randomData);
            } else {
                status = "2";
                errorMsg = "Can't convert random data!";
            }
        }

        if (status.equals("1") && missingParam) {

            if (badParams.endsWith(",")) {
                badParams = badParams.substring(0, badParams.length() - 1);
            }
            errorMsg = "Missing input parameters :" + badParams;
        }

        resp.setContentType("text/html");
        String value = "";

        value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        if (status.equals("0")) {
            value = value + "&" + IRemoteRequest.TKS_RESPONSE_RandomData + "=" + randomDataOut;
        }

        try {
            resp.setContentLength(value.length());
            logger.debug("TokenServler::processComputeRandomData :outputString.length " + value.length());

            OutputStream ooss = resp.getOutputStream();
            ooss.write(value.getBytes());
            ooss.flush();
            mRenderResult = false;
        } catch (Exception e) {
            logger.warn("TokenServlet::processComputeRandomData " + e.getMessage(), e);
        }

        if (status.equals("0")) {
            ComputeRandomDataRequestProcessedEvent event = ComputeRandomDataRequestProcessedEvent.success(
                    status,
                    agentId);

            signedAuditLogger.log(event);

        } else {
            ComputeRandomDataRequestProcessedEvent event = ComputeRandomDataRequestProcessedEvent.failure(
                    status,
                    agentId,
                    errorMsg);

            signedAuditLogger.log(event);
        }
    }

    @Override
    public void process(CMSRequest cmsReq) throws EBaseException {
        HttpServletRequest req = cmsReq.getHttpReq();
        HttpServletResponse resp = cmsReq.getHttpResp();

        IAuthToken authToken = authenticate(cmsReq);
        AuthzToken authzToken = null;

        mCurrentUID = (String) authToken.get(IAuthToken.UID) ;

        try {
            authzToken = authorize(mAclMethod, authToken,
                    mAuthzResourceName, "execute");
        } catch (Exception e) {
        }

        if (authzToken == null) {

            try {
                resp.setContentType("text/html");
                String value = "unauthorized=";
                logger.debug("TokenServlet: Unauthorized");

                resp.setContentLength(value.length());
                OutputStream ooss = resp.getOutputStream();
                ooss.write(value.getBytes());
                ooss.flush();
                mRenderResult = false;
            } catch (Exception e) {
                logger.warn("TokenServlet: " + e.getMessage(), e);
            }

            //       cmsReq.setStatus(CMSRequest.UNAUTHORIZED);
            return;
        }

        String temp = req.getParameter(IRemoteRequest.TOKEN_CARD_CHALLENGE);
        String protocol = req.getParameter(IRemoteRequest.CHANNEL_PROTOCOL);
        String derivationConstant = req.getParameter(IRemoteRequest.DERIVATION_CONSTANT);
        //logger.debug("Protocol: " + protocol + " temp: " + temp);

        setDefaultSlotAndKeyName(req);
        if (temp != null && protocol == null) {
            processComputeSessionKey(req, resp);
        } else if (req.getParameter(IRemoteRequest.TOKEN_DATA) != null) {
            processEncryptData(req, resp);
        } else if (req.getParameter(IRemoteRequest.TOKEN_NEW_KEYINFO) != null) {
            processDiversifyKey(req, resp);
        } else if (req.getParameter(IRemoteRequest.TOKEN_DATA_NUM_BYTES) != null) {
            processComputeRandomData(req, resp);
        } else if (protocol != null && protocol.contains("2") && (derivationConstant != null)) {
            //SCP02 compute one session key.
            processComputeSessionKeySCP02(req, resp);

        }  else if (protocol != null && protocol.contains("3") ) {
            processComputeSessionKeysSCP03(req,resp);
        } else {
            throw new EBaseException("Process: Can't decide upon function to call!");
        }
    }

    //Create all the session keys for scp03 at once and return.
    //ToDo: calcualte the optional rmac key
    private void processComputeSessionKeysSCP03(HttpServletRequest req, HttpServletResponse resp) throws EBaseException {
        String method = "processComputeSessionKeysSCP03:";
        logger.debug(method + " entering ...");

        byte[] card_challenge, host_challenge, xCUID, xKDD;
        byte[] card_crypto, host_cryptogram, input_card_crypto;
        byte[] xcard_challenge, xhost_challenge;
        byte[] enc_session_key, xkeyInfo,mac_session_key, kek_session_key;
        String auditMessage = null;
        String errorMsg = "";
        String badParams = "";
        String transportKeyName = "";
        String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);

        String rKDD = req.getParameter("KDD");
        if ((rKDD == null) || (rKDD.length() == 0)) {
            // KDF phase1: default to rCUID if not present
            logger.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
            rKDD = rCUID;
        }

        String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
        if (keySet == null || keySet.equals("")) {
            keySet = "defKeySet";
        }
        logger.debug("keySet selected: " + keySet);

        GPParams gp3Params = readGPSettings(keySet);

        boolean serversideKeygen = false;

        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        boolean isCryptoValidate = true;
        boolean missingParam = false;

        Exception missingSetting_exception = null;

        mac_session_key = null;
        kek_session_key = null;
        card_crypto = null;
        host_cryptogram = null;
        enc_session_key = null;

        SessionContext sContext = SessionContext.getContext();

        String agentId = "";
        if (sContext != null) {
            agentId =
                    (String) sContext.get(SessionContext.USER_ID);
        }

        auditMessage = CMS.getLogMessage(
                AuditEvent.COMPUTE_SESSION_KEY_REQUEST,
                rCUID,
                rKDD,
                ILogger.SUCCESS,
                agentId);

        audit(auditMessage);

        String kek_wrapped_desKeyString = null;
        String keycheck_s = null;

        String useSoftToken_s = config.getString("tks.useSoftToken", "true");
        if (!useSoftToken_s.equalsIgnoreCase("true"))
            useSoftToken_s = "false";

        logger.debug(method + " useSoftToken: " + useSoftToken_s);

        String rServersideKeygen = req.getParameter(IRemoteRequest.SERVER_SIDE_KEYGEN);
        if (rServersideKeygen.equals("true")) {

            serversideKeygen = true;
        }

        logger.debug(method + " serversideKeygen: " + serversideKeygen);

        try {
            isCryptoValidate = config.getBoolean("cardcryptogram.validate.enable", true);
        } catch (EBaseException eee) {
        }

        logger.debug(method + " Do crypto validation: " + isCryptoValidate);

        transportKeyName = getSharedSecretName(config);

        String rcard_challenge = req.getParameter(IRemoteRequest.TOKEN_CARD_CHALLENGE);
        String rhost_challenge = req.getParameter(IRemoteRequest.TOKEN_HOST_CHALLENGE);
        String rKeyInfo = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
        String rcard_cryptogram = req.getParameter(IRemoteRequest.TOKEN_CARD_CRYPTOGRAM);

        if ((rCUID == null) || (rCUID.equals(""))) {
            logger.debug(method + " missing request parameter: CUID");
            badParams += " CUID,";
            missingParam = true;
        }

        if ((rKDD == null) || (rKDD.length() == 0)) {
            logger.debug(method + " missing request parameter: KDD");
            badParams += " KDD,";
            missingParam = true;
        }

        if ((rcard_challenge == null) || (rcard_challenge.equals(""))) {
            badParams += " card_challenge,";
            logger.debug(method + " missing request parameter: card challenge");
            missingParam = true;
        }

        if ((rhost_challenge == null) || (rhost_challenge.equals(""))) {
            badParams += " host_challenge,";
            logger.debug(method + " missing request parameter: host challenge");
            missingParam = true;
        }

        if ((rcard_cryptogram == null) || (rcard_cryptogram.equals(""))) {
            badParams += " card_cryptogram,";
            logger.debug(method + " missing request parameter: card_cryptogram");
            missingParam = true;
        }

        if ((rKeyInfo == null) || (rKeyInfo.equals(""))) {
            badParams += " KeyInfo,";
            logger.debug(method + "missing request parameter: key info");
            missingParam = true;
        }

        String selectedToken = null;
        String keyNickName = null;
        boolean sameCardCrypto = true;

        xCUID = null;
        xKDD = null;
        xkeyInfo = null;
        xcard_challenge = null;
        xhost_challenge = null;

        if (!missingParam) {
            xCUID = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rCUID);
            if (xCUID == null || xCUID.length != 10) {
                badParams += " CUID length,";
                logger.debug("TokenServlet: Invalid CUID length");
                missingParam = true;
            }

            xKDD = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKDD);
            if (xKDD == null || xKDD.length != 10) {
                badParams += " KDD length,";
                logger.debug("TokenServlet: Invalid KDD length");
                missingParam = true;
            }

            xkeyInfo = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rKeyInfo);
            if (xkeyInfo == null || xkeyInfo.length != 3) {
                badParams += " KeyInfo length,";
                logger.debug("TokenServlet: Invalid key info length.");
                missingParam = true;
            }
            xcard_challenge =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rcard_challenge);
            if (xcard_challenge == null || xcard_challenge.length != 8) {
                badParams += " card_challenge length,";
                logger.debug("TokenServlet: Invalid card challenge length.");
                missingParam = true;
            }

            xhost_challenge = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rhost_challenge);
            if (xhost_challenge == null || xhost_challenge.length != 8) {
                badParams += " host_challenge length,";
                logger.debug("TokenServlet: Invalid host challenge length");
                missingParam = true;
            }
        }

        ArrayList<String> serverSideValues = null;

        if (!missingParam) {
            card_challenge =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rcard_challenge);

            host_challenge = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rhost_challenge);

            String keyInfoMap = "tks." + keySet + ".mk_mappings." + rKeyInfo.substring(0,6); //#xx#xx
            String mappingValue = config.getString(keyInfoMap, null);


            if (mappingValue == null) {
                selectedToken = config.getString("tks.defaultSlot", "internal");
                keyNickName = rKeyInfo;
            } else {
                StringTokenizer st = new StringTokenizer(mappingValue, ":");
                if (st.hasMoreTokens())
                    selectedToken = st.nextToken();
                if (st.hasMoreTokens())
                    keyNickName = st.nextToken();
            }

            logger.debug(method + " selectedToken: " + selectedToken + " keyNickName: " + keyNickName );

            SymmetricKey macSessionKey = null;
            SymmetricKey encSessionKey = null;
            SymmetricKey kekSessionKey = null;

            if (selectedToken != null && keyNickName != null
                    && missingSetting_exception == null) {

                try {

                    byte macKeyArray[] =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                    + keySet + ".mac_key"));
                    logger.debug("TokenServlet about to try ComputeSessionKey selectedToken="
                            + selectedToken + " keyNickName=" + keyNickName);

                    SecureChannelProtocol protocol = new SecureChannelProtocol(SecureChannelProtocol.PROTOCOL_THREE);

                    macSessionKey = protocol.computeSessionKey_SCP03(selectedToken, keyNickName,xkeyInfo,
                            SecureChannelProtocol.macType, macKeyArray, keySet,xCUID, xKDD, xhost_challenge, xcard_challenge,
                            transportKeyName,gp3Params);

                    mac_session_key = protocol.wrapSessionKey(selectedToken, macSessionKey, null);

                    if (mac_session_key == null) {
                        logger.error(method + " Can't get mac session key bytes");
                        throw new Exception(method + " Can't get mac session key bytes");

                    }

                    byte encKeyArray[] =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                    + keySet + ".auth_key"));

                    encSessionKey = protocol.computeSessionKey_SCP03(selectedToken, keyNickName,xkeyInfo,
                            SecureChannelProtocol.encType, encKeyArray, keySet, xCUID, xKDD, xhost_challenge, xcard_challenge,
                            transportKeyName,gp3Params);

                    enc_session_key = protocol.wrapSessionKey(selectedToken, encSessionKey, null);

                    if (enc_session_key == null) {
                        logger.error("TokenServlet:Tried ComputeEncSessionKey, got NULL ");
                        throw new Exception("Can't compute enc session key!");

                    }

                    byte kekKeyArray[] =
                            org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(config.getString("tks."
                                    + keySet + ".kek_key"));

                    kekSessionKey = protocol.computeSessionKey_SCP03(selectedToken, keyNickName, xkeyInfo,
                            SecureChannelProtocol.kekType, kekKeyArray, keySet, xCUID, xKDD, xhost_challenge,
                            xcard_challenge,
                            transportKeyName,gp3Params);

                    kek_session_key = protocol.wrapSessionKey(selectedToken, kekSessionKey, null);


                    //Offload some of the tedious params gathering to another method
                    //ToDo, create a method that reads all this stuff at once for all major methods
                    if (serversideKeygen) {
                        try {
                            serverSideValues = calculateServerSideKeygenValues(useSoftToken_s, selectedToken,
                                    kekSessionKey, protocol);
                        } catch (EBaseException e) {
                            logger.warn(method + " Can't calcualte server side keygen required values: " + e.getMessage(), e);
                        }
                    }

                    try {
                        isCryptoValidate = config.getBoolean("cardcryptogram.validate.enable", true);
                    } catch (EBaseException eee) {
                    }

                    ByteArrayOutputStream contextStream = new ByteArrayOutputStream();
                    try {
                        contextStream.write(host_challenge);
                        contextStream.write(card_challenge);
                    } catch (IOException e) {
                        throw new EBaseException(method + " Error calculating derivation data!");
                    }

                    host_cryptogram = protocol.computeCryptogram_SCP03(macSessionKey, selectedToken, contextStream.toByteArray(),NistSP800_108KDF.HOST_CRYPTO_KDF_CONSTANT);
                    //SecureChannelProtocol.debugByteArray(host_cryptogram, method + " calculated host crypto: " + host_cryptogram.length);


                   if( isCryptoValidate) {
                       if (rcard_cryptogram == null) {
                           logger.error(method + " missing card cryptogram");
                           throw new Exception(method + "Missing card cryptogram");
                       }
                       input_card_crypto =
                               org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(rcard_cryptogram);
                       card_crypto = protocol.computeCryptogram_SCP03(macSessionKey, selectedToken, contextStream.toByteArray(),NistSP800_108KDF.CARD_CRYPTO_KDF_CONSTANT);
                       //SecureChannelProtocol.debugByteArray(card_crypto, method + " calculated card crypto: ");
                       //SecureChannelProtocol.debugByteArray(input_card_crypto, method + " original card crypto: ");

                       if(!cryptoGramsAreEqual(input_card_crypto, card_crypto)) {
                           throw new Exception(method + "Card cryptogram mismatch!");
                       }

                   }
                } catch (Exception e) {
                    logger.warn("TokenServlet Computing Session Key: " + e.getMessage(), e);
                    if (isCryptoValidate)
                        sameCardCrypto = false;
                }
            }
        } // ! missingParam

        String value = "";

        resp.setContentType("text/html");

        String encSessionKeyString = "";
        String macSessionKeyString = "";
        String kekSessionKeyString = "";

        String drm_trans_wrapped_desKeyString = "";
        String cryptogram = "";
        String status = "0";

        if (enc_session_key != null && enc_session_key.length > 0) {
            encSessionKeyString =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(enc_session_key);
        } else {
            status = "1";
        }

        if (mac_session_key != null && mac_session_key.length > 0) {
            macSessionKeyString =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(mac_session_key);
        } else {
            status = "1";
        }

        if (kek_session_key != null && kek_session_key.length > 0) {
            kekSessionKeyString =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(kek_session_key);
        } else {
            status = "1";
        }

        if (serversideKeygen == true) {
            if (serverSideValues.size() == 3) {
                drm_trans_wrapped_desKeyString = serverSideValues.get(2);
                kek_wrapped_desKeyString = serverSideValues.get(0);
                keycheck_s = serverSideValues.get(1);
            }
            else {
                status = "1";
            }
        }

        if (host_cryptogram != null && host_cryptogram.length > 0) {
            cryptogram =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(host_cryptogram);
        } else {
            if (status.equals("0") == true) {
                status = "2";
            }
        }

        if (selectedToken == null || keyNickName == null) {
            // AC: Bugfix: Don't override status's value if an error was already flagged
            if (status.equals("0") == true) {
                status = "4";
            }
        }

        if (!sameCardCrypto) {
            if (status.equals("0") == true) {
                status = "5";
            }
        }

        if (missingSetting_exception != null) {
            status = "6";
        }

        if (missingParam) {
            status = "3";
        }

        if (!status.equals("0")) {

            if (status.equals("1")) {
                errorMsg = "Problem generating session key info.";
            }

            if (status.equals("2")) {
                errorMsg = "Problem creating host_cryptogram.";
            }

            if (status.equals("5")) {
                errorMsg = "Card cryptogram mismatch. Token likely has incorrect keys.";
            }

            if (status.equals("4")) {
                errorMsg = "Problem obtaining token information.";
            }

            if (status.equals("6")) {
                errorMsg = "Problem reading required configuration value.";
            }

            if (status.equals("3")) {
                if (badParams.endsWith(",")) {
                    badParams = badParams.substring(0, badParams.length() - 1);
                }
                errorMsg = "Missing input parameters :" + badParams;
            }

            value = IRemoteRequest.RESPONSE_STATUS + "=" + status;
        } else {
            if (serversideKeygen == true) {
                StringBuffer sb = new StringBuffer();
                sb.append(IRemoteRequest.RESPONSE_STATUS + "=0&");
                sb.append(IRemoteRequest.TKS_RESPONSE_MacSessionKey + "=");
                sb.append(macSessionKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_HostCryptogram + "=");
                sb.append(cryptogram);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_EncSessionKey + "=");
                sb.append(encSessionKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_KekSessionKey + "=");
                sb.append(kekSessionKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_KEK_DesKey + "=");
                sb.append(kek_wrapped_desKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_KeyCheck + "=");
                sb.append(keycheck_s);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_DRM_Trans_DesKey + "=");
                sb.append(drm_trans_wrapped_desKeyString);
                value = sb.toString();
            } else {
                StringBuffer sb = new StringBuffer();
                sb.append(IRemoteRequest.RESPONSE_STATUS + "=0&");
                sb.append(IRemoteRequest.TKS_RESPONSE_MacSessionKey + "=");
                sb.append(macSessionKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_HostCryptogram + "=");
                sb.append(cryptogram);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_EncSessionKey + "=");
                sb.append(encSessionKeyString);
                sb.append("&" + IRemoteRequest.TKS_RESPONSE_KekSessionKey + "=");
                value = sb.toString();
            }

        }
        //logger.debug(method + "outputString.encode " + value);

        try {
            resp.setContentLength(value.length());
            logger.debug("TokenServlet:outputString.length " + value.length());
            OutputStream ooss = resp.getOutputStream();
            ooss.write(value.getBytes());
            ooss.flush();
            mRenderResult = false;
        } catch (IOException e) {
            logger.warn("TokenServlet: " + e.getMessage(), e);
        }

        if (status.equals("0")) {
            ComputeSessionKeyRequestProcessedEvent event = ComputeSessionKeyRequestProcessedEvent.success(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    isCryptoValidate ? "true" : "false", // IsCryptoValidate
                    serversideKeygen ? "true" : "false", // IsServerSideKeygen
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // KeyInfo_KeyVersion
                    null,
                    null
            );

            signedAuditLogger.log(event);

        } else {
            ComputeSessionKeyRequestProcessedEvent event = ComputeSessionKeyRequestProcessedEvent.failure(
                    log_string_from_specialDecoded_byte_array(xCUID), // CUID_decoded
                    log_string_from_specialDecoded_byte_array(xKDD), // KDD_decoded
                    status, // status
                    agentId, // AgentID
                    isCryptoValidate ? "true" : "false", // IsCryptoValidate
                    serversideKeygen ? "true" : "false", // IsServerSideKeygen
                    selectedToken, // SelectedToken
                    keyNickName, // KeyNickName
                    keySet, // TKSKeyset
                    log_string_from_keyInfo(xkeyInfo), // KeyInfo_KeyVersion
                    null,
                    null,
                    errorMsg // Error
            );

            signedAuditLogger.log(event);

        }
    }

    /**
     * Serves HTTP admin request.
     *
     * @param req HTTP request
     * @param resp HTTP response
     */
    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        super.service(req, resp);
    }

    private PK11SymKey getSharedSecretKey() throws EBaseException, NotInitializedException {

        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        String sharedSecretName = null;
        try {

            sharedSecretName = getSharedSecretName(config);

        } catch (EBaseException e) {
            throw new EBaseException("TokenServlet.getSharedSecetKey: Internal error finding config value: "
                    + e);

        }

        logger.debug("TokenServlet.getSharedSecretTransportKey: calculated key name: " + sharedSecretName);

        String symmKeys = null;
        boolean keyPresent = false;
        try {
            symmKeys = SessionKey.ListSymmetricKeys(CryptoUtil.INTERNAL_TOKEN_NAME);
            logger.debug("TokenServlet.getSharedSecretTransportKey: symmKeys List: " + symmKeys);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            logger.warn("TokenServlet: " + e.getMessage(), e);
        }

        for (String keyName : symmKeys.split(",")) {
            if (sharedSecretName.equals(keyName)) {
                logger.debug("TokenServlet.getSharedSecret: shared secret key found!");
                keyPresent = true;
                break;
            }

        }

        if (!keyPresent) {
            throw new EBaseException("TokenServlet.getSharedSecret: Can't find shared secret!");
        }

        // We know for now that shared secret is on this token
        String tokenName = CryptoUtil.INTERNAL_TOKEN_FULL_NAME;
        PK11SymKey sharedSecret = SessionKey.GetSymKeyByName(tokenName, sharedSecretName);

        logger.debug("TokenServlet.getSharedSecret: SymKey returns: " + sharedSecret);

        return sharedSecret;

    }

    //returns ArrayList of following values
    // 0 : Kek wrapped des key
    // 1 : keycheck value
    // 2 : trans wrapped des key
    private ArrayList<String> calculateServerSideKeygenValues(String useSoftToken, String selectedToken,
            SymmetricKey kekSessionKey, SecureChannelProtocol protocol) throws EBaseException {

        SymmetricKey desKey = null;
        String method = "TokenServlet.calculateSErverSideKeygenValues: ";
        ArrayList<String> values = new ArrayList<>();

        /**
         * 0. generate des key
         * 1. encrypt des key with kek key
         * 2. encrypt des key with DRM transport key
         * These two wrapped items are to be sent back to
         * TPS. 2nd item is to DRM
         **/
        logger.debug(method + " entering...");

        // (1) generate DES key
        /* applet does not support DES3
        org.mozilla.jss.crypto.KeyGenerator kg =
            internalToken.getKeyGenerator(KeyGenAlgorithm.DES3);
            desKey = kg.generate();*/

        /*
         * GenerateSymkey firt generates a 16 byte DES2 key.
         * It then pads it into a 24 byte key with last
         * 8 bytes copied from the 1st 8 bytes.  Effectively
         * making it a 24 byte DES2 key.  We need this for
         * wrapping private keys on DRM.
         */
        /*generate it on whichever token the master key is at*/

        if (useSoftToken.equals("true")) {
            logger.debug(method + " key encryption key generated on internal");
            desKey = protocol.generateSymKey("internal");
            //cfu audit here? sym key gen done
        } else {
            logger.debug("TokenServlet: key encryption key generated on " + selectedToken);
            desKey = protocol.generateSymKey(selectedToken);
        }
        if (desKey == null) {
            throw new EBaseException(method + "can't generate key encryption key");
        }

        /*
         * ECBencrypt actually takes the 24 byte DES2 key
         * and discard the last 8 bytes before it encrypts.
         * This is done so that the applet can digest it
         */


       // protocol.wrapSessionKey(tokenName, sessionKey, wrappingKey)

        byte[] encDesKey = protocol.ecbEncrypt(kekSessionKey, desKey, selectedToken);

        String kek_wrapped_desKeyString =
                org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(encDesKey);

        //logger.debug(method + "kek_wrapped_desKeyString: " + kek_wrapped_desKeyString);

        values.add(kek_wrapped_desKeyString);

        // get keycheck

        byte[] keycheck = null;

        keycheck = protocol.computeKeyCheck(desKey, selectedToken);

        String keycheck_s =
                org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(keycheck);

        //logger.debug(method + "keycheck_s " + keycheck_s);

        values.add(keycheck_s);

        //use DRM transport cert to wrap desKey
        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        String drmTransNickname = config.getString("tks.drm_transport_cert_nickname", "");

        if ((drmTransNickname == null) || (drmTransNickname == "")) {
            logger.error(method + " did not find DRM transport certificate nickname");
            throw new EBaseException(method + "can't find DRM transport certificate nickname");
        } else {
            logger.debug(method + " drmtransport_cert_nickname=" + drmTransNickname);
        }

        X509Certificate drmTransCert = null;
        try {

            drmTransCert = CryptoManager.getInstance().findCertByNickname(drmTransNickname);
            // wrap kek session key with DRM transport public key
            CryptoToken token = null;
            if (useSoftToken.equals("true")) {
                //token = CryptoManager.getInstance().getTokenByName(selectedToken);
                token = CryptoManager.getInstance().getInternalCryptoToken();
            } else {
                token = CryptoManager.getInstance().getTokenByName(selectedToken);
            }
            PublicKey pubKey = drmTransCert.getPublicKey();
            String pubKeyAlgo = pubKey.getAlgorithm();
            logger.debug("Transport Cert Key Algorithm: " + pubKeyAlgo);
            KeyWrapper keyWrapper = null;
            //For wrapping symmetric keys don't need IV, use ECB
            if (pubKeyAlgo.equals("EC")) {
                keyWrapper = token.getKeyWrapper(KeyWrapAlgorithm.AES_ECB);
                keyWrapper.initWrap(pubKey, null);
            } else {

                boolean useOAEP = config.getBoolean("keyWrap.useOAEP",false);
                KeyWrapAlgorithm wrapAlg = KeyWrapAlgorithm.RSA;
                if(useOAEP == true) {
                    wrapAlg = KeyWrapAlgorithm.RSA_OAEP;
                }

                keyWrapper = token.getKeyWrapper(wrapAlg);
                OAEPParameterSpec params = null;
                if(useOAEP == true) {
                    params = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
                }
                keyWrapper.initWrap(pubKey, params);
            }
            logger.debug("desKey token " + desKey.getOwningToken().getName() + " token: " + token.getName());
            byte[] drm_trans_wrapped_desKey = keyWrapper.wrap(desKey);

            String drmWrappedDesStr =
                    org.mozilla.jss.netscape.security.util.Utils.SpecialEncode(drm_trans_wrapped_desKey);

            //logger.debug(method + " drmWrappedDesStr: " + drmWrappedDesStr);
            values.add(drmWrappedDesStr);

        } catch (Exception e) {
            throw new EBaseException(e);
        }

        return values;
    }

    private boolean cryptoGramsAreEqual(byte[] original_cryptogram, byte[] calculated_cryptogram) {
        boolean sameCardCrypto = true;

        if (original_cryptogram == null || calculated_cryptogram == null) {
            return false;
        }
        if (original_cryptogram.length == calculated_cryptogram.length) {
            for (int i = 0; i < original_cryptogram.length; i++) {
                if (original_cryptogram[i] != calculated_cryptogram[i]) {
                    sameCardCrypto = false;
                    break;
                }
            }
        } else {
            // different length; must be different
            sameCardCrypto = false;
        }

        return sameCardCrypto;
    }

  //For now only used for scp03

    static GPParams readGPSettings(String keySet) {
        GPParams params = new GPParams();

        String method = "TokenServlet.readGPSettings: ";
        String gp3Settings = "tks." + keySet + ".prot3";

        TKSEngine engine = TKSEngine.getInstance();
        TKSEngineConfig config = engine.getConfig();
        String divers = "emv";
        try {
            divers = config.getString(gp3Settings + ".divers", "emv");
        } catch (EBaseException e) {
        }

        params.setDiversificationScheme(divers);

        logger.debug(method + " Divers: " + divers);

        String diversVer1Keys = "emv";

        try {
            diversVer1Keys = config.getString(gp3Settings + ".diversVer1Keys","emv");
        } catch (EBaseException e) {
        }

        params.setVersion1DiversificationScheme(diversVer1Keys);
        logger.debug(method + " Version 1 keys Divers: " + divers);

        String keyType = null;
        try {
            keyType = config.getString(gp3Settings + ".devKeyType","DES3");
        } catch (EBaseException e) {
        }

        logger.debug(method + " devKeyType: " + keyType);

        params.setDevKeyType(keyType);

        try {
            keyType = config.getString(gp3Settings + ".masterKeyType","DES3");
        } catch (EBaseException e) {
        }

        params.setMasterKeyType(keyType);

        logger.debug(method + " masterKeyType: " + keyType);


        return params;
    }

    private byte[] getDeveKeyArray(String keyType,IConfigStore sconfig,String keySet) throws EBaseException {
        byte devKeyArray[] = null;
        try {
            devKeyArray = org.mozilla.jss.netscape.security.util.Utils.SpecialDecode(sconfig.getString("tks."
                    + keySet + "." + keyType));
        } catch (Exception e) {
            throw new EBaseException("Can't read static developer key array: " + keySet + ": " + keyType);
        }

        return devKeyArray;
    }


}
