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);
      }
   }
}