package com.netscape.cms.servlet.tks;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.mozilla.jss.crypto.BadPaddingException;
import org.mozilla.jss.crypto.Cipher;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.crypto.EncryptionAlgorithm;
import org.mozilla.jss.crypto.HMACAlgorithm;
import org.mozilla.jss.crypto.IVParameterSpec;
import org.mozilla.jss.crypto.IllegalBlockSizeException;
import org.mozilla.jss.crypto.JSSMessageDigest;
import org.mozilla.jss.crypto.SymmetricKey;
import org.mozilla.jss.crypto.TokenException;

import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.base.EBaseException;

public class NistSP800_108KDF extends KDF {

    static final int KDF_OUTPUT_SIZE_BITS = 384;
    static final int KDF_OUTPUT_SIZE_BYTES = KDF_OUTPUT_SIZE_BITS / 8;
    static final int KEY_DATA_SIZE_BYTES = KDF_OUTPUT_SIZE_BYTES / 3;

    static final int KDD_SIZE_BYTES = 10; // expected KDD field length in bytes

    static final byte KDF_LABEL = 0x04; // arbitra

    //SCP03, AES related constants

    public static final int SHA256_LENGTH = 32;
    private static final int AES_CMAC_BLOCK_SIZE = 16;
    private static final byte AES_CMAC_CONSTANT = (byte) 0x87;
    public static final byte ENC_KDF_CONSTANT = (byte) 0x04;
    public static final byte MAC_KDF_CONSTANT = (byte) 0x06;
    public static final byte RMAC_KDF_CONSTANT = (byte) 0x07;
    public static final byte CARD_CRYPTO_KDF_CONSTANT = 0x0;
    public static final byte HOST_CRYPTO_KDF_CONSTANT = 0x1;

    static final int OFFSET_CHIP_BATCH = 4;
    static final int LENGTH_CHIP_BATCH = 2;
    static final int OFFSET_CHIP_SERIAL = 6;
    static final int LENGTH_CHIP_SERIAL = 4;

    SecureChannelProtocol protocol = null;

    NistSP800_108KDF(SecureChannelProtocol protocol) {
        this.protocol = protocol;
    }

    static public boolean useThisKDF(byte nistSP800_108KDFonKeyVersion, byte requestedKeyVersion) {
        return ((requestedKeyVersion & 0xFF) >= (nistSP800_108KDFonKeyVersion & 0xFF));
    }

    /*******************************************************************************
     Generates three PK11SymKey objects using the KDF_CM_SHA256HMAC_L384() function for key data.
     After calling KDF_CM_SHA256HMAC_L384, the function splits up the output, sets DES parity,
       and imports the keys into the token.

     Careful:  This function currently generates the key data **IN RAM** using calls to NSS sha256.
                The key data is then "unwrapped" (imported) to the NSS token and then erased from RAM.
                (This means that a malicious actor on the box could steal the key data.)

     Note: Returned key material from the KDF is converted into keys according to the following:
       * Bytes 0  - 15 : enc/auth key
       * Bytes 16 - 31 : mac key
       * Bytes 32 - 47 : kek key
       We chose this order to conform with the key order used by the PUT KEY command.

    *******************************************************************************/

