GordianKeySetRecipe.java

/*
 * GordianKnot: Security Suite
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.gordianknot.impl.core.keyset;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianIdManager;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianPersonalisation;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianPersonalisation.GordianPersonalId;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;

/**
 * Class for assembling/disassembling data encrypted by a KeySet.
 */
public final class GordianKeySetRecipe {
    /**
     * Recipe length (Integer).
     */
    private static final int RECIPELEN = Integer.BYTES;

    /**
     * Salt length.
     */
    private static final int SALTLEN = GordianLength.LEN_128.getByteLength();

    /**
     * Salt length.
     */
    static final int HDRLEN = SALTLEN + RECIPELEN;

    /**
     * The Recipe.
     */
    private final byte[] theRecipe;

    /**
     * The KeySet Parameters.
     */
    private final GordianKeySetParameters theParams;

    /**
     * Constructor for new recipe.
     *
     * @param pFactory the factory
     * @param pSpec    the keySetSpec
     * @param pAEAD    true/false is AEAD in use?
     */
    private GordianKeySetRecipe(final GordianBaseFactory pFactory,
                                final GordianKeySetSpec pSpec,
                                final boolean pAEAD) {
        /* Allocate new set of parameters */
        theParams = new GordianKeySetParameters(pFactory, pSpec, pAEAD);
        theRecipe = theParams.getRecipe();
    }

    /**
     * Constructor for external form parse.
     *
     * @param pFactory the factory
     * @param pSpec    the keySetSpec
     * @param pHeader  the header
     * @param pAEAD    true/false is AEAD in use?
     */
    private GordianKeySetRecipe(final GordianBaseFactory pFactory,
                                final GordianKeySetSpec pSpec,
                                final byte[] pHeader,
                                final boolean pAEAD) {
        /* Allocate buffers */
        theRecipe = new byte[RECIPELEN];
        final byte[] mySalt = new byte[SALTLEN];

        /* Copy Data into buffers */
        System.arraycopy(pHeader, 0, theRecipe, 0, RECIPELEN);
        System.arraycopy(pHeader, RECIPELEN, mySalt, 0, SALTLEN);

        /* Allocate new set of parameters */
        theParams = new GordianKeySetParameters(pFactory, pSpec, theRecipe, mySalt, pAEAD);
    }

    /**
     * Create a new recipe.
     *
     * @param pFactory the factory
     * @param pSpec    the keySetSpec
     * @param pAEAD    true/false is AEAD in use?
     * @return the recipe
     */
    static GordianKeySetRecipe newRecipe(final GordianBaseFactory pFactory,
                                         final GordianKeySetSpec pSpec,
                                         final boolean pAEAD) {
        return new GordianKeySetRecipe(pFactory, pSpec, pAEAD);
    }

    /**
     * parse the encryption recipe.
     *
     * @param pFactory the factory
     * @param pSpec    the keySetSpec
     * @param pHeader  the header
     * @param pAEAD    true/false is AEAD in use?
     * @return the recipe
     * @throws GordianException on error
     */
    static GordianKeySetRecipe parseRecipe(final GordianBaseFactory pFactory,
                                           final GordianKeySetSpec pSpec,
                                           final byte[] pHeader,
                                           final boolean pAEAD) throws GordianException {
        /* Check that the input data is long enough */
        if (pHeader.length < HDRLEN) {
            throw new GordianDataException("Header too short");
        }

        /* Process the recipe */
        return new GordianKeySetRecipe(pFactory, pSpec, pHeader, pAEAD);
    }

    /**
     * Obtain the keySet parameters.
     *
     * @return the parameters
     */
    GordianKeySetParameters getParameters() {
        return theParams;
    }

    /**
     * Build Header.
     *
     * @param pHeader the header
     */
    void buildHeader(final byte[] pHeader) {
        /* Copy Data into buffer */
        System.arraycopy(theRecipe, 0, pHeader, 0, RECIPELEN);
        System.arraycopy(theParams.getSalt(), 0, pHeader, RECIPELEN, SALTLEN);
    }

    /**
     * The parameters class.
     */
    public static final class GordianKeySetParameters {
        /**
         * The Recipe.
         */
        private final byte[] theRecipe;

        /**
         * The Salt.
         */
        private final byte[] theSalt;

