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

    }