    public Map<String, SymmetricKey> computeCardKeys(SymmetricKey masterKey, byte[] context, CryptoToken token)
            throws EBaseException {

        String method = "NistSP800_108KDF.computeCardKeys:";

        if (masterKey == null || context == null || token == null) {
            throw new EBaseException(method + " Invalid input parameters!");
        }

        Map<String, SymmetricKey> keys = new HashMap<String, SymmetricKey>();

        byte[] kdf_output = null;

        kdf_output = kdf_CM_SHA256_HMAC_L384(masterKey, context, KDF_LABEL, KDF_OUTPUT_SIZE_BYTES, token);

        //Now create the 3 keys from only 48 of the 64 bytes...

        byte[] enc = new byte[16];
        byte[] mac = new byte[16];
        byte[] kek = new byte[16];

        System.arraycopy(kdf_output, 0 * SecureChannelProtocol.DES2_LENGTH, enc, 0, SecureChannelProtocol.DES2_LENGTH);
        System.arraycopy(kdf_output, 1 * SecureChannelProtocol.DES2_LENGTH, mac, 0, SecureChannelProtocol.DES2_LENGTH);
        System.arraycopy(kdf_output, 2 * SecureChannelProtocol.DES2_LENGTH, kek, 0, SecureChannelProtocol.DES2_LENGTH);

        byte[] encFinal = KDF.getDesParity(enc);
        byte[] macFinal = KDF.getDesParity(mac);
        byte[] kekFinal = KDF.getDesParity(kek);

        boolean showKeysOnlyForDebug = false;

        if (showKeysOnlyForDebug) {

            SecureChannelProtocol.debugByteArray(kdf_output, "kdf_CM_SHA256_HMAC_L384 output: ");

            SecureChannelProtocol.debugByteArray(mac, " Nist mac before parity: ");
            SecureChannelProtocol.debugByteArray(enc, " Nist enc before parity: ");
            SecureChannelProtocol.debugByteArray(kek, " Nist kek before parityl: ");


            SecureChannelProtocol.debugByteArray(macFinal, " Nist macFinal: ");
            SecureChannelProtocol.debugByteArray(encFinal, " Nist encFinal: ");
            SecureChannelProtocol.debugByteArray(kekFinal, " Nist kekFinal: ");
        }

        Arrays.fill(enc, (byte) 0);
        Arrays.fill(mac, (byte) 0);
        Arrays.fill(kek, (byte) 0);
        Arrays.fill(kdf_output, (byte) 0);

        SymmetricKey macKey = protocol.unwrapSymKeyOnToken(token, null, macFinal, false,SymmetricKey.DES3);
        SymmetricKey encKey = protocol.unwrapSymKeyOnToken(token, null, encFinal, false,SymmetricKey.DES3);
        SymmetricKey kekKey = protocol.unwrapSymKeyOnToken(token, null, kekFinal, false,SymmetricKey.DES3);

        Arrays.fill(encFinal, (byte) 0);
        Arrays.fill(macFinal, (byte) 0);
        Arrays.fill(kekFinal, (byte) 0);

        keys.put(SecureChannelProtocol.macType, macKey);
        keys.put(SecureChannelProtocol.encType, encKey);
        keys.put(SecureChannelProtocol.kekType, kekKey);

        return keys;

    }

    //Compute the AES based CMAC operation. Used to derive session keys and cryptograms
    public byte[] kdf_AES_CMAC_SCP03(SymmetricKey masterKey, byte[] context, byte kdfConstant,
            int kdfOutputSizeBytes) throws EBaseException {

        String method = "NistSP800_108KDF.kdf_AES_CMAC_SCP03:";
        // 11 bytes label
        byte[] label = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

        // sanity checking

        if (masterKey == null || context == null || kdfOutputSizeBytes <= 0) {
            throw new EBaseException(method + " Invalid input!");
        }

        ByteArrayOutputStream data = new ByteArrayOutputStream();

        int outputBits = kdfOutputSizeBytes * 8;

        //output size of cmac PRF
        final int h = 128;

        int remainder = outputBits % h;

        //calculate counter size
        int n = 0;
        if (remainder == 0) {
            n = outputBits / h;
        } else {
            n = outputBits / h + 1;
        }

        byte b1 = (byte) ((outputBits >> 8) & 0xFF);
        byte b2 = (byte) (outputBits & 0xFF);

        byte[] outputBitsBinary = new byte[2];
        outputBitsBinary[0] = b1;
        outputBitsBinary[1] = b2;

        try {
            data.write(label);
            data.write(kdfConstant);
            data.write(0x0);
            data.write(outputBitsBinary);
        } catch (IOException e) {
            throw new EBaseException(method + "Unable to calculate kdf!");
        }

        byte[] headerBytes = data.toByteArray();

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        ByteArrayOutputStream input = new ByteArrayOutputStream();

        byte[] kI = null;
        for (int i = 1; i <= n; i++) {

            try {
                input.write(headerBytes);
                input.write((byte) i);
                input.write(context);

                kI = computeAES_CMAC(masterKey, input.toByteArray());

                output.write(kI);

            } catch (IOException e) {
                throw new EBaseException(method + "Unable to calculate kdf!");
            }

        }

        return output.toByteArray();
    }

