# TransportSecurity.java

    /*
    ** Module   : TransportSecurity.java
    ** Abstract : handles JSSE setup of a custom KeyManager and TrustManager
    **
    ** Copyright (c) 2005-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
    ** 001 NVS 20050208   @19718 Created the file and completed the initial
    **                           implementation. Certificates that should be
    **                           located in the directory, are temporarily
    **                           loaded from a file based truststore.
    ** 002 NVS 20050303   @20254 Added new method getTrustStore. Needed in
    **                           SecurityManager class, authenticateClient
    **                           method to verify the server's certificate in
    **                           case DNS based name verification fails.
    **                           Server side trust store is now built in
    **                           SecurityCache class.
    ** 003 NVS 20050303   @24615 Implemented truly anonymous (no certificate)
    **                           connections support with the server's
    **                           certificate validation now optional.
    ** 004 GES 20061221   @31801 Refactoring and massive code cleanup. Removed
    **                           dependencies on AuthUIHelper.
    ** 005 JJC 20090208   @41407 Added a special trust manager for the use
    **                           in the admin applet exclusively -
    **                           ZeroActionTrustManager.
    ** 006 LMR 20101209          TransportSecurity's constructor was modified
    **                           to bypass all logic related to keystores and
    **                           certificates if net/connection/secure is false
    **                           in the BootstrapConfig (if not present it
    **                           defaults to false).
    ** 007 GES 20111003          Make the ZeroActionTrustManager more easily
    **                           accessible via a specific config value.
    ** 008 CA  20140206          Changed to allow the key and trust stores to be read from the
    **                           bootstrap config (needed by launching the processes via the spawner
    **                           and scheduler infrastructure).
    ** 009 ECF 20150828          Enable Java 8.
    ** 010 CA  20180608          Extracted the key store loading into a separate API - loadKeyStore.
    **                           This loads just the certificate, not the actual private keys.
    ** 011 GBB 20230825          JavaDoc updated.
    */
    /*
    ** 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.util.*;
    import java.security.*;
    import java.security.cert.*;
    import javax.net.ssl.*;
    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.directory.Base64;

    /**
     * Handles the setup of the JSSE environment to use a custom
     * <code>KeyManager</code> and <code>TrustManager</code>.
     * <p>
     * Instances are obtained using getTransportSecurity method of {@link SecurityManager#certificateSm}.
     */
    public class TransportSecurity
    {
       /** Custom key manager. */
       private KeyManager[] km = null;

       /** Custom trust manager. */
       private TrustManager[] tm = null;

       /** In-memory trust store. */
       private KeyStore trustStore = null;

       /** In-memory key store. */
       private KeyStore keyStore = null;

       /**
        * Uses the truststore and keystore resources defined in a given bootstrap
        * configuration to create in-memory <code>KeyStore</code> objects holding
        * certificates (trust store) and the private key (key store). Bootstrap
        * configuration flag <code>net/connection/secure</code> needs to be
        * enabled for this setup to take place.
        *
        * <p>
        * On the client, both the key store and the trust store may be either
        * specified or omitted. The interpretation of these combinations is as
        * follows:
        * <p>
        * <ul>
        *   <li>key store, trust store: fully certified connection where the
        *       client and server are both certified/verified using the given
        *       resources
        *   <li>key store only: client certified, server's certificate is
        *       verified with the J2SE standard trust store
        *   <li>trust store only: anonymous connection, server's certificate
        *       is verified using the specified trust store
        *   <li>no key store, no trust store: anonymous connection, optionally
        *       the server's certificate is verified using the J2SE standard
        *       trust store (if security:certificate:validate=true)
        * </ul>
        * <p>
        * An empty truststore on the server is allowed.  This means no client TLS
        * authentication will be in effect.
        * <p>
        * Key stores, if specified, have to contain one or more key entries.
        * <p>
        * Key entries in use can be specified by processalias or useralias keyword
        * on the client, and alias keyword on the server.
        * <p>
        * Valid combinations on the client are:
        * <ul>
        *   <li>processalias is coded and there is matching alias for a key entry;
        *   <li>useralias is coded and there is matching alias for a key entry;
        *   <li>nothing is coded and there is exactly one key entry
        * </ul>
        * Valid combinations on the server are:
        * <ul>
        *   <li>alias is coded and there is matching alias for a key entry;
        *   <li>nothing is coded and there is exactly one key entry
        * </ul>
        *
        * @param    bc
        *           Security related configuration information.
        * @param    srv
        *           <code>true</code> if this is the server.
        * @param    trust
        *           On the server, it is a trust store loaded with certificates,
        *           built from the directory.  <code>null</code> otherwise.
        */
       TransportSecurity(BootstrapConfig bc, boolean srv, KeyStore trust)
       throws NoSuchAlgorithmException,
              KeyStoreException,
              UnrecoverableKeyException,
              ConfigurationException
       {
          // bypass all keystore/truststore setup in the insecure case:
          boolean secure = bc.getBoolean("net", "connection", "secure", false);
          if (!secure)
          {
             return;
          }

          // obtain the keystore
          keyStore = loadKeyStore(bc, srv);
          boolean loaded = (keyStore != null);
          if (!loaded)
          {
             keyStore = KeyStore.getInstance("JKS");

             // no keystore file is specified, the keystore will be empty
             // but only on the client

             if (srv)
             {
                throw new ConfigurationException("misconfigured keystore: file " +
                                                 "must be specified");
             }

             try
             {
                keyStore.load(null, null);
             }

             catch (Exception e)
             {
                // should not be here
             }
          }

          // create a key manager
          KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
          String apw = bc.getString("access", "password", "keyentry", null);
          char[] kspw = (!loaded || apw == null) ? null : apw.toCharArray();
          kmf.init(keyStore, kspw);
          km = kmf.getKeyManagers();

          // create an in-memory truststore
          // On the server, truststore is specified as a parameter.
          // On the client, there are three choices: default J2SE truststore,
          // empty (not specified) trusstore, or with the contens of the specified
          // file. A TrustManager is created for the latter two cases to use this
          // truststore.
          // The default J2SE truststore is used if no truststore was specified
          // and the server's certificate validation is requested.

          boolean noTrust = false;

          if (srv)
          {
             // server side truststore initialization
             trustStore = trust;
          }
          else
          {
             // client side truststore initialization (this is a file or URL
             // specified in bootstrap config)

             InputStream trustIn = bc.getInputStream("security",
                                                     "truststore",
                                                     "filename",
                                                     null);
             boolean trustFromFile = true;
             if (trustIn == null)
             {
                String store = bc.getConfigItem("security", "truststore", "bytes");
                if (store != null)
                {
                   trustFromFile = false;
                   byte[] bytes = Base64.base64ToByteArray(store);
                   trustIn = new ByteArrayInputStream(bytes);
                }
             }
             trustStore = KeyStore.getInstance("JKS");

             if (trustIn != null)
             {
                // a truststore file is specified
                try
                {
                   String tpw = bc.getString("access",
                                             "password",
                                             "truststore",
                                             null);
                   char[] tspw = (tpw == null) ? null : tpw.toCharArray();
                   trustStore.load(trustIn, tspw);
                   trustIn.close();
                }

                catch (Exception e)
                {
                   String txt = bc.getString("security",
                                             "truststore",
                                             (trustFromFile ? "filename" : "bytes"),
                                             null);
                   throw new ConfigurationException("misconfigured truststore " +
                                                    txt,
                                                    e);
                }

                // request server certificate validation
                bc.setConfigItem("security", "certificate", "validate", "true");
             }
             else
             {
                // no keystore file is specified. the keystore will be empty
                try
                {
                   trustStore.load(null, null);
                   noTrust = true;
                }
                catch (Exception e)
                {
                   // should not be here
                }
             }
          }

          // optionally, create a trust manager for this truststore
          // otherwise, the J2SE default will be used
          if (!noTrust ||
              !bc.getBoolean("security", "certificate", "validate", false))
          {
             tm = getTrustManagers();
          }

          // for applet code, to disable server certificate validation it will
          // take a special trust manager that disables checking since there is
          // no local trust store that can be accessed
          String txt = bc.getString("security", "trust_mgr", "disable", "false");

          if (txt.equalsIgnoreCase("true"))
          {
              tm = new TrustManager[] { new ZeroActionTrustManager() };
          }
       }

       /**
        * Load the keystore from the specified configuration, and verify if a certificate with the
        * configured alias exists.
        * <p>
        * This loads just the certificate(s), not the private keys.
        *
        * @param    bc
        *           Security related configuration information.
        * @param    srv
        *           <code>true</code> if this is the server.
        *
        * @return   The loaded key store, or null if it couldn't be loaded.
        */
       static KeyStore loadKeyStore(BootstrapConfig bc, boolean srv)
       throws NoSuchAlgorithmException,
              KeyStoreException,
              UnrecoverableKeyException,
              ConfigurationException
       {
          String alias = getSubjectAlias(bc, srv);

          KeyStore keyStore = null;

          // On the client, the keystore can be empty (not specified) or it can
          // be initialized with the contents of the specified file.  A
          // KeyManager is always created to use this keystore.
          // On the server, an empty keystore is not allowed.

          InputStream keyIn = bc.getInputStream("security",
                                                "keystore",
                                                "filename",
                                                null);
          boolean keyFromFile = true;
          if (keyIn == null)
          {
             String store = bc.getConfigItem("security", "keystore", "bytes");
             if (store != null)
             {
                keyFromFile = false;
                byte[] bytes = Base64.base64ToByteArray(store);
                keyIn = new ByteArrayInputStream(bytes);
             }
          }

          if (keyIn != null)
          {
             // create an in-memory keystore
             keyStore = KeyStore.getInstance("JKS");

             // a keystore file is specified
             try
             {
                String kpw = bc.getString("access", "password", "keystore", null);
                keyStore.load(keyIn, (kpw == null) ? null : kpw.toCharArray());
                keyIn.close();
             }

             catch (Exception e)
             {
                String txt = bc.getString("security",
                                          "keystore",
                                          (keyFromFile ? "filename" : "bytes"),
                                          null);
                throw new ConfigurationException("misconfigured keystore " + txt,
                                                 e);
             }

             // request server certificate validation
             bc.setConfigItem("security", "certificate", "validate", "true");

             // verify the keystore: must have an alias that matches alias
             // or if not specified, exactly one key entry
             alias = verifyKeystore(keyStore, alias);
          }

          return keyStore;
       }

       /**
        * Initializes the given SSLContext object so that the latter uses the key
        * manager and the trust manager embedded into this TransportSecurity
        * class.
        *
        * @param  sslc
        *         a <code>SSLContext</code> that has to be set up to use the
        *         facilities of this <code>TransportSecurity</code> instance
        * @throws KeyManagementException - if this operation fails
        */
        public void attach(SSLContext sslc)
        throws KeyManagementException
        {
           sslc.init(km, tm, null);
        }

       /**
        * Returns the trust store.
        *
        * @return trust store built by this <code>TransportSecurity</code>
        */
       KeyStore getTrustStore()
       {
          return trustStore;
       }

       /**
        * Looks up the proper subject alias in the given configuration.
        *
        * @param    bc
        *           Security related configuration information.
        * @param    srv
        *           <code>true</code> if this is the server.
        *
        * @return   The subject alias or <code>null</code> if no alias is
        *           specified.
        */
       static String getSubjectAlias(BootstrapConfig bc, boolean srv)
       throws ConfigurationException
       {
          String alias;
          if (srv)
          {
             alias = bc.getString("security", "keystore", "alias", null);
          }
          else
          {
             alias = bc.getString("security", "keystore", "processalias", null);

             String ua = bc.getString("security", "keystore", "useralias", null);

             if (alias != null)
             {
                if (ua != null)
                {
                   throw new ConfigurationException("misconfigured keystore: " +
                                                    "processalias and useralias" +
                                                    " are mutually exclusive");
                }
             }
             else
             {
                alias = ua;
             }
          }

          return alias;
       }

       /**
        * Verify and normalize the keystore to ensure that the specified alias
        * exists as a key entry OR that there is one and only one key entry.
        * <p>
        * All unnecessary key entries are removed from the key store.
        *
        * @param    keyStore
        *           The key store to verify.
        * @param    alias
        *           The name used to differentiate between multiple key entries.
        *
        * @return   If the <code>alias</code> was specified as <code>null</code>
        *           and the key store only has one key entry, then that name is
        *           returned.  Otherwise the alias passed as a parameter is
        *           returned.
        */
       private static String verifyKeystore(KeyStore keyStore, String alias)
       throws NoSuchAlgorithmException,
              KeyStoreException,
              UnrecoverableKeyException,
              ConfigurationException
       {
          if (alias != null)
          {
             if (!keyStore.isKeyEntry(alias))
             {
                throw new ConfigurationException("misconfigured keystore: " +
                                                 "no matching alias for "   +
                                                 alias);
             }
          }
          else
          {
             Enumeration en = keyStore.aliases();
             int numkeys = 0;

             while (en.hasMoreElements())
             {
                String next = (String) en.nextElement();

                if (keyStore.isKeyEntry(next))
                   numkeys ++;

                // the first entry is used as the alias
                if (alias == null)
                   alias = next;
             }

             if (numkeys == 0)
             {
                throw new ConfigurationException("misconfigured keystore: " +
                                                 "no private keys");
             }

             if (numkeys > 1)
             {
                throw new ConfigurationException("misconfigured keystore: " +
                                                 "multiple private keys with " +
                                                 "no specified alias to " +
                                                 "differentiate");
             }
          }

          Enumeration en = keyStore.aliases();

          // normalize the keystore by deleting all unnecessary entries
          while (en.hasMoreElements())
          {
             String next = (String) en.nextElement();

             if (!next.equals(alias))
             {
                keyStore.deleteEntry(next);
             }
          }

          return alias;
       }

       /**
        * Returns the trust managers which have been initialized using the
        * instance's defined trust store.
        *
        * @return   The default trust manager.
        */
       private TrustManager[] getTrustManagers()
       throws NoSuchAlgorithmException,
              KeyStoreException
       {
          TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
          tmf.init(trustStore);
          return tmf.getTrustManagers();
       }

       /**
        * Zero action trust manager class which accepts anything as a valid
        * certificate and produces a zero list of accepted issuers.
        */
       public class ZeroActionTrustManager
       implements X509TrustManager
       {
          /**
           * Accepts anything.
           */
          public void checkClientTrusted(X509Certificate[] chain, String authType)
          throws CertificateException
          {
             // anything is acceptable
          }

          /**
           * Accepts anything.
           */
          public void checkServerTrusted(X509Certificate[] chain, String authType)
          throws CertificateException
          {
             // anything is acceptable
          }

          /**
           * Produces zero size list pf accepter issuers.
           *
           * @return  empty array of accepted issuers
           */
          public X509Certificate[] getAcceptedIssuers()
          {
             return new X509Certificate[0];
          }
       }
    }