GordianPBESpec.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.api.cipher;

import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;

import java.util.Objects;

/**
 * PBE Specification.
 */
public abstract class GordianPBESpec {
    /**
     * The Separator.
     */
    private static final String SEP = "-";

    /**
     * The PBEType.
     */
    private final GordianPBEType theType;

    /**
     * is the Spec valid?
     */
    private boolean isValid;

    /**
     * Constructor.
     *
     * @param pPBEType the PBEType.
     */
    GordianPBESpec(final GordianPBEType pPBEType) {
        theType = pPBEType;
    }

    /**
     * Obtain the PBEType.
     *
     * @return the PBEType
     */
    public GordianPBEType getPBEType() {
        return theType;
    }

    /**
     * Is the Spec valid?
     *
     * @return true/false
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * Set as valid.
     */
    void setValid() {
        isValid = true;
    }

    /**
     * DigestAndCountSpec.
     */
    public static class GordianPBEDigestAndCountSpec
            extends GordianPBESpec {
        /**
         * The DigestSpec.
         */
        private final GordianDigestSpec theDigestSpec;

        /**
         * The count.
         */
        private final int theCount;

        /**
         * Constructor.
         *
         * @param pPBEType    the PBEType.
         * @param pDigestSpec the digestSpec.
         * @param pCount      the iteration count
         */
        GordianPBEDigestAndCountSpec(final GordianPBEType pPBEType,
                                     final GordianDigestSpec pDigestSpec,
                                     final int pCount) {
            /* Init underlying class and store params */
            super(pPBEType);
            theDigestSpec = pDigestSpec;
            theCount = pCount;

            /* Check validity */
            checkValidity();
        }

        /**
         * Obtain the digestSpec.
         *
         * @return the digestSpec
         */
        public GordianDigestSpec getDigestSpec() {
            return theDigestSpec;
        }

        /**
         * Obtain the iteration count.
         *
         * @return the count
         */
        public int getIterationCount() {
            return theCount;
        }

        /**
         * Check validity.
         */
        private void checkValidity() {
            /* Check PBEType */
            if (getPBEType() != GordianPBEType.PBKDF2
                    && getPBEType() != GordianPBEType.PKCS12) {
                return;
            }

            /* Check DigestSpec and Count > 0 */
            if (theDigestSpec != null
                    && theDigestSpec.isValid()
                    && theCount > 0) {
                setValid();
            }
        }

        @Override
        public boolean equals(final Object pThat) {
            /* Handle trivial cases */
            if (this == pThat) {
                return true;
            }
            if (pThat == null) {
                return false;
            }

            /* Check count, digestSpec and PBEType */
            return pThat instanceof GordianPBEDigestAndCountSpec myThat
                    && theCount == myThat.getIterationCount()
                    && theDigestSpec.equals(myThat.getDigestSpec())
                    && getPBEType() == myThat.getPBEType();
        }

        @Override
        public int hashCode() {
            return Objects.hash(theDigestSpec, theCount, getPBEType());
        }

        @Override
        public String toString() {
            return getPBEType().toString() + SEP + theDigestSpec.toString() + SEP + theCount;
        }
    }