    /**
     *  Diversify the provided AES key using the GP KMC diversification method, which makes use
     *  of the NIST SP800-108 KDF in Counter Mode but with a different ordering and length of the
     *  input to the KDF.
     */
    public SymmetricKey diversifyAESKey(
            SymmetricKey staticKey,
            byte[] tokenCUID,
            byte derivationConstant,
            CryptoToken token) throws EBaseException {

        String method = "NistSP800_108KDF.diversifyAESKey: ";

        // Input checks
        if(staticKey == null || staticKey.getType() != SymmetricKey.Type.AES ||
           tokenCUID == null || tokenCUID.length < 10) {
            throw new EBaseException(method + "Invalid input.");
        }

        // Assemble the diversification context
        byte[] context = new byte[8];
        context[0] = 0; // 2nd to last byte of card manager AID
        context[1] = 0; // Last byte of card manager AID
        System.arraycopy(tokenCUID, OFFSET_CHIP_SERIAL, context, 2, LENGTH_CHIP_SERIAL); // Chip serial number
        System.arraycopy(tokenCUID, OFFSET_CHIP_BATCH, context, 2 + LENGTH_CHIP_SERIAL, LENGTH_CHIP_BATCH); // Chip batch number

        //CMS.debug(method + "Diversification context: " + SecureChannelProtocol.getHexString(context));
        //CMS.debug(method + "Derivation constant:     " + derivationConstant);

        // Number of KDF iterations is based on the length of the static key
        int iterations = staticKey.getLength() / AES_CMAC_BLOCK_SIZE;
        if(staticKey.getLength() % AES_CMAC_BLOCK_SIZE != 0)
            iterations++;

        // Run CMAC the necessary number of times
        ByteArrayOutputStream cmacOutput = new ByteArrayOutputStream(AES_CMAC_BLOCK_SIZE * iterations);
        for(int i = 0; i < iterations; i++) {
            byte[] cmacInput = new byte[16];

            cmacInput[0] = (byte)(i + 1);       // Counter/iteration variable "i"
            cmacInput[1] = 0;
            cmacInput[2] = 0;
            cmacInput[3] = 0;
            cmacInput[4] = derivationConstant;  // Label (index 1-4)
            cmacInput[5] = 0;                   // Separation indicator
            System.arraycopy(context, 0, cmacInput, 6, context.length); // Context
            cmacInput[14] = (byte)(staticKey.getLength() * 8 >> 8);     // Output bit length high byte
            cmacInput[15] = (byte)(staticKey.getLength() * 8 & 0xFF);   // Output bit length low byte

            try {
                cmacOutput.write(computeAES_CMAC(staticKey, cmacInput));
            } catch (IOException e) {
                throw new EBaseException(method + "Error computing CMAC output.");
            }
        }

        //CMS.debug(method + "Final diversified key: " + SecureChannelProtocol.getHexString(cmacOutput.toByteArray()));

        return this.protocol.unwrapAESSymKeyOnToken(token, cmacOutput.toByteArray(), false);
    }

    /*******************************************************************************
     Key Derivation Function in Counter Mode using PRF = SHA256HMAC (NIST SP 800-108)
       Calculates 384 bits of diversified output from the provided master key (K_I)
    *******************************************************************************/

    private byte[] kdf_CM_SHA256_HMAC_L384(SymmetricKey masterKey, byte[] context, byte kdfLabel,
            int kdfOutputSizeBytes, CryptoToken token) throws EBaseException {

        String method = "NistSP800_108KDF.kdf_CM_SHA256_HMAC_L384:";

        CMS.debug(method + " entering..");
        final byte n = 2; // ceil(384 / (SHA256LENGTH * 8)) == 2
        int L_BYTE_array_length = 2; // 384 = 0x0180 hex; 2 byte long representation

        if (context == null) {
        }
        // sanity check that output buffer is large enough to contain 384 bits
        if (kdfOutputSizeBytes < KDF_OUTPUT_SIZE_BYTES) {
            throw new EBaseException(method + " Array \"output\" must be at least 48 bytes in size.");
        }

        // calculate size of temporary buffer
        int HMAC_DATA_INPUT_SIZE = context.length + 3 + L_BYTE_array_length; // Don't change without reviewing code below.

        // prevent integer overflow
        if (HMAC_DATA_INPUT_SIZE < context.length) {
            throw new EBaseException(method + " Input parameter context size is too large.");
        }

        byte L_BYTE_array[] = new byte[L_BYTE_array_length]; // Array to store L in BYTES
        L_BYTE_array[0] = 0x01;
        L_BYTE_array[1] = (byte) 0x80;

        // Hash Input = context + 5 BYTES
        byte[] hmac_data_input = new byte[HMAC_DATA_INPUT_SIZE];

        byte[] K = new byte[n * SHA256_LENGTH];

        hmac_data_input[1] = kdfLabel;
        hmac_data_input[2] = 0x0;
        System.arraycopy(context, 0, hmac_data_input, 3, context.length);
        System.arraycopy(L_BYTE_array, 0, hmac_data_input, context.length + 3, 2);

        byte[] outputHMAC256 = null;

        for (byte i = 1; i <= n; i++) {
            hmac_data_input[0] = i;
            outputHMAC256 = sha256HMAC(masterKey, hmac_data_input, HMAC_DATA_INPUT_SIZE, token);
            CMS.debug(method + "outputHMAC256 len: " + outputHMAC256.length);
            System.arraycopy(outputHMAC256, 0, K, (i - 1) * SHA256_LENGTH, SHA256_LENGTH);
            Arrays.fill(outputHMAC256, (byte)0);
        }

        CMS.debug(method + " Full array: " + K.length + " bytes...");

        byte[] finalOutput = new byte[KDF_OUTPUT_SIZE_BYTES];

        System.arraycopy(K, 0, finalOutput, 0, KDF_OUTPUT_SIZE_BYTES);

        Arrays.fill(K, (byte) 0);

        CMS.debug(method + " finalOutput: " + finalOutput.length + " bytes...");

        return finalOutput;
    }