        /**
         * The SymKeySet.
         */
        private GordianSymKeyType[] theSymKeyTypes;

        /**
         * The DigestType.
         */
        private GordianDigestType theDigestType;

        /**
         * The Poly1305 SymKeyType.
         */
        private GordianSymKeyType thePoly1305SymKeyType;

        /**
         * The Initialisation Vector.
         */
        private byte[] theInitVector;

        /**
         * Construct the parameters from random.
         *
         * @param pFactory the factory
         * @param pSpec    the keySetSpec
         * @param pAEAD    true/false is AEAD in use?
         */
        GordianKeySetParameters(final GordianBaseFactory pFactory,
                                final GordianKeySetSpec pSpec,
                                final boolean pAEAD) {
            /* Obtain random */
            final SecureRandom myRandom = pFactory.getRandomSource().getRandom();

            /* Allocate the initVector */
            theSalt = new byte[SALTLEN];
            myRandom.nextBytes(theSalt);

            /* Generate recipe */
            final int mySeed = myRandom.nextInt();
            theRecipe = GordianDataConverter.integerToByteArray(mySeed);

            /* Process the recipe */
            processRecipe(pFactory, pSpec, pAEAD);
        }

        /**
         * Construct the parameters from recipe.
         *
         * @param pFactory the factory
         * @param pSpec    the keySetSpec
         * @param pRecipe  the recipe bytes
         * @param pSalt    the salt
         * @param pAEAD    true/false is AEAD in use?
         */
        GordianKeySetParameters(final GordianBaseFactory pFactory,
                                final GordianKeySetSpec pSpec,
                                final byte[] pRecipe,
                                final byte[] pSalt,
                                final boolean pAEAD) {
            /* Store recipe, salt and Mac */
            theRecipe = pRecipe;
            theSalt = pSalt;

            /* Process the recipe */
            processRecipe(pFactory, pSpec, pAEAD);
        }

        /**
         * Process the recipe and salt.
         *
         * @param pFactory the factory
         * @param pSpec    the keySetSpec
         * @param pAEAD    true/false is AEAD in use?
         */
        private void processRecipe(final GordianBaseFactory pFactory,
                                   final GordianKeySetSpec pSpec,
                                   final boolean pAEAD) {
            /* Obtain Id manager and random */
            final GordianIdManager myManager = pFactory.getIdManager();
            final GordianPersonalisation myPersonal = pFactory.getPersonalisation();

            /* Calculate the initVector */
            theInitVector = myPersonal.adjustIV(theSalt);

            /* Generate seededRandom */
            final Random mySeededRandom = myPersonal.getSeededRandom(GordianPersonalId.KEYSETRANDOM, theRecipe);

            /* Ask for the relevant number of keys */
            final int myNumKeys = pSpec.getCipherSteps() + (pAEAD ? 1 : 0);
            theSymKeyTypes = myManager.deriveKeySetSymKeyTypesFromSeed(mySeededRandom, pSpec.getKeyLength(), myNumKeys);

            /* Adjust for AEAD */
            if (pAEAD) {
                theDigestType = myManager.deriveExternalDigestTypeFromSeed(mySeededRandom);
                thePoly1305SymKeyType = theSymKeyTypes[myNumKeys - 1];
                theSymKeyTypes = Arrays.copyOf(theSymKeyTypes, myNumKeys - 1);
            }
        }

        /**
         * Obtain the salt.
         *
         * @return the salt
         */
        byte[] getSalt() {
            return theSalt;
        }

        /**
         * Obtain the SymKey Types.
         *
         * @return the symKeyTypes
         */
        GordianSymKeyType[] getSymKeyTypes() {
            return theSymKeyTypes;
        }

        /**
         * Obtain the digestType.
         *
         * @return the digestType
         */
        GordianDigestType getDigestType() {
            return theDigestType;
        }

        /**
         * Obtain the Poly1305 symKeyType.
         *
         * @return the symKeyType
         */
        GordianSymKeyType getPoly1305SymKeyType() {
            return thePoly1305SymKeyType;
        }

        /**
         * Obtain the Initialisation vector.
         *
         * @return the initialisation vector
         */
        byte[] getInitVector() {
            return theInitVector;
        }

        /**
         * Obtain the Recipe.
         *
         * @return the recipe
         */
        byte[] getRecipe() {
            return theRecipe;
        }
    }
}