GordianKeyStoreDocument.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.GordianCertificateId;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianKnuthObfuscater;
import io.github.tonywasher.joceanus.gordianknot.api.keystore.GordianKeyStoreEntry;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cert.GordianCoreCertificate;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cert.GordianCoreCertificateId;
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.keystore.GordianBaseKeyStore.GordianKeyStoreCertificateKey;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianKeyStoreElement.GordianKeyStoreCertificateElement;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianKeyStoreElement.GordianKeyStoreKeyElement;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianKeyStoreElement.GordianKeyStoreLockElement;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianKeyStoreElement.GordianKeyStorePairElement;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keystore.GordianKeyStoreElement.GordianKeyStoreSetElement;
import io.github.tonywasher.joceanus.gordianknot.impl.core.lock.GordianPasswordLockSpecASN1;
import org.bouncycastle.asn1.x500.X500Name;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * KeyStore Document.
 */
public final class GordianKeyStoreDocument {
    /**
     * The Invalid XML error.
     */
    private static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.BASIC_ISO_DATE;

    /**
     * The Invalid XML error.
     */
    private static final String ERROR_BADXML = "Invalid XML";

    /**
     * The KeyStore element.
     */
    private static final String DOC_KEYSTORE = "KeyStore";

    /**
     * The KeySetSpec attribute.
     */
    private static final String ATTR_KEYSETSPEC = "KeySetSpec";

    /**
     * The CreationDate attribute.
     */
    private static final String ATTR_DATE = "CreationDate";

    /**
     * The Aliases element.
     */
    private static final String ELEMENT_ALIASES = "Aliases";

    /**
     * The Alias element.
     */
    private static final String ATTR_ALIAS = "Alias";

    /**
     * The Certificates element.
     */
    private static final String ELEMENT_CERTIFICATES = "Certificates";

    /**
     * The PairCertificate element.
     */
    private static final String ELEMENT_PAIRCERT = "PairCertificate";

    /**
     * The keyType attribute.
     */
    private static final String ATTR_KEYSPEC = "KeySpec";

    /**
     * The securedKey element.
     */
    private static final String ELEMENT_SECUREDKEY = "SecuredKey";

    /**
     * The lock element.
     */
    private static final String ELEMENT_LOCK = "LockBytes";

    /**
     * The CertificateKey element.
     */
    private static final String ELEMENT_CERTKEY = "CertificateKey";

    /**
     * The Subject element.
     */
    private static final String ELEMENT_SUBJECT = "Subject";

    /**
     * The Issuer element.
     */
    private static final String ELEMENT_ISSUER = "Issuer";

    /**
     * The Name element.
     */
    private static final String ELEMENT_NAME = "Name";

    /**
     * The Id element.
     */
    private static final String ELEMENT_ID = "Id";

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

    /**
     * The document.
     */
    private final Document theDocument;

    /**
     * Constructor from keyStore.
     *
     * @param pKeyStore the keyStore
     * @throws GordianException on error
     */
    public GordianKeyStoreDocument(final GordianBaseKeyStore pKeyStore) throws GordianException {
        try {
            /* Store the keyStore */
            theKeyStore = pKeyStore;

            /* Create the document */
            final DocumentBuilderFactory myFactory = DocumentBuilderFactory.newInstance();
            myFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
            myFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
            final DocumentBuilder myBuilder = myFactory.newDocumentBuilder();
            theDocument = myBuilder.newDocument();

            /* Create the main document node */
            final Element myMain = theDocument.createElement(DOC_KEYSTORE);
            theDocument.appendChild(myMain);

            /* Record the keySetSpec */
            final GordianPasswordLockSpecASN1 mySpecASN1 = new GordianPasswordLockSpecASN1(pKeyStore.getPasswordLockSpec());
            final String myAttrSpec = GordianDataConverter.byteArrayToBase64(mySpecASN1.getEncodedBytes());
            myMain.setAttribute(ATTR_KEYSETSPEC, myAttrSpec);

            /* Create the aliases */
            final Element myAliases = theDocument.createElement(ELEMENT_ALIASES);
            myMain.appendChild(myAliases);
            buildAliases(myAliases);

            /* Create the certificates */
            final Element myCerts = theDocument.createElement(ELEMENT_CERTIFICATES);
            myMain.appendChild(myCerts);
            buildCertificates(myCerts);

        } catch (ParserConfigurationException e) {
            throw new GordianIOException("Failed to initialise author", e);
        }
    }

