GordianHKDFParams.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.kdf;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * HKDF parameters.
 */
public final class GordianHKDFParams {
    /**
     * The initial keying material.
     */
    private final List<byte[]> theIKMs;

    /**
     * The salts.
     */
    private final List<byte[]> theSalts;

    /**
     * The pseudo-random key.
     */
    private final byte[] thePRK;

    /**
     * The expanding info.
     */
    private final List<byte[]> theInfo;

    /**
     * The mode.
     */
    private final GordianHKDFMode theMode;

    /**
     * The expand length.
     */
    private final int theLength;

    /**
     * Constructor.
     *
     * @param pMode   theMode
     * @param pPRK    the pseudo-random key
     * @param pLength the length
     * @throws GordianException on error
     */
    private GordianHKDFParams(final GordianHKDFMode pMode,
                              final byte[] pPRK,
                              final int pLength) throws GordianException {
        /* Store parameters */
        theMode = pMode;
        thePRK = pPRK == null ? null : pPRK.clone();
        theLength = pLength;
        theIKMs = new ArrayList<>();
        theSalts = new ArrayList<>();
        theInfo = new ArrayList<>();

        /* Check PRK */
        if (GordianHKDFMode.EXPAND.equals(theMode)
                && (thePRK == null || pPRK.length == 0)) {
            throw new GordianDataException("PRK must be non-null and non-zero length for expandOnly");
        }

        /* Check length */
        if (theMode.doExpand() && theLength <= 0) {
            throw new GordianDataException("Length must be greater than zero");
        }
    }

    /**
     * Create an extractOnly parameters.
     *
     * @return an extractOnly parameters
     * @throws GordianException on error
     */
    public static GordianHKDFParams extractOnly() throws GordianException {
        return new GordianHKDFParams(GordianHKDFMode.EXTRACT, null, 0);
    }

    /**
     * Create an expandOnly parameters.
     *
     * @param pPRK    the pseudo-random key
     * @param pLength the length
     * @return an expandOnly parameters
     * @throws GordianException on error
     */
    public static GordianHKDFParams expandOnly(final byte[] pPRK,
                                               final int pLength) throws GordianException {
        return new GordianHKDFParams(GordianHKDFMode.EXPAND, pPRK, pLength);
    }

    /**
     * Create an extractThenExpand parameters.
     *
     * @param pLength the length
     * @return an extractThenExpand parameters
     * @throws GordianException on error
     */
    public static GordianHKDFParams extractThenExpand(final int pLength) throws GordianException {
        return new GordianHKDFParams(GordianHKDFMode.EXTRACTTHENEXPAND, null, pLength);
    }

    /**
     * Obtain the mode.
     *
     * @return the mode
     */
    GordianHKDFMode getMode() {
        return theMode;
    }

    /**
     * Obtain the PRK.
     *
     * @return the pseudo-random key
     */
    byte[] getPRK() {
        return thePRK;
    }

    /**
     * Add initial keying material.
     *
     * @param pIKM the initial keying material
     * @return the parameters
     */
    public GordianHKDFParams withIKM(final byte[] pIKM) {
        if (pIKM != null && pIKM.length > 0) {
            theIKMs.add(pIKM.clone());
        }
        return this;
    }

    /**
     * Obtain the ikmIterator.
     *
     * @return the iterator.
     */
    Iterator<byte[]> ikmIterator() {
        return theIKMs.iterator();
    }

    /**
     * Add salt.
     *
     * @param pSalt the salt
     * @return the parameters
     */
    public GordianHKDFParams withSalt(final byte[] pSalt) {
        if (pSalt != null && pSalt.length > 0) {
            theSalts.add(pSalt.clone());
        }
        return this;
    }

    /**
     * Obtain the saltIterator.
     *
     * @return the iterator.
     */
    Iterator<byte[]> saltIterator() {
        return theSalts.iterator();
    }

    /**
     * Add info.
     *
     * @param pInfo the info
     * @return the parameters
     */
    public GordianHKDFParams withInfo(final byte[] pInfo) {
        if (pInfo != null && pInfo.length > 0) {
            theInfo.add(pInfo.clone());
        }
        return this;
    }

    /**
     * Obtain the infoIterator.
     *
     * @return the iterator.
     */
    Iterator<byte[]> infoIterator() {
        return theInfo.iterator();
    }

    /**
     * Obtain the length.
     *
     * @return the length
     */
    int getLength() {
        return theLength;
    }

    /**
     * Clear parameters.
     */
    public void clearParameters() {
        /* Clear all initial keying materials */
        for (final byte[] myIKM : theIKMs) {
            Arrays.fill(myIKM, (byte) 0);
        }

        /* Clear all salts */
        for (final byte[] mySalt : theSalts) {
            Arrays.fill(mySalt, (byte) 0);
        }

        /* Clear all info */
        for (final byte[] myInfo : theInfo) {
            Arrays.fill(myInfo, (byte) 0);
        }

        /* Clear PRK */
        if (thePRK != null) {
            Arrays.fill(thePRK, (byte) 0);
        }
    }
}