From e333af0c8fbb8d69d79f420de01ce39dcade5930 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 24 Sep 2026 08:06:21 +0000
Subject: [PATCH] Keep PKCS5S2 usable on a FIPS-restricted JCE, and name the key wrapping property when the runtime has no RSA-OAEP (#1058)
---
opendj-doc-generated-ref/src/main/asciidoc/install-guide/chap-install.adoc | 56 +++++
opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java | 61 +++++
opendj-server-legacy/src/main/java/org/opends/server/extensions/AbstractPBKDF2PasswordStorageScheme.java | 3
opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java | 10
opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java | 26 ++
opendj-doc-generated-ref/src/main/asciidoc/admin-guide/chap-import-export.adoc | 8
opendj-server-legacy/src/messages/org/opends/messages/core.properties | 4
opendj-server-legacy/src/test/java/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java | 30 ++
opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureDS.java | 130 +++++++++--
opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java | 58 +++++
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java | 8
opendj-server-legacy/src/test/java/org/opends/server/tools/ConfigureDSTestCase.java | 167 +++++++++++++++
opendj-server-legacy/src/test/java/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java | 77 +++++++
opendj-server-legacy/src/messages/org/opends/messages/tool.properties | 5
opendj-server-legacy/src/main/java/org/opends/server/extensions/ExtensionsConstants.java | 6
15 files changed, 603 insertions(+), 46 deletions(-)
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/admin-guide/chap-import-export.adoc b/opendj-doc-generated-ref/src/main/asciidoc/admin-guide/chap-import-export.adoc
index 9a3cb46..b65a114 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/admin-guide/chap-import-export.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/admin-guide/chap-import-export.adoc
@@ -12,7 +12,7 @@
information: "Portions copyright [year] [name of copyright owner]".
Copyright 2017 ForgeRock AS.
- Portions Copyright 2025 3A Systems LLC.
+ Portions Copyright 2025-2026 3A Systems LLC.
////
:figure-caption!:
@@ -228,9 +228,9 @@
initials: AAA
$ ldifmodify \
- --sourceLDIF generated.ldif \
- --changesLDIF changes.ldif \
- --targetLDIF new.ldif
+ --outputLDIF new.ldif \
+ generated.ldif \
+ changes.ldif
----
Notice that the resulting new LDIF file is likely to be about the same size as the source LDIF file.
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/install-guide/chap-install.adoc b/opendj-doc-generated-ref/src/main/asciidoc/install-guide/chap-install.adoc
index 7e0bee3..106f829 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/install-guide/chap-install.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/install-guide/chap-install.adoc
@@ -789,6 +789,60 @@
====
+[#install-fips]
+.To Install OpenDJ Directory Server on a FIPS 140 Java Runtime
+====
+OpenDJ ships the Bouncy Castle FIPS provider. The `setup` command and the server register it themselves when the server key store is a BCFKS key store (`--useBcfksKeystore`), and the server registers it at start whenever the `org.openidentityplatform.opendj.fips.register` Java system property is `true`, for example through the `OPENDJ_JAVA_ARGS` environment variable. With this provider in its default mode the default configuration works as it is. The approved-only mode of the provider (the `org.bouncycastle.fips.approved_only` Java system property), which OpenDJ does not turn on, is not covered here.
+
+The crypto manager wraps the secret keys it shares with the other servers of a replication topology with each server's public key. The transformation it uses, the `key-wrapping-transformation` property of the crypto manager, is `RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING` by default: RSA-OAEP, the key transport scheme of NIST SP 800-56B, which Bouncy Castle FIPS provides. A Java runtime whose cryptography comes from a `SunPKCS11` provider alone, such as `SunPKCS11-NSS-FIPS` on a Linux system in FIPS mode, provides no RSA-OAEP at all: its only RSA cipher is `RSA/ECB/PKCS1Padding`, which NIST SP 800-131A Rev. 2 disallows for key transport. On such a runtime the server refuses to start with the default transformation, `setup` says so, and the choice of another one is yours to make: OpenDJ does not make it for you. If you make it, keep in mind that every server of a replication topology must use the same transformation, since each server unwraps what the others wrapped.
+
+. Install the server without starting it:
++
+
+[source, console]
+----
+$ ./setup --cli --doNotStart \
+ --hostname opendj.example.com \
+ --ldapPort 1389 \
+ --adminConnectorPort 4444 \
+ --rootUserDN "cn=Directory Manager" \
+ --rootUserPassword password \
+ --baseDN dc=example,dc=com \
+ --acceptLicense \
+ --no-prompt
+----
+
+. Set the transformation in the server configuration file, which the `dsconfig` command cannot change while the server is stopped:
++
+
+[source, console]
+----
+$ cat changes.ldif
+dn: cn=Crypto Manager,cn=config
+changetype: modify
+replace: ds-cfg-key-wrapping-transformation
+ds-cfg-key-wrapping-transformation: RSA/ECB/PKCS1Padding
+
+$ ldifmodify \
+ --outputLDIF /path/to/opendj/config/config.ldif.new \
+ /path/to/opendj/config/config.ldif \
+ changes.ldif
+
+$ mv /path/to/opendj/config/config.ldif.new /path/to/opendj/config/config.ldif
+----
+
+. Start the server:
++
+
+[source, console]
+----
+$ start-ds
+----
++
+Once the server runs, the `dsconfig set-crypto-manager-prop` command changes the property, and refuses a transformation the runtime does not support.
+
+====
+
[#pdb-to-je]
.To Move Data from a PDB Backend to a JE Backend
====
@@ -933,7 +987,7 @@
ds-cfg-java-class: org.opends.server.backends.jeb.JEBackend
EOF
-./bin/ldifmodify --targetLDIF "$LOC"/config/config.ldif.$$ --sourceLDIF "$LOC"/config/config.ldif --changesLDIF /tmp/changes_$$
+./bin/ldifmodify --outputLDIF "$LOC"/config/config.ldif.$$ "$LOC"/config/config.ldif /tmp/changes_$$
if test $? -ne 0
then
echo "Modifications failed. Restoring the original configuration"
diff --git a/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java b/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java
index a512941..09ab444 100644
--- a/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java
@@ -130,6 +130,7 @@
import org.opends.server.backends.task.TaskState;
import org.opends.server.tools.BackendTypeHelper;
import org.opends.server.tools.BackendTypeHelper.BackendTypeUIAdapter;
+import org.opends.server.tools.ConfigureDS;
import org.opends.server.types.HostPort;
import org.opends.server.util.CertificateManager;
import org.opends.server.util.CollectionUtils;
@@ -1363,6 +1364,13 @@
};
invokeLongOperation(thread);
notifyListeners(getFormattedDoneWithLineBreak());
+ // Given here rather than by ConfigureDS, whose output the listeners do not see while it runs.
+ final LocalizableMessage keyWrappingWarning = ConfigureDS.unsupportedKeyWrappingTransformationWarning();
+ if (keyWrappingWarning != null)
+ {
+ notifyListeners(getFormattedWarning(keyWrappingWarning));
+ notifyListeners(getLineBreak());
+ }
checkAbort();
configureCertificate(sec);
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
index c15db1c..4ab3d15 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
@@ -392,6 +392,9 @@
requestedKeyWrappingTransformation));
isAcceptable = false;
}
+ else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformation, unacceptableReasons)) {
+ isAcceptable = false;
+ }
else {
try {
/* Note that the TrustStoreBackend not available at initial,
@@ -430,6 +433,29 @@
return isAcceptable;
}
+ /**
+ * Checks that this Java runtime provides the key wrapping transformation. Only a refusal here
+ * names the key-wrapping-transformation property: the wrap which follows it also needs an MD5
+ * digest and a 1024-bit RSA key, and changing the property does not help when one of those is
+ * what the runtime refuses.
+ */
+ private static boolean isKeyWrappingTransformationSupported(
+ final String transformation, final List<LocalizableMessage> unacceptableReasons)
+ {
+ try
+ {
+ Cipher.getInstance(transformation);
+ return true;
+ }
+ catch (GeneralSecurityException ex)
+ {
+ logger.traceException(ex);
+ unacceptableReasons.add(
+ ERR_CRYPTOMGR_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.get(transformation, getExceptionMessage(ex)));
+ return false;
+ }
+ }
+
@Override
public ConfigChangeResult applyConfigurationChange(CryptoManagerCfg cfg)
{
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/AbstractPBKDF2PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/AbstractPBKDF2PasswordStorageScheme.java
index c92c267..0eaa4ad 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/AbstractPBKDF2PasswordStorageScheme.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/AbstractPBKDF2PasswordStorageScheme.java
@@ -82,7 +82,8 @@
}
catch (NoSuchAlgorithmException e)
{
- throw new InitializationException(null);
+ throw new InitializationException(
+ ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(getMessageDigestAlgorithm(), e), e);
}
this.config = configuration;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/ExtensionsConstants.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/ExtensionsConstants.java
index 43b7092..81ad8a0 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/ExtensionsConstants.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/ExtensionsConstants.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;
@@ -153,11 +154,6 @@
public static final String MESSAGE_DIGEST_ALGORITHM_PBKDF2_HMAC_SHA512 =
"PBKDF2WithHmacSHA512";
- /**
- * The name of the pseudo-random number generator using SHA-1.
- */
- public static final String SECURE_PRNG_SHA1 = "SHA1PRNG";
-
/**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
index 8b881cc..dbcd4fe 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
@@ -13,6 +13,7 @@
*
* Copyright 2014-2016 ForgeRock AS.
* Portions Copyright 2014 Emidio Stani & Andrea Stani
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.extensions;
@@ -85,13 +86,15 @@
{
try
{
- random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
+ // The provider's default random source: a FIPS-restricted JCE registers no SHA1PRNG.
+ random = new SecureRandom();
// Just try to verify if the algorithm is supported
SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
}
catch (NoSuchAlgorithmException e)
{
- throw new InitializationException(null);
+ throw new InitializationException(
+ ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(MESSAGE_DIGEST_ALGORITHM_PBKDF2, e), e);
}
}
@@ -246,8 +249,7 @@
{
try
{
- final SecureRandom random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
- return encodeWithRandomSalt(plaintext, saltBytes, random);
+ return encodeWithRandomSalt(plaintext, saltBytes, new SecureRandom());
}
catch (DirectoryException e)
{
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureDS.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureDS.java
index 02efe2c..8df0c77 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureDS.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureDS.java
@@ -1208,45 +1208,36 @@
*/
private void updateCryptoCipher() throws ConfigureDSException
{
- final CryptoManagerCfgDefn cryptoManager = CryptoManagerCfgDefn.getInstance();
- final StringPropertyDefinition prop = cryptoManager.getKeyWrappingTransformationPropertyDefinition();
- String defaultCipher = null;
-
- final DefaultBehaviorProvider<?> p = prop.getDefaultBehaviorProvider();
- if (p instanceof DefinedDefaultBehaviorProvider)
- {
- final Collection<?> defaultValues = ((DefinedDefaultBehaviorProvider<?>) p).getDefaultValues();
- if (!defaultValues.isEmpty())
- {
- defaultCipher = defaultValues.iterator().next().toString();
- }
- }
-
+ final String defaultCipher = defaultKeyWrappingTransformation();
if (defaultCipher != null)
{
- // Check that the default cipher is supported by the JVM.
+ final String cipher;
try
{
- Cipher.getInstance(defaultCipher);
+ cipher = supportedKeyWrappingTransformation(defaultCipher);
}
catch (final GeneralSecurityException ex)
{
- // The cipher is not supported: try to find an alternative one.
- final String alternativeCipher = getAlternativeCipher();
- if (alternativeCipher != null)
+ // The default stays, and the server will refuse to start with it: there is no secure
+ // transformation to fall back to (#776), so the administrator has to choose one. Under
+ // setup this stream reaches the setup log only, and the installer gives the warning
+ // itself (see unsupportedKeyWrappingTransformationWarning()).
+ printWrappedText(err, unsupportedKeyWrappingTransformationWarning(defaultCipher, ex));
+ return;
+ }
+ if (!cipher.equals(defaultCipher))
+ {
+ try
{
- try
- {
- updateConfigEntryWithAttribute(
- DN_CRYPTO_MANAGER,
- ATTR_CRYPTO_CIPHER_KEY_WRAPPING_TRANSFORMATION,
- CoreSchema.getDirectoryStringSyntax(),
- alternativeCipher);
- }
- catch (final Exception e)
- {
- throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CRYPTO_MANAGER.get(e));
- }
+ updateConfigEntryWithAttribute(
+ DN_CRYPTO_MANAGER,
+ ATTR_CRYPTO_CIPHER_KEY_WRAPPING_TRANSFORMATION,
+ CoreSchema.getDirectoryStringSyntax(),
+ cipher);
+ }
+ catch (final Exception e)
+ {
+ throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CRYPTO_MANAGER.get(e));
}
}
}
@@ -1327,6 +1318,83 @@
}
/**
+ * Returns the warning to give when this Java runtime supports neither the default key wrapping
+ * transformation of the crypto manager nor an alternative to it: the server will then refuse to
+ * start until the administrator sets one. The installer calls this itself, since what
+ * {@code configMain} writes while it runs under setup reaches the setup log only.
+ *
+ * @return The warning, or {@code null} when the runtime supports a transformation.
+ */
+ public static LocalizableMessage unsupportedKeyWrappingTransformationWarning()
+ {
+ final String defaultCipher = defaultKeyWrappingTransformation();
+ if (defaultCipher == null)
+ {
+ return null;
+ }
+ try
+ {
+ supportedKeyWrappingTransformation(defaultCipher);
+ return null;
+ }
+ catch (final GeneralSecurityException ex)
+ {
+ return unsupportedKeyWrappingTransformationWarning(defaultCipher, ex);
+ }
+ }
+
+ private static LocalizableMessage unsupportedKeyWrappingTransformationWarning(
+ final String defaultCipher, final GeneralSecurityException ex)
+ {
+ return WARN_CONFIGDS_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.get(defaultCipher, ex.getMessage());
+ }
+
+ /** Returns the default key wrapping transformation of the crypto manager, or {@code null}. */
+ private static String defaultKeyWrappingTransformation()
+ {
+ final StringPropertyDefinition prop =
+ CryptoManagerCfgDefn.getInstance().getKeyWrappingTransformationPropertyDefinition();
+ final DefaultBehaviorProvider<?> p = prop.getDefaultBehaviorProvider();
+ if (p instanceof DefinedDefaultBehaviorProvider)
+ {
+ final Collection<?> defaultValues = ((DefinedDefaultBehaviorProvider<?>) p).getDefaultValues();
+ if (!defaultValues.isEmpty())
+ {
+ return defaultValues.iterator().next().toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the key wrapping transformation this Java runtime supports: the default one when it
+ * does, otherwise the OAEP alternative of {@link #getAlternativeCipher()}.
+ *
+ * @param defaultCipher
+ * The default key wrapping transformation of the crypto manager.
+ * @return The transformation to configure.
+ * @throws GeneralSecurityException
+ * If the runtime supports neither, with the reason the default one is not.
+ */
+ static String supportedKeyWrappingTransformation(final String defaultCipher) throws GeneralSecurityException
+ {
+ try
+ {
+ Cipher.getInstance(defaultCipher);
+ return defaultCipher;
+ }
+ catch (final GeneralSecurityException ex)
+ {
+ final String alternativeCipher = getAlternativeCipher();
+ if (alternativeCipher == null)
+ {
+ throw ex;
+ }
+ return alternativeCipher;
+ }
+ }
+
+ /**
* Returns a cipher that is supported by the JVM we are running at.
* Returns <CODE>null</CODE> if no alternative cipher could be found.
* @return a cipher that is supported by the JVM we are running at.
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/core.properties b/opendj-server-legacy/src/messages/org/opends/messages/core.properties
index ddbd5db..72759e0 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/core.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/core.properties
@@ -1376,3 +1376,7 @@
up in the trust store used for server to server communication: %s. A nickname that \
trust store does not hold is only reported once the server has been restarted with it, \
so check the nicknames against the trust store before restarting the server
+ERR_CRYPTOMGR_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED_766=This Java runtime \
+ does not provide the key wrapping transformation %s: %s. The \
+ key-wrapping-transformation property of the crypto manager must name a \
+ transformation which the runtime supports
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/tool.properties b/opendj-server-legacy/src/messages/org/opends/messages/tool.properties
index 930df01..34d2f5e 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/tool.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/tool.properties
@@ -2634,6 +2634,11 @@
SUPPLEMENT_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_INDEX_STATUS_20016=\
<xinclude:include href="variablelist-backendstat-index-status.xml" />
INFO_DESCRIPTION_DEFAULT_ADD_20017=Legacy argument for ForgeRock OpenDJ compatibility.
+WARN_CONFIGDS_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED_20018=This Java runtime \
+ supports neither the default key wrapping transformation %s nor an alternative \
+ to it: %s. The server will not start until the key-wrapping-transformation \
+ property of the crypto manager names a transformation which the runtime \
+ supports; set it in config/config.ldif before starting the server
INFO_LDAP_CONN_PROMPT_SECURITY_LDAP=LDAP
INFO_LDAP_CONN_PROMPT_SECURITY_USE_SSL=LDAP with SSL
INFO_LDAP_CONN_PROMPT_SECURITY_USE_START_TLS=LDAP with StartTLS
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
index 0f5e74b..3d41854 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
@@ -57,6 +57,8 @@
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.file.Paths;
+import java.security.Provider;
+import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -68,6 +70,7 @@
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.concurrent.Callable;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
@@ -321,6 +324,64 @@
}
}
+ /**
+ * Runs {@code action} while no installed JCE provider offers the given service, the way a
+ * FIPS-restricted JVM lacks it, and puts the withdrawn providers back where they were
+ * afterwards. The {@code standIns} are installed ahead of the remaining providers for the
+ * duration, for whatever the action still needs that only the withdrawn providers offered.
+ *
+ * @param type
+ * The JCE service type, e.g. {@code SecureRandom}.
+ * @param algorithm
+ * The algorithm to withdraw, e.g. {@code SHA1PRNG}.
+ * @param action
+ * What to run without the service.
+ * @param standIns
+ * Providers to install first while the service is withdrawn.
+ * @throws Exception
+ * If the action fails, or if the service could not be withdrawn.
+ */
+ public static void withoutJceService(final String type, final String algorithm,
+ final Callable<Void> action, final Provider... standIns) throws Exception
+ {
+ final String service = type + "." + algorithm;
+ final List<Provider> installed = Arrays.asList(Security.getProviders());
+ final Provider[] offering = Security.getProviders(service);
+ assertNotNull(offering, "no installed provider offers " + service + ": nothing to withdraw");
+ for (Provider provider : offering)
+ {
+ Security.removeProvider(provider.getName());
+ }
+ final List<Provider> addedStandIns = new ArrayList<>();
+ for (int i = 0; i < standIns.length; i++)
+ {
+ if (Security.insertProviderAt(standIns[i], i + 1) != -1)
+ {
+ addedStandIns.add(standIns[i]);
+ }
+ }
+ try
+ {
+ assertNull(Security.getProviders(service), service + " is still offered: the fixture does not withdraw it");
+ action.call();
+ }
+ finally
+ {
+ for (Provider standIn : addedStandIns)
+ {
+ Security.removeProvider(standIn.getName());
+ }
+ // Ascending original positions, so that the list comes back in its original order.
+ for (Provider provider : installed)
+ {
+ if (Arrays.asList(offering).contains(provider))
+ {
+ Security.insertProviderAt(provider, installed.indexOf(provider) + 1);
+ }
+ }
+ }
+ }
+
public static void startServer() throws Exception
{
System.setProperty(PROPERTY_RUNNING_UNIT_TESTS, "true");
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java
index 37537a7..0642f4c 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java
@@ -39,7 +39,10 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
+import java.security.Provider;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.TreeSet;
import java.util.UUID;
@@ -298,6 +301,61 @@
}
/**
+ A key wrapping transformation this Java runtime cannot provide is refused, at start (where
+ the refusal is what keeps the server from starting) as on a change, and the refusal has to
+ name the property to set, not only the cipher which failed: on a FIPS-restricted runtime
+ without RSA-OAEP that is all the administrator has to go on.
+ */
+ @Test
+ public void testUnsupportedKeyWrappingTransformationIsRefusedNamingTheProperty() throws Exception
+ {
+ final CryptoManagerImpl cm = DirectoryServer.getCryptoManager();
+ final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager();
+ final String unsupported = "RSA/ECB/NoSuchPadding";
+ final List<LocalizableMessage> why = new ArrayList<>();
+
+ final boolean acceptable =
+ cm.isConfigurationChangeAcceptable(withProperty(cfg, "getKeyWrappingTransformation", unsupported), why);
+
+ assertThat(acceptable).isFalse();
+ assertThat(why).hasSize(1);
+ assertThat(why.get(0).ordinal()).isEqualTo(ERR_CRYPTOMGR_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.ordinal());
+ assertThat(why.get(0).toString()).contains(unsupported).contains("key-wrapping-transformation");
+ }
+
+ /**
+ A transformation the runtime provides, refused because of the rest of the check (here the
+ MD5 digest of the instance key identifier), is not reported as a matter of the property:
+ changing key-wrapping-transformation would not help.
+ */
+ @Test
+ public void testKeyWrappingRefusalForAnotherCauseDoesNotNameTheProperty() throws Exception
+ {
+ final CryptoManagerImpl cm = DirectoryServer.getCryptoManager();
+ final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager();
+ final String supported = "RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING";
+ assertThat(supported).isNotEqualTo(cfg.getKeyWrappingTransformation());
+ final List<LocalizableMessage> why = new ArrayList<>();
+
+ // Withdrawing MD5 withdraws the SUN provider, whose SHA-1 digest the OAEP cipher still needs.
+ final Provider sha1Only = new Provider("Sha1OnlyDigest", "1.0", "SHA-1 digest only") {};
+ sha1Only.put("MessageDigest.SHA-1", "sun.security.provider.SHA");
+ sha1Only.put("Alg.Alias.MessageDigest.SHA1", "SHA-1");
+
+ withoutJceService("MessageDigest", "MD5", () ->
+ {
+ assertThat(cm.isConfigurationChangeAcceptable(withProperty(cfg, "getKeyWrappingTransformation", supported), why))
+ .isFalse();
+ return null;
+ }, sha1Only);
+
+ assertThat(why).hasSize(1);
+ final String reason = why.get(0).toString();
+ assertThat(why.get(0).ordinal()).as(reason).isEqualTo(ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.ordinal());
+ assertThat(reason).contains("MD5").doesNotContain("key-wrapping-transformation");
+ }
+
+ /**
Returns the crypto manager configuration as it stands, with the ssl-cert-nickname
property answering the provided nicknames, so that a change to that property is applied
without the configuration of the running server being modified.
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java
index 65641de..9cc9455 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java
@@ -12,13 +12,19 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.extensions;
import org.forgerock.opendj.server.config.meta.PBKDF2PasswordStorageSchemeCfgDefn;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.opends.server.TestCaseUtils.withoutJceService;
+import static org.testng.Assert.*;
/**
* A set of test cases for the PBKDF2 password storage scheme.
@@ -70,4 +76,28 @@
{
return PBKDF2PasswordStorageScheme.encodeOffline(plaintextBytes);
}
+
+ /**
+ * When the derivation is unavailable, the failure has to name the algorithm: a message-less
+ * InitializationException leaves the administrator with a server which does not start and
+ * no word on why.
+ */
+ @Test
+ public void testInitializationFailureNamesTheMissingAlgorithm() throws Exception
+ {
+ withoutJceService("SecretKeyFactory", "PBKDF2WithHmacSHA1", () ->
+ {
+ try
+ {
+ getScheme();
+ fail("initialization succeeded without PBKDF2WithHmacSHA1");
+ }
+ catch (InitializationException e)
+ {
+ assertNotNull(e.getMessageObject(), "the failure carries no message");
+ assertTrue(e.getMessage().contains("for the PBKDF2WithHmacSHA1 algorithm"), e.getMessage());
+ }
+ return null;
+ });
+ }
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java
index b5cb07d..ec0d4b7 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java
@@ -12,15 +12,25 @@
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.extensions;
+import java.security.SecureRandom;
+import java.util.concurrent.Callable;
+
+import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
+import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.server.config.meta.PKCS5S2PasswordStorageSchemeCfgDefn;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import static org.opends.server.TestCaseUtils.withoutJceService;
+import static org.testng.Assert.*;
+
/**
* A set of test cases for the PKCS5S2 password storage scheme.
*/
@@ -123,4 +133,71 @@
return PKCS5S2PasswordStorageScheme.encodeOffline(plaintextBytes);
}
+ /**
+ * A FIPS-restricted JCE (SunPKCS11-NSS-FIPS, BC-FIPS) registers no {@code SHA1PRNG}: the
+ * scheme has to take the provider's default random source, as the other PBKDF2 schemes do,
+ * instead of failing to initialize and taking the server start down with it.
+ */
+ @Test
+ public void testInitializesAndEncodesWithoutSha1Prng() throws Exception
+ {
+ withoutSha1Prng(() ->
+ {
+ final PasswordStorageScheme<?> scheme = getScheme();
+ final ByteString plaintext = ByteString.valueOfUtf8("correct horse battery staple");
+ assertTrue(scheme.passwordMatches(plaintext, scheme.encodePassword(plaintext)));
+ return null;
+ });
+ }
+
+ /** Same for the offline encoder, which a tool may call before any scheme is initialized. */
+ @Test
+ public void testEncodesOfflineWithoutSha1Prng() throws Exception
+ {
+ withoutSha1Prng(() ->
+ {
+ final ByteString plaintext = ByteString.valueOfUtf8("correct horse battery staple");
+ final String encoded = PKCS5S2PasswordStorageScheme.encodeOffline(plaintext.toByteArray());
+ final String prefix = "{" + getScheme().getStorageSchemeName() + "}";
+ assertTrue(encoded.startsWith(prefix), encoded);
+ assertTrue(getScheme().passwordMatches(plaintext, ByteString.valueOfUtf8(encoded.substring(prefix.length()))));
+ return null;
+ });
+ }
+
+ /**
+ * When the derivation itself is unavailable, the failure has to name the algorithm: a
+ * message-less InitializationException leaves the administrator with a server which does
+ * not start and no word on why.
+ */
+ @Test
+ public void testInitializationFailureNamesTheMissingAlgorithm() throws Exception
+ {
+ withoutJceService("SecretKeyFactory", "PBKDF2WithHmacSHA1", () ->
+ {
+ try
+ {
+ getScheme();
+ fail("initialization succeeded without PBKDF2WithHmacSHA1");
+ }
+ catch (InitializationException e)
+ {
+ assertNotNull(e.getMessageObject(), "the failure carries no message");
+ assertTrue(e.getMessage().contains("for the PBKDF2WithHmacSHA1 algorithm"), e.getMessage());
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Withdraws every provider registering {@code SHA1PRNG} (the SUN provider on a stock JDK),
+ * with BC-FIPS standing in for the digests the derivation still needs from it.
+ */
+ private static void withoutSha1Prng(final Callable<Void> action) throws Exception
+ {
+ final BouncyCastleFipsProvider bcFips = new BouncyCastleFipsProvider();
+ // Seed the provider's DRBG while the JDK's own random source is still installed.
+ SecureRandom.getInstance("DEFAULT", bcFips).nextBytes(new byte[8]);
+ withoutJceService("SecureRandom", "SHA1PRNG", action, bcFips);
+ }
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/tools/ConfigureDSTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/tools/ConfigureDSTestCase.java
new file mode 100644
index 0000000..a158ce4
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/tools/ConfigureDSTestCase.java
@@ -0,0 +1,167 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2026 3A Systems, LLC.
+ */
+package org.opends.server.tools;
+
+import static org.opends.server.TestCaseUtils.withoutJceService;
+import static org.testng.Assert.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.opends.server.TestCaseUtils;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/** Tests the setup-time configuration done by {@link ConfigureDS}. */
+@SuppressWarnings("javadoc")
+public class ConfigureDSTestCase extends ToolsTestCase
+{
+ private static final String DEFAULT_KEY_WRAPPING_TRANSFORMATION = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING";
+
+ /** The tool locates the server lock file when it is created, from the server environment. */
+ @BeforeClass
+ public void startServer() throws Exception
+ {
+ TestCaseUtils.startServer();
+ }
+
+ /** A runtime which has the default transformation keeps it. */
+ @Test
+ public void testKeyWrappingTransformationStaysTheDefaultWhereTheRuntimeHasIt() throws Exception
+ {
+ assertEquals(ConfigureDS.supportedKeyWrappingTransformation(DEFAULT_KEY_WRAPPING_TRANSFORMATION),
+ DEFAULT_KEY_WRAPPING_TRANSFORMATION);
+ }
+
+ /**
+ * A runtime without RSA-OAEP under either spelling gets no transformation at all, rather than
+ * a weaker one: setup is to say so, and the administrator is to choose.
+ */
+ @Test
+ public void testNoKeyWrappingTransformationIsChosenWhereTheRuntimeHasNoRsaOaep() throws Exception
+ {
+ withoutJceService("Cipher", "RSA", () ->
+ {
+ try
+ {
+ final String chosen = ConfigureDS.supportedKeyWrappingTransformation(DEFAULT_KEY_WRAPPING_TRANSFORMATION);
+ fail("a transformation was chosen on a runtime without RSA-OAEP: " + chosen);
+ }
+ catch (GeneralSecurityException expected)
+ {
+ assertTrue(expected.getMessage().contains(DEFAULT_KEY_WRAPPING_TRANSFORMATION), expected.getMessage());
+ }
+ return null;
+ });
+ }
+
+ /**
+ * A runtime whose only RSA cipher is PKCS#1 v1.5, as a SunPKCS11 provider on its own, does not
+ * get that transformation as the fallback either (#776). Withdrawing {@code Cipher.RSA} alone
+ * cannot show it, since the PKCS#1 v1.5 transformation goes through that service as well.
+ */
+ @Test
+ public void testNoKeyWrappingTransformationIsChosenWhereTheRuntimeHasOnlyPkcs1() throws Exception
+ {
+ withoutJceService("Cipher", "RSA", () ->
+ {
+ final Provider pkcs1Only = new Provider("Pkcs1OnlyRsa", "1.0", "RSA with PKCS#1 v1.5 padding only") {};
+ pkcs1Only.put("Cipher.RSA/ECB/PKCS1Padding", "com.sun.crypto.provider.RSACipher");
+ Security.insertProviderAt(pkcs1Only, 1);
+ try
+ {
+ assertEquals(Cipher.getInstance("RSA/ECB/PKCS1Padding").getProvider().getName(), pkcs1Only.getName(),
+ "the fixture offers no PKCS#1 v1.5 transformation");
+ try
+ {
+ final String chosen = ConfigureDS.supportedKeyWrappingTransformation(DEFAULT_KEY_WRAPPING_TRANSFORMATION);
+ fail("a transformation was chosen on a runtime whose only RSA cipher is PKCS#1 v1.5: " + chosen);
+ }
+ catch (GeneralSecurityException expected)
+ {
+ assertTrue(expected.getMessage().contains(DEFAULT_KEY_WRAPPING_TRANSFORMATION), expected.getMessage());
+ }
+ }
+ finally
+ {
+ Security.removeProvider(pkcs1Only.getName());
+ }
+ return null;
+ });
+ }
+
+ /** Where the runtime has the default transformation, setup has nothing to warn about. */
+ @Test
+ public void testNoWarningWhereTheRuntimeHasTheDefaultKeyWrappingTransformation() throws Exception
+ {
+ assertNull(ConfigureDS.unsupportedKeyWrappingTransformationWarning());
+ assertEquals(updateCryptoCipher(), "");
+ }
+
+ /**
+ * Where the runtime has no RSA-OAEP, the warning names the default transformation and the
+ * property to set, both the one the installer gives and the one configure-ds writes; and the
+ * configuration keeps the default (the configuration handler is not even there to change it).
+ */
+ @Test
+ public void testWarningNamesTheTransformationAndThePropertyWhereTheRuntimeHasNoRsaOaep() throws Exception
+ {
+ withoutJceService("Cipher", "RSA", () ->
+ {
+ final LocalizableMessage warning = ConfigureDS.unsupportedKeyWrappingTransformationWarning();
+ assertNotNull(warning, "no warning on a runtime without RSA-OAEP");
+ assertNamesTransformationAndProperty(warning.toString());
+
+ assertNamesTransformationAndProperty(updateCryptoCipher());
+ return null;
+ });
+ }
+
+ private static void assertNamesTransformationAndProperty(final String warning)
+ {
+ final String text = warning.replaceAll("\\s+", " ");
+ assertTrue(text.startsWith("This Java runtime supports neither the default key wrapping transformation "
+ + DEFAULT_KEY_WRAPPING_TRANSFORMATION + " "), text);
+ assertTrue(text.contains("the key-wrapping-transformation property"), text);
+ }
+
+ /**
+ * Runs the key wrapping step of configure-ds on its own, and returns what it wrote to its error
+ * stream. The tool has no configuration handler here, so a step which tried to change the
+ * configuration would fail.
+ */
+ private static String updateCryptoCipher() throws Exception
+ {
+ final ByteArrayOutputStream err = new ByteArrayOutputStream();
+ final Constructor<ConfigureDS> constructor =
+ ConfigureDS.class.getDeclaredConstructor(String[].class, OutputStream.class, OutputStream.class);
+ constructor.setAccessible(true);
+ final ConfigureDS tool = constructor.newInstance(new String[0], new ByteArrayOutputStream(), err);
+ final Method updateCryptoCipher = ConfigureDS.class.getDeclaredMethod("updateCryptoCipher");
+ updateCryptoCipher.setAccessible(true);
+ updateCryptoCipher.invoke(tool);
+ return new String(err.toByteArray(), StandardCharsets.UTF_8);
+ }
+}
--
Gitblit v1.10.0