Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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");

Check failure

Code scanning / CodeQL

Use of a potentially broken or risky cryptographic algorithm High

Cryptographic algorithm
MD5
may not be secure. Consider using a different algorithm.
Comment thread
vharseko marked this conversation as resolved.
Dismissed
}
catch (Exception e)
{
Expand All @@ -125,6 +128,17 @@
throw new InitializationException(message, e);
}

md5Digest = ThreadLocal.withInitial(() -> {
try
{
return MessageDigest.getInstance("MD5");

Check failure

Code scanning / CodeQL

Use of a potentially broken or risky cryptographic algorithm High

Cryptographic algorithm
MD5
may not be secure. Consider using a different algorithm.
Comment thread
vharseko marked this conversation as resolved.
Dismissed
}
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];
Expand Down Expand Up @@ -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);
}
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);
// 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);
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];
}
// 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);
// 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);
}
// Return an MD5 digest of the resulting array.
return md5Digest.digest(oPadAndHash);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -53,11 +54,13 @@
private static final String CLASS_NAME =
"org.opends.server.extensions.MD5PasswordStorageScheme";

/** The message digest that will actually be used to generate the MD5 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 MD5 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
Expand All @@ -76,7 +79,8 @@
{
try
{
messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
// Fail fast at initialization time if the algorithm is unavailable.
MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);

Check failure

Code scanning / CodeQL

Use of a potentially broken or risky cryptographic algorithm High

Cryptographic algorithm
MD5
may not be secure. Consider using a different algorithm.
Comment thread
vharseko marked this conversation as resolved.
Dismissed
}
catch (Exception e)
{
Expand All @@ -87,7 +91,16 @@
throw new InitializationException(message, e);
}

digestLock = new Object();
messageDigest = ThreadLocal.withInitial(() -> {
try
{
return MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);

Check failure

Code scanning / CodeQL

Use of a potentially broken or risky cryptographic algorithm High

Cryptographic algorithm
MD5
may not be secure. Consider using a different algorithm.
Comment thread
vharseko marked this conversation as resolved.
Dismissed
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
});
}

@Override
Expand All @@ -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);
}
}

Expand All @@ -144,29 +154,26 @@
byte[] plaintextBytes = null;
byte[] digestBytes;

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);
}
}

Expand All @@ -182,27 +189,24 @@
byte[] plaintextPasswordBytes = null;
ByteString userPWDigestBytes;

synchronized (digestLock)
try
{
try
{
// TODO: Can we avoid this copy?
plaintextPasswordBytes = plaintextPassword.toByteArray();
userPWDigestBytes =
ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
}
catch (Exception e)
{
logger.traceException(e);
// TODO: Can we avoid this copy?
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);
}
}

Expand Down
Loading
Loading