JcaSignature.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.GordianDigestType;
import net.sourceforge.joceanus.gordianknot.api.keypair.GordianEdwardsElliptic;
import net.sourceforge.joceanus.gordianknot.api.keypair.GordianKeyPair;
import net.sourceforge.joceanus.gordianknot.api.keypair.GordianKeyPairType;
import net.sourceforge.joceanus.gordianknot.api.keypair.GordianXMSSKeySpec;
import net.sourceforge.joceanus.gordianknot.api.sign.GordianSignatureSpec;
import net.sourceforge.joceanus.gordianknot.api.sign.GordianSignatureType;
import net.sourceforge.joceanus.gordianknot.impl.core.base.GordianCoreFactory;
import net.sourceforge.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
import net.sourceforge.joceanus.gordianknot.impl.core.sign.GordianCoreSignature;

import java.security.InvalidKeyException;
import java.security.Signature;
import java.security.SignatureException;

/**
 * Jca implementation of signature.
 */
public abstract class JcaSignature
        extends GordianCoreSignature {
    /**
     * The Signature error.
     */
    private static final String SIG_ERROR = "Signature error";

    /**
     * The RSA PSS MGF1 Algorithm.
     */
    private static final String RSA_PSSMGF1_ALGOBASE = "withRSAandMGF1";

    /**
     * The RSA PSS SHAKE128 Algorithm.
     */
    private static final String RSA_PSS128_ALGOBASE = "withRSAandSHAKE128";

    /**
     * The RSA PSS SHAKE256 Algorithm.
     */
    private static final String RSA_PSS256_ALGOBASE = "withRSAandSHAKE256";

    /**
     * The RSA PSS PureSHAKE Algorithm.
     */
    private static final String RSA_PSSSHAKE_ALGOBASE = "withRSA/PSS";

    /**
     * The RSA X9.31 Algorithm.
     */
    private static final String RSA_X931_ALGOBASE = "withRSA/X9.31";

    /**
     * The RSA ISO9796d2 Algorithm.
     */
    private static final String RSA_ISO9796D2_ALGOBASE = "withRSA/ISO9796-2";

    /**
     * The RSA preHash Algorithm.
     */
    private static final String RSA_PREHASH_ALGOBASE = "withRSAEncryption";

    /**
     * The ECDSA Signature.
     */
    private static final String EC_DSA_ALGOBASE = "withECDSA";

    /**
     * The ECDDSA Signature.
     */
    private static final String EC_DDSA_ALGOBASE = "withECDDSA";

    /**
     * The DSA Signature.
     */
    private static final String DSA_ALGOBASE = "withDSA";

    /**
     * The DDSA Signature.
     */
    private static final String DDSA_ALGOBASE = "withDDSA";

    /**
     * The ECNR Signature.
     */
    private static final String EC_NR_ALGOBASE = "withECNR";

    /**
     * The SM2 Signature.
     */
    private static final String EC_SM2_ALGOBASE = "WITHSM2";

    /**
     * The DSTU Signature.
     */
    private static final String DSTU_SIGN = "DSTU4145";

    /**
     * The PQC Hash prefix.
     */
    private static final String PQC_HASH_PFX = "HASH-";

    /**
     * The RSA Signer.
     */
    private Signature theSigner;

    /**
     * Constructor.
     * @param pFactory the factory
     * @param pSpec the signature Spec
     */
    JcaSignature(final GordianCoreFactory pFactory,
                 final GordianSignatureSpec pSpec) {
        super(pFactory, pSpec);
    }

    /**
     * Set the signer.
     * @param pSigner the signer.
     */
    protected void setSigner(final Signature pSigner) {
        theSigner = pSigner;
    }

    /**
     * Obtain the signer.
     * @return the signer.
     */
    protected Signature getSigner() {
        return theSigner;
    }

    @Override
    public void initForSigning(final GordianKeyPair pKeyPair) throws GordianException {
        /* Initialise detail */
        JcaKeyPair.checkKeyPair(pKeyPair);
        super.initForSigning(pKeyPair);

        /* Initialise for signing */
        try {
            /* Determine whether we should use random for signatures */
            final boolean useRandom = getSignatureSpec().getKeyPairType().useRandomForSignatures();

            /* Initialise the signing */
            if (useRandom) {
                getSigner().initSign(getKeyPair().getPrivateKey().getPrivateKey(), getRandom());
            } else {
                getSigner().initSign(getKeyPair().getPrivateKey().getPrivateKey());
            }

            /* Catch exceptions */
        } catch (InvalidKeyException e) {
            throw new GordianCryptoException(SIG_ERROR, e);
        }
    }

    @Override
    public void initForVerify(final GordianKeyPair pKeyPair) throws GordianException {
        /* Initialise detail */
        JcaKeyPair.checkKeyPair(pKeyPair);
        super.initForVerify(pKeyPair);

        /* Initialise for signing */
        try {
            /* Initialise for verification */
            getSigner().initVerify(getKeyPair().getPublicKey().getPublicKey());

            /* Catch exceptions */
        } catch (InvalidKeyException e) {
            throw new GordianCryptoException(SIG_ERROR, e);
        }
    }

    @Override
    public void update(final byte[] pBytes,
                       final int pOffset,
                       final int pLength) {
        try {
            theSigner.update(pBytes, pOffset, pLength);
        } catch (SignatureException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public void update(final byte pByte) {
        try {
            theSigner.update(pByte);
        } catch (SignatureException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public byte[] sign() throws GordianException {
        /* Check that we are in signing mode */
        checkMode(GordianSignatureMode.SIGN);

        /* Protect against exception */
        try {
            return getSigner().sign();

        } catch (SignatureException e) {
            throw new GordianCryptoException(SIG_ERROR, e);
        }
    }

    @Override
    public boolean verify(final byte[] pSignature) throws GordianException {
        /* Check that we are in verify mode */
        checkMode(GordianSignatureMode.VERIFY);

        /* Protect against exception */
        try {
            return getSigner().verify(pSignature);
        } catch (SignatureException e) {
            throw new GordianCryptoException(SIG_ERROR, e);
        }
    }

    @Override
    public void reset() {
        /* NoOp */
    }

    @Override
    protected JcaKeyPair getKeyPair() {
        return (JcaKeyPair) super.getKeyPair();
    }

    /**
     * RSA signature.
     */
    static class JcaRSASignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaRSASignature(final GordianCoreFactory pFactory,
                        final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            final String myDigest = JcaDigest.getSignAlgorithm(pSignatureSpec.getDigestSpec());
            setSigner(JcaSignatureFactory.getJavaSignature(myDigest + getSignatureBase(pSignatureSpec), false));
        }
    }

    /**
     * Obtain Signer base.
     * @param pSignatureSpec the signatureSpec
     * @return the base
     */
    static String getSignatureBase(final GordianSignatureSpec pSignatureSpec) {
        /* Handle SM2 explicitly */
        if (GordianKeyPairType.SM2.equals(pSignatureSpec.getKeyPairType())) {
            return EC_SM2_ALGOBASE;
        }

        /* Note if we are DSA */
        final boolean isDSA = GordianKeyPairType.DSA.equals(pSignatureSpec.getKeyPairType());
        final boolean isSHAKE = GordianDigestType.SHAKE.equals(pSignatureSpec.getDigestSpec().getDigestType());

        /* Switch on signature type */
        switch (pSignatureSpec.getSignatureType()) {
            case PSSMGF1:
                return RSA_PSSMGF1_ALGOBASE;
            case PSS128:
                return isSHAKE ? RSA_PSSSHAKE_ALGOBASE : RSA_PSS128_ALGOBASE;
            case PSS256:
                return isSHAKE ? RSA_PSSSHAKE_ALGOBASE : RSA_PSS256_ALGOBASE;
            case X931:
                return RSA_X931_ALGOBASE;
            case ISO9796D2:
                return RSA_ISO9796D2_ALGOBASE;
            case PREHASH:
                return RSA_PREHASH_ALGOBASE;
            case DSA:
                return isDSA
                       ? DSA_ALGOBASE
                       : EC_DSA_ALGOBASE;
            case DDSA:
                return isDSA
                       ? DDSA_ALGOBASE
                       : EC_DDSA_ALGOBASE;
            case NR:
                return EC_NR_ALGOBASE;
            default:
                return null;
        }
    }

    /**
     * DSA signer.
     */
    static class JcaDSASignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaDSASignature(final GordianCoreFactory pFactory,
                        final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            final String myDigest = JcaDigest.getSignAlgorithm(pSignatureSpec.getDigestSpec());
            setSigner(JcaSignatureFactory.getJavaSignature(myDigest + getSignatureBase(pSignatureSpec), false));
        }
    }

    /**
     * GOST signature.
     */
    static class JcaGOSTSignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaGOSTSignature(final GordianCoreFactory pFactory,
                         final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            setSigner(JcaSignatureFactory.getJavaSignature(getSignature(pSignatureSpec), false));
        }

        /**
         * Obtain Signer base.
         * @param pSignatureSpec the signatureSpec
         * @return the base
         */
        private static String getSignature(final GordianSignatureSpec pSignatureSpec) {
            /* Handle DSTU explicitly */
            if (GordianKeyPairType.DSTU4145.equals(pSignatureSpec.getKeyPairType())) {
                return DSTU_SIGN;
            }

            /* Obtain the digest length */
            final GordianLength myLength = pSignatureSpec.getDigestSpec().getDigestLength();

            /* Build the algorithm */
            final StringBuilder myBuilder = new StringBuilder();
            myBuilder.append("GOST3411-2012-")
                    .append(myLength.getLength())
                    .append("withECGOST3410-2012-")
                    .append(myLength.getLength());
            return myBuilder.toString();
        }
    }

    /**
     * SLHDSA signature.
     */
    static class JcaSLHDSASignature
            extends JcaSignature {
        /**
         * Base name.
         */
        private static final String BASE_NAME = "SLH-DSA";

        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaSLHDSASignature(final GordianCoreFactory pFactory,
                           final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);
        }

        @Override
        public void initForSigning(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, false));

            /* pass on call */
            super.initForSigning(pKeyPair);
        }

        @Override
        public void initForVerify(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, false));

            /* pass on call */
            super.initForVerify(pKeyPair);
        }

        /**
         * Obtain algorithmName for keyPair.
         * @param pKeyPair the keyPair
         * @return the name
         */
        private static String getAlgorithmForKeyPair(final GordianKeyPair pKeyPair) {
            /* Build the algorithm */
            final boolean isHash = pKeyPair.getKeyPairSpec().getSLHDSAKeySpec().isHash();
            return isHash ? PQC_HASH_PFX + BASE_NAME : BASE_NAME;
        }
    }

    /**
     * MLDSA signature.
     */
    static class JcaMLDSASignature
            extends JcaSignature {
        /**
         * Base name.
         */
        private static final String BASE_NAME = "ML-DSA";

        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaMLDSASignature(final GordianCoreFactory pFactory,
                          final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);
        }

        @Override
        public void initForSigning(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, false));

            /* pass on call */
            super.initForSigning(pKeyPair);
        }

        @Override
        public void initForVerify(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, false));

            /* pass on call */
            super.initForVerify(pKeyPair);
        }

        /**
         * Obtain algorithmName for keyPair.
         * @param pKeyPair the keyPair
         * @return the name
         */
        private static String getAlgorithmForKeyPair(final GordianKeyPair pKeyPair) {
            /* Build the algorithm */
            final boolean isHash = pKeyPair.getKeyPairSpec().getMLDSAKeySpec().isHash();
            return isHash ? PQC_HASH_PFX + BASE_NAME : BASE_NAME;
        }
    }

    /**
     * Falcon signature.
     */
    static class JcaFalconSignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaFalconSignature(final GordianCoreFactory pFactory,
                           final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            setSigner(JcaSignatureFactory.getJavaSignature("FALCON", true));
        }
    }

    /**
     * Picnic signature.
     */
    static class JcaPicnicSignature
            extends JcaSignature {
        /**
         * SIgnature base.
         */
        private static final String BASE_NAME = "PICNIC";

        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaPicnicSignature(final GordianCoreFactory pFactory,
                           final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            final String myName = determineSignatureName(pSignatureSpec);
            setSigner(JcaSignatureFactory.getJavaSignature(myName, true));
        }

        /**
         * Determine signatureName.
         * @param pSignatureSpec the signatureSpec
         * @return the algorithm name
         */
        private static String determineSignatureName(final GordianSignatureSpec pSignatureSpec) {
            /* If we do not have a digest */
            if (pSignatureSpec.getSignatureSpec() == null) {
                return BASE_NAME;
            }

            /* Switch on digest Type */
            switch (pSignatureSpec.getDigestSpec().getDigestType()) {
                case SHA2:
                    return "SHA512With" + BASE_NAME;
                case SHA3:
                    return "SHA3-512With" + BASE_NAME;
                case SHAKE:
                    return "SHAKE256With" + BASE_NAME;
                default:
                    throw new IllegalArgumentException("Bad SignatureSpec");
            }
        }

    }

    /**
     * Rainbow signature.
     */
    static class JcaRainbowSignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaRainbowSignature(final GordianCoreFactory pFactory,
                            final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            setSigner(JcaSignatureFactory.getJavaSignature("RAINBOW", true));
        }
    }

    /**
     * XMSS signature.
     */
    static class JcaXMSSSignature
            extends JcaSignature {
        /**
         * Is this a preHash signature?
         */
        private final boolean preHash;

        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaXMSSSignature(final GordianCoreFactory pFactory,
                         final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Determine preHash */
            preHash = GordianSignatureType.PREHASH.equals(pSignatureSpec.getSignatureType());
        }

        @Override
        public void initForSigning(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, true));

            /* pass on call */
            super.initForSigning(pKeyPair);
        }

        @Override
        public void initForVerify(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, true));

            /* pass on call */
            super.initForVerify(pKeyPair);
        }

        /**
         * Obtain algorithmName for keyPair.
         * @param pKeyPair the keyPair
         * @return the name
         * @throws GordianException on error
         */
        private String getAlgorithmForKeyPair(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            final GordianXMSSKeySpec myXMSSKeySpec = pKeyPair.getKeyPairSpec().getXMSSKeySpec();
            final GordianDigestSpec myDigestSpec = myXMSSKeySpec.getDigestType().getDigestSpec();
            final String myDigest = JcaDigest.getAlgorithm(myDigestSpec);

            /* Create builder */
            final StringBuilder myBuilder = new StringBuilder();
            myBuilder.append(myXMSSKeySpec.getKeyType().name())
                    .append('-')
                    .append(myDigest);
            if (preHash) {
                myBuilder.insert(0, "with")
                        .insert(0, myDigest);
            }

            /* Build the algorithm */
            return myBuilder.toString();
        }
    }

    /**
     * EdDSA signature.
     */
    static class JcaEdDSASignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaEdDSASignature(final GordianCoreFactory pFactory,
                          final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);
        }

        @Override
        public void initForSigning(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, false));

            /* pass on call */
            super.initForSigning(pKeyPair);
        }

        @Override
        public void initForVerify(final GordianKeyPair pKeyPair) throws GordianException {
            /* Determine the required signer */
            JcaKeyPair.checkKeyPair(pKeyPair);
            final String mySignName = getAlgorithmForKeyPair(pKeyPair);
            setSigner(JcaSignatureFactory.getJavaSignature(mySignName, false));

            /* pass on call */
            super.initForVerify(pKeyPair);
        }

        /**
         * Obtain algorithmName for keyPair.
         * @param pKeyPair the keyPair
         * @return the name
         */
        private static String getAlgorithmForKeyPair(final GordianKeyPair pKeyPair) {
            /* Determine the required signer */
            final GordianEdwardsElliptic myEdwards = pKeyPair.getKeyPairSpec().getEdwardsElliptic();
            final boolean is25519 = myEdwards.is25519();

            /* Build the algorithm */
            return is25519 ? "Ed25519" : "Ed448";
        }
    }

    /**
     * LMS signature.
     */
    static class JcaLMSSignature
            extends JcaSignature {
        /**
         * Constructor.
         * @param pFactory the factory
         * @param pSignatureSpec the signatureSpec
         * @throws GordianException on error
         */
        JcaLMSSignature(final GordianCoreFactory pFactory,
                        final GordianSignatureSpec pSignatureSpec) throws GordianException {
            /* Initialise class */
            super(pFactory, pSignatureSpec);

            /* Create the signature class */
            setSigner(JcaSignatureFactory.getJavaSignature("LMS", true));
        }
    }
}