From f93487455cc1b5d7062ea6577d99dea25bfd81b3 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 14 Oct 2026 07:27:37 +0000
Subject: [PATCH] [#1188] Serialize and replace atomically the writes of the ads-truststore, and report a certificate it cannot take or drop (#1189)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java |  156 +++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 105 insertions(+), 51 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java
index a1d58fd..07b410d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/TrustStoreBackend.java
@@ -118,6 +118,15 @@
 
   /** The certificate manager for the trust store. */
   private CertificateManager certificateManager;
+  /**
+   * Held over every access to the trust store file and to the certificate manager. A change
+   * reads the file, changes that copy and writes the whole file back over the old one, and the
+   * certificate manager keeps the copy it read last. Replication replays the instance keys of
+   * cn=admin data on parallel threads, each of which changes the file here, and an SSL context
+   * is built from the file for every replication connection: unserialized, one of two changes
+   * is lost, and a reader finds the file half written (#1188).
+   */
+  private final Object trustStoreLock = new Object();
   /** The server context. */
   private ServerContext serverContext;
 
@@ -183,10 +192,13 @@
 
     trustStorePIN = getTrustStorePIN(configuration, true);
     final String keyStorePath = getFileForPath(trustStoreFile).getPath();
-    certificateManager = new CertificateManager(keyStorePath, trustStoreType, trustStorePIN);
+    synchronized (trustStoreLock)
+    {
+      certificateManager = new CertificateManager(keyStorePath, trustStoreType, trustStorePIN);
 
-    // Generate a self-signed certificate, if there is none.
-    generateInstanceCertificateIfAbsent();
+      // Generate a self-signed certificate, if there is none.
+      generateInstanceCertificateIfAbsent();
+    }
 
     // Construct the trust store base entry.
     LinkedHashMap<ObjectClass,String> objectClasses = new LinkedHashMap<>(2);
@@ -327,7 +339,11 @@
 
     try
     {
-      String[] aliases = certificateManager.getCertificateAliases();
+      String[] aliases;
+      synchronized (trustStoreLock)
+      {
+        aliases = certificateManager.getCertificateAliases();
+      }
       if (aliases != null)
       {
         numEntries += aliases.length;
@@ -409,7 +425,11 @@
     ByteString certValue;
     try
     {
-      Certificate cert = certificateManager.getCertificate(certAlias);
+      Certificate cert;
+      synchronized (trustStoreLock)
+      {
+        cert = certificateManager.getCertificate(certAlias);
+      }
       if (cert == null)
       {
         LocalizableMessage message = ERR_TRUSTSTORE_CERTIFICATE_NOT_FOUND.get(entryDN, certAlias);
@@ -468,7 +488,10 @@
 
     if (parentDN.equals(getBaseDN()))
     {
-      addCertificate(entry);
+      synchronized (trustStoreLock)
+      {
+        addCertificate(entry);
+      }
     }
     else
     {
@@ -494,7 +517,10 @@
       throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
     }
 
-    deleteCertificate(entryDN);
+    synchronized (trustStoreLock)
+    {
+      deleteCertificate(entryDN);
+    }
   }
 
   @Override
@@ -538,7 +564,10 @@
       String[] aliases = null;
       try
       {
-        aliases = certificateManager.getCertificateAliases();
+        synchronized (trustStoreLock)
+        {
+          aliases = certificateManager.getCertificateAliases();
+        }
       }
       catch (KeyStoreException e)
       {
@@ -772,12 +801,15 @@
 
     if (ccr.getResultCode() == ResultCode.SUCCESS)
     {
-      trustStoreFile = newTrustStoreFile;
-      trustStoreType = newTrustStoreType;
-      trustStorePIN  = newPIN;
-      configuration  = cfg;
-      final String keyStorePath = getFileForPath(trustStoreFile).getPath();
-      certificateManager = new CertificateManager(keyStorePath, trustStoreType, trustStorePIN);
+      synchronized (trustStoreLock)
+      {
+        trustStoreFile = newTrustStoreFile;
+        trustStoreType = newTrustStoreType;
+        trustStorePIN  = newPIN;
+        configuration  = cfg;
+        final String keyStorePath = getFileForPath(trustStoreFile).getPath();
+        certificateManager = new CertificateManager(keyStorePath, trustStoreType, trustStorePIN);
+      }
     }
 
     return ccr;
@@ -802,24 +834,28 @@
   public KeyManager[] getKeyManagers()
          throws DirectoryException
   {
-    final KeyStore keyStore = loadKeyStore();
-
-    try
+    // the keys are opened with the PIN of the file they were read from, not that of a new one
+    synchronized (trustStoreLock)
     {
-      String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
-      KeyManagerFactory keyManagerFactory =
-           KeyManagerFactory.getInstance(keyManagerAlgorithm);
-      keyManagerFactory.init(keyStore, trustStorePIN);
-      return keyManagerFactory.getKeyManagers();
-    }
-    catch (Exception e)
-    {
-      logger.traceException(e);
+      final KeyStore keyStore = loadKeyStore();
 
-      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_CREATE_FACTORY.get(
-          trustStoreFile, getExceptionMessage(e));
-      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
-                                   message, e);
+      try
+      {
+        String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
+        KeyManagerFactory keyManagerFactory =
+             KeyManagerFactory.getInstance(keyManagerAlgorithm);
+        keyManagerFactory.init(keyStore, trustStorePIN);
+        return keyManagerFactory.getKeyManagers();
+      }
+      catch (Exception e)
+      {
+        logger.traceException(e);
+
+        LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_CREATE_FACTORY.get(
+            trustStoreFile, getExceptionMessage(e));
+        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
+                                     message, e);
+      }
     }
   }
 
@@ -864,16 +900,19 @@
 
   private KeyStore loadKeyStore() throws DirectoryException
   {
-    try (FileInputStream inputStream = new FileInputStream(getFileForPath(trustStoreFile)))
+    synchronized (trustStoreLock)
     {
-      final KeyStore keyStore = KeyStore.getInstance(trustStoreType);
-      keyStore.load(inputStream, trustStorePIN);
-      return keyStore;
-    }
-    catch (Exception e)
-    {
-      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, getExceptionMessage(e));
-      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(), message, e);
+      try (FileInputStream inputStream = new FileInputStream(getFileForPath(trustStoreFile)))
+      {
+        final KeyStore keyStore = KeyStore.getInstance(trustStoreType);
+        keyStore.load(inputStream, trustStorePIN);
+        return keyStore;
+      }
+      catch (Exception e)
+      {
+        LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, getExceptionMessage(e));
+        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(), message, e);
+      }
     }
   }
 
@@ -925,20 +964,24 @@
   public Key getKey(String alias)
          throws DirectoryException
   {
-    KeyStore trustStore = loadKeyStore();
-
-    try
+    // the key is opened with the PIN of the file it was read from, not that of a new one
+    synchronized (trustStoreLock)
     {
-      return trustStore.getKey(alias, trustStorePIN);
-    }
-    catch (Exception e)
-    {
-      logger.traceException(e);
+      KeyStore trustStore = loadKeyStore();
 
-      LocalizableMessage message = ERR_TRUSTSTORE_ERROR_READING_KEY.get(
-           alias, trustStoreFile, getExceptionMessage(e));
-      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
-                                   message, e);
+      try
+      {
+        return trustStore.getKey(alias, trustStorePIN);
+      }
+      catch (Exception e)
+      {
+        logger.traceException(e);
+
+        LocalizableMessage message = ERR_TRUSTSTORE_ERROR_READING_KEY.get(
+             alias, trustStoreFile, getExceptionMessage(e));
+        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
+                                     message, e);
+      }
     }
   }
 
@@ -1047,6 +1090,12 @@
         }
       }
     }
+    catch (DirectoryException e)
+    {
+      // a refusal made above keeps its result code: an alias in use is an entry which exists,
+      // not a failure of the server
+      throw e;
+    }
     catch (Exception e)
     {
       LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_ADD_CERT.get(
@@ -1079,6 +1128,11 @@
 
       certificateManager.removeCertificate(certAlias);
     }
+    catch (DirectoryException e)
+    {
+      // an alias not in use is an entry which does not exist, not a failure of the server
+      throw e;
+    }
     catch (Exception e)
     {
       LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_DELETE_CERT.get(

--
Gitblit v1.10.0