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