GordianCoreKeyStoreManager.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.keystore;

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.cert.GordianCertificate;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUsage;
import io.github.tonywasher.joceanus.gordianknot.api.cert.GordianKeyPairUse;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianAsyncFactory;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKey;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKeyGenerator;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairFactory;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairGenerator;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetFactory;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keystore.GordianKeyStoreEntry.GordianKeyStoreKey;
import io.github.tonywasher.joceanus.gordianknot.api.keystore.GordianKeyStoreEntry.GordianKeyStorePair;
import io.github.tonywasher.joceanus.gordianknot.api.keystore.GordianKeyStoreEntry.GordianKeyStoreSet;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cert.GordianCoreCertificate;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianLogicException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianCoreKeyPair;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianCoreKeyStoreEntry.GordianCoreKeyStorePair;
import org.bouncycastle.asn1.x500.X500Name;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * KeyStore Manager implementation.
 */
public class GordianCoreKeyStoreManager
        implements GordianBaseKeyStoreManager {
    /**
     * The factory.
     */
    private final GordianBaseFactory theFactory;

    /**
     * The keyStore.
     */
    private final GordianCoreKeyStore theKeyStore;

    /**
     * Constructor.
     *
     * @param pFactory  the factory
     * @param pKeyStore the keyStore
     */
    GordianCoreKeyStoreManager(final GordianBaseFactory pFactory,
                               final GordianCoreKeyStore pKeyStore) {
        theFactory = pFactory;
        theKeyStore = pKeyStore;
    }

    @Override
    public GordianCoreKeyStore getKeyStore() {
        return theKeyStore;
    }

    @Override
    public GordianKeyStoreSet createKeySet(final GordianKeySetSpec pKeySetSpec,
                                           final String pAlias,
                                           final char[] pPassword) throws GordianException {
        final GordianKeySetFactory myFactory = theFactory.getKeySetFactory();
        final GordianKeySet myKeySet = myFactory.generateKeySet(pKeySetSpec);
        theKeyStore.setKeySet(pAlias, myKeySet, pPassword);
        return (GordianKeyStoreSet) theKeyStore.getEntry(pAlias, pPassword);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <K extends GordianKeySpec> GordianKeyStoreKey<K> createKey(final K pKeySpec,
                                                                      final String pAlias,
                                                                      final char[] pPassword) throws GordianException {
        /* Access the relevant keyGenerator */
        final GordianKeyGenerator<K> myGenerator = pKeySpec instanceof GordianMacSpec
                ? theFactory.getMacFactory().getKeyGenerator(pKeySpec)
                : theFactory.getCipherFactory().getKeyGenerator(pKeySpec);

        /* Generate, store and return key */
        final GordianKey<K> myKey = myGenerator.generateKey();
        theKeyStore.setKey(pAlias, myKey, pPassword);
        return (GordianKeyStoreKey<K>) theKeyStore.getEntry(pAlias, pPassword);
    }

    @Override
    public GordianCoreKeyStorePair createRootKeyPair(final GordianKeyPairSpec pKeySpec,
                                                     final X500Name pSubject,
                                                     final String pAlias,
                                                     final char[] pPassword) throws GordianException {
        /* Check that the keySpec can provide a signature */
        if (theFactory.getAsyncFactory().getSignatureFactory().defaultForKeyPair(pKeySpec) == null) {
            throw new GordianDataException("Root keyPair must be capable of signing");
        }

        /* Create the new keyPair */
        final GordianKeyPairFactory myFactory = theFactory.getAsyncFactory().getKeyPairFactory();
        final GordianKeyPairGenerator myGenerator = myFactory.getKeyPairGenerator(pKeySpec);
        final GordianCoreKeyPair myKeyPair = (GordianCoreKeyPair) myGenerator.generateKeyPair();

        /* Create the certificate */
        final GordianCoreCertificate myCert = new GordianCoreCertificate(theFactory, myKeyPair, pSubject);
        final List<GordianCertificate> myChain = Collections.singletonList(myCert);

        /* Record into keyStore */
        theKeyStore.setKeyPair(pAlias, myKeyPair, pPassword, myChain);
        return (GordianCoreKeyStorePair) theKeyStore.getEntry(pAlias, pPassword);
    }

    @Override
    public GordianCoreKeyStorePair createKeyPair(final GordianKeyPairSpec pKeySpec,
                                                 final X500Name pSubject,
                                                 final GordianKeyPairUsage pUsage,
                                                 final GordianKeyStorePair pSigner,
                                                 final String pAlias,
                                                 final char[] pPassword) throws GordianException {
        /* Create the new keyPair */
        checkKeyPairUsage(pKeySpec, pUsage);
        final GordianKeyPairFactory myFactory = theFactory.getAsyncFactory().getKeyPairFactory();
        final GordianKeyPairGenerator myGenerator = myFactory.getKeyPairGenerator(pKeySpec);
        final GordianKeyPair myKeyPair = myGenerator.generateKeyPair();

        /* Create the certificate */
        final GordianCoreCertificate myCert = new GordianCoreCertificate(theFactory, pSigner, myKeyPair, pSubject, pUsage);

        /* Create the new chain */
        final List<GordianCertificate> myParentChain = pSigner.getCertificateChain();
        final List<GordianCertificate> myChain = new ArrayList<>(myParentChain);
        myChain.addFirst(myCert);

        /* Record into keyStore */
        theKeyStore.setKeyPair(pAlias, myKeyPair, pPassword, myChain);
        return (GordianCoreKeyStorePair) theKeyStore.getEntry(pAlias, pPassword);
    }

    @Override
    public GordianCoreKeyStorePair createAlternate(final GordianKeyStorePair pKeyPair,
                                                   final GordianKeyStorePair pSigner,
                                                   final String pAlias,
                                                   final char[] pPassword) throws GordianException {
        /* Access the keyPair and subject */
        final GordianCoreKeyPair myKeyPair = (GordianCoreKeyPair) pKeyPair.getKeyPair();
        final GordianCertificate myBase = pKeyPair.getCertificateChain().getFirst();
        final X500Name mySubject = myBase.getSubject().getName();
        final GordianKeyPairUsage myUsage = myBase.getUsage();

        /* Create the certificate */
        final GordianCoreCertificate myCert = new GordianCoreCertificate(theFactory, pSigner, myKeyPair, mySubject, myUsage);

        /* Create the new chain */
        final List<GordianCertificate> myParentChain = pSigner.getCertificateChain();
        final List<GordianCertificate> myChain = new ArrayList<>(myParentChain);
        myChain.addFirst(myCert);

        /* Record into keyStore */
        theKeyStore.setKeyPair(pAlias, myKeyPair, pPassword, myChain);
        return (GordianCoreKeyStorePair) theKeyStore.getEntry(pAlias, pPassword);
    }

    @Override
    public List<GordianCertificate> signKeyPair(final GordianKeyPair pKeyPair,
                                                final X500Name pSubject,
                                                final GordianKeyPairUsage pUsage,
                                                final GordianKeyStorePair pSigner) throws GordianException {
        /* Create the certificate */
        final GordianCoreCertificate myCert = new GordianCoreCertificate(theFactory, pSigner, pKeyPair, pSubject, pUsage);

        /* Create the new chain */
        final List<GordianCertificate> myParentChain = pSigner.getCertificateChain();
        final List<GordianCertificate> myChain = new ArrayList<>(myParentChain);
        myChain.addFirst(myCert);
        return myChain;
    }

    /**
     * Check Usage for keyPairSpec.
     *
     * @param pKeyPairSpec the keyPairSpec
     * @param pUsage       the key usage
     * @throws GordianException on error
     */
    private void checkKeyPairUsage(final GordianKeyPairSpec pKeyPairSpec,
                                   final GordianKeyPairUsage pUsage) throws GordianException {
        /* Determine the requirements */
        final boolean needsSign = pUsage.hasUse(GordianKeyPairUse.CERTIFICATE)
                || pUsage.hasUse(GordianKeyPairUse.SIGNATURE);
        final boolean needsEnc = pUsage.hasUse(GordianKeyPairUse.KEYENCRYPT)
                || pUsage.hasUse(GordianKeyPairUse.DATAENCRYPT);
        final boolean needsAgree = pUsage.hasUse(GordianKeyPairUse.AGREEMENT);

        /* Validate keyPairSpec against requirements */
        final GordianAsyncFactory myAsyncFactory = theFactory.getAsyncFactory();
        final boolean bFail = (needsSign && myAsyncFactory.getSignatureFactory().defaultForKeyPair(pKeyPairSpec) == null)
                || (needsEnc && myAsyncFactory.getEncryptorFactory().defaultForKeyPair(pKeyPairSpec) == null)
                || (needsAgree && myAsyncFactory.getAgreementFactory().defaultForKeyPair(pKeyPairSpec) == null);

        /* Handle failure */
        if (bFail) {
            throw new GordianLogicException("Unsupported Usage for keyPair");
        }
    }
}