From 70d9a179cdd8d975b44e1815c249e20d9f91097f Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 16 Sep 2026 08:10:15 +0000
Subject: [PATCH] [#912] Provision the ads-truststore from an existing key store at setup time (#984)

---
 opendj-server-legacy/src/test/java/org/opends/server/util/CertificateManagerTestCase.java |  264 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 264 insertions(+), 0 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/util/CertificateManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/util/CertificateManagerTestCase.java
index 013ed48..774d735 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/util/CertificateManagerTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/util/CertificateManagerTestCase.java
@@ -20,7 +20,9 @@
 
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.cert.Certificate;
 import java.util.Arrays;
@@ -1152,6 +1154,268 @@
 
 
   /**
+   * Tests that {@code importKeyEntry} copies the whole certificate chain of the source
+   * key entry and re-encrypts the private key with the password of the destination key
+   * store: the key managers of the server are initialised with the store password only,
+   * so a key which kept the password of the source key store could not be read back.
+   *
+   * @throws  Exception  If a problem occurs.
+   */
+  @Test
+  public void testImportKeyEntryCopiesChainAndReEncryptsKey()
+         throws Exception
+  {
+    final File tmpDir = TestCaseUtils.createTemporaryDirectory("importKeyEntry");
+    try
+    {
+      final CertificateFixture ca = new CertificateFixture("CN=Example CA,O=Example");
+      final File source = new File(tmpDir, "server.p12");
+      ca.addKeyEntry(source, "PKCS12", "sourcePassword", "server-cert", "CN=host.example.com", true);
+
+      final File destination = new File(tmpDir, "ads-truststore");
+      final CertificateManager destinationManager =
+           new CertificateManager(destination.getAbsolutePath(), "JKS", "destinationPassword");
+      destinationManager.importKeyEntry("server-cert",
+           new CertificateManager(source.getAbsolutePath(), "PKCS12", "sourcePassword"), "server-cert");
+
+      final KeyStore keyStore = KeyStore.getInstance("JKS");
+      try (final FileInputStream in = new FileInputStream(destination))
+      {
+        keyStore.load(in, "destinationPassword".toCharArray());
+      }
+      assertTrue(keyStore.isKeyEntry("server-cert"));
+      assertNotNull(keyStore.getKey("server-cert", "destinationPassword".toCharArray()));
+      assertEquals(keyStore.getCertificateChain("server-cert").length, 2);
+    }
+    finally
+    {
+      TestCaseUtils.deleteDirectory(tmpDir);
+    }
+  }
+
+
+
+  /**
+   * Tests that {@code importKeyEntry} reports an alias which the source key store does
+   * not hold, rather than silently importing nothing.
+   *
+   * @throws  Exception  If a problem occurs.
+   */
+  @Test
+  public void testImportKeyEntryNonexistentSourceAlias()
+         throws Exception
+  {
+    final File tmpDir = TestCaseUtils.createTemporaryDirectory("importKeyEntryMissing");
+    try
+    {
+      final CertificateFixture ca = new CertificateFixture("CN=Example CA,O=Example");
+      final File source = new File(tmpDir, "server.p12");
+      ca.addKeyEntry(source, "PKCS12", "sourcePassword", "server-cert", "CN=host.example.com", true);
+
+      final CertificateManager destinationManager = new CertificateManager(
+           new File(tmpDir, "ads-truststore").getAbsolutePath(), "JKS", "destinationPassword");
+      try
+      {
+        destinationManager.importKeyEntry("nonexistent",
+             new CertificateManager(source.getAbsolutePath(), "PKCS12", "sourcePassword"), "nonexistent");
+        fail("Expected a key store exception due to a nonexistent source alias");
+      } catch (KeyStoreException kse) {}
+    }
+    finally
+    {
+      TestCaseUtils.deleteDirectory(tmpDir);
+    }
+  }
+
+
+
+  /**
+   * Tests that {@code getCertificateChain} returns the issuers of a key entry, which is
+   * where the certificates to trust are taken from when a key pair is provisioned.
+   *
+   * @throws  Exception  If a problem occurs.
+   */
+  @Test
+  public void testGetCertificateChainReturnsIssuers()
+         throws Exception
+  {
+    final File tmpDir = TestCaseUtils.createTemporaryDirectory("getCertificateChain");
+    try
+    {
+      final CertificateFixture ca = new CertificateFixture("CN=Example CA,O=Example");
+      final File source = new File(tmpDir, "server.p12");
+      ca.addKeyEntry(source, "PKCS12", "sourcePassword", "server-cert", "CN=host.example.com", true);
+
+      final CertificateManager sourceManager =
+           new CertificateManager(source.getAbsolutePath(), "PKCS12", "sourcePassword");
+      final Certificate[] chain = sourceManager.getCertificateChain("server-cert");
+      assertEquals(chain.length, 2);
+      assertEquals(chain[1], ca.getCaCertificate());
+      assertNull(sourceManager.getCertificateChain("nonexistent"));
+    }
+    finally
+    {
+      TestCaseUtils.deleteDirectory(tmpDir);
+    }
+  }
+
+
+
+  /**
+   * Tests that {@code addTrustedCertificate} stores a certificate held in memory as a
+   * trusted certificate entry: only such an entry is a trust anchor, the certificate
+   * chain of a key entry is not.
+   *
+   * @throws  Exception  If a problem occurs.
+   */
+  @Test
+  public void testAddTrustedCertificateStoresTrustAnchor()
+         throws Exception
+  {
+    final File tmpDir = TestCaseUtils.createTemporaryDirectory("addTrustedCertificate");
+    try
+    {
+      final CertificateFixture ca = new CertificateFixture("CN=Example CA,O=Example");
+      final File destination = new File(tmpDir, "ads-truststore");
+
+      final CertificateManager destinationManager =
+           new CertificateManager(destination.getAbsolutePath(), "JKS", "destinationPassword");
+      destinationManager.addTrustedCertificate("ads-ca-1", ca.getCaCertificate());
+
+      final KeyStore keyStore = KeyStore.getInstance("JKS");
+      try (final FileInputStream in = new FileInputStream(destination))
+      {
+        keyStore.load(in, "destinationPassword".toCharArray());
+      }
+      assertTrue(keyStore.isCertificateEntry("ads-ca-1"));
+      assertEquals(keyStore.getCertificate("ads-ca-1"), ca.getCaCertificate());
+    }
+    finally
+    {
+      TestCaseUtils.deleteDirectory(tmpDir);
+    }
+  }
+
+
+
+  /**
+   * Tests that {@code importKeyEntry} names the cause when the private key is protected
+   * by a password of its own: the key managers of the server unlock keys with the store
+   * password only, and "Cannot recover key" says neither that nor what to do about it.
+   *
+   * @throws  Exception  If a problem occurs.
+   */
+  @Test
+  public void testImportKeyEntryReportsAKeyPasswordWhichDiffers()
+         throws Exception
+  {
+    final File tmpDir = TestCaseUtils.createTemporaryDirectory("importKeyEntryKeyPassword");
+    try
+    {
+      final CertificateFixture ca = new CertificateFixture("CN=Example CA,O=Example");
+      final File source = new File(tmpDir, "server.jks");
+      ca.addKeyEntry(source, "JKS", "sourcePassword", "keyPassword", "server-cert", "CN=host.example.com", true);
+
+      final File destination = new File(tmpDir, "ads-truststore");
+      final CertificateManager destinationManager =
+           new CertificateManager(destination.getAbsolutePath(), "JKS", "destinationPassword");
+      try
+      {
+        destinationManager.importKeyEntry("server-cert",
+             new CertificateManager(source.getAbsolutePath(), "JKS", "sourcePassword"), "server-cert");
+        fail("Expected a key store exception due to a key password which differs from the store password");
+      }
+      catch (KeyStoreException kse)
+      {
+        assertTrue(kse.getMessage().contains("server-cert"), kse.getMessage());
+        assertTrue(kse.getMessage().contains(source.getAbsolutePath()), kse.getMessage());
+        assertTrue(kse.getMessage().contains("password"), kse.getMessage());
+        assertFalse(kse.getMessage().contains("Cannot recover key"), kse.getMessage());
+      }
+      assertFalse(destination.exists(), "a destination key store was written for a key which could not be read");
+    }
+    finally
+    {
+      TestCaseUtils.deleteDirectory(tmpDir);
+    }
+  }
+
+
+
+  /**
+   * Tests that neither {@code importKeyEntry} nor {@code addTrustedCertificate} replaces
+   * an entry which is already there, as {@code generateSelfSignedCertificate} and
+   * {@code addCertificate} do not: the key store methods they wrap overwrite silently.
+   *
+   * @throws  Exception  If a problem occurs.
+   */
+  @Test
+  public void testImportKeyEntryAndAddTrustedCertificateRefuseAnAliasInUse()
+         throws Exception
+  {
+    final File tmpDir = TestCaseUtils.createTemporaryDirectory("aliasInUse");
+    try
+    {
+      final CertificateFixture ca = new CertificateFixture("CN=Example CA,O=Example");
+      final File source = new File(tmpDir, "server.p12");
+      ca.addKeyEntry(source, "PKCS12", "sourcePassword", "server-cert", "CN=host.example.com", true);
+      ca.addKeyEntry(source, "PKCS12", "sourcePassword", "other-cert", "CN=other.example.com", true);
+      final CertificateManager sourceManager =
+           new CertificateManager(source.getAbsolutePath(), "PKCS12", "sourcePassword");
+
+      final File destination = new File(tmpDir, "ads-truststore");
+      final CertificateManager destinationManager =
+           new CertificateManager(destination.getAbsolutePath(), "JKS", "destinationPassword");
+      destinationManager.importKeyEntry("server-cert", sourceManager, "server-cert");
+      destinationManager.addTrustedCertificate("ads-ca-1", ca.getCaCertificate());
+
+      try
+      {
+        destinationManager.importKeyEntry("server-cert", sourceManager, "other-cert");
+        fail("Expected a key store exception due to a key entry alias already in use");
+      }
+      catch (KeyStoreException kse)
+      {
+        assertTrue(kse.getMessage().contains("server-cert"), kse.getMessage());
+      }
+      try
+      {
+        destinationManager.addTrustedCertificate("ads-ca-1", sourceManager.getCertificate("other-cert"));
+        fail("Expected a key store exception due to a trusted certificate alias already in use");
+      }
+      catch (KeyStoreException kse)
+      {
+        assertTrue(kse.getMessage().contains("ads-ca-1"), kse.getMessage());
+      }
+      try
+      {
+        destinationManager.importKeyEntry("ads-ca-1", sourceManager, "other-cert");
+        fail("Expected a key store exception due to an alias held by a trusted certificate");
+      }
+      catch (KeyStoreException kse)
+      {
+        assertTrue(kse.getMessage().contains("ads-ca-1"), kse.getMessage());
+      }
+
+      final KeyStore keyStore = KeyStore.getInstance("JKS");
+      try (final FileInputStream in = new FileInputStream(destination))
+      {
+        keyStore.load(in, "destinationPassword".toCharArray());
+      }
+      assertEquals(keyStore.getCertificate("server-cert"), sourceManager.getCertificate("server-cert"),
+           "the key entry was replaced");
+      assertEquals(keyStore.getCertificate("ads-ca-1"), ca.getCaCertificate(), "the trusted certificate was replaced");
+      assertEquals(keyStore.size(), 2);
+    }
+    finally
+    {
+      TestCaseUtils.deleteDirectory(tmpDir);
+    }
+  }
+
+
+
+  /**
    * Exports a certificate to a temporary file.
    *
    * @throws  Exception  If a problem occurs.

--
Gitblit v1.10.0