Browse Source

Remove BouncyCastle dependency from runtime (#32193)

* Remove BouncyCastle dependency from runtime

This commit introduces a new gradle  project that contains
 the classes that have a dependency on BouncyCastle. For 
the default distribution, It builds  a jar from those and
 in puts it in a subdirectory of lib
 (/tools/security-cli) along with the BouncyCastle jars. 
This directory is then passed in the
ES_ADDITIONAL_CLASSPATH_DIRECTORIES of the CLI tools 
that use these classes.

BouncyCastle is removed as a runtime dependency (remains
as a compileOnly one) from x-pack core and x-pack security.
Ioannis Kakavas 7 years ago
parent
commit
aaa8f842d6
25 changed files with 294 additions and 172 deletions
  1. 1 1
      distribution/archives/build.gradle
  2. 18 10
      distribution/build.gradle
  3. 1 1
      distribution/packages/build.gradle
  4. 22 0
      qa/vagrant/src/main/java/org/elasticsearch/packaging/test/ArchiveTestCase.java
  5. 1 0
      qa/vagrant/src/main/java/org/elasticsearch/packaging/util/Installation.java
  6. 1 3
      x-pack/plugin/core/build.gradle
  7. 5 5
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java
  8. 2 3
      x-pack/plugin/security/build.gradle
  9. 20 0
      x-pack/plugin/security/cli/build.gradle
  10. 0 0
      x-pack/plugin/security/cli/licenses/bcpkix-jdk15on-1.59.jar.sha1
  11. 0 0
      x-pack/plugin/security/cli/licenses/bcprov-jdk15on-1.59.jar.sha1
  12. 0 0
      x-pack/plugin/security/cli/licenses/bouncycastle-LICENSE.txt
  13. 0 0
      x-pack/plugin/security/cli/licenses/bouncycastle-NOTICE.txt
  14. 11 11
      x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertGenUtils.java
  15. 69 59
      x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java
  16. 61 59
      x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java
  17. 2 1
      x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertGenUtilsTests.java
  18. 8 6
      x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateGenerateToolTests.java
  19. 11 9
      x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java
  20. 23 0
      x-pack/plugin/security/cli/src/test/resources/org/elasticsearch/xpack/security/cli/testnode.crt
  21. 30 0
      x-pack/plugin/security/cli/src/test/resources/org/elasticsearch/xpack/security/cli/testnode.pem
  22. 2 1
      x-pack/plugin/security/src/main/bin/elasticsearch-certgen
  23. 2 1
      x-pack/plugin/security/src/main/bin/elasticsearch-certgen.bat
  24. 2 1
      x-pack/plugin/security/src/main/bin/elasticsearch-certutil
  25. 2 1
      x-pack/plugin/security/src/main/bin/elasticsearch-certutil.bat

+ 1 - 1
distribution/archives/build.gradle

@@ -49,7 +49,7 @@ CopySpec archiveFiles(CopySpec modulesFiles, String distributionType, boolean os
   return copySpec {
     into("elasticsearch-${version}") {
       into('lib') {
-        with libFiles
+        with libFiles(oss)
       }
       into('config') {
         dirMode 0750

+ 18 - 10
distribution/build.gradle

@@ -227,16 +227,24 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
     /*****************************************************************************
      *                   Common files in all distributions                       *
      *****************************************************************************/
-    libFiles = copySpec {
-      // delay by using closures, since they have not yet been configured, so no jar task exists yet
-      from { project(':server').jar }
-      from { project(':server').configurations.runtime }
-      from { project(':libs:plugin-classloader').jar }
-      from { project(':distribution:tools:java-version-checker').jar }
-      from { project(':distribution:tools:launchers').jar }
-      into('tools/plugin-cli') {
-        from { project(':distribution:tools:plugin-cli').jar }
-        from { project(':distribution:tools:plugin-cli').configurations.runtime }
+    libFiles = { oss ->
+      copySpec {
+        // delay by using closures, since they have not yet been configured, so no jar task exists yet
+        from { project(':server').jar }
+        from { project(':server').configurations.runtime }
+        from { project(':libs:plugin-classloader').jar }
+        from { project(':distribution:tools:java-version-checker').jar }
+        from { project(':distribution:tools:launchers').jar }
+        into('tools/plugin-cli') {
+          from { project(':distribution:tools:plugin-cli').jar }
+          from { project(':distribution:tools:plugin-cli').configurations.runtime }
+        }
+        if (oss == false) {
+          into('tools/security-cli') {
+            from { project(':x-pack:plugin:security:cli').jar }
+            from { project(':x-pack:plugin:security:cli').configurations.compile }
+          }
+        }
       }
     }
 

+ 1 - 1
distribution/packages/build.gradle

@@ -126,7 +126,7 @@ Closure commonPackageConfig(String type, boolean oss) {
       }
       into('lib') {
         with copySpec {
-          with libFiles
+          with libFiles(oss)
           // we need to specify every intermediate directory so we iterate through the parents; duplicate calls with the same part are fine
           eachFile { FileCopyDetails fcp ->
             String[] segments = fcp.relativePath.segments

+ 22 - 0
qa/vagrant/src/main/java/org/elasticsearch/packaging/test/ArchiveTestCase.java

@@ -57,6 +57,7 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.isEmptyString;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
@@ -302,5 +303,26 @@ public abstract class ArchiveTestCase extends PackagingTestCase {
         }
     }
 
+    public void test90SecurityCliPackaging() {
+        assumeThat(installation, is(notNullValue()));
+
+        final Installation.Executables bin = installation.executables();
+        final Shell sh = new Shell();
+
+        if (distribution().equals(Distribution.DEFAULT_TAR) || distribution().equals(Distribution.DEFAULT_ZIP)) {
+            assertTrue(Files.exists(installation.lib.resolve("tools").resolve("security-cli")));
+            Platforms.onLinux(() -> {
+                final Result result = sh.run(bin.elasticsearchCertutil + " help");
+                assertThat(result.stdout, containsString("Simplifies certificate creation for use with the Elastic Stack"));
+            });
+
+            Platforms.onWindows(() -> {
+                final Result result = sh.run(bin.elasticsearchCertutil + " help");
+                assertThat(result.stdout, containsString("Simplifies certificate creation for use with the Elastic Stack"));
+            });
+        } else if (distribution().equals(Distribution.OSS_TAR) || distribution().equals(Distribution.OSS_ZIP)) {
+            assertFalse(Files.exists(installation.lib.resolve("tools").resolve("security-cli")));
+        }
+    }
 
 }

+ 1 - 0
qa/vagrant/src/main/java/org/elasticsearch/packaging/util/Installation.java

@@ -101,6 +101,7 @@ public class Installation {
         public final Path elasticsearchPlugin = platformExecutable("elasticsearch-plugin");
         public final Path elasticsearchKeystore = platformExecutable("elasticsearch-keystore");
         public final Path elasticsearchTranslog = platformExecutable("elasticsearch-translog");
+        public final Path elasticsearchCertutil = platformExecutable("elasticsearch-certutil");
 
         private Path platformExecutable(String name) {
             final String platformExecutableName = Platforms.WINDOWS

+ 1 - 3
x-pack/plugin/core/build.gradle

@@ -20,7 +20,6 @@ esplugin {
 }
 
 dependencyLicenses {
-  mapping from: /bc.*/, to: 'bouncycastle'
   mapping from: /http.*/, to: 'httpclient' // pulled in by rest client
   mapping from: /commons-.*/, to: 'commons' // pulled in by rest client
 }
@@ -38,8 +37,6 @@ dependencies {
 
     // security deps
     compile 'com.unboundid:unboundid-ldapsdk:3.2.0'
-    compile 'org.bouncycastle:bcprov-jdk15on:1.59'
-    compile 'org.bouncycastle:bcpkix-jdk15on:1.59'
     compile project(path: ':modules:transport-netty4', configuration: 'runtime')
 
     testCompile 'org.elasticsearch:securemock:1.2'
@@ -116,6 +113,7 @@ task testJar(type: Jar) {
     appendix 'test'
     from sourceSets.test.output
 }
+
 artifacts {
     // normal es plugins do not publish the jar but we need to since users need it for Transport Clients and extensions
     archives jar

+ 5 - 5
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java

@@ -63,7 +63,7 @@ public class CertParsingUtils {
         return PathUtils.get(path).normalize();
     }
 
-    static KeyStore readKeyStore(Path path, String type, char[] password)
+    public static KeyStore readKeyStore(Path path, String type, char[] password)
             throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
         try (InputStream in = Files.newInputStream(path)) {
             KeyStore store = KeyStore.getInstance(type);
@@ -108,7 +108,7 @@ public class CertParsingUtils {
         return certificates.toArray(new X509Certificate[0]);
     }
 
-    static List<Certificate> readCertificates(InputStream input) throws CertificateException, IOException {
+    public static List<Certificate> readCertificates(InputStream input) throws CertificateException, IOException {
         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
         Collection<Certificate> certificates = (Collection<Certificate>) certFactory.generateCertificates(input);
         return new ArrayList<>(certificates);
@@ -140,7 +140,7 @@ public class CertParsingUtils {
     /**
      * Creates a {@link KeyStore} from a PEM encoded certificate and key file
      */
-    static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword)
+    public static KeyStore getKeyStoreFromPEM(Path certificatePath, Path keyPath, char[] keyPassword)
             throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
         final PrivateKey key = PemUtils.readPrivateKey(keyPath, () -> keyPassword);
         final Certificate[] certificates = readCertificates(Collections.singletonList(certificatePath));
@@ -168,7 +168,7 @@ public class CertParsingUtils {
     /**
      * Returns a {@link X509ExtendedKeyManager} that is built from the provided keystore
      */
-    static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm)
+    public static X509ExtendedKeyManager keyManager(KeyStore keyStore, char[] password, String algorithm)
             throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException {
         KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
         kmf.init(keyStore, password);
@@ -271,7 +271,7 @@ public class CertParsingUtils {
     /**
      * Creates a {@link X509ExtendedTrustManager} based on the trust material in the provided {@link KeyStore}
      */
-    static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm)
+    public static X509ExtendedTrustManager trustManager(KeyStore keyStore, String algorithm)
             throws NoSuchAlgorithmException, KeyStoreException {
         TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
         tmf.init(keyStore);

+ 2 - 3
x-pack/plugin/security/build.gradle

@@ -22,8 +22,8 @@ dependencies {
     testCompile project(path: xpackModule('core'), configuration: 'testArtifacts')
 
     compile 'com.unboundid:unboundid-ldapsdk:3.2.0'
-    compile 'org.bouncycastle:bcprov-jdk15on:1.59'
-    compile 'org.bouncycastle:bcpkix-jdk15on:1.59'
+    compileOnly 'org.bouncycastle:bcprov-jdk15on:1.59'
+    compileOnly 'org.bouncycastle:bcpkix-jdk15on:1.59'
 
     // the following are all SAML dependencies - might as well download the whole internet
     compile "org.opensaml:opensaml-core:3.3.0"
@@ -79,7 +79,6 @@ sourceSets.test.resources {
     srcDir '../core/src/test/resources'
 }
 dependencyLicenses {
-    mapping from: /bc.*/, to: 'bouncycastle'
     mapping from: /java-support|opensaml-.*/, to: 'shibboleth'
     mapping from: /http.*/, to: 'httpclient'
 }

+ 20 - 0
x-pack/plugin/security/cli/build.gradle

@@ -0,0 +1,20 @@
+apply plugin: 'elasticsearch.build'
+
+archivesBaseName = 'elasticsearch-security-cli'
+
+dependencies {
+    compileOnly "org.elasticsearch:elasticsearch:${version}"
+    compileOnly xpackProject('plugin:core')
+    compile 'org.bouncycastle:bcprov-jdk15on:1.59'
+    compile 'org.bouncycastle:bcpkix-jdk15on:1.59'
+    testImplementation 'com.google.jimfs:jimfs:1.1'
+    testCompile "junit:junit:${versions.junit}"
+    testCompile "org.hamcrest:hamcrest-all:${versions.hamcrest}"
+    testCompile 'org.elasticsearch:securemock:1.2'
+    testCompile "org.elasticsearch.test:framework:${version}"
+    testCompile project(path: xpackModule('core'), configuration: 'testArtifacts')
+}
+
+dependencyLicenses {
+    mapping from: /bc.*/, to: 'bouncycastle'
+}

+ 0 - 0
x-pack/plugin/core/licenses/bcpkix-jdk15on-1.59.jar.sha1 → x-pack/plugin/security/cli/licenses/bcpkix-jdk15on-1.59.jar.sha1


+ 0 - 0
x-pack/plugin/core/licenses/bcprov-jdk15on-1.59.jar.sha1 → x-pack/plugin/security/cli/licenses/bcprov-jdk15on-1.59.jar.sha1


+ 0 - 0
x-pack/plugin/core/licenses/bouncycastle-LICENSE.txt → x-pack/plugin/security/cli/licenses/bouncycastle-LICENSE.txt


+ 0 - 0
x-pack/plugin/core/licenses/bouncycastle-NOTICE.txt → x-pack/plugin/security/cli/licenses/bouncycastle-NOTICE.txt


+ 11 - 11
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertGenUtils.java → x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertGenUtils.java

@@ -3,7 +3,7 @@
  * or more contributor license agreements. Licensed under the Elastic License;
  * you may not use this file except in compliance with the Elastic License.
  */
-package org.elasticsearch.xpack.core.ssl;
+package org.elasticsearch.xpack.security.cli;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -78,7 +78,7 @@ public class CertGenUtils {
      * Generates a CA certificate
      */
     public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days)
-            throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
+        throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
         return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null);
     }
 
@@ -100,7 +100,7 @@ public class CertGenUtils {
      */
     public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
                                                             X509Certificate caCert, PrivateKey caPrivKey, int days)
-            throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
+        throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
         return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null);
     }
 
@@ -125,7 +125,7 @@ public class CertGenUtils {
     public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
                                                             X509Certificate caCert, PrivateKey caPrivKey,
                                                             int days, String signatureAlgorithm)
-            throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
+        throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
         return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, signatureAlgorithm);
     }
 
@@ -150,7 +150,7 @@ public class CertGenUtils {
     private static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
                                                              X509Certificate caCert, PrivateKey caPrivKey, boolean isCa,
                                                              int days, String signatureAlgorithm)
-            throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
+        throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
         Objects.requireNonNull(keyPair, "Key-Pair must not be null");
         final DateTime notBefore = new DateTime(DateTimeZone.UTC);
         if (days < 1) {
@@ -175,8 +175,8 @@ public class CertGenUtils {
         }
 
         JcaX509v3CertificateBuilder builder =
-                new JcaX509v3CertificateBuilder(issuer, serial,
-                        new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic());
+            new JcaX509v3CertificateBuilder(issuer, serial,
+                new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic());
 
         builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
         builder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier);
@@ -187,8 +187,8 @@ public class CertGenUtils {
 
         PrivateKey signingKey = caPrivKey != null ? caPrivKey : keyPair.getPrivate();
         ContentSigner signer = new JcaContentSignerBuilder(
-                (Strings.isNullOrEmpty(signatureAlgorithm)) ? getDefaultSignatureAlgorithm(signingKey) : signatureAlgorithm)
-                .setProvider(CertGenUtils.BC_PROV).build(signingKey);
+            (Strings.isNullOrEmpty(signatureAlgorithm)) ? getDefaultSignatureAlgorithm(signingKey) : signatureAlgorithm)
+            .setProvider(CertGenUtils.BC_PROV).build(signingKey);
         X509CertificateHolder certificateHolder = builder.build(signer);
         return new JcaX509CertificateConverter().getCertificate(certificateHolder);
     }
@@ -214,7 +214,7 @@ public class CertGenUtils {
                 break;
             default:
                 throw new IllegalArgumentException("Unsupported algorithm : " + key.getAlgorithm()
-                        + " for signature, allowed values for private key algorithm are [RSA, DSA, EC]");
+                    + " for signature, allowed values for private key algorithm are [RSA, DSA, EC]");
         }
         return signatureAlgorithm;
     }
@@ -229,7 +229,7 @@ public class CertGenUtils {
      * @return a certificate signing request
      */
     static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList)
-            throws IOException, OperatorCreationException {
+        throws IOException, OperatorCreationException {
         Objects.requireNonNull(keyPair, "Key-Pair must not be null");
         Objects.requireNonNull(keyPair.getPublic(), "Public-Key must not be null");
         Objects.requireNonNull(principal, "Principal must not be null");

+ 69 - 59
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertificateGenerateTool.java → x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java

@@ -3,7 +3,7 @@
  * or more contributor license agreements. Licensed under the Elastic License;
  * you may not use this file except in compliance with the Elastic License.
  */
-package org.elasticsearch.xpack.core.ssl;
+package org.elasticsearch.xpack.security.cli;
 
 import joptsimple.ArgumentAcceptingOptionSpec;
 import joptsimple.OptionSet;
@@ -34,6 +34,8 @@ import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.env.Environment;
+import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
+import org.elasticsearch.xpack.core.ssl.PemUtils;
 
 import javax.security.auth.x500.X500Principal;
 
@@ -68,6 +70,7 @@ import java.util.zip.ZipOutputStream;
 
 /**
  * CLI tool to make generation of certificates or certificate requests easier for users
+ *
  * @deprecated Replaced by {@link CertificateTool}
  */
 @Deprecated
@@ -81,7 +84,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
     private static final int FILE_EXTENSION_LENGTH = 4;
     static final int MAX_FILENAME_LENGTH = 255 - FILE_EXTENSION_LENGTH;
     private static final Pattern ALLOWED_FILENAME_CHAR_PATTERN =
-            Pattern.compile("[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}");
+        Pattern.compile("[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}");
     private static final int DEFAULT_KEY_SIZE = 2048;
     private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
 
@@ -96,11 +99,11 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
         // of the parser in this class so that we can defer initialization until after logging has been initialized
         static {
             @SuppressWarnings("unchecked") final ConstructingObjectParser<CertificateInformation, Void> instanceParser =
-                    new ConstructingObjectParser<>(
-                            "instances",
-                            a -> new CertificateInformation(
-                                    (String) a[0], (String) (a[1] == null ? a[0] : a[1]),
-                                    (List<String>) a[2], (List<String>) a[3], (List<String>) a[4]));
+                new ConstructingObjectParser<>(
+                    "instances",
+                    a -> new CertificateInformation(
+                        (String) a[0], (String) (a[1] == null ? a[0] : a[1]),
+                        (List<String>) a[2], (List<String>) a[3], (List<String>) a[4]));
             instanceParser.declareString(ConstructingObjectParser.constructorArg(), new ParseField("name"));
             instanceParser.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("filename"));
             instanceParser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("ip"));
@@ -125,29 +128,29 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
     CertificateGenerateTool() {
         super(DESCRIPTION);
         outputPathSpec = parser.accepts("out", "path of the zip file that the output should be written to")
-                .withRequiredArg();
+            .withRequiredArg();
         csrSpec = parser.accepts("csr", "only generate certificate signing requests");
         caCertPathSpec = parser.accepts("cert", "path to an existing ca certificate").availableUnless(csrSpec).withRequiredArg();
         caKeyPathSpec = parser.accepts("key", "path to an existing ca private key")
-                .availableIf(caCertPathSpec)
-                .requiredIf(caCertPathSpec)
-                .withRequiredArg();
+            .availableIf(caCertPathSpec)
+            .requiredIf(caCertPathSpec)
+            .withRequiredArg();
         caPasswordSpec = parser.accepts("pass", "password for an existing ca private key or the generated ca private key")
-                .availableUnless(csrSpec)
-                .withOptionalArg();
+            .availableUnless(csrSpec)
+            .withOptionalArg();
         caDnSpec = parser.accepts("dn", "distinguished name to use for the generated ca. defaults to " + AUTO_GEN_CA_DN)
-                .availableUnless(caCertPathSpec)
-                .availableUnless(csrSpec)
-                .withRequiredArg();
+            .availableUnless(caCertPathSpec)
+            .availableUnless(csrSpec)
+            .withRequiredArg();
         keysizeSpec = parser.accepts("keysize", "size in bits of RSA keys").withRequiredArg().ofType(Integer.class);
         inputFileSpec = parser.accepts("in", "file containing details of the instances in yaml format").withRequiredArg();
         daysSpec = parser.accepts("days", "number of days that the generated certificates are valid")
-                .availableUnless(csrSpec)
-                .withRequiredArg()
-                .ofType(Integer.class);
+            .availableUnless(csrSpec)
+            .withRequiredArg()
+            .ofType(Integer.class);
         p12Spec = parser.accepts("p12", "output a p12 (PKCS#12) version for each certificate/key pair, with optional password")
-                .availableUnless(csrSpec)
-                .withOptionalArg();
+            .availableUnless(csrSpec)
+            .withOptionalArg();
     }
 
     public static void main(String[] args) throws Exception {
@@ -178,7 +181,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
                 p12Password = null;
             }
             CAInfo caInfo = getCAInfo(terminal, dn, caCertPathSpec.value(options), caKeyPathSpec.value(options), keyPass, prompt, env,
-                    keysize, days);
+                keysize, days);
             Collection<CertificateInformation> certificateInformations = getCertificateInformationList(terminal, inputFile);
             generateAndWriteSignedCertificates(outputFile, certificateInformations, caInfo, keysize, days, p12Password);
         }
@@ -197,7 +200,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
     /**
      * Checks for output file in the user specified options or prompts the user for the output file
      *
-     * @param terminal terminal to communicate with a user
+     * @param terminal   terminal to communicate with a user
      * @param outputPath user specified output file, may be {@code null}
      * @return a {@link Path} to the output file
      */
@@ -223,12 +226,13 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
     /**
      * This method handles the collection of information about each instance that is necessary to generate a certificate. The user may
      * be prompted or the information can be gathered from a file
-     * @param terminal the terminal to use for user interaction
+     *
+     * @param terminal  the terminal to use for user interaction
      * @param inputFile an optional file that will be used to load the instance information
      * @return a {@link Collection} of {@link CertificateInformation} that represents each instance
      */
     static Collection<CertificateInformation> getCertificateInformationList(Terminal terminal, String inputFile)
-            throws Exception {
+        throws Exception {
         if (inputFile != null) {
             return parseAndValidateFile(terminal, resolvePath(inputFile).toAbsolutePath());
         }
@@ -239,7 +243,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
             if (name.isEmpty() == false) {
                 final boolean isNameValidFilename = Name.isValidFilename(name);
                 String filename = terminal.readText("Enter name for directories and files " + (isNameValidFilename ? "[" + name + "]" : "")
-                        + ": " );
+                    + ": ");
                 if (filename.isEmpty() && isNameValidFilename) {
                     filename = name;
                 }
@@ -267,7 +271,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
             }
 
             String exit = terminal.readText("Would you like to specify another instance? Press 'y' to continue entering instance " +
-                    "information: ");
+                "information: ");
             if ("y".equals(exit) == false) {
                 done = true;
             }
@@ -283,7 +287,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
             if (errors.size() > 0) {
                 hasError = true;
                 terminal.println(Terminal.Verbosity.SILENT, "Configuration for instance " + certInfo.name.originalName
-                        + " has invalid details");
+                    + " has invalid details");
                 for (String message : errors) {
                     terminal.println(Terminal.Verbosity.SILENT, " * " + message);
                 }
@@ -298,6 +302,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
 
     /**
      * Parses the input file to retrieve the certificate information
+     *
      * @param file the file to parse
      * @return a collection of certificate information
      */
@@ -305,22 +310,23 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
         try (Reader reader = Files.newBufferedReader(file)) {
             // EMPTY is safe here because we never use namedObject
             XContentParser xContentParser = XContentType.YAML.xContent()
-                    .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, reader);
+                .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, reader);
             return InputFileParser.PARSER.parse(xContentParser, new ArrayList<>(), null);
         }
     }
 
     /**
      * Generates certificate signing requests and writes them out to the specified file in zip format
+     *
      * @param outputFile the file to write the output to. This file must not already exist
-     * @param certInfo the details to use in the certificate signing requests
+     * @param certInfo   the details to use in the certificate signing requests
      */
     static void generateAndWriteCsrs(Path outputFile, Collection<CertificateInformation> certInfo, int keysize) throws Exception {
         fullyWriteFile(outputFile, (outputStream, pemWriter) -> {
             for (CertificateInformation certificateInformation : certInfo) {
                 KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
                 GeneralNames sanList = getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
-                        certificateInformation.commonNames);
+                    certificateInformation.commonNames);
                 PKCS10CertificationRequest csr = CertGenUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList);
 
                 final String dirName = certificateInformation.name.filename + "/";
@@ -347,15 +353,15 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
      * Returns the CA certificate and private key that will be used to sign certificates. These may be specified by the user or
      * automatically generated
      *
-     * @param terminal the terminal to use for prompting the user
-     * @param dn the distinguished name to use for the CA
+     * @param terminal   the terminal to use for prompting the user
+     * @param dn         the distinguished name to use for the CA
      * @param caCertPath the path to the CA certificate or {@code null} if not provided
-     * @param caKeyPath the path to the CA private key or {@code null} if not provided
-     * @param prompt whether we should prompt the user for a password
-     * @param keyPass the password to the private key. If not present and the key is encrypted the user will be prompted
-     * @param env the environment for this tool to resolve files with
-     * @param keysize the size of the key in bits
-     * @param days the number of days that the certificate should be valid for
+     * @param caKeyPath  the path to the CA private key or {@code null} if not provided
+     * @param prompt     whether we should prompt the user for a password
+     * @param keyPass    the password to the private key. If not present and the key is encrypted the user will be prompted
+     * @param env        the environment for this tool to resolve files with
+     * @param keysize    the size of the key in bits
+     * @param days       the number of days that the certificate should be valid for
      * @return CA cert and private key
      */
     static CAInfo getCAInfo(Terminal terminal, String dn, String caCertPath, String caKeyPath, char[] keyPass, boolean prompt,
@@ -366,7 +372,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
             Certificate[] certificates = CertParsingUtils.readCertificates(Collections.singletonList(resolvedCaCertPath), env);
             if (certificates.length != 1) {
                 throw new IllegalArgumentException("expected a single certificate in file [" + caCertPath + "] but found [" +
-                        certificates.length + "]");
+                    certificates.length + "]");
             }
             Certificate caCert = certificates[0];
             PrivateKey privateKey = readPrivateKey(caKeyPath, keyPass, terminal, prompt);
@@ -388,11 +394,12 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
 
     /**
      * Generates signed certificates in PEM format stored in a zip file
-     * @param outputFile the file that the certificates will be written to. This file must not exist
+     *
+     * @param outputFile              the file that the certificates will be written to. This file must not exist
      * @param certificateInformations details for creation of the certificates
-     * @param caInfo the CA information to sign the certificates with
-     * @param keysize the size of the key in bits
-     * @param days the number of days that the certificate should be valid for
+     * @param caInfo                  the CA information to sign the certificates with
+     * @param keysize                 the size of the key in bits
+     * @param days                    the number of days that the certificate should be valid for
      */
     static void generateAndWriteSignedCertificates(Path outputFile, Collection<CertificateInformation> certificateInformations,
                                                    CAInfo caInfo, int keysize, int days, char[] pkcs12Password) throws Exception {
@@ -403,9 +410,9 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
             for (CertificateInformation certificateInformation : certificateInformations) {
                 KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
                 Certificate certificate = CertGenUtils.generateSignedCertificate(certificateInformation.name.x500Principal,
-                        getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
-                                certificateInformation.commonNames),
-                        keyPair, caInfo.caCert, caInfo.privateKey, days);
+                    getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
+                        certificateInformation.commonNames),
+                    keyPair, caInfo.caCert, caInfo.privateKey, days);
 
                 final String dirName = certificateInformation.name.filename + "/";
                 ZipEntry zipEntry = new ZipEntry(dirName);
@@ -429,7 +436,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
                     final KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
                     pkcs12.load(null);
                     pkcs12.setKeyEntry(certificateInformation.name.originalName, keyPair.getPrivate(), pkcs12Password,
-                            new Certificate[]{certificate});
+                        new Certificate[]{certificate});
 
                     outputStream.putNextEntry(new ZipEntry(entryBase + ".p12"));
                     pkcs12.store(outputStream, pkcs12Password);
@@ -441,7 +448,8 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
 
     /**
      * This method handles the deletion of a file in the case of a partial write
-     * @param file the file that is being written to
+     *
+     * @param file   the file that is being written to
      * @param writer writes the contents of the file
      */
     private static void fullyWriteFile(Path file, Writer writer) throws Exception {
@@ -468,9 +476,10 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
     /**
      * This method handles writing out the certificate authority cert and private key if the certificate authority was generated by
      * this invocation of the tool
+     *
      * @param outputStream the output stream to write to
-     * @param pemWriter the writer for PEM objects
-     * @param info the certificate authority information
+     * @param pemWriter    the writer for PEM objects
+     * @param info         the certificate authority information
      */
     private static void writeCAInfoIfGenerated(ZipOutputStream outputStream, JcaPEMWriter pemWriter, CAInfo info) throws Exception {
         if (info.generated) {
@@ -577,14 +586,15 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
     /**
      * Helper method to read a private key and support prompting of user for a key. To avoid passwords being placed as an argument we
      * can prompt the user for their password if we encounter an encrypted key.
-     * @param path the path to the private key
+     *
+     * @param path     the path to the private key
      * @param password the password provided by the user or {@code null}
      * @param terminal the terminal to use for user interaction
-     * @param prompt whether to prompt the user or not
+     * @param prompt   whether to prompt the user or not
      * @return the {@link PrivateKey} that was read from the file
      */
     private static PrivateKey readPrivateKey(String path, char[] password, Terminal terminal, boolean prompt)
-                                            throws Exception {
+        throws Exception {
         AtomicReference<char[]> passwordReference = new AtomicReference<>(password);
         try {
             return PemUtils.readPrivateKey(resolvePath(path), () -> {
@@ -682,7 +692,7 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
                 }
             } catch (IllegalArgumentException e) {
                 String error = "[" + name + "] could not be converted to a valid DN\n" + e.getMessage() + "\n"
-                        + ExceptionsHelper.stackTrace(e);
+                    + ExceptionsHelper.stackTrace(e);
                 return new Name(name, null, null, error);
             }
 
@@ -695,15 +705,15 @@ public class CertificateGenerateTool extends EnvironmentAwareCommand {
 
         static boolean isValidFilename(String name) {
             return ALLOWED_FILENAME_CHAR_PATTERN.matcher(name).matches()
-                    && ALLOWED_FILENAME_CHAR_PATTERN.matcher(resolvePath(name).toString()).matches()
-                    && name.startsWith(".") == false;
+                && ALLOWED_FILENAME_CHAR_PATTERN.matcher(resolvePath(name).toString()).matches()
+                && name.startsWith(".") == false;
         }
 
         @Override
         public String toString() {
             return getClass().getSimpleName()
-                    + "{original=[" + originalName + "] principal=[" + x500Principal
-                    + "] file=[" + filename + "] err=[" + error + "]}";
+                + "{original=[" + originalName + "] principal=[" + x500Principal
+                + "] file=[" + filename + "] err=[" + error + "]}";
         }
     }
 

+ 61 - 59
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertificateTool.java → x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java

@@ -3,7 +3,7 @@
  * or more contributor license agreements. Licensed under the Elastic License;
  * you may not use this file except in compliance with the Elastic License.
  */
-package org.elasticsearch.xpack.core.ssl;
+package org.elasticsearch.xpack.security.cli;
 
 import joptsimple.OptionParser;
 import joptsimple.OptionSet;
@@ -39,6 +39,8 @@ import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.env.Environment;
+import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
+import org.elasticsearch.xpack.core.ssl.PemUtils;
 
 import javax.security.auth.x500.X500Principal;
 
@@ -101,7 +103,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
     private static final int FILE_EXTENSION_LENGTH = 4;
     static final int MAX_FILENAME_LENGTH = 255 - FILE_EXTENSION_LENGTH;
     private static final Pattern ALLOWED_FILENAME_CHAR_PATTERN =
-            Pattern.compile("[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}");
+        Pattern.compile("[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}");
     private static final int DEFAULT_KEY_SIZE = 2048;
 
     /**
@@ -115,11 +117,11 @@ public class CertificateTool extends LoggingAwareMultiCommand {
         // of the parser in this class so that we can defer initialization until after logging has been initialized
         static {
             @SuppressWarnings("unchecked") final ConstructingObjectParser<CertificateInformation, Void> instanceParser =
-                    new ConstructingObjectParser<>(
-                            "instances",
-                            a -> new CertificateInformation(
-                                    (String) a[0], (String) (a[1] == null ? a[0] : a[1]),
-                                    (List<String>) a[2], (List<String>) a[3], (List<String>) a[4]));
+                new ConstructingObjectParser<>(
+                    "instances",
+                    a -> new CertificateInformation(
+                        (String) a[0], (String) (a[1] == null ? a[0] : a[1]),
+                        (List<String>) a[2], (List<String>) a[3], (List<String>) a[4]));
             instanceParser.declareString(ConstructingObjectParser.constructorArg(), new ParseField("name"));
             instanceParser.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("filename"));
             instanceParser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("ip"));
@@ -144,28 +146,28 @@ public class CertificateTool extends LoggingAwareMultiCommand {
 
 
     static final String INTRO_TEXT = "This tool assists you in the generation of X.509 certificates and certificate\n" +
-            "signing requests for use with SSL/TLS in the Elastic stack.";
+        "signing requests for use with SSL/TLS in the Elastic stack.";
 
     static final String INSTANCE_EXPLANATION =
-            "    * An instance is any piece of the Elastic Stack that requires a SSL certificate.\n" +
-                    "      Depending on your configuration, Elasticsearch, Logstash, Kibana, and Beats\n" +
-                    "      may all require a certificate and private key.\n" +
-                    "    * The minimum required value for each instance is a name. This can simply be the\n" +
-                    "      hostname, which will be used as the Common Name of the certificate. A full\n" +
-                    "      distinguished name may also be used.\n" +
-                    "    * A filename value may be required for each instance. This is necessary when the\n" +
-                    "      name would result in an invalid file or directory name. The name provided here\n" +
-                    "      is used as the directory name (within the zip) and the prefix for the key and\n" +
-                    "      certificate files. The filename is required if you are prompted and the name\n" +
-                    "      is not displayed in the prompt.\n" +
-                    "    * IP addresses and DNS names are optional. Multiple values can be specified as a\n" +
-                    "      comma separated string. If no IP addresses or DNS names are provided, you may\n" +
-                    "      disable hostname verification in your SSL configuration.";
+        "    * An instance is any piece of the Elastic Stack that requires a SSL certificate.\n" +
+            "      Depending on your configuration, Elasticsearch, Logstash, Kibana, and Beats\n" +
+            "      may all require a certificate and private key.\n" +
+            "    * The minimum required value for each instance is a name. This can simply be the\n" +
+            "      hostname, which will be used as the Common Name of the certificate. A full\n" +
+            "      distinguished name may also be used.\n" +
+            "    * A filename value may be required for each instance. This is necessary when the\n" +
+            "      name would result in an invalid file or directory name. The name provided here\n" +
+            "      is used as the directory name (within the zip) and the prefix for the key and\n" +
+            "      certificate files. The filename is required if you are prompted and the name\n" +
+            "      is not displayed in the prompt.\n" +
+            "    * IP addresses and DNS names are optional. Multiple values can be specified as a\n" +
+            "      comma separated string. If no IP addresses or DNS names are provided, you may\n" +
+            "      disable hostname verification in your SSL configuration.";
 
     static final String CA_EXPLANATION =
-            "    * All certificates generated by this tool will be signed by a certificate authority (CA).\n" +
-                    "    * The tool can automatically generate a new CA for you, or you can provide your own with the\n" +
-                    "         -ca or -ca-cert command line options.";
+        "    * All certificates generated by this tool will be signed by a certificate authority (CA).\n" +
+            "    * The tool can automatically generate a new CA for you, or you can provide your own with the\n" +
+            "         -ca or -ca-cert command line options.";
 
 
     abstract static class CertificateCommand extends EnvironmentAwareCommand {
@@ -202,32 +204,32 @@ public class CertificateTool extends LoggingAwareMultiCommand {
         final void acceptCertificateGenerationOptions() {
             pemFormatSpec = parser.accepts("pem", "output certificates and keys in PEM format instead of PKCS#12");
             daysSpec = parser.accepts("days", "number of days that the generated certificates are valid")
-                    .withRequiredArg().ofType(Integer.class);
+                .withRequiredArg().ofType(Integer.class);
         }
 
         final void acceptsCertificateAuthority() {
             caPkcs12PathSpec = parser.accepts("ca", "path to an existing ca key pair (in PKCS#12 format)").withRequiredArg();
             caCertPathSpec = parser.accepts("ca-cert", "path to an existing ca certificate")
-                    .availableUnless(caPkcs12PathSpec)
-                    .withRequiredArg();
+                .availableUnless(caPkcs12PathSpec)
+                .withRequiredArg();
             caKeyPathSpec = parser.accepts("ca-key", "path to an existing ca private key")
-                    .availableIf(caCertPathSpec)
-                    .requiredIf(caCertPathSpec)
-                    .withRequiredArg();
+                .availableIf(caCertPathSpec)
+                .requiredIf(caCertPathSpec)
+                .withRequiredArg();
 
             keepCaKeySpec = parser.accepts("keep-ca-key", "retain the CA private key for future use")
-                    .availableUnless(caPkcs12PathSpec)
-                    .availableUnless(caCertPathSpec);
+                .availableUnless(caPkcs12PathSpec)
+                .availableUnless(caCertPathSpec);
 
             caPasswordSpec = parser.accepts("ca-pass", "password for an existing ca private key or the generated ca private key")
-                    .withOptionalArg();
+                .withOptionalArg();
 
             acceptsCertificateAuthorityName();
         }
 
         void acceptsCertificateAuthorityName() {
             OptionSpecBuilder builder = parser.accepts("ca-dn",
-                    "distinguished name to use for the generated ca. defaults to " + AUTO_GEN_CA_DN);
+                "distinguished name to use for the generated ca. defaults to " + AUTO_GEN_CA_DN);
             if (caPkcs12PathSpec != null) {
                 builder = builder.availableUnless(caPkcs12PathSpec);
             }
@@ -336,11 +338,11 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             char[] passwordOption = getChars(caPasswordSpec.value(options));
 
             Map<Certificate, Key> keys = withPassword("CA (" + path + ")", passwordOption,
-                    terminal, password -> CertParsingUtils.readPkcs12KeyPairs(path, password, a -> password));
+                terminal, password -> CertParsingUtils.readPkcs12KeyPairs(path, password, a -> password));
 
             if (keys.size() != 1) {
                 throw new IllegalArgumentException("expected a single key in file [" + path.toAbsolutePath() + "] but found [" +
-                        keys.size() + "]");
+                    keys.size() + "]");
             }
             final Map.Entry<Certificate, Key> pair = keys.entrySet().iterator().next();
             return new CAInfo((X509Certificate) pair.getKey(), (PrivateKey) pair.getValue());
@@ -358,7 +360,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             Certificate[] certificates = CertParsingUtils.readCertificates(Collections.singletonList(resolvedCaCertPath), env);
             if (certificates.length != 1) {
                 throw new IllegalArgumentException("expected a single certificate in file [" + resolvedCaCertPath + "] but found [" +
-                        certificates.length + "]");
+                    certificates.length + "]");
             }
             X509Certificate caCert = (X509Certificate) certificates[0];
             PrivateKey privateKey = readPrivateKey(key, getChars(password), terminal);
@@ -391,7 +393,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
          * @return a {@link Collection} of {@link CertificateInformation} that represents each instance
          */
         Collection<CertificateInformation> getCertificateInformationList(Terminal terminal, OptionSet options)
-                throws Exception {
+            throws Exception {
             final Path input = resolvePath(options, inputFileSpec);
             if (input != null) {
                 return parseAndValidateFile(terminal, input.toAbsolutePath());
@@ -456,7 +458,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 }
 
                 String exit = terminal.readText("Would you like to specify another instance? Press 'y' to continue entering instance " +
-                        "information: ");
+                    "information: ");
                 if ("y".equals(exit) == false) {
                     done = true;
                 }
@@ -468,7 +470,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             final boolean isNameValidFilename = Name.isValidFilename(certName);
             while (true) {
                 String filename = terminal.readText("Enter name for directories and files of " + certName +
-                        (isNameValidFilename ? " [" + certName + "]" : "") + ": ");
+                    (isNameValidFilename ? " [" + certName + "]" : "") + ": ");
                 if (filename.isEmpty() && isNameValidFilename) {
                     return certName;
                 }
@@ -490,7 +492,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
          * @param includeKey   if true, write the CA key in PEM format
          */
         static void writeCAInfo(ZipOutputStream outputStream, JcaPEMWriter pemWriter, CAInfo info, boolean includeKey)
-                throws Exception {
+            throws Exception {
             final String caDirName = createCaDirectory(outputStream);
             outputStream.putNextEntry(new ZipEntry(caDirName + "ca.crt"));
             pemWriter.writeObject(info.certAndKey.cert);
@@ -546,7 +548,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             pkcs12.load(null);
             withPassword(fileName, password, terminal, p12Password -> {
                 if (isAscii(p12Password)) {
-                    pkcs12.setKeyEntry(alias, pair.key, p12Password, new Certificate[] { pair.cert });
+                    pkcs12.setKeyEntry(alias, pair.key, p12Password, new Certificate[]{pair.cert});
                     if (caCert != null) {
                         pkcs12.setCertificateEntry("ca", caCert);
                     }
@@ -574,7 +576,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             terminal.println("The 'csr' mode generates certificate signing requests that can be sent to");
             terminal.println("a trusted certificate authority");
             terminal.println("    * By default, this generates a single CSR for a single instance.");
-            terminal.println("    * You can use the '-multiple' option to generate CSRs for multiple" );
+            terminal.println("    * You can use the '-multiple' option to generate CSRs for multiple");
             terminal.println("       instances, each with their own private key.");
             terminal.println("    * The '-in' option allows for the CSR generation to be automated");
             terminal.println("       by describing the details of each instance in a YAML file");
@@ -616,7 +618,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 for (CertificateInformation certificateInformation : certInfo) {
                     KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
                     GeneralNames sanList = getSubjectAlternativeNamesValue(certificateInformation.ipAddresses,
-                            certificateInformation.dnsNames, certificateInformation.commonNames);
+                        certificateInformation.dnsNames, certificateInformation.commonNames);
                     PKCS10CertificationRequest csr = CertGenUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList);
 
                     final String dirName = certificateInformation.name.filename + "/";
@@ -750,7 +752,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
          */
         void generateAndWriteSignedCertificates(Path output, boolean writeZipFile, OptionSet options,
                                                 Collection<CertificateInformation> certs, CAInfo caInfo, Terminal terminal)
-                throws Exception {
+            throws Exception {
 
             checkDirectory(output, terminal);
 
@@ -805,7 +807,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                             final String fileName = entryBase + ".p12";
                             outputStream.putNextEntry(new ZipEntry(fileName));
                             writePkcs12(fileName, outputStream, certificateInformation.name.originalName, pair, caInfo.certAndKey.cert,
-                                    outputPassword, terminal);
+                                outputPassword, terminal);
                             outputStream.closeEntry();
                         }
                     }
@@ -815,7 +817,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 CertificateInformation certificateInformation = certs.iterator().next();
                 CertificateAndKey pair = generateCertificateAndKey(certificateInformation, caInfo, keySize, days);
                 fullyWriteFile(output, stream -> writePkcs12(output.getFileName().toString(), stream,
-                        certificateInformation.name.originalName, pair, caInfo.certAndKey.cert, outputPassword, terminal));
+                    certificateInformation.name.originalName, pair, caInfo.certAndKey.cert, outputPassword, terminal));
             }
         }
 
@@ -823,9 +825,9 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                                                             int keySize, int days) throws Exception {
             KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
             Certificate certificate = CertGenUtils.generateSignedCertificate(certificateInformation.name.x500Principal,
-                    getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
-                            certificateInformation.commonNames),
-                    keyPair, caInfo.certAndKey.cert, caInfo.certAndKey.key, days);
+                getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames,
+                    certificateInformation.commonNames),
+                keyPair, caInfo.certAndKey.cert, caInfo.certAndKey.key, days);
             return new CertificateAndKey((X509Certificate) certificate, keyPair.getPrivate());
         }
 
@@ -872,7 +874,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             } else {
                 final String fileName = output.getFileName().toString();
                 fullyWriteFile(output, outputStream ->
-                        writePkcs12(fileName, outputStream, "ca", caInfo.certAndKey, null, caInfo.password, terminal));
+                    writePkcs12(fileName, outputStream, "ca", caInfo.certAndKey, null, caInfo.password, terminal));
             }
         }
     }
@@ -912,7 +914,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
         try (Reader reader = Files.newBufferedReader(file)) {
             // EMPTY is safe here because we never use namedObject
             XContentParser xContentParser = XContentType.YAML.xContent()
-                    .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, reader);
+                .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, reader);
             return CertificateToolParser.PARSER.parse(xContentParser, new ArrayList<>(), null);
         }
     }
