org.texai.x509.X509Utils.java Source code

Java tutorial

Introduction

Here is the source code for org.texai.x509.X509Utils.java

Source

/*
 * X509Utils.java
 *
 * Created on Jan 19, 2010, 3:12:10 PM
 *
 * Description: X509 utilities adapted from "Beginning Cryptography With Java", David Hook, WROX.
 *
 * Copyright (C) Jan 19, 2010 reed.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms
 * of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
// refer to http://www.bouncycastle.org/docs/docs1.5on/index.html for substitutes for deprecated references
package org.texai.x509;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.texai.util.StringUtils;
import org.texai.util.TexaiException;
import sun.security.x509.X509CertImpl;

/** X509 utilities adapted from "Beginning Cryptography With Java", David Hook, WROX.
 *
 * How to regenerate the root X.509 certificate on the development system.
 * (1) delete /home/reed/texai-keystore.jceks
 * (2) run the JUnit test X509UtilsTest.java - expect several unit test failures [none on most recent regeneration]
 * (3) copy the byte array values from the unit test output (root certificate bytes...)
 *     into the array initialialization value for ROOT_CERTIFICATE_BYTES.
 * (4) delete X509Security/data/truststore.*, test-client-keystore.*, test-server-keystore.*
 * (5) re-run the unit test correcting for the new root UID
 * (6) likewise correct KeyStoreTestUtilsTest, X509SecurityInfoTest and TexaiSSLContextFactoryTest
 * (7) ensure that Git updates the new keystore files when committing
 * (7) copy truststore.uber and truststore.jceks files to the Network, AlbusHCNSupport, WebServer, X509CertificateServerTest, and X509CertificateServer
 *     (development and production) data directories
 * (8) copy test-client-keystore.uber and test-client-keystore.jceks to AlbusHCNSupport, Network, WebServer and X509CertificateServerTest data directories
 *
 * @author reed
 */
public final class X509Utils {

    /** the logger */
    private static final Logger LOGGER = Logger.getLogger(X509Utils.class);
    /** the default secure random serialization path */
    public static final String DEFAULT_SECURE_RANDOM_PATH = "data/secure-random.ser";
    /** the root certificate alias */
    public static final String ROOT_ALIAS = "root";
    /** the root certificate alias */
    public static final String JAR_SIGNER_ALIAS = "jar-signer";
    /** the period in which the certificate is valid */
    private static final long VALIDITY_PERIOD = 10L * 365L * 24L * 60L * 60L * 1000L; // ten years
    /** the Bouncy Castle cryptography provider */
    public static final String BOUNCY_CASTLE_PROVIDER = "BC";
    /** the digital signature algorithm */
    public static final String DIGITAL_SIGNATURE_ALGORITHM = "SHA512withRSA";
    /** the indicator whether the JCE unlimited strength jurisdiction policy files are installed */
    private static boolean isJCEUnlimitedStrenthPolicy;
    /** the root certificate bytes */
    private final static byte[] ROOT_CERTIFICATE_BYTES = { 48, -126, 4, -94, 48, -126, 3, 10, -96, 3, 2, 1, 2, 2, 2,
            3, -72, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 13, 5, 0, 48, 114, 49, 52, 48, 50, 6, 10, 9,
            -110, 38, -119, -109, -14, 44, 100, 1, 1, 12, 36, 101, 100, 54, 100, 54, 55, 49, 56, 45, 56, 48, 100,
            101, 45, 52, 56, 52, 56, 45, 97, 102, 52, 51, 45, 102, 101, 100, 55, 98, 100, 98, 97, 51, 99, 51, 54,
            49, 38, 48, 36, 6, 3, 85, 4, 10, 12, 29, 84, 101, 120, 97, 105, 32, 67, 101, 114, 116, 105, 102, 105,
            99, 97, 116, 105, 111, 110, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 18, 48, 16, 6, 3, 85, 4,
            3, 12, 9, 116, 101, 120, 97, 105, 46, 111, 114, 103, 48, 30, 23, 13, 49, 52, 48, 54, 50, 48, 49, 54, 50,
            57, 49, 53, 90, 23, 13, 50, 52, 48, 54, 49, 55, 49, 54, 50, 57, 50, 53, 90, 48, 114, 49, 52, 48, 50, 6,
            10, 9, -110, 38, -119, -109, -14, 44, 100, 1, 1, 12, 36, 101, 100, 54, 100, 54, 55, 49, 56, 45, 56, 48,
            100, 101, 45, 52, 56, 52, 56, 45, 97, 102, 52, 51, 45, 102, 101, 100, 55, 98, 100, 98, 97, 51, 99, 51,
            54, 49, 38, 48, 36, 6, 3, 85, 4, 10, 12, 29, 84, 101, 120, 97, 105, 32, 67, 101, 114, 116, 105, 102,
            105, 99, 97, 116, 105, 111, 110, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 18, 48, 16, 6, 3,
            85, 4, 3, 12, 9, 116, 101, 120, 97, 105, 46, 111, 114, 103, 48, -126, 1, -94, 48, 13, 6, 9, 42, -122,
            72, -122, -9, 13, 1, 1, 1, 5, 0, 3, -126, 1, -113, 0, 48, -126, 1, -118, 2, -126, 1, -127, 0, -125, 91,
            21, -120, -83, -108, -52, 15, -18, -128, -104, -68, -70, -9, 58, 58, -111, 5, -47, -7, -119, 110, 63,
            -24, 89, -61, -98, -108, -53, -84, 56, -65, 62, -40, 31, -97, -99, -121, 64, -77, 43, -15, -12, -16,
            -108, 110, 101, -12, -7, -98, -50, -62, -121, -41, -69, -118, 37, 19, -3, 119, 26, 111, -80, 62, 124,
            -99, -42, 86, 125, 32, -74, 97, 79, -26, 3, -30, 13, -61, 35, -54, 44, 114, -43, 84, -13, -33, 80, 22,
            73, 93, 77, 90, 12, -117, 50, -104, 63, 38, 99, -109, -119, -118, 19, 86, 18, -48, 87, -14, 119, 69,
            -67, -69, -74, -13, 24, 31, 60, -79, -62, -2, -114, -118, -15, 121, 68, 116, 67, -97, -9, -69, -36, -94,
            -33, -93, 12, -46, -105, -92, 21, 27, 120, -58, -37, 5, 47, -21, 106, 25, -101, 3, -104, 31, -5, 60,
            -89, -74, 20, 25, 65, -116, -75, 48, -8, 50, -11, 70, 108, -49, -43, -35, 67, 106, -51, 127, 39, 87,
            -93, 71, -10, 103, -13, -54, 101, -80, 15, 11, 112, 19, -107, -44, -49, -63, 86, -112, -74, 9, 102,
            -124, 81, 74, -98, -109, 44, 29, 37, 42, 106, 87, -58, -128, -58, 67, 73, -39, -103, -30, -2, -13, 121,
            -90, -95, 120, -4, 20, 114, 8, 97, 40, -26, 38, -96, -87, -4, 6, -87, -48, -53, 72, 10, 1, -62, -15, -2,
            54, -67, 3, 4, -115, -90, 31, -25, 102, -30, 89, 124, -46, -91, -83, 83, 95, -39, -70, 57, -121, -13,
            -35, 105, -84, -33, -30, -93, -94, -79, -7, -15, 21, -15, 36, -11, -92, 90, 36, 61, 110, 103, 66, 31,
            103, -71, 24, 4, 45, -72, -60, 26, 45, -123, 11, 0, 97, -34, -113, -99, -99, -33, -71, 102, 127, 29, 36,
            95, -17, 0, -3, -97, -124, 117, -52, -92, -23, -41, -45, 76, -115, 61, -38, -44, 52, -51, 94, -118, 110,
            -126, -7, -51, -44, -69, -19, 88, -25, -28, -89, -75, -113, 26, -99, -16, -90, 97, 56, -91, 26, -58, 22,
            81, 48, -92, -65, -95, -28, 50, -73, 110, -63, -63, 43, 111, 76, 58, 102, 96, 25, -4, 48, -52, -20, 123,
            100, 21, 116, -26, 65, 115, -107, 2, 3, 1, 0, 1, -93, 66, 48, 64, 48, 29, 6, 3, 85, 29, 14, 4, 22, 4,
            20, 1, 119, 4, -4, 27, -14, 82, -42, -46, -98, -16, -87, -109, -19, -27, -102, 72, -75, -126, -44, 48,
            15, 6, 3, 85, 29, 19, 1, 1, -1, 4, 5, 48, 3, 1, 1, -1, 48, 14, 6, 3, 85, 29, 15, 1, 1, -1, 4, 4, 3, 2,
            1, 6, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 13, 5, 0, 3, -126, 1, -127, 0, 42, -128, -7, -36,
            -35, -1, -54, -87, 47, -61, -18, 77, 93, 80, -116, -62, 110, 2, 97, -79, -54, -29, 68, 0, 87, 25, -77,
            -4, -81, 78, 65, -14, 30, 127, 28, -2, -36, 82, -97, 38, -119, -68, -25, 100, -41, -102, -11, 101, -60,
            -26, 0, 58, 35, -50, 11, 98, -59, 23, 56, -89, 99, 26, 91, -80, -122, -83, -64, -99, 84, 9, -94, 37,
            -63, 114, 126, -123, -110, -60, 111, -102, -36, -114, -58, 82, -106, -73, -56, 86, -67, 105, -122, -67,
            -41, 32, -47, -75, -30, -44, -114, -22, -121, -95, 78, -76, 70, -66, 32, 78, 37, 101, 5, 121, -120, 124,
            -90, -13, 84, -62, -73, 84, -84, 28, -54, 113, 98, 29, 62, 14, -61, 80, 13, 107, 85, 65, -114, -36,
            -103, 50, 114, 52, 56, 125, -1, 97, 38, 106, -6, -73, -102, -60, 109, -115, -86, -37, 107, -67, -36, 80,
            45, -53, -124, -85, -21, -101, 13, 67, -70, -38, -78, 54, -53, -128, -93, -98, 52, 34, 23, -74, -95,
            -49, 115, 45, 96, 21, 85, -31, 106, -128, -28, 15, -67, 79, 52, -89, -111, -93, -41, -3, 6, -118, -64,
            -73, -16, 106, 100, -86, -61, 41, 25, -48, 113, -69, -63, 80, 77, 28, 12, 2, 120, 26, -7, 51, 69, 34,
            -101, 25, -68, -25, -43, -8, 35, -26, -38, -101, 26, -120, -38, 86, -82, 89, -75, 31, 84, 101, 27, -95,
            86, -114, 30, -12, -120, -16, 49, 67, 32, -1, 111, 87, 101, 119, 127, 43, -9, -39, 68, 127, -74, 34, 54,
            -25, 58, -91, -69, -26, -7, -31, 73, 127, -105, -34, 8, 19, 52, 127, 3, -55, -115, 56, 100, 29, 94, -32,
            80, 108, 47, 84, -103, 12, -118, 99, 29, -67, 88, -95, -126, -66, -16, 35, 62, -59, -10, -95, -50, 111,
            -76, 112, 84, 77, -21, -100, 98, -95, 13, -56, -86, -60, -28, -94, -4, 93, 25, 19, -89, -38, 126, 80,
            -24, 20, -109, -19, 95, -42, -48, 23, -7, -36, 105, -33, 60, 3, -24, -62, 76, 89, 9, 7, -64, -123, 123,
            22, 26, 96, -24, -40, 117, 118, 126, 60, -118, -91, -61, 20, 105, -72, -3, 113, -35, 66, -36, 117, -45,
            -120, 31, -85 };
    /** the root certificate */
    private static final X509Certificate ROOT_X509_CERTIFICATE;
    /** the truststore entry alias */
    public static final String TRUSTSTORE_ENTRY_ALIAS = "texai root certificate";
    /** the truststore */
    private static KeyStore truststore;
    /** the truststore password */
    public static final char[] TRUSTSTORE_PASSWORD = "truststore-password".toCharArray();
    /** the certificate entry alias */
    public static final String ENTRY_ALIAS = "certificate";
    /** the installer keystore password */
    public static final char[] INSTALLER_KEYSTORE_PASSWORD = "installer-keystore-password".toCharArray();
    /** the intermediate, signing entry alias */
    public static final String INTERMEDIATE_ENTRY_ALIAS = "Texai intermediate certificate";

