GordianCoreKeyPairAlgId.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.keypair;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianKeyPairSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianKeyPairSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianLMSSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianNTRUPrimeSpec.GordianNTRUPrimeType;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianXMSSSpec.GordianXMSSDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianXMSSSpec.GordianXMSSHeight;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.spec.GordianXMSSSpec.GordianXMSSMTLayers;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianIOException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreBIKESpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreCMCESpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreDHSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreDSASpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreDSTUSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreECSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreFRODOSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreFalconSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreGOSTSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreHQCSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreKeyPairSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreLMSSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreMLDSASpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreMLKEMSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreMayoSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreNTRUPlusSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreNTRUPrimeSpec.GordianCoreNTRUPrimeParams;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreNTRUSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCorePicnicSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreRSASpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreSABERSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreSLHDSASpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreSM2Spec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.spec.keypair.GordianCoreSnovaSpec;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.oiw.ElGamalParameter;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.DHParameter;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers;
import org.bouncycastle.asn1.ua.DSTU4145NamedCurves;
import org.bouncycastle.asn1.ua.DSTU4145Params;
import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DSAParameter;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.DomainParameters;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.DHParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
import org.bouncycastle.pqc.asn1.XMSSKeyParams;
import org.bouncycastle.pqc.asn1.XMSSMTKeyParams;
import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
import org.bouncycastle.pqc.crypto.lms.LMSPublicKeyParameters;
import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
import org.bouncycastle.util.Pack;

import java.io.IOException;
import java.math.BigInteger;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Mappings from EncodedId to KeyPairSpec.
 */
public class GordianCoreKeyPairAlgId {
    /**
     * TWO as big integer.
     */
    private static final BigInteger TWO = BigInteger.valueOf(2);

    /**
     * The algorithm error.
     */
    private static final String ERROR_ALGO = "Unrecognised algorithm";

    /**
     * The parse error.
     */
    private static final String ERROR_PARSE = "Failed to parse Key";

    /**
     * The namedCurve error.
     */
    private static final String ERROR_NAMEDCURVE = "Not a Named Curve";

    /**
     * The unsupportedCurve error.
     */
    private static final String ERROR_UNSUPCURVE = "Unsupported Curve: ";

    /**
     * The treeDigest error.
     */
    private static final String ERROR_TREEDIGEST = "Unsupported treeDigest: ";

    /**
     * The parser map.
     */
    private final Map<ASN1ObjectIdentifier, GordianEncodedParser> theParserMap;

    /**
     * Constructor.
     */
    public GordianCoreKeyPairAlgId() {
        /* Create the map */
        theParserMap = new HashMap<>();

        /* Register the parsers */
        GordianRSAEncodedParser.register(this);
        GordianElGamalEncodedParser.register(this);
        GordianDSAEncodedParser.register(this);
        GordianDHEncodedParser.register(this);
        GordianECEncodedParser.register(this);
        GordianDSTUEncodedParser.register(this);
        GordianGOSTEncodedParser.register(this);
        GordianEdwardsEncodedParser.register(this);
        GordianSLHDSAEncodedParser.register(this);
        GordianXMSSEncodedParser.register(this);
        GordianXMSSMTEncodedParser.register(this);
        GordianLMSEncodedParser.register(this);
        GordianNewHopeEncodedParser.register(this);
        GordianCMCEEncodedParser.register(this);
        GordianFrodoEncodedParser.register(this);
        GordianSABEREncodedParser.register(this);
        GordianMLKEMEncodedParser.register(this);
        GordianMLDSAEncodedParser.register(this);
        GordianHQCEncodedParser.register(this);
        GordianBIKEEncodedParser.register(this);
        GordianNTRUEncodedParser.register(this);
        GordianNTRUPlusEncodedParser.register(this);
        GordianNTRUPrimeEncodedParser.register(this);
        GordianFalconEncodedParser.register(this);
        GordianMayoEncodedParser.register(this);
        GordianSnovaEncodedParser.register(this);
        GordianPicnicEncodedParser.register(this);
        GordianCompositeEncodedParser.register(this);
    }