    private byte[] sha256HMAC(SymmetricKey masterKey, // HMAC Secret Key (K_I)
            byte[] hmac_data_input, // HMAC Input (i||04||00||context||0180)
            int hMAC_DATA_INPUT_SIZE, // Input Length
            CryptoToken token) throws EBaseException {

        String method = "NistSP800_108KDF.sha256HMAC:";

        CMS.debug(method + " Entering...");

        byte[] digestBytes = null;

        if (token == null) {
            throw new EBaseException(method = " Invalid Crypto Token input!");
        }

        try {
            JSSMessageDigest digest = token.getDigestContext(HMACAlgorithm.SHA256);
            digest.initHMAC(masterKey);

            digestBytes = digest.digest(hmac_data_input);
        } catch (Exception e) {

            CMS.debug(method + " Failure to HMAC the input data: " + e);
            throw new EBaseException(method + e);
        }

        // SecureChannelProtocol.debugByteArray(digestBytes, " output of sha256HMAC: ");

        return digestBytes;
    }

    // Implements agorithm http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38b.pdf
    // Input an aes key of 128, 192, or 256 bits
    // For now calling code only using 128
    // Will move later to common class used by both tks and tps

    public static byte[] computeAES_CMAC(SymmetricKey aesKey, byte[] input) throws EBaseException {

        String method = "NistSP800_108KDF.computeAES_CMAC:";
        byte iv[] = null;

        if (aesKey == null || input == null) {
            throw new EBaseException(method + " invalid input data!");
        }

        byte[] data = new byte[input.length];
        System.arraycopy(input, 0, data, 0, input.length);

        String alg = aesKey.getAlgorithm();
        System.out.println(" AES ALG: " + alg);

        EncryptionAlgorithm eAlg = EncryptionAlgorithm.AES_128_CBC;
        int ivLength = eAlg.getIVLength();

        if (ivLength > 0) {
            iv = new byte[ivLength];
        }

        if (!("AES".equals(alg))) {
            throw new EBaseException(method + " invalid in put key type , must be AES!");
        }

        byte[] k0 = new byte[AES_CMAC_BLOCK_SIZE];

        //Encrypt the zero array
        CryptoToken token = aesKey.getOwningToken();
        Cipher encryptor = null;

        try {
            encryptor = token.getCipherContext(EncryptionAlgorithm.AES_128_CBC);
            encryptor.initEncrypt(aesKey, new IVParameterSpec(iv));
            k0 = encryptor.doFinal(k0);

        } catch (NoSuchAlgorithmException | TokenException | IllegalStateException | IllegalBlockSizeException
                | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new EBaseException(e);
        }

        byte[] k1 = getAES_CMAC_SubKey(k0);
        byte[] k2 = getAES_CMAC_SubKey(k1);

        int numBlocks = 0;
        int messageSize = data.length;;
        boolean perfectBlocks = false;

        if (((messageSize % AES_CMAC_BLOCK_SIZE) == 0) && (messageSize != 0)) {
            numBlocks = messageSize / AES_CMAC_BLOCK_SIZE;
            perfectBlocks = true;
        }
        else {
            numBlocks = messageSize / AES_CMAC_BLOCK_SIZE + 1;
            perfectBlocks = false;
        }

        int index = 0;
        byte inb = 0;

        byte[] finalData = null;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );

