GordianIdManager.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.base;

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.GordianCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacFactory;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacType;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * Security Id Manager.
 */
public class GordianIdManager {
    /**
     * The Factory.
     */
    private final GordianBaseSupplier theFactory;

    /**
     * Constructor.
     *
     * @param pFactory the security factory
     * @throws GordianException on error
     */
    public GordianIdManager(final GordianBaseSupplier pFactory) throws GordianException {
        /* Store the factory */
        theFactory = pFactory;
    }

    /**
     * Obtain random SymKeySpec.
     *
     * @param pKeyLen the keyLength
     * @return the random symKeySpec
     */
    public GordianSymKeySpec generateRandomSymKeySpec(final GordianLength pKeyLen) {
        /* Access the list of symKeySpecs and unique symKeyTypes */
        final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
        final List<GordianSymKeySpec> mySpecs = myCiphers.listAllSupportedSymKeySpecs(pKeyLen);
        final List<GordianSymKeyType> myTypes = mySpecs.stream().map(GordianSymKeySpec::getSymKeyType).toList();

        /* Determine a random index into the list and obtain the symKeyType */
        final SecureRandom myRandom = theFactory.getRandomSource().getRandom();
        int myIndex = myRandom.nextInt(myTypes.size());
        final GordianSymKeyType myKeyType = myTypes.get(myIndex);

        /* Select from among possible keySpecs of this type */
        mySpecs.removeIf(s -> s.getSymKeyType() != myKeyType);
        myIndex = myRandom.nextInt(mySpecs.size());
        return mySpecs.get(myIndex);
    }

    /**
     * Obtain set of random keySet SymKeySpecs.
     *
     * @param pKeyLen the keyLength
     * @param pCount  the count
     * @return the random symKeySpecs
     */
    public GordianSymKeySpec[] generateRandomKeySetSymKeySpecs(final GordianLength pKeyLen,
                                                               final int pCount) {
        /* Access the list to select from */
        final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
        final List<GordianSymKeySpec> mySpecs = myCiphers.listAllSupportedSymKeySpecs(pKeyLen);

        /* Remove the short block specs that cannot support SIC Mode */
        mySpecs.removeIf(s -> s.getBlockLength() == GordianLength.LEN_64);

        /* Create the Access list and loop to populate */
        final GordianSymKeySpec[] myResult = new GordianSymKeySpec[pCount];
        for (int i = 0; i < pCount; i++) {
            myResult[i] = selectSymKeySpecFromList(mySpecs);
        }

        /* Return the result  */
        return myResult;
    }

    /**
     * Obtain a random symKeySpec and remove all of the same symKeyType.
     *
     * @param pList the list of symKeySpecs
     * @return the random symKeySpec
     */
    public GordianSymKeySpec selectSymKeySpecFromList(final List<GordianSymKeySpec> pList) {
        /* Select the random Spec */
        final List<GordianSymKeyType> myTypes = pList.stream().map(GordianSymKeySpec::getSymKeyType).toList();
        final SecureRandom myRandom = theFactory.getRandomSource().getRandom();
        int myIndex = myRandom.nextInt(pList.size());
        final GordianSymKeyType myType = myTypes.get(myIndex);

        /* Strip out the possible specs */
        final List<GordianSymKeySpec> mySpecs = pList.stream().filter(mySpec -> mySpec.getSymKeyType() == myType).toList();
        pList.removeIf(mySpec -> mySpec.getSymKeyType() == myType);

        /* Return the result */
        myIndex = myRandom.nextInt(mySpecs.size());
        return mySpecs.get(myIndex);
    }

    /**
     * Obtain random StreamKeySpec.
     *
     * @param pKeyLen    the keyLength
     * @param pLargeData only generate a Key that is suitable for processing large amounts of data
     * @return the random streamKeySpec
     */
    public GordianStreamKeySpec generateRandomStreamKeySpec(final GordianLength pKeyLen,
                                                            final boolean pLargeData) {
        /* Access the list to select from */
        final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
        final List<GordianStreamKeySpec> mySpecs = myCiphers.listAllSupportedStreamKeySpecs(pKeyLen);
        if (pLargeData) {
            mySpecs.removeIf(s -> !s.getStreamKeyType().supportsLargeData());
        }
        final List<GordianStreamKeyType> myTypes = mySpecs.stream().map(GordianStreamKeySpec::getStreamKeyType).toList();

        /* Determine a random index into the list and obtain the streamKeyType */
        final SecureRandom myRandom = theFactory.getRandomSource().getRandom();
        int myIndex = myRandom.nextInt(myTypes.size());
        final GordianStreamKeyType myKeyType = myTypes.get(myIndex);

        /* Select from among possible keySpecs of this type */
        mySpecs.removeIf(s -> s.getStreamKeyType() != myKeyType);
        myIndex = myRandom.nextInt(mySpecs.size());
        return mySpecs.get(myIndex);
    }

