/* * 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()); } } }