org.guanxi.sp.engine.form.RegisterGuardFormController.java Source code

Java tutorial

Introduction

Here is the source code for org.guanxi.sp.engine.form.RegisterGuardFormController.java

Source

//: "The contents of this file are subject to the Mozilla Public License
//: Version 1.1 (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.mozilla.org/MPL/
//:
//: Software distributed under the License is distributed on an "AS IS"
//: basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//: License for the specific language governing rights and limitations
//: under the License.
//:
//: The Original Code is Guanxi (http://www.guanxi.uhi.ac.uk).
//:
//: The Initial Developer of the Original Code is Alistair Young alistair@codebrane.com
//: All Rights Reserved.
//:

package org.guanxi.sp.engine.form;

import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.openssl.PEMWriter;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlOptions;
import org.guanxi.common.definitions.Guanxi;
import org.guanxi.common.filters.FileName;
import org.guanxi.common.filters.RFC2253;
import org.guanxi.xal.saml_2_0.metadata.*;
import org.guanxi.xal.saml2.metadata.GuanxiGuardServiceDocument;
import org.guanxi.xal.saml2.metadata.GuardRoleDescriptorExtensions;
import org.guanxi.sp.engine.Config;
import org.guanxi.xal.w3.xmldsig.KeyInfoType;
import org.guanxi.xal.w3.xmldsig.X509DataType;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.validation.BindException;
import org.springframework.context.MessageSource;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import javax.security.auth.x500.X500Principal;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.math.BigInteger;
import java.util.*;

/**
 * CA is the Guanxi Service Provider Certification Authority. It's used to
 * generate public/private key pairs for Guards and to digitally sign
 * the corresponding certificate and place it in a keystore.
 * <p />
 * The keystores are stored for later use by the identity masquerading
 * layer to properly authenticate an HTTPS connection to an IdP on
 * behalf of a Guard.
 * <p />
 * The way it's used is a user will access a web form at the Engine and
 * supply required information. The Engine will respond with a visual
 * representation of the signed certificate chain and will also create
 * a new keystore with that chain in it.
 * <p />
 * The idea is that each Guard will have it's own directory in the
 * WEB-INF/metadata/guards directory. The name of the directory will be
 * the Guard's ID. In there we'll store the XML files the Engine needs
 * to work on behalf of the Guard as well as a ZIP file of the Guard's
 * configuration which the owner of the Guard can download.
 *
 * @author Alistair Young (alistair@smo.uhi.ac.uk)
 */
public class RegisterGuardFormController extends SimpleFormController {
    /** Our logger */
    private static final Logger logger = Logger.getLogger(RegisterGuardFormController.class.getName());
    /** Our config object */
    private Config config = null;
    /** Contains the full path to the keystore to be used for signing CSRs */
    private String rootCAKeystore = null;
    /** Password for the signing keystore */
    private String rootCAKeystorePassword = null;
    /** The alias of the certificate entry in the signing keystore */
    private String rootCAKeystoreAlias = null;
    /** The localised messages */
    private MessageSource messageSource = null;
    /** The view to use to display any errors */
    private String errorView = null;
    /** The variable to use in the error view to display the error */
    private String errorViewDisplayVar = null;

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public void setErrorView(String errorView) {
        this.errorView = errorView;
    }

    public void setErrorViewDisplayVar(String errorViewDisplayVar) {
        this.errorViewDisplayVar = errorViewDisplayVar;
    }

    /**
     * Initialises the CA by loading the BouncyCastle Security Provider
     *
     * @throws ServletException if an error occurs
     */
    public void init() throws ServletException {

        // Get the config
        config = (Config) getServletContext().getAttribute(Guanxi.CONTEXT_ATTR_ENGINE_CONFIG);

        /* Where to load the root keystore. This contains the certificate and private key
         * of the Service Provider Engine and is used to sign Guard CSRs.
         */
        rootCAKeystore = config.getKeystore();
        // ... the password for opening the keystore ...
        rootCAKeystorePassword = config.getKeystorePassword();
        // ... the alias of the Engine's certificate in the keystore ...
        rootCAKeystoreAlias = config.getCertificateAlias();
    }