    static {
        try {
            setIsJCEUnlimitedStrengthPolicy(Cipher.getMaxAllowedKeyLength("AES") == Integer.MAX_VALUE);
            assert !isTrustedDevelopmentSystem()
                    || X509Utils.isJCEUnlimitedStrengthPolicy() : "JCE unlimited strength policy must be in effect";
        } catch (NoSuchAlgorithmException ex) {
            throw new TexaiException(ex);
        }
    }

    static {
        LOGGER.info("adding Bouncy Castle cryptography provider");
        Security.addProvider(new BouncyCastleProvider());

        LOGGER.info("initializing the root X.509 certificate");

        try {
            ROOT_X509_CERTIFICATE = new X509CertImpl(ROOT_CERTIFICATE_BYTES);
        } catch (CertificateException ex) {
            throw new TexaiException(ex);
        }
    }
    /** the secure random */
    private static SecureRandom secureRandom;
    /** the secure random synchronization lock */
    private static final Object secureRandom_lock = new Object();

    static {
        X509Utils.initializeSecureRandom(DEFAULT_SECURE_RANDOM_PATH);
    }

    /** Prevents the instantiation of this utility class. */
    private X509Utils() {
    }

    /** Makes a canonical X.509 certificate by serializing it to bytes and reconsituting it. This ensures
     * that all issuer and subject names have no space following the commas.
        
     * @param x509Certificate the input certificate
     * @return the canonical certificate
     */
    public static X509Certificate makeCanonicalX509Certificate(final X509Certificate x509Certificate) {
        //Preconditions
        assert x509Certificate != null : "x509Certificate must not be null";

        X509Certificate canonicalX509Certificate;
        try {
            final byte[] certificateBytes = x509Certificate.getEncoded();
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(certificateBytes);
            canonicalX509Certificate = readX509Certificate(byteArrayInputStream);
        } catch (CertificateException | NoSuchProviderException ex) {
            throw new TexaiException(ex);
        }

        LOGGER.debug("x509Certificate (" + x509Certificate.getClass().getName() + ")...\n" + x509Certificate
                + "\ncanonicalX509Certificate(" + canonicalX509Certificate.getClass().getName() + ")...\n"
                + canonicalX509Certificate);

        //Postconditions
        assert canonicalX509Certificate.equals(
                x509Certificate) : "canonicalX509Certificate must equal x509Certificate,\ncanonicalX509Certificate...\n"
                        + canonicalX509Certificate + "\nx509Certificate...\n" + x509Certificate;

        return canonicalX509Certificate;
    }

    /** Gets the secure random, and lazily initializes it.
     *
     * @return the initialized secure random
     */
    public static SecureRandom getSecureRandom() {
        synchronized (secureRandom_lock) {
            if (secureRandom == null) {
                LOGGER.info("creating and seeding secure random");
                try {
                    secureRandom = SecureRandom.getInstance("SHA1PRNG");
                    secureRandom.nextInt();
                } catch (NoSuchAlgorithmException ex) {
                    throw new TexaiException(ex);
                }
                secureRandom.nextInt();
            }
            return secureRandom;
        }
    }

