From 133dc0d6d29269810fa0c441c6a76606e99cf39a Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 16 Sep 2026 13:38:30 +0300 Subject: [PATCH 1/2] Initialize the PKCS5S2 scheme from the provider's default random source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PKCS5S2PasswordStorageScheme asked for SecureRandom "SHA1PRNG" by name, at initialization and again in the offline encoder. A FIPS-restricted JCE (SunPKCS11-NSS-FIPS, BC-FIPS) registers no such algorithm, so the scheme threw a message-less InitializationException and the server refused to start — the failure #1054 works around by disabling the scheme in the config template, which would also stop {PKCS5S2} hashes imported from Atlassian products from binding on every new installation. Take the provider's default SecureRandom instead, as the PBKDF2 family already does, and let the InitializationException of both PKCS5S2 and the PBKDF2 schemes name the algorithm that is missing rather than leaving the administrator with no reason for the failed start. The SHA1PRNG constant has no user left and goes. The tests withdraw the providers which register the service — SUN for SHA1PRNG, with BC-FIPS standing in for the digests, SunJCE for PBKDF2WithHmacSHA1 — run the scheme without them, and put them back. --- .../AbstractPBKDF2PasswordStorageScheme.java | 3 +- .../extensions/ExtensionsConstants.java | 6 +- .../PKCS5S2PasswordStorageScheme.java | 10 ++- .../java/org/opends/server/TestCaseUtils.java | 61 +++++++++++++++ .../PBKDF2PasswordStorageSchemeTestCase.java | 30 ++++++++ .../PKCS5S2PasswordStorageSchemeTestCase.java | 77 +++++++++++++++++++ 6 files changed, 177 insertions(+), 10 deletions(-) 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/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/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); + } } From 4ac5363988be941f157f804516cb136b5f375d0d Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 16 Sep 2026 13:38:31 +0300 Subject: [PATCH 2/2] Say at setup, and in the refusal at start, which property to set when the runtime has no RSA-OAEP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crypto manager validates its key wrapping transformation when it is created, so a Java runtime whose only RSA cipher is PKCS#1 v1.5 — a SunPKCS11 provider on its own, as on a Linux system in FIPS mode (JDK-6190389 is still open) — cannot start the server with the default, RSA-OAEP. Setup noticed as much in ConfigureDS.updateCryptoCipher and, since #776 left it no secure transformation to fall back to, silently kept the default; the failed start which followed named the cipher it could not get and nothing else. Setup now prints a warning naming the transformation, the reason and the property to set before the first start, and the crypto manager's refusal names the property as well. The choice itself stays with the administrator: the install guide gains a procedure for a FIPS 140 runtime which says what the bundled BC-FIPS provider offers, what a SunPKCS11-only runtime does not, and how to set key-wrapping-transformation in config.ldif between setup --doNotStart and start-ds. --- .../asciidoc/install-guide/chap-install.adoc | 54 +++++++++++++++ .../org/opends/server/tools/ConfigureDS.java | 65 ++++++++++++++----- .../org/opends/messages/core.properties | 4 +- .../org/opends/messages/tool.properties | 5 ++ .../server/crypto/CryptoManagerTestCase.java | 24 +++++++ .../server/tools/ConfigureDSTestCase.java | 60 +++++++++++++++++ 6 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 opendj-server-legacy/src/test/java/org/opends/server/tools/ConfigureDSTestCase.java 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/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/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/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; + }); + } +}