Răsfoiți Sursa

Remove support of creating CA on the fly when generating certificates (#65590)

Generating certificates with the cert sub-command now requires either: 1) a CA
to be provided with --ca or --ca-cert/--ca-key; or 2) make them self-signed
with the --self-signed option. Generating a CA on the fly is no longer
supported. The --keep-ca-key option is removed and the tool throws an error 
saying the CA needs to be generated separately if the option is specified.

This is a follow-up PR for #61884, which deprecated the "ca-on-the-fly" usage.
Yang Wang 4 ani în urmă
părinte
comite
b018c761e9

+ 5 - 9
docs/reference/commands/certutil.asciidoc

@@ -18,7 +18,7 @@ bin/elasticsearch-certutil
 | (cert ([--ca <file_path>] | [--ca-cert <file_path> --ca-key <file_path>])
 [--ca-dn <name>] [--ca-pass <password>] [--days <n>]
 [--dns <domain_name>] [--in <input_file>] [--ip <ip_addresses>]
-[--keep-ca-key] [--multiple] [--name <file_name>] [--pem] [--self-signed])
+[--multiple] [--name <file_name>] [--pem] [--self-signed])
 
 | (csr [--dns <domain_name>] [--in <input_file>] [--ip <ip_addresses>]
 [--name <file_name>])
@@ -79,9 +79,8 @@ cannot perform hostname verification and you might need to configure the
 about this setting, see <<security-settings>>.
 
 All certificates that are generated by this command are signed by a CA unless
-the `--self-signed` parameter is specified. You can provide your own CA with the
-`--ca` or `--ca-cert` and `--ca-key` parameters. Otherwise, the command automatically generates a new CA for you.
-deprecated:[7.11.0,"Generating certificates without specifying a CA certificate and key is deprecated. In the next major version you must provide a CA certificate unless the `--self-signed` option is specified."]
+the `--self-signed` parameter is specified. You must provide your own CA with the
+`--ca` or `--ca-cert` and `--ca-key` parameters unless `--self-signed` is specified.
 For more information about generating a CA, see the
 <<certutil-ca,CA mode of this command>>.
 To generate self-signed certificates, use the `--self-signed` parameter.
@@ -90,7 +89,7 @@ By default, the `cert` mode produces a single PKCS#12 output file which holds
 the instance certificate, the instance private key, and the CA certificate. If
 you specify the `--pem` parameter, the command generates PEM formatted
 certificates and keys and packages them into a zip file.
-If you specify the `--keep-ca-key`, `--multiple` or `--in` parameters,
+If you specify the `--multiple` or `--in` parameters,
 the command produces a zip file containing the generated certificates and keys.
 
 [discrete]
@@ -180,9 +179,6 @@ parameter.
 `--ip <IP_addresses>`:: Specifies a comma-separated list of IP addresses. This
 parameter cannot be used with the `ca` parameter.
 
-`--keep-ca-key`:: When running in `cert` mode with an automatically-generated
-CA, specifies to retain the CA private key for future use.
-
 `--keysize <bits>`::
 Defines the number of bits that are used in generated RSA keys. The default
 value is `2048`.
@@ -305,7 +301,7 @@ parameter to specify the location of the file. For example:
 
 [source, sh]
 --------------------------------------------------
-bin/elasticsearch-certutil cert --silent --in instances.yml --out test1.zip --pass testpassword --keep-ca-key
+bin/elasticsearch-certutil cert --silent --in instances.yml --out test1.zip --pass testpassword --ca elastic-stack-ca.p12
 --------------------------------------------------
 
 This command generates a compressed `test1.zip` file. After you decompress the

+ 2 - 1
x-pack/docs/en/security/securing-communications/configuring-tls-docker.asciidoc

@@ -72,7 +72,8 @@ services:
     command: >
       bash -c '
         if [[ ! -f /certs/bundle.zip ]]; then
