All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.crypto.fips.FipsKDF Maven / Gradle / Ivy

Go to download

The FIPS 140-3 Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms certified to FIPS 140-3 level 1. This jar contains JCE provider and low-level API for the BC-FJA version 2.0.0, FIPS Certificate #4743. Please see certificate for certified platform details.

There is a newer version: 2.0.0
Show newest version
package org.bouncycastle.crypto.fips;

import java.math.BigInteger;

import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.KDFCalculator;
import org.bouncycastle.crypto.KDFOperatorFactory;
import org.bouncycastle.crypto.Parameters;
import org.bouncycastle.crypto.internal.BlockCipher;
import org.bouncycastle.crypto.internal.DerivationFunction;
import org.bouncycastle.crypto.internal.Digest;
import org.bouncycastle.crypto.internal.EngineProvider;
import org.bouncycastle.crypto.internal.Mac;
import org.bouncycastle.crypto.internal.StreamCipher;
import org.bouncycastle.crypto.internal.macs.HMac;
import org.bouncycastle.crypto.internal.modes.SICBlockCipher;
import org.bouncycastle.crypto.internal.params.KDFCounterParameters;
import org.bouncycastle.crypto.internal.params.KDFDoublePipelineIterationParameters;
import org.bouncycastle.crypto.internal.params.KDFFeedbackParameters;
import org.bouncycastle.crypto.internal.params.KDFParameters;
import org.bouncycastle.crypto.internal.params.KeyParameterImpl;
import org.bouncycastle.crypto.internal.params.ParametersWithIV;
import org.bouncycastle.crypto.internal.test.BasicKatTest;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Properties;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;

/**
 * Source class for FIPS approved Key Derivation Function (KDF) implementations.
 */
public final class FipsKDF
{
    private static final byte[] ZERO_BYTE = new byte[1];

    private FipsKDF()
    {
    }

    /**
     * Algorithm parameter source for NIST SP 800-108 KDF in Counter Mode.
     */
    public static final CounterModeParametersBuilder COUNTER_MODE = new CounterModeParametersBuilder(new FipsAlgorithm("CounterMode"));

    /**
     * Algorithm parameter source for NIST SP 800-108 KDF in Feedback Mode.
     */
    public static final FeedbackModeParametersBuilder FEEDBACK_MODE = new FeedbackModeParametersBuilder(new FipsAlgorithm("FeedbackMode"));

    /**
     * Algorithm parameter source for NIST SP 800-108 KDF in Double-Pipeline Mode.
     */
    public static final DoublePipelineModeParametersBuilder DOUBLE_PIPELINE_ITERATION_MODE = new DoublePipelineModeParametersBuilder(new FipsAlgorithm("DoublePipelineIterationMode"));

    /**
     * Algorithm parameter source for Secure Shell (SSH)
     */
    public static final SSHParametersBuilder SSH = new SSHParametersBuilder(new FipsAlgorithm("SSH"), SSHPRF.SHA1);

    /**
     * Algorithm parameter source for Internet Key Exchange Version 2 (IKEv2)
     */
    public static final IKEv2ParametersBuilder IKEv2 = new IKEv2ParametersBuilder(new FipsAlgorithm("IKEv2"), IKEv2PRF.SHA1);

    /**
     * Algorithm parameter source for Secure Real-time Transport Protocol (SRTP)
     */
    public static final SRTPParametersBuilder SRTP = new SRTPParametersBuilder(new FipsAlgorithm("SRTP"), SRTPPRF.AES_CM);

    /**
     * Algorithm parameter source for Transport Layer Security Version 1.0 (TLSv1.0)
     */
    public static final TLSParametersBuilder TLS1_0 = new TLSParametersBuilder(new FipsAlgorithm("TLS1.0"));

    /**
     * Algorithm parameter source for Transport Layer Security Version 1.1 (TLSv1.1)
     */
    public static final TLSParametersBuilder TLS1_1 = new TLSParametersBuilder(new FipsAlgorithm("TLS1.1"));

    /**
     * Algorithm parameter source for Transport Layer Security Version 1.2 (TLSv1.2)
     */
    public static final TLSParametersWithPRFBuilder TLS1_2 = new TLSParametersWithPRFBuilder(new FipsAlgorithm("TLS1.2"), TLSPRF.SHA256_HMAC);

    /**
     * Algorithm parameter source for ASN X9.63-2001 - default PRF is SHA-1
     */
    public static final AgreementKDFParametersBuilder X963 = new AgreementKDFParametersBuilder(new FipsAlgorithm("X9.63"), AgreementKDFPRF.SHA1);

    /**
     * Algorithm parameter source for concatenating KDF in FIPS SP 800-56A/B - default PRF is SHA-1
     */
    public static final AgreementKDFParametersBuilder CONCATENATION = new AgreementKDFParametersBuilder(new FipsAlgorithm("Concatenation"), AgreementKDFPRF.SHA1);

    /**
     * An enumeration of the FIPS approved psuedo-random-function (PRF) for KDFs used with SP 800-108.
     */
    public enum PRF {
        AES_CMAC(FipsAES.CMAC.getAlgorithm()),
        TRIPLEDES_CMAC(FipsTripleDES.CMAC.getAlgorithm()),
        SHA1_HMAC(FipsSHS.Algorithm.SHA1_HMAC),
        SHA224_HMAC(FipsSHS.Algorithm.SHA224_HMAC),
        SHA256_HMAC(FipsSHS.Algorithm.SHA256_HMAC),
        SHA384_HMAC(FipsSHS.Algorithm.SHA384_HMAC),
        SHA512_HMAC(FipsSHS.Algorithm.SHA512_HMAC),
        SHA512_224_HMAC(FipsSHS.Algorithm.SHA512_224_HMAC),
        SHA512_256_HMAC(FipsSHS.Algorithm.SHA512_256_HMAC);

        private final FipsAlgorithm algorithm;

        PRF(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public FipsAlgorithm getAlgorithm()
        {
            return algorithm;
        }
    }

    static
    {
        // FSM_STATE:3.KBKDF.0,"KBKDF GENERATE KAT", "The module is performing KBKDF generate KAT self-test"
        // FSM_TRANS:3.KBKDF.0,"POWER ON SELF-TEST", "PBKDF GENERATE KAT",	"Invoke KBKDF Generate KAT self-test"
        new CounterModeProvider(PRF.AES_CMAC).createEngine();
        new CounterModeProvider(PRF.TRIPLEDES_CMAC).createEngine();
        new CounterModeProvider(PRF.SHA1_HMAC).createEngine();
        new CounterModeProvider(PRF.SHA224_HMAC).createEngine();
        new CounterModeProvider(PRF.SHA256_HMAC).createEngine();
        new CounterModeProvider(PRF.SHA384_HMAC).createEngine();
        new CounterModeProvider(PRF.SHA512_HMAC).createEngine();
        new CounterModeProvider(PRF.SHA512_224_HMAC).createEngine();
        new CounterModeProvider(PRF.SHA512_256_HMAC).createEngine();

        new FeedbackModeProvider(PRF.AES_CMAC).createEngine();
        new FeedbackModeProvider(PRF.TRIPLEDES_CMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA1_HMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA224_HMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA256_HMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA384_HMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA512_HMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA512_224_HMAC).createEngine();
        new FeedbackModeProvider(PRF.SHA512_256_HMAC).createEngine();

        new DoublePipelineModeProvider(PRF.AES_CMAC).createEngine();
        new DoublePipelineModeProvider(PRF.TRIPLEDES_CMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA1_HMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA224_HMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA256_HMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA384_HMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA512_HMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA512_224_HMAC).createEngine();
        new DoublePipelineModeProvider(PRF.SHA512_256_HMAC).createEngine();

        tlsLegacyKAT();   // full KAT test - not just MD5
        tls1_1and2KAT();
        sshKAT();
        // FSM_TRANS:3.KBKDF.1, "KBKDF GENERATE KAT", "POWER ON SELF-TEST", "KBKDF Generate KAT self-test successful completion"
    }