        if (perfectBlocks == true)
        {
            // If the size of the message is an integer multiple of the block  block size (namely, 128 bits) (16 bytes)
            // the last block shall be exclusive-OR'ed with the first subKey k1

            for (int j = 0; j < k1.length; j++) {
                index = messageSize - AES_CMAC_BLOCK_SIZE + j;
                inb = data[index];
                data[index] = (byte) (inb ^ k1[j]);
            }
            try {
                outputStream.write(data);
            } catch (IOException e) {
                throw new EBaseException(method + " internal buffer erro!");
            }
            finalData = outputStream.toByteArray();
        }
        else
        {
            // Otherwise, the last block shall be padded with 10^i
            byte[] padding = new byte[AES_CMAC_BLOCK_SIZE - messageSize % AES_CMAC_BLOCK_SIZE];
            padding[0] = (byte) 0x80;

            try {
                outputStream.write(data);
                outputStream.write(padding);
            } catch (IOException e) {
                throw new EBaseException(method + " internal buffer error!");
            }

            finalData = outputStream.toByteArray();

            //Get new data size , it's changed
            messageSize = finalData.length;

            // and exclusive-OR'ed with K2
            for (int j = 0; j < k2.length; j++) {
                index = messageSize - AES_CMAC_BLOCK_SIZE + j;
                inb = finalData[index];
                finalData[index] = (byte) (inb ^ k2[j]);
            }
        }

        // Initialization vector starts as zeroes but changes inside the loop's
        // subsequent iterations, it becomes the last encryption output
        byte[] encData = new byte[AES_CMAC_BLOCK_SIZE];
        byte[] currentBlock = new byte[AES_CMAC_BLOCK_SIZE];
        for (int i = 0; i < numBlocks; i++) {
            try {
                encryptor.initEncrypt(aesKey, new IVParameterSpec(encData));
                System.arraycopy(finalData, i * AES_CMAC_BLOCK_SIZE, currentBlock, 0, AES_CMAC_BLOCK_SIZE);
                encData = encryptor.doFinal(currentBlock);
            } catch (TokenException | IllegalStateException | IllegalBlockSizeException
                    | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
                throw new EBaseException(e);
            }
        }