    /**
     * Obtain KeySpec from X509KeySpec.
     *
     * @param pEncoded X509 keySpec
     * @return the keySpec
     * @throws GordianException on error
     */
    public GordianKeyPairSpec determineKeyPairSpec(final PKCS8EncodedKeySpec pEncoded) throws GordianException {
        /* Determine the algorithm Id. */
        final PrivateKeyInfo myInfo = PrivateKeyInfo.getInstance(pEncoded.getEncoded());
        final AlgorithmIdentifier myId = myInfo.getPrivateKeyAlgorithm();
        final ASN1ObjectIdentifier myAlgId = myId.getAlgorithm();

        /* Obtain the parser */
        final GordianEncodedParser myParser = theParserMap.get(myAlgId);
        if (myParser != null) {
            return myParser.determineKeyPairSpec(myInfo);
        }
        throw new GordianDataException(ERROR_ALGO);
    }

    /**
     * Obtain KeySpec from X509KeySpec.
     *
     * @param pEncoded X509 keySpec
     * @return the keySpec
     * @throws GordianException on error
     */
    public GordianKeyPairSpec determineKeyPairSpec(final X509EncodedKeySpec pEncoded) throws GordianException {
        /* Determine the algorithm Id. */
        final SubjectPublicKeyInfo myInfo = SubjectPublicKeyInfo.getInstance(pEncoded.getEncoded());
        final AlgorithmIdentifier myId = myInfo.getAlgorithm();
        final ASN1ObjectIdentifier myAlgId = myId.getAlgorithm();

        /* Obtain the parser */
        final GordianEncodedParser myParser = theParserMap.get(myAlgId);
        if (myParser != null) {
            return myParser.determineKeyPairSpec(myInfo);
        }
        throw new GordianDataException(ERROR_ALGO);
    }

    /**
     * register the parser.
     *
     * @param pAlgId  the algorithm Id.
     * @param pParser the parser
     */
    void registerParser(final ASN1ObjectIdentifier pAlgId,
                        final GordianEncodedParser pParser) {
        theParserMap.put(pAlgId, pParser);
    }

    /**
     * EncodedParser interface.
     */
    private interface GordianEncodedParser {
        /**
         * Obtain KeySpec from PrivateKeyInfo.
         *
         * @param pInfo keySpec
         * @return the keySpec
         * @throws GordianException on error
         */
        GordianKeyPairSpec determineKeyPairSpec(SubjectPublicKeyInfo pInfo) throws GordianException;

        /**
         * Obtain KeySpec from SubjectPublicKeyInfo.
         *
         * @param pInfo keySpec
         * @return the keySpec
         * @throws GordianException on error
         */
        GordianKeyPairSpec determineKeyPairSpec(PrivateKeyInfo pInfo) throws GordianException;
    }

    /**
     * RSA Encoded parser.
     */
    private static final class GordianRSAEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(PKCSObjectIdentifiers.rsaEncryption, new GordianRSAEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            /* Protect against exceptions */
            try {
                /* Parse the publicKey */
                final RSAPublicKey myPublic = RSAPublicKey.getInstance(pInfo.parsePublicKey());
                return determineKeyPairSpec(myPublic.getModulus());

                /* Handle exceptions */
            } catch (IOException e) {
                throw new GordianIOException(ERROR_PARSE, e);
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            /* Protect against exceptions */
            try {
                /* Parse the publicKey */
                final RSAPrivateKey myPrivate = RSAPrivateKey.getInstance(pInfo.parsePrivateKey());
                return determineKeyPairSpec(myPrivate.getModulus());

                /* Handle exceptions */
            } catch (IOException e) {
                throw new GordianIOException(ERROR_PARSE, e);
            }
        }

        /**
         * Obtain keySpec from Modulus.
         *
         * @param pModulus the modulus
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final BigInteger pModulus) throws GordianException {
            final GordianCoreRSASpec mySpec = GordianCoreRSASpec.getRSASpecForInteger(pModulus);
            if (mySpec == null) {
                throw new GordianDataException("RSA strength not supported: " + pModulus.bitLength());
            }
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.rsa(mySpec.getSpec());
        }
    }

    /**
     * DSA Encoded parser.
     */
    private static final class GordianDSAEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(X9ObjectIdentifiers.id_dsa, new GordianDSAEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getAlgorithm();
            final DSAParameter myParms = DSAParameter.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getPrivateKeyAlgorithm();
            final DSAParameter myParms = DSAParameter.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final DSAParameter pParms) throws GordianException {
            final GordianCoreDSASpec mySpec = GordianCoreDSASpec.getDSASpecForParms(pParms);
            if (mySpec == null) {
                throw new GordianDataException("Unsupported DSA parameters: "
                        + pParms.getP().bitLength() + ":" + pParms.getQ().bitLength());
            }
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.dsa(mySpec.getSpec());
        }
    }