    /**
     * Parameters for the Counter Mode parameters builder.
     */
    public static final class CounterModeParametersBuilder
        extends FipsParameters
    {
        private final PRF prf;
        private final int r;

        CounterModeParametersBuilder(FipsAlgorithm algorithm)
        {
            this(algorithm, PRF.SHA1_HMAC, 8);
        }

        private CounterModeParametersBuilder(FipsAlgorithm algorithm, PRF prf, int r)
        {
            super(algorithm);
            this.prf = prf;
            this.r = r;
        }

        /**
         * Return a new parameters builder based around the passed in PRF and counter size.
         *
         * @param prf the PRF to be used in the final KDF.
         * @param r the length in bits of the counter to be used.
         * @return a new parameters builder.
         */
        public CounterModeParametersBuilder withPRFAndR(PRF prf, int r)
        {
            return new CounterModeParametersBuilder(getAlgorithm(), prf, r);
        }

        /**
         * Return a new parameter set for ki and a prefix.
         *
         * @param ki derivation key for the KDF.
         * @param fixedInputPrefix prefix data to come before the counter during calculation.
         * @return a CounterModeParameters object.
         */
        public CounterModeParameters using(byte[] ki, byte[] fixedInputPrefix)
        {
            return new CounterModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, Arrays.clone(ki), Arrays.clone(fixedInputPrefix), null);
        }

        /**
         * Return a new parameter set for ki and the prefix/suffix data.
         *
         * @param ki derivation key for the KDF.
         * @param fixedInputPrefix prefix data to come before the counter during calculation.
         * @param fixedInputSuffix suffix data to come after the counter during calculation.
         * @return a CounterModeParameters object.
         */
        public CounterModeParameters using(byte[] ki, byte[] fixedInputPrefix, byte[] fixedInputSuffix)
        {
            return new CounterModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, Arrays.clone(ki), Arrays.clone(fixedInputPrefix), Arrays.clone(fixedInputSuffix));
        }