-          bin/elasticsearch-certutil cert --silent --pem --in config/certificates/instances.yml -out /certs/bundle.zip;
+          bin/elasticsearch-certutil ca --silent --out /certs/ca.p12 --pass ""; <1>
+          bin/elasticsearch-certutil cert --silent --pem --ca /certs/ca.p12 --in config/certificates/instances.yml -out /certs/bundle.zip;
           unzip /certs/bundle.zip -d /certs; <1>
         fi;
         chown -R 1000:0 /certs

+ 21 - 46
x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java

@@ -5,6 +5,7 @@
  */
 package org.elasticsearch.xpack.security.cli;
 
+import joptsimple.OptionException;
 import joptsimple.OptionParser;
 import joptsimple.OptionSet;
 import joptsimple.OptionSpec;
@@ -145,6 +146,21 @@ public class CertificateTool extends LoggingAwareMultiCommand {
         subcommands.put("http", new HttpCertificateCommand());
     }
 
+    @Override
+    protected void execute(Terminal terminal, OptionSet options) throws Exception {
+        try {
+            super.execute(terminal, options);
+        } catch (OptionException e) {
+            if (e.options().size() == 1 && e.options().contains("keep-ca-key")) {
+                throw new UserException(ExitCodes.USAGE,
+                    "Generating certificates without providing a CA is no longer supported.\n" +
+                        "Please first generate a CA with the 'ca' sub-command and provide the ca file \n" +
+                        "with either --ca or --ca-cert/--ca-key to generate certificates.");
+            } else {
+                throw e;
+            }
+        }
+    }
 
     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.";
@@ -187,7 +203,6 @@ public class CertificateTool extends LoggingAwareMultiCommand {
         OptionSpec<String> caKeyPathSpec;
         OptionSpec<String> caPasswordSpec;
         OptionSpec<String> caDnSpec;
-        OptionSpec<Void> keepCaKeySpec;
 
         OptionSpec<Void> multipleNodesSpec;
         OptionSpec<String> nameSpec;
@@ -219,10 +234,6 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 .requiredIf(caCertPathSpec)
                 .withRequiredArg();
 
-            keepCaKeySpec = parser.accepts("keep-ca-key", "retain the CA private key for future use")
-                .availableUnless(caPkcs12PathSpec)
-                .availableUnless(caCertPathSpec);
-
             caPasswordSpec = parser.accepts("ca-pass", "password for an existing ca private key or the generated ca private key")
                 .withOptionalArg();
 
@@ -295,10 +306,6 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             }
         }
 
-        boolean keepCaKey(OptionSet options) {
-            return options.has(keepCaKeySpec);
-        }
-
         boolean usePemFormat(OptionSet options) {
             return options.has(pemFormatSpec);
         }
@@ -521,24 +528,6 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             }
         }
 
