BouncyMacFactory.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.bc;

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.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
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.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.mac.GordianCoreMacFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianBlake3Digest;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianBlake2Mac;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianBlake2XMac;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianBlake3Mac;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianKMACWrapper;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianSkeinMac;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianSkeinXMac;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianZuc128Mac;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.macs.GordianZuc256Mac;
import org.bouncycastle.crypto.CipherKeyGenerator;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.Xof;
import org.bouncycastle.crypto.generators.Poly1305KeyGenerator;
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
import org.bouncycastle.crypto.macs.CMac;
import org.bouncycastle.crypto.macs.DSTU7564Mac;
import org.bouncycastle.crypto.macs.GMac;
import org.bouncycastle.crypto.macs.GOST28147Mac;
import org.bouncycastle.crypto.macs.Poly1305;
import org.bouncycastle.crypto.macs.SipHash;
import org.bouncycastle.crypto.macs.SipHash128;
import org.bouncycastle.crypto.macs.VMPCMac;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.patch.macs.GordianDSTU7624Mac;
import org.bouncycastle.crypto.patch.macs.GordianKGMac;
import org.bouncycastle.crypto.patch.modes.GordianKGCMBlockCipher;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Factory for BouncyCastle Macs.
 */
public class BouncyMacFactory
        extends GordianCoreMacFactory {
    /**
     * KeyPairGenerator Cache.
     */
    private final Map<GordianKeySpec, BouncyKeyGenerator<? extends GordianKeySpec>> theCache;

    /**
     * Constructor.
     *
     * @param pFactory the factory
     */
    BouncyMacFactory(final GordianBaseFactory pFactory) {
        /* Initialise underlying class */
        super(pFactory);

        /* Create the cache */
        theCache = new HashMap<>();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends GordianKeySpec> BouncyKeyGenerator<T> getKeyGenerator(final T pMacSpec) throws GordianException {
        /* Look up in the cache */
        BouncyKeyGenerator<T> myGenerator = (BouncyKeyGenerator<T>) theCache.get(pMacSpec);
        if (myGenerator == null) {
            /* Check validity of MacSpec */
            checkMacSpec(pMacSpec);

            /* Create the new generator */
            final CipherKeyGenerator myBCGenerator = getBCKeyGenerator((GordianMacSpec) pMacSpec);
            myGenerator = new BouncyKeyGenerator<>(getFactory(), pMacSpec, myBCGenerator);

            /* Add to cache */
            theCache.put(pMacSpec, myGenerator);
        }
        return myGenerator;
    }

    /**
     * Create the BouncyCastle KeyGenerator.
     *
     * @param pKeyType the keyType
     * @return the KeyGenerator
     */
    private static CipherKeyGenerator getBCKeyGenerator(final GordianMacSpec pKeyType) {
        return GordianMacType.POLY1305.equals(pKeyType.getMacType())
                ? new Poly1305KeyGenerator()
                : new CipherKeyGenerator();
    }

    @Override
    public BouncyMac createMac(final GordianMacSpec pMacSpec) throws GordianException {
        /* Check validity of MacSpec */
        checkMacSpec(pMacSpec);

        /* Create Mac */
        final Mac myBCMac = getBCMac(pMacSpec);
        return myBCMac instanceof Xof myXof
                ? new BouncyMacXof(getFactory(), pMacSpec, myXof)
                : new BouncyMac(getFactory(), pMacSpec, myBCMac);
    }

    /**
     * Create the BouncyCastle MAC.
     *
     * @param pMacSpec the MacSpec
     * @return the MAC
     * @throws GordianException on error
     */
    private Mac getBCMac(final GordianMacSpec pMacSpec) throws GordianException {
        switch (pMacSpec.getMacType()) {
            case HMAC:
                return getBCHMac(pMacSpec);
            case GMAC:
                return getBCGMac(Objects.requireNonNull(pMacSpec.getSymKeySpec()));
            case CMAC:
                return getBCCMac(Objects.requireNonNull(pMacSpec.getSymKeySpec()));
            case POLY1305:
                return getBCPoly1305Mac(pMacSpec.getSymKeySpec());
            case SKEIN:
                return getBCSkeinMac(Objects.requireNonNull(pMacSpec.getDigestSpec()));
            case BLAKE2:
                return getBCBlake2Mac(Objects.requireNonNull(pMacSpec.getDigestSpec()));
            case BLAKE3:
                return getBCBlake3Mac(Objects.requireNonNull(pMacSpec.getDigestSpec()));
            case KMAC:
                return getBCKMAC(Objects.requireNonNull(pMacSpec.getDigestSpec()));
            case KALYNA:
                return getBCKalynaMac(Objects.requireNonNull(pMacSpec.getSymKeySpec()));
            case KUPYNA:
                return getBCKupynaMac(Objects.requireNonNull(pMacSpec.getDigestSpec()));
            case VMPC:
                return getBCVMPCMac();
            case CBCMAC:
                return getBCCBCMac(Objects.requireNonNull(pMacSpec.getSymKeySpec()));
            case CFBMAC:
                return getBCCFBMac(Objects.requireNonNull(pMacSpec.getSymKeySpec()));
            case SIPHASH:
                return getBCSipHash(Objects.requireNonNull(pMacSpec.getSipHashSpec()));
            case GOST:
                return getBCGOSTMac();
            case ZUC:
                return getBCZucMac(pMacSpec);
            default:
                throw new GordianDataException(GordianBaseData.getInvalidText(pMacSpec));
        }
    }

    /**
     * Create the BouncyCastle HMAC.
     *
     * @param pMacSpec the macSpec
     * @return the MAC
     * @throws GordianException on error
     */
    private Mac getBCHMac(final GordianMacSpec pMacSpec) throws GordianException {
        return new BouncyHMac(getFactory().getDigestFactory(), pMacSpec);
    }

    /**
     * Create the BouncyCastle GMac.
     *
     * @param pSymKeySpec the SymKeySpec
     * @return the MAC
     * @throws GordianException on error
     */
    private static Mac getBCGMac(final GordianSymKeySpec pSymKeySpec) throws GordianException {
        return GordianSymKeyType.KALYNA.equals(pSymKeySpec.getSymKeyType())
                ? new GordianKGMac(new GordianKGCMBlockCipher(BouncyCipherFactory.getBCSymEngine(pSymKeySpec)))
                : new GMac(GCMBlockCipher.newInstance(BouncyCipherFactory.getBCSymEngine(pSymKeySpec)));
    }

    /**
     * Create the BouncyCastle CMac.
     *
     * @param pSymKeySpec the SymKeySpec
     * @return the MAC
     * @throws GordianException on error
     */
    private static Mac getBCCMac(final GordianSymKeySpec pSymKeySpec) throws GordianException {
        return new CMac(BouncyCipherFactory.getBCSymEngine(pSymKeySpec));
    }

    /**
     * Create the BouncyCastle Poly1305Mac.
     *
     * @param pSymKeySpec the SymKeySpec
     * @return the MAC
     * @throws GordianException on error
     */
    private static Mac getBCPoly1305Mac(final GordianSymKeySpec pSymKeySpec) throws GordianException {
        return pSymKeySpec == null
                ? new Poly1305()
                : new Poly1305(BouncyCipherFactory.getBCSymEngine(pSymKeySpec));
    }

    /**
     * Create the BouncyCastle SkeinMac.
     *
     * @param pSpec the digestSpec
     * @return the MAC
     */
    private static Mac getBCSkeinMac(final GordianDigestSpec pSpec) {
        return pSpec.isXofMode()
                ? new GordianSkeinXMac(pSpec.getDigestState().getLength().getLength())
                : new GordianSkeinMac(pSpec.getDigestState().getLength().getLength(), pSpec.getDigestLength().getLength());
    }

    /**
     * Create the BouncyCastle KalynaMac.
     *
     * @param pSymKeySpec the SymKeySpec
     * @return the MAC
     */
    private static Mac getBCKalynaMac(final GordianSymKeySpec pSymKeySpec) {
        final GordianLength myLen = pSymKeySpec.getBlockLength();
        return new GordianDSTU7624Mac(myLen.getLength(), myLen.getLength());
    }

    /**
     * Create the BouncyCastle kupynaMac.
     *
     * @param pSpec the digestSpec
     * @return the MAC
     */
    private static Mac getBCKupynaMac(final GordianDigestSpec pSpec) {
        return new DSTU7564Mac(pSpec.getDigestLength().getLength());
    }

    /**
     * Create the BouncyCastle blakeMac.
     *
     * @param pSpec the digestSpec
     * @return the MAC
     */
    private static Mac getBCBlake2Mac(final GordianDigestSpec pSpec) {
        return pSpec.isXofMode()
                ? new GordianBlake2XMac(BouncyDigestFactory.getBlake2Xof(pSpec))
                : new GordianBlake2Mac(BouncyDigestFactory.getBlake2Digest(pSpec));
    }

    /**
     * Create the BouncyCastle blake3Mac.
     *
     * @param pSpec the digestSpec
     * @return the MAC
     */
    private static Mac getBCBlake3Mac(final GordianDigestSpec pSpec) {
        final GordianBlake3Digest myDigest = new GordianBlake3Digest(pSpec.getDigestLength().getByteLength());
        return new GordianBlake3Mac(myDigest);
    }

    /**
     * Create the BouncyCastle KMAC.
     *
     * @param pSpec the digestSpec
     * @return the MAC
     */
    private static Mac getBCKMAC(final GordianDigestSpec pSpec) {
        return new GordianKMACWrapper(pSpec.getDigestState().getLength().getLength());
    }

    /**
     * Create the BouncyCastle VMPCMac.
     *
     * @return the MAC
     */
    private static Mac getBCVMPCMac() {
        return new VMPCMac();
    }

    /**
     * Create the BouncyCastle CBCMac.
     *
     * @param pSymKeySpec the SymKeySpec
     * @return the MAC
     * @throws GordianException on error
     */
    private static Mac getBCCBCMac(final GordianSymKeySpec pSymKeySpec) throws GordianException {
        return new CBCBlockCipherMac(BouncyCipherFactory.getBCSymEngine(pSymKeySpec));
    }

    /**
     * Create the BouncyCastle CFBMac.
     *
     * @param pSymKeySpec the SymKeySpec
     * @return the MAC
     * @throws GordianException on error
     */
    private static Mac getBCCFBMac(final GordianSymKeySpec pSymKeySpec) throws GordianException {
        return new CFBBlockCipherMac(BouncyCipherFactory.getBCSymEngine(pSymKeySpec));
    }

    /**
     * Create the BouncyCastle SipHash.
     *
     * @param pSpec the SipHashSpec
     * @return the MAC
     */
    private static Mac getBCSipHash(final GordianSipHashSpec pSpec) {
        return pSpec.isLong()
                ? new SipHash128(pSpec.getCompression(), pSpec.getFinalisation())
                : new SipHash(pSpec.getCompression(), pSpec.getFinalisation());
    }

    /**
     * Create the BouncyCastle GOSTMac.
     *
     * @return the MAC
     */
    private static Mac getBCGOSTMac() {
        return new GOST28147Mac();
    }

    /**
     * Create the BouncyCastle ZucMac.
     *
     * @param pMacSpec the macSpec
     * @return the MAC
     */
    private static Mac getBCZucMac(final GordianMacSpec pMacSpec) {
        return GordianLength.LEN_128 == pMacSpec.getKeyLength()
                ? new GordianZuc128Mac()
                : new GordianZuc256Mac(pMacSpec.getMacLength().getLength());
    }
}