    /**
     * Constructor from document.
     *
     * @param pFactory  the factory
     * @param pDocument the document
     * @throws GordianException on error
     */
    public GordianKeyStoreDocument(final GordianFactory pFactory,
                                   final Document pDocument) throws GordianException {
        /* Access the document element */
        final Element myDocElement = pDocument.getDocumentElement();

        /* Check that it is correct document */
        if (!DOC_KEYSTORE.equals(myDocElement.getNodeName())) {
            throw new GordianDataException("Invalid Document");
        }

        /* Access the keySetSpec */
        final String myAttrSpec = myDocElement.getAttribute(ATTR_KEYSETSPEC);
        final byte[] myAttrArray = GordianDataConverter.base64ToByteArray(myAttrSpec);
        final GordianPasswordLockSpecASN1 mySpecASN1 = GordianPasswordLockSpecASN1.getInstance(myAttrArray);

        /* Create the empty keyStore */
        theKeyStore = (GordianBaseKeyStore) pFactory.getAsyncFactory().getKeyStoreFactory().createKeyStore(mySpecASN1.getLockSpec());
        theDocument = pDocument;

        /* Loop through the nodes */
        Node myNode = myDocElement.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is the aliases Node */
            if (ELEMENT_ALIASES.equals(myNodeName)) {
                /* Parse the aliases */
                parseAliases(myNode);
            }

            /* If this is the certificates Node */
            if (ELEMENT_CERTIFICATES.equals(myNodeName)) {
                /* Parse the certificates */
                parseCertificates(myNode);
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * Obtain the keyStore.
     *
     * @return the keyStore
     */
    public GordianBaseKeyStore getKeyStore() {
        return theKeyStore;
    }

    /**
     * Obtain the document.
     *
     * @return the document
     */
    public Document getDocument() {
        return theDocument;
    }

    /**
     * build the aliases.
     *
     * @param pAliases the aliases element
     * @throws GordianException on error
     */
    private void buildAliases(final Node pAliases) throws GordianException {
        /* Access the Alias entries */
        for (Entry<String, GordianKeyStoreEntry> myEntry : theKeyStore.getAliasMap().entrySet()) {
            /* Determine the entry type */
            final GordianKeyStoreEntry myElement = myEntry.getValue();
            final GordianStoreEntryType myType = GordianStoreEntryType.determineEntryType(myElement);

            /* Build alias entry */
            final Element myAliasEl = theDocument.createElement(myType.getElementName());
            myAliasEl.setAttribute(ATTR_ALIAS, myEntry.getKey());
            myAliasEl.setAttribute(ATTR_DATE, myElement.getCreationDate().format(DATEFORMATTER));
            pAliases.appendChild(myAliasEl);

            /* Switch on element type */
            switch (myType) {
                case KEY:
                    buildKeyElement(myAliasEl, (GordianKeyStoreKeyElement<?>) myElement);
                    break;
                case KEYSET:
                    buildKeySetElement(myAliasEl, (GordianKeyStoreSetElement) myElement);
                    break;
                case KEYSETLOCK:
                    buildKeySetLockElement(myAliasEl, (GordianKeyStoreLockElement) myElement);
                    break;
                case PRIVATEKEYPAIR:
                    buildPrivateKeyElement(myAliasEl, (GordianKeyStorePairElement) myElement);
                    break;
                case TRUSTEDPAIRCERT:
                default:
                    buildCertificateElement(myAliasEl, (GordianKeyStoreCertificateElement) myElement);
                    break;
            }
        }
    }

    /**
     * build the keySetHash element.
     *
     * @param pNode  the keySetHash node to build
     * @param pEntry the keySetHash entry
     */
    private void buildKeySetLockElement(final Element pNode,
                                        final GordianKeyStoreLockElement pEntry) {
        /* Build lock entry */
        final Element myLockEl = theDocument.createElement(ELEMENT_LOCK);
        pNode.appendChild(myLockEl);
        myLockEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getLock()));
    }