-        /**
-         * This method handles writing out the certificate authority in PKCS#12 format to a zip file.
-         *
-         * @param outputStream the output stream to write to
-         * @param info         the certificate authority information
-         * @param terminal     used to prompt for a password (if not already supplied)
-         */
-        static void writeCAInfo(ZipOutputStream outputStream, CAInfo info, Terminal terminal) throws Exception {
-            final String dirName = createCaDirectory(outputStream);
-            final String fileName = dirName + "ca.p12";
-            outputStream.putNextEntry(new ZipEntry(fileName));
-            withPassword("Generated CA", info.password, terminal, caPassword -> {
-                writePkcs12(fileName, outputStream, "ca", info.certAndKey, null, caPassword, null);
-                return null;
-            });
-            outputStream.closeEntry();
-        }
-
         private static String createCaDirectory(ZipOutputStream outputStream) throws IOException {
             final String caDirName = "ca/";
             ZipEntry zipEntry = new ZipEntry(caDirName);
@@ -684,7 +673,6 @@ public class CertificateTool extends LoggingAwareMultiCommand {
             terminal.println("");
             terminal.println("If you specify any of the following options:");
             terminal.println("    * -pem (PEM formatted output)");
-            terminal.println("    * -keep-ca-key (retain generated CA key)");
             terminal.println("    * -multiple (generate multiple certificates)");
             terminal.println("    * -in (generate certificates from an input file)");
             terminal.println("then the output will be be a zip file containing individual certificate/key files");
@@ -692,9 +680,8 @@ public class CertificateTool extends LoggingAwareMultiCommand {
 
             CAInfo caInfo = getCAInfo(terminal, options, env);
             Collection<CertificateInformation> certInfo = getCertificateInformationList(terminal, options);
-            final boolean keepCaKey = keepCaKey(options);
             final boolean usePemFormat = usePemFormat(options);
-            final boolean writeZipFile = options.has(multipleNodesSpec) || options.has(inputFileSpec) || keepCaKey || usePemFormat;
+            final boolean writeZipFile = options.has(multipleNodesSpec) || options.has(inputFileSpec) || usePemFormat;
 
             final String outputName;
             if (writeZipFile) {
@@ -716,12 +703,7 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 terminal.print(Terminal.Verbosity.NORMAL, "all instances");
             } else {
                 terminal.println(Terminal.Verbosity.NORMAL, "This file should be properly secured as it contains the private key for ");
-                terminal.print(Terminal.Verbosity.NORMAL, "your instance");
-            }
-            if (caInfo != null && caInfo.generated && keepCaKey) {
-                terminal.println(Terminal.Verbosity.NORMAL, " and for the certificate authority.");
-            } else {
-                terminal.println(Terminal.Verbosity.NORMAL, ".");
+                terminal.print(Terminal.Verbosity.NORMAL, "your instance.");
             }
             terminal.println("");
             final String filesDescription;
@@ -751,6 +733,9 @@ public class CertificateTool extends LoggingAwareMultiCommand {
 
         @Override
         CAInfo getCAInfo(Terminal terminal, OptionSet options, Environment env) throws Exception {
+            if (false == options.has(selfSigned) && false == options.has(caPkcs12PathSpec) && false == options.has(caCertPathSpec)) {
+                throw new UserException(ExitCodes.USAGE, "Must specify either --ca or --ca-cert/--ca-key or --self-signed");
+            }
             return options.has(selfSigned) ? null : super.getCAInfo(terminal, options, env);
         }
 
@@ -777,16 +762,6 @@ public class CertificateTool extends LoggingAwareMultiCommand {
                 final boolean usePem = usePemFormat(options);
                 final boolean usePassword = super.useOutputPassword(options);
                 fullyWriteZipFile(output, (outputStream, pemWriter) -> {
-                    // write out the CA info first if it was generated
-                    if (caInfo != null && caInfo.generated) {
-                        final boolean writeCAKey = keepCaKey(options);
-                        if (usePem) {
-                            writeCAInfo(outputStream, pemWriter, caInfo, writeCAKey);
-                        } else if (writeCAKey) {
-                            writeCAInfo(outputStream, caInfo, terminal);
-                        }
-                    }
-
                     for (CertificateInformation certificateInformation : certs) {
                         CertificateAndKey pair = generateCertificateAndKey(certificateInformation, caInfo, keySize, days);
 

+ 69 - 119
x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/CertificateToolTests.java

@@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.cli;
 
 import com.google.common.jimfs.Configuration;
 import com.google.common.jimfs.Jimfs;
+import joptsimple.NonOptionArgumentSpec;
 import joptsimple.OptionSet;
 import joptsimple.OptionSpec;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -21,8 +22,6 @@ import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.openssl.PEMDecryptorProvider;
-import org.bouncycastle.openssl.PEMEncryptedKeyPair;
 import org.bouncycastle.openssl.PEMParser;
 import org.bouncycastle.pkcs.PKCS10CertificationRequest;
 import org.elasticsearch.cli.MockTerminal;
@@ -46,11 +45,9 @@ import org.elasticsearch.xpack.security.cli.CertificateTool.CertificateInformati
 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;
-import org.mockito.Mockito;
 
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.TrustManagerFactory;
@@ -72,7 +69,6 @@ import java.security.Key;
 import java.security.KeyPair;
 import java.security.KeyStore;
 import java.security.Principal;
-import java.security.PrivateKey;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAKey;
@@ -97,6 +93,9 @@ import static org.hamcrest.Matchers.in;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * Unit tests for the tool used to simplify SSL certificate generation
@@ -306,22 +305,17 @@ public class CertificateToolTests extends ESTestCase {
         X509Certificate caCert = CertGenUtils.generateCACertificate(new X500Principal("CN=test ca"), keyPair, days);
 
         final boolean selfSigned = randomBoolean();
-        final boolean generatedCa = randomBoolean();
-        final boolean keepCaKey = generatedCa && randomBoolean();
         final String keyPassword = randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD : null;
 
         assertFalse(Files.exists(outputFile));
         CAInfo caInfo = selfSigned ? null :
-            new CAInfo(caCert, keyPair.getPrivate(), generatedCa, keyPassword == null ? null : keyPassword.toCharArray());
+            new CAInfo(caCert, keyPair.getPrivate(), false, keyPassword == null ? null : keyPassword.toCharArray());
         final GenerateCertificateCommand command = new GenerateCertificateCommand();
         List<String> args = CollectionUtils.arrayAsArrayList("-keysize", String.valueOf(keySize), "-days", String.valueOf(days), "-pem");
         if (keyPassword != null) {
             args.add("-pass");
             args.add(keyPassword);
         }
-        if (selfSigned == false && keepCaKey) {
-            args.add("-keep-ca-key");
-        }
         final OptionSet options = command.getParser().parse(Strings.toStringArray(args));
 
         command.generateAndWriteSignedCertificates(outputFile, true, options, certInfos, caInfo, null);
@@ -335,49 +329,7 @@ public class CertificateToolTests extends ESTestCase {
         FileSystem fileSystem = FileSystems.newFileSystem(new URI("jar:" + outputFile.toUri()), Collections.emptyMap());
         Path zipRoot = fileSystem.getPath("/");
 
-        if (selfSigned == false && generatedCa) {
-            assertTrue(Files.exists(zipRoot.resolve("ca")));
-            assertTrue(Files.exists(zipRoot.resolve("ca").resolve("ca.crt")));
-            // check the CA cert
-            try (InputStream input = Files.newInputStream(zipRoot.resolve("ca").resolve("ca.crt"))) {
-                X509Certificate parsedCaCert = readX509Certificate(input);
-                assertThat(parsedCaCert.getSubjectX500Principal().getName(), containsString("test ca"));
-                assertEquals(caCert, parsedCaCert);
-                long daysBetween = getDurationInDays(caCert);
-                assertEquals(days, (int) daysBetween);
-            }
-
-            if (keepCaKey) {
-                assertTrue(Files.exists(zipRoot.resolve("ca").resolve("ca.key")));
-                // check the CA key
-                if (keyPassword != null) {
-                    try (Reader reader = Files.newBufferedReader(zipRoot.resolve("ca").resolve("ca.key"))) {
-                        PEMParser pemParser = new PEMParser(reader);
-                        Object parsed = pemParser.readObject();
-                        assertThat(parsed, instanceOf(PEMEncryptedKeyPair.class));
-                        // Verify we are using AES encryption
-                        final PEMDecryptorProvider pemDecryptorProvider = Mockito.mock(PEMDecryptorProvider.class);
-                        try {
-                            ((PEMEncryptedKeyPair) parsed).decryptKeyPair(pemDecryptorProvider);
-                        } catch (Exception e) {
-                            // Catch error thrown by the empty mock, we are only interested in the argument passed in
-                        }
-                        finally {
-                            Mockito.verify(pemDecryptorProvider).get("AES-128-CBC");
-                        }
-                        char[] zeroChars = new char[caInfo.password.length];
-                        Arrays.fill(zeroChars, (char) 0);
-                        assertArrayEquals(zeroChars, caInfo.password);
-                    }
-                }
-
-                PrivateKey privateKey = PemUtils.readPrivateKey(zipRoot.resolve("ca").resolve("ca.key"),
-                    () -> keyPassword != null ? keyPassword.toCharArray() : null);
-                assertEquals(caInfo.certAndKey.key, privateKey);
-            }
-        } else {
-            assertFalse(Files.exists(zipRoot.resolve("ca")));
-        }
+        assertFalse(Files.exists(zipRoot.resolve("ca")));
 
         for (CertificateInformation certInfo : certInfos) {
             String filename = certInfo.name.filename;
@@ -413,6 +365,22 @@ public class CertificateToolTests extends ESTestCase {
         }
     }
 
+    @SuppressWarnings("unchecked")
+    public void testErrorMessageOnInvalidKeepCaOption() {
+        final CertificateTool certificateTool = new CertificateTool();
+        final OptionSet optionSet = mock(OptionSet.class);
+        when(optionSet.valuesOf(any(OptionSpec.class))).thenAnswer(invocation -> {
+            if (invocation.getArguments()[0] instanceof NonOptionArgumentSpec) {
+                return List.of("cert", "--keep-ca-key");
+            } else {
+                return List.of();
+            }
+        });
+        final UserException e = expectThrows(UserException.class,
+            () -> certificateTool.execute(new MockTerminal(), optionSet));
+        assertThat(e.getMessage(), containsString("Generating certificates without providing a CA is no longer supported"));
+    }
+
     public void testGetCAInfo() throws Exception {
         Environment env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build());
         Path testNodeCertPath = getDataPath("/org/elasticsearch/xpack/security/cli/testnode.crt");
@@ -470,17 +438,9 @@ public class CertificateToolTests extends ESTestCase {
             }
         }
 
-        options = command.getParser().parse(Strings.toStringArray(args));
-        caInfo = command.getCAInfo(terminal, options, env);
-        caCK = caInfo.certAndKey;
-        assertThat(terminal.getOutput(),
-            containsString("Generating certificates without providing a CA certificate is deprecated."));
-        assertThat(caCK.cert, instanceOf(X509Certificate.class));
-        assertEquals(caCK.cert.getSubjectX500Principal().getName(), "CN=foo bar");
-        assertThat(caCK.key.getAlgorithm(), containsString("RSA"));
-        assertTrue(caInfo.generated);
-        assertEquals(keySize, getKeySize(caCK.key));
-        assertEquals(days, getDurationInDays(caCK.cert));
+        final OptionSet options2 = command.getParser().parse(Strings.toStringArray(args));
+        final UserException e = expectThrows(UserException.class, () -> command.getCAInfo(terminal, options2, env));
+        assertThat(e.getMessage(), containsString("Must specify either --ca or --ca-cert/--ca-key or --self-signed"));
 
         // test self-signed
         args = CollectionUtils.arrayAsArrayList("-self-signed");
@@ -551,7 +511,7 @@ public class CertificateToolTests extends ESTestCase {
      * A multi-stage test that:
      * - Create a new CA
      * - Uses that CA to create 2 node certificates
-     * - Creates a 3rd node certificate using an auto-generated CA or a self-signed cert
+     * - Creates a 3rd node certificate as a self-signed cert
      * - Checks that the first 2 node certificates trust one another
      * - Checks that the 3rd node certificate is _not_ trusted
      * - Checks that all 3 certificates have the right values based on the command line options provided during generation
@@ -567,14 +527,12 @@ public class CertificateToolTests extends ESTestCase {
         final Path node2File = tempDir.resolve("node2.p12").toAbsolutePath();
         final Path node3File = tempDir.resolve("node3.p12").toAbsolutePath();
 
-        final int caKeySize = randomIntBetween(4, 8) * 512;
         final int node1KeySize = randomIntBetween(2, 6) * 512;
         final int node2KeySize = randomIntBetween(2, 6) * 512;
         final int node3KeySize = randomIntBetween(1, 4) * 512;
 
         final int days = randomIntBetween(7, 1500);
 
-        final String caPassword = randomFrom("", randomAlphaOfLengthBetween(4, 16));
         final String node1Password = randomFrom("", randomAlphaOfLengthBetween(4, 16));
         final String node2Password = randomFrom("", randomAlphaOfLengthBetween(4, 16));
         final String node3Password = randomFrom("", randomAlphaOfLengthBetween(4, 16));
@@ -583,23 +541,7 @@ public class CertificateToolTests extends ESTestCase {
         final String node2Ip = "200.182." + randomIntBetween(1, 250) + "." + randomIntBetween(1, 250);
         final String node3Ip = "200.183." + randomIntBetween(1, 250) + "." + randomIntBetween(1, 250);
 
-        final CertificateAuthorityCommand caCommand = new CertificateAuthorityCommand() {
-            @Override
-            Path resolveOutputPath(Terminal terminal, OptionSet options, String defaultFilename) throws IOException {
-                // Needed to work within the security manager
-                return caFile;
-            }
-        };
-        final OptionSet caOptions = caCommand.getParser().parse(
-                "-ca-dn", "CN=My ElasticSearch Cluster",
-                "-pass", caPassword,
-                "-out", caFile.toString(),
-                "-keysize", String.valueOf(caKeySize),
-                "-days", String.valueOf(days)
-        );
-        caCommand.execute(terminal, caOptions, env);
-
-        assertThat(caFile, pathExists());
+        final String caPassword = generateCA(caFile, terminal, env);
 
         final GenerateCertificateCommand gen1Command = new PathAwareGenerateCertificateCommand(caFile, node1File);
         final OptionSet gen1Options = gen1Command.getParser().parse(
@@ -640,13 +582,7 @@ public class CertificateToolTests extends ESTestCase {
             "-dns", "node03.cluster2.es.internal.corp.net",
             "-ip", node3Ip
         );
-        final boolean selfSigned = randomBoolean();
-        if (selfSigned) {
-            gen3Args.add("-self-signed");
-        } else {
-            gen3Args.add("-ca-dn");
-            gen3Args.add("CN=My ElasticSearch Cluster 2");
-        }
+        gen3Args.add("-self-signed");
         final GenerateCertificateCommand gen3Command = new PathAwareGenerateCertificateCommand(null, node3File);
         final OptionSet gen3Options = gen3Command.getParser().parse(
                 Strings.toStringArray(gen3Args));
@@ -688,14 +624,8 @@ public class CertificateToolTests extends ESTestCase {
         assertThat(getKeySize(node3Key), equalTo(node3KeySize));
         final Certificate[] certificateChain = node3KeyStore.getCertificateChain(CertificateTool.DEFAULT_CERT_NAME);
         final X509Certificate node3x509Certificate = (X509Certificate) certificateChain[0];
-        if (selfSigned) {
-            assertEquals(1, certificateChain.length);
-            assertEquals(node3x509Certificate.getSubjectX500Principal(), node3x509Certificate.getIssuerX500Principal());
-        } else {
-            assertEquals(2, certificateChain.length);
-            assertEquals(node3x509Certificate.getIssuerX500Principal(),
-                ((X509Certificate) certificateChain[1]).getSubjectX500Principal());
-        }
+        assertEquals(1, certificateChain.length);
+        assertEquals(node3x509Certificate.getSubjectX500Principal(), node3x509Certificate.getIssuerX500Principal());
     }
 
 
@@ -711,18 +641,21 @@ public class CertificateToolTests extends ESTestCase {
         final MockTerminal terminal = new MockTerminal();
         Environment env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", tempDir).build());
 
-        final Path pkcs12Zip = tempDir.resolve("p12.zip");
+        final Path caFile = tempDir.resolve("ca.p12");
+        final String caPassword = generateCA(caFile, terminal, env);
+
+        final Path node1Pkcs12 = tempDir.resolve("node1.p12");
         final Path pemZip = tempDir.resolve("pem.zip");
 
         final int keySize = randomIntBetween(4, 8) * 512;
         final int days = randomIntBetween(500, 1500);
 
-        final String caPassword = randomAlphaOfLengthBetween(4, 16);
         final String node1Password = randomAlphaOfLengthBetween(4, 16);
 
-        final GenerateCertificateCommand gen1Command = new PathAwareGenerateCertificateCommand(null, pkcs12Zip);
+        final GenerateCertificateCommand gen1Command = new PathAwareGenerateCertificateCommand(caFile, node1Pkcs12);
         final OptionSet gen1Options = gen1Command.getParser().parse(
-                "-keep-ca-key",
+                "-ca", "<ca>",
+                "-ca-pass", caPassword,
                 "-out", "<zip>",
                 "-keysize", String.valueOf(keySize),
                 "-days", String.valueOf(days),
@@ -730,22 +663,12 @@ public class CertificateToolTests extends ESTestCase {
                 "-name", "node01"
         );
 
-        terminal.addSecretInput(caPassword);
         terminal.addSecretInput(node1Password);
         gen1Command.execute(terminal, gen1Options, env);
 
-        assertThat(pkcs12Zip, pathExists());
+        assertThat(node1Pkcs12, pathExists());
 
-        FileSystem zip1FS = FileSystems.newFileSystem(new URI("jar:" + pkcs12Zip.toUri()), Collections.emptyMap());
-        Path zip1Root = zip1FS.getPath("/");
-
-        final Path caP12 = zip1Root.resolve("ca/ca.p12");
-        assertThat(caP12, pathExists());
-
-        final Path node1P12 = zip1Root.resolve("node01/node01.p12");
-        assertThat(node1P12, pathExists());
-
-        final GenerateCertificateCommand gen2Command = new PathAwareGenerateCertificateCommand(caP12, pemZip);
+        final GenerateCertificateCommand gen2Command = new PathAwareGenerateCertificateCommand(caFile, pemZip);
         final OptionSet gen2Options = gen2Command.getParser().parse(
                 "-ca", "<ca>",
                 "-out", "<zip>",
@@ -772,11 +695,11 @@ public class CertificateToolTests extends ESTestCase {
         final Path node2Key = zip2Root.resolve("node02/node02.key");
         assertThat(node2Key, pathExists());
 
-        final KeyStore node1KeyStore = CertParsingUtils.readKeyStore(node1P12, "PKCS12", node1Password.toCharArray());
+        final KeyStore node1KeyStore = CertParsingUtils.readKeyStore(node1Pkcs12, "PKCS12", node1Password.toCharArray());
         final KeyStore node1TrustStore = node1KeyStore;
 
         final KeyStore node2KeyStore = CertParsingUtils.getKeyStoreFromPEM(node2Cert, node2Key, new char[0]);
-        final KeyStore node2TrustStore = CertParsingUtils.readKeyStore(caP12, "PKCS12", caPassword.toCharArray());
+        final KeyStore node2TrustStore = CertParsingUtils.readKeyStore(caFile, "PKCS12", caPassword.toCharArray());
 
         checkTrust(node1KeyStore, node1Password.toCharArray(), node2TrustStore, true);
         checkTrust(node2KeyStore, new char[0], node1TrustStore, true);
@@ -808,8 +731,9 @@ public class CertificateToolTests extends ESTestCase {
             }
         };
 
-        final String optionThatTriggersZip = randomFrom("-pem", "-keep-ca-key", "-multiple", "-in=input.yml");
+        final String optionThatTriggersZip = randomFrom("-pem", "-multiple", "-in=input.yml");
         final OptionSet genOptions = genCommand.getParser().parse(
+                "--self-signed",
                 "-out", "<zip>",
                 optionThatTriggersZip
         );
@@ -982,4 +906,30 @@ public class CertificateToolTests extends ESTestCase {
             return outFile;
         }
     }
+
+    private String generateCA(Path caFile, Terminal terminal, Environment env) throws Exception {
+        final int caKeySize = randomIntBetween(4, 8) * 512;
+        final int days = randomIntBetween(7, 1500);
+        final String caPassword = randomFrom("", randomAlphaOfLengthBetween(4, 16));
+
+        final CertificateAuthorityCommand caCommand = new CertificateAuthorityCommand() {
+            @Override
+            Path resolveOutputPath(Terminal terminal, OptionSet options, String defaultFilename) {
+                // Needed to work within the security manager
+                return caFile;
+            }
+        };
+        final OptionSet caOptions = caCommand.getParser().parse(
+            "-ca-dn", "CN=My ElasticSearch Cluster",
+            "-pass", caPassword,
+            "-out", caFile.toString(),
+            "-keysize", String.valueOf(caKeySize),
+            "-days", String.valueOf(days)
+        );
+        caCommand.execute(terminal, caOptions, env);
+
+        assertThat(caFile, pathExists());
+
+        return caPassword;
+    }
 }