JcaDigest.java

/*******************************************************************************
 * GordianKnot: Security Suite
 * Copyright 2012,2025 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 net.sourceforge.joceanus.gordianknot.impl.jca;

import net.sourceforge.joceanus.gordianknot.api.base.GordianException;
import net.sourceforge.joceanus.gordianknot.api.base.GordianLength;
import net.sourceforge.joceanus.gordianknot.api.digest.GordianDigestSpec;
import net.sourceforge.joceanus.gordianknot.api.digest.GordianDigestSubSpec.GordianDigestState;
import net.sourceforge.joceanus.gordianknot.api.digest.GordianDigestType;
import net.sourceforge.joceanus.gordianknot.impl.core.digest.GordianCoreDigest;
import net.sourceforge.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
import net.sourceforge.joceanus.gordianknot.impl.core.exc.GordianDataException;

import java.security.DigestException;
import java.security.MessageDigest;

/**
 * Jca Digest.
 */
public final class JcaDigest
        extends GordianCoreDigest {
    /**
     * The MessageDigest.
     */
    private final MessageDigest theDigest;

    /**
     * Constructor.
     * @param pDigestSpec the digestSpec
     * @param pDigest the digest
     */
    JcaDigest(final GordianDigestSpec pDigestSpec,
              final MessageDigest pDigest) {
        super(pDigestSpec);
        theDigest = pDigest;
    }

    @Override
    public int getDigestSize() {
        return theDigest.getDigestLength();
    }

    @Override
    public void doUpdate(final byte[] pBytes,
                         final int pOffset,
                         final int pLength) {
        theDigest.update(pBytes, pOffset, pLength);
    }

    @Override
    public void update(final byte pByte) {
        theDigest.update(pByte);
    }

    @Override
    public void reset() {
        theDigest.reset();
    }

    @Override
    public byte[] finish() {
        return theDigest.digest();
    }

    @Override
    public byte[] finish(final byte[] pBytes) {
        return theDigest.digest(pBytes);
    }

    @Override
    public int doFinish(final byte[] pBuffer,
                        final int pOffset) throws GordianException {
        try {
            return theDigest.digest(pBuffer, pOffset, getDigestSize());
        } catch (DigestException e) {
            throw new GordianCryptoException("Failed to calculate Digest", e);
        }
    }

    /**
     * Obtain the sha2 signature algorithm.
     * @param pDigestSpec the digestSpec
     * @return the algorithm
     * @throws GordianException on error
     */
    static String getSignAlgorithm(final GordianDigestSpec pDigestSpec) throws GordianException {
        /* If this is a sha2 extended algorithm */
        if (GordianDigestType.SHA2.equals(pDigestSpec.getDigestType())
                && pDigestSpec.isSha2Hybrid()) {
            return GordianLength.LEN_256.equals(pDigestSpec.getDigestLength())
                   ? "SHA512(256)"
                   : "SHA512(224)";
        }

        /* Access digest details */
        return getAlgorithm(pDigestSpec);
    }

    /**
     * Create the sha2 hMac algorithm.
     * @param pDigestSpec the digestSpec
     * @return the algorithm
     * @throws GordianException on error
     */
    static String getHMacAlgorithm(final GordianDigestSpec pDigestSpec) throws GordianException {
        /* If this is a sha2 extended algorithm */
        if (GordianDigestType.SHA2.equals(pDigestSpec.getDigestType())
                && pDigestSpec.isSha2Hybrid()) {
            return GordianLength.LEN_256.equals(pDigestSpec.getDigestLength())
                   ? "SHA512/256"
                   : "SHA512/224";
        }

        /* Access digest details */
        return getAlgorithm(pDigestSpec);
    }

    /**
     * Obtain the full algorithm name.
     * @param pDigestSpec the digestSpec
     * @return the name
     * @throws GordianException on error
     */
    static String getFullAlgorithm(final GordianDigestSpec pDigestSpec) throws GordianException {
        /* Access standard name */
        final String myAlgorithm = getAlgorithm(pDigestSpec);

        switch (pDigestSpec.getDigestType()) {
            case SHAKE:
            case BLAKE3:
                return myAlgorithm + "-" + pDigestSpec.getDigestLength();
            default:
                return myAlgorithm;
        }
    }

    /**
     * Obtain the algorithm name.
     * @param pDigestSpec the digestSpec
     * @return the name
     * @throws GordianException on error
     */
    static String getAlgorithm(final GordianDigestSpec pDigestSpec) throws GordianException {
        /* Access digest details */
        final GordianDigestType myType = pDigestSpec.getDigestType();
        final GordianLength myLen = pDigestSpec.getDigestLength();

        /* Switch on digestType */
        switch (myType) {
            case SHA2:
                return getSHA2Algorithm(pDigestSpec);
            case STREEBOG:
                return getStreebogAlgorithm(myLen);
            case RIPEMD:
                return getRIPEMDAlgorithm(myLen);
            case SKEIN:
                return getSkeinAlgorithm(pDigestSpec);
            case SHA3:
                return getSHA3Algorithm(myLen);
            case BLAKE2:
                return getBlake2Algorithm(pDigestSpec);
            case KUPYNA:
                return getKupynaAlgorithm(myLen);
            case SHAKE:
                return getSHAKEAlgorithm(pDigestSpec.getDigestState());
            case HARAKA:
                return pDigestSpec.toString();
            case GOST:
                return "GOST3411";
            case WHIRLPOOL:
            case TIGER:
            case SHA1:
            case MD5:
            case MD4:
            case MD2:
            case SM3:
                return myType.name();
            case BLAKE3:
                return pDigestSpec.toString();
            default:
                throw new GordianDataException("Invalid DigestSpec :- " + pDigestSpec);
        }
    }

    /**
     * Determine the RIPEMD algorithm.
     * @param pLength the digest length
     * @return the name
     */
    private static String getRIPEMDAlgorithm(final GordianLength pLength) {
        switch (pLength) {
            case LEN_128:
                return "RIPEMD128";
            case LEN_160:
                return "RIPEMD160";
            case LEN_256:
                return "RIPEMD256";
            case LEN_320:
            default:
                return "RIPEMD320";
        }
    }

    /**
     * Determine the Blake2 algorithm.
     * @param pSpec the digestSpec
     * @return the name
     */
    private static String getBlake2Algorithm(final GordianDigestSpec pSpec) {
        return pSpec.toString();
    }

    /**
     * Determine the Kupyna algorithm.
     * @param pLength the digest length
     * @return the name
     */
    private static String getKupynaAlgorithm(final GordianLength pLength) {
        switch (pLength) {
            case LEN_256:
                return "DSTU7564-256";
            case LEN_384:
                return "DSTU7564-384";
            case LEN_512:
            default:
                return "DSTU7564-512";
        }
    }

    /**
     * Determine the SHA2 algorithm.
     * @param pSpec the digestSpec
     * @return the name
     */
    private static String getSHA2Algorithm(final GordianDigestSpec pSpec) {
        /* Access lengths */
        final GordianDigestState myState = pSpec.getDigestState();
        final GordianLength myLen = pSpec.getDigestLength();

        /* Switch on length */
        switch (myLen) {
            case LEN_224:
                return GordianDigestState.STATE256.equals(myState)
                       ? "SHA224"
                       : "SHA-512/224";
            case LEN_256:
                return GordianDigestState.STATE256.equals(myState)
                       ? "SHA256"
                       : "SHA-512/256";
            case LEN_384:
                return "SHA384";
            case LEN_512:
            default:
                return "SHA512";
        }
    }

    /**
     * Determine the SHA3 algorithm.
     * @param pLength the digest length
     * @return the name
     */
    private static String getSHA3Algorithm(final GordianLength pLength) {
        switch (pLength) {
            case LEN_224:
                return "SHA3-224";
            case LEN_256:
                return "SHA3-256";
            case LEN_384:
                return "SHA3-384";
            case LEN_512:
            default:
                return "SHA3-512";
        }
    }

    /**
     * Determine the SHAKE algorithm.
     * @param pState the stateLength
     * @return the name
     */
    private static String getSHAKEAlgorithm(final GordianDigestState pState) {
        /* Determine SHAKE digest */
        return "SHAKE" + pState;
    }

    /**
     * Determine the Skein algorithm.
     * @param pSpec the digestSpec
     * @return the name
     */
    private static String getSkeinAlgorithm(final GordianDigestSpec pSpec) {
        return "Skein-"
                + pSpec.getDigestState()
                + '-'
                + pSpec.getDigestLength();
    }

    /**
     * Determine the Streebog algorithm.
     * @param pLength the digest length
     * @return the name
     */
    private static String getStreebogAlgorithm(final GordianLength pLength) {
        return "GOST3411-2012-" + pLength;
    }

    /**
     * Determine whether the algorithm is supported.
     * @param pDigestType the digest type
     * @return true/false
     */
    static boolean isHMacSupported(final GordianDigestType pDigestType) {
        switch (pDigestType) {
            case BLAKE2:
            case BLAKE3:
            case KUPYNA:
            case SHAKE:
                return false;
            default:
                return true;
        }
    }
}