    /**
     * SCryptSpec.
     */
    public static class GordianPBESCryptSpec
            extends GordianPBESpec {
        /**
         * Max Small Block Cost.
         */
        private static final int MAX_SMALL_COST = 0xFFFF;

        /**
         * Parallel limit.
         */
        private static final int PARALLEL_LIMIT = 128;

        /**
         * The BlockSize.
         */
        private final int theBlockSize;

        /**
         * The cost.
         */
        private final int theCost;

        /**
         * The Parallelism.
         */
        private final int theParallel;

        /**
         * Constructor.
         *
         * @param pCost      the cost
         * @param pBlockSize the blockSize
         * @param pParallel  the parallelism
         */
        GordianPBESCryptSpec(final int pCost,
                             final int pBlockSize,
                             final int pParallel) {
            /* Init underlying class and store params */
            super(GordianPBEType.SCRYPT);
            theCost = pCost;
            theBlockSize = pBlockSize;
            theParallel = pParallel;

            /* Check validity */
            checkValidity();
        }

        /**
         * Obtain the blockSize.
         *
         * @return the blockSize
         */
        public int getBlockSize() {
            return theBlockSize;
        }

        /**
         * Obtain the cost.
         *
         * @return the cost
         */
        public int getCost() {
            return theCost;
        }

        /**
         * Obtain the parallelism.
         *
         * @return the parallelism
         */
        public int getParallel() {
            return theParallel;
        }

        /**
         * Check validity.
         */
        private void checkValidity() {
            /* Check BlockSize is > 0 */
            if (theBlockSize <= 0) {
                return;
            }

            /* Check Cost is > 1 and power of two */
            if (theCost <= 1
                    || (theCost & (theCost - 1)) != 0) {
                return;
            }

            /* Check Cost restriction for BlockSize of 1 */
            if (theBlockSize == 1
                    && theCost > MAX_SMALL_COST) {
                return;
            }

            /* Check Parallel restriction */
            final int maxParallel = Integer.MAX_VALUE / (PARALLEL_LIMIT * theBlockSize * Byte.SIZE);
            if (theParallel >= 1
                    && theParallel <= maxParallel) {
                setValid();
            }
        }

        @Override
        public boolean equals(final Object pThat) {
            /* Handle trivial cases */
            if (this == pThat) {
                return true;
            }
            if (pThat == null) {
                return false;
            }

            /* Check cost, blockSize and parallel */
            return pThat instanceof GordianPBESCryptSpec myThat
                    && theCost == myThat.getCost()
                    && theBlockSize == myThat.getBlockSize()
                    && theParallel == myThat.getParallel()
                    && getPBEType() == myThat.getPBEType();
        }

        @Override
        public int hashCode() {
            return Objects.hash(theBlockSize, theCost, theParallel, getPBEType());
        }

        @Override
        public String toString() {
            return getPBEType().toString() + SEP + theBlockSize + SEP + theCost + SEP + theParallel;
        }
    }

    /**
     * Argon2Spec.
     */
    public static class GordianPBEArgon2Spec
            extends GordianPBESpec {
        /**
         * The Memory.
         */
        private final int theMemory;

        /**
         * The lanes.
         */
        private final int theLanes;

        /**
         * The Iterations.
         */
        private final int theIterations;

        /**
         * Constructor.
         *
         * @param pLanes      the Lanes
         * @param pMemory     the Memory
         * @param pIterations the iterations
         */
        GordianPBEArgon2Spec(final int pLanes,
                             final int pMemory,
                             final int pIterations) {
            /* Init underlying class and store params */
            super(GordianPBEType.ARGON2);
            theLanes = pLanes;
            theMemory = pMemory;
            theIterations = pIterations;

            /* Check validity */
            checkValidity();
        }

        /**
         * Obtain the lanes.
         *
         * @return the lanes
         */
        public int getLanes() {
            return theLanes;
        }

        /**
         * Obtain the memory.
         *
         * @return the memory
         */
        public int getMemory() {
            return theMemory;
        }

        /**
         * Obtain the iteration count.
         *
         * @return the count
         */
        public int getIterationCount() {
            return theIterations;
        }

        /**
         * Check validity.
         */
        private void checkValidity() {
            /* Check Iterations and Lanes are > 0 */
            if (theIterations <= 0
                    || theLanes <= 0) {
                return;
            }

            /* Check Memory is >= 2 * lanes */
            if (theMemory >= theLanes << 1) {
                setValid();
            }
        }

        @Override
        public boolean equals(final Object pThat) {
            /* Handle trivial cases */
            if (this == pThat) {
                return true;
            }
            if (pThat == null) {
                return false;
            }

            /* Check lanes, memory and iterations */
            return pThat instanceof GordianPBEArgon2Spec myThat
                    && theLanes == myThat.getLanes()
                    && theMemory == myThat.getMemory()
                    && theIterations == myThat.getIterationCount()
                    && getPBEType() == myThat.getPBEType();
        }

        @Override
        public int hashCode() {
            return Objects.hash(theLanes, theMemory, theIterations, getPBEType());
        }

        @Override
        public String toString() {
            return getPBEType().toString() + SEP + theLanes + SEP + theMemory + SEP + theIterations;
        }
    }
}