    /** Initializes the secure random from a serialized object.
     *
     * @param path the path to the previously serialized secure random
     * @return the initialized secure random
     */
    public static SecureRandom initializeSecureRandom(final String path) {
        //Preconditions
        assert path != null : "path must not be null";
        assert !path.isEmpty() : "path must not be empty";

        final File file = new File(path);
        if (file.exists()) {
            // read the secure random from a file to avoid the potentially long delay of creating and initializing it
            try {
                try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(path))) {
                    synchronized (secureRandom_lock) {
                        secureRandom = (SecureRandom) in.readObject();
                    }
                    LOGGER.info("secure random loaded from " + path);
                }
            } catch (IOException | ClassNotFoundException ex) {
                throw new TexaiException(ex);
            }
        } else {
            serializeSecureRandom(path);
        }
        return secureRandom;
    }

    /** Serializes the secure random to a file for a subsequent restart.
     *
     * @param path the path to the previously serialized secure random
     */
    public static void serializeSecureRandom(final String path) {
        //Preconditions
        assert path != null : "path must not be null";
        assert !path.isEmpty() : "path must not be empty";

        try {
            // serialize the secure random in a file for reuse during a restart
            try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path))) {
                out.writeObject(getSecureRandom());
            }
        } catch (IOException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Gets the root X509 certificate.
     *
     * @return the root X509 certificate
     */
    public static X509Certificate getRootX509Certificate() {
        return ROOT_X509_CERTIFICATE;
    }

    /** Gets the indicator whether the JCE unlimited strength jurisdiction policy files are installed.
     *
     * @return the indicator whether the JCE unlimited strength jurisdiction policy files are installed
     */
    public static synchronized boolean isJCEUnlimitedStrengthPolicy() {
        return isJCEUnlimitedStrenthPolicy;
    }

    /** Sets the indicator whether the JCE unlimited strength jurisdiction policy files are installed.
     *
     * @param _isJCEUnlimitedStrenthPolicy the indicator whether the JCE unlimited strength jurisdiction policy files are installed
     */
    public static synchronized void setIsJCEUnlimitedStrengthPolicy(boolean _isJCEUnlimitedStrenthPolicy) {
        isJCEUnlimitedStrenthPolicy = _isJCEUnlimitedStrenthPolicy;
        LOGGER.debug("isJCEUnlimitedStrenthPolicy: " + isJCEUnlimitedStrenthPolicy);
    }

    /** Returns the maximum key length allowed by the ciphers on this JVM, which depends on whether the unlimited
     * strength encryption policy jar files have been downloaded and installed.
     *
     * @return the maximum allowed key size
     * @throws NoSuchAlgorithmException when the encryption algorithm cannot be found
     */
    public static int getMaxAllowedKeyLength() throws NoSuchAlgorithmException {
        return Cipher.getMaxAllowedKeyLength("AES");
    }

    /** Logs the cryptography providers. */
    public static void logProviders() {
        LOGGER.info("cryptography providers ...");
        final Provider[] providers = Security.getProviders();
        for (int i = 0; i != providers.length; i++) {
            LOGGER.info("  Name: " + providers[i].getName()
                    + StringUtils.makeBlankString(15 - providers[i].getName().length()) + " Version: "
                    + providers[i].getVersion());
        }
    }

    /** Logs the capabilities of the cryptography providers.
     * @param providerString the provider identifier
     */
    public static void logProviderCapabilities(final String providerString) {
        //Preconditions
        assert providerString != null : "providerString must not be null";
        assert !providerString.isEmpty() : "providerString must not be empty";

        final Provider provider = Security.getProvider(providerString);

        final Iterator<Object> propertyKey_iter = provider.keySet().iterator();

        LOGGER.info("cryptography provider " + providerString + " capabilities ...");
        final List<String> propertyStrings = new ArrayList<>();
        while (propertyKey_iter.hasNext()) {
            String propertyString = (String) propertyKey_iter.next();
            if (propertyString.startsWith("Alg.Alias.")) {
                // this indicates the entry refers to another entry
                propertyString = propertyString.substring("Alg.Alias.".length());
            }
            propertyStrings.add(propertyString);
        }
        Collections.sort(propertyStrings);
        for (final String propertyString : propertyStrings) {
            final String factoryClass = propertyString.substring(0, propertyString.indexOf('.'));
            final String name = propertyString.substring(factoryClass.length() + 1);
            LOGGER.info("  " + factoryClass + ": " + name);
        }
    }

    /** Creates a random 3072 bit RSA key pair.
     * @return a random 3072 bit RSA key pair
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws NoSuchProviderException  when an invalid provider is given
     * @throws InvalidAlgorithmParameterException when an invalid algorithm parameter is given
     */
    public static KeyPair generateRSAKeyPair3072()
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BOUNCY_CASTLE_PROVIDER);
        final AlgorithmParameterSpec algorithmParameterSpec = new RSAKeyGenParameterSpec(3072,
                RSAKeyGenParameterSpec.F4);
        keyPairGenerator.initialize(algorithmParameterSpec, getSecureRandom());
        return keyPairGenerator.generateKeyPair();
    }

    /** Creates a random 2048 bit RSA key pair.
     * @return a random 2048 bit RSA key pair
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws NoSuchProviderException  when an invalid provider is given
     * @throws InvalidAlgorithmParameterException when an invalid algorithm parameter is given
     */
    public static KeyPair generateRSAKeyPair2048()
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BOUNCY_CASTLE_PROVIDER);
        final AlgorithmParameterSpec algorithmParameterSpec = new RSAKeyGenParameterSpec(2048,
                RSAKeyGenParameterSpec.F4);
        keyPairGenerator.initialize(algorithmParameterSpec, getSecureRandom());
        return keyPairGenerator.generateKeyPair();
    }

    /** Gets the truststore that contains the single trusted root X.509 certificate.
     *
     * @return the truststore
     */
    public static synchronized KeyStore getTruststore() {
        //Preconditions
        assert (new File("data/truststore.uber")).exists()
                || (new File("data/truststore.jceks")).exists() : "truststore file must exist";
        assert !isTrustedDevelopmentSystem()
                || X509Utils.isJCEUnlimitedStrengthPolicy() : "JCE unlimited strength policy must be in effect";

        String filePath = "";
        if (truststore == null) {

            if (X509Utils.isJCEUnlimitedStrengthPolicy()) {
                filePath = "data/truststore.uber";
            } else {
                filePath = "data/truststore.jceks";
            }
            LOGGER.info("reading truststore from " + filePath);
            try {
                truststore = X509Utils.findOrCreateKeyStore(filePath, TRUSTSTORE_PASSWORD);
            } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException
                    | NoSuchProviderException ex) {
                throw new TexaiException(ex);
            }
        }

        //Postconditions
        assert truststore != null : "truststore must not be null";
        assert X509Utils.BOUNCY_CASTLE_PROVIDER
                .equals(truststore.getProvider().getName()) : "truststore type must be " + BOUNCY_CASTLE_PROVIDER
                        + ", but was " + truststore.getProvider().getName() + ", filePath: " + filePath;
        assert !filePath.endsWith(".uber")
                || truststore.getType().equals("UBER") : "truststore type must be UBER, but was "
                        + truststore.getType() + ", filePath: " + filePath;

        return truststore;
    }

    /** Generates an intermediate CA certificate, that is to be used to sign end-use certificates.
     *
     * @param myPublicKey the public key for this certificate
     * @param issuerPrivateKey the issuer's private key
     * @param issuerCertificate the issuer's certificate, which is either the root CA certificate or another intermediate
     * CA certificate
     * @param pathLengthConstraint the maximum number of CA certificates that may follow this certificate in a certification
     * path. (Note: One end-entity certificate will follow the final CA certificate in the path. The last certificate in a path
     * is considered an end-entity certificate, whether the subject of the certificate is a CA or not.)
     * @return an intermediate CA certificate
     *
     * @throws CertificateParsingException when the certificate cannot be parsed
     * @throws CertificateEncodingException when the certificate cannot be encoded
     * @throws NoSuchProviderException when an invalid provider is given
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws SignatureException when the an invalid signature is present
     * @throws InvalidKeyException when the given key is invalid
     * @throws IOException if an input/output error occurs while processing the serial number file
     */
    public static X509Certificate generateIntermediateX509Certificate(final PublicKey myPublicKey,
            final PrivateKey issuerPrivateKey, final X509Certificate issuerCertificate, int pathLengthConstraint)
            throws CertificateParsingException, CertificateEncodingException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException {
        //Preconditions
        assert myPublicKey != null : "myPublicKey must not be null";
        assert issuerPrivateKey != null : "issuerPrivateKey must not be null";
        assert issuerCertificate != null : "issuerCertificate must not be null";

        //final X500Name issuer = new X500Name(issuerCertificate.getSubjectX500Principal().getName());
        final X500Name issuer = new X500Name(
                StringUtils.reverseCommaDelimitedString(issuerCertificate.getSubjectX500Principal().getName()));
        final UUID intermediateUUID = UUID.randomUUID();
        // provide items to X500Principal in reverse order
        final X500Principal x500Principal = new X500Principal(
                "UID=" + intermediateUUID + ", DC=IntermediateCertificate, CN=texai.org");
        final X500Name subject = new X500Name(x500Principal.getName());
        SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo(
                ASN1Sequence.getInstance(myPublicKey.getEncoded()));
        final X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder(issuer,
                getNextSerialNumber(), // serial
                new Date(System.currentTimeMillis() - 10000L), // notBefore,
                new Date(System.currentTimeMillis() + VALIDITY_PERIOD), // notAfter,
                subject, publicKeyInfo);

        // see http://www.ietf.org/rfc/rfc3280.txt
        // see http://stackoverflow.com/questions/20175447/creating-certificates-for-ssl-communication
        final JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils();

        // Add authority key identifier
        x509v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false, // isCritical
                jcaX509ExtensionUtils.createAuthorityKeyIdentifier(issuerCertificate));

        // Add subject key identifier
        x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false, // isCritical
                jcaX509ExtensionUtils.createSubjectKeyIdentifier(myPublicKey));

        // add basic constraints
        x509v3CertificateBuilder.addExtension(Extension.basicConstraints, true, // isCritical
                new BasicConstraints(pathLengthConstraint)); // is a CA certificate with specified certification path length

        // add key usage
        final KeyUsage keyUsage = new KeyUsage(
                // the keyCertSign bit indicates that the subject public key may be used for verifying a signature on
                // certificates
                KeyUsage.keyCertSign | // the cRLSign indicates that the subject public key may be used for verifying a signature on revocation
                                       // information
                        KeyUsage.cRLSign);

        x509v3CertificateBuilder.addExtension(Extension.keyUsage, true, // isCritical
                keyUsage);

        X509Certificate x509Certificate;
        try {
            final ContentSigner contentSigner = new JcaContentSignerBuilder(DIGITAL_SIGNATURE_ALGORITHM)
                    .setProvider(BOUNCY_CASTLE_PROVIDER).build(issuerPrivateKey);
            final X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(contentSigner);
            final JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter();
            x509Certificate = makeCanonicalX509Certificate(
                    jcaX509CertificateConverter.getCertificate(x509CertificateHolder));
        } catch (CertificateException | OperatorCreationException ex) {
            throw new TexaiException(ex);
        }

        //Postconditions
        try {
            x509Certificate.checkValidity();
            x509Certificate.verify(issuerCertificate.getPublicKey());
        } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException
                | SignatureException ex) {
            throw new TexaiException(ex);
        }

        return x509Certificate;
    }

    /** Generates a signed end-use certificate that cannot be used to sign other certificates.
     *
     * @param myPublicKey the public key for this certificate
     * @param issuerPrivateKey the issuer's private key
     * @param issuerCertificate the issuer's certificate
     * @param domainComponent the domain component
     * @return a signed end-use certificate
     *
     * @throws CertificateParsingException when the certificate cannot be parsed
     * @throws CertificateEncodingException when the certificate cannot be encoded
     * @throws NoSuchProviderException when an invalid provider is given
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws SignatureException when the an invalid signature is present
     * @throws InvalidKeyException when the given key is invalid
     * @throws IOException if an input/output error occurs while processing the serial number file
     */
    public static X509Certificate generateX509Certificate(final PublicKey myPublicKey,
            final PrivateKey issuerPrivateKey, final X509Certificate issuerCertificate,
            final String domainComponent)
            throws CertificateParsingException, CertificateEncodingException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException {
        //Preconditions
        assert myPublicKey != null : "myPublicKey must not be null";
        assert issuerPrivateKey != null : "issuerPrivateKey must not be null";
        assert issuerCertificate != null : "issuerCertificate must not be null";

        return generateX509Certificate(myPublicKey, issuerPrivateKey, issuerCertificate, UUID.randomUUID(),
                domainComponent);
    }

    /** Generates a certificate path for a signed end-use certificate that cannot be used to sign other certificates, but can be used for authentication
     * and for message signing.
     *
     * @param myPublicKey the public key for this certificate
     * @param issuerPrivateKey the issuer's private key
     * @param issuerCertificate the issuer's X.509 certificate
     * @param issuerCertPath the issuer's certificate path
     * @param domainComponent the domain component, e.g. NodeRuntime
     * @return a certificate path for a signed end-use certificate
     *
     * @throws CertificateParsingException when the certificate cannot be parsed
     * @throws CertificateEncodingException when the certificate cannot be encoded
     * @throws NoSuchProviderException when an invalid provider is given
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws SignatureException when the an invalid signature is present
     * @throws InvalidKeyException when the given key is invalid
     * @throws IOException if an input/output error occurs while processing the serial number file
     * @throws CertificateException when an invalid certificate is present
     */
    public static CertPath generateX509CertificatePath(final PublicKey myPublicKey,
            final PrivateKey issuerPrivateKey, final X509Certificate issuerCertificate,
            final CertPath issuerCertPath, final String domainComponent)
            throws CertificateParsingException, CertificateEncodingException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException, CertificateException {
        //Preconditions
        assert myPublicKey != null : "myPublicKey must not be null";
        assert issuerPrivateKey != null : "issuerPrivateKey must not be null";
        assert issuerCertificate != null : "issuerCertificate must not be null";
        assert issuerCertPath != null : "issuerCertPath must not be null";

        final X509Certificate generatedCertificate = generateX509Certificate(myPublicKey, issuerPrivateKey,
                issuerCertificate, UUID.randomUUID(), domainComponent);
        final List<Certificate> certificateList = new ArrayList<>();
        certificateList.add(generatedCertificate);
        certificateList.addAll(issuerCertPath.getCertificates());
        return generateCertPath(certificateList);
    }

    /** Generates a signed end-use certificate that cannot be used to sign other certificates, but can be used for authentication
     * and for message signing.
     *
     * @param myPublicKey the public key for this certificate
     * @param issuerPrivateKey the issuer's private key
     * @param issuerCertificate the issuer's certificate
     * @param uid the subject UID
     * @param domainComponent the domain component, e.g. TexaiLauncher or NodeRuntime
     * @return a signed end-use certificate
     *
     * @throws CertificateParsingException when the certificate cannot be parsed
     * @throws CertificateEncodingException when the certificate cannot be encoded
     * @throws NoSuchProviderException when an invalid provider is given
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws SignatureException when the an invalid signature is present
     * @throws InvalidKeyException when the given key is invalid
     * @throws IOException if an input/output error occurs while processing the serial number file
     */
    public static X509Certificate generateX509Certificate(final PublicKey myPublicKey,
            final PrivateKey issuerPrivateKey, final X509Certificate issuerCertificate, final UUID uid,
            final String domainComponent)
            throws CertificateParsingException, CertificateEncodingException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException {
        //Preconditions
        assert myPublicKey != null : "myPublicKey must not be null";
        assert issuerPrivateKey != null : "issuerPrivateKey must not be null";
        assert issuerCertificate != null : "issuerCertificate must not be null";
        assert uid != null : "uid must not be null";

        final String x500PrincipalString;
        // provide items to X500Principal in reverse order
        if (domainComponent == null || domainComponent.isEmpty()) {
            x500PrincipalString = "UID=" + uid + ", CN=texai.org";
        } else {
            x500PrincipalString = "UID=" + uid + ", DC=" + domainComponent + " ,CN=texai.org";
        }
        final X500Principal x500Principal = new X500Principal(x500PrincipalString);

        LOGGER.info("issuer: " + issuerCertificate.getIssuerX500Principal().getName());

        final X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder(
                new X500Name(StringUtils
                        .reverseCommaDelimitedString(issuerCertificate.getSubjectX500Principal().getName())), // issuer,
                getNextSerialNumber(), // serial
                new Date(System.currentTimeMillis() - 10000L), // notBefore,
                new Date(System.currentTimeMillis() + VALIDITY_PERIOD), // notAfter,
                new X500Name(x500Principal.getName()), // subject,
                new SubjectPublicKeyInfo(ASN1Sequence.getInstance(myPublicKey.getEncoded()))); // publicKeyInfo

        // see http://www.ietf.org/rfc/rfc3280.txt
        // see http://stackoverflow.com/questions/20175447/creating-certificates-for-ssl-communication
        final JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils();
        // Add authority key identifier
        x509v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false, // isCritical
                jcaX509ExtensionUtils.createAuthorityKeyIdentifier(issuerCertificate));

        // Add subject key identifier
        x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false, // isCritical
                jcaX509ExtensionUtils.createSubjectKeyIdentifier(myPublicKey));

        // add basic constraints
        x509v3CertificateBuilder.addExtension(Extension.basicConstraints, true, // isCritical
                new BasicConstraints(false)); // is not a CA certificate

        // add key usage
        final KeyUsage keyUsage = new KeyUsage(
                // the digitalSignature usage indicates that the subject public key may be used with a digital signature
                // mechanism to support security services other than non-repudiation, certificate signing, or revocation
                // information signing
                KeyUsage.digitalSignature | // the nonRepudiation usage indicates that the subject public key may be used to verify digital signatures
                                            // used to provide a non-repudiation service which protects against the signing entity falsely denying some
                                            // action, excluding certificate or CRL signing
                        KeyUsage.nonRepudiation | // the keyEncipherment usage indicates that the subject public key may be used for key transport, e.g. the
                                                  // exchange of efficient symmetric keys in SSL
                        KeyUsage.keyEncipherment | // the dataEncipherment usage indicates that the subject public key may be used for enciphering user data,
                                                   // other than cryptographic keys
                        KeyUsage.dataEncipherment | // the keyAgreement usage indicates that the subject public key may be used for key agreement, e.g. when a
                                                    // Diffie-Hellman key is to be used for key management
                        KeyUsage.keyAgreement | // the keyCertSign bit indicates that the subject public key may be used for verifying a signature on
                                                // certificates
                        KeyUsage.keyCertSign | // the cRLSign indicates that the subject public key may be used for verifying a signature on revocation
                                               // information
                        KeyUsage.cRLSign | // see http://www.docjar.com/html/api/sun/security/validator/EndEntityChecker.java.html - bit 0 needs to set for SSL
                                           // client authorization
                        KeyUsage.encipherOnly);
        x509v3CertificateBuilder.addExtension(Extension.keyUsage, true, // isCritical
                keyUsage);

        X509Certificate x509Certificate;
        try {
            final ContentSigner contentSigner = new JcaContentSignerBuilder(DIGITAL_SIGNATURE_ALGORITHM)
                    .setProvider(BOUNCY_CASTLE_PROVIDER).build(issuerPrivateKey);
            final X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(contentSigner);
            final JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter();
            x509Certificate = makeCanonicalX509Certificate(
                    jcaX509CertificateConverter.getCertificate(x509CertificateHolder));
        } catch (CertificateException | OperatorCreationException ex) {
            throw new TexaiException(ex);
        }

        //Postconditions
        try {
            x509Certificate.checkValidity();
            x509Certificate.verify(issuerCertificate.getPublicKey());
        } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException
                | SignatureException ex) {
            throw new TexaiException(ex);
        }
        assert x509Certificate.getKeyUsage()[0] : "must have digital signature key usage";

        return x509Certificate;
    }

    /** Returns whether the given certificate has the given key usage bit set, i.e. digitalSignature usage.
     *
     *       digitalSignature        (0)
     *       nonRepudiation          (1)
     *       keyEncipherment         (2)
     *       dataEncipherment        (3)
     *       keyAgreement            (4)
     *       keyCertSign             (5)
     *       cRLSign                 (6)
     *       encipherOnly            (7)
     *       decipherOnly            (8)
     *
     * @param x509Certificate the given certificate
     * @param keyUsageBitMask the given key usage bit, i.e. KeyUsage.digitalSignature
     * @return whether the given certificate has the given key usage bit set
     */
    public static boolean hasKeyUsage(final X509Certificate x509Certificate, final int keyUsageBitMask) {
        //Preconditions
        assert x509Certificate != null : "x509Certificate must not be null";

        final boolean[] keyUsage = x509Certificate.getKeyUsage();
        int certificateKeyUsageBitmask = 0;
        final int keyUsage_len = keyUsage.length - 1; // ignore pad bit
        for (int i = 0; i < keyUsage_len; i++) {
            if (keyUsage[i]) {
                certificateKeyUsageBitmask += Math.pow(2, (keyUsage_len - i - 1));
            }
        }

        return (certificateKeyUsageBitmask & keyUsageBitMask) != 0;
    }

    /** Gets the UUID from the subject name contained in the given X.509 certificate.
     *
     * @param x509Certificate the given X.509 certificate
     * @return the UUID
     */
    public static UUID getUUID(final X509Certificate x509Certificate) {
        //Preconditions
        assert x509Certificate != null : "x509Certificate must not be null";

        final String subjectString = x509Certificate.getSubjectX500Principal().toString();
        assert !subjectString.isEmpty() : "subject DN must not be empty";
        final int index = subjectString.indexOf("UID=");
        assert index > -1 : "UID not found in the subject DN";
        final String uuidString = subjectString.substring(index + 4, index + 40);
        return UUID.fromString(uuidString);
    }

    /** Reads a DER encoded certificate from an input stream.
     * @param inputStream the input stream containing the DER encoded bytes of an X.509 certificate
     * @return the certificate
     * @throws CertificateException when an invalid certificate is read
     * @throws NoSuchProviderException when the cryptography provider cannot be found
     */
    public static X509Certificate readX509Certificate(final InputStream inputStream)
            throws CertificateException, NoSuchProviderException {
        //Preconditions
        assert inputStream != null : "inputStream must not be null";

        try {
            return new X509CertImpl(IOUtils.toByteArray(inputStream));
        } catch (IOException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Writes a DER encoded certificate to the given file path.
     * @param x509Certificate the X.509 certificate
     * @param filePath the given file path
     * @throws CertificateEncodingException if the certificate cannot be encoded
     * @throws IOException when an input/output error occurs
     */
    public static void writeX509Certificate(final X509Certificate x509Certificate, final String filePath)
            throws CertificateEncodingException, IOException {
        //Preconditions
        assert x509Certificate != null : "x509Certificate must not be null";
        assert filePath != null : "filePath must not be null";
        assert !filePath.isEmpty() : "filePath must not be empty";
        try (OutputStream certificateOutputStream = new FileOutputStream(filePath)) {
            certificateOutputStream.write(x509Certificate.getEncoded());
            certificateOutputStream.flush();
        }
    }

    /** Finds or creates the keystore specified by the given path.
     *
     * @param filePath the file path to the keystore
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findOrCreateKeyStore(final String filePath, final char[] password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        if (isJCEUnlimitedStrengthPolicy()) {
            assert filePath.endsWith(".uber") : "file extension must be .uber";
        } else {
            assert filePath.endsWith(".jceks") : "file extension must be .jceks";
        }
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        KeyStore keyStore;
        if (isJCEUnlimitedStrengthPolicy()) {
            keyStore = KeyStore.getInstance("UBER", BOUNCY_CASTLE_PROVIDER);
        } else {
            keyStore = KeyStore.getInstance("JCEKS");
        }
        if (keyStoreFile.exists()) {
            try (final FileInputStream keyStoreInputStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(keyStoreInputStream, password);
            }
        } else {
            keyStore.load(null, null);
            try (final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStoreFile)) {
                keyStore.store(keyStoreOutputStream, password);
            }
        }

        //Postconditions
        assert !filePath.endsWith(".uber") || keyStore.getType().equals("UBER") : "keyStore type is "
                + keyStore.getType() + ", expected UBER, filePath: " + filePath;

        return keyStore;
    }

    /** Finds or creates the jceks keystore specified by the given path.
     *
     * @param filePath the file path to the keystore
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findOrCreateJceksKeyStore(final String filePath, final char[] password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        assert filePath.endsWith(".jceks") : "file extension must be .jceks";
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        final KeyStore keyStore = KeyStore.getInstance("JCEKS");
        if (keyStoreFile.exists()) {
            try (final FileInputStream keyStoreInputStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(keyStoreInputStream, password);
            }
        } else {
            keyStore.load(null, null);
            try (final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStoreFile)) {
                keyStore.store(keyStoreOutputStream, password);
            }
        }
        return keyStore;
    }

    /** Finds or creates the uber keystore specified by the given path.
     *
     * @param filePath the file path to the keystore
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findOrCreateUberKeyStore(final String filePath, final char[] password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        assert filePath.endsWith(".uber") : "file extension must be .uber";
        assert isJCEUnlimitedStrengthPolicy() : "JCE unlimited strength policy file must be installed";
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        final KeyStore keyStore = KeyStore.getInstance("UBER", BOUNCY_CASTLE_PROVIDER);
        if (keyStoreFile.exists()) {
            try (final FileInputStream keyStoreInputStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(keyStoreInputStream, password);
            }
        } else {
            keyStore.load(null, null);
            try (final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStoreFile)) {
                keyStore.store(keyStoreOutputStream, password);
            }
        }
        return keyStore;
    }

    /** Copies the given keystore from the .uber format to the .jceks format.
     *
     * @param uberKeyStorePath the .uber keystore path
     * @param uberKeyStorePassword the .uber keystore password
     * @param jceksKeyStorePath the .jceks keystore path
     * @param jceksKeyStorePassword the .jceks keystore password
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     * @throws UnrecoverableEntryException if the keystore entry cannot be recovered with the provided password and alias
     */
    public static synchronized void copyKeyStoreUberToJceks(final String uberKeyStorePath,
            final char[] uberKeyStorePassword, final String jceksKeyStorePath, final char[] jceksKeyStorePassword)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException, UnrecoverableEntryException {
        //Preconditions
        assert uberKeyStorePath != null : "uberKeyStorePath must not be null";
        assert !uberKeyStorePath.isEmpty() : "uberKeyStorePath must not be empty";
        assert uberKeyStorePath.endsWith(".uber") : "uber keystore file extension must be .uber";
        assert uberKeyStorePassword != null : "uberKeyStorePassword must not be null";
        assert jceksKeyStorePath != null : "jceksKeyStorePath must not be null";
        assert !jceksKeyStorePath.isEmpty() : "jceksKeyStorePath must not be empty";
        assert jceksKeyStorePath.endsWith(".jceks") : "jceks keystore file extension must be .jceks";
        assert uberKeyStorePassword != null : "uberKeyStorePassword must not be null";

        LOGGER.info("copying keystore contents of " + uberKeyStorePath + " to " + jceksKeyStorePath);
        final KeyStore uberKeyStore = findOrCreateUberKeyStore(uberKeyStorePath, uberKeyStorePassword);
        final KeyStore jceksKeyStore = findOrCreateJceksKeyStore(jceksKeyStorePath, jceksKeyStorePassword);
        final Enumeration<String> aliases_enumeration = uberKeyStore.aliases();
        final PasswordProtection uberPasswordProtection = new PasswordProtection(uberKeyStorePassword);
        final PasswordProtection jceksPasswordProtection = new PasswordProtection(jceksKeyStorePassword);
        while (aliases_enumeration.hasMoreElements()) {
            final String alias = aliases_enumeration.nextElement();
            final KeyStore.Entry entry = uberKeyStore.getEntry(alias, uberPasswordProtection);
            assert entry != null;
            jceksKeyStore.setEntry(alias, entry, jceksPasswordProtection);
            LOGGER.info("  copied entry: " + alias);
        }
        jceksKeyStore.store(new FileOutputStream(jceksKeyStorePath), jceksKeyStorePassword);
    }

    /** Finds the keystore specified by the given path.
     *
     * @param filePath the file path to the keystore
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findKeyStore(final String filePath, final char[] password) throws KeyStoreException,
            IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        assert filePath.endsWith(".uber") || filePath.endsWith(".jceks") : "file extension must be .uber or .jceks";
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        KeyStore keyStore;
        if (filePath.endsWith(".uber")) {
            assert isJCEUnlimitedStrengthPolicy() : "must have unlimited security policy files installed";
            keyStore = KeyStore.getInstance("UBER", BOUNCY_CASTLE_PROVIDER);
        } else {
            keyStore = KeyStore.getInstance("JCEKS");
        }
        if (keyStoreFile.exists()) {
            try (FileInputStream keyStoreStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(keyStoreStream, password);
            }
            return keyStore;
        } else {
            return null;
        }
    }

    /** Finds or creates the PKCS12 keystore specified by the given path.
     *
     * @param filePath the file path to the keystore, having the .pkcs12 extension
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findOrCreatePKCS12KeyStore(final String filePath, final char[] password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        assert filePath.endsWith(".p12") : "file extension must be .p12";
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        final KeyStore keyStore;
        if (isJCEUnlimitedStrengthPolicy()) {
            keyStore = KeyStore.getInstance("pkcs12", BOUNCY_CASTLE_PROVIDER);
        } else {
            keyStore = KeyStore.getInstance("pkcs12");
        }
        if (keyStoreFile.exists()) {
            try (final FileInputStream fileInputStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(fileInputStream, password);
            }
        } else {
            keyStore.load(null, null);
            try (final FileOutputStream fileOutputStream = new FileOutputStream(keyStoreFile)) {
                keyStore.store(fileOutputStream, password);
            }
        }
        return keyStore;
    }

    /** Finds or creates the BKS keystore specified by the given path.
     *
     * @param filePath the file path to the keystore, having the .bks extension
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findOrCreateBKSKeyStore(final String filePath, final char[] password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        assert filePath.endsWith(".bks") : "file extension must be .bks";
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        final KeyStore keyStore;
        keyStore = KeyStore.getInstance("BKS");
        if (keyStoreFile.exists()) {
            try (final FileInputStream fileInputStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(fileInputStream, password);
            }
        } else {
            keyStore.load(null, null);
            try (final FileOutputStream fileOutputStream = new FileOutputStream(keyStoreFile)) {
                keyStore.store(fileOutputStream, password);
            }
        }
        return keyStore;
    }

    /** Finds or creates the JKS keystore specified by the given path.
     *
     * @param filePath the file path to the keystore, having the .jks extension
     * @param password the keystore password
     * @return the keystore
     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the specified type
     * @throws IOException if there is an I/O or format problem with the keystore data,
     * if a password is required but not given, or if the given password was incorrect
     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
     * @throws CertificateException if any of the certificates in the keystore could not be loaded
     * @throws NoSuchProviderException if the cryptography provider cannot be found
     */
    public static KeyStore findOrCreateJKSKeyStore(final String filePath, final char[] password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
            NoSuchProviderException {
        //Preconditions
        assert filePath != null : "filePath must not be null";
        assert filePath.endsWith(".jks") : "file extension must be .jks";
        assert password != null : "password must not be null";
        assert password.length > 0 : "password must not be empty";

        final File keyStoreFile = new File(filePath);
        final KeyStore keyStore;
        keyStore = KeyStore.getInstance("JKS");
        if (keyStoreFile.exists()) {
            try (final FileInputStream fileInputStream = new FileInputStream(keyStoreFile)) {
                keyStore.load(fileInputStream, password);
            }
        } else {
            keyStore.load(null, null);
            try (final FileOutputStream fileOutputStream = new FileOutputStream(keyStoreFile)) {
                keyStore.store(fileOutputStream, password);
            }
        }
        return keyStore;
    }

    /** Adds an entry to the specified keystore, creating the keystore if it does not already exist.
     *
     * @param keyStoreFilePath the file path to the keystore
     * @param keyStorePassword the keystore's password
     * @param certPath the certificate path to add
     * @param privateKey the private key associated with the first certificate in the path
     * @return the keystore
     * @throws KeyStoreException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws NoSuchProviderException
     */
    public static KeyStore addEntryToKeyStore(final String keyStoreFilePath, final char[] keyStorePassword,
            final CertPath certPath, final PrivateKey privateKey) throws KeyStoreException, IOException,
            NoSuchAlgorithmException, CertificateException, NoSuchProviderException {
        //Preconditions
        assert keyStoreFilePath != null : "keyStoreFilePath must not be null";
        assert !keyStoreFilePath.isEmpty() : "keyStoreFilePath must not be empty";
        assert keyStorePassword != null : "keyStorePassword must not be null";

        return addEntryToKeyStore(keyStoreFilePath, keyStorePassword, X509Utils.ENTRY_ALIAS, certPath, privateKey);
    }

    /** Adds an entry to the specified keystore, creating the keystore if it does not already exist.
     *
     * @param keyStoreFilePath the file path to the keystore
     * @param keyStorePassword the keystore's password
     * @param alias the entry alias
     * @param certPath the certificate path to add
     * @param privateKey the private key associated with the first certificate in the path
     * @return the keystore
     * @throws KeyStoreException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws NoSuchProviderException
     */
    public static KeyStore addEntryToKeyStore(final String keyStoreFilePath, final char[] keyStorePassword,
            final String alias, final CertPath certPath, final PrivateKey privateKey) throws KeyStoreException,
            IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException {
        //Preconditions
        assert keyStoreFilePath != null : "keyStoreFilePath must not be null";
        assert !keyStoreFilePath.isEmpty() : "keyStoreFilePath must not be empty";
        assert keyStorePassword != null : "keyStorePassword must not be null";
        assert alias != null : "alias must not be null";
        assert !alias.isEmpty() : "alias must not be empty";

        final KeyStore keyStore = X509Utils.findOrCreateKeyStore(keyStoreFilePath, keyStorePassword);
        final Certificate[] certificateChain = new Certificate[certPath.getCertificates().size() + 1];
        for (int i = 0; i < certPath.getCertificates().size(); i++) {
            certificateChain[i] = certPath.getCertificates().get(i);
        }
        certificateChain[certPath.getCertificates().size()] = X509Utils.getRootX509Certificate();
        keyStore.setKeyEntry(alias, privateKey, keyStorePassword, certificateChain);
        keyStore.store(new FileOutputStream(keyStoreFilePath), keyStorePassword);

        //Postconditions
        assert keyStore != null : "keyStore must not be null";

        return keyStore;
    }

    /** Resets the certificate serial number.
     *
     * @throws IOException if an input/output error occurred
     */
    protected static void resetSerialNumber() throws IOException {
        File serialNumberFile = new File("../X509Security/data/certificate-serial-nbr.txt");
        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(serialNumberFile))) {
            bufferedWriter.write("0");
        }
    }

    /** Returns the next certificate serial number.
     *
     * @return the next certificate serial number
     * @throws IOException if an input/output error occurred
     */
    protected static BigInteger getNextSerialNumber() throws IOException {
        @SuppressWarnings("UnusedAssignment")
        File serialNumberFile = null;
        File dataDirectoryFile = new File("../X509Security/data");
        if (dataDirectoryFile.exists()) {
            serialNumberFile = new File("../X509Security/data/certificate-serial-nbr.txt");
            if (!serialNumberFile.exists()) {
                try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(serialNumberFile))) {
                    bufferedWriter.write("0");
                }
            }
        } else {
            // for testing the message router
            dataDirectoryFile = new File("data");
            assert dataDirectoryFile.exists();
            serialNumberFile = new File("data/certificate-serial-nbr.txt");
            if (!serialNumberFile.exists()) {
                try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(serialNumberFile))) {
                    bufferedWriter.write("0");
                }
            }
        }
        final String line;
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(serialNumberFile))) {
            line = bufferedReader.readLine();
        }
        final Long nextSerialNumber = Long.valueOf(line.trim()) + 1L;
        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(serialNumberFile))) {
            bufferedWriter.write(String.valueOf(nextSerialNumber));
        }
        return new BigInteger(String.valueOf(nextSerialNumber));
    }

    /** Returns whether this is the trusted development system.
     *
     * @return whether this is the trusted development system
     */
    public static boolean isTrustedDevelopmentSystem() {
        final File keyStorePasswordFile = new File(System.getenv("SECURITY_DIR") + "/texai-keystore-password.txt");
        return keyStorePasswordFile.exists();
    }

    /** Returns a certificate path consisting of the given certificate array.
     *
     * @param certificates the given certificate array
     * @return the certificate path consisting of the given certificate list
     * @throws CertificateException if an invalid certificate is present
     * @throws NoSuchProviderException if the cryptography service provider is not found
     */
    public static CertPath generateCertPath(final Certificate[] certificates)
            throws CertificateException, NoSuchProviderException {
        final List<Certificate> certificateList = new ArrayList<>();
        for (final Certificate certificate : certificates) {
            certificateList.add(certificate);
        }
        return generateCertPath(certificateList);
    }

    /** Returns a certificate path consisting of the given certificate list.
     *
     * @param certificateList the given certificate list
     * @return the certificate path consisting of the given certificate list
     * @throws CertificateException if an invalid certificate is present
     * @throws NoSuchProviderException if the cryptography service provider is not found
     */
    public static CertPath generateCertPath(final List<Certificate> certificateList)
            throws CertificateException, NoSuchProviderException {
        final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509",
                X509Utils.BOUNCY_CASTLE_PROVIDER);
        return certificateFactory.generateCertPath(certificateList);
    }

    /** Validates the given X.509 certificate path, throwing an exception if the path is invalid.
     *
     * @param certPath the given X.509 certificate path, which does not include the trust anchor in contrast to a
     * certificate chain that does
     *
     * @throws InvalidAlgorithmParameterException if an invalid certificate path validation parameter is provided
     * @throws NoSuchAlgorithmException if an invalid encryption algorithm is specified
     * @throws CertPathValidatorException if the given x.509 certificate path is invalid
     */
    public static void validateCertificatePath(final CertPath certPath)
            throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, CertPathValidatorException {
        //Preconditions
        assert certPath != null : "certPath must not be null";

        final Set<TrustAnchor> trustAnchors = new HashSet<>();
        trustAnchors.add(new TrustAnchor(X509Utils.getRootX509Certificate(), null)); // nameConstraints
        final PKIXParameters params = new PKIXParameters(trustAnchors);
        params.setSigProvider(BOUNCY_CASTLE_PROVIDER);
        params.setRevocationEnabled(false);
        final CertPathValidator certPathValidator = CertPathValidator
                .getInstance(CertPathValidator.getDefaultType());
        certPathValidator.validate(certPath, params);
    }

    /** Generates the X.509 security information.
     *
     * @param keyPair the key pair for the generated certificate
     * @param issuerPrivateKey the issuer's private key
     * @param issuerCertificate the issuer's certificate
     * @param uid the generated certificate's subject UID
     * @param keystorePassword the keystore password
     * @param isJCEUnlimitedStrengthPolicy the indicator whether the generated X.509 security information will be
     * hosted on a system with unlimited strength policy
     * @param domainComponent the domain component
     * @return the X509 security information
     */
    public static X509SecurityInfo generateX509SecurityInfo(final KeyPair keyPair,
            final PrivateKey issuerPrivateKey, final X509Certificate issuerCertificate, final UUID uid,
            final char[] keystorePassword, final boolean isJCEUnlimitedStrengthPolicy,
            final String domainComponent) {
        //Preconditions
        assert keyPair != null : "keyPair must not be null";
        assert issuerPrivateKey != null : "issuerPrivateKey must not be null";
        assert issuerCertificate != null : "issuerCertificate must not be null";
        assert uid != null : "uid must not be null";
        assert keystorePassword != null : "keystorePassword must not be null";

        try {
            final X509Certificate x509Certificate = generateX509Certificate(keyPair.getPublic(), issuerPrivateKey,
                    issuerCertificate, uid, domainComponent);
            assert X509Utils.isJCEUnlimitedStrengthPolicy();

            final KeyStore keyStore;
            if (isJCEUnlimitedStrengthPolicy) {
                keyStore = KeyStore.getInstance("UBER", BOUNCY_CASTLE_PROVIDER);
            } else {
                keyStore = KeyStore.getInstance("JCEKS");
            }
            keyStore.load(null, null);
            keyStore.setKeyEntry(X509Utils.ENTRY_ALIAS, keyPair.getPrivate(), keystorePassword,
                    new Certificate[] { x509Certificate, X509Utils.getRootX509Certificate() });

            return new X509SecurityInfo(X509Utils.getTruststore(), keyStore, keystorePassword, null);
        } catch (NoSuchProviderException | NoSuchAlgorithmException | SignatureException | InvalidKeyException
                | IOException | KeyStoreException | CertificateException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Gets the X509 security information for the given keystore and private key alias.
     *
     * @param keyStoreFilePath the file path to the keystore
     * @param keyStorePassword the keystore password
     * @param alias the private key entry alias
     * @return the X509 security information for a test client
     */
    public static X509SecurityInfo getX509SecurityInfo(final String keyStoreFilePath, final char[] keyStorePassword,
            final String alias) {
        //Preconditions
        assert keyStoreFilePath != null : "keyStoreFilePath must not be null";
        assert !keyStoreFilePath.isEmpty() : "keyStoreFilePath must not be empty";
        assert keyStoreFilePath.endsWith(".uber")
                || keyStoreFilePath.endsWith(".jceks") : "keystore file extension must be .uber or .jceks";
        assert keyStorePassword != null : "keyStorePassword must not be null";

        try {
            return new X509SecurityInfo(X509Utils.getTruststore(),
                    findOrCreateKeyStore(keyStoreFilePath, keyStorePassword), // keyStore
                    keyStorePassword, alias);
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException
                | NoSuchProviderException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Initializes the installer keystore on the trusted development system, from where it is copied into the distributed code. */
    public static synchronized void initializeInstallerKeyStore() {
        if (!X509Utils.isTrustedDevelopmentSystem()) {
            return;
        }
        String filePath = "data/installer-keystore.uber";
        File file = new File(filePath);
        if (file.exists()) {
            // do not overwrite it
            return;
        }
        try {
            LOGGER.info("creating the installer keystores");
            // the installer keystore consists of the single client X.509 certificate, which is generated and signed by
            // the Texai root certificate on the developement system that has the root private key.
            final KeyPair installerKeyPair = X509Utils.generateRSAKeyPair2048();
            final X509Certificate installerX509Certificate = X509Utils.generateX509Certificate(
                    installerKeyPair.getPublic(), X509Utils.getRootPrivateKey(), X509Utils.getRootX509Certificate(),
                    null);

            // proceed as though the JCE unlimited strength jurisdiction policy files are installed, which they will be on the
            // trusted development system.
            LOGGER.info("creating installer-keystore.uber");
            assert X509Utils.isJCEUnlimitedStrengthPolicy();
            KeyStore installerKeyStore = X509Utils.findOrCreateKeyStore(filePath, INSTALLER_KEYSTORE_PASSWORD);
            installerKeyStore.setKeyEntry(X509Utils.ENTRY_ALIAS, installerKeyPair.getPrivate(),
                    INSTALLER_KEYSTORE_PASSWORD,
                    new Certificate[] { installerX509Certificate, X509Utils.getRootX509Certificate() });
            installerKeyStore.store(new FileOutputStream(filePath), INSTALLER_KEYSTORE_PASSWORD);

            // then proceed after disabling the JCE unlimited strength jurisdiction policy files indicator
            X509Utils.setIsJCEUnlimitedStrengthPolicy(false);
            filePath = "data/installer-keystore.jceks";
            LOGGER.info("creating installer-keystore.jceks");
            installerKeyStore = X509Utils.findOrCreateKeyStore(filePath, INSTALLER_KEYSTORE_PASSWORD);
            installerKeyStore.setKeyEntry(X509Utils.ENTRY_ALIAS, installerKeyPair.getPrivate(),
                    INSTALLER_KEYSTORE_PASSWORD,
                    new Certificate[] { installerX509Certificate, X509Utils.getRootX509Certificate() });
            installerKeyStore.store(new FileOutputStream(filePath), INSTALLER_KEYSTORE_PASSWORD);
            // restore the JCE unlimited strength jurisdiction policy files indicator
            X509Utils.setIsJCEUnlimitedStrengthPolicy(true);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException
                | SignatureException | InvalidKeyException | IOException | KeyStoreException
                | CertificateException ex) {
            LOGGER.error(StringUtils.getStackTraceAsString(ex));
            throw new TexaiException(ex);
        }

        //Postconditions
        assert !isTrustedDevelopmentSystem()
                || X509Utils.isJCEUnlimitedStrengthPolicy() : "JCE unlimited strength policy must be in effect";
    }

    /** Generates a self-signed certificate to use as a CA root certificate.
     *
     * @param keyPair the root public/private key pair
     * @return a self-signed CA root certificate
     *
     * @throws CertificateEncodingException when the certificate cannot be encoded
     * @throws NoSuchProviderException when an invalid provider is given
     * @throws NoSuchAlgorithmException when an invalid algorithm is given
     * @throws SignatureException when the an invalid signature is present
     * @throws InvalidKeyException when the given key is invalid
     * @throws IOException if an input/output error occurs while processing the serial number file
     */
    protected static X509Certificate generateRootX509Certificate(final KeyPair keyPair)
            throws CertificateEncodingException, NoSuchProviderException, NoSuchAlgorithmException,
            SignatureException, InvalidKeyException, IOException {
        //Preconditions
        assert keyPair != null : "keyPair must not be null";

        final UUID rootUUID = UUID.randomUUID();
        // provide items to X500Principal in reverse order
        final X500Principal rootX500Principal = new X500Principal(
                "UID=" + rootUUID + ", O=Texai Certification Authority, CN=texai.org");
        final X500Name subject = new X500Name(rootX500Principal.getName());
        final X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder(
                new X500Name(rootX500Principal.getName()), // issuer,
                getNextSerialNumber(), // serial
                new Date(System.currentTimeMillis() - 10000L), // notBefore,
                new Date(System.currentTimeMillis() + VALIDITY_PERIOD), // notAfter,
                subject, new SubjectPublicKeyInfo(ASN1Sequence.getInstance(keyPair.getPublic().getEncoded()))); // publicKeyInfo

        // see http://www.ietf.org/rfc/rfc3280.txt
        // see http://stackoverflow.com/questions/20175447/creating-certificates-for-ssl-communication
        final JcaX509ExtensionUtils jcaX509ExtensionUtils = new JcaX509ExtensionUtils();

        // Add subject key identifier
        x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false, // isCritical
                jcaX509ExtensionUtils.createSubjectKeyIdentifier(keyPair.getPublic()));

        // add basic constraints
        x509v3CertificateBuilder.addExtension(Extension.basicConstraints, true, // isCritical
                new BasicConstraints(true)); // is a CA certificate with an unlimited certification path length

        final KeyUsage keyUsage = new KeyUsage(
                // the keyCertSign bit indicates that the subject public key may be used for verifying a signature on
                // certificates
                KeyUsage.keyCertSign | // the cRLSign indicates that the subject public key may be used for verifying a signature on revocation
                                       // information
                        KeyUsage.cRLSign);

        // add key usage
        x509v3CertificateBuilder.addExtension(Extension.keyUsage, true, // isCritical
                keyUsage);

        X509Certificate rootX509Certificate;
        try {
            final ContentSigner contentSigner = new JcaContentSignerBuilder(DIGITAL_SIGNATURE_ALGORITHM)
                    .setProvider(BOUNCY_CASTLE_PROVIDER).build(keyPair.getPrivate());
            final X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(contentSigner);
            final JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter();
            rootX509Certificate = jcaX509CertificateConverter.getCertificate(x509CertificateHolder);
        } catch (CertificateException | OperatorCreationException ex) {
            throw new TexaiException(ex);
        }

        //Postconditions
        try {
            rootX509Certificate.checkValidity();
            rootX509Certificate.verify(keyPair.getPublic());

            return rootX509Certificate;
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException
                | CertificateException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Creates the Texai root X.509 certificate keystore on the trusted development system.  This
     * keystore also includes a jar-signing certificate.
     */
    protected static synchronized void createTexaiRootKeyStore() {
        //Preconditions
        assert !isTrustedDevelopmentSystem() || X509Utils.isJCEUnlimitedStrengthPolicy();

        if (!isTrustedDevelopmentSystem()) {
            return;
        }
        final char[] keyStorePassword = getRootKeyStorePassword();
        assert keyStorePassword != null;
        final String filePath = System.getenv("SECURITY_DIR") + "/texai-keystore.jceks";
        final File serverKeyStoreFile = new File(filePath);
        if (serverKeyStoreFile.exists()) {
            // do not overwrite it
            return;
        }
        try {
            LOGGER.info("creating Texai root key pair");
            final KeyPair rootKeyPair = generateRSAKeyPair3072();
            LOGGER.info("creating Texai root X.509 certificate");
            final X509Certificate rootX509Certificate = generateRootX509Certificate(rootKeyPair);
            LOGGER.info("root certificate...\n" + rootX509Certificate);
            final StringBuilder stringBuilder = new StringBuilder();
            for (final byte b : rootX509Certificate.getEncoded()) {
                stringBuilder.append(Byte.toString(b));
                stringBuilder.append(", ");
            }
            LOGGER.info("root certificate...\n" + rootX509Certificate);
            LOGGER.info("\nroot certificate bytes...\n" + stringBuilder.toString());
            LOGGER.info("creating Texai root X.509 certificate keystore");
            final KeyStore rootKeyStore = X509Utils.findOrCreateJceksKeyStore(filePath, keyStorePassword);
            rootKeyStore.setKeyEntry(ROOT_ALIAS, rootKeyPair.getPrivate(), keyStorePassword,
                    new Certificate[] { rootX509Certificate });

            // create and store the jar-signer certificate
            LOGGER.info("creating jar-signer key pair");
            final KeyPair jarSignerKeyPair = generateRSAKeyPair2048();
            LOGGER.info("creating jar-signer X.509 certificate");
            final UUID jarSignerUUID = UUID.randomUUID();
            LOGGER.info("jar-signer UUID: " + jarSignerUUID);
            final X509Certificate jarSignerX509Certificate = generateX509Certificate(jarSignerKeyPair.getPublic(),
                    rootKeyPair.getPrivate(), rootX509Certificate, jarSignerUUID, "RootCertificate"); // domainComponent
            LOGGER.info("jar-signer certificate:\n" + jarSignerX509Certificate);
            rootKeyStore.setKeyEntry(JAR_SIGNER_ALIAS, jarSignerKeyPair.getPrivate(), keyStorePassword,
                    new Certificate[] { jarSignerX509Certificate, rootX509Certificate });
            rootKeyStore.store(new FileOutputStream(filePath), keyStorePassword);

            //Postconditions
            final PrivateKey privateKey = (PrivateKey) rootKeyStore.getKey(ROOT_ALIAS, keyStorePassword);
            assert privateKey != null;

        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException
                | SignatureException | InvalidKeyException | IOException | KeyStoreException | CertificateException
                | UnrecoverableKeyException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Gets the root keystore password, or null if not executing on a trusted root system.
     *
     * @return the root keystore password, or null if not executing on a trusted root system
     */
    protected static char[] getRootKeyStorePassword() {
        if (!isTrustedDevelopmentSystem()) {
            return null;
        }
        File keyStorePasswordFile = new File(System.getenv("SECURITY_DIR") + "/texai-keystore-password.txt");
        try {
            final char[] keyStorePassword;
            try (BufferedReader bufferedReader = new BufferedReader(new FileReader(keyStorePasswordFile))) {
                final String keyStorePasswordString = bufferedReader.readLine().trim();
                keyStorePassword = keyStorePasswordString.toCharArray();
                assert keyStorePassword != null;
                assert keyStorePassword.length > 0;
            }
            return keyStorePassword;
        } catch (IOException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Gets the root private key or null if not executing on a trusted root system.
     *
     * @return the root private key or null if not executing on a trusted root system
     */
    public static PrivateKey getRootPrivateKey() {
        if (!isTrustedDevelopmentSystem()) {
            return null;
        }
        try {
            final String filePath = System.getenv("SECURITY_DIR") + "/texai-keystore.jceks";
            final File serverKeyStoreFile = new File(filePath);
            assert serverKeyStoreFile.exists();
            final char[] keyStorePassword = getRootKeyStorePassword();
            assert keyStorePassword != null;
            final boolean isJCEUnlimitedStrenthPolicy1 = isJCEUnlimitedStrengthPolicy();
            setIsJCEUnlimitedStrengthPolicy(true);
            final KeyStore rootKeyStore = findKeyStore(System.getenv("SECURITY_DIR") + "/texai-keystore.jceks",
                    getRootKeyStorePassword());
            setIsJCEUnlimitedStrengthPolicy(isJCEUnlimitedStrenthPolicy1);
            assert rootKeyStore != null;
            final PrivateKey privateKey = (PrivateKey) rootKeyStore.getKey(ROOT_ALIAS, keyStorePassword);

            //Postconditions
            assert privateKey != null : "privateKey must not be null";
            assert !isTrustedDevelopmentSystem()
                    || X509Utils.isJCEUnlimitedStrengthPolicy() : "JCE unlimited strength policy must be in effect";

            return privateKey;
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException
                | NoSuchProviderException | UnrecoverableKeyException ex) {
            throw new TexaiException(ex);
        }
    }

    /** Initializes the truststore on the trusted development system, from where the truststore is copied to the
     * code repository. */
    public static synchronized void initializeTrustore() {
        if (!isTrustedDevelopmentSystem()) {
            return;
        }

        // The truststore consists of the single Texai root X.509 certificate, which was generated and self-signed on the
        // developement system that has its private key. Proceed as though the JCE unlimited strength jurisdiction policy
        // files are installed, which they will be on the trusted development system.
        assert isJCEUnlimitedStrengthPolicy();
        String filePath = "data/truststore.uber";

        try {
            LOGGER.info("creating truststore.uber");
            (new File(filePath)).delete();
            truststore = X509Utils.findOrCreateKeyStore(filePath, TRUSTSTORE_PASSWORD);
            truststore.setCertificateEntry(TRUSTSTORE_ENTRY_ALIAS, getRootX509Certificate());
            final FileOutputStream fileOutputStream = new FileOutputStream(filePath);
            truststore.store(fileOutputStream, TRUSTSTORE_PASSWORD);
            assert "UBER".equals(truststore.getType());
            Enumeration<String> aliases = truststore.aliases();
            int aliasCnt = 0;
            while (aliases.hasMoreElements()) {
                aliasCnt++;
                aliases.nextElement();
            }
            assert aliasCnt > 0;

            // then proceed after disabling the JCE unlimited strength jurisdiction policy files indicator
            setIsJCEUnlimitedStrengthPolicy(false);
            filePath = "data/truststore.jceks";
            (new File(filePath)).delete();
            LOGGER.info("creating truststore.jceks");
            truststore = X509Utils.findOrCreateKeyStore(filePath, TRUSTSTORE_PASSWORD);
            truststore.setCertificateEntry(TRUSTSTORE_ENTRY_ALIAS, getRootX509Certificate());
            truststore.store(new FileOutputStream(filePath), TRUSTSTORE_PASSWORD);
            assert "JCEKS".equals(truststore.getType());
            aliases = truststore.aliases();
            aliasCnt = 0;
            while (aliases.hasMoreElements()) {
                aliasCnt++;
                aliases.nextElement();
            }
            assert aliasCnt > 0;

            filePath = "data/truststore.jks";
            (new File(filePath)).delete();
            LOGGER.info("creating truststore.jks");
            truststore = X509Utils.findOrCreateJKSKeyStore(filePath, TRUSTSTORE_PASSWORD);
            truststore.setCertificateEntry(TRUSTSTORE_ENTRY_ALIAS, getRootX509Certificate());
            truststore.store(new FileOutputStream(filePath), TRUSTSTORE_PASSWORD);
            assert "JKS".equals(truststore.getType());
            aliases = truststore.aliases();
            aliasCnt = 0;
            while (aliases.hasMoreElements()) {
                aliasCnt++;
                aliases.nextElement();
            }
            assert aliasCnt > 0;

            filePath = "data/truststore.bks";
            (new File(filePath)).delete();
            LOGGER.info("creating truststore.bks");
            truststore = X509Utils.findOrCreateBKSKeyStore(filePath, TRUSTSTORE_PASSWORD);
            truststore.setCertificateEntry(TRUSTSTORE_ENTRY_ALIAS, getRootX509Certificate());
            truststore.store(new FileOutputStream(filePath), TRUSTSTORE_PASSWORD);
            assert "BKS".equals(truststore.getType());
            aliases = truststore.aliases();
            aliasCnt = 0;
            while (aliases.hasMoreElements()) {
                aliasCnt++;
                aliases.nextElement();
            }
            assert aliasCnt > 0;

            // create the PKCS12 keystore from which the trusted certificate can be imported into a web browser
            filePath = "data/truststore.p12";
            (new File(filePath)).delete();
            LOGGER.info("creating truststore.p12");
            truststore = X509Utils.findOrCreatePKCS12KeyStore(filePath, TRUSTSTORE_PASSWORD);
            truststore.setCertificateEntry(TRUSTSTORE_ENTRY_ALIAS, getRootX509Certificate());
            truststore.store(new FileOutputStream(filePath), TRUSTSTORE_PASSWORD);
            assert truststore != null;
            assert "pkcs12".equals(truststore.getType());
            aliases = truststore.aliases();
            aliasCnt = 0;
            while (aliases.hasMoreElements()) {
                aliasCnt++;
                aliases.nextElement();
            }
            assert aliasCnt > 0;

        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException
                | NoSuchProviderException ex) {
            throw new TexaiException(ex);
        } finally {
            // restore the JCE unlimited strength jurisdiction policy files indicator
            setIsJCEUnlimitedStrengthPolicy(true);
            // refresh the cached reference to the truststore
            truststore = null;
            getTruststore();

            //Postconditions
            assert !isTrustedDevelopmentSystem()
                    || X509Utils.isJCEUnlimitedStrengthPolicy() : "JCE unlimited strength policy must be in effect";
        }
    }

    /** Logs the aliases contained in the given keystore.
        
    @param keyStore the given keystore
    */
    public static void logAliases(final KeyStore keyStore) {
        Enumeration<String> aliases;
        try {
            aliases = keyStore.aliases();
        } catch (KeyStoreException ex) {
            throw new TexaiException(ex);
        }
        LOGGER.info("aliases...");
        while (aliases.hasMoreElements()) {
            LOGGER.info("  " + aliases.nextElement());
        }
    }
}