/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2026 3A Systems, LLC. */ package org.opends.server.tools; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.io.File; import org.opends.server.DirectoryServerTestCase; import org.opends.server.TestCaseUtils; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import com.forgerock.opendj.cli.ArgumentException; /** * Tests the arguments which let {@code setup} provision the trust store used for server * to server communication from a key store the operator already holds. */ public class InstallDSArgumentParserTestCase extends DirectoryServerTestCase { /** A fragment of the message reporting that the key pair to present has to come from a key store. */ private static final String KEY_STORE_REQUIRED = "requires an existing key store"; /** A fragment of the message reporting a certificate file which cannot be read. */ private static final String CERT_FILE_INVALID = "does not exist"; /** A fragment of the message reporting an argument the parser does not know. */ private static final String UNKNOWN_ARGUMENT = "is not allowed for use with this program"; /** * The key pair presented on the replication port is imported from an existing key * store, so asking for a self-signed certificate to be generated cannot satisfy it. * * @throws Exception * If a problem occurs. */ @Test public void testUseKeyStoreForReplicationRejectsAGeneratedCertificate() throws Exception { assertReportsUseKeyStoreForReplication(true, "--cli", "-n", "-w", "password", "-b", "dc=example,dc=com", "--generateSelfSignedCertificate", "--enableStartTLS", "--useKeyStoreForReplication"); } /** * The private key of a PKCS#11 token cannot be exported, so it cannot be copied into * the trust store used for replication. * * @throws Exception * If a problem occurs. */ @Test public void testUseKeyStoreForReplicationRejectsAPkcs11Token() throws Exception { assertReportsUseKeyStoreForReplication(true, "--cli", "-n", "-w", "password", "-b", "dc=example,dc=com", "--usePkcs11Keystore", "--keyStorePassword", "password", "--enableStartTLS", "--useKeyStoreForReplication"); } /** * A key store the installer can read the key pair from is accepted, whichever of the * four key store arguments names it. * * @param keyStoreArgument * The argument naming the key store. * @throws Exception * If a problem occurs. */ @Test(dataProvider = "keyStoreArguments") public void testUseKeyStoreForReplicationAcceptsAKeyStore(String keyStoreArgument) throws Exception { final File tmpDir = TestCaseUtils.createTemporaryDirectory("useKeyStoreForReplication"); try { final File keyStore = new File(tmpDir, "server.keystore"); assertTrue(keyStore.createNewFile()); assertReportsUseKeyStoreForReplication(false, "--cli", "-n", "-w", "password", "-b", "dc=example,dc=com", keyStoreArgument, keyStore.getAbsolutePath(), "--keyStorePassword", "password", "--enableStartTLS", "--useKeyStoreForReplication"); } finally { TestCaseUtils.deleteDirectory(tmpDir); } } @DataProvider public Object[][] keyStoreArguments() { return new Object[][] { { "--useJavaKeystore" }, { "--useJCEKS" }, { "--usePkcs12keyStore" }, { "--useBcfksKeystore" } }; } /** * Certificates to trust on the replication port are only meaningful together with the * key pair to present there: a server which trusts an authority but keeps presenting * its self-signed certificate is the half-configured state this provisioning exists to * avoid. * * @throws Exception * If a problem occurs. */ @Test public void testReplicationCaCertFileRequiresUseKeyStoreForReplication() throws Exception { final File tmpDir = TestCaseUtils.createTemporaryDirectory("replicationCaCertFile"); try { final File caFile = new File(tmpDir, "ca.crt"); assertTrue(caFile.createNewFile()); final String error = parseAndReturnError( "--cli", "-n", "-w", "password", "-b", "dc=example,dc=com", "--usePkcs12keyStore", new File(tmpDir, "server.p12").getAbsolutePath(), "--keyStorePassword", "password", "--enableStartTLS", "--replicationCaCertFile", caFile.getAbsolutePath()); assertTrue(error.contains("replicationCaCertFile") && error.contains("useKeyStoreForReplication"), error); assertFalse(error.contains(UNKNOWN_ARGUMENT), error); } finally { TestCaseUtils.deleteDirectory(tmpDir); } } /** * A certificate file which does not exist is reported while the arguments are checked, * rather than half way through the installation. * * @throws Exception * If a problem occurs. */ @Test public void testReplicationCaCertFileMustExist() throws Exception { final File tmpDir = TestCaseUtils.createTemporaryDirectory("replicationCaCertMissing"); try { final File missing = new File(tmpDir, "nonexistent.crt"); final String error = parseAndReturnError( "--cli", "-n", "-w", "password", "-b", "dc=example,dc=com", "--usePkcs12keyStore", new File(tmpDir, "server.p12").getAbsolutePath(), "--keyStorePassword", "password", "--enableStartTLS", "--useKeyStoreForReplication", "--replicationCaCertFile", missing.getAbsolutePath()); assertTrue(error.contains(missing.getAbsolutePath()) && error.contains(CERT_FILE_INVALID), error); } finally { TestCaseUtils.deleteDirectory(tmpDir); } } /** * Asserts whether the arguments are rejected because {@code --useKeyStoreForReplication} * cannot be satisfied. Other errors, such as a port which happens to be in use on the * machine running the tests, are ignored: only the message under test is looked for. The * wording is matched as well as the argument name, so that an argument the parser does * not know at all, which is reported by name too, does not pass for the check under test. */ private void assertReportsUseKeyStoreForReplication(boolean expected, String... args) throws Exception { final String error = parseAndReturnError(args); final boolean reported = error.contains("useKeyStoreForReplication") && error.contains(KEY_STORE_REQUIRED); if (expected) { assertTrue(reported, error); } else { assertFalse(reported, error); assertFalse(error.contains(UNKNOWN_ARGUMENT), error); } } /** Parses the provided arguments and returns the reported errors, empty if there is none. */ private String parseAndReturnError(String... args) throws Exception { final InstallDSArgumentParser parser = new InstallDSArgumentParser(InstallDS.class.getName()); parser.initializeArguments(); try { parser.parseArguments(args); return ""; } catch (ArgumentException e) { return e.getMessage(); } } }