@@ -1015,7 +1017,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
      * @return the {@link PrivateKey} that was read from the file
      */
     private static PrivateKey readPrivateKey(Path path, char[] password, Terminal terminal)
-            throws Exception {
+        throws Exception {
         AtomicReference<char[]> passwordReference = new AtomicReference<>(password);
         try {
             return PemUtils.readPrivateKey(path, () -> {
@@ -1125,7 +1127,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 }
             } catch (IllegalArgumentException e) {
                 String error = "[" + name + "] could not be converted to a valid DN\n" + e.getMessage() + "\n"
-                        + ExceptionsHelper.stackTrace(e);
+                    + ExceptionsHelper.stackTrace(e);
                 return new Name(name, null, null, error);
             }
 
@@ -1138,15 +1140,15 @@ public class CertificateTool extends LoggingAwareMultiCommand {
 
         static boolean isValidFilename(String name) {
             return ALLOWED_FILENAME_CHAR_PATTERN.matcher(name).matches()
-                    && ALLOWED_FILENAME_CHAR_PATTERN.matcher(resolvePath(name).toString()).matches()
-                    && name.startsWith(".") == false;
+                && ALLOWED_FILENAME_CHAR_PATTERN.matcher(resolvePath(name).toString()).matches()
+                && name.startsWith(".") == false;
         }
 
         @Override
         public String toString() {
             return getClass().getSimpleName()
-                    + "{original=[" + originalName + "] principal=[" + x500Principal
-                    + "] file=[" + filename + "] err=[" + error + "]}";
+                + "{original=[" + originalName + "] principal=[" + x500Principal
+                + "] file=[" + filename + "] err=[" + error + "]}";
         }
     }
 

+ 2 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/CertGenUtilsTests.java → x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertGenUtilsTests.java

@@ -4,7 +4,7 @@
  * you may not use this file except in compliance with the Elastic License.
  */
 
-package org.elasticsearch.xpack.core.ssl;
+package org.elasticsearch.xpack.security.cli;
 
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
@@ -12,6 +12,7 @@ import org.elasticsearch.common.SuppressForbidden;
 import org.elasticsearch.common.network.InetAddresses;
 import org.elasticsearch.common.network.NetworkAddress;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.security.cli.CertGenUtils;
 import org.junit.BeforeClass;
 
 import java.math.BigInteger;

+ 8 - 6
x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/core/ssl/CertificateGenerateToolTests.java → x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateGenerateToolTests.java

@@ -3,7 +3,7 @@
  * or more contributor license agreements. Licensed under the Elastic License;
  * you may not use this file except in compliance with the Elastic License.
  */
-package org.elasticsearch.xpack.core.ssl;
+package org.elasticsearch.xpack.security.cli;
 
 import com.google.common.jimfs.Configuration;
 import com.google.common.jimfs.Jimfs;
@@ -33,9 +33,11 @@ import org.elasticsearch.env.Environment;
 import org.elasticsearch.env.TestEnvironment;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.SecuritySettingsSourceField;
-import org.elasticsearch.xpack.core.ssl.CertificateGenerateTool.CAInfo;
-import org.elasticsearch.xpack.core.ssl.CertificateGenerateTool.CertificateInformation;
-import org.elasticsearch.xpack.core.ssl.CertificateGenerateTool.Name;
+import org.elasticsearch.xpack.security.cli.CertificateGenerateTool.CAInfo;
+import org.elasticsearch.xpack.security.cli.CertificateGenerateTool.CertificateInformation;
+import org.elasticsearch.xpack.security.cli.CertificateGenerateTool.Name;
+import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
+import org.elasticsearch.xpack.core.ssl.PemUtils;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.BeforeClass;
@@ -359,8 +361,8 @@ public class CertificateGenerateToolTests extends ESTestCase {
 
     public void testGetCAInfo() throws Exception {
         Environment env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build());
-        Path testNodeCertPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt");
-        Path testNodeKeyPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem");
+        Path testNodeCertPath = getDataPath("/org/elasticsearch/xpack/security/cli/testnode.crt");
+        Path testNodeKeyPath = getDataPath("/org/elasticsearch/xpack/security/cli/testnode.pem");
         final boolean passwordPrompt = randomBoolean();
         MockTerminal terminal = new MockTerminal();
         if (passwordPrompt) {

+ 11 - 9
x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/core/ssl/CertificateToolTests.java → x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java

@@ -3,7 +3,7 @@
  * or more contributor license agreements. Licensed under the Elastic License;
  * you may not use this file except in compliance with the Elastic License.
  */
-package org.elasticsearch.xpack.core.ssl;
+package org.elasticsearch.xpack.security.cli;
 
 import com.google.common.jimfs.Configuration;
 import com.google.common.jimfs.Jimfs;
@@ -39,12 +39,14 @@ import org.elasticsearch.env.TestEnvironment;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.SecuritySettingsSourceField;
 import org.elasticsearch.test.TestMatchers;
-import org.elasticsearch.xpack.core.ssl.CertificateTool.CAInfo;
-import org.elasticsearch.xpack.core.ssl.CertificateTool.CertificateAuthorityCommand;
-import org.elasticsearch.xpack.core.ssl.CertificateTool.CertificateCommand;
-import org.elasticsearch.xpack.core.ssl.CertificateTool.CertificateInformation;
-import org.elasticsearch.xpack.core.ssl.CertificateTool.GenerateCertificateCommand;
-import org.elasticsearch.xpack.core.ssl.CertificateTool.Name;
+import org.elasticsearch.xpack.security.cli.CertificateTool.CAInfo;
+import org.elasticsearch.xpack.security.cli.CertificateTool.CertificateAuthorityCommand;
+import org.elasticsearch.xpack.security.cli.CertificateTool.CertificateCommand;
+import org.elasticsearch.xpack.security.cli.CertificateTool.CertificateInformation;
+import org.elasticsearch.xpack.security.cli.CertificateTool.GenerateCertificateCommand;
+import org.elasticsearch.xpack.security.cli.CertificateTool.Name;
+import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
+import org.elasticsearch.xpack.core.ssl.PemUtils;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.BeforeClass;
@@ -387,8 +389,8 @@ public class CertificateToolTests extends ESTestCase {
 
     public void testGetCAInfo() throws Exception {
         Environment env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build());
-        Path testNodeCertPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt");
-        Path testNodeKeyPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem");
+        Path testNodeCertPath = getDataPath("/org/elasticsearch/xpack/security/cli/testnode.crt");
+        Path testNodeKeyPath = getDataPath("/org/elasticsearch/xpack/security/cli/testnode.pem");
         final boolean passwordPrompt = randomBoolean();
         MockTerminal terminal = new MockTerminal();
         if (passwordPrompt) {

+ 23 - 0
x-pack/plugin/security/cli/src/test/resources/org/elasticsearch/xpack/security/cli/testnode.crt

@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV
+BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp
+Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3
+WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV
+BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUKKNR1
+Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c
+7u0sLch9p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg
+/lATm8V71LMY68inht71/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5
+zJhn660es/1ZnR6nvwt6xnSTl/mNHMjkfv1bs4rJ/py3qPxicdoSIn/KyojUcgHV
+F38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQABo4G/MIG8MAkGA1UdEwQC
+MAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREEgYcwgYSC
+CWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghds
+b2NhbGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5s
+b2NhbGRvbWFpbjaHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
+BQADggEBAMjGGXT8Nt1tbl2GkiKtmiuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHe
+k2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugoQ3wct0bQC5wEWYN+oMDvSyO6
+M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzqk/mQTug+Y8aE
+mVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z
+1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxC
+y4g6cTMM3S/UMt5/+aIB2JAuMKyuD+A=
+-----END CERTIFICATE-----

+ 30 - 0
x-pack/plugin/security/cli/src/test/resources/org/elasticsearch/xpack/security/cli/testnode.pem

@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,9D867F7E0C94D013
+
+dVoVCjPeg1wgS7rVtOvGfQcrZyLkx393aWRnFq45tbjKBVuITtJ9vI7o4QXOV/15
+Gnb6WhXGIdWrzsxEAd46K6hIuNSISd4Emsx6c2Q5hTqWXXfexbOZBNfTtXtdJPnJ
+1jAaikhtztLo3JSLTKNY5sNxd+XbaQyYVUWvueK6zOaIIMETvB+VPVFd9i1ROibk
+Sgdtyj01KjkoalifqK/tA0CIYNKL0S6/eoK3UhAlpIprlpV+cnXa940C6bjLeJPt
+PMAGGp5RrplxSgrSerw3I9DOWkHGtpqzIka3XneNUXJP8k4HUJ+aZkGH2ZILKS8d
+4KMIb+KZSpHEGn+6uGccWLtZZmAjWJrDw56JbQtSHdRYLBRSOjLbTvQoPu/2Hpli
+7HOxbotlvjptMunncq5aqK57SHA1dh0cwF7J3LUmGFJ67eoz+VV3b5qMn4MopSeI
+mS16Ydd3nGpjSrln/elM0CQxqWfcOAXRZpDpFUQoXcBrLVzvz2DBl/0CrTRLhgzi
+CO+5/IVcBWRlYpRNGgjjP7q0j6URID3jk5J06fYQXmBiwQT5j+GZqqzpMCJ9mIy2
+1O9SN1hebJnIcEU+E0njn/MGjlYdPywhaCy8pqElp6Q8TUEJpwLRFO/owCoBet/n
+ZmCXUjfCGhc1pWHufFcDEQ6xMgEWWY/tdwCZeSU7EhErTjCbfupg+55A5fpDml0m
+3wH4CFcuRjlqyx6Ywixm1ATeitDtJl5HQTw6b8OtEXwSgRmZ0eSqSRVk9QbVS7gu
+IpQe09/Zimb5HzjZqZ3fdqHlcW4xax8hyJeyIvF5ZJ57eY8CBvu/wP2GDn26QnvF
+xQqdfDbq1H4JmpwUHpbFwBoQK4Q6WFd1z4EA9bRQeo3H9PoqoOwMDjzajwLRF7b7
+q6tYH/n9PyHwdf1c4fFwgSmL1toXGfKlA9hjIaLsRSDD6srT5EdUk78bsnddwI51
+tu7C7P4JG+h1VdRNMNTlqtileWsIE7Nn2A1OkcUxZdF5mamENpDpJcHePLto6c8q
+FKiwyFMsxhgsj6HK2HqO+UA4sX5Ni4oHwiPmb//EZLn045M5i1AN26KosJmb8++D
+sgR5reWRy+UqJCTYblVg+7Dx++ggUnfxVyQEsWmw5r5f4KU5wXBkvoVMGtPNa9DE
+n/uLtObD1qkNL38pRsr2OGRchYCgEoKGqEISBP4knfGXLOlWiW/246j9QzI97r1u
+tvy7fKg28G7AUz9l6bpewsPHefBUeRQeieP9eJINaEpxkF/w2RpKDLpQjWxwDDOM
+s+D0mrBMJve17AmJ8rMw6dIQPZYNZ88/jz1uQuUwQ2YlbmtZbCG81k9YMFGEU9XS
+cyhJxj8hvYnt2PR5Z9/cJPyWOs0m/ufOeeQQ8SnU/lzmrQnpzUd2Z6p5i/B7LdRP
+n1kX+l1qynuPnjvBz4nJQE0p6nzW8RyCDSniC9mtYtZmhgC8icqxgbvS7uEOBIYJ
+NbK+0bEETTO34iY/JVTIqLOw3iQZYMeUpxpj6Phgx/oooxMTquMecPKNgeVtaBst
+qjTNPX0ti1/HYpZqzYi8SV8YjHSJWCVMsZjKPr3W/HIcCKqYoIfgzi83Ha2KMQx6
+-----END RSA PRIVATE KEY-----

+ 2 - 1
x-pack/plugin/security/src/main/bin/elasticsearch-certgen

@@ -4,7 +4,8 @@
 # or more contributor license agreements. Licensed under the Elastic License;
 # you may not use this file except in compliance with the Elastic License.
 
-ES_MAIN_CLASS=org.elasticsearch.xpack.core.ssl.CertificateGenerateTool \
+ES_MAIN_CLASS=org.elasticsearch.xpack.security.cli.CertificateGenerateTool \
   ES_ADDITIONAL_SOURCES="x-pack-env;x-pack-security-env" \
+  ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/security-cli \
   "`dirname "$0"`"/elasticsearch-cli \
   "$@"

+ 2 - 1
x-pack/plugin/security/src/main/bin/elasticsearch-certgen.bat

@@ -7,8 +7,9 @@ rem you may not use this file except in compliance with the Elastic License.
 setlocal enabledelayedexpansion
 setlocal enableextensions
 
-set ES_MAIN_CLASS=org.elasticsearch.xpack.core.ssl.CertificateGenerateTool
+set ES_MAIN_CLASS=org.elasticsearch.xpack.security.cli.CertificateGenerateTool
 set ES_ADDITIONAL_SOURCES=x-pack-env;x-pack-security-env
+set ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/security-cli
 call "%~dp0elasticsearch-cli.bat" ^
   %%* ^
   || exit /b 1

+ 2 - 1
x-pack/plugin/security/src/main/bin/elasticsearch-certutil

@@ -4,7 +4,8 @@
 # or more contributor license agreements. Licensed under the Elastic License;
 # you may not use this file except in compliance with the Elastic License.
 
-ES_MAIN_CLASS=org.elasticsearch.xpack.core.ssl.CertificateTool \
+ES_MAIN_CLASS=org.elasticsearch.xpack.security.cli.CertificateTool \
   ES_ADDITIONAL_SOURCES="x-pack-env;x-pack-security-env" \
+  ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/security-cli \
   "`dirname "$0"`"/elasticsearch-cli \
   "$@"

+ 2 - 1
x-pack/plugin/security/src/main/bin/elasticsearch-certutil.bat

@@ -7,8 +7,9 @@ rem you may not use this file except in compliance with the Elastic License.
 setlocal enabledelayedexpansion
 setlocal enableextensions
 
-set ES_MAIN_CLASS=org.elasticsearch.xpack.core.ssl.CertificateTool
+set ES_MAIN_CLASS=org.elasticsearch.xpack.security.cli.CertificateTool
 set ES_ADDITIONAL_SOURCES=x-pack-env;x-pack-security-env
+set ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/security-cli
 call "%~dp0elasticsearch-cli.bat" ^
   %%* ^
   || exit /b 1