        /**
         * Build method for parameters which builds fixed input as outlined in SP 800-108 with the fixed input
         * as a prefix, or suffix, to the counter.
         *
         * @param ki  input key.
         * @param isPrefix is the fixed input a prefix or a suffix.
         * @param label label - fixed input component.
         * @param context context - fixed input component.
         * @param L number of bits per request for the KDF these parameters will initialise - fixed input component.
         * @return a CounterModeParameters object.
         */
        public CounterModeParameters using(byte[] ki, boolean isPrefix, byte[] label, byte[] context, int L)
        {
            return new CounterModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, Arrays.clone(ki), (isPrefix ? buildFixedInput(label, context, L) : null), (isPrefix ? null : buildFixedInput(label, context, L)));
        }
    }

    /**
     * Parameters for the Counter Mode key derivation function.
     */
    public static final class CounterModeParameters
        extends FipsParameters
    {
        final int r;
        final byte[] ki;
        final byte[] fixedInputPrefix;
        final byte[] fixedInputSuffix;

        private CounterModeParameters(FipsAlgorithm algorithm, int r, byte[] ki, byte[] fixedInputPrefix, byte[] fixedInputSuffix)
        {
            super(algorithm);

            this.r = r;
            this.ki = ki;
            this.fixedInputPrefix = fixedInputPrefix;
            this.fixedInputSuffix = fixedInputSuffix;
        }
    }

    /**
     * Factory for Counter Mode KDFs.
     */
    public static final class CounterModeFactory
        extends FipsKDFOperatorFactory
    {
        public CounterModeFactory()
        {
        }

        public KDFCalculator createKDFCalculator(final CounterModeParameters params)
        {
            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            final KDFCounterBytesGenerator kdfGenerator = new CounterModeProvider(params.getAlgorithm()).createEngine();

            kdfGenerator.init(new KDFCounterParameters(params.ki, params.fixedInputPrefix, params.fixedInputSuffix, params.r));

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public CounterModeParameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    kdfGenerator.generateBytes(out, outOff, len);
                }
            });
        }
    }

    /**
     * An enumeration of the counter locations for Feedback Mode and Double Pipeline Iteration Mode.
     */
    public enum CounterLocation
    {
        AFTER_ITERATION_DATA(KDFFeedbackParameters.AFTER_ITER),
        AFTER_FIXED_INPUT(KDFFeedbackParameters.AFTER_FIXED),
        BEFORE_ITERATION_DATA(KDFFeedbackParameters.BEFORE_ITER);

        private final int code;

        CounterLocation(int code)
        {
            this.code = code;
        }
    }

    /**
     * Parameters for the Feedback Mode parameters builder.
     */
    public static final class FeedbackModeParametersBuilder
        extends FipsParameters
    {
        private final PRF prf;
        private final int r;
        private final CounterLocation counterLocation;

        FeedbackModeParametersBuilder(FipsAlgorithm algorithm)
        {
            this(algorithm, PRF.SHA1_HMAC, -1, null);
        }

        private FeedbackModeParametersBuilder(FipsAlgorithm algorithm, PRF prf, int r, CounterLocation counterLocation)
        {
            super(algorithm);
            this.prf = prf;
            this.r = r;
            this.counterLocation = counterLocation;
        }

        /**
         * Return a new parameters builder based around the passed in PRF.
         *
         * @param prf the PRF to be used in the final KDF.
         * @return a new parameters builder.
         */
        public FeedbackModeParametersBuilder withPRF(PRF prf)
        {
            return new FeedbackModeParametersBuilder(getAlgorithm(), prf, -1, null);
        }

        /**
         * Return a new parameters builder based around the passed in counter size. The
         * counter will be after the iteration data.
         *
         * @param r the length in bits of the counter to be used.
         * @return a new parameters builder.
         */
        public FeedbackModeParametersBuilder withR(int r)
        {
            return new FeedbackModeParametersBuilder(getAlgorithm(), prf, r, CounterLocation.AFTER_ITERATION_DATA);
        }

        /**
         * Return a new parameters builder based around the passed in counter size and counter position.
         *
         * @param r the length in bits of the counter to be used.
         * @param counterLocation the location of the counter in data passed to the PRF during calculation.
         * @return a new parameters builder.
         */
        public FeedbackModeParametersBuilder withRAndLocation(int r, CounterLocation counterLocation)
        {
            return new FeedbackModeParametersBuilder(getAlgorithm(), prf, r, counterLocation);
        }

        /**
         * Return a new parameter set for ki and a prefix.
         *
         * @param ki derivation key for the KDF.
         * @param iv the IV to use at the start of the calculation.
         * @param fixedInputData fixed input data to use in calculation.
         * @return a FeedbackModeParameters object.
         */
        public FeedbackModeParameters using(byte[] ki, byte[] iv, byte[] fixedInputData)
        {
            return new FeedbackModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, counterLocation, Arrays.clone(ki), Arrays.clone(iv), Arrays.clone(fixedInputData));
        }

        /**
         * Build method for parameters which builds fixed input as outlined in SP 800-108 with the fixed input
         * as a prefix, or suffix, to the counter.
         *
         * @param ki  input key.
         * @param iv  initialization vector.
         * @param label label - fixed input component.
         * @param context context - fixed input component.
         * @param L number of bits per request for the KDF these parameters will initialise - fixed input component.
         * @return a FeedbackModeParameters object.
         */
        public FeedbackModeParameters using(byte[] ki, byte[] iv, byte[] label, byte[] context, int L)
        {
            return new FeedbackModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, counterLocation, Arrays.clone(ki), Arrays.clone(iv), buildFixedInput(label, context, L));
        }
    }

    /**
     * Parameters for the Feedback Mode key derivation function.
     */
    public static final class FeedbackModeParameters
        extends FipsParameters
    {
        private final int r;
        private final CounterLocation counterLocation;
        private final byte[] ki;
        private final byte[] iv;
        private final byte[] fixedInputData;

        private FeedbackModeParameters(FipsAlgorithm algorithm, int r, CounterLocation counterLocation, byte[] ki, byte[] iv, byte[] fixedInputData)
        {
            super(algorithm);

            this.r = r;
            this.counterLocation = counterLocation;
            this.ki = ki;
            this.iv = iv;
            this.fixedInputData = fixedInputData;
        }
    }

    /**
     * Factory for Feedback Mode KDFs.
     */
    public static final class FeedbackModeFactory
        extends FipsKDFOperatorFactory
    {
        public FeedbackModeFactory()
        {
        }

        public KDFCalculator createKDFCalculator(final FeedbackModeParameters params)
        {
            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            final KDFFeedbackBytesGenerator kdfGenerator = new FeedbackModeProvider(params.getAlgorithm()).createEngine();
            CounterLocation counterLocation = params.counterLocation;
            int r = params.r;

            if (r > 0)
            {
                kdfGenerator.init(KDFFeedbackParameters.createWithCounter(counterLocation.code, params.ki, params.iv, params.fixedInputData, r));
            }
            else
            {
                kdfGenerator.init(KDFFeedbackParameters.createWithoutCounter(params.ki, params.iv, params.fixedInputData));
            }

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public FeedbackModeParameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    kdfGenerator.generateBytes(out, outOff, len);
                }
            });
        }
    }

    /**
     * Parameters for the Double Pipeline Mode parameters builder.
     */
    public static final class DoublePipelineModeParametersBuilder
        extends FipsParameters
    {
        private final PRF prf;
        private final int r;
        private final CounterLocation counterLocation;

        DoublePipelineModeParametersBuilder(FipsAlgorithm algorithm)
        {
            this(algorithm, PRF.SHA1_HMAC, -1, null);
        }

        private DoublePipelineModeParametersBuilder(FipsAlgorithm algorithm, PRF prf, int r, CounterLocation counterLocation)
        {
            super(algorithm);
            this.prf = prf;
            this.r = r;
            this.counterLocation = counterLocation;
        }

        /**
         * Return a new parameters builder based around the passed in PRF.
         *
         * @param prf the PRF to be used in the final KDF.
         * @return a new parameters builder.
         */
        public DoublePipelineModeParametersBuilder withPRF(PRF prf)
        {
            return new DoublePipelineModeParametersBuilder(getAlgorithm(), prf, -1, null);
        }

        /**
         * Return a new parameters builder based around the passed in counter size. The
         * counter will be after the iteration data.
         *
         * @param r the length in bits of the counter to be used.
         * @return a new parameters builder.
         */
        public DoublePipelineModeParametersBuilder withR(int r)
        {
            return new DoublePipelineModeParametersBuilder(getAlgorithm(), prf, r, CounterLocation.AFTER_ITERATION_DATA);
        }

        /**
         * Return a new parameters builder based around the passed in counter size and counter position.
         *
         * @param r the length in bits of the counter to be used.
         * @param counterLocation the location of the counter in data passed to the PRF during calculation.
         * @return a new parameters builder.
         */
        public DoublePipelineModeParametersBuilder withRAndLocation(int r, CounterLocation counterLocation)
        {
            return new DoublePipelineModeParametersBuilder(getAlgorithm(), prf, r, counterLocation);
        }

        /**
         * Return a new parameter set for ki and a prefix.
         *
         * @param ki derivation key for the KDF.
         * @param fixedInputData fixed input data to use in calculation.
         * @return a DoublePipelineModeParameters object.
         */
        public DoublePipelineModeParameters using(byte[] ki, byte[] fixedInputData)
        {
            return new DoublePipelineModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, counterLocation, Arrays.clone(ki), Arrays.clone(fixedInputData));
        }

        /**
         * Build method for parameters which builds fixed input as outlined in SP 800-108 with the fixed input
         * as a prefix, or suffix, to the counter.
         *
         * @param ki  input key.
         * @param label label - fixed input component.
         * @param context context - fixed input component.
         * @param L number of bits per request for the KDF these parameters will initialise - fixed input component.
         * @return a DoublePipelineModeParameters object.
         */
        public DoublePipelineModeParameters using(byte[] ki, byte[] label, byte[] context, int L)
        {
            return new DoublePipelineModeParameters(new FipsAlgorithm(getAlgorithm(), prf), r, counterLocation, Arrays.clone(ki), buildFixedInput(label, context, L));
        }
    }

    /**
     * Parameters for the Double Pipeline Mode key derivation function.
     */
    public static final class DoublePipelineModeParameters
        extends FipsParameters
    {
        private final int r;
        private final CounterLocation counterLocation;
        private final byte[] ki;
        private final byte[] fixedInputData;

        private DoublePipelineModeParameters(FipsAlgorithm algorithm, int r, CounterLocation counterLocation, byte[] ki, byte[] fixedInputData)
        {
            super(algorithm);

            this.r = r;
            this.counterLocation = counterLocation;
            this.ki = ki;
            this.fixedInputData = fixedInputData;
        }
    }

    /**
     * Factory for Double Pipeline Iteration Mode KDF.
     */
    public static final class DoublePipelineModeFactory
        extends FipsKDFOperatorFactory
    {
        public DoublePipelineModeFactory()
        {

        }

        public KDFCalculator createKDFCalculator(final DoublePipelineModeParameters params)
        {
            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            final KDFDoublePipelineIterationBytesGenerator kdfGenerator = new DoublePipelineModeProvider(params.getAlgorithm()).createEngine();
            CounterLocation counterLocation = params.counterLocation;
            int r = params.r;

            if (r > 0)
            {
                kdfGenerator.init(KDFDoublePipelineIterationParameters.createWithCounter(counterLocation.code, params.ki, params.fixedInputData, r));
            }
            else
            {
                kdfGenerator.init(KDFDoublePipelineIterationParameters.createWithoutCounter(params.ki, params.fixedInputData));
            }

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public DoublePipelineModeParameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    kdfGenerator.generateBytes(out, outOff, len);
                }
            });
        }
    }

    private static byte[] buildFixedInput(byte[] label, byte[] context, int L)
    {
        return Arrays.concatenate(label, ZERO_BYTE, context, Pack.intToBigEndian(L));
    }

    private static FipsEngineProvider createPRF(PRF prfAlgorithm)
    {
        FipsEngineProvider macProvider;
        if (prfAlgorithm == PRF.TRIPLEDES_CMAC)
        {
            // check only meaningful when out of startup phase.
            if (!FipsStatus.isBooting())
            {
                if (CryptoServicesRegistrar.isInApprovedOnlyMode() && !Properties.isOverrideSet("org.bouncycastle.tripledes.allow_prf"))
                {
                    throw new FipsUnapprovedOperationError("Triple-DES prf disallowed");
                }
            }

            macProvider = FipsTripleDES.getMacProvider(FipsTripleDES.CMAC.getAlgorithm());
        }
        else if (prfAlgorithm == PRF.AES_CMAC)
        {
            macProvider = FipsAES.getMacProvider(FipsAES.CMAC.getAlgorithm());
        }
        else
        {
            macProvider = FipsSHS.getMacProvider(prfAlgorithm.algorithm);
        }

        if (macProvider == null)
        {
            throw new IllegalArgumentException("Unknown algorithm passed to FipsKDF.createPRF: " + prfAlgorithm);
        }

        return macProvider;
    }

    static byte[] processZBytes(byte[] zBytes, FipsAgreementParameters parameters)
    {
        PRF prfMacAlg = parameters.getPrfAlgorithm();
        byte[] salt = parameters.salt;
        FipsAlgorithm digestAlg = parameters.digestAlgorithm;
        KDFOperatorFactory kdfOperatorFactory = new FipsKDF.AgreementOperatorFactory();
        FipsKDF.AgreementKDFParametersBuilder kdfType = parameters.kdfType;

        if (prfMacAlg == PRF.TRIPLEDES_CMAC && CryptoServicesRegistrar.isInApprovedOnlyMode())
        {
            throw new FipsUnapprovedOperationError("Requested PRF has insufficient security level for approved mode: " + prfMacAlg.name());
        }

        if (prfMacAlg != null)
        {
            final Mac prfMac = FipsKDF.createPRF(prfMacAlg).createEngine();

            if (salt == null)
            {
                if (prfMac instanceof HMac)
                {
                    prfMac.init(new KeyParameterImpl(new byte[((HMac)prfMac).getUnderlyingDigest().getByteLength()]));
                }
                else
                {
                    prfMac.init(new KeyParameterImpl(new byte[16]));
                }
            }
            else
            {
                prfMac.init(new KeyParameterImpl(Arrays.clone(salt)));
            }

            byte[] mac = new byte[prfMac.getMacSize()];

            prfMac.update(zBytes, 0, zBytes.length);

            prfMac.doFinal(mac, 0);

            // ZEROIZE
            Arrays.fill(zBytes, (byte)0);

            return mac;
        }
        else if (digestAlg != null)
        {
            Digest digest = FipsSHS.createDigest(digestAlg);

            byte[] hash = new byte[digest.getDigestSize()];

            digest.update(zBytes, 0, zBytes.length);

            digest.doFinal(hash, 0);

            // ZEROIZE
            Arrays.fill(zBytes, (byte)0);

            return hash;
        }
        else if (kdfType != null)
        {
            KDFCalculator kdfCalculator = kdfOperatorFactory.createKDFCalculator(kdfType.using(zBytes).withIV(salt));

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

            byte[] rv = new byte[parameters.outputSize];

            kdfCalculator.generateBytes(rv);

            return rv;
        }
        else
        {
            return zBytes;
        }
    }

    /**
     * An enumeration of the FIPS approved psuedo-random-function (PRF) for KDFs used with TLS.
     */
    public enum TLSPRF {
        SHA256_HMAC(FipsSHS.Algorithm.SHA256_HMAC),
        SHA384_HMAC(FipsSHS.Algorithm.SHA384_HMAC),
        SHA512_HMAC(FipsSHS.Algorithm.SHA512_HMAC);

        private final FipsAlgorithm algorithm;

        TLSPRF(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public FipsAlgorithm getAlgorithm()
        {
            return algorithm;
        }
    }

    /**
     * The standard string values for TLS key calculation stages.
     */
    public static final class TLSStage
    {
        private TLSStage()
        {

        }

        public static final String MASTER_SECRET = "master secret";
        public static final String KEY_EXPANSION = "key expansion";
        public static final String EXTENDED_MASTER_SECRET = "extended master secret";
    }

    /**
     * Parameter builder for TLS 1.0/1.1
     */
    public static class TLSParametersBuilder
        extends FipsParameters
    {
        TLSParametersBuilder(FipsAlgorithm algorithm)
        {
            super(algorithm);
        }

        /**
          * Create parameters for a version TLS 1.0/1.1 KDF
          *
          * @param secret secret to use
          * @param label e.g. 'master secret', or 'key expansion'
          * @param seedMaterial one or more byte arrays making up the seed
          */
        public TLSParameters using(byte[] secret, String label, byte[]... seedMaterial)
        {
            return new TLSParameters(getAlgorithm(), Arrays.clone(secret), label, Arrays.concatenate(seedMaterial));
        }
    }

    /**
     * Parameter builder for TLS 1.2
     */
    public static final class TLSParametersWithPRFBuilder
        extends TLSParametersBuilder
    {
        private final TLSPRF prf;

        TLSParametersWithPRFBuilder(FipsAlgorithm algorithm, TLSPRF prf)
        {
            super(algorithm);
            this.prf = prf;
        }

        public TLSParametersWithPRFBuilder withPRF(TLSPRF prf)
        {
            return new TLSParametersWithPRFBuilder(getAlgorithm(), prf);
        }

        /**
         * Create parameters for a version TLS 1.2 KDF.
         *
         * @param secret secret to use
         * @param label e.g. 'master secret', or 'key expansion'
         * @param seedMaterial one or more byte arrays making up the seed
         */
        public TLSParameters using(byte[] secret, String label, byte[]... seedMaterial)
        {
            return new TLSParameters(new FipsAlgorithm(getAlgorithm(), prf), Arrays.clone(secret), label, Arrays.concatenate(seedMaterial));
        }
    }

    /**
     * Parameters for the TLS key derivation functions.
     */
    public static final class TLSParameters
        extends FipsParameters
    {
        private final byte[] secret;
        private final String label;
        private final byte[] seed;

        /**
         * Constructor specifying which version of TLS the KDF should be for.
         *
         * @param version TLS version this is for.
         * @param secret secret to use
         * @param label e.g. 'master secret', or 'key expansion'
         * @param seed the seed material
         */
        TLSParameters(FipsAlgorithm version, byte[] secret, String label,  byte[] seed)
        {
            super(version);

            this.secret = secret;
            this.label = label;
            this.seed = seed;
        }
    }

    /**
     * Factory for operators that derive key material using the TLS family of KDFs.
     */
    public static final class TLSOperatorFactory
        extends FipsKDFOperatorFactory
    {
        /**
         * Create the operator factory.
         */
        public TLSOperatorFactory()
        {

        }

        public KDFCalculator createKDFCalculator(final TLSParameters params)
        {
            final TLSPRF prfAlgorithm = (TLSPRF)params.getAlgorithm().basicVariation();

            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            if (prfAlgorithm == null)
            {
                final Mac md5Hmac = new HMac(md5Provider.createEngine());
                final Mac sha1HMac = FipsSHS.createHMac(FipsSHS.Algorithm.SHA1_HMAC);

                return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
                {
                    public TLSParameters getParameters()
                    {
                        return params;
                    }

                    public void generateBytes(byte[] out, int outOff, int len)
                    {
                        byte[] tmp = PRF_legacy(params, params.secret, params.label, len, md5Hmac, sha1HMac);

                        System.arraycopy(tmp, 0, out, outOff, len);
                    }
                });
            }

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public TLSParameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    byte[] tmp = PRF(params, prfAlgorithm, params.secret, params.label, len);

                    System.arraycopy(tmp, 0, out, outOff, len);
                }
            });
        }
    }

    private static byte[] PRF(TLSParameters parameters, TLSPRF prfAlgorithm, byte[] secret, String asciiLabel, int size)
    {
        byte[] label = Strings.toByteArray(asciiLabel);
        byte[] labelSeed = Arrays.concatenate(label, parameters.seed);

        Mac prfMac = FipsSHS.createHMac(prfAlgorithm.algorithm);
        byte[] buf = new byte[size];
        hmac_hash(prfMac, secret, labelSeed, buf);
        return buf;
    }

    private static byte[] PRF_legacy(TLSParameters parameters, byte[] secret, String asciiLabel, int size, Mac md5Hmac, Mac sha1HMac)
    {
        byte[] label = Strings.toByteArray(asciiLabel);
        byte[] labelSeed = Arrays.concatenate(label, parameters.seed);

        int s_half = (secret.length + 1) / 2;
        byte[] s1 = new byte[s_half];
        byte[] s2 = new byte[s_half];
        System.arraycopy(secret, 0, s1, 0, s_half);
        System.arraycopy(secret, secret.length - s_half, s2, 0, s_half);

        byte[] b1 = new byte[size];
        byte[] b2 = new byte[size];
        hmac_hash(md5Hmac, s1, labelSeed, b1);
        hmac_hash(sha1HMac, s2, labelSeed, b2);
        for (int i = 0; i < size; i++)
        {
            b1[i] ^= b2[i];
        }
        return b1;
    }

    private static void hmac_hash(Mac mac, byte[] secret, byte[] seed, byte[] out)
    {
        mac.init(new KeyParameterImpl(secret));
        byte[] a = seed;
        int size = mac.getMacSize();
        int iterations = (out.length + size - 1) / size;
        byte[] buf = new byte[mac.getMacSize()];
        byte[] buf2 = new byte[mac.getMacSize()];
        for (int i = 0; i < iterations; i++)
        {
            mac.update(a, 0, a.length);
            mac.doFinal(buf, 0);
            a = buf;
            mac.update(a, 0, a.length);
            mac.update(seed, 0, seed.length);
            mac.doFinal(buf2, 0);
            System.arraycopy(buf2, 0, out, (size * i), Math.min(size, out.length - (size * i)));
        }
    }

    /**
     * An enumeration of the FIPS approved psuedo-random-function (PRF) for KDFs used with SSH key exchange.
     */
    public enum SSHPRF {
        SHA1(FipsSHS.Algorithm.SHA1),
        SHA224(FipsSHS.Algorithm.SHA224),
        SHA256(FipsSHS.Algorithm.SHA256),
        SHA384(FipsSHS.Algorithm.SHA384),
        SHA512(FipsSHS.Algorithm.SHA512);

        private final FipsAlgorithm algorithm;

        SSHPRF(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public FipsAlgorithm getAlgorithm()
        {
            return algorithm;
        }
    }

    /**
     * Parameters builder for the SSH key derivation function.
     */
    public static final class SSHParametersBuilder
        extends FipsParameters
    {
        SSHPRF prf;

        SSHParametersBuilder(FipsAlgorithm algorithm, SSHPRF prf)
        {
            super(algorithm);
            this.prf = prf;
        }

        public SSHParametersBuilder withPRF(SSHPRF prf)
        {
            return new SSHParametersBuilder(getAlgorithm(), prf);
        }

        public SSHParameters using(char x, byte[] sharedKey, byte[] exchangeHash, byte[] sessionID)
        {
            return new SSHParameters(new FipsAlgorithm(getAlgorithm(), prf), x, Arrays.clone(sharedKey), Arrays.clone(exchangeHash), Arrays.clone(sessionID));
        }

        public SSHPRF getPRF()
        {
            return prf;
        }
    }

    /**
     * Parameters for the SSH key derivation function.
     */
    public static final class SSHParameters
        extends FipsParameters
    {
        private final char x;
        private final byte[] sharedKey;
        private final byte[] exchangeHash;
        private final byte[] sessionID;

        /**
         * Base constructor. Create parameters for a SSH KDF.
         *
         */
        SSHParameters(FipsAlgorithm algorithm, char x, byte[] sharedKey, byte[] exchangeHash, byte[] sessionID)
        {
            super(algorithm);

            this.x = x;
            this.sharedKey = sharedKey;
            this.exchangeHash = exchangeHash;
            this.sessionID = sessionID;
        }

        SSHParameters(SSHParameters params, SSHPRF prfAlgorithm)
        {
            this(new FipsAlgorithm(params.getAlgorithm(), prfAlgorithm), params.x, params.sharedKey, params.exchangeHash, params.sessionID);
        }

        public SSHParameters withX(char x)
        {
            return new SSHParameters(this.getAlgorithm(), x, this.sharedKey, this.exchangeHash, this.sessionID);
        }
    }

    /**
     * Factory for operators that derive key material using the SSH KDF.
     */
    public static final class SSHOperatorFactory
        extends FipsKDFOperatorFactory
    {
        public SSHOperatorFactory()
        {
        }

        public KDFCalculator createKDFCalculator(final SSHParameters params)
        {
            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            final Digest digest = FipsSHS.createDigest(((SSHPRF)params.getAlgorithm().basicVariation()).algorithm);

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public SSHParameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    hash(digest, params, out, outOff, len);
                }
            });
        }

        /*
           -  Initial IV client to server: HASH(K || H || "A" || session_id)
              (Here K is encoded as mpint and "A" as byte and session_id as raw
              data.  "A" means the single character A, ASCII 65).

           -  Initial IV server to client: HASH(K || H || "B" || session_id)

           -  Encryption key client to server: HASH(K || H || "C" || session_id)

           -  Encryption key server to client: HASH(K || H || "D" || session_id)

           -  Integrity key client to server: HASH(K || H || "E" || session_id)

           -  Integrity key server to client: HASH(K || H || "F" || session_id)


              K1 = HASH(K || H || X || session_id)   (X is e.g., "A")
              K2 = HASH(K || H || K1)
              K3 = HASH(K || H || K1 || K2)
              ...
              key = K1 || K2 || K3 || ...
         */
        private static void hash(Digest digest, SSHParameters params, byte[] out, int outOff, int len)
        {
            int size = digest.getDigestSize();
            int iterations = (len + size - 1) / size;
            byte[] buf = new byte[digest.getDigestSize()];

            digest.update(params.sharedKey, 0, params.sharedKey.length);
            digest.update(params.exchangeHash, 0, params.exchangeHash.length);
            digest.update((byte)params.x);
            digest.update(params.sessionID, 0, params.sessionID.length);

            digest.doFinal(buf, 0);

            System.arraycopy(buf, 0, out, outOff, Math.min(size, len));

            for (int i = 1; i < iterations; i++)
            {
                digest.update(params.sharedKey, 0, params.sharedKey.length);
                digest.update(params.exchangeHash, 0, params.exchangeHash.length);
                digest.update(out, outOff, size * i);

                digest.doFinal(buf, 0);

                System.arraycopy(buf, 0, out, outOff + (size * i), Math.min(size, out.length - (size * i)));
            }
        }
    }

    /**
     * An enumeration of the FIPS approved psuedo-random-function (PRF) for KDFs used with key agreement.
     */
    public enum AgreementKDFPRF
    {
        SHA1(FipsSHS.Algorithm.SHA1),
        SHA224(FipsSHS.Algorithm.SHA224),
        SHA256(FipsSHS.Algorithm.SHA256),
        SHA384(FipsSHS.Algorithm.SHA384),
        SHA512(FipsSHS.Algorithm.SHA512),
        SHA512_224(FipsSHS.Algorithm.SHA512_224),
        SHA512_256(FipsSHS.Algorithm.SHA512_256),
        SHA3_224(FipsSHS.Algorithm.SHA3_224),
        SHA3_256(FipsSHS.Algorithm.SHA3_256),
        SHA3_384(FipsSHS.Algorithm.SHA3_384),
        SHA3_512(FipsSHS.Algorithm.SHA3_512);

        private final FipsAlgorithm algorithm;

        AgreementKDFPRF(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public FipsAlgorithm getAlgorithm()
        {
            return algorithm;
        }
    }

    /**
     * Parameters builder for the X9.63 and CONCATENATION key derivation function.
     */
    public static final class AgreementKDFParametersBuilder
        extends FipsParameters
    {
        AgreementKDFPRF prf;

        AgreementKDFParametersBuilder(FipsAlgorithm algorithm, AgreementKDFPRF prf)
        {
            super(algorithm);
            this.prf = prf;
        }

        public AgreementKDFParametersBuilder withPRF(AgreementKDFPRF prf)
        {
            return new AgreementKDFParametersBuilder(getAlgorithm(), prf);
        }

        public AgreementKDFParameters using(byte[] shared)
        {
            return new AgreementKDFParameters(new FipsAlgorithm(getAlgorithm(), prf), Arrays.clone(shared));
        }

        public AgreementKDFPRF getPRF()
        {
            return prf;
        }
    }

    /**
     * Parameters for the X9.63 and CONCATENATION key derivation function.
     */
    public static final class AgreementKDFParameters
        extends FipsParameters
    {
        private final byte[] shared;
        private final byte[] iv;

        AgreementKDFParameters(FipsAlgorithm algorithm, byte[] shared)
        {
            this(algorithm, shared, null);
        }

        AgreementKDFParameters(FipsAlgorithm algorithm, byte[] shared, byte[] iv)
        {
            super(algorithm);

            this.shared = shared;
            this.iv = iv;
        }

        public AgreementKDFParameters withIV(byte[] iv)
        {
            return new AgreementKDFParameters(getAlgorithm(), shared, Arrays.clone(iv));
        }
    }

    /**
     * Factory for operators that derive key material and are associated with key agreement.
     */
    public static final class AgreementOperatorFactory
        extends FipsKDFOperatorFactory
    {
        /**
         * Create an operator factory for creating key agreement KDF generators (X9.63/Concatenation).
         */
        public AgreementOperatorFactory()
        {

        }

        public KDFCalculator createKDFCalculator(final AgreementKDFParameters params)
        {
            if (params.getAlgorithm().getName().startsWith(X963.getAlgorithm().getName()))
            {
                return createX963KDFCalculator(approvedModeOnly, params);
            }
            else
            {
                return createConcatenationKDFCalculator(approvedModeOnly, params);
            }
        }
    }

    /**
     * Factory for operators that derive key material using the X9.63 KDF.
     */
    private static KDFCalculator createX963KDFCalculator(boolean approvedModeOnly, final AgreementKDFParameters params)
    {
        Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

        final DerivationFunction df = new KDF2BytesGenerator(FipsSHS.createDigest(((AgreementKDFPRF)params.getAlgorithm().basicVariation()).algorithm));

        df.init(new KDFParameters(params.shared, params.iv));

        return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
        {
            public AgreementKDFParameters getParameters()
            {
                return params;
            }

            public void generateBytes(byte[] out, int outOff, int len)
            {
                df.generateBytes(out, outOff, len);
            }
        });
    }

    /**
     * Factory method for operators that derive key material using the SP800-56A Concatenation KDF.
     */
    private static KDFCalculator createConcatenationKDFCalculator(boolean approvedModeOnly, final AgreementKDFParameters params)
    {
        Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

        final DerivationFunction df = new ConcatenationKDFGenerator(FipsSHS.createDigest(((AgreementKDFPRF)params.getAlgorithm().basicVariation()).algorithm));

        df.init(new KDFParameters(params.shared, params.iv));

        return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
        {
            public AgreementKDFParameters getParameters()
            {
                return params;
            }

            public void generateBytes(byte[] out, int outOff, int len)
            {
                df.generateBytes(out, outOff, len);
            }
        });
    }

    /**
     * An enumeration of the FIPS approved psuedo-random-function (PRF) for KDFs used with IKEv2.
     */
    public enum IKEv2PRF
    {
        SHA1(FipsSHS.Algorithm.SHA1_HMAC),
        SHA224(FipsSHS.Algorithm.SHA224_HMAC),
        SHA256(FipsSHS.Algorithm.SHA256_HMAC),
        SHA384(FipsSHS.Algorithm.SHA384_HMAC),
        SHA512(FipsSHS.Algorithm.SHA512_HMAC);

        private final FipsAlgorithm algorithm;

        IKEv2PRF(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public FipsAlgorithm getAlgorithm()
        {
            return algorithm;
        }
    }

    /**
     * Parameters builder for the IKEv2 key derivation function.
     */
    public static class IKEv2ParametersBuilder
        extends FipsParameters
    {
        private final IKEv2PRF prf;

        IKEv2ParametersBuilder(FipsAlgorithm algorithm, IKEv2PRF prf)
        {
            super(algorithm);
            this.prf = prf;
        }

        public IKEv2ParametersBuilder withPRF(IKEv2PRF prf)
        {
            return new IKEv2ParametersBuilder(getAlgorithm(), prf);
        }

        public IKEv2PRF getPRF()
        {
            return prf;
        }

        public IKEv2Parameters createForPrf(byte[] shared, byte[]... keyPad)
        {
            return new IKEv2Parameters(new FipsAlgorithm(getAlgorithm(), prf), false, Arrays.clone(shared), Arrays.concatenate(keyPad));
        }

        public IKEv2Parameters createForPrfPlus(byte[] shared, byte[]... keyPad)
        {
            return new IKEv2Parameters(new FipsAlgorithm(getAlgorithm(), prf), true, Arrays.clone(shared), Arrays.concatenate(keyPad));
        }
    }

    /**
     * Parameters for the IKVEv2 key derivation function.
     */
    public static class IKEv2Parameters
        extends FipsParameters
    {
        private final boolean isPlus;
        private final byte[] shared;
        private final byte[] keyPad;

        IKEv2Parameters(FipsAlgorithm algorithm, boolean isPlus, byte[] shared, byte[] keyPad)
        {
            super(algorithm);
            this.isPlus = isPlus;
            this.shared = shared;
            this.keyPad = keyPad;
        }
    }

    /**
     * Factory for operators that derive key material using the IKEv2 KDF.
     */
    public static final class IKEv2OperatorFactory
        extends FipsKDFOperatorFactory
    {
        /**
         * Create an operator factory for creating IKEv2 KDF generators.
         */
        public IKEv2OperatorFactory()
        {

        }

        public KDFCalculator createKDFCalculator(final IKEv2Parameters params)
        {
            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            final Mac hMac = FipsSHS.createHMac(((IKEv2PRF)params.getAlgorithm().basicVariation()).algorithm);

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public IKEv2Parameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    prf(hMac, params, out, outOff, len);
                }
            });
        }

        private static void prf(Mac hmac, IKEv2Parameters params, byte[] out, int outOff, int len)
        {
            int size = hmac.getMacSize();
            int iterations = (len + size - 1) / size;
            byte[] buf = new byte[size];


            if (!params.isPlus)
            {
                hmac.init(new KeyParameterImpl(params.shared));
                hmac.update(params.keyPad, 0, params.keyPad.length);
                hmac.doFinal(buf, 0);

                System.arraycopy(buf, 0, out, outOff, buf.length);
            }
            else
            {
                hmac.init(new KeyParameterImpl(params.shared));
                hmac.update(params.keyPad, 0, params.keyPad.length);
                hmac.update((byte)1);

                hmac.doFinal(buf, 0);

                System.arraycopy(buf, 0, out, outOff, Math.min(size, len));

                for (int i = 1; i < iterations; i++)
                {
                    hmac.update(buf, 0, buf.length);
                    hmac.update(params.keyPad, 0, params.keyPad.length);
                    hmac.update((byte)(i + 1));

                    hmac.doFinal(buf, 0);

                    System.arraycopy(buf, 0, out, outOff + (size * i), Math.min(size, out.length - (size * i)));
                }
            }
        }
    }

    /**
     * An enumeration of the FIPS approved psuedo-random-function (PRF) for KDFs used with SRTP.
     */
    public enum SRTPPRF
    {
        AES_CM(FipsAES.CTR.getAlgorithm(), FipsAES.ENGINE_PROVIDER);

        private final FipsAlgorithm algorithm;
        private final EngineProvider engineProvider;

        SRTPPRF(FipsAlgorithm algorithm, EngineProvider engineProvider)
        {
            this.algorithm = algorithm;
            this.engineProvider = engineProvider;
        }

        public FipsAlgorithm getAlgorithm()
        {
            return algorithm;
        }
    }

    /**
     * Parameters for the SRTP key derivation function.
     */
    public static class SRTPParametersBuilder
        extends FipsParameters
    {
        private final SRTPPRF prf;

        SRTPParametersBuilder(FipsAlgorithm algorithm, SRTPPRF prf)
        {
            super(algorithm);
            this.prf = prf;
        }

        public SRTPParametersBuilder withPRF(SRTPPRF prf)
        {
            return new SRTPParametersBuilder(getAlgorithm(), prf);
        }

        public SRTPParameters using(byte[] kMaster, byte[] masterSalt, int kdr, byte[] index)
        {
            return new SRTPParameters(new FipsAlgorithm(getAlgorithm(), prf), (byte)0, Arrays.clone(kMaster), Arrays.clone(masterSalt), kdr, Arrays.clone(index));
        }

        public SRTPPRF getPRF()
        {
            return prf;
        }
    }

    /**
     * Parameters for the SRTP key derivation function.
     */
    public static class SRTPParameters
        extends FipsParameters
    {
        private final byte label;
        private final byte[] kMaster;
        private final byte[] masterSalt;
        private final int kdr;
        private final byte[] index;
        private final byte[] div;

        SRTPParameters(FipsAlgorithm algorithm, byte label, byte[] kMaster, byte[] masterSalt, int kdr, byte[] index)
        {
            super(algorithm);

            this.label = label;
            this.kMaster = kMaster;
            this.masterSalt = masterSalt;
            this.kdr = kdr;
            this.index = index;
            this.div = new byte[index.length];

            if (kdr != 0)
            {
                byte[] adjusted;
                if (index.length <= 7)
                {
                    byte[] val = new byte[8];

                    System.arraycopy(index, 0, val, val.length - index.length, index.length);

                    long ind = Pack.bigEndianToLong(val, 0) / kdr;

                    adjusted = Pack.longToBigEndian(ind);
                }
                else
                {
                    BigInteger ind = new BigInteger(1, index).divide(BigInteger.valueOf(kdr));

                    adjusted = ind.toByteArray();
                }

                if (adjusted.length < div.length)
                {
                    System.arraycopy(adjusted, 0, div, div.length - adjusted.length, adjusted.length);
                }
                else
                {
                    System.arraycopy(adjusted, adjusted.length - div.length, div, 0, div.length);
                }
            }
        }

        public SRTPParameters withLabel(byte label)
        {
            return new SRTPParameters(this.getAlgorithm(), label, this.kMaster, this.masterSalt, this.kdr, this.index);
        }
    }

    /**
     * Factory for operators that derive key material using the SRTP KDF.
     */
    public static final class SRTPOperatorFactory
        extends FipsKDFOperatorFactory
    {
        /**
         * Create an operator factory for creating SRTP KDF generators.
         */
        public SRTPOperatorFactory()
        {

        }

        public KDFCalculator createKDFCalculator(final SRTPParameters params)
        {
            Utils.approvedModeCheck(approvedModeOnly, params.getAlgorithm());

            final SICBlockCipher prfEngine = new SICBlockCipher(((SRTPPRF)params.getAlgorithm().basicVariation()).engineProvider.createEngine());

            byte[] iv = new byte[prfEngine.getBlockSize()];

            System.arraycopy(params.masterSalt, 0, iv, 0, params.masterSalt.length);

            iv[params.masterSalt.length - (params.div.length + 1)] ^= params.label;
            for (int i = 0; i != params.div.length; i++)
            {
                iv[i + (params.masterSalt.length - params.div.length)] ^= params.div[i];
            }

            prfEngine.init(true, new ParametersWithIV(new KeyParameterImpl(params.kMaster), iv));

            return new MonitoringKDFCalculator(approvedModeOnly, new BaseKDFCalculator()
            {
                public SRTPParameters getParameters()
                {
                    return params;
                }

                public void generateBytes(byte[] out, int outOff, int len)
                {
                    prf(prfEngine, out, outOff, len);
                }
            });
        }

        private static void prf(StreamCipher prfEngine, byte[] out, int outOff, int len)
        {
            for (int i = outOff; i != outOff + len; i++)
            {
                out[i] = 0;
            }

            prfEngine.processBytes(out, outOff, len, out, outOff);
        }
    }

    private interface BaseKDFCalculator
    {
        T getParameters();

        void generateBytes(byte[] out, int outOff, int len);
    }
    
    private static class MonitoringKDFCalculator
        implements KDFCalculator
    {
        private final boolean approvedModeOnly;
        private final BaseKDFCalculator kdf;
        private final FipsAlgorithm algorithm;

        MonitoringKDFCalculator(boolean approvedModeOnly, BaseKDFCalculator kdf)
        {
            this.approvedModeOnly = approvedModeOnly;
            this.kdf = kdf;
            this.algorithm = (FipsAlgorithm)kdf.getParameters().getAlgorithm();
        }

        public T getParameters()
        {
            Utils.approvedModeCheck(approvedModeOnly, algorithm);

            return kdf.getParameters();
        }

        public void generateBytes(byte[] out)
        {
            generateBytes(out, 0, out.length);
        }

        public void generateBytes(byte[] out, int outOff, int len)
        {
            Utils.approvedModeCheck(approvedModeOnly, algorithm);

            kdf.generateBytes(out, outOff, len);
        }
    }

    private static EngineProvider md5Provider = new EngineProvider()
    {
        public Digest createEngine()
        {
            // FSM_STATE:3.KDF.0, TLS 1.0 KAT, "The module is performing the KAT test for the MD5 digest in TLS 1.0"
            // FSM_TRANS:3.KDF.0, "POWER ON SELF-TEST",	"TLS 1.0 KDF GENERATE VERIFY KAT",	"Invoke MD5 digest in TLS 1.0 KDF Generate/Verify KAT self-test"
            return SelfTestExecutor.validate(FipsKDF.TLS1_0.getAlgorithm(), new MD5Digest(), new Md5KatTest());
            // FSM_TRANS:3.KDF.1, "TLS 1.0 KDF GENERATE VERIFY KAT", "POWER ON SELF-TEST",	"MD5 digest in TLS 1.0 KDF KAT self-test successful completion"

        }
    };

    private static class Md5KatTest
        implements BasicKatTest
    {
        private static final byte[] stdShaVector = Strings.toByteArray("abc");
        private static final byte[] kat = Hex.decode("900150983cd24fb0d6963f7d28e17f72");

        public boolean hasTestPassed(Digest digest)
        {
            digest.update(stdShaVector, 0, stdShaVector.length);

            byte[] result = new byte[digest.getDigestSize()];

            digest.doFinal(result, 0);

            return Arrays.areEqual(result, kat);
        }
    }

    private static final class CounterModeProvider
        extends FipsEngineProvider
    {
        private static final byte[] KI = Hex.decode("dff1e50ac0b69dc40f1051d46c2b069c");
        private static final byte[] FIP = new byte[] { 0x01 };
        private static final byte[] FIS = new byte[] { 0x02 };

        private static final byte[] aes_cmac_vec = Hex.decode("53023e21d00cc5046b15");
        private static final byte[] tripleDes_vec = Hex.decode("d4e062f13b0baefa4943");
        private static final byte[] sha1_vec = Hex.decode("76f881b780e4939d485a");
        private static final byte[] sha224_vec = Hex.decode("66db824abdf2b4e85de2");
        private static final byte[] sha256_vec = Hex.decode("3a46d9be7ab8ea092558");
        private static final byte[] sha384_vec = Hex.decode("d209b2f985ff77301fd1");
        private static final byte[] sha512_vec = Hex.decode("0c51da7c89503acc0050");
        private static final byte[] sha512_224_vec = Hex.decode("86e14446abd90b94c828");
        private static final byte[] sha512_256_vec = Hex.decode("26593c9ef9b39d94bafc");

        private final FipsAlgorithm algorithm;

        public CounterModeProvider(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public CounterModeProvider(PRF prf)
        {
            this.algorithm = new FipsAlgorithm(COUNTER_MODE.getAlgorithm(), prf);
        }

        public KDFCounterBytesGenerator createEngine()
        {
            final PRF prf = (PRF)algorithm.basicVariation();
            FipsEngineProvider  macProvider = createPRF(prf);

            return SelfTestExecutor.validate(algorithm, new KDFCounterBytesGenerator(macProvider.createEngine()), new VariantKatTest()
            {
                public void evaluate(KDFCounterBytesGenerator kdfGenerator)
                {
                    kdfGenerator.init(new KDFCounterParameters(KI, FIP, FIS, 8));

                    byte[] out = new byte[10];

                    kdfGenerator.generateBytes(out, 0, out.length);

                    if (!Arrays.areEqual(expectedOutput(prf), out))
                    {
                        fail("failed self test on generation: " + Hex.toHexString(out));
                    }
                }
            });
        }

        private static byte[] expectedOutput(PRF prf)
        {
            switch (prf)
            {
            case AES_CMAC:
                return aes_cmac_vec;
            case TRIPLEDES_CMAC:
                return tripleDes_vec;
            case SHA1_HMAC:
                return sha1_vec;
            case SHA224_HMAC:
                return sha224_vec;
            case SHA256_HMAC:
                return sha256_vec;
            case SHA384_HMAC:
                return sha384_vec;
            case SHA512_HMAC:
                return sha512_vec;
            case SHA512_224_HMAC:
                return sha512_224_vec;
            case SHA512_256_HMAC:
                return sha512_256_vec;
            default:
                throw new SelfTestExecutor.TestFailedException("unknown PRF");
            }
        }
    }

    private static final class FeedbackModeProvider
        extends FipsEngineProvider
    {
        private static final byte[] KI = Hex.decode("dff1e50ac0b69dc40f1051d46c2b069c");
        private static final byte[] IV = new byte[]{0x01};
        private static final byte[] FID = new byte[]{0x02};

        private static final byte[] aes_cmac_vec = Hex.decode("af7eb5b9a3eb72a1a0cb");
        private static final byte[] tripleDes_vec = Hex.decode("cf65681ac0d3c4f65ce0");
        private static final byte[] sha1_vec = Hex.decode("bfe9d9a6cd8b7befe0fb");
        private static final byte[] sha224_vec = Hex.decode("71d5790138202ab1edc9");
        private static final byte[] sha256_vec = Hex.decode("650d3f9da0f4a8bcf602");
        private static final byte[] sha384_vec = Hex.decode("2a9375ae10e75a9a5ba2");
        private static final byte[] sha512_vec = Hex.decode("e0f3f35c27358f3d0dda");
        private static final byte[] sha512_224_vec = Hex.decode("5fd1372077522505be4a");
        private static final byte[] sha512_256_vec = Hex.decode("ae930bec79b81ee15c67");

        private final FipsAlgorithm algorithm;

        public FeedbackModeProvider(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public FeedbackModeProvider(PRF prf)
        {
            this.algorithm = new FipsAlgorithm(FEEDBACK_MODE.getAlgorithm(), prf);
        }

        public KDFFeedbackBytesGenerator createEngine()
        {
            final PRF prf = (PRF)algorithm.basicVariation();
            FipsEngineProvider macProvider = createPRF(prf);

            return SelfTestExecutor.validate(algorithm, new KDFFeedbackBytesGenerator(macProvider.createEngine()), new VariantKatTest()
            {
                public void evaluate(KDFFeedbackBytesGenerator kdfGenerator)
                {
                    kdfGenerator.init(KDFFeedbackParameters.createWithCounter(KDFFeedbackParameters.AFTER_FIXED, KI, IV, FID, 8));

                    byte[] out = new byte[10];

                    kdfGenerator.generateBytes(out, 0, out.length);

                    if (!Arrays.areEqual(expectedOutput(prf), out))
                    {
                        fail("failed self test on generation: " + Hex.toHexString(out));
                    }
                }
            });
        }

        private static byte[] expectedOutput(PRF prf)
        {
            switch (prf)
            {
            case AES_CMAC:
                return aes_cmac_vec;
            case TRIPLEDES_CMAC:
                return tripleDes_vec;
            case SHA1_HMAC:
                return sha1_vec;
            case SHA224_HMAC:
                return sha224_vec;
            case SHA256_HMAC:
                return sha256_vec;
            case SHA384_HMAC:
                return sha384_vec;
            case SHA512_HMAC:
                return sha512_vec;
            case SHA512_224_HMAC:
                return sha512_224_vec;
            case SHA512_256_HMAC:
                return sha512_256_vec;
            default:
                throw new SelfTestExecutor.TestFailedException("unknown PRF");
            }
        }
    }

    private static final class DoublePipelineModeProvider
        extends FipsEngineProvider
    {
        private static final byte[] KI = Hex.decode("dff1e50ac0b69dc40f1051d46c2b069c");
        private static final byte[] FID = new byte[]{0x02};

        private static final byte[] aes_cmac_vec = Hex.decode("ace76ed103e31681ed03");
        private static final byte[] tripleDes_vec = Hex.decode("41d79be29b5c34ffa40d");
        private static final byte[] sha1_vec = Hex.decode("e5e5666cb2a73b8ce638");
        private static final byte[] sha224_vec = Hex.decode("c4c12b540e51d106abd8");
        private static final byte[] sha256_vec = Hex.decode("b6c232a28b4b450210ee");
        private static final byte[] sha384_vec = Hex.decode("48268b8bf87297a5ce8f");
        private static final byte[] sha512_vec = Hex.decode("52d86063e22a84188285");
        private static final byte[] sha512_224_vec = Hex.decode("d1f521fbc7e736685709");
        private static final byte[] sha512_256_vec = Hex.decode("dca0e9d25e22ca54c0ca");

        private final FipsAlgorithm algorithm;

        public DoublePipelineModeProvider(FipsAlgorithm algorithm)
        {
            this.algorithm = algorithm;
        }

        public DoublePipelineModeProvider(PRF prf)
        {
            this.algorithm = new FipsAlgorithm(DOUBLE_PIPELINE_ITERATION_MODE.getAlgorithm(), prf);
        }

        public KDFDoublePipelineIterationBytesGenerator createEngine()
        {
            final PRF prf = (PRF)algorithm.basicVariation();
            FipsEngineProvider macProvider = createPRF(prf);

            return SelfTestExecutor.validate(algorithm, new KDFDoublePipelineIterationBytesGenerator(macProvider.createEngine()), new VariantKatTest()
            {
                public void evaluate(KDFDoublePipelineIterationBytesGenerator kdfGenerator)
                {
                    kdfGenerator.init(KDFDoublePipelineIterationParameters.createWithCounter(KDFFeedbackParameters.BEFORE_ITER, KI, FID, 8));

                    byte[] out = new byte[10];

                    kdfGenerator.generateBytes(out, 0, out.length);

                    if (!Arrays.areEqual(expectedOutput(prf), out))
                    {
                        fail("failed self test on generation: " + Hex.toHexString(out));
                    }
                }
            });
        }

        private static byte[] expectedOutput(PRF prf)
        {
            switch (prf)
            {
            case AES_CMAC:
                return aes_cmac_vec;
            case TRIPLEDES_CMAC:
                return tripleDes_vec;
            case SHA1_HMAC:
                return sha1_vec;
            case SHA224_HMAC:
                return sha224_vec;
            case SHA256_HMAC:
                return sha256_vec;
            case SHA384_HMAC:
                return sha384_vec;
            case SHA512_HMAC:
                return sha512_vec;
            case SHA512_224_HMAC:
                return sha512_224_vec;
            case SHA512_256_HMAC:
                return sha512_256_vec;
            default:
                throw new SelfTestExecutor.TestFailedException("unknown PRF");
            }
        }
    }

    private static void tlsLegacyKAT()
    {
        final Mac md5Hmac = new HMac(new MD5Digest());
        final Mac sha1HMac = FipsSHS.createHMac(FipsSHS.Algorithm.SHA1_HMAC);

        TLSParameters testParams = new TLSParameters(TLS1_0.getAlgorithm(), Hex.decode("0102030405060708090a0b0c0d0e0f"), TLSStage.MASTER_SECRET, Hex.decode("deadbeefbeefdead"));
        byte[] kat = PRF_legacy(testParams, testParams.secret, testParams.label, 32, md5Hmac, sha1HMac);
        if (!Arrays.areEqual(kat, Hex.decode("ef9dca01113c0f6fcaef528e604b3092c8e65022de73a1b117408297a0d969a9")))
        {
            FipsStatus.moveToErrorStatus(new FipsSelfTestFailedError("Exception on self test: TLS Legacy KAT", TLS1_0.getAlgorithm()));
        }
    }

    private static void tls1_1and2KAT()
    {
        TLSParameters testParams = new TLSParameters(TLS1_2.getAlgorithm(), Hex.decode("0102030405060708090a0b0c0d0e0f"), TLSStage.MASTER_SECRET, Hex.decode("deadbeefbeefdead"));
        byte[] kat = PRF(testParams, TLSPRF.SHA256_HMAC, testParams.secret, testParams.label, 32);
        if (!Arrays.areEqual(kat, Hex.decode("fd9224c363882243d0d949139981093693407e438a508b3c324fd163247e210f")))
        {
            FipsStatus.moveToErrorStatus(new FipsSelfTestFailedError("Exception on self test: TLS KAT", TLS1_1.getAlgorithm()));
        }
    }

    private static void sshKAT()
    {
        final Digest sha256 = FipsSHS.createDigest(FipsSHS.Algorithm.SHA256);
        SSHParameters testParams = new SSHParameters(SSH.getAlgorithm(), 'A', Hex.decode("0102030405060708090a0b0c0d0e0f"), Hex.decode("deadbeefbeefdead"), Hex.decode("a1a2a3a4a5a6a7a8a9a0aaabacadaeaf"));
        byte[] kat = new byte[32];
        SSHOperatorFactory.hash(sha256, testParams, kat, 0, 32);
        if (!Arrays.areEqual(kat, Hex.decode("5ec5d5b69202eecc55e4d932cd9907352c349b0c2ecd2432356dba984495cf2d")))
        {
            FipsStatus.moveToErrorStatus(new FipsSelfTestFailedError("Exception on self test: SSH KAT", SSH.getAlgorithm()));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy