org.elasticsearch.xpack.core.ssl.CertificateGenerateToolTests.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.xpack.core.ssl.CertificateGenerateToolTests.java

Source

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * 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;

import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.elasticsearch.core.internal.io.IOUtils;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
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.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.common.settings.Settings;
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.hamcrest.Matchers;
import org.junit.After;
import org.junit.BeforeClass;

import javax.security.auth.x500.X500Principal;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.InetAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAKey;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.elasticsearch.test.TestMatchers.pathExists;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;

/**
 * Unit tests for the tool used to simplify SSL certificate generation
 */
// TODO baz - fix this to work in intellij+java9, its complaining about java.sql.Date not being on the classpath
public class CertificateGenerateToolTests extends ESTestCase {

    private FileSystem jimfs;
    private static final String CN_OID = "2.5.4.3";

    private Path initTempDir() throws Exception {
        Configuration conf = Configuration.unix().toBuilder().setAttributeViews("posix").build();
        jimfs = Jimfs.newFileSystem(conf);
        Path tempDir = jimfs.getPath("temp");
        IOUtils.rm(tempDir);
        Files.createDirectories(tempDir);
        return tempDir;
    }

    @BeforeClass
    public static void checkFipsJvm() {
        assumeFalse("Can't run in a FIPS JVM, depends on Non FIPS BouncyCastle", inFipsJvm());
    }

    @After
    public void tearDown() throws Exception {
        IOUtils.close(jimfs);
        super.tearDown();
    }

    public void testOutputDirectory() throws Exception {
        Path outputDir = createTempDir();
        Path outputFile = outputDir.resolve("certs.zip");
        MockTerminal terminal = new MockTerminal();

        // test with a user provided dir
        Path resolvedOutputFile = CertificateGenerateTool.getOutputFile(terminal, outputFile.toString(), null);
        assertEquals(outputFile, resolvedOutputFile);
        assertTrue(terminal.getOutput().isEmpty());

        // test without a user provided directory
        Path userPromptedOutputFile = outputDir.resolve("csr");
        assertFalse(Files.exists(userPromptedOutputFile));
        terminal.addTextInput(userPromptedOutputFile.toString());
        resolvedOutputFile = CertificateGenerateTool.getOutputFile(terminal, null, "out.zip");
        assertEquals(userPromptedOutputFile, resolvedOutputFile);
        assertTrue(terminal.getOutput().isEmpty());

        // test with empty user input
        String defaultFilename = randomAlphaOfLengthBetween(1, 10);
        Path expectedDefaultPath = resolvePath(defaultFilename);
        terminal.addTextInput("");
        resolvedOutputFile = CertificateGenerateTool.getOutputFile(terminal, null, defaultFilename);
        assertEquals(expectedDefaultPath, resolvedOutputFile);
        assertTrue(terminal.getOutput().isEmpty());
    }

    public void testPromptingForInstanceInformation() throws Exception {
        final int numberOfInstances = scaledRandomIntBetween(1, 12);
        Map<String, Map<String, String>> instanceInput = new HashMap<>(numberOfInstances);
        for (int i = 0; i < numberOfInstances; i++) {
            final String name;
            while (true) {
                String randomName = getValidRandomInstanceName();
                if (instanceInput.containsKey(randomName) == false) {
                    name = randomName;
                    break;
                }
            }
            Map<String, String> instanceInfo = new HashMap<>();
            instanceInput.put(name, instanceInfo);
            instanceInfo.put("ip", randomFrom("127.0.0.1", "::1", "192.168.1.1,::1", ""));
            instanceInfo.put("dns", randomFrom("localhost", "localhost.localdomain", "localhost,myhost", ""));
            logger.info("instance [{}] name [{}] [{}]", i, name, instanceInfo);
        }

        int count = 0;
        MockTerminal terminal = new MockTerminal();
        for (Entry<String, Map<String, String>> entry : instanceInput.entrySet()) {
            terminal.addTextInput(entry.getKey());
            terminal.addTextInput("");
            terminal.addTextInput(entry.getValue().get("ip"));
            terminal.addTextInput(entry.getValue().get("dns"));
            count++;
            if (count == numberOfInstances) {
                terminal.addTextInput("n");
            } else {
                terminal.addTextInput("y");
            }
        }

        Collection<CertificateInformation> certInfos = CertificateGenerateTool
                .getCertificateInformationList(terminal, null);
        logger.info("certificate tool output:\n{}", terminal.getOutput());
        assertEquals(numberOfInstances, certInfos.size());
        for (CertificateInformation certInfo : certInfos) {
            String name = certInfo.name.originalName;
            Map<String, String> instanceInfo = instanceInput.get(name);
            assertNotNull("did not find map for " + name, instanceInfo);
            List<String> expectedIps = Arrays
                    .asList(Strings.commaDelimitedListToStringArray(instanceInfo.get("ip")));
            List<String> expectedDns = Arrays
                    .asList(Strings.commaDelimitedListToStringArray(instanceInfo.get("dns")));
            assertEquals(expectedIps, certInfo.ipAddresses);
            assertEquals(expectedDns, certInfo.dnsNames);
            instanceInput.remove(name);
        }
        assertEquals(0, instanceInput.size());
        final String output = terminal.getOutput();
        assertTrue("Output: " + output, output.isEmpty());
    }

    public void testParsingFile() throws Exception {
        Path tempDir = initTempDir();
        Path instanceFile = writeInstancesTo(tempDir.resolve("instances.yml"));
        Collection<CertificateInformation> certInfos = CertificateGenerateTool.parseFile(instanceFile);
        assertEquals(4, certInfos.size());

        Map<String, CertificateInformation> certInfosMap = certInfos.stream()
                .collect(Collectors.toMap((c) -> c.name.originalName, Function.identity()));
        CertificateInformation certInfo = certInfosMap.get("node1");
        assertEquals(Collections.singletonList("127.0.0.1"), certInfo.ipAddresses);
        assertEquals(Collections.singletonList("localhost"), certInfo.dnsNames);
        assertEquals(Collections.emptyList(), certInfo.commonNames);
        assertEquals("node1", certInfo.name.filename);

        certInfo = certInfosMap.get("node2");
        assertEquals(Collections.singletonList("::1"), certInfo.ipAddresses);
        assertEquals(Collections.emptyList(), certInfo.dnsNames);
        assertEquals(Collections.singletonList("node2.elasticsearch"), certInfo.commonNames);
        assertEquals("node2", certInfo.name.filename);

        certInfo = certInfosMap.get("node3");
        assertEquals(Collections.emptyList(), certInfo.ipAddresses);
        assertEquals(Collections.emptyList(), certInfo.dnsNames);
        assertEquals(Collections.emptyList(), certInfo.commonNames);
        assertEquals("node3", certInfo.name.filename);

        certInfo = certInfosMap.get("CN=different value");
        assertEquals(Collections.emptyList(), certInfo.ipAddresses);
        assertEquals(Collections.singletonList("node4.mydomain.com"), certInfo.dnsNames);
        assertEquals(Collections.emptyList(), certInfo.commonNames);
        assertEquals("different file", certInfo.name.filename);
    }

    public void testGeneratingCsr() throws Exception {
        Path tempDir = initTempDir();
        Path outputFile = tempDir.resolve("out.zip");
        Path instanceFile = writeInstancesTo(tempDir.resolve("instances.yml"));
        Collection<CertificateInformation> certInfos = CertificateGenerateTool.parseFile(instanceFile);
        assertEquals(4, certInfos.size());

        assertFalse(Files.exists(outputFile));
        CertificateGenerateTool.generateAndWriteCsrs(outputFile, certInfos, randomFrom(1024, 2048));
        assertTrue(Files.exists(outputFile));

        Set<PosixFilePermission> perms = Files.getPosixFilePermissions(outputFile);
        assertTrue(perms.toString(), perms.contains(PosixFilePermission.OWNER_READ));
        assertTrue(perms.toString(), perms.contains(PosixFilePermission.OWNER_WRITE));
        assertEquals(perms.toString(), 2, perms.size());

        FileSystem fileSystem = FileSystems.newFileSystem(new URI("jar:" + outputFile.toUri()),
                Collections.emptyMap());
        Path zipRoot = fileSystem.getPath("/");

        assertFalse(Files.exists(zipRoot.resolve("ca")));
        for (CertificateInformation certInfo : certInfos) {
            String filename = certInfo.name.filename;
            assertTrue(Files.exists(zipRoot.resolve(filename)));
            final Path csr = zipRoot.resolve(filename + "/" + filename + ".csr");
            assertTrue(Files.exists(csr));
            assertTrue(Files.exists(zipRoot.resolve(filename + "/" + filename + ".key")));
            PKCS10CertificationRequest request = readCertificateRequest(csr);
            assertEquals(certInfo.name.x500Principal.getName(), request.getSubject().toString());
            Attribute[] extensionsReq = request.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
            if (certInfo.ipAddresses.size() > 0 || certInfo.dnsNames.size() > 0) {
                assertEquals(1, extensionsReq.length);
                Extensions extensions = Extensions.getInstance(extensionsReq[0].getAttributeValues()[0]);
                GeneralNames subjAltNames = GeneralNames.fromExtensions(extensions,
                        Extension.subjectAlternativeName);
                assertSubjAltNames(subjAltNames, certInfo);
            } else {
                assertEquals(0, extensionsReq.length);
            }
        }
    }

    public void testGeneratingSignedCertificates() throws Exception {
        Path tempDir = initTempDir();
        Path outputFile = tempDir.resolve("out.zip");
        Path instanceFile = writeInstancesTo(tempDir.resolve("instances.yml"));
        Collection<CertificateInformation> certInfos = CertificateGenerateTool.parseFile(instanceFile);
        assertEquals(4, certInfos.size());

        final int keysize = randomFrom(1024, 2048);
        final int days = randomIntBetween(1, 1024);
        KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
        X509Certificate caCert = CertGenUtils.generateCACertificate(new X500Principal("CN=test ca"), keyPair, days);

        final boolean generatedCa = randomBoolean();
        final char[] keyPassword = randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() : null;
        final char[] pkcs12Password = randomBoolean() ? randomAlphaOfLengthBetween(1, 12).toCharArray() : null;
        assertFalse(Files.exists(outputFile));
        CAInfo caInfo = new CAInfo(caCert, keyPair.getPrivate(), generatedCa, keyPassword);
        CertificateGenerateTool.generateAndWriteSignedCertificates(outputFile, certInfos, caInfo, keysize, days,
                pkcs12Password);
        assertTrue(Files.exists(outputFile));

        Set<PosixFilePermission> perms = Files.getPosixFilePermissions(outputFile);
        assertTrue(perms.toString(), perms.contains(PosixFilePermission.OWNER_READ));
        assertTrue(perms.toString(), perms.contains(PosixFilePermission.OWNER_WRITE));
        assertEquals(perms.toString(), 2, perms.size());

        FileSystem fileSystem = FileSystems.newFileSystem(new URI("jar:" + outputFile.toUri()),
                Collections.emptyMap());
        Path zipRoot = fileSystem.getPath("/");

        if (generatedCa) {
            assertTrue(Files.exists(zipRoot.resolve("ca")));
            assertTrue(Files.exists(zipRoot.resolve("ca").resolve("ca.crt")));
            assertTrue(Files.exists(zipRoot.resolve("ca").resolve("ca.key")));
            // 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 = ChronoUnit.DAYS.between(caCert.getNotBefore().toInstant(),
                        caCert.getNotAfter().toInstant());
                assertEquals(days, (int) daysBetween);
            }

            // 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));
                    char[] zeroChars = new char[keyPassword.length];
                    Arrays.fill(zeroChars, (char) 0);
                    assertArrayEquals(zeroChars, keyPassword);
                }
            }

            PrivateKey privateKey = PemUtils.readPrivateKey(zipRoot.resolve("ca").resolve("ca.key"),
                    () -> keyPassword != null ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() : null);
            assertEquals(caInfo.privateKey, privateKey);
        } else {
            assertFalse(Files.exists(zipRoot.resolve("ca")));
        }

        for (CertificateInformation certInfo : certInfos) {
            String filename = certInfo.name.filename;
            assertTrue(Files.exists(zipRoot.resolve(filename)));
            final Path cert = zipRoot.resolve(filename + "/" + filename + ".crt");
            assertTrue(Files.exists(cert));
            assertTrue(Files.exists(zipRoot.resolve(filename + "/" + filename + ".key")));
            final Path p12 = zipRoot.resolve(filename + "/" + filename + ".p12");
            try (InputStream input = Files.newInputStream(cert)) {
                X509Certificate certificate = readX509Certificate(input);
                assertEquals(certInfo.name.x500Principal.toString(),
                        certificate.getSubjectX500Principal().getName());
                final int sanCount = certInfo.ipAddresses.size() + certInfo.dnsNames.size()
                        + certInfo.commonNames.size();
                if (sanCount == 0) {
                    assertNull(certificate.getSubjectAlternativeNames());
                } else {
                    X509CertificateHolder x509CertHolder = new X509CertificateHolder(certificate.getEncoded());
                    GeneralNames subjAltNames = GeneralNames.fromExtensions(x509CertHolder.getExtensions(),
                            Extension.subjectAlternativeName);
                    assertSubjAltNames(subjAltNames, certInfo);
                }
                if (pkcs12Password != null) {
                    assertThat(p12, pathExists(p12));
                    try (InputStream in = Files.newInputStream(p12)) {
                        final KeyStore ks = KeyStore.getInstance("PKCS12");
                        ks.load(in, pkcs12Password);
                        final Certificate p12Certificate = ks.getCertificate(certInfo.name.originalName);
                        assertThat("Certificate " + certInfo.name, p12Certificate, notNullValue());
                        assertThat(p12Certificate, equalTo(certificate));
                        final Key key = ks.getKey(certInfo.name.originalName, pkcs12Password);
                        assertThat(key, notNullValue());
                    }
                } else {
                    assertThat(p12, not(pathExists(p12)));
                }
            }
        }
    }

    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");
        final boolean passwordPrompt = randomBoolean();
        MockTerminal terminal = new MockTerminal();
        if (passwordPrompt) {
            terminal.addSecretInput("testnode");
        }

        final int days = randomIntBetween(1, 1024);
        CAInfo caInfo = CertificateGenerateTool.getCAInfo(terminal, "CN=foo", testNodeCertPath.toString(),
                testNodeKeyPath.toString(), passwordPrompt ? null : "testnode".toCharArray(), passwordPrompt, env,
                randomFrom(1024, 2048), days);
        assertTrue(terminal.getOutput().isEmpty());
        assertEquals(caInfo.caCert.getSubjectX500Principal().getName(),
                "CN=Elasticsearch Test Node,OU=elasticsearch,O=org");
        assertThat(caInfo.privateKey.getAlgorithm(), containsString("RSA"));
        assertEquals(2048, ((RSAKey) caInfo.privateKey).getModulus().bitLength());
        assertFalse(caInfo.generated);
        long daysBetween = ChronoUnit.DAYS.between(caInfo.caCert.getNotBefore().toInstant(),
                caInfo.caCert.getNotAfter().toInstant());
        assertEquals(1460L, daysBetween);

        // test generation
        final boolean passwordProtected = randomBoolean();
        final char[] password;
        if (passwordPrompt && passwordProtected) {
            password = null;
            terminal.addSecretInput("testnode");
        } else {
            password = "testnode".toCharArray();
        }
        final int keysize = randomFrom(1024, 2048);
        caInfo = CertificateGenerateTool.getCAInfo(terminal, "CN=foo bar", null, null, password,
                passwordProtected && passwordPrompt, env, keysize, days);
        assertTrue(terminal.getOutput().isEmpty());
        assertThat(caInfo.caCert, instanceOf(X509Certificate.class));
        assertEquals(caInfo.caCert.getSubjectX500Principal().getName(), "CN=foo bar");
        assertThat(caInfo.privateKey.getAlgorithm(), containsString("RSA"));
        assertTrue(caInfo.generated);
        assertEquals(keysize, ((RSAKey) caInfo.privateKey).getModulus().bitLength());
        daysBetween = ChronoUnit.DAYS.between(caInfo.caCert.getNotBefore().toInstant(),
                caInfo.caCert.getNotAfter().toInstant());
        assertEquals(days, (int) daysBetween);
    }

    public void testNameValues() throws Exception {
        // good name
        Name name = Name.fromUserProvidedName("my instance", "my instance");
        assertEquals("my instance", name.originalName);
        assertNull(name.error);
        assertEquals("CN=my instance", name.x500Principal.getName());
        assertEquals("my instance", name.filename);

        // too long
        String userProvidedName = randomAlphaOfLength(CertificateGenerateTool.MAX_FILENAME_LENGTH + 1);
        name = Name.fromUserProvidedName(userProvidedName, userProvidedName);
        assertEquals(userProvidedName, name.originalName);
        assertThat(name.error, containsString("valid filename"));

        // too short
        name = Name.fromUserProvidedName("", "");
        assertEquals("", name.originalName);
        assertThat(name.error, containsString("valid filename"));
        assertEquals("CN=", name.x500Principal.getName());
        assertNull(name.filename);

        // invalid characters only
        userProvidedName = "<>|<>*|?\"\\";
        name = Name.fromUserProvidedName(userProvidedName, userProvidedName);
        assertEquals(userProvidedName, name.originalName);
        assertThat(name.error, containsString("valid DN"));
        assertNull(name.x500Principal);
        assertNull(name.filename);

        // invalid for file but DN ok
        userProvidedName = "*";
        name = Name.fromUserProvidedName(userProvidedName, userProvidedName);
        assertEquals(userProvidedName, name.originalName);
        assertThat(name.error, containsString("valid filename"));
        assertEquals("CN=" + userProvidedName, name.x500Principal.getName());
        assertNull(name.filename);

        // invalid with valid chars for filename
        userProvidedName = "*.mydomain.com";
        name = Name.fromUserProvidedName(userProvidedName, userProvidedName);
        assertEquals(userProvidedName, name.originalName);
        assertThat(name.error, containsString("valid filename"));
        assertEquals("CN=" + userProvidedName, name.x500Principal.getName());

        // valid but could create hidden file/dir so it is not allowed
        userProvidedName = ".mydomain.com";
        name = Name.fromUserProvidedName(userProvidedName, userProvidedName);
        assertEquals(userProvidedName, name.originalName);
        assertThat(name.error, containsString("valid filename"));
        assertEquals("CN=" + userProvidedName, name.x500Principal.getName());
    }

    private PKCS10CertificationRequest readCertificateRequest(Path path) throws Exception {
        try (Reader reader = Files.newBufferedReader(path); PEMParser pemParser = new PEMParser(reader)) {
            Object object = pemParser.readObject();
            assertThat(object, instanceOf(PKCS10CertificationRequest.class));
            return (PKCS10CertificationRequest) object;
        }
    }

    private X509Certificate readX509Certificate(InputStream input) throws Exception {
        List<Certificate> list = CertParsingUtils.readCertificates(input);
        assertEquals(1, list.size());
        assertThat(list.get(0), instanceOf(X509Certificate.class));
        return (X509Certificate) list.get(0);
    }

    private void assertSubjAltNames(GeneralNames subjAltNames, CertificateInformation certInfo) throws Exception {
        final int expectedCount = certInfo.ipAddresses.size() + certInfo.dnsNames.size()
                + certInfo.commonNames.size();
        assertEquals(expectedCount, subjAltNames.getNames().length);
        Collections.sort(certInfo.dnsNames);
        Collections.sort(certInfo.ipAddresses);
        for (GeneralName generalName : subjAltNames.getNames()) {
            if (generalName.getTagNo() == GeneralName.dNSName) {
                String dns = ((ASN1String) generalName.getName()).getString();
                assertTrue(certInfo.dnsNames.stream().anyMatch(dns::equals));
            } else if (generalName.getTagNo() == GeneralName.iPAddress) {
                byte[] ipBytes = DEROctetString.getInstance(generalName.getName()).getOctets();
                String ip = NetworkAddress.format(InetAddress.getByAddress(ipBytes));
                assertTrue(certInfo.ipAddresses.stream().anyMatch(ip::equals));
            } else if (generalName.getTagNo() == GeneralName.otherName) {
                ASN1Sequence seq = ASN1Sequence.getInstance(generalName.getName());
                assertThat(seq.size(), equalTo(2));
                assertThat(seq.getObjectAt(0), instanceOf(ASN1ObjectIdentifier.class));
                assertThat(seq.getObjectAt(0).toString(), equalTo(CN_OID));
                assertThat(seq.getObjectAt(1), instanceOf(DERTaggedObject.class));
                DERTaggedObject taggedName = (DERTaggedObject) seq.getObjectAt(1);
                assertThat(taggedName.getTagNo(), equalTo(0));
                assertThat(taggedName.getObject(), instanceOf(ASN1String.class));
                assertThat(taggedName.getObject().toString(), Matchers.isIn(certInfo.commonNames));
            } else {
                fail("unknown general name with tag " + generalName.getTagNo());
            }
        }
    }

    /**
     * Gets a random name that is valid for certificate generation. There are some cases where the random value could match one of the
     * reserved names like ca, so this method allows us to avoid these issues.
     */
    private String getValidRandomInstanceName() {
        String name;
        boolean valid;
        do {
            name = randomAlphaOfLengthBetween(1, 32);
            valid = Name.fromUserProvidedName(name, name).error == null;
        } while (valid == false);
        return name;
    }

    /**
     * Writes the description of instances to a given {@link Path}
     */
    private Path writeInstancesTo(Path path) throws IOException {
        Iterable<String> instances = Arrays.asList("instances:", "  - name: \"node1\"", "    ip:",
                "      - \"127.0.0.1\"", "    dns: \"localhost\"", "  - name: \"node2\"", "    filename: \"node2\"",
                "    ip: \"::1\"", "    cn:", "      - \"node2.elasticsearch\"", "  - name: \"node3\"",
                "    filename: \"node3\"", "  - name: \"CN=different value\"", "    filename: \"different file\"",
                "    dns:", "      - \"node4.mydomain.com\"");

        return Files.write(path, instances, StandardCharsets.UTF_8);
    }

    @SuppressForbidden(reason = "resolve paths against CWD for a CLI tool")
    private static Path resolvePath(String path) {
        return PathUtils.get(path).toAbsolutePath();
    }
}