    /**
     * DH Encoded parser.
     */
    public static class GordianDHEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(PKCSObjectIdentifiers.dhKeyAgreement, new GordianDHEncodedParser());
            pIdManager.registerParser(X9ObjectIdentifiers.dhpublicnumber, new GordianDHEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final DHParameters myParms = determineParameters(pInfo.getAlgorithm());
            return determineKeyPairSpec(myParms);
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final DHParameters myParms = determineParameters(pInfo.getPrivateKeyAlgorithm());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain parameters from encoded sequence.
         *
         * @param pId the algorithm Identifier
         * @return the parameters
         */
        public static DHParameters determineParameters(final AlgorithmIdentifier pId) {
            /* Access algorithmId */
            final ASN1ObjectIdentifier myId = pId.getAlgorithm();

            /* If this is key agreement */
            if (PKCSObjectIdentifiers.dhKeyAgreement.equals(myId)) {
                /* Access the DHParameter */
                final DHParameter myParams = DHParameter.getInstance(pId.getParameters());

                /* If we have an L value */
                return myParams.getL() != null
                        ? new DHParameters(myParams.getP(), myParams.getG(), null, myParams.getL().intValue())
                        : new DHParameters(myParams.getP(), myParams.getG());

            } else if (X9ObjectIdentifiers.dhpublicnumber.equals(myId)) {
                /* Access Domain Parameters */
                final DomainParameters myParams = DomainParameters.getInstance(pId.getParameters());

                return new DHParameters(myParams.getP(), myParams.getG(), myParams.getQ(), myParams.getJ(), null);
            } else {
                throw new IllegalArgumentException("unknown algorithm type: " + myId);
            }
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final DHParameters pParms) throws GordianException {
            final GordianCoreDHSpec myGroup = GordianCoreDHSpec.getSpecForParams(pParms);
            if (myGroup == null) {
                throw new GordianDataException("Unsupported DH parameters: "
                        + pParms.getP().bitLength());
            }
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.dh(myGroup.getSpec());
        }
    }

    /**
     * ElGamal Encoded parser.
     */
    public static class GordianElGamalEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(OIWObjectIdentifiers.elGamalAlgorithm, new GordianElGamalEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final DHParameters myParms = determineParameters(pInfo.getAlgorithm());
            return determineKeyPairSpec(myParms);
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final DHParameters myParms = determineParameters(pInfo.getPrivateKeyAlgorithm());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain parameters from encoded sequence.
         *
         * @param pId the algorithm Identifier
         * @return the parameters
         */
        public static DHParameters determineParameters(final AlgorithmIdentifier pId) {
            /* Access the ElGamalParameter */
            final ElGamalParameter myParams = ElGamalParameter.getInstance(pId.getParameters());

            /* Convert to DH parameters */
            return new DHParameters(myParams.getP(), TWO, myParams.getG());
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final DHParameters pParms) throws GordianException {
            final GordianCoreDHSpec myGroup = GordianCoreDHSpec.getSpecForParams(pParms);
            if (myGroup == null) {
                throw new GordianDataException("Unsupported DH parameters: "
                        + pParms.getP().bitLength());
            }
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.elGamal(myGroup.getSpec());
        }
    }