    /**
     * build the keySet element.
     *
     * @param pNode  the keySet node to build
     * @param pEntry the keySet entry
     */
    private void buildKeySetElement(final Element pNode,
                                    final GordianKeyStoreSetElement pEntry) {
        /* Build securedKey entry */
        final Element myKeyEl = theDocument.createElement(ELEMENT_SECUREDKEY);
        pNode.appendChild(myKeyEl);
        myKeyEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getSecuredKeySet()));

        /* Build lock entry */
        final Element myLockEl = theDocument.createElement(ELEMENT_LOCK);
        pNode.appendChild(myLockEl);
        myLockEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getSecuringLockBytes()));
    }

    /**
     * build the key element.
     *
     * @param pNode  the key node to build
     * @param pEntry the key entry
     * @throws GordianException on error
     */
    private void buildKeyElement(final Element pNode,
                                 final GordianKeyStoreKeyElement<?> pEntry) throws GordianException {
        /* Build securedKey entry */
        final Element myKeyEl = theDocument.createElement(ELEMENT_SECUREDKEY);
        pNode.appendChild(myKeyEl);
        myKeyEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getSecuredKey()));

        /* Add the keySpec */
        final GordianKnuthObfuscater myObfuscater = theKeyStore.getFactory().getObfuscater();
        final int myId = myObfuscater.deriveExternalIdFromType(pEntry.getKeyType());
        myKeyEl.setAttribute(ATTR_KEYSPEC, Integer.toString(myId));

        /* Build lock entry */
        final Element myLockEl = theDocument.createElement(ELEMENT_LOCK);
        pNode.appendChild(myLockEl);
        myLockEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getSecuringLockBytes()));
    }

    /**
     * build the privateKey element.
     *
     * @param pNode  the privateKey node to build
     * @param pEntry the privateKey entry
     * @throws GordianException on error
     */
    private void buildPrivateKeyElement(final Element pNode,
                                        final GordianKeyStorePairElement pEntry) throws GordianException {
        /* Build securedKey entry */
        final Element myKeyEl = theDocument.createElement(ELEMENT_SECUREDKEY);
        pNode.appendChild(myKeyEl);
        myKeyEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getSecuredKey()));

        /* Build lock entry */
        final Element myLockEl = theDocument.createElement(ELEMENT_LOCK);
        pNode.appendChild(myLockEl);
        myLockEl.setTextContent(GordianDataConverter.byteArrayToBase64(pEntry.getSecuringLockBytes()));

        /* Build certificates entry */
        final Element myChainEl = theDocument.createElement(ELEMENT_CERTIFICATES);
        pNode.appendChild(myChainEl);

        /* Build the certificate chain */
        for (GordianKeyStoreCertificateKey myCert : pEntry.getCertificateChain()) {
            /* Build certificateKey */
            buildCertificateKey(myChainEl, myCert);
        }
    }

    /**
     * build the certificate element.
     *
     * @param pNode  the certificate node to build
     * @param pEntry the certificate entry
     * @throws GordianException on error
     */
    private void buildCertificateElement(final Element pNode,
                                         final GordianKeyStoreCertificateElement pEntry) throws GordianException {
        /* Build certificateKey */
        buildCertificateKey(pNode, pEntry.getCertificateKey());
    }

    /**
     * build the certificateKey element.
     *
     * @param pNode the holding node
     * @param pKey  the certificate key
     * @throws GordianException on error
     */
    private void buildCertificateKey(final Element pNode,
                                     final GordianKeyStoreCertificateKey pKey) throws GordianException {
        /* Build certificateKey entry */
        final Element myKeyEl = theDocument.createElement(ELEMENT_CERTKEY);
        pNode.appendChild(myKeyEl);

        /* Build subject entry */
        final Element mySubEl = theDocument.createElement(ELEMENT_SUBJECT);
        myKeyEl.appendChild(mySubEl);
        buildCertificateId(mySubEl, pKey.getSubject());

        /* Build issuer entry */
        final Element myIssEl = theDocument.createElement(ELEMENT_ISSUER);
        myKeyEl.appendChild(myIssEl);
        buildCertificateId(myIssEl, pKey.getIssuer());
    }

    /**
     * build the certificateId element.
     *
     * @param pNode the holding node
     * @param pId   the certificate id
     * @throws GordianException on error
     */
    private void buildCertificateId(final Element pNode,
                                    final GordianCertificateId pId) throws GordianException {
        /* protect against exceptions */
        try {
            /* Build Name entry */
            final Element myNameEl = theDocument.createElement(ELEMENT_NAME);
            pNode.appendChild(myNameEl);
            myNameEl.setTextContent(GordianDataConverter.byteArrayToBase64(pId.getName().toASN1Primitive().getEncoded()));

            /* Build id entry */
            final Element myIdEl = theDocument.createElement(ELEMENT_ID);
            pNode.appendChild(myIdEl);
            myIdEl.setTextContent(GordianDataConverter.byteArrayToBase64(pId.getId()));

            /* Handle exceptions */
        } catch (IOException e) {
            throw new GordianIOException("Failed to parse certificate Id", e);
        }
    }

    /**
     * build the certificates.
     *
     * @param pCerts the certificates element
     */
    private void buildCertificates(final Node pCerts) {
        /* Access the Subject MapOfMaps */
        for (Map<GordianCertificateId, GordianCertificate> myMap : theKeyStore.getSubjectMapOfMaps().values()) {
            for (GordianCertificate myCert : myMap.values()) {
                /* Build certificate entry */
                final Element myCertEl = theDocument.createElement(ELEMENT_PAIRCERT);
                pCerts.appendChild(myCertEl);

                /* Build the certificate element */
                myCertEl.setTextContent(GordianDataConverter.byteArrayToBase64(myCert.getEncoded()));
            }
        }
    }

    /**
     * parse the aliases.
     *
     * @param pAliases the aliases element
     * @throws GordianException on error
     */
    private void parseAliases(final Node pAliases) throws GordianException {
        /* Loop through the nodes */
        Node myNode = pAliases.getFirstChild();
        while (myNode != null) {
            /* Determine the entry type */
            final GordianStoreEntryType myType = GordianStoreEntryType.determineEntryType(myNode.getNodeName());

            /* Access alias and creationDate */
            final String myAlias = ((Element) myNode).getAttribute(ATTR_ALIAS);
            final LocalDate myDate = LocalDate.parse(((Element) myNode).getAttribute(ATTR_DATE), DATEFORMATTER);

            /* Switch on element type */
            switch (myType) {
                case KEY:
                    parseKeyElement(myNode, myAlias, myDate);
                    break;
                case KEYSET:
                    parseKeySetElement(myNode, myAlias, myDate);
                    break;
                case KEYSETLOCK:
                    parseKeySetLockElement(myNode, myAlias, myDate);
                    break;
                case PRIVATEKEYPAIR:
                    parsePrivateKeyElement(myNode, myAlias, myDate);
                    break;
                case TRUSTEDPAIRCERT:
                default:
                    parseCertificateElement(myNode, myAlias, myDate);
                    break;
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * parse the keySetLock alias.
     *
     * @param pNode  the node to parse
     * @param pAlias the alias
     * @param pDate  the creation date
     */
    private void parseKeySetLockElement(final Node pNode,
                                        final String pAlias,
                                        final LocalDate pDate) {
        /* Loop through the nodes */
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a lock node */
            if (ELEMENT_LOCK.equals(myNodeName)) {
                /* Obtain the hash and build the entry */
                final byte[] myLock = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
                final GordianKeyStoreLockElement myEntry = new GordianKeyStoreLockElement(myLock, pDate);
                theKeyStore.getAliasMap().put(pAlias, myEntry);
                return;
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * parse the keySet alias.
     *
     * @param pNode  the node to parse
     * @param pAlias the alias
     * @param pDate  the creation date
     * @throws GordianException on error
     */
    private void parseKeySetElement(final Node pNode,
                                    final String pAlias,
                                    final LocalDate pDate) throws GordianException {
        /* Loop through the nodes */
        byte[] mySecuredKey = null;
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a securedKey node */
            if (ELEMENT_SECUREDKEY.equals(myNodeName)) {
                /* Obtain the securedKey */
                mySecuredKey = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
            }

            /* If this is a lock node */
            if (ELEMENT_LOCK.equals(myNodeName)) {
                /* Check for valid xml */
                if (mySecuredKey == null) {
                    throw new GordianDataException(ERROR_BADXML);
                }

                /* Obtain the lock and build the entry */
                final byte[] myLock = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
                final GordianKeyStoreSetElement myEntry = new GordianKeyStoreSetElement(mySecuredKey, myLock, pDate);
                theKeyStore.getAliasMap().put(pAlias, myEntry);
                return;
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * parse the key alias.
     *
     * @param pNode  the node to parse
     * @param pAlias the alias
     * @param pDate  the creation date
     * @throws GordianException on error
     */
    private void parseKeyElement(final Node pNode,
                                 final String pAlias,
                                 final LocalDate pDate) throws GordianException {
        /* Loop through the nodes */
        byte[] mySecuredKey = null;
        GordianKeySpec mySpec = null;
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a securedKey node */
            if (ELEMENT_SECUREDKEY.equals(myNodeName)) {
                /* Obtain the securedKey */
                mySecuredKey = GordianDataConverter.base64ToByteArray(myNode.getTextContent());

                /* Obtain the keySpec */
                final GordianKnuthObfuscater myObfuscater = theKeyStore.getFactory().getObfuscater();
                final String mySpecId = ((Element) myNode).getAttribute(ATTR_KEYSPEC);
                mySpec = (GordianKeySpec) myObfuscater.deriveTypeFromExternalId(Integer.parseInt(mySpecId));
            }

            /* If this is a lock node */
            if (ELEMENT_LOCK.equals(myNodeName)) {
                /* Check for valid xml */
                if (mySecuredKey == null) {
                    throw new GordianDataException(ERROR_BADXML);
                }

                /* Obtain the lock and build the entry */
                final byte[] myLock = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
                final GordianKeyStoreKeyElement<GordianKeySpec> myEntry = new GordianKeyStoreKeyElement<>(mySpec, mySecuredKey, myLock, pDate);
                theKeyStore.getAliasMap().put(pAlias, myEntry);
                return;
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }


    /**
     * parse the key alias.
     *
     * @param pNode  the node to parse
     * @param pAlias the alias
     * @param pDate  the creation date
     * @throws GordianException on error
     */
    private void parsePrivateKeyElement(final Node pNode,
                                        final String pAlias,
                                        final LocalDate pDate) throws GordianException {
        /* Loop through the nodes */
        byte[] mySecuredKey = null;
        byte[] myLock = null;
        final List<GordianKeyStoreCertificateKey> myChain = new ArrayList<>();
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a securedKey node */
            if (ELEMENT_SECUREDKEY.equals(myNodeName)) {
                /* Obtain the securedKey */
                mySecuredKey = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
            }

            /* If this is a lock node */
            if (ELEMENT_LOCK.equals(myNodeName)) {
                /* Obtain the lock */
                myLock = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
            }

            /* If this is a hash node */
            if (ELEMENT_CERTIFICATES.equals(myNodeName)) {
                /* Check for valid xml */
                if (mySecuredKey == null || myLock == null) {
                    throw new GordianDataException(ERROR_BADXML);
                }

                /* Loop through the certificates */
                Node myChild = myNode.getFirstChild();
                while (myChild != null) {
                    /* Access the Node name */
                    final String myName = myChild.getNodeName();

                    /* If this is a certificateKey node */
                    if (ELEMENT_CERTKEY.equals(myName)) {
                        /* Add the certificate to the list */
                        myChain.add(parseCertificateKey(myChild));
                    }

                    /* Move to next node */
                    myChild = myChild.getNextSibling();
                }

                /* build the entry */
                final GordianKeyStorePairElement myEntry = new GordianKeyStorePairElement(mySecuredKey, myLock, myChain, pDate);
                theKeyStore.getAliasMap().put(pAlias, myEntry);
                return;
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * parse the certificate alias.
     *
     * @param pNode  the node to parse
     * @param pAlias the alias
     * @param pDate  the creation date
     * @throws GordianException on error
     */
    private void parseCertificateElement(final Node pNode,
                                         final String pAlias,
                                         final LocalDate pDate) throws GordianException {
        /* Loop through the nodes */
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a certificateKey node */
            if (ELEMENT_CERTKEY.equals(myNodeName)) {
                /* Obtain the key and build the entry */
                final GordianKeyStoreCertificateKey myKey = parseCertificateKey(myNode);
                final GordianKeyStoreCertificateElement myEntry = new GordianKeyStoreCertificateElement(myKey, pDate);
                theKeyStore.getAliasMap().put(pAlias, myEntry);
                return;
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * parse the certificateKey.
     *
     * @param pNode the node to parse
     * @return the key
     * @throws GordianException on error
     */
    private static GordianKeyStoreCertificateKey parseCertificateKey(final Node pNode) throws GordianException {
        /* Loop through the nodes */
        GordianCertificateId mySubject = null;
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a subject node */
            if (ELEMENT_SUBJECT.equals(myNodeName)) {
                /* Parse the subject */
                mySubject = parseCertificateId(myNode);
            }

            /* If this is a issuer node */
            if (ELEMENT_ISSUER.equals(myNodeName)) {
                /* Check for valid xml */
                if (mySubject == null) {
                    throw new GordianDataException(ERROR_BADXML);
                }

                /* Parse the issuer and build the key */
                final GordianCertificateId myIssuer = parseCertificateId(myNode);
                return new GordianKeyStoreCertificateKey(myIssuer, mySubject);
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }

        /* Failed to parse the key */
        throw new GordianDataException("Failed to parse certificate Key");
    }

    /**
     * parse the certificateKey.
     *
     * @param pNode the node to parse
     * @return the key
     * @throws GordianException on error
     */
    private static GordianCertificateId parseCertificateId(final Node pNode) throws GordianException {
        /* Loop through the nodes */
        X500Name myName = null;
        Node myNode = pNode.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a name node */
            if (ELEMENT_NAME.equals(myNodeName)) {
                /* Parse the name */
                final byte[] myNameBytes = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
                myName = X500Name.getInstance(myNameBytes);
            }

            /* If this is a id node */
            if (ELEMENT_ID.equals(myNodeName)) {
                /* Check for valid xml */
                if (myName == null) {
                    throw new GordianDataException(ERROR_BADXML);
                }

                /* Parse the issuer and build the key */
                final byte[] myId = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
                return new GordianCoreCertificateId(myName, myId);
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }

        /* Failed to parse the id */
        throw new GordianDataException("Failed to parse certificate Id");
    }

    /**
     * parse the certificates.
     *
     * @param pCerts the certificates element
     * @throws GordianException on error
     */
    private void parseCertificates(final Node pCerts) throws GordianException {
        /* Loop through the nodes */
        Node myNode = pCerts.getFirstChild();
        while (myNode != null) {
            /* Access the Node name */
            final String myNodeName = myNode.getNodeName();

            /* If this is a pairCertificate node */
            if (ELEMENT_PAIRCERT.equals(myNodeName)) {
                /* Access the encoded certificate */
                final byte[] myEncoded = GordianDataConverter.base64ToByteArray(myNode.getTextContent());
                final GordianCoreCertificate myCert = new GordianCoreCertificate(theKeyStore.getFactory(), myEncoded);
                theKeyStore.storeCertificate(myCert);
            }

            /* Move to next node */
            myNode = myNode.getNextSibling();
        }
    }

    /**
     * The Entry types.
     */
    private enum GordianStoreEntryType {
        /**
         * Certificate.
         */
        TRUSTEDPAIRCERT("TrustedCertificate"),

        /**
         * PrivateKey.
         */
        PRIVATEKEYPAIR("PrivateKeyPair"),

        /**
         * Key.
         */
        KEY("Key"),

        /**
         * KeySet.
         */
        KEYSET("KeySet"),

        /**
         * KeySetLock.
         */
        KEYSETLOCK("KeySetLock");

        /**
         * The Element Name.
         */
        private final String theElement;

        /**
         * Constructor.
         *
         * @param pElement the element name
         */
        GordianStoreEntryType(final String pElement) {
            theElement = pElement;
        }

        /**
         * Obtain the element name.
         *
         * @return the element name
         */
        public String getElementName() {
            return theElement;
        }

        /**
         * Determine entry type.
         *
         * @param pEntry the entry
         * @return the entry type
         */
        public static GordianStoreEntryType determineEntryType(final GordianKeyStoreEntry pEntry) {
            if (pEntry instanceof GordianKeyStoreSetElement) {
                return KEYSET;
            }
            if (pEntry instanceof GordianKeyStoreLockElement) {
                return KEYSETLOCK;
            }
            if (pEntry instanceof GordianKeyStoreKeyElement) {
                return KEY;
            }
            if (pEntry instanceof GordianKeyStorePairElement) {
                return PRIVATEKEYPAIR;
            }
            if (pEntry instanceof GordianKeyStoreCertificateElement) {
                return TRUSTEDPAIRCERT;
            }
            throw new IllegalArgumentException();
        }

        /**
         * Determine entry type.
         *
         * @param pElement the element
         * @return the entry type
         */
        public static GordianStoreEntryType determineEntryType(final String pElement) {
            for (GordianStoreEntryType myType : values()) {
                if (pElement.equals(myType.getElementName())) {
                    return myType;
                }
            }
            throw new IllegalArgumentException();
        }
    }
}