        return encData;

    }

    // SCP03 AES-CMAC support function
    private static byte[] getAES_CMAC_SubKey(byte[] input) {

        byte[] output = new byte[input.length];

        boolean msbSet = ((input[0]&0x80) != 0);
        for (int i=0; i<input.length; i++) {
            output[i] = (byte) (input[i] << 1);
            if (i+1 < input.length && ((input[i+1]&0x80) != 0)) {
                output[i] |= 0x01;
            }
        }
        if (msbSet) {
            output[output.length-1] ^= AES_CMAC_CONSTANT;
        }
        return output;
    }

    // Collection of informal invocations of api used to create various session keys
    // Done with test data.
    public static void main(String[] args) {

  /*   Options options = new Options();

        options.addOption("d", true, "Directory for tokendb");

        String db_dir = null;
        CryptoManager cm = null;

        byte devKey[] = { (byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46,
                (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x4a, (byte) 0x4b, (byte) 0x4c, (byte) 0x4d, (byte) 0x4e,
                (byte) 0x4f };

        byte test_cuid[] = { (byte) 0x47,(byte) 0x90,(byte)0x50,(byte)0x37,(byte)0x72,(byte)0x71,(byte)0x97,(byte)0x00,(byte)0x74,(byte)0xA9 };
        byte test_kdd[] = { 0x00 ,0x00, 0x04 ,(byte)0x47 ,0x00 ,(byte)0x1F ,0x00 ,(byte)0x46 ,(byte)0xA7 ,0x02 };


        byte test_host_challenge[]  = { (byte)0x2F ,(byte)0xB7 ,(byte)0x9F ,(byte)0xB7 ,(byte)0x04 ,(byte)0xFA ,(byte)0x60 ,(byte)0xE8 };
        byte test_card_challenge[]  = { (byte)0xB9,(byte) 0x69 ,(byte)0xB0 ,(byte)0xCA ,(byte)0x37 ,(byte)0x27 ,(byte)0x2F ,(byte)0x89};

        byte test_host_challenge_1[] = { (byte)0xD9 ,(byte)0xA0 ,(byte)0x0E ,(byte)0x36 ,(byte)0x69 ,(byte)0x67 ,(byte)0xFA ,(byte)0xFB };
        byte test_card_challenge_1[] = {(byte)0x08 ,(byte) 0xF3 ,(byte) 0xE2 ,(byte)0xC3 ,0x72 ,(byte)0xF0 ,(byte)0xBE ,0x26 };

        byte test_key_info[] = { (byte) 0x01,(byte) 03,(byte) 70 };
        byte test_old_key_info[] = {0x01,0x03,0x00};

        byte test_sequence_counter[] = { 0x00 ,0x00 ,0x06 };

        try {
            CommandLineParser parser = new DefaultParser();
            CommandLine cmd = parser.parse(options, args);

            if (cmd.hasOption("d")) {
                db_dir = cmd.getOptionValue("d");
            }

        } catch (ParseException e) {
            System.err.println("Error in parsing command line options: " + e.getMessage());

        }

        SymmetricKey encKey = null;
        SymmetricKey macKey = null;
        SymmetricKey kekKey = null;


        try {
            CryptoManager.initialize(db_dir);
            cm = CryptoManager.getInstance();

            CryptoToken token = cm.getInternalKeyStorageToken();

            String transportName = "TPS-dhcp-16-206.sjc.redhat.com-8443 sharedSecret";
            SecureChannelProtocol prot = new SecureChannelProtocol(SecureChannelProtocol.PROTOCOL_THREE);

            SymmetricKey masterKey =  SecureChannelProtocol.getSymKeyByName(token,"new_master");

            GPParams params = new GPParams();
            params.setVersion1DiversificationScheme("emv");
            params.setDiversificationScheme("emv");
            params.setDevKeyType(GPParams.AES);
            params.setMasterKeyType(GPParams.AES);

            //create test session keys
            encKey = prot.computeSessionKey_SCP03("internal", "#01#03#70",test_key_info,
                    SecureChannelProtocol.encType, devKey, "defKeySet", test_cuid, test_kdd, test_host_challenge_1, test_card_challenge_1,
                    transportName,params);

            macKey = prot.computeSessionKey_SCP03("internal", "#01#03#70",test_key_info,
                    SecureChannelProtocol.macType,devKey,"defKeySet", test_cuid, test_kdd, test_host_challenge_1, test_card_challenge_1,
                    transportName,params);

            kekKey = prot.computeSessionKey_SCP03("internal", "#01#03#70",test_key_info,
                    SecureChannelProtocol.kekType, devKey, "defKeySet", test_cuid, test_kdd, test_host_challenge_1, test_card_challenge_1,
                    transportName,params);

            System.out.println("masterKey: " + masterKey);

            System.out.println("\n");


            SecureChannelProtocol.debugByteArray(encKey.getKeyData(), " derived enc session key data: ");
            SecureChannelProtocol.debugByteArray(macKey.getKeyData(), " derived mac session key data: ");
            SecureChannelProtocol.debugByteArray(kekKey.getKeyData(), " derived kek session key data: ");


            ByteArrayOutputStream testContext = new ByteArrayOutputStream();

            testContext.write(test_host_challenge_1);
            testContext.write(test_card_challenge_1);

            SecureChannelProtocol.debugByteArray(testContext.toByteArray(), "Test context bytes: ");


            NistSP800_108KDF  nistKdf = new NistSP800_108KDF(prot);


            byte[] cardCryptoVerify = nistKdf.kdf_AES_CMAC_SCP03(macKey, testContext.toByteArray(), CARD_CRYPTO_KDF_CONSTANT, 8);
            SecureChannelProtocol.debugByteArray(cardCryptoVerify, " calculated card cryptogram");

            byte[] hostCrypto = nistKdf.kdf_AES_CMAC_SCP03(macKey, testContext.toByteArray(), HOST_CRYPTO_KDF_CONSTANT, 8);
            SecureChannelProtocol.debugByteArray(hostCrypto, " calculated host cryptogram");

        } catch (AlreadyInitializedException e) {
            // it is ok if it is already initialized
        } catch (Exception e) {
            System.err.println("JSS error!" + e);
            System.exit(1);
        }
*/
    }
}