    /**
     * Obtain random DigestSpec.
     *
     * @param pLargeData only generate a Digest that is suitable for processing large amounts of data
     * @return the random digestSpec
     */
    public GordianDigestSpec generateRandomDigestSpec(final boolean pLargeData) {
        /* Access the list to select from */
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();
        final List<GordianDigestSpec> mySpecs = myDigests.listAllSupportedSpecs();
        if (pLargeData) {
            mySpecs.removeIf(s -> !s.getDigestType().supportsLargeData());
        }
        final List<GordianDigestType> myTypes = mySpecs.stream().map(GordianDigestSpec::getDigestType).toList();

        /* Determine a random index into the list and obtain the digestType */
        final SecureRandom myRandom = theFactory.getRandomSource().getRandom();
        int myIndex = myRandom.nextInt(myTypes.size());
        final GordianDigestType myDigestType = myTypes.get(myIndex);

        /* Select from among possible digestSpecs of this type */
        mySpecs.removeIf(s -> s.getDigestType() != myDigestType);
        myIndex = myRandom.nextInt(mySpecs.size());
        return mySpecs.get(myIndex);
    }

    /**
     * Derive set of keySet SymKeyTypes from seed.
     *
     * @param pRandom the seeded random
     * @param pKeyLen the keyLength
     * @param pCount  the number of distinct digestTypes to select
     * @return the remaining seed
     */
    public GordianSymKeyType[] deriveKeySetSymKeyTypesFromSeed(final Random pRandom,
                                                               final GordianLength pKeyLen,
                                                               final int pCount) {
        /* Access the list to select from */
        final GordianCipherFactory myCiphers = theFactory.getCipherFactory();
        final GordianValidator myValidator = theFactory.getValidator();
        final List<GordianSymKeyType> myTypes = myCiphers.listAllSupportedSymKeyTypes().stream()
                .filter(myValidator.supportedKeySetSymKeyTypes(pKeyLen))
                .collect(Collectors.toCollection(ArrayList::new));

        /* Allocate the array to return */
        final GordianSymKeyType[] myResult = new GordianSymKeyType[pCount];

        /* Loop selecting digestTypes */
        for (int i = 0; i < pCount; i++) {
            /* Select from the list and remove the selected item */
            final int myIndex = pRandom.nextInt(myTypes.size());
            final GordianSymKeyType myType = myTypes.get(myIndex);
            myTypes.removeIf(t -> t == myType);
            myResult[i] = myType;
        }

        /* return the selected symKeyTypes */
        return myResult;
    }

    /**
     * Derive secret lockDigestType from seed.
     *
     * @param pRandom the seeded random
     * @return the selected keyHashDigestTypes
     */
    public GordianDigestType deriveLockSecretTypeFromSeed(final Random pRandom) {
        /* Access the list to select from */
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();
        final GordianValidator myValidator = theFactory.getValidator();
        final List<GordianDigestType> myTypes = myDigests.listAllSupportedTypes().stream()
                .filter(myValidator.supportedLockDigestTypes())
                .filter(myValidator.isExternalHashDigest())
                .toList();

        /* Select from the list and remove the selected item */
        final int myIndex = pRandom.nextInt(myTypes.size());
        return myTypes.get(myIndex);
    }

    /**
     * Derive set of lockDigestTypes from seed.
     *
     * @param pRandom the seeded random
     * @param pCount  the number of distinct digestTypes to select
     * @return the selected keyHashDigestTypes
     */
    public GordianDigestType[] deriveLockDigestTypesFromSeed(final Random pRandom,
                                                             final int pCount) {
        /* Access the list to select from */
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();
        final GordianValidator myValidator = theFactory.getValidator();
        final List<GordianDigestType> myTypes = myDigests.listAllSupportedTypes().stream()
                .filter(myValidator.supportedLockDigestTypes())
                .collect(Collectors.toCollection(ArrayList::new));

        /* Allocate the array to return */
        final GordianDigestType[] myResult = new GordianDigestType[pCount];

        /* Loop selecting digestTypes */
        for (int i = 0; i < pCount; i++) {
            /* Select from the list and remove the selected item */
            final int myIndex = pRandom.nextInt(myTypes.size());
            final GordianDigestType myType = myTypes.get(myIndex);
            myTypes.removeIf(t -> t == myType);
            myResult[i] = myType;
        }

        /* return the selected digestTypes */
        return myResult;
    }

