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 7e0bee33e3..c843f5fd96 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 @@ At this point you can use OpenDJ directory server, or you can perform additional
====
+[#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 the default configuration works as it is.
+
+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 \
+ --sourceLDIF /path/to/opendj/config/config.ldif \
+ --changesLDIF changes.ldif \
+ --targetLDIF /path/to/opendj/config/config.ldif.new
+
+$ 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
====
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 c92c267307..0eaa4ad4af 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 @@ public void initializePasswordStorageScheme(PBKDF2PasswordStorageSchemeCfg confi
}
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 43b709238a..81ad8a030a 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 class ExtensionsConstants
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 8b881cc22d..dbcd4feab4 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 @@ public void initializePasswordStorageScheme(PKCS5S2PasswordStorageSchemeCfg conf
{
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 @@ private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltByte
{
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 02efe2c534..e0b0b9d3e7 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
@@ -1224,29 +1224,32 @@ private void updateCryptoCipher() throws ConfigureDSException
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, and
+ // has to be told so here rather than by the failed start.
+ printWrappedText(err, WARN_CONFIGDS_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.get(defaultCipher, ex.getMessage()));
+ 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));
}
}
}
@@ -1326,6 +1329,34 @@ private Entry removeAttribute(Entry entry, String attrName)
return duplicateEntry;
}
+ /**
+ * 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 null if no alternative cipher could be found.
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 ddbd5db640..7475d280a2 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/core.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/core.properties
@@ -1195,7 +1195,9 @@ ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_MAC_ENGINE_662=CryptoManager cannot \
ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_ENCRYPTION_CIPHER_663=CryptoManager \
cannot get the requested encryption cipher %s: %s
ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER_664=CryptoManager \
- cannot get the preferred key wrapping cipher: %s
+ cannot get the preferred key wrapping cipher: %s. The \
+ key-wrapping-transformation property of the crypto manager must name a \
+ transformation which this Java runtime supports
ERR_CRYPTOMGR_FAILED_TO_INITIATE_INSTANCE_KEY_GENERATION_665=CryptoManager \
failed to add entry "%s" to initiate instance key generation
ERR_CRYPTOMGR_FAILED_TO_RETRIEVE_INSTANCE_CERTIFICATE_666=CryptoManager \
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 930df01dfe..34d2f5e210 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 @@ ERR_FILE_NOT_FULLY_READABLE_20015=Could not completely read file '%s'
SUPPLEMENT_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_INDEX_STATUS_20016=\
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 436f379b40..3cdc9d30eb 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;
@@ -67,6 +69,7 @@
import java.util.HashSet;
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;
@@ -316,6 +319,64 @@ static class TestPorts
}
}
+ /**
+ * 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 action, final Provider... standIns) throws Exception
+ {
+ final String service = type + "." + algorithm;
+ final List 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 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 37537a72b7..ffeb850f31 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,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.TreeSet;
import java.util.UUID;
@@ -297,6 +299,28 @@ public void testSslCertNicknameChangeReportsATrustStoreItCannotRead() throws Exc
.contains(trustStore.getTrustStoreFile());
}
+ /**
+ 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 why = new ArrayList<>();
+
+ final boolean acceptable =
+ cm.isConfigurationChangeAcceptable(withProperty(cfg, "getKeyWrappingTransformation", unsupported), why);
+
+ assertThat(acceptable).isFalse();
+ assertThat(why).hasSize(1);
+ assertThat(why.get(0).toString()).contains(unsupported).contains("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
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 65641de2d1..d966d50ff4 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 @@ protected String encodeOffline(final byte[] plaintextBytes) throws DirectoryExce
{
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("PBKDF2WithHmacSHA1"), 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 b5cb07d339..4098ebf896 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 @@ protected String encodeOffline(final byte[] plaintextBytes) throws DirectoryExce
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 is what encode-password and the initial root password use. */
+ @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("PBKDF2WithHmacSHA1"), 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 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 0000000000..3ad72c455e
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/tools/ConfigureDSTestCase.java
@@ -0,0 +1,60 @@
+/*
+ * 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.security.GeneralSecurityException;
+
+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";
+
+ /** 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 (#776): 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;
+ });
+ }
+}