    /**
     * EC Encoded parser.
     */
    private static final class GordianECEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(X9ObjectIdentifiers.id_ecPublicKey, new GordianECEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getAlgorithm();
            final X962Parameters myParms = X962Parameters.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getPrivateKeyAlgorithm();
            final X962Parameters myParms = X962Parameters.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final X962Parameters pParms) throws GordianException {
            /* Reject if not a named curve */
            if (!pParms.isNamedCurve()) {
                throw new GordianDataException(ERROR_NAMEDCURVE);
            }

            /* Check for EC named curve */
            final ASN1ObjectIdentifier myId = (ASN1ObjectIdentifier) pParms.getParameters();
            String myName = CustomNamedCurves.getName(myId);
            if (myName == null) {
                myName = ECNamedCurveTable.getName(myId);
            }
            if (myName != null) {
                final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                final GordianCoreECSpec myECCurve = GordianCoreECSpec.getCurveForName(myName);
                if (myECCurve != null) {
                    return myBuilder.ec(myECCurve.getSpec());
                }
                final GordianCoreSM2Spec mySM2Curve = GordianCoreSM2Spec.getCurveForName(myName);
                if (mySM2Curve != null) {
                    return myBuilder.sm2(mySM2Curve.getSpec());
                }
                throw new GordianDataException(ERROR_UNSUPCURVE + myName);
            }

            /* Curve is not supported */
            throw new GordianDataException(ERROR_UNSUPCURVE + pParms);
        }
    }

    /**
     * DSTU Encoded parser.
     */
    private static final class GordianDSTUEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(UAObjectIdentifiers.dstu4145be, new GordianDSTUEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getAlgorithm();
            final DSTU4145Params myParms = DSTU4145Params.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getPrivateKeyAlgorithm();
            final X962Parameters myParms = X962Parameters.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final DSTU4145Params pParms) throws GordianException {
            /* Reject if not a named curve */
            if (!pParms.isNamedCurve()) {
                throw new GordianDataException(ERROR_NAMEDCURVE);
            }
            return determineKeyPairSpec(pParms.getNamedCurve());
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final X962Parameters pParms) throws GordianException {
            /* Reject if not a named curve */
            if (!pParms.isNamedCurve()) {
                throw new GordianDataException(ERROR_NAMEDCURVE);
            }
            return determineKeyPairSpec((ASN1ObjectIdentifier) pParms.getParameters());
        }

        /**
         * Obtain keySpec from curveId.
         *
         * @param pId the curveId
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final ASN1ObjectIdentifier pId) throws GordianException {
            /* Check for EC named surve */
            final String myName = pId.toString();
            final ECDomainParameters myParms = DSTU4145NamedCurves.getByOID(pId);
            final GordianCoreDSTUSpec myCurve = GordianCoreDSTUSpec.getCurveForName(myName);
            if (myParms == null || myCurve == null) {
                throw new GordianDataException(ERROR_UNSUPCURVE + myName);
            }
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.dstu4145(myCurve.getSpec());
        }
    }

    /**
     * GOST Encoded parser.
     */
    private static final class GordianGOSTEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianGOSTEncodedParser myParser = new GordianGOSTEncodedParser();
            pIdManager.registerParser(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256, myParser);
            pIdManager.registerParser(RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512, myParser);
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return determineKeyPairSpec(pInfo.getAlgorithm());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return determineKeyPairSpec(pInfo.getPrivateKeyAlgorithm());
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pId the algorithmId
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final AlgorithmIdentifier pId) throws GordianException {
            /* Determine the curve name */
            final GOST3410PublicKeyAlgParameters myParms = GOST3410PublicKeyAlgParameters.getInstance(pId.getParameters());
            final ASN1ObjectIdentifier myId = myParms.getPublicKeyParamSet();
            final String myName = ECGOST3410NamedCurves.getName(myId);

            /* Determine curve */
            if (myName != null) {
                final GordianCoreGOSTSpec myCurve = GordianCoreGOSTSpec.getCurveForName(myName);
                if (myCurve == null) {
                    throw new GordianDataException(ERROR_UNSUPCURVE + myName);
                }
                final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                return myBuilder.gost2012(myCurve.getSpec());
            }

            /* Curve is not supported */
            throw new GordianDataException(ERROR_UNSUPCURVE + myParms);
        }
    }

    /**
     * SLHDSA Encoded parser.
     */
    private static class GordianSLHDSAEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianSLHDSAEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreSLHDSASpec mySpec : GordianCoreSLHDSASpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianSLHDSAEncodedParser(myBuilder.slhdsa(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * CMCE Encoded parser.
     */
    private static class GordianCMCEEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianCMCEEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreCMCESpec mySpec : GordianCoreCMCESpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianCMCEEncodedParser(myBuilder.cmce(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * Frodo Encoded parser.
     */
    private static class GordianFrodoEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianFrodoEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreFRODOSpec mySpec : GordianCoreFRODOSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianFrodoEncodedParser(myBuilder.frodo(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * SABER Encoded parser.
     */
    private static class GordianSABEREncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianSABEREncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreSABERSpec mySpec : GordianCoreSABERSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianSABEREncodedParser(myBuilder.saber(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * MLKEM Encoded parser.
     */
    private static class GordianMLKEMEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianMLKEMEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreMLKEMSpec mySpec : GordianCoreMLKEMSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianMLKEMEncodedParser(myBuilder.mlkem(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * MLDSA Encoded parser.
     */
    private static class GordianMLDSAEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianMLDSAEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreMLDSASpec mySpec : GordianCoreMLDSASpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianMLDSAEncodedParser(myBuilder.mldsa(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * HQC Encoded parser.
     */
    private static class GordianHQCEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianHQCEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreHQCSpec mySpec : GordianCoreHQCSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianHQCEncodedParser(myBuilder.hqc(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * BIKE Encoded parser.
     */
    private static class GordianBIKEEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianBIKEEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreBIKESpec mySpec : GordianCoreBIKESpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianBIKEEncodedParser(myBuilder.bike(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * NTRU Encoded parser.
     */
    private static class GordianNTRUEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianNTRUEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreNTRUSpec mySpec : GordianCoreNTRUSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianNTRUEncodedParser(myBuilder.ntru(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * NTRUPlus Encoded parser.
     */
    private static class GordianNTRUPlusEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianNTRUPlusEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreNTRUPlusSpec mySpec : GordianCoreNTRUPlusSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianNTRUEncodedParser(myBuilder.ntruPlus(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * NTRUPrime Encoded parser.
     */
    private static class GordianNTRUPrimeEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianNTRUPrimeEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreNTRUPrimeParams myParams : GordianCoreNTRUPrimeParams.values()) {
                pIdManager.registerParser(myParams.getNTRULIdentifier(),
                        new GordianNTRUPrimeEncodedParser(myBuilder.ntruprime(GordianNTRUPrimeType.NTRUL, myParams.getParams())));
                pIdManager.registerParser(myParams.getSNTRUIdentifier(),
                        new GordianNTRUPrimeEncodedParser(myBuilder.ntruprime(GordianNTRUPrimeType.SNTRU, myParams.getParams())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * Falcon Encoded parser.
     */
    private static class GordianFalconEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianFalconEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreFalconSpec mySpec : GordianCoreFalconSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianFalconEncodedParser(myBuilder.falcon(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * Mayo Encoded parser.
     */
    private static class GordianMayoEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianMayoEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreMayoSpec mySpec : GordianCoreMayoSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianMayoEncodedParser(myBuilder.mayo(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * Snova Encoded parser.
     */
    private static class GordianSnovaEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianSnovaEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCoreSnovaSpec mySpec : GordianCoreSnovaSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianSnovaEncodedParser(myBuilder.snova(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * Picnic Encoded parser.
     */
    private static class GordianPicnicEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianPicnicEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            for (GordianCorePicnicSpec mySpec : GordianCorePicnicSpec.values()) {
                pIdManager.registerParser(mySpec.getIdentifier(), new GordianPicnicEncodedParser(myBuilder.picnic(mySpec.getSpec())));
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * NewHope Encoded parser.
     */
    private static final class GordianNewHopeEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(PQCObjectIdentifiers.newHope, new GordianNewHopeEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.newHope();
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.newHope();
        }
    }

    /**
     * XMSS Encoded parser.
     */
    private static final class GordianXMSSEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(PQCObjectIdentifiers.xmss, new GordianXMSSEncodedParser());
            pIdManager.registerParser(IsaraObjectIdentifiers.id_alg_xmss, new GordianXMSSEncodedParser());
        }


        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getAlgorithm();
            final XMSSKeyParams myParms = XMSSKeyParams.getInstance(myId.getParameters());
            if (myParms != null) {
                return determineKeyPairSpec(myParms);
            }

            /* Protect against exceptions */
            try {
                final byte[] keyEnc = Objects.requireNonNull(ASN1OctetString.getInstance(pInfo.parsePublicKey())).getOctets();
                final int myOID = Pack.bigEndianToInt(keyEnc, 0);
                final XMSSParameters myParams = XMSSParameters.lookupByOID(myOID);
                final GordianKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                return myBuilder.xmss(determineKeyType(myParams.getTreeDigestOID()), determineHeight(myParams.getHeight()));
            } catch (IOException e) {
                throw new GordianIOException("Failed to resolve key", e);
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getPrivateKeyAlgorithm();
            final XMSSKeyParams myParms = XMSSKeyParams.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final XMSSKeyParams pParms) throws GordianException {
            final ASN1ObjectIdentifier myDigest = pParms.getTreeDigest().getAlgorithm();
            final GordianXMSSHeight myHeight = determineHeight(pParms.getHeight());
            final GordianKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.xmss(determineKeyType(myDigest), myHeight);
        }

        /**
         * Obtain keyType from digest.
         *
         * @param pDigest the treeDigest
         * @return the keyType
         * @throws GordianException on error
         */
        static GordianXMSSDigestType determineKeyType(final ASN1ObjectIdentifier pDigest) throws GordianException {
            if (pDigest.equals(NISTObjectIdentifiers.id_sha256)) {
                return GordianXMSSDigestType.SHA256;
            }
            if (pDigest.equals(NISTObjectIdentifiers.id_sha512)) {
                return GordianXMSSDigestType.SHA512;
            }
            if (pDigest.equals(NISTObjectIdentifiers.id_shake128)) {
                return GordianXMSSDigestType.SHAKE128;
            }
            if (pDigest.equals(NISTObjectIdentifiers.id_shake256)) {
                return GordianXMSSDigestType.SHAKE256;
            }

            /* Tree Digest is not supported */
            throw new GordianDataException(ERROR_TREEDIGEST + pDigest);
        }

        /**
         * Obtain height.
         *
         * @param pHeight the height
         * @return the xmssHeight
         * @throws GordianException on error
         */
        static GordianXMSSHeight determineHeight(final int pHeight) throws GordianException {
            /* Loo through the heights */
            for (GordianXMSSHeight myHeight : GordianXMSSHeight.values()) {
                if (myHeight.getHeight() == pHeight) {
                    return myHeight;
                }
            }

            /* Height is not supported */
            throw new GordianDataException("Inavlid height: " + pHeight);
        }
    }

    /**
     * XMSSMT Encoded parser.
     */
    private static final class GordianXMSSMTEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(PQCObjectIdentifiers.xmss_mt, new GordianXMSSMTEncodedParser());
            pIdManager.registerParser(IsaraObjectIdentifiers.id_alg_xmssmt, new GordianXMSSMTEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getAlgorithm();
            final XMSSMTKeyParams myParms = XMSSMTKeyParams.getInstance(myId.getParameters());
            if (myParms != null) {
                return determineKeyPairSpec(myParms);
            }

            /* Protect against exceptions */
            try {
                final byte[] keyEnc = Objects.requireNonNull(ASN1OctetString.getInstance(pInfo.parsePublicKey())).getOctets();
                final int myOID = Pack.bigEndianToInt(keyEnc, 0);
                final XMSSMTParameters myParams = XMSSMTParameters.lookupByOID(myOID);
                final GordianKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                return myBuilder.xmssmt(GordianXMSSEncodedParser.determineKeyType(myParams.getTreeDigestOID()),
                        GordianXMSSEncodedParser.determineHeight(myParams.getHeight()), determineLayers(myParams.getLayers()));
            } catch (IOException e) {
                throw new GordianIOException("Failed to resolve key", e);
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            final AlgorithmIdentifier myId = pInfo.getPrivateKeyAlgorithm();
            final XMSSMTKeyParams myParms = XMSSMTKeyParams.getInstance(myId.getParameters());
            return determineKeyPairSpec(myParms);
        }

        /**
         * Obtain keySpec from Parameters.
         *
         * @param pParms the parameters
         * @return the keySpec
         * @throws GordianException on error
         */
        private static GordianKeyPairSpec determineKeyPairSpec(final XMSSMTKeyParams pParms) throws GordianException {
            final ASN1ObjectIdentifier myDigest = pParms.getTreeDigest().getAlgorithm();
            final GordianXMSSHeight myHeight = GordianXMSSEncodedParser.determineHeight(pParms.getHeight());
            final GordianXMSSMTLayers myLayers = determineLayers(pParms.getLayers());
            final GordianKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            return myBuilder.xmssmt(GordianXMSSEncodedParser.determineKeyType(myDigest), myHeight, myLayers);
        }

        /**
         * Obtain layers.
         *
         * @param pLayers the layers
         * @return the xmssMTLayers
         * @throws GordianException on error
         */
        static GordianXMSSMTLayers determineLayers(final int pLayers) throws GordianException {
            /* Loo through the heights */
            for (GordianXMSSMTLayers myLayers : GordianXMSSMTLayers.values()) {
                if (myLayers.getLayers() == pLayers) {
                    return myLayers;
                }
            }

            /* Layers is not supported */
            throw new GordianDataException("Invalid layers: " + pLayers);
        }
    }

    /**
     * Edwards Encoded parser.
     */
    private static class GordianEdwardsEncodedParser implements GordianEncodedParser {
        /**
         * AsymKeySpec.
         */
        private final GordianKeyPairSpec theKeySpec;

        /**
         * Constructor.
         *
         * @param pKeySpec the keySpec
         */
        GordianEdwardsEncodedParser(final GordianKeyPairSpec pKeySpec) {
            theKeySpec = pKeySpec;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
            pIdManager.registerParser(EdECObjectIdentifiers.id_X25519, new GordianEdwardsEncodedParser(myBuilder.x25519()));
            pIdManager.registerParser(EdECObjectIdentifiers.id_X448, new GordianEdwardsEncodedParser(myBuilder.x448()));
            pIdManager.registerParser(EdECObjectIdentifiers.id_Ed25519, new GordianEdwardsEncodedParser(myBuilder.ed25519()));
            pIdManager.registerParser(EdECObjectIdentifiers.id_Ed448, new GordianEdwardsEncodedParser(myBuilder.ed448()));
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            return theKeySpec;
        }
    }

    /**
     * LMS Encoded parser.
     */
    private static final class GordianLMSEncodedParser implements GordianEncodedParser {
        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig, new GordianLMSEncodedParser());
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            /* Protect against exceptions */
            try {
                /* Parse public key */
                final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                final HSSPublicKeyParameters myPublic = (HSSPublicKeyParameters) PublicKeyFactory.createKey(pInfo);
                final int myDepth = myPublic.getL();
                final LMSPublicKeyParameters myLMSPublicKey = myPublic.getLMSPublicKey();
                final GordianLMSSpec myKeySpec = determineKeyPairSpec(myLMSPublicKey);
                return myBuilder.hss(myKeySpec.getHash(), myKeySpec.getHeight(), myKeySpec.getWidth(), myKeySpec.getLength(), myDepth);

                /* Handle exceptions */
            } catch (IOException e) {
                throw new GordianIOException(ERROR_PARSE, e);
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            /* Protect against exceptions */
            try {
                /* Parse public key */
                final GordianCoreKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                final HSSPrivateKeyParameters myPrivate = (HSSPrivateKeyParameters) PrivateKeyFactory.createKey(pInfo);
                final int myDepth = myPrivate.getL();
                final LMSPublicKeyParameters myLMSPublicKey = myPrivate.getPublicKey().getLMSPublicKey();
                final GordianLMSSpec myKeySpec = determineKeyPairSpec(myLMSPublicKey);
                return myBuilder.hss(myKeySpec.getHash(), myKeySpec.getHeight(), myKeySpec.getWidth(), myKeySpec.getLength(), myDepth);

                /* Handle exceptions */
            } catch (IOException e) {
                throw new GordianIOException(ERROR_PARSE, e);
            }
        }

        /**
         * Obtain keySpec from public key.
         *
         * @param pPublic the publicKeyParams
         * @return the LMSKeySpec
         */
        static GordianLMSSpec determineKeyPairSpec(final LMSPublicKeyParameters pPublic) {
            return GordianCoreLMSSpec.determineSpec(pPublic.getSigParameters(), pPublic.getOtsParameters());
        }
    }

    /**
     * Composite Encoded parser.
     */
    private static class GordianCompositeEncodedParser implements GordianEncodedParser {
        /**
         * The KeyPairFactory.
         */
        private final GordianCoreKeyPairAlgId theIdManager;

        /**
         * Constructor.
         *
         * @param pIdManager the idManager
         */
        GordianCompositeEncodedParser(final GordianCoreKeyPairAlgId pIdManager) {
            theIdManager = pIdManager;
        }

        /**
         * Registrar.
         *
         * @param pIdManager the idManager
         */
        static void register(final GordianCoreKeyPairAlgId pIdManager) {
            pIdManager.registerParser(MiscObjectIdentifiers.id_alg_composite, new GordianCompositeEncodedParser(pIdManager));
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final SubjectPublicKeyInfo pInfo) throws GordianException {
            /* Protect against exceptions */
            try {
                final ASN1Sequence myKeys = ASN1Sequence.getInstance(pInfo.getPublicKeyData().getBytes());
                final List<GordianKeyPairSpec> mySpecs = new ArrayList<>();

                /* Build the list from the keys sequence */
                final Enumeration<?> en = myKeys.getObjects();
                while (en.hasMoreElements()) {
                    final SubjectPublicKeyInfo myPKInfo = SubjectPublicKeyInfo.getInstance(en.nextElement());
                    mySpecs.add(theIdManager.determineKeyPairSpec(new X509EncodedKeySpec(myPKInfo.getEncoded())));
                }
                final GordianKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                return myBuilder.composite(mySpecs);

                /* Handle exceptions */
            } catch (IOException e) {
                throw new GordianIOException(ERROR_PARSE, e);
            }
        }

        @Override
        public GordianKeyPairSpec determineKeyPairSpec(final PrivateKeyInfo pInfo) throws GordianException {
            /* Protect against exceptions */
            try {
                final ASN1Sequence myKeys = ASN1Sequence.getInstance(pInfo.getPrivateKey().getOctets());
                final List<GordianKeyPairSpec> mySpecs = new ArrayList<>();

                /* Build the list from the keys sequence */
                final Enumeration<?> en = myKeys.getObjects();
                while (en.hasMoreElements()) {
                    final PrivateKeyInfo myPKInfo = PrivateKeyInfo.getInstance(en.nextElement());
                    mySpecs.add(theIdManager.determineKeyPairSpec(new PKCS8EncodedKeySpec(myPKInfo.getEncoded())));
                }
                final GordianKeyPairSpecBuilder myBuilder = GordianCoreKeyPairSpecBuilder.newInstance();
                return myBuilder.composite(mySpecs);

                /* Handle exceptions */
            } catch (IOException e) {
                throw new GordianIOException(ERROR_PARSE, e);
            }
        }
    }
}