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.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.GordianDigestSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSubSpec.GordianDigestState;
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 io.github.tonywasher.joceanus.gordianknot.api.mac.GordianSipHashSpec;
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 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 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 GordianDigestSpec mySpec = pMacSpec.getDigestSpec();
        final GordianSymKeySpec mySymSpec = pMacSpec.getSymKeySpec();
        switch (myType) {
            case HMAC:
                return supportedHMacDigestSpecs().test(mySpec);
            case GMAC:
                return supportedGMacSymKeySpecs().test(mySymSpec);
            case CMAC:
                return supportedCMacSymKeySpecs().test(mySymSpec);
            case POLY1305:
                return supportedPoly1305SymKeySpecs().test(mySymSpec);
            case SKEIN:
                return GordianDigestType.SKEIN.equals(Objects.requireNonNull(mySpec).getDigestType())
                        && myDigests.supportedDigestSpecs().test(mySpec);
            case BLAKE2:
                return GordianDigestType.BLAKE2.equals(Objects.requireNonNull(mySpec).getDigestType())
                        && myDigests.supportedDigestSpecs().test(mySpec);
            case BLAKE3:
                return GordianDigestType.BLAKE3.equals(Objects.requireNonNull(mySpec).getDigestType())
                        && myDigests.supportedDigestSpecs().test(mySpec);
            case KUPYNA:
                return GordianDigestType.KUPYNA.equals(Objects.requireNonNull(mySpec).getDigestType())
                        && myDigests.supportedDigestSpecs().test(mySpec);
            case KALYNA:
                return GordianSymKeyType.KALYNA.equals(Objects.requireNonNull(mySymSpec).getSymKeyType())
                        && myCiphers.validSymKeySpec(mySymSpec);
            case CBCMAC:
            case CFBMAC:
                return (!GordianSymKeyType.RC5.equals(Objects.requireNonNull(mySymSpec).getSymKeyType())
                        || !GordianLength.LEN_128.equals(mySymSpec.getBlockLength()))
                        && myCiphers.validSymKeySpec(mySymSpec);
            case ZUC:
            case VMPC:
            case SIPHASH:
            case GOST:
            case KMAC:
                return true;
            default:
                return false;
        }
    }

    /**
     * Determine supported Poly1305 algorithms.
     *
     * @param pKeySpec the keySpec
     * @return true/false
     */
    protected boolean validPoly1305SymKeySpec(final GordianSymKeySpec pKeySpec) {
        switch (pKeySpec.getSymKeyType()) {
            case KUZNYECHIK:
            case RC5:
                return false;
            default:
                final GordianCoreCipherFactory myCiphers = (GordianCoreCipherFactory) theFactory.getCipherFactory();
                return 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) {
        /* Create the array list */
        final List<GordianMacSpec> myList = new ArrayList<>();

        /* For each digestSpec */
        for (final GordianDigestSpec mySpec : theFactory.getDigestFactory().listAllPossibleSpecs()) {
            /* Add the hMacSpec */
            myList.add(GordianMacSpecBuilder.hMac(mySpec, pKeyLen));

            /* Add KMAC for digestType of SHAKE */
            if (GordianDigestType.SHAKE == mySpec.getDigestType()) {
                myList.add(GordianMacSpecBuilder.kMac(pKeyLen, mySpec));
            }
        }

        /* For each SymKey */
        for (final GordianSymKeySpec mySymKeySpec : theFactory.getCipherFactory().listAllSymKeySpecs(pKeyLen)) {
            /* Add gMac/cMac/cfbMac/cbcMac */
            myList.add(GordianMacSpecBuilder.gMac(mySymKeySpec));
            myList.add(GordianMacSpecBuilder.cMac(mySymKeySpec));
            myList.add(GordianMacSpecBuilder.cbcMac(mySymKeySpec));
            myList.add(GordianMacSpecBuilder.cfbMac(mySymKeySpec));

            /* Add kalynaMac for keyType of Kalyna */
            if (GordianSymKeyType.KALYNA == mySymKeySpec.getSymKeyType()) {
                myList.add(GordianMacSpecBuilder.kalynaMac(mySymKeySpec));
            }
        }

        /* Only add poly1305 for 256bit keyLengths */
        if (GordianLength.LEN_256 == pKeyLen) {
            /* For each SymKey at 128 bits*/
            for (final GordianSymKeySpec mySymKeySpec : theFactory.getCipherFactory().listAllSymKeySpecs(GordianLength.LEN_128)) {
                myList.add(GordianMacSpecBuilder.poly1305Mac(mySymKeySpec));
            }

            /* Add raw poly1305 */
            myList.add(GordianMacSpecBuilder.poly1305Mac());

            /* Add Blake3 macs */
            for (final GordianLength myLength : GordianDigestType.BLAKE3.getSupportedLengths()) {
                myList.add(GordianMacSpecBuilder.blake3Mac(myLength));
            }
        }

        /* Add kupynaMac */
        for (final GordianLength myLength : GordianDigestType.KUPYNA.getSupportedLengths()) {
            myList.add(GordianMacSpecBuilder.kupynaMac(pKeyLen, myLength));
        }

        /* Loop through states */
        for (final GordianDigestState myState : GordianDigestState.values()) {
            /* Add SkeinMacs */
            for (final GordianLength myLength : GordianDigestType.SKEIN.getSupportedLengths()) {
                final GordianMacSpec mySkeinSpec = GordianMacSpecBuilder.skeinMac(pKeyLen, myState, myLength);
                if (mySkeinSpec.isValid()) {
                    myList.add(mySkeinSpec);
                }
            }
            final GordianMacSpec mySkeinSpec = GordianMacSpecBuilder.skeinXMac(pKeyLen, myState);
            if (mySkeinSpec.isValid()) {
                myList.add(mySkeinSpec);
            }

            /* Add blake2Macs */
            for (final GordianLength myLength : GordianDigestType.BLAKE2.getSupportedLengths()) {
                final GordianMacSpec myBlakeSpec = GordianMacSpecBuilder.blake2Mac(pKeyLen, GordianDigestSpecBuilder.blake2(myState, myLength));
                if (myBlakeSpec.isValid()) {
                    myList.add(myBlakeSpec);
                }
            }

            final GordianMacSpec myBlakeSpec = GordianMacSpecBuilder.blake2XMac(pKeyLen, myState);
            if (myBlakeSpec.isValid()) {
                myList.add(myBlakeSpec);
            }
        }

        /* Add vmpcMac */
        myList.add(GordianMacSpecBuilder.vmpcMac(pKeyLen));

        /* Add sipHash for 128bit keys */
        if (GordianLength.LEN_128 == pKeyLen) {
            for (final GordianSipHashSpec mySpec : GordianSipHashSpec.values()) {
                myList.add(GordianMacSpecBuilder.sipHash(mySpec));
            }
        }

        /* Add gostHash for 256bit keys */
        if (GordianLength.LEN_256 == pKeyLen) {
            myList.add(GordianMacSpecBuilder.gostMac());
        }

        /* Add zucMac */
        if (GordianLength.LEN_128 == pKeyLen) {
            myList.add(GordianMacSpecBuilder.zucMac(pKeyLen, GordianLength.LEN_32));
        } else if (GordianLength.LEN_256 == pKeyLen) {
            myList.add(GordianMacSpecBuilder.zucMac(pKeyLen, GordianLength.LEN_32));
            myList.add(GordianMacSpecBuilder.zucMac(pKeyLen, GordianLength.LEN_64));
            myList.add(GordianMacSpecBuilder.zucMac(pKeyLen, GordianLength.LEN_128));
        }

        /* Return the list */
        return myList;
    }
}