From 1bda529e3cf685d5108832ecac7f433c85acb6fd Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 06 Aug 2026 11:31:31 +0000
Subject: [PATCH] Remove global digestLock serialization in digest password storage schemes (#667)

---
 opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java |  130 ++++++++++++++++++++++---------------------
 1 files changed, 67 insertions(+), 63 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java
index b3843bf..f16a88f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/SHA1PasswordStorageScheme.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2008 Sun Microsystems, Inc.
  * Portions Copyright 2013-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.extensions;
 
@@ -53,11 +54,13 @@
   private static final String CLASS_NAME =
        "org.opends.server.extensions.SHA1PasswordStorageScheme";
 
-  /** The message digest that will actually be used to generate the SHA-1 hashes. */
-  private MessageDigest messageDigest;
-
-  /** The lock used to provide threadsafe access to the message digest. */
-  private Object digestLock;
+  /**
+   * The message digests used to generate the SHA-1 hashes.
+   * MessageDigest is not thread-safe, so a per-thread instance is used
+   * instead of a shared instance guarded by a lock: hashing under a global
+   * lock serializes all concurrent bind password verifications.
+   */
+  private ThreadLocal<MessageDigest> messageDigest;
 
   /**
    * Creates a new instance of this password storage scheme.  Note that no
@@ -76,7 +79,8 @@
   {
     try
     {
-      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
+      // Fail fast at initialization time if the algorithm is unavailable.
+      MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
     }
     catch (Exception e)
     {
@@ -87,7 +91,16 @@
       throw new InitializationException(message, e);
     }
 
-    digestLock = new Object();
+    messageDigest = ThreadLocal.withInitial(() -> {
+      try
+      {
+        return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
+      }
+      catch (Exception e)
+      {
+        throw new IllegalStateException(e);
+      }
+    });
   }
 
   @Override
@@ -103,29 +116,26 @@
     byte[] digestBytes;
     byte[] plaintextBytes = null;
 
-    synchronized (digestLock)
+    try
     {
-      try
-      {
-        // TODO: Can we avoid this copy?
-        plaintextBytes = plaintext.toByteArray();
-        digestBytes = messageDigest.digest(plaintextBytes);
-      }
-      catch (Exception e)
-      {
-        logger.traceException(e);
+      // TODO: Can we avoid this copy?
+      plaintextBytes = plaintext.toByteArray();
+      digestBytes = messageDigest.get().digest(plaintextBytes);
+    }
+    catch (Exception e)
+    {
+      logger.traceException(e);
 
-        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
-            CLASS_NAME, getExceptionMessage(e));
-        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
-                                     message, e);
-      }
-      finally
+      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
+          CLASS_NAME, getExceptionMessage(e));
+      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
+                                   message, e);
+    }
+    finally
+    {
+      if (plaintextBytes != null)
       {
-        if (plaintextBytes != null)
-        {
-          Arrays.fill(plaintextBytes, (byte) 0);
-        }
+        Arrays.fill(plaintextBytes, (byte) 0);
       }
     }
 
@@ -145,28 +155,25 @@
     byte[] plaintextBytes = null;
     byte[] digestBytes;
 
-    synchronized (digestLock)
+    try
     {
-      try
-      {
-        plaintextBytes = plaintext.toByteArray();
-        digestBytes = messageDigest.digest(plaintextBytes);
-      }
-      catch (Exception e)
-      {
-        logger.traceException(e);
+      plaintextBytes = plaintext.toByteArray();
+      digestBytes = messageDigest.get().digest(plaintextBytes);
+    }
+    catch (Exception e)
+    {
+      logger.traceException(e);
 
-        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
-            CLASS_NAME, getExceptionMessage(e));
-        throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
-                                     message, e);
-      }
-      finally
+      LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
+          CLASS_NAME, getExceptionMessage(e));
+      throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(),
+                                   message, e);
+    }
+    finally
+    {
+      if (plaintextBytes != null)
       {
-        if (plaintextBytes != null)
-        {
-          Arrays.fill(plaintextBytes, (byte) 0);
-        }
+        Arrays.fill(plaintextBytes, (byte) 0);
       }
     }
 
@@ -183,26 +190,23 @@
     byte[] plaintextPasswordBytes = null;
     ByteString userPWDigestBytes;
 
-    synchronized (digestLock)
+    try
     {
-      try
-      {
-        plaintextPasswordBytes = plaintextPassword.toByteArray();
-        userPWDigestBytes =
-            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
-      }
-      catch (Exception e)
-      {
-        logger.traceException(e);
+      plaintextPasswordBytes = plaintextPassword.toByteArray();
+      userPWDigestBytes =
+          ByteString.wrap(messageDigest.get().digest(plaintextPasswordBytes));
+    }
+    catch (Exception e)
+    {
+      logger.traceException(e);
 
-        return false;
-      }
-      finally
+      return false;
+    }
+    finally
+    {
+      if (plaintextPasswordBytes != null)
       {
-        if (plaintextPasswordBytes != null)
-        {
-          Arrays.fill(plaintextPasswordBytes, (byte) 0);
-        }
+        Arrays.fill(plaintextPasswordBytes, (byte) 0);
       }
     }
 

--
Gitblit v1.10.0