JcaKeyPairGenerator.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.jca.keypair;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianCoreKeyPairGenerator;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianKeyPairValidity;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.base.JcaProvider;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair.JcaPrivateKey;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair.JcaPublicKey;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair.JcaStateAwareKeyPair;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.keypair.JcaKeyPair.JcaStateAwarePrivateKey;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * Jca KeyPair generator.
 */
public abstract class JcaKeyPairGenerator
        extends GordianCoreKeyPairGenerator {
    /**
     * Parse error.
     */
    private static final String PARSE_ERROR = "Failed to parse encoding";

    /**
     * Factory.
     */
    private KeyFactory theFactory;

    /**
     * Generator.
     */
    private KeyPairGenerator theGenerator;

    /**
     * Constructor.
     *
     * @param pFactory the Security Factory
     * @param pKeySpec the keySpec
     */
    JcaKeyPairGenerator(final GordianBaseFactory pFactory,
                        final GordianKeyPairSpec pKeySpec) {
        super(pFactory, pKeySpec);
    }

    /**
     * Obtain the keyPair generator.
     *
     * @return the keyPairGenerator
     */
    KeyPairGenerator getGenerator() {
        return theGenerator;
    }

    /**
     * Set the keyPairGenerator.
     *
     * @param pGenerator the keyPairGenerator
     */
    void setKeyPairGenerator(final KeyPairGenerator pGenerator) {
        theGenerator = pGenerator;
    }

    /**
     * Obtain the key factory.
     *
     * @return the keyFactory
     */
    KeyFactory getKeyFactory() {
        return theFactory;
    }

    /**
     * Set the key factory.
     *
     * @param pFactory the keyFactory
     */
    void setKeyFactory(final KeyFactory pFactory) {
        theFactory = pFactory;
    }

    @Override
    public PKCS8EncodedKeySpec getPKCS8Encoding(final GordianKeyPair pKeyPair) throws GordianException {
        /* Check the keyPair */
        JcaKeyPair.checkKeyPair(pKeyPair);

        /* derive the encoding */
        final JcaPrivateKey myPrivateKey = (JcaPrivateKey) getPrivateKey(pKeyPair);
        return new PKCS8EncodedKeySpec(myPrivateKey.getPrivateKey().getEncoded());
    }

    @Override
    public X509EncodedKeySpec getX509Encoding(final GordianKeyPair pKeyPair) throws GordianException {
        /* Check the keyPair */
        JcaKeyPair.checkKeyPair(pKeyPair);

        /* derive the encoding */
        final JcaPublicKey myPublicKey = (JcaPublicKey) getPublicKey(pKeyPair);
        return new X509EncodedKeySpec(myPublicKey.getPublicKey().getEncoded());
    }

    @Override
    public JcaKeyPair deriveKeyPair(final X509EncodedKeySpec pPublicKey,
                                    final PKCS8EncodedKeySpec pPrivateKey) throws GordianException {
        /* Protect against exceptions */
        try {
            /* Check the keySpec */
            checkKeySpec(pPrivateKey);

            /* derive the keyPair */
            final JcaPublicKey myPublic = derivePublicKey(pPublicKey);
            final JcaPrivateKey myPrivate = createPrivate(theFactory.generatePrivate(pPrivateKey));
            final JcaKeyPair myPair = new JcaKeyPair(myPublic, myPrivate);

            /* Check that we have a matching pair */
            GordianKeyPairValidity.checkValidity(getFactory(), myPair);

            /* Return the keyPair */
            return myPair;

        } catch (InvalidKeySpecException e) {
            throw new GordianCryptoException(PARSE_ERROR, e);
        }
    }

    /**
     * Create private key.
     *
     * @param pPrivateKey the private key
     * @return the private key
     */
    protected JcaPrivateKey createPrivate(final PrivateKey pPrivateKey) {
        return new JcaPrivateKey(getKeySpec(), pPrivateKey);
    }

    /**
     * Create public key.
     *
     * @param pPublicKey the public key
     * @return the public key
     */
    protected JcaPublicKey createPublic(final PublicKey pPublicKey) {
        return new JcaPublicKey(getKeySpec(), pPublicKey);
    }

    @Override
    public JcaKeyPair derivePublicOnlyKeyPair(final X509EncodedKeySpec pPublicKey) throws GordianException {
        final JcaPublicKey myPublic = derivePublicKey(pPublicKey);
        return new JcaKeyPair(myPublic);
    }

    /**
     * Derive the public key.
     *
     * @param pEncodedKey the encoded public key
     * @return the public key
     * @throws GordianException on error
     */
    protected JcaPublicKey derivePublicKey(final X509EncodedKeySpec pEncodedKey) throws GordianException {
        /* Protect against exceptions */
        try {
            /* Check the keySpec */
            checkKeySpec(pEncodedKey);

            /* derive the key */
            return createPublic(theFactory.generatePublic(pEncodedKey));

        } catch (InvalidKeySpecException e) {
            throw new GordianCryptoException(PARSE_ERROR, e);
        }
    }

    @Override
    public JcaKeyPair generateKeyPair() {
        /* Generate and return the keyPair */
        final KeyPair myPair = theGenerator.generateKeyPair();
        final JcaPublicKey myPublic = createPublic(myPair.getPublic());
        final JcaPrivateKey myPrivate = createPrivate(myPair.getPrivate());
        return new JcaKeyPair(myPublic, myPrivate);
    }

    /**
     * Create generator and factory.
     *
     * @param pAlgorithm  the Algorithm
     * @param postQuantum is this a postQuantum algorithm?
     * @throws GordianException on error
     */
    void createFactories(final String pAlgorithm,
                         final boolean postQuantum) throws GordianException {
        final KeyPairGenerator myGenerator = getJavaKeyPairGenerator(pAlgorithm, postQuantum);
        setKeyPairGenerator(myGenerator);

        /* Create the factory */
        setKeyFactory(getJavaKeyFactory(pAlgorithm, postQuantum));
    }

    /**
     * Jca StateAware KeyPair generator.
     */
    public abstract static class JcaStateAwareKeyPairGenerator
            extends JcaKeyPairGenerator {
        /**
         * Constructor.
         *
         * @param pFactory the Security Factory
         * @param pKeySpec the keySpec
         * @throws GordianException on error
         */
        JcaStateAwareKeyPairGenerator(final GordianBaseFactory pFactory,
                                      final GordianKeyPairSpec pKeySpec) throws GordianException {
            /* initialize underlying class */
            super(pFactory, pKeySpec);
        }

        @Override
        public JcaKeyPair generateKeyPair() {
            /* Generate and return the keyPair */
            final KeyPair myPair = getGenerator().generateKeyPair();
            final JcaPublicKey myPublic = createPublic(myPair.getPublic());
            final JcaStateAwarePrivateKey myPrivate = createPrivate(myPair.getPrivate());
            return new JcaStateAwareKeyPair(myPublic, myPrivate);
        }

        @Override
        protected JcaStateAwarePrivateKey createPrivate(final PrivateKey pPrivateKey) {
            return new JcaStateAwarePrivateKey(getKeySpec(), pPrivateKey);
        }

        @Override
        public JcaKeyPair deriveKeyPair(final X509EncodedKeySpec pPublicKey,
                                        final PKCS8EncodedKeySpec pPrivateKey) throws GordianException {
            /* Protect against exceptions */
            try {
                /* Check the keySpecs */
                checkKeySpec(pPrivateKey);

                /* derive keyPair */
                final JcaPublicKey myPublic = derivePublicKey(pPublicKey);
                JcaStateAwarePrivateKey myPrivate = createPrivate(getKeyFactory().generatePrivate(pPrivateKey));
                final JcaKeyPair myPair = new JcaStateAwareKeyPair(myPublic, myPrivate);

                /* Check that we have a matching pair */
                GordianKeyPairValidity.checkValidity(getFactory(), myPair);

                /* Rebuild and return the keyPair to avoid incrementing usage count */
                myPrivate = createPrivate(getKeyFactory().generatePrivate(pPrivateKey));
                return new JcaStateAwareKeyPair(myPublic, myPrivate);

            } catch (InvalidKeySpecException e) {
                throw new GordianCryptoException(PARSE_ERROR, e);
            }
        }
    }

    /**
     * Create the BouncyCastle KeyFactory via JCA.
     *
     * @param pAlgorithm  the Algorithm
     * @param postQuantum is this a postQuantum algorithm?
     * @return the KeyFactory
     * @throws GordianException on error
     */
    static KeyFactory getJavaKeyFactory(final String pAlgorithm,
                                        final boolean postQuantum) throws GordianException {
        /* Protect against exceptions */
        try {
            /* Return a KeyFactory for the algorithm */
            return KeyFactory.getInstance(pAlgorithm, postQuantum
                    ? JcaProvider.BCPQPROV
                    : JcaProvider.BCPROV);

            /* Catch exceptions */
        } catch (NoSuchAlgorithmException e) {
            /* Throw the exception */
            throw new GordianCryptoException("Failed to create KeyFactory", e);
        }
    }

    /**
     * Create the BouncyCastle KeyPairGenerator via JCA.
     *
     * @param pAlgorithm  the Algorithm
     * @param postQuantum is this a postQuantum algorithm?
     * @return the KeyPairGenerator
     * @throws GordianException on error
     */
    static KeyPairGenerator getJavaKeyPairGenerator(final String pAlgorithm,
                                                    final boolean postQuantum) throws GordianException {
        /* Protect against exceptions */
        try {
            /* Return a KeyPairGenerator for the algorithm */
            return KeyPairGenerator.getInstance(pAlgorithm, postQuantum
                    ? JcaProvider.BCPQPROV
                    : JcaProvider.BCPROV);

            /* Catch exceptions */
        } catch (NoSuchAlgorithmException e) {
            /* Throw the exception */
            throw new GordianCryptoException("Failed to create KeyPairGenerator", e);
        }
    }
}