GordianCoreMacFactory.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.mac;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.spec.GordianSymKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacFactory;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacParamsBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.mac.spec.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.spec.GordianMacSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.mac.spec.GordianMacType;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipherFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.mac.GordianCoreMacSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.mac.GordianCoreMacSpecBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Core Mac Factory.
 */
public abstract class GordianCoreMacFactory
        implements GordianMacFactory {
    /**
     * The factory.
     */
    private final GordianBaseFactory theFactory;

    /**
     * Constructor.
     *
     * @param pFactory the factory.
     */
    protected GordianCoreMacFactory(final GordianBaseFactory pFactory) {
        theFactory = pFactory;
    }

    /**
     * Obtain the factory.
     *
     * @return the factory
     */
    protected GordianBaseFactory getFactory() {
        return theFactory;
    }

    @Override
    public GordianMacSpecBuilder newMacSpecBuilder() {
        return GordianCoreMacSpecBuilder.newInstance();
    }

    @Override
    public GordianMacParamsBuilder newMacParamsBuilder() {
        return GordianCoreMacParamsBuilder.newInstance();
    }

    @Override
    public Predicate<GordianMacSpec> supportedMacSpecs() {
        return this::validMacSpec;
    }

    @Override
    public Predicate<GordianMacType> supportedMacTypes() {
        return this::validMacType;
    }

    /**
     * Obtain predicate for supported hMac digestSpecs.
     *
     * @return the predicate
     */
    public Predicate<GordianDigestSpec> supportedHMacDigestSpecs() {
        return this::validHMacSpec;
    }

    /**
     * Obtain predicate for supported poly1305 symKeySpecs.
     *
     * @return the predicate
     */
    private Predicate<GordianSymKeySpec> supportedPoly1305SymKeySpecs() {
        return p -> p == null
                || (validPoly1305SymKeySpec(p)
                && p.getBlockLength() == GordianLength.LEN_128);
    }

    /**
     * Obtain predicate for supported gMac symKeySpecs.
     *
     * @return the predicate
     */
    private Predicate<GordianSymKeySpec> supportedGMacSymKeySpecs() {
        return p -> validGMacSymKeySpec(p)
                && p.getBlockLength() == GordianLength.LEN_128;
    }

    /**
     * Obtain predicate for supported cMac symKeyTypes.
     *
     * @return the predicate
     */
    private Predicate<GordianSymKeySpec> supportedCMacSymKeySpecs() {
        return this::validCMacSymKeySpec;
    }

    /**
     * Check MacType.
     *
     * @param pMacType the macType
     * @return true/false
     */
    protected boolean validMacType(final GordianMacType pMacType) {
        return pMacType != null;
    }

    /**
     * Check the macSpec.
     *
     * @param pMacSpec the macSpec
     * @throws GordianException on error
     */
    protected void checkMacSpec(final GordianKeySpec pMacSpec) throws GordianException {
        /* Check validity of MacSpec */
        if (!(pMacSpec instanceof GordianMacSpec mySpec)
                || !supportedMacSpecs().test(mySpec)) {
            throw new GordianDataException(GordianBaseData.getInvalidText(pMacSpec));
        }
    }

    /**
     * Check HMacSpec.
     *
     * @param pDigestSpec the digestSpec
     * @return true/false
     */
    public boolean validHMacSpec(final GordianDigestSpec pDigestSpec) {
        /* Access details */
        final GordianDigestType myType = pDigestSpec.getDigestType();
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();

        /* Check validity */
        return theFactory.getValidator().supportedHMacDigestTypes().test(myType)
                && !pDigestSpec.isXofMode()
                && myDigests.supportedDigestSpecs().test(pDigestSpec);
    }

    /**
     * Check MacSpec.
     *
     * @param pMacSpec the macSpec
     * @return true/false
     */
    private boolean validMacSpec(final GordianMacSpec pMacSpec) {
        /* Reject invalid macSpec */
        if (pMacSpec == null || !pMacSpec.isValid()) {
            return false;
        }

        /* Check that the macType is supported */
        final GordianMacType myType = pMacSpec.getMacType();
        if (!supportedMacTypes().test(myType)) {
            return false;
        }

        /* Switch on MacType */
        final GordianDigestFactory myDigests = theFactory.getDigestFactory();
        final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
        final GordianCoreMacSpec myMacSpec = (GordianCoreMacSpec) pMacSpec;
        final GordianDigestSpec myDigestSpec = myMacSpec.getDigestSpec();
        final GordianSymKeySpec mySymSpec = myMacSpec.getSymKeySpec();
        return switch (myType) {
            case HMAC -> supportedHMacDigestSpecs().test(myDigestSpec);
            case GMAC -> supportedGMacSymKeySpecs().test(mySymSpec);
            case CMAC -> supportedCMacSymKeySpecs().test(mySymSpec);
            case POLY1305 -> supportedPoly1305SymKeySpecs().test(mySymSpec);
            case SKEIN -> GordianDigestType.SKEIN.equals(Objects.requireNonNull(myDigestSpec).getDigestType())
                    && myDigests.supportedDigestSpecs().test(myDigestSpec);
            case BLAKE2 -> GordianDigestType.BLAKE2.equals(Objects.requireNonNull(myDigestSpec).getDigestType())
                    && myDigests.supportedDigestSpecs().test(myDigestSpec);
            case BLAKE3 -> GordianDigestType.BLAKE3.equals(Objects.requireNonNull(myDigestSpec).getDigestType())
                    && myDigests.supportedDigestSpecs().test(myDigestSpec);
            case KUPYNA -> GordianDigestType.KUPYNA.equals(Objects.requireNonNull(myDigestSpec).getDigestType())
                    && myDigests.supportedDigestSpecs().test(myDigestSpec);
            case KALYNA -> GordianSymKeyType.KALYNA.equals(Objects.requireNonNull(mySymSpec).getSymKeyType())
                    && myCiphers.validSymKeySpec(mySymSpec);
            case CBCMAC, CFBMAC -> (!GordianSymKeyType.RC5.equals(Objects.requireNonNull(mySymSpec).getSymKeyType())
                    || !GordianLength.LEN_128.equals(mySymSpec.getBlockLength()))
                    && myCiphers.validSymKeySpec(mySymSpec);
            case ZUC, VMPC, SIPHASH, GOST, KMAC -> true;
            default -> false;
        };
    }

    /**
     * Determine supported Poly1305 algorithms.
     *
     * @param pKeySpec the keySpec
     * @return true/false
     */
    protected boolean validPoly1305SymKeySpec(final GordianSymKeySpec pKeySpec) {
        return switch (pKeySpec.getSymKeyType()) {
            case KUZNYECHIK, RC5 -> false;
            default -> {
                final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
                yield myCiphers.validSymKeySpec(pKeySpec);
            }
        };
    }

    /**
     * Determine supported GMAC algorithms.
     *
     * @param pKeySpec the keySpec
     * @return true/false
     */
    protected boolean validGMacSymKeySpec(final GordianSymKeySpec pKeySpec) {
        if (GordianSymKeyType.RC5.equals(pKeySpec.getSymKeyType())) {
            return false;
        }
        final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
        return myCiphers.validSymKeySpec(pKeySpec);
    }

    /**
     * Determine supported CMAC algorithms.
     *
     * @param pKeySpec the keySpec
     * @return true/false
     */
    protected boolean validCMacSymKeySpec(final GordianSymKeySpec pKeySpec) {
        if (GordianSymKeyType.RC5.equals(pKeySpec.getSymKeyType())) {
            return false;
        }
        final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
        return myCiphers.validSymKeySpec(pKeySpec);
    }

    @Override
    public List<GordianMacSpec> listAllSupportedSpecs(final GordianLength pKeyLen) {
        return listAllPossibleSpecs(pKeyLen)
                .stream()
                .filter(supportedMacSpecs())
                .collect(Collectors.toCollection(ArrayList::new));
    }

    /**
     * List all possible macSpecs for a keyLength.
     *
     * @param pKeyLen the keyLength
     * @return the list
     */
    public List<GordianMacSpec> listAllPossibleSpecs(final GordianLength pKeyLen) {
        return GordianCoreMacSpecBuilder.listAllPossibleSpecs(pKeyLen);
    }
}