ServerKeyStore.java

/*
** Module   : ServerKeyStore.java
** Abstract : define a container used to export server key store.
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------------Description------------------------------------------
** 001 MAG 20131120 First version.
** 002 CA  20140207 If not explicitly specified, build the server's private key from the directory.
** 003 SBI 20151119 Added the trust server alias.
** 004 CA  20160205 Reworked to initialize the external store only once (and share it accross
**                  copies).  If the store is kept in-directory, this will create a store 
**                  encrypted with unique passwords, for each copy.
** 005 SBI 20171116 Added web store with its store type and web store alias.
** 006 IAS 20200914 Re-work (de)serialization
** 007 CA  20210910 Allow a webstore to be initialized from the directory even if the server's store is
**                  initialized from the file system.
** 008 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 009 GBB 20230825 SecurityManager certificate methods calls updated.
** 010 GBB 20240214 Making method getStore() public.
*/
/*
** 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.main;

import static com.goldencode.util.NativeTypeSerializer.*; 
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.util.logging.*;

import org.apache.commons.codec.digest.DigestUtils;

import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.security.SecurityManager.*;
import com.goldencode.p2j.util.logging.CentralLogger;
import com.goldencode.util.*;

/**
 * A container used to export server key store.
 * The server KeyStore is loaded into this structure and serialized (exported)
 * via a RemoteObject protocol to the spawned process.
 * 
 * @author mag
 *
 */
