From 0046d6bdfa44cfcef7ea8eb567731e06591f3eff Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 16 Sep 2026 13:38:30 +0300 Subject: [PATCH 1/3] 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 0f5e74b6db..3d41854f7c 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 @@ 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 38773d69388f35905b5be35de314c1415909e977 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 16 Sep 2026 13:38:31 +0300 Subject: [PATCH 2/3] 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; + }); + } +} From adb4a39fe48710779a67d5f5a80dcc6dcc80207e Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 23 Sep 2026 15:09:44 +0300 Subject: [PATCH 3/3] Give the key wrapping warning from the installer, and name the property only when the transformation is refused Setup runs ConfigureDS in-process with its error stream bound to the installer's ApplicationPrintStream, and silences the progress listeners for the whole configure step, so the warning ConfigureDS printed reached the setup log only, which a successful setup deletes: setup --doNotStart on a runtime without RSA-OAEP still ended silently. ConfigureDS now builds the warning in unsupportedKeyWrappingTransformationWarning(), which the installer calls once the configure step is over, when the listeners are back. A standalone configure-ds still prints it. The check which reports ERR 664 also takes an MD5 digest of the dummy certificate and wraps with its 1024-bit RSA key, so the sentence about key-wrapping-transformation added to that message could point at a property which is not the cause. The transformation is now checked on its own first, and only that refusal (ERR 766) names the property; ERR 664 is back to its original text. The install guide and the admin guide called ldifmodify with --sourceLDIF, --changesLDIF and --targetLDIF, which it does not have: it takes the source and changes files as trailing arguments and the output file with --outputLDIF. The FIPS procedure now says that the default configuration works with the BC-FIPS provider in its default mode, not in approved-only mode. Tests: the setup-time warning, with and without RSA-OAEP, from the installer's entry point and from configure-ds's own step; a runtime whose only RSA cipher is PKCS#1 v1.5 gets no fallback (#776), which withdrawing Cipher.RSA alone could not show; a refusal for a cause other than the transformation does not name the property; the scheme initialization failures are asserted on the algorithm argument of the message rather than on the text of the exception it carries. --- .../admin-guide/chap-import-export.adoc | 8 +- .../asciidoc/install-guide/chap-install.adoc | 10 +- .../quicksetup/installer/Installer.java | 8 ++ .../server/crypto/CryptoManagerImpl.java | 26 +++++ .../org/opends/server/tools/ConfigureDS.java | 71 +++++++++--- .../org/opends/messages/core.properties | 8 +- .../server/crypto/CryptoManagerTestCase.java | 34 ++++++ .../PBKDF2PasswordStorageSchemeTestCase.java | 2 +- .../PKCS5S2PasswordStorageSchemeTestCase.java | 4 +- .../server/tools/ConfigureDSTestCase.java | 109 +++++++++++++++++- 10 files changed, 247 insertions(+), 33 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 9a3cb46055..b65a114adb 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 @@ replace: initials 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 c843f5fd96..106f82998a 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 @@ -792,7 +792,7 @@ 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. +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. @@ -824,9 +824,9 @@ 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 + --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 ---- @@ -987,7 +987,7 @@ replace: ds-cfg-java-class 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 0d3096cfc2..bcb26d92cb 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 @@ -133,6 +133,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; @@ -1346,6 +1347,13 @@ public void abort() }; 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 c15db1c66a..4ab3d15b3b 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 @@ public boolean isConfigurationChangeAcceptable( requestedKeyWrappingTransformation)); isAcceptable = false; } + else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformation, unacceptableReasons)) { + isAcceptable = false; + } else { try { /* Note that the TrustStoreBackend not available at initial, @@ -430,6 +433,29 @@ public boolean isConfigurationChangeAcceptable( 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 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/tools/ConfigureDS.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureDS.java index e0b0b9d3e7..8df0c77aa5 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,20 +1208,7 @@ private void updateCryptoManagerCertNickname() throws ConfigureDSException */ 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) { final String cipher; @@ -1232,9 +1219,10 @@ private void updateCryptoCipher() throws ConfigureDSException catch (final GeneralSecurityException ex) { // 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())); + // 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)) @@ -1329,6 +1317,55 @@ private Entry removeAttribute(Entry entry, String attrName) return duplicateEntry; } + /** + * 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()}. 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 7475d280a2..72759e0df7 100644 --- a/opendj-server-legacy/src/messages/org/opends/messages/core.properties +++ b/opendj-server-legacy/src/messages/org/opends/messages/core.properties @@ -1195,9 +1195,7 @@ 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. The \ - key-wrapping-transformation property of the crypto manager must name a \ - transformation which this Java runtime supports + cannot get the preferred key wrapping cipher: %s 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 \ @@ -1378,3 +1376,7 @@ WARN_CRYPTOMGR_SSL_CERT_NICKNAME_LOOKUP_FAILED_765=The certificate nicknames now 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/test/java/org/opends/server/crypto/CryptoManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java index ffeb850f31..0642f4cb4e 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,6 +39,7 @@ 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; @@ -318,9 +319,42 @@ public void testUnsupportedKeyWrappingTransformationIsRefusedNamingTheProperty() 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 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 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 d966d50ff4..9cc9455c66 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 @@ -95,7 +95,7 @@ public void testInitializationFailureNamesTheMissingAlgorithm() throws Exception catch (InitializationException e) { assertNotNull(e.getMessageObject(), "the failure carries no message"); - assertTrue(e.getMessage().contains("PBKDF2WithHmacSHA1"), e.getMessage()); + 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 4098ebf896..ec0d4b7b49 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 @@ -150,7 +150,7 @@ public void testInitializesAndEncodesWithoutSha1Prng() throws Exception }); } - /** Same for the offline encoder, which is what encode-password and the initial root password use. */ + /** Same for the offline encoder, which a tool may call before any scheme is initialized. */ @Test public void testEncodesOfflineWithoutSha1Prng() throws Exception { @@ -183,7 +183,7 @@ public void testInitializationFailureNamesTheMissingAlgorithm() throws Exception catch (InitializationException e) { assertNotNull(e.getMessageObject(), "the failure carries no message"); - assertTrue(e.getMessage().contains("PBKDF2WithHmacSHA1"), e.getMessage()); + assertTrue(e.getMessage().contains("for the PBKDF2WithHmacSHA1 algorithm"), e.getMessage()); } return null; }); 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 index 3ad72c455e..a158ce47e3 100644 --- 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 @@ -18,8 +18,20 @@ 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}. */ @@ -28,6 +40,13 @@ 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 @@ -38,7 +57,7 @@ public void testKeyWrappingTransformationStaysTheDefaultWhereTheRuntimeHasIt() t /** * 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. + * a weaker one: setup is to say so, and the administrator is to choose. */ @Test public void testNoKeyWrappingTransformationIsChosenWhereTheRuntimeHasNoRsaOaep() throws Exception @@ -57,4 +76,92 @@ public void testNoKeyWrappingTransformationIsChosenWhereTheRuntimeHasNoRsaOaep() 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 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); + } }