# WebCertificates.java

    /*
    ** Module   : WebCertificates.java
    ** Abstract : Defines web certificates configuration manager.
    **
    ** Copyright (c) 2017, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 SBI 20171116 Added webCertificates configuration to the directory.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.security;

    import java.io.*;
    import java.security.*;
    import java.security.cert.*;
    import java.util.*;

    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.directory.*;
    import com.goldencode.p2j.security.SSLCertFactory.*;

    /**
     * Defines web certificates configuration manager.
     */
    public class WebCertificates
    {
       /** The root node for certificates */
       private static final String CERTS_ROOT = "/security/certificates";

       /** The root node for web certificates */
       private static final String WEB_ROOT_CERTS = "/security/certificates/webCertificates";

       /** Certificates builder */
       private final SSLCertFactory factory;

       /** X.509 certificate factory */
       private final CertificateFactory cf;

       /**
        * The default constructor
        *
        * @throws   SSLCertGenException
        *           If BCCertFactory can't be instantiated.
        */
       public WebCertificates()
       throws SSLCertGenException
       {
          this(new BCCertFactory());
       }

       /**
        * Creates parameterized web certificates reader.
        *
        * @param    factory
        *           The SSL certificates utility
        *
        * @throws   SSLCertGenException
        *           If CertificateFactory can't be instantiated.
        */
       public WebCertificates(SSLCertFactory factory)
       throws SSLCertGenException
       {
          this.factory = factory;

          try
          {
             this.cf = CertificateFactory.getInstance("X.509");
          }
          catch (CertificateException ex)
          {
             throw new SSLCertGenException(ex);
          }
       }

       /**
        * Reads web alias for the given server.
        *
        * @param    ds
        *           The directory service
        * @param    serverId
        *           The server identifier
        *
        * @return   The web alias
        */
       public String getWebAlias(DirectoryService ds, String serverId)
       {
          StringBuilder nodePath = new StringBuilder(WEB_ROOT_CERTS);
          nodePath.append("/").append(serverId).append("/alias");

          return ds.getNodeString(nodePath.toString(), "value");
       }

       /**
        * Reads the password that is used to encrypt the web private certificate for the given server.
        *
        * @param    ds
        *           The directory service
        * @param    serverId
        *           The server identifier
        *
        * @return   The private key password
        */
       public String getPrivateKeyPassword(DirectoryService ds, String serverId)
       {
          StringBuilder nodePath = new StringBuilder(WEB_ROOT_CERTS);
          nodePath.append("/").append(serverId).append("/key/key-password");

          byte[] passwordData = ds.getNodeByteArray(nodePath.toString(), "value");

          if (passwordData == null || passwordData.length == 0)
          {
             return null;
          }

          return new String(passwordData);
       }

       /**
        * Reads web certificates from the directory for the given server.
        *
        * @param    ds
        *           The directory service
        * @param    serverId
        *           The server identifier
        *
        * @return   The certificate suite
        *
        * @throws   SSLCertGenException
        *           If web certificates can't be read due to the current settings
        */
       public CertificateSuite getWebCertificates(DirectoryService ds, String serverId)
       throws SSLCertGenException
       {
          try
          {
             StringBuilder nodePath = new StringBuilder(WEB_ROOT_CERTS);
             nodePath.append("/").append(serverId);

             String webCertRootPath = nodePath.toString();

             nodePath.append("/alias");

             String webAlias = ds.getNodeString(nodePath.toString(), "value");

             if (webAlias == null || webAlias.trim().isEmpty())
             {
                throw new ConfigurationException("Missed web alias - " + nodePath);
             }

             nodePath.setLength(0);
             nodePath.append(webCertRootPath).append("/certificate");

             byte[] data = ds.getNodeByteArray(nodePath.toString(), "value");

             if (data == null || data.length == 0)
             {
                throw new ConfigurationException("Missed certificate - " + nodePath);
             }

             ByteArrayInputStream bis  = new ByteArrayInputStream(data);

             X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);

             List<X509Certificate> chainList = new LinkedList<X509Certificate>();

             nodePath.setLength(0);
             nodePath.append(webCertRootPath).append("/chain");

             String[] nodes = ds.enumerateNodes(nodePath.toString());

             if (nodes == null || nodes.length == 0)
             {
                throw new ConfigurationException("Missed certificates chain - " + nodePath);
             }

             for(String node : nodes)
             {
                String fullId = nodePath.toString() + "/" + node;
                data = ds.getNodeByteArray(fullId, "value");
                if (data == null || data.length == 0)
                {
                   throw new ConfigurationException("Missed certificate - " + fullId);
                }
                bis  = new ByteArrayInputStream(data);
                chainList.add((X509Certificate)cf.generateCertificate(bis));
             }

             nodePath.setLength(0);
             nodePath.append(webCertRootPath).append("/key");

             if (ds.getNodeClass(nodePath.toString()) == null)
             {
                throw new ConfigurationException("Missed private key - " + nodePath);
             }

             String kpId = nodePath.toString()  + "/key-password";
             String keId = nodePath.toString()  + "/key-entry";

             byte[] kpData = ds.getNodeByteArray(kpId, "value");

             if (kpData == null)
             {
                throw new ConfigurationException("Missed private key password - " + kpId);
             }

             String keyEntryPassword = new String(kpData);

             byte[] encrypted = ds.getNodeByteArray(keId, "value");

             if (encrypted == null)
             {
                throw new ConfigurationException("Missed private key encrypted data - " + keId);
             }

             PrivateKey pkey = factory.decryptPrivateKey(encrypted, keyEntryPassword);

             CertificateSuite suite = new CertificateSuite();

             suite.privateKey = pkey;
             suite.publicKey  = cert;
             suite.chain      = chainList.toArray(new X509Certificate[chainList.size()]);

             return suite;
          }
          catch(Throwable ex)
          {
             throw new SSLCertGenException("webCertificates is corrupted!", ex);
          }
       }

       /**
        * Writes the given certificate suite into the directory located by
        * /security/certificates/webCertificates/serverId.
        *
        * @param    suite
        *           The given certificate suite
        * @param    ds
        *           The directory service
        * @param    serverId
        *           The server identifier
        * @param    alias
        *           The web certificate alias
        * @param    keyPassword
        *           The private key password
        *
        * @throws   SSLCertGenException
        *           If it is failed.
        */
       public void setCertificateSuite(CertificateSuite suite,
                                       DirectoryService ds,
                                       String           serverId,
                                       String           alias,
                                       String           keyPassword)
       throws SSLCertGenException
       {
          try
          {
             StringBuilder nodePath = new StringBuilder(WEB_ROOT_CERTS);

             if (ds.getNodeClass(nodePath.toString()) == null)
             {
                NodeHelper.openBatch(ds, CERTS_ROOT);
                NodeHelper.addNode(ds, nodePath.toString(), "container", (Attribute[]) null);
                NodeHelper.closeBatch(ds, CERTS_ROOT);
             }

             NodeHelper.openBatch(ds, WEB_ROOT_CERTS);

             nodePath.append("/").append(serverId);

             String webCertRootPath = nodePath.toString();

             if (ds.getNodeClass(webCertRootPath) == null)
             {
                NodeHelper.addNode(ds, webCertRootPath, "container", (Attribute[]) null);
             }

             nodePath.append("/alias");

             NodeHelper.deleteNode(ds, nodePath.toString());

             NodeHelper.addNode(ds, nodePath.toString(), "string", alias);

             nodePath.setLength(0);
             nodePath.append(webCertRootPath);

             nodePath.append("/certificate");
             NodeHelper.deleteNode(ds, nodePath.toString());
             NodeHelper.addNode(ds, nodePath.toString(), "bytes", suite.publicKey.getEncoded());

             nodePath.setLength(0);
             nodePath.append(webCertRootPath);

             nodePath.append("/chain");
             NodeHelper.deleteNode(ds, nodePath.toString());
             NodeHelper.addNode(ds, nodePath.toString(), "container", (Attribute[]) null);

             X509Certificate[] chain = suite.chain;

             StringBuilder aliasBuffer = new StringBuilder(alias);

             for(int i = 0; i < chain.length; i++)
             {
                aliasBuffer.append("-").append(i + 1);
                String chainNodeId = nodePath.toString() + "/" + aliasBuffer.toString();
                NodeHelper.addNode(ds, chainNodeId, "bytes", chain[i].getEncoded());
             }

             nodePath.setLength(0);
             nodePath.append(webCertRootPath);

             nodePath.append("/key");
             NodeHelper.deleteNode(ds, nodePath.toString());
             NodeHelper.addNode(ds, nodePath.toString(), "container", (Attribute[]) null);

             NodeHelper.addNode(ds, nodePath.toString() + "/key-password", "bytes", keyPassword.getBytes());
             byte[] encoded = factory.encryptPrivateKey(suite.privateKey, keyPassword);
             NodeHelper.addNode(ds, nodePath.toString() + "/key-entry", "bytes", encoded);

             NodeHelper.closeBatch(ds, WEB_ROOT_CERTS);
          }
          catch (Throwable ex)
          {
             throw new SSLCertGenException("writeCertificateSuite is failed", ex);
          }
       }
    }