    /**
     * Cleans up the CA by unloading the BouncyCastle Service Provider
     */
    public void destroy() {
    }

    /**
     * Called once, just before the HTML form is displayed for the first time.
     * It's here that we initialise the ControlPanelForm to tell the form what
     * it can do with the job.
     *
     * @param request Standard HttpServletRequest
     * @return Instance of ControlPanelForm
     * @throws ServletException
     */
    protected Object formBackingObject(HttpServletRequest request) throws ServletException {
        return new RegisterGuard();
    }

    /**
     * Handles input from the web form to generate and sign a CSR and store the resulting
     * certificate chain in a keystore.
     *
     * @param request Standard issue HttpServletRequest
     * @param response Standard issue HttpServletResponse
     * @throws ServletException
     */
    @SuppressWarnings("unchecked")
    public ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command,
            BindException errors) throws ServletException {

        RegisterGuard form = (RegisterGuard) command;
        String escapedGuardID = FileName.encode(form.getGuardid().toLowerCase());

        // Adjust the metadata directory for the new Guard
        String metadataDirectory = config.getGuardsMetadataDirectory() + File.separator + escapedGuardID;

        // Create the new Guard metadata directory
        if (!createGuardMetadataDirectory(metadataDirectory)) {
            ModelAndView mAndV = new ModelAndView();
            mAndV.setViewName(errorView);
            mAndV.getModel().put(errorViewDisplayVar,
                    messageSource.getMessage("register.guard.error.create.dir", null, request.getLocale()));
            return mAndV;
        }

        // Build an X509 name
        String x509DN = "CN=" + RFC2253.encode(form.getGuardid());
        x509DN += ",OU=" + RFC2253.encode(form.getOrgunit());
        x509DN += ",O=" + RFC2253.encode(form.getOrg());
        x509DN += ",L=" + RFC2253.encode(form.getCity());
        x509DN += ",ST=" + RFC2253.encode(form.getLocality());
        x509DN += ",C=" + RFC2253.encode(form.getCountry());

        // Generate a CSR and sign it
        CABean caBean = createSignedCertificateChain(x509DN, config.getKeyType());

        // Use a random number for the keystore password
        Random randomNumberGenerator = new Random();
        String keystorePassword = String.valueOf(randomNumberGenerator.nextInt());

        /* Store the certificate chain in a keystore. The name of the keystore must
         * correspond to the ID of the Guard that will use it. i.e. when the Engine
         * masquerades for the Guard over the SSL connection to the IdP, it must
         * know where the Guard's keystore is.
         * To this end the keystore will be the lowercase equivalent of the Guard ID
         * and it's certificate alias will be the same.
         */
        String guardKeystore = metadataDirectory + File.separator + escapedGuardID + ".jks";
        createKeystoreWithChain(guardKeystore, form.getGuardid().toLowerCase(), keystorePassword, caBean);

        createGuardMetadataFile(metadataDirectory, guardKeystore, keystorePassword, form, caBean);

        // Load the new Guard so the main Engine can use it
        loadGuardMetadata(metadataDirectory + File.separator + escapedGuardID + ".xml");

        // Show the certificate chain to the user
        displayChain(request, response, caBean);

        return new ModelAndView(getSuccessView(), errors.getModel());
    }

    /**
     * Creates an authenticated certificate chain for the specified X509 name
     *
     * @param x509DN X509 name to for which to create a certificate chain
     * @param keyType The type of the key, e.g. "RSA", "DSA"
     * @return Returns a CABean instance encapsulating certificate chain and key information
     * or null if an error occurred
     */
    private CABean createSignedCertificateChain(String x509DN, String keyType) {
        try {
            // Create a public/private keypair...
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyType);
            keyGen.initialize(1024, new SecureRandom());
            KeyPair keypair = keyGen.generateKeyPair();
            PrivateKey clientPrivateKey = keypair.getPrivate();
            PublicKey clientPublicKey = keypair.getPublic();

            // ...and a CSR from them...
            PKCS10CertificationRequest csr = generateRequest(x509DN, clientPublicKey, clientPrivateKey, keyType);

            // ...sign it
            KeyStore rootKS = loadRootKeyStore();
            X509Certificate rootCert = (X509Certificate) rootKS.getCertificate(rootCAKeystoreAlias);
            if (rootCert == null) {
                logger.error("Can't get root certificate from CA keystore");
                return null;
            }
            PrivateKey rootPrivKey = (PrivateKey) rootKS.getKey(rootCAKeystoreAlias,
                    rootCAKeystorePassword.toCharArray());
            X509Certificate[] signedChain = createSignedCert(rootCert, rootPrivKey, csr, keyType);

            //...package up the result...
            CABean caBean = new CABean();
            caBean.setChain(signedChain);
            caBean.setCSRPrivateKey(clientPrivateKey);
            caBean.setSubjectDN(x509DN);

            // ...and send it back
            return caBean;
        } catch (Exception e) {
            logger.error(e);
            return null;
        }
    }

    /**
     * Creates a JKS keystore and imports the specified certificate chain
     *
     * @param ksFileName The full path/name of the keystore to create
     * @param alias The alias for the certificate entry to create
     * @param password The password for the keystore and also the private key
     * @param caBean CABean instance from a call to createSignedCertificateChain
     */
    private void createKeystoreWithChain(String ksFileName, String alias, String password, CABean caBean) {
        try {
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(null, null);
            ks.setKeyEntry(alias, caBean.getCSRPrivateKey(), password.toCharArray(), caBean.getChain());
            ks.store(new FileOutputStream(ksFileName), password.toCharArray());
        } catch (Exception e) {
            logger.error(e);
        }
    }

    /**
     * Generates a Certificate Signing Request (CSR) for an entity
     *
     * @param x509DN The X509 name of the entity
     * @param pubkey The public key of the entity
     * @param privkey The private key of the entity
     * @param keyType The type of the key, e.g. "RSA", "DSA"
     * @return A PKCS10CertificationRequest or null if an error occurred
     */
    private PKCS10CertificationRequest generateRequest(String x509DN, PublicKey pubkey, PrivateKey privkey,
            String keyType) {
        try {
            if (keyType.toLowerCase().equals("rsa")) {
                return new PKCS10CertificationRequest("SHA256withRSA", new X500Principal(x509DN), pubkey, null,
                        privkey);
            } else if (keyType.toLowerCase().equals("dsa")) {
                return new PKCS10CertificationRequest("DSAWithSHA1", new X500Principal(x509DN), pubkey, null,
                        privkey);
            } else {
                logger.error("Unrecognised key type : " + keyType);
                return null;
            }

        } catch (Exception e) {
            logger.error(e);
            return null;
        }
    }

    /**
     * Handles the nitty gritty of signing a CSR
     *
     * @param rootCert The certificate of the root authority who will vouch for the entity
     * @param rootPrivKey The private key of the root authority who will vouch for the entity
     * @param csr The entitie's CSR
     * @param keyType The type of the key, e.g. "RSA", "DSA"
     * @return A certificate chain as an array of X509Certificate instances or null if an
     * error occurred
     */
    private X509Certificate[] createSignedCert(X509Certificate rootCert, PrivateKey rootPrivKey,
            PKCS10CertificationRequest csr, String keyType) {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        try {
            Date validFrom = new Date();
            validFrom.setTime(validFrom.getTime() - (10 * 60 * 1000));
            Date validTo = new Date();
            validTo.setTime(validTo.getTime() + (20 * (24 * 60 * 60 * 1000)));

            certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
            certGen.setIssuerDN(rootCert.getSubjectX500Principal());
            certGen.setNotBefore(validFrom);
            certGen.setNotAfter(validTo);
            certGen.setSubjectDN(csr.getCertificationRequestInfo().getSubject());
            certGen.setPublicKey(csr.getPublicKey("BC"));

            if (keyType.toLowerCase().equals("rsa"))
                certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
            if (keyType.toLowerCase().equals("dsa"))
                certGen.setSignatureAlgorithm("DSAWithSHA1");

            certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                    new AuthorityKeyIdentifierStructure(rootCert));
            certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
                    new SubjectKeyIdentifierStructure(csr.getPublicKey("BC")));
            certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
            certGen.addExtension(X509Extensions.KeyUsage, true,
                    new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
            certGen.addExtension(X509Extensions.ExtendedKeyUsage, true,
                    new ExtendedKeyUsage(KeyPurposeId.id_kp_clientAuth));

            X509Certificate issuedCert = certGen.generate(rootPrivKey, "BC");
            return new X509Certificate[] { issuedCert, rootCert };
        } catch (Exception e) {
            logger.error(e);
            return null;
        }
    }

    /**
     * Loads the root authority's keystore containing it's private key and
     * public key certificate
     *
     * @return A KeyStore instance or null if an error occurred
     */
    private KeyStore loadRootKeyStore() {
        try {
            KeyStore ks = null;
            ks = KeyStore.getInstance("jks");

            FileInputStream fis = null;
            //fis = new FileInputStream(new File("/Users/alistair/dev/incubator/SSL/keys/guanxi.uhi.ac.uk.jks"));
            fis = new FileInputStream(rootCAKeystore);

            ks.load(fis, rootCAKeystorePassword.toCharArray());

            return ks;
        } catch (Exception e) {
            logger.error(e);
            return null;
        }
    }

    /**
     * Output a visual representation of a certificate chain
     *
     * @param request Standard issue HttpServletRequest
     * @param response Standard issue HttpServletResponse
     * @param caBean CABean instance from a call to createSignedCertificateChain
     */
    private void displayChain(HttpServletRequest request, HttpServletResponse response, CABean caBean) {
        try {
            PEMWriter pemWriter = new PEMWriter(response.getWriter());
            X509Certificate[] certs = caBean.getChain();
            for (int count = 0; count < certs.length; count++) {
                pemWriter.writeObject(certs[count]);
            }
            pemWriter.close();
        } catch (Exception e) {
            logger.error(e);
            try {
                request.setAttribute("ERROR_ID", "ID_NEED_ALL_PARAMETERS");
                request.setAttribute("ERROR_MESSAGE", e.getMessage());
                request.getRequestDispatcher("/guanxi_sp/sp_error.jsp").forward(request, response);
            } catch (Exception ex) {
                logger.error(e);
            }
        }
    }

    /**
     * Creates the directory to hold a new Guard's metadata
     *
     * @param dir Full path of the directory to create
     * @return true on success, otherwise false
     */
    public boolean createGuardMetadataDirectory(String dir) {
        return new File(dir).mkdir();
    }

    /**
     * Creates a SAML2 metadata file for the Guard based on its settings in the form
     *
     * @param guardDir Where to create the metadata file
     * @param keystore The name of the Guard's keystore that has been created
     * @param keystorePassword The password for the Guard's keystore
     * @param form The form object describing the Guard
     * @param caBean The bean encapsulating certificate information
     */
    private void createGuardMetadataFile(String guardDir, String keystore, String keystorePassword,
            RegisterGuard form, CABean caBean) {
        EntityDescriptorDocument entityDoc = EntityDescriptorDocument.Factory.newInstance();
        EntityDescriptorType entityDescriptor = entityDoc.addNewEntityDescriptor();

        entityDescriptor.setEntityID(form.getGuardid().toLowerCase());

        // <EntityDescriptor>/<RoleDescriptor>
        RoleDescriptorDocument roleDoc = RoleDescriptorDocument.Factory.newInstance();
        RoleDescriptorType role = roleDoc.addNewRoleDescriptor();
        // <EntityDescriptor>/<RoleDescriptor>/<Extensions>/<GuanxiGuardService>
        GuanxiGuardServiceDocument guardService = GuanxiGuardServiceDocument.Factory.newInstance();
        GuardRoleDescriptorExtensions guardExt = guardService.addNewGuanxiGuardService();
        String appURL = form.getScheme() + "://" + form.getUrl();
        if (!form.getPort().equals("80"))
            appURL += ":" + form.getPort();
        appURL += "/" + form.getApplicationName();
        guardExt.setVerifierURL(appURL + "/guard.sessionVerifier");
        guardExt.setAttributeConsumerServiceURL(appURL + "/guard.guanxiGuardACS");
        guardExt.setPodderURL(appURL + "/guard.guanxiGuardPodder");
        guardExt.setKeystore(keystore);
        guardExt.setKeystorePassword(keystorePassword);
        ExtensionsType ext = ExtensionsType.Factory.newInstance();
        ext.getDomNode().appendChild(ext.getDomNode().getOwnerDocument().importNode(guardExt.getDomNode(), true));
        role.setExtensions(ext);

        // Add the GuanxiGuardDescriptor to the EntityDescriptor
        entityDescriptor.setRoleDescriptorArray(new RoleDescriptorType[] { role });

        // <EntityDescriptor>/<Organization>
        OrganizationType organisation = entityDescriptor.addNewOrganization();
        // <EntityDescriptor>/<Organization>/<OrganizationName>
        LocalizedNameType orgName = LocalizedNameType.Factory.newInstance();
        orgName.setLang("en");
        orgName.setStringValue(form.getOrg());
        organisation.setOrganizationDisplayNameArray(new LocalizedNameType[] { orgName });
        // <EntityDescriptor>/<Organization>/<OrganizationDisplayName>
        LocalizedNameType displayName = LocalizedNameType.Factory.newInstance();
        displayName.setLang("en");
        displayName.setStringValue(form.getOrg());
        organisation.setOrganizationNameArray(new LocalizedNameType[] { displayName });
        // <EntityDescriptor>/<Organization>/<OrganizationURL>
        LocalizedURIType orgURL = LocalizedURIType.Factory.newInstance();
        orgURL.setLang("en");
        orgURL.setStringValue(form.getOrg());
        organisation.setOrganizationURLArray(new LocalizedURIType[] { orgURL });

        // <EntityDescriptor>/<SPSSODescriptor>
        SPSSODescriptorType spSSO = entityDescriptor.addNewSPSSODescriptor();

        // <EntityDescriptor>/SPSSODescriptor>/<KeyDescriptor> : signing
        KeyDescriptorType keyDescriptor = spSSO.addNewKeyDescriptor();
        keyDescriptor.setUse(KeyTypes.SIGNING);
        KeyInfoType keyInfo = keyDescriptor.addNewKeyInfo();
        X509DataType x509Data = keyInfo.addNewX509Data();

        StringWriter sw = new StringWriter();
        PEMWriter pemWriter = new PEMWriter(sw);
        String x509 = null;

        try {
            pemWriter.writeObject(caBean.getSubjectCertificate());
            pemWriter.close();
            x509 = sw.toString();
            x509 = x509.replaceAll("-----BEGIN CERTIFICATE-----", "");
            x509 = x509.replaceAll("-----END CERTIFICATE-----", "");
            x509Data.addNewX509Certificate().setStringValue(x509);
        } catch (Exception e) {
            logger.error("Error creating Guard signing certificate metadata", e);
        }
        // <EntityDescriptor>/SPSSODescriptor>/<KeyDescriptor> : encryption
        keyDescriptor = spSSO.addNewKeyDescriptor();
        keyDescriptor.setUse(KeyTypes.ENCRYPTION);
        keyInfo = keyDescriptor.addNewKeyInfo();
        x509Data = keyInfo.addNewX509Data();
        try {
            x509Data.addNewX509Certificate().setStringValue(x509);
        } catch (Exception e) {
            logger.error("Error creating Guard encryption certificate metadata", e);
        }

        // <EntityDescriptor>/<AssertionConsumerService>
        IndexedEndpointType acs = spSSO.addNewAssertionConsumerService();
        acs.setIndex(0);
        acs.setBinding("urn:oasis:names:tc:SAML:1.0:profiles:browser-post");
        acs.setLocation("YOUR_ENGINE_URL/samlengine/shibb/acs");
        acs = spSSO.addNewAssertionConsumerService();
        acs.setIndex(1);
        acs.setBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
        acs.setLocation("YOUR_ENGINE_URL/samlengine/s2/wbsso/acs");
        acs = spSSO.addNewAssertionConsumerService();
        acs.setIndex(2);
        acs.setBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
        acs.setLocation("YOUR_ENGINE_URL/samlengine/s2/wbsso/acs");

        // <EntityDescriptor>/<ContactPerson>
        ContactType contact = entityDescriptor.addNewContactPerson();
        contact.setContactType(ContactTypeType.TECHNICAL);
        contact.setCompany(form.getContactCompany());
        contact.setGivenName(form.getContactGivenName());
        contact.setSurName(form.getContactSurname());
        contact.setEmailAddressArray(new String[] { form.getContactEmail() });
        contact.setTelephoneNumberArray(new String[] { form.getContactPhone() });

        HashMap<String, String> ns = new HashMap<String, String>();
        ns.put("urn:guanxi:metadata", "gxmeta");

        XmlOptions xmlOptions = new XmlOptions();
        xmlOptions.setSavePrettyPrint();
        xmlOptions.setSavePrettyPrintIndent(2);
        xmlOptions.setUseDefaultNamespace();
        xmlOptions.setSaveAggressiveNamespaces();
        xmlOptions.setSaveSuggestedPrefixes(ns);
        xmlOptions.setSaveNamespacesFirst();
        try {
            entityDoc.save(
                    new File(guardDir + File.separator + FileName.encode(form.getGuardid().toLowerCase()) + ".xml"),
                    xmlOptions);
        } catch (Exception e) {
            logger.error(e);
        }
    }

    /**
     * Loads a new Guard's metadata and adds it to the list of those Guards
     * already loaded by the main Engine.
     *
     * @param guardMetadataFile Full path and name of the Guard's metadata file
     * @return true if loaded otherwise false if an error occurred
     */
    private boolean loadGuardMetadata(String guardMetadataFile) {
        try {
            EntityDescriptorDocument edDoc = EntityDescriptorDocument.Factory.parse(new File(guardMetadataFile));
            EntityDescriptorType entityDescriptor = edDoc.getEntityDescriptor();

            // Bung the Guard's SAML2 EntityDescriptor in the session under the Guard's entityID
            getServletContext().setAttribute(entityDescriptor.getEntityID(), entityDescriptor);

            logger.info("CA loaded new Guard : " + entityDescriptor.getEntityID());

            return true;
        } catch (Exception e) {
            logger.error("CA could not load new Guard", e);
            return false;
        }
    }

    /**
     * Encapsulates a certificate chain information. The class carries enough information
     * to be passed between specialised units to form a pipeline.
     */
    class CABean {
        /** An authenticated certificate chain */
        X509Certificate[] chain = null;
        /** The private key of the signing authority that produced the certificate chain */
        PrivateKey csrPrivateKey = null;

        /**
         * Sets the subject DN
         * @param subjectDN the dn of the subject of the chain
         */
        public void setSubjectDN(String subjectDN) {
            this.subjectDN = subjectDN;
        }

        String subjectDN = null;

        /**
         * Store the certificate chain in the bean
         * @param chain X509 certificate chain
         */
        public void setChain(X509Certificate[] chain) {
            this.chain = chain;
        }

        /**
         * Retrieve the certificate chain from the bean
         *
         * @return An array of X509Certificate instances
         */
        public X509Certificate[] getChain() {
            return chain;
        }

        /**
         * Returns the X509 certificate of the subject of the chain
         *
         * @return X509 certificate of the subject of the chain or null
         */
        public X509Certificate getSubjectCertificate() {
            String subjectCN = subjectDN.split(",")[0].split("=")[1];
            for (X509Certificate x509 : chain) {
                String[] parts = x509.getSubjectDN().getName().split(",");
                String x509CN = parts[parts.length - 1].split("=")[1];
                if (x509CN.equals(subjectCN)) {
                    return x509;
                }
            }
            return null;
        }

        /**
         * Store the signing authority's private key in the bean. This must be the
         * private key that signed the chain that's stored in the bean.
         *
         * @param csrPrivateKey PrivateKey of the signing authority
         */
        public void setCSRPrivateKey(PrivateKey csrPrivateKey) {
            this.csrPrivateKey = csrPrivateKey;
        }

        /**
         * Retrieve the private key of the signing authority. This will be the private
         * key of the authority that signed the chain that's stored in the bean
         *
         * @return PrivateKey of the signing authority
         */
        public PrivateKey getCSRPrivateKey() {
            return csrPrivateKey;
        }
    }
}