public class ServerKeyStore
implements Externalizable,
           Cloneable
{
   /** Logger. */
   private static final CentralLogger LOG = CentralLogger.get(ServerKeyStore.class.getName());

   /**
    * A template instance from which copies are made; if the store is in-directory, these copies 
    * will use unique passwords to encrypt the resulted store.  Otherwise, the store file specified
    * at the server's bootstrap config will be shared accross copies.
    */
   private static ServerKeyStore store;
   
   /** Key store file content */
   private byte[] keyStore;

   /** Key store password */
   private String keyStorePassword;

   /** Key store manager password */
   private String keyManagerPassword;

   /** Authorization token */
   private String authorizationToken;
   
   /** Trust server alias */
   private String trustServerAlias;
   
   
   /** Web key store file content */
   private byte[] webKeyStore;

   /** Web key store password */
   private String webKeyStorePassword;

   /** Web key store manager password */
   private String webKeyManagerPassword;
   
   /** Web server alias */
   private String webServerAlias;
   
   /** Worker to resolve the in-directory store. */
   private transient EncryptedKeyStoreFunction encryptedKeyStoreWorker;
   
   /** Flag indicating if the server store is read from the directory. */
   private boolean fromDirectory = false;
   
   /**
    * Constructor. Just creates an empty instance.
    */
   public ServerKeyStore() 
   {
      // no-op
   }

   /**
    * Initializes the template {@link #store}.  Will be called during server initialization, with
    * the proper server context.
    */
   static synchronized void initialize()
   {
      if (store != null)
      {
         return;
      }

      store = new ServerKeyStore();

      BootstrapConfig config = SessionManager.get().config();
      
      SecurityManager sm = SecurityManager.getInstance();
      
      // key store file name
      String keyStoreFile = config.getString("security", "keystore", "filename", null);
      
      if (keyStoreFile != null)
      {
         // read key store content
         File file = new File(keyStoreFile);
         store.keyStore = new byte[(int) file.length()];
         DataInputStream dis;
         try
         {
            dis = new DataInputStream(new FileInputStream(file));
            dis.readFully(store.keyStore);
            dis.close();
            // get passwords
            store.keyStorePassword   = config.getString("access", "password", "keystore", null);
            store.keyManagerPassword = config.getString("access", "password", "keyentry", null);
            
            store.trustServerAlias = getAlias(store.keyStore, store.keyStorePassword.toCharArray());
         }
         catch (IOException e)
         {
            LOG.severe("Cannot load server keystore file.", e);
         }
      }
      else
      {
         // store will be built on each call
         store.trustServerAlias = sm.getServerAlias();
         
         // these remain null and will be populated no each call
         store.keyManagerPassword = null;
         store.keyStorePassword = null;
         store.keyStore = null;
         
         store.fromDirectory = true;
      }
      
      store.encryptedKeyStoreWorker = sm.certificateSm.getEncryptedKeyStore();

      store.webServerAlias = sm.getWebServerAlias();
   }
   
   /**
    * Get a copy of the encrypted server store, based on the shared {@link #store template}. If
    * the store is kept in-directory, the copy will contain a store encrypted with unique 
    * passwords for each copy.
    * 
    * @throws   NullPointerException
    *           If the {@link #store} is <code>null</code>.
    */
   public static synchronized ServerKeyStore getStore()
   {
      if (store == null)
      {
         throw new NullPointerException("The template store was not initialized!");
      }
      
      ServerKeyStore copy = store.clone();

      // generate a random authorization token
      copy.authorizationToken = DigestUtils.md5Hex(UUID.randomUUID().toString());

      String ksPassword = null;
      String kePassword = null;

      if (store.fromDirectory)
      {
         // store is in directory, create and encrypt using random passwords
         
         // build the key store from the directory using the server's alias
         ksPassword = RandomWordGenerator.create();
         kePassword = RandomWordGenerator.create();
         copy.keyStore = store.encryptedKeyStoreWorker.getEncryptedKeyStore(copy.trustServerAlias, 
                                                                            ksPassword, 
                                                                            kePassword,
                                                                            Store.TRUST_STORE);
         if (copy.keyStore == null)
         {
            copy.keyManagerPassword = null;
            copy.keyStorePassword = null;
         }
         else
         {
            copy.keyStorePassword   = ksPassword;
            copy.keyManagerPassword = kePassword;
         }
      }
      
      if (copy.webServerAlias != null)
      {
         ksPassword = RandomWordGenerator.create();
         kePassword = RandomWordGenerator.create();
         copy.webKeyStore = store.encryptedKeyStoreWorker.getEncryptedKeyStore(
                  copy.webServerAlias, ksPassword, kePassword, Store.WEB_STORE);
      }
      else
      {
         copy.webKeyStore = null;
      }
      
      if (copy.webKeyStore == null)
      {
         copy.webKeyManagerPassword = null;
         copy.webKeyStorePassword   = null;
      }
      else
      {
         copy.webKeyStorePassword   = ksPassword;
         copy.webKeyManagerPassword = kePassword;
      }
      
      return copy;
   }
   
   /**
    * Get KeyStore file content.
    * 
    * @return  KeyStore file content.
    */
   public byte[] getKeyStore()
   {
      return keyStore;
   }

   /**
    * Get KeyStore password.
    * 
    * @return  KeyStore password.
    */
   public String getKeyStorePassword()
   {
      return keyStorePassword;
   }
   
   /**
    * Get KeyStore manager password.
    * 
    * @return  KeyStore manager password.
    */
   public String getKeyManagerPassword()
   {
      return keyManagerPassword;
   }

   /**
    * Get the web key store file content.
    * 
    * @return   The web key store file content
    */
   public byte[] getWebKeyStore()
   {
      return webKeyStore;
   }

   /**
    * Get the web key store password.
    * 
    * @return   The web key store password
    */
   public String getWebKeyStorePassword()
   {
      return webKeyStorePassword;
   }

   /**
    * Get the web key store manager password.
    * 
    * @return   The web key store manager password
    */
   public String getWebKeyManagerPassword()
   {
      return webKeyManagerPassword;
   }

   /**
    * Check if the content is valid
    * 
    * @return  <code>true</code> if all parameters are not <code>null</code>
    *          <code>false</code> otherwise.
    */
   public boolean isValid()
   {
      return keyStore != null && keyStorePassword != null && keyManagerPassword != null;
   }

   /**
    * Check if the web store content is valid
    * 
    * @return   <code>true</code> if all parameters are not <code>null</code>
    *           <code>false</code> otherwise.
    */
   public boolean isWebValid()
   {
      return webKeyStore != null && webKeyStorePassword != null && webKeyManagerPassword != null;
   }

   /**
    * Get authorization token.
    * 
    * @return  Return authorization token.
    */
   public String getAuthorizationToken()
   {
      return authorizationToken;
   }

   /**
    * Return the trust server certificate alias.
    * 
    * @return   The trust server alias.
    */
   public String getTrustServerAlias()
   {
      return trustServerAlias;
   }
   
   /**
    * Return the web server certificate alias.
    * 
    * @return   The web server certificate alias
    */
   public String getWebServerAlias()
   {
      return webServerAlias;
   }
   
   /**
    * Create a copy of this instance.
    * 
    * @return   A copy of this instance.
    */
   @Override
   protected ServerKeyStore clone()
   {
      ServerKeyStore copy = new ServerKeyStore();
      copy.keyManagerPassword = this.keyManagerPassword;
      copy.keyStore           = this.keyStore;
      copy.keyStorePassword   = this.keyStorePassword;
      copy.trustServerAlias   = this.trustServerAlias;
      
      copy.webKeyManagerPassword = this.webKeyManagerPassword;
      copy.webKeyStore           = this.webKeyStore;
      copy.webKeyStorePassword   = this.webKeyStorePassword;
      copy.webServerAlias        = this.webServerAlias;
      
      return copy;
   }
   
   /**
    * Retrieves the server alias from its keys store.
    * 
    * @param    store
    *           The bytes serialized keys store (JKS).
    * @param    password
    *           The keys store password.
    * @return   The server alias.
    */
   private static String getAlias(byte[] store, char[] password)
   {
      String alias = null;
      ByteArrayInputStream in = new ByteArrayInputStream(store);
      
      try
      {
         KeyStore ks = KeyStore.getInstance("JKS");
         ks.load(in, password);
         Enumeration<String> e = ks.aliases();
         while (e.hasMoreElements())
         {
            alias = e.nextElement();
            break;
         }
      }
      
      catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e)
      {
         LOG.warning("", e);
      }
      
      return alias;
   }
   
   /**
    * Define the store types.
    */
   public static enum Store
   {
      /** Defines the trust store type */
      TRUST_STORE,
      
      /** Defines the web store type */
      WEB_STORE
   }

   /**
    * Replacement for the default object writing method.
    * 
    * @param    out
    *           The output destination to which fields will be saved.
    *
    * @throws   IOException
    *           In case of I/O errors.
    */
   @Override
   public void writeExternal(ObjectOutput out) throws IOException
   {
      writeByteArray(out, keyStore);
      writeString(out, keyStorePassword);
      writeString(out, keyManagerPassword);
      writeString(out, authorizationToken);
      writeString(out, trustServerAlias);
      writeByteArray(out, webKeyStore);
      writeString(out, webKeyStorePassword);
      writeString(out, webKeyManagerPassword);
      writeString(out, webServerAlias);
   }

   /**
    * Replacement for the default object reading method.
    * 
    * @param    in
    *           Input source from which fields will be restored.
    *
    * @throws   IOException
    *           In case of I/O errors.
    * @throws   ClassNotFoundException
    *           If payload can't be instantiated.
    */
   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
   {
      keyStore = readByteArray(in);
      keyStorePassword = readString(in);
      keyManagerPassword = readString(in);
      authorizationToken = readString(in);
      trustServerAlias = readString(in);
      webKeyStore  = readByteArray(in);
      webKeyStorePassword = readString(in);
      webKeyManagerPassword = readString(in);
      webServerAlias = readString(in);
   }

}