    /**
     * Derive a Lock externalDigestTypes from seededRandom.
     *
     * @param pRandom the seeded random
     * @return the selected externalDigestType
     */
    public GordianDigestType deriveExternalDigestTypeFromSeed(final Random pRandom) {
        /* Access the list to select from */
        final GordianValidator myValidator = theFactory.getValidator();
        final List<GordianDigestType> myTypes = myValidator.listAllExternalDigestTypes();

        /* Select from the list */
        final int myIndex = pRandom.nextInt(myTypes.size());
        return myTypes.get(myIndex);
    }

    /**
     * Derive set of keyGenDigestTypes from seed.
     *
     * @param pRandom the seeded random
     * @param pCount  the number of distinct digestTypes to select
     * @return the selected keyGenDigestTypes
     */
    public GordianDigestType[] deriveKeyGenDigestTypesFromSeed(final Random pRandom,
                                                               final int pCount) {
        /* Access the list to select from */
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();
        final GordianValidator myValidator = theFactory.getValidator();
        final List<GordianDigestType> myTypes = myDigests.listAllSupportedTypes().stream()
                .filter(myValidator.supportedKeyGenDigestTypes())
                .collect(Collectors.toCollection(ArrayList::new));

        /* Allocate the array to return */
        final GordianDigestType[] myResult = new GordianDigestType[pCount];

        /* Loop selecting digestTypes */
        for (int i = 0; i < pCount; i++) {
            /* Select from the list and remove the selected item */
            final int myIndex = pRandom.nextInt(myTypes.size());
            final GordianDigestType myType = myTypes.get(myIndex);
            myTypes.removeIf(t -> t == myType);
            myResult[i] = myType;
        }

        /* return the selected digestTypes */
        return myResult;
    }

    /**
     * Derive agreementDigestType from seed.
     *
     * @param pRandom the seeded random
     * @return the selected agreementDigestType
     */
    public GordianDigestType deriveAgreementDigestTypeFromSeed(final Random pRandom) {
        /* Access the list to select from */
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();
        final GordianValidator myValidator = theFactory.getValidator();
        final List<GordianDigestType> myTypes = myDigests.listAllSupportedTypes().stream()
                .filter(myValidator.supportedAgreementDigestTypes())
                .toList();

        /* Select from the list */
        final int myIndex = pRandom.nextInt(myTypes.size());
        return myTypes.get(myIndex);
    }

    /**
     * generate random GordianMacSpec.
     *
     * @param pKeyLen    the keyLength
     * @param pLargeData only generate a Mac that is suitable for parsing large amounts of data
     * @return the new MacSpec
     */
    public GordianMacSpec generateRandomMacSpec(final GordianLength pKeyLen,
                                                final boolean pLargeData) {
        /* Access the list to select from */
        final GordianMacFactory myMacs = theFactory.getMacFactory();
        final List<GordianMacSpec> mySpecs = myMacs.listAllSupportedSpecs(pKeyLen);

        /* Modify list (if required) to remove macs that do not support largeData */
        if (pLargeData) {
            mySpecs.removeIf(s -> !s.getMacType().supportsLargeData());
        }

        /* Modify list to remove rawPoly1305 */
        mySpecs.remove(GordianMacSpecBuilder.poly1305Mac());

        /* Extract the macTypes */
        final List<GordianMacType> myTypes = mySpecs.stream().map(GordianMacSpec::getMacType).toList();

        /* Determine a random index into the list and obtain the macType */
        final SecureRandom myRandom = theFactory.getRandomSource().getRandom();
        int myIndex = myRandom.nextInt(myTypes.size());
        final GordianMacType myMacType = myTypes.get(myIndex);

        /* Select from among possible macSpecs of this type */
        mySpecs.removeIf(s -> s.getMacType() != myMacType);
        myIndex = myRandom.nextInt(mySpecs.size());
        return mySpecs.get(myIndex);
    }
}