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/CRAMMD5SASLMechanismHandler.java | 90 +++++++++++++++++++++++++-------------------
1 files changed, 51 insertions(+), 39 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
index 8149ec5..400c3a0 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
@@ -13,6 +13,7 @@
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.extensions;
@@ -81,11 +82,13 @@
/** The identity mapper that will be used to map ID strings to user entries. */
private IdentityMapper<?> identityMapper;
- /** The message digest engine that will be used to create the MD5 digests. */
- private MessageDigest md5Digest;
-
- /** The lock that will be used to provide threadsafe access to the message digest. */
- private Object digestLock;
+ /**
+ * The message digest engines that will be used to create the MD5 digests.
+ * 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 CRAM-MD5 binds.
+ */
+ private ThreadLocal<MessageDigest> md5Digest;
/** The random number generator that we will use to create the server challenge. */
private SecureRandom randomGenerator;
@@ -109,12 +112,12 @@
currentConfig = configuration;
// Initialize the variables needed for the MD5 digest creation.
- digestLock = new Object();
randomGenerator = new SecureRandom();
try
{
- md5Digest = MessageDigest.getInstance("MD5");
+ // Fail fast at initialization time if the algorithm is unavailable.
+ MessageDigest.getInstance("MD5");
}
catch (Exception e)
{
@@ -125,6 +128,17 @@
throw new InitializationException(message, e);
}
+ md5Digest = ThreadLocal.withInitial(() -> {
+ try
+ {
+ return MessageDigest.getInstance("MD5");
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException(e);
+ }
+ });
+
// Create and fill the iPad and oPad arrays.
iPad = new byte[HMAC_MD5_BLOCK_LENGTH];
oPad = new byte[HMAC_MD5_BLOCK_LENGTH];
@@ -427,40 +441,38 @@
byte[] p = password.toByteArray();
byte[] c = challenge.toByteArray();
- // Grab a lock to protect the MD5 digest generation.
- synchronized (digestLock)
+ MessageDigest md5Digest = this.md5Digest.get();
+
+ // If the password is longer than the HMAC-MD5 block length, then use an
+ // MD5 digest of the password rather than the password itself.
+ if (p.length > HMAC_MD5_BLOCK_LENGTH)
{
- // If the password is longer than the HMAC-MD5 block length, then use an
- // MD5 digest of the password rather than the password itself.
- if (p.length > HMAC_MD5_BLOCK_LENGTH)
- {
- p = md5Digest.digest(p);
- }
-
- // Create byte arrays with data needed for the hash generation.
- byte[] iPadAndData = new byte[HMAC_MD5_BLOCK_LENGTH + c.length];
- System.arraycopy(iPad, 0, iPadAndData, 0, HMAC_MD5_BLOCK_LENGTH);
- System.arraycopy(c, 0, iPadAndData, HMAC_MD5_BLOCK_LENGTH, c.length);
-
- byte[] oPadAndHash = new byte[HMAC_MD5_BLOCK_LENGTH + MD5_DIGEST_LENGTH];
- System.arraycopy(oPad, 0, oPadAndHash, 0, HMAC_MD5_BLOCK_LENGTH);
-
- // Iterate through the bytes in the key and XOR them with the iPad and
- // oPad as appropriate.
- for (int i=0; i < p.length; i++)
- {
- iPadAndData[i] ^= p[i];
- oPadAndHash[i] ^= p[i];
- }
-
- // Copy an MD5 digest of the iPad-XORed key and the data into the array to
- // be hashed.
- System.arraycopy(md5Digest.digest(iPadAndData), 0, oPadAndHash,
- HMAC_MD5_BLOCK_LENGTH, MD5_DIGEST_LENGTH);
-
- // Return an MD5 digest of the resulting array.
- return md5Digest.digest(oPadAndHash);
+ p = md5Digest.digest(p);
}
+
+ // Create byte arrays with data needed for the hash generation.
+ byte[] iPadAndData = new byte[HMAC_MD5_BLOCK_LENGTH + c.length];
+ System.arraycopy(iPad, 0, iPadAndData, 0, HMAC_MD5_BLOCK_LENGTH);
+ System.arraycopy(c, 0, iPadAndData, HMAC_MD5_BLOCK_LENGTH, c.length);
+
+ byte[] oPadAndHash = new byte[HMAC_MD5_BLOCK_LENGTH + MD5_DIGEST_LENGTH];
+ System.arraycopy(oPad, 0, oPadAndHash, 0, HMAC_MD5_BLOCK_LENGTH);
+
+ // Iterate through the bytes in the key and XOR them with the iPad and
+ // oPad as appropriate.
+ for (int i=0; i < p.length; i++)
+ {
+ iPadAndData[i] ^= p[i];
+ oPadAndHash[i] ^= p[i];
+ }
+
+ // Copy an MD5 digest of the iPad-XORed key and the data into the array to
+ // be hashed.
+ System.arraycopy(md5Digest.digest(iPadAndData), 0, oPadAndHash,
+ HMAC_MD5_BLOCK_LENGTH, MD5_DIGEST_LENGTH);
+
+ // Return an MD5 digest of the resulting array.
+ return md5Digest.digest(oPadAndHash);
}
@Override
--
Gitblit v1.10.0