Keep PKCS5S2 usable on a FIPS-restricted JCE, and name the key wrapping property when the runtime has no RSA-OAEP (#1058)
14 files modified
1 files added
| | |
| | | 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!: |
| | |
| | | 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. |
| | | |
| | |
| | | |
| | | ==== |
| | | |
| | | [#install-fips] |
| | | .To Install OpenDJ Directory Server on a FIPS 140 Java Runtime |
| | | ==== |
| | | OpenDJ ships the Bouncy Castle FIPS provider. The `setup` command and the server register it themselves when the server key store is a BCFKS key store (`--useBcfksKeystore`), and the server registers it at start whenever the `org.openidentityplatform.opendj.fips.register` Java system property is `true`, for example through the `OPENDJ_JAVA_ARGS` environment variable. With this provider in its default mode the default configuration works as it is. The approved-only mode of the provider (the `org.bouncycastle.fips.approved_only` Java system property), which OpenDJ does not turn on, is not covered here. |
| | | |
| | | The crypto manager wraps the secret keys it shares with the other servers of a replication topology with each server's public key. The transformation it uses, the `key-wrapping-transformation` property of the crypto manager, is `RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING` by default: RSA-OAEP, the key transport scheme of NIST SP 800-56B, which Bouncy Castle FIPS provides. A Java runtime whose cryptography comes from a `SunPKCS11` provider alone, such as `SunPKCS11-NSS-FIPS` on a Linux system in FIPS mode, provides no RSA-OAEP at all: its only RSA cipher is `RSA/ECB/PKCS1Padding`, which NIST SP 800-131A Rev. 2 disallows for key transport. On such a runtime the server refuses to start with the default transformation, `setup` says so, and the choice of another one is yours to make: OpenDJ does not make it for you. If you make it, keep in mind that every server of a replication topology must use the same transformation, since each server unwraps what the others wrapped. |
| | | |
| | | . Install the server without starting it: |
| | | + |
| | | |
| | | [source, console] |
| | | ---- |
| | | $ ./setup --cli --doNotStart \ |
| | | --hostname opendj.example.com \ |
| | | --ldapPort 1389 \ |
| | | --adminConnectorPort 4444 \ |
| | | --rootUserDN "cn=Directory Manager" \ |
| | | --rootUserPassword password \ |
| | | --baseDN dc=example,dc=com \ |
| | | --acceptLicense \ |
| | | --no-prompt |
| | | ---- |
| | | |
| | | . Set the transformation in the server configuration file, which the `dsconfig` command cannot change while the server is stopped: |
| | | + |
| | | |
| | | [source, console] |
| | | ---- |
| | | $ cat changes.ldif |
| | | dn: cn=Crypto Manager,cn=config |
| | | changetype: modify |
| | | replace: ds-cfg-key-wrapping-transformation |
| | | ds-cfg-key-wrapping-transformation: RSA/ECB/PKCS1Padding |
| | | |
| | | $ ldifmodify \ |
| | | --outputLDIF /path/to/opendj/config/config.ldif.new \ |
| | | /path/to/opendj/config/config.ldif \ |
| | | changes.ldif |
| | | |
| | | $ mv /path/to/opendj/config/config.ldif.new /path/to/opendj/config/config.ldif |
| | | ---- |
| | | |
| | | . Start the server: |
| | | + |
| | | |
| | | [source, console] |
| | | ---- |
| | | $ start-ds |
| | | ---- |
| | | + |
| | | Once the server runs, the `dsconfig set-crypto-manager-prop` command changes the property, and refuses a transformation the runtime does not support. |
| | | |
| | | ==== |
| | | |
| | | [#pdb-to-je] |
| | | .To Move Data from a PDB Backend to a JE Backend |
| | | ==== |
| | |
| | | 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" |
| | |
| | | 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; |
| | |
| | | }; |
| | | 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); |
| | | } |
| | |
| | | requestedKeyWrappingTransformation)); |
| | | isAcceptable = false; |
| | | } |
| | | else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformation, unacceptableReasons)) { |
| | | isAcceptable = false; |
| | | } |
| | | else { |
| | | try { |
| | | /* Note that the TrustStoreBackend not available at initial, |
| | |
| | | return isAcceptable; |
| | | } |
| | | |
| | | /** |
| | | * Checks that this Java runtime provides the key wrapping transformation. Only a refusal here |
| | | * names the key-wrapping-transformation property: the wrap which follows it also needs an MD5 |
| | | * digest and a 1024-bit RSA key, and changing the property does not help when one of those is |
| | | * what the runtime refuses. |
| | | */ |
| | | private static boolean isKeyWrappingTransformationSupported( |
| | | final String transformation, final List<LocalizableMessage> unacceptableReasons) |
| | | { |
| | | try |
| | | { |
| | | Cipher.getInstance(transformation); |
| | | return true; |
| | | } |
| | | catch (GeneralSecurityException ex) |
| | | { |
| | | logger.traceException(ex); |
| | | unacceptableReasons.add( |
| | | ERR_CRYPTOMGR_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.get(transformation, getExceptionMessage(ex))); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public ConfigChangeResult applyConfigurationChange(CryptoManagerCfg cfg) |
| | | { |
| | |
| | | } |
| | | catch (NoSuchAlgorithmException e) |
| | | { |
| | | throw new InitializationException(null); |
| | | throw new InitializationException( |
| | | ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(getMessageDigestAlgorithm(), e), e); |
| | | } |
| | | |
| | | this.config = configuration; |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions copyright 2013-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | |
| | | 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"; |
| | | |
| | | |
| | | |
| | | /** |
| | |
| | | * |
| | | * Copyright 2014-2016 ForgeRock AS. |
| | | * Portions Copyright 2014 Emidio Stani & Andrea Stani |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | |
| | | { |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | { |
| | | try |
| | | { |
| | | final SecureRandom random = SecureRandom.getInstance(SECURE_PRNG_SHA1); |
| | | return encodeWithRandomSalt(plaintext, saltBytes, random); |
| | | return encodeWithRandomSalt(plaintext, saltBytes, new SecureRandom()); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | |
| | | */ |
| | | private void updateCryptoCipher() throws ConfigureDSException |
| | | { |
| | | final CryptoManagerCfgDefn cryptoManager = CryptoManagerCfgDefn.getInstance(); |
| | | final StringPropertyDefinition prop = cryptoManager.getKeyWrappingTransformationPropertyDefinition(); |
| | | String defaultCipher = null; |
| | | |
| | | final DefaultBehaviorProvider<?> p = prop.getDefaultBehaviorProvider(); |
| | | if (p instanceof DefinedDefaultBehaviorProvider) |
| | | { |
| | | final Collection<?> defaultValues = ((DefinedDefaultBehaviorProvider<?>) p).getDefaultValues(); |
| | | if (!defaultValues.isEmpty()) |
| | | { |
| | | defaultCipher = defaultValues.iterator().next().toString(); |
| | | } |
| | | } |
| | | |
| | | final String defaultCipher = defaultKeyWrappingTransformation(); |
| | | if (defaultCipher != null) |
| | | { |
| | | // Check that the default cipher is supported by the JVM. |
| | | final String cipher; |
| | | try |
| | | { |
| | | Cipher.getInstance(defaultCipher); |
| | | cipher = supportedKeyWrappingTransformation(defaultCipher); |
| | | } |
| | | catch (final GeneralSecurityException ex) |
| | | { |
| | | // The cipher is not supported: try to find an alternative one. |
| | | final String alternativeCipher = getAlternativeCipher(); |
| | | if (alternativeCipher != null) |
| | | // The default stays, and the server will refuse to start with it: there is no secure |
| | | // transformation to fall back to (#776), so the administrator has to choose one. Under |
| | | // setup this stream reaches the setup log only, and the installer gives the warning |
| | | // itself (see unsupportedKeyWrappingTransformationWarning()). |
| | | printWrappedText(err, unsupportedKeyWrappingTransformationWarning(defaultCipher, ex)); |
| | | return; |
| | | } |
| | | if (!cipher.equals(defaultCipher)) |
| | | { |
| | | try |
| | | { |
| | | try |
| | | { |
| | | updateConfigEntryWithAttribute( |
| | | DN_CRYPTO_MANAGER, |
| | | ATTR_CRYPTO_CIPHER_KEY_WRAPPING_TRANSFORMATION, |
| | | CoreSchema.getDirectoryStringSyntax(), |
| | | alternativeCipher); |
| | | } |
| | | catch (final Exception e) |
| | | { |
| | | throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CRYPTO_MANAGER.get(e)); |
| | | } |
| | | updateConfigEntryWithAttribute( |
| | | DN_CRYPTO_MANAGER, |
| | | ATTR_CRYPTO_CIPHER_KEY_WRAPPING_TRANSFORMATION, |
| | | CoreSchema.getDirectoryStringSyntax(), |
| | | cipher); |
| | | } |
| | | catch (final Exception e) |
| | | { |
| | | throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CRYPTO_MANAGER.get(e)); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the warning to give when this Java runtime supports neither the default key wrapping |
| | | * transformation of the crypto manager nor an alternative to it: the server will then refuse to |
| | | * start until the administrator sets one. The installer calls this itself, since what |
| | | * {@code configMain} writes while it runs under setup reaches the setup log only. |
| | | * |
| | | * @return The warning, or {@code null} when the runtime supports a transformation. |
| | | */ |
| | | public static LocalizableMessage unsupportedKeyWrappingTransformationWarning() |
| | | { |
| | | final String defaultCipher = defaultKeyWrappingTransformation(); |
| | | if (defaultCipher == null) |
| | | { |
| | | return null; |
| | | } |
| | | try |
| | | { |
| | | supportedKeyWrappingTransformation(defaultCipher); |
| | | return null; |
| | | } |
| | | catch (final GeneralSecurityException ex) |
| | | { |
| | | return unsupportedKeyWrappingTransformationWarning(defaultCipher, ex); |
| | | } |
| | | } |
| | | |
| | | private static LocalizableMessage unsupportedKeyWrappingTransformationWarning( |
| | | final String defaultCipher, final GeneralSecurityException ex) |
| | | { |
| | | return WARN_CONFIGDS_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.get(defaultCipher, ex.getMessage()); |
| | | } |
| | | |
| | | /** Returns the default key wrapping transformation of the crypto manager, or {@code null}. */ |
| | | private static String defaultKeyWrappingTransformation() |
| | | { |
| | | final StringPropertyDefinition prop = |
| | | CryptoManagerCfgDefn.getInstance().getKeyWrappingTransformationPropertyDefinition(); |
| | | final DefaultBehaviorProvider<?> p = prop.getDefaultBehaviorProvider(); |
| | | if (p instanceof DefinedDefaultBehaviorProvider) |
| | | { |
| | | final Collection<?> defaultValues = ((DefinedDefaultBehaviorProvider<?>) p).getDefaultValues(); |
| | | if (!defaultValues.isEmpty()) |
| | | { |
| | | return defaultValues.iterator().next().toString(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Returns the key wrapping transformation this Java runtime supports: the default one when it |
| | | * does, otherwise the OAEP alternative of {@link #getAlternativeCipher()}. |
| | | * |
| | | * @param defaultCipher |
| | | * The default key wrapping transformation of the crypto manager. |
| | | * @return The transformation to configure. |
| | | * @throws GeneralSecurityException |
| | | * If the runtime supports neither, with the reason the default one is not. |
| | | */ |
| | | static String supportedKeyWrappingTransformation(final String defaultCipher) throws GeneralSecurityException |
| | | { |
| | | try |
| | | { |
| | | Cipher.getInstance(defaultCipher); |
| | | return defaultCipher; |
| | | } |
| | | catch (final GeneralSecurityException ex) |
| | | { |
| | | final String alternativeCipher = getAlternativeCipher(); |
| | | if (alternativeCipher == null) |
| | | { |
| | | throw ex; |
| | | } |
| | | return alternativeCipher; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Returns a cipher that is supported by the JVM we are running at. |
| | | * Returns <CODE>null</CODE> if no alternative cipher could be found. |
| | | * @return a cipher that is supported by the JVM we are running at. |
| | |
| | | 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 |
| | |
| | | SUPPLEMENT_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_INDEX_STATUS_20016=\ |
| | | <xinclude:include href="variablelist-backendstat-index-status.xml" /> |
| | | INFO_DESCRIPTION_DEFAULT_ADD_20017=Legacy argument for ForgeRock OpenDJ compatibility. |
| | | WARN_CONFIGDS_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED_20018=This Java runtime \ |
| | | supports neither the default key wrapping transformation %s nor an alternative \ |
| | | to it: %s. The server will not start until the key-wrapping-transformation \ |
| | | property of the crypto manager names a transformation which the runtime \ |
| | | supports; set it in config/config.ldif before starting the server |
| | | INFO_LDAP_CONN_PROMPT_SECURITY_LDAP=LDAP |
| | | INFO_LDAP_CONN_PROMPT_SECURITY_USE_SSL=LDAP with SSL |
| | | INFO_LDAP_CONN_PROMPT_SECURITY_USE_START_TLS=LDAP with StartTLS |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Runs {@code action} while no installed JCE provider offers the given service, the way a |
| | | * FIPS-restricted JVM lacks it, and puts the withdrawn providers back where they were |
| | | * afterwards. The {@code standIns} are installed ahead of the remaining providers for the |
| | | * duration, for whatever the action still needs that only the withdrawn providers offered. |
| | | * |
| | | * @param type |
| | | * The JCE service type, e.g. {@code SecureRandom}. |
| | | * @param algorithm |
| | | * The algorithm to withdraw, e.g. {@code SHA1PRNG}. |
| | | * @param action |
| | | * What to run without the service. |
| | | * @param standIns |
| | | * Providers to install first while the service is withdrawn. |
| | | * @throws Exception |
| | | * If the action fails, or if the service could not be withdrawn. |
| | | */ |
| | | public static void withoutJceService(final String type, final String algorithm, |
| | | final Callable<Void> action, final Provider... standIns) throws Exception |
| | | { |
| | | final String service = type + "." + algorithm; |
| | | final List<Provider> installed = Arrays.asList(Security.getProviders()); |
| | | final Provider[] offering = Security.getProviders(service); |
| | | assertNotNull(offering, "no installed provider offers " + service + ": nothing to withdraw"); |
| | | for (Provider provider : offering) |
| | | { |
| | | Security.removeProvider(provider.getName()); |
| | | } |
| | | final List<Provider> addedStandIns = new ArrayList<>(); |
| | | for (int i = 0; i < standIns.length; i++) |
| | | { |
| | | if (Security.insertProviderAt(standIns[i], i + 1) != -1) |
| | | { |
| | | addedStandIns.add(standIns[i]); |
| | | } |
| | | } |
| | | try |
| | | { |
| | | assertNull(Security.getProviders(service), service + " is still offered: the fixture does not withdraw it"); |
| | | action.call(); |
| | | } |
| | | finally |
| | | { |
| | | for (Provider standIn : addedStandIns) |
| | | { |
| | | Security.removeProvider(standIn.getName()); |
| | | } |
| | | // Ascending original positions, so that the list comes back in its original order. |
| | | for (Provider provider : installed) |
| | | { |
| | | if (Arrays.asList(offering).contains(provider)) |
| | | { |
| | | Security.insertProviderAt(provider, installed.indexOf(provider) + 1); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | public static void startServer() throws Exception |
| | | { |
| | | System.setProperty(PROPERTY_RUNNING_UNIT_TESTS, "true"); |
| | |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.security.MessageDigest; |
| | | import java.security.Provider; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.TreeSet; |
| | | import java.util.UUID; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | A key wrapping transformation this Java runtime cannot provide is refused, at start (where |
| | | the refusal is what keeps the server from starting) as on a change, and the refusal has to |
| | | name the property to set, not only the cipher which failed: on a FIPS-restricted runtime |
| | | without RSA-OAEP that is all the administrator has to go on. |
| | | */ |
| | | @Test |
| | | public void testUnsupportedKeyWrappingTransformationIsRefusedNamingTheProperty() throws Exception |
| | | { |
| | | final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); |
| | | final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); |
| | | final String unsupported = "RSA/ECB/NoSuchPadding"; |
| | | final List<LocalizableMessage> why = new ArrayList<>(); |
| | | |
| | | final boolean acceptable = |
| | | cm.isConfigurationChangeAcceptable(withProperty(cfg, "getKeyWrappingTransformation", unsupported), why); |
| | | |
| | | assertThat(acceptable).isFalse(); |
| | | assertThat(why).hasSize(1); |
| | | assertThat(why.get(0).ordinal()).isEqualTo(ERR_CRYPTOMGR_KEY_WRAPPING_TRANSFORMATION_UNSUPPORTED.ordinal()); |
| | | assertThat(why.get(0).toString()).contains(unsupported).contains("key-wrapping-transformation"); |
| | | } |
| | | |
| | | /** |
| | | A transformation the runtime provides, refused because of the rest of the check (here the |
| | | MD5 digest of the instance key identifier), is not reported as a matter of the property: |
| | | changing key-wrapping-transformation would not help. |
| | | */ |
| | | @Test |
| | | public void testKeyWrappingRefusalForAnotherCauseDoesNotNameTheProperty() throws Exception |
| | | { |
| | | final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); |
| | | final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); |
| | | final String supported = "RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING"; |
| | | assertThat(supported).isNotEqualTo(cfg.getKeyWrappingTransformation()); |
| | | final List<LocalizableMessage> why = new ArrayList<>(); |
| | | |
| | | // Withdrawing MD5 withdraws the SUN provider, whose SHA-1 digest the OAEP cipher still needs. |
| | | final Provider sha1Only = new Provider("Sha1OnlyDigest", "1.0", "SHA-1 digest only") {}; |
| | | sha1Only.put("MessageDigest.SHA-1", "sun.security.provider.SHA"); |
| | | sha1Only.put("Alg.Alias.MessageDigest.SHA1", "SHA-1"); |
| | | |
| | | withoutJceService("MessageDigest", "MD5", () -> |
| | | { |
| | | assertThat(cm.isConfigurationChangeAcceptable(withProperty(cfg, "getKeyWrappingTransformation", supported), why)) |
| | | .isFalse(); |
| | | return null; |
| | | }, sha1Only); |
| | | |
| | | assertThat(why).hasSize(1); |
| | | final String reason = why.get(0).toString(); |
| | | assertThat(why.get(0).ordinal()).as(reason).isEqualTo(ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.ordinal()); |
| | | assertThat(reason).contains("MD5").doesNotContain("key-wrapping-transformation"); |
| | | } |
| | | |
| | | /** |
| | | Returns the crypto manager configuration as it stands, with the ssl-cert-nickname |
| | | property answering the provided nicknames, so that a change to that property is applied |
| | | without the configuration of the running server being modified. |
| | |
| | | * 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. |
| | |
| | | { |
| | | return PBKDF2PasswordStorageScheme.encodeOffline(plaintextBytes); |
| | | } |
| | | |
| | | /** |
| | | * When the derivation is unavailable, the failure has to name the algorithm: a message-less |
| | | * InitializationException leaves the administrator with a server which does not start and |
| | | * no word on why. |
| | | */ |
| | | @Test |
| | | public void testInitializationFailureNamesTheMissingAlgorithm() throws Exception |
| | | { |
| | | withoutJceService("SecretKeyFactory", "PBKDF2WithHmacSHA1", () -> |
| | | { |
| | | try |
| | | { |
| | | getScheme(); |
| | | fail("initialization succeeded without PBKDF2WithHmacSHA1"); |
| | | } |
| | | catch (InitializationException e) |
| | | { |
| | | assertNotNull(e.getMessageObject(), "the failure carries no message"); |
| | | assertTrue(e.getMessage().contains("for the PBKDF2WithHmacSHA1 algorithm"), e.getMessage()); |
| | | } |
| | | return null; |
| | | }); |
| | | } |
| | | } |
| | |
| | | * 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. |
| | | */ |
| | |
| | | return PKCS5S2PasswordStorageScheme.encodeOffline(plaintextBytes); |
| | | } |
| | | |
| | | /** |
| | | * A FIPS-restricted JCE (SunPKCS11-NSS-FIPS, BC-FIPS) registers no {@code SHA1PRNG}: the |
| | | * scheme has to take the provider's default random source, as the other PBKDF2 schemes do, |
| | | * instead of failing to initialize and taking the server start down with it. |
| | | */ |
| | | @Test |
| | | public void testInitializesAndEncodesWithoutSha1Prng() throws Exception |
| | | { |
| | | withoutSha1Prng(() -> |
| | | { |
| | | final PasswordStorageScheme<?> scheme = getScheme(); |
| | | final ByteString plaintext = ByteString.valueOfUtf8("correct horse battery staple"); |
| | | assertTrue(scheme.passwordMatches(plaintext, scheme.encodePassword(plaintext))); |
| | | return null; |
| | | }); |
| | | } |
| | | |
| | | /** Same for the offline encoder, which a tool may call before any scheme is initialized. */ |
| | | @Test |
| | | public void testEncodesOfflineWithoutSha1Prng() throws Exception |
| | | { |
| | | withoutSha1Prng(() -> |
| | | { |
| | | final ByteString plaintext = ByteString.valueOfUtf8("correct horse battery staple"); |
| | | final String encoded = PKCS5S2PasswordStorageScheme.encodeOffline(plaintext.toByteArray()); |
| | | final String prefix = "{" + getScheme().getStorageSchemeName() + "}"; |
| | | assertTrue(encoded.startsWith(prefix), encoded); |
| | | assertTrue(getScheme().passwordMatches(plaintext, ByteString.valueOfUtf8(encoded.substring(prefix.length())))); |
| | | return null; |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * When the derivation itself is unavailable, the failure has to name the algorithm: a |
| | | * message-less InitializationException leaves the administrator with a server which does |
| | | * not start and no word on why. |
| | | */ |
| | | @Test |
| | | public void testInitializationFailureNamesTheMissingAlgorithm() throws Exception |
| | | { |
| | | withoutJceService("SecretKeyFactory", "PBKDF2WithHmacSHA1", () -> |
| | | { |
| | | try |
| | | { |
| | | getScheme(); |
| | | fail("initialization succeeded without PBKDF2WithHmacSHA1"); |
| | | } |
| | | catch (InitializationException e) |
| | | { |
| | | assertNotNull(e.getMessageObject(), "the failure carries no message"); |
| | | assertTrue(e.getMessage().contains("for the PBKDF2WithHmacSHA1 algorithm"), e.getMessage()); |
| | | } |
| | | return null; |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Withdraws every provider registering {@code SHA1PRNG} (the SUN provider on a stock JDK), |
| | | * with BC-FIPS standing in for the digests the derivation still needs from it. |
| | | */ |
| | | private static void withoutSha1Prng(final Callable<Void> action) throws Exception |
| | | { |
| | | final BouncyCastleFipsProvider bcFips = new BouncyCastleFipsProvider(); |
| | | // Seed the provider's DRBG while the JDK's own random source is still installed. |
| | | SecureRandom.getInstance("DEFAULT", bcFips).nextBytes(new byte[8]); |
| | | withoutJceService("SecureRandom", "SHA1PRNG", action, bcFips); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * The contents of this file are subject to the terms of the Common Development and |
| | | * Distribution License (the License). You may not use this file except in compliance with the |
| | | * License. |
| | | * |
| | | * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | * specific language governing permission and limitations under the License. |
| | | * |
| | | * When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.tools; |
| | | |
| | | import static org.opends.server.TestCaseUtils.withoutJceService; |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.OutputStream; |
| | | import java.lang.reflect.Constructor; |
| | | import java.lang.reflect.Method; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.GeneralSecurityException; |
| | | import java.security.Provider; |
| | | import java.security.Security; |
| | | |
| | | import javax.crypto.Cipher; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** Tests the setup-time configuration done by {@link ConfigureDS}. */ |
| | | @SuppressWarnings("javadoc") |
| | | public class ConfigureDSTestCase extends ToolsTestCase |
| | | { |
| | | private static final String DEFAULT_KEY_WRAPPING_TRANSFORMATION = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"; |
| | | |
| | | /** The tool locates the server lock file when it is created, from the server environment. */ |
| | | @BeforeClass |
| | | public void startServer() throws Exception |
| | | { |
| | | TestCaseUtils.startServer(); |
| | | } |
| | | |
| | | /** A runtime which has the default transformation keeps it. */ |
| | | @Test |
| | | public void testKeyWrappingTransformationStaysTheDefaultWhereTheRuntimeHasIt() throws Exception |
| | | { |
| | | assertEquals(ConfigureDS.supportedKeyWrappingTransformation(DEFAULT_KEY_WRAPPING_TRANSFORMATION), |
| | | DEFAULT_KEY_WRAPPING_TRANSFORMATION); |
| | | } |
| | | |
| | | /** |
| | | * A runtime without RSA-OAEP under either spelling gets no transformation at all, rather than |
| | | * a weaker one: setup is to say so, and the administrator is to choose. |
| | | */ |
| | | @Test |
| | | public void testNoKeyWrappingTransformationIsChosenWhereTheRuntimeHasNoRsaOaep() throws Exception |
| | | { |
| | | withoutJceService("Cipher", "RSA", () -> |
| | | { |
| | | try |
| | | { |
| | | final String chosen = ConfigureDS.supportedKeyWrappingTransformation(DEFAULT_KEY_WRAPPING_TRANSFORMATION); |
| | | fail("a transformation was chosen on a runtime without RSA-OAEP: " + chosen); |
| | | } |
| | | catch (GeneralSecurityException expected) |
| | | { |
| | | assertTrue(expected.getMessage().contains(DEFAULT_KEY_WRAPPING_TRANSFORMATION), expected.getMessage()); |
| | | } |
| | | return null; |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * A runtime whose only RSA cipher is PKCS#1 v1.5, as a SunPKCS11 provider on its own, does not |
| | | * get that transformation as the fallback either (#776). Withdrawing {@code Cipher.RSA} alone |
| | | * cannot show it, since the PKCS#1 v1.5 transformation goes through that service as well. |
| | | */ |
| | | @Test |
| | | public void testNoKeyWrappingTransformationIsChosenWhereTheRuntimeHasOnlyPkcs1() throws Exception |
| | | { |
| | | withoutJceService("Cipher", "RSA", () -> |
| | | { |
| | | final Provider pkcs1Only = new Provider("Pkcs1OnlyRsa", "1.0", "RSA with PKCS#1 v1.5 padding only") {}; |
| | | pkcs1Only.put("Cipher.RSA/ECB/PKCS1Padding", "com.sun.crypto.provider.RSACipher"); |
| | | Security.insertProviderAt(pkcs1Only, 1); |
| | | try |
| | | { |
| | | assertEquals(Cipher.getInstance("RSA/ECB/PKCS1Padding").getProvider().getName(), pkcs1Only.getName(), |
| | | "the fixture offers no PKCS#1 v1.5 transformation"); |
| | | try |
| | | { |
| | | final String chosen = ConfigureDS.supportedKeyWrappingTransformation(DEFAULT_KEY_WRAPPING_TRANSFORMATION); |
| | | fail("a transformation was chosen on a runtime whose only RSA cipher is PKCS#1 v1.5: " + chosen); |
| | | } |
| | | catch (GeneralSecurityException expected) |
| | | { |
| | | assertTrue(expected.getMessage().contains(DEFAULT_KEY_WRAPPING_TRANSFORMATION), expected.getMessage()); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | Security.removeProvider(pkcs1Only.getName()); |
| | | } |
| | | return null; |
| | | }); |
| | | } |
| | | |
| | | /** Where the runtime has the default transformation, setup has nothing to warn about. */ |
| | | @Test |
| | | public void testNoWarningWhereTheRuntimeHasTheDefaultKeyWrappingTransformation() throws Exception |
| | | { |
| | | assertNull(ConfigureDS.unsupportedKeyWrappingTransformationWarning()); |
| | | assertEquals(updateCryptoCipher(), ""); |
| | | } |
| | | |
| | | /** |
| | | * Where the runtime has no RSA-OAEP, the warning names the default transformation and the |
| | | * property to set, both the one the installer gives and the one configure-ds writes; and the |
| | | * configuration keeps the default (the configuration handler is not even there to change it). |
| | | */ |
| | | @Test |
| | | public void testWarningNamesTheTransformationAndThePropertyWhereTheRuntimeHasNoRsaOaep() throws Exception |
| | | { |
| | | withoutJceService("Cipher", "RSA", () -> |
| | | { |
| | | final LocalizableMessage warning = ConfigureDS.unsupportedKeyWrappingTransformationWarning(); |
| | | assertNotNull(warning, "no warning on a runtime without RSA-OAEP"); |
| | | assertNamesTransformationAndProperty(warning.toString()); |
| | | |
| | | assertNamesTransformationAndProperty(updateCryptoCipher()); |
| | | return null; |
| | | }); |
| | | } |
| | | |
| | | private static void assertNamesTransformationAndProperty(final String warning) |
| | | { |
| | | final String text = warning.replaceAll("\\s+", " "); |
| | | assertTrue(text.startsWith("This Java runtime supports neither the default key wrapping transformation " |
| | | + DEFAULT_KEY_WRAPPING_TRANSFORMATION + " "), text); |
| | | assertTrue(text.contains("the key-wrapping-transformation property"), text); |
| | | } |
| | | |
| | | /** |
| | | * Runs the key wrapping step of configure-ds on its own, and returns what it wrote to its error |
| | | * stream. The tool has no configuration handler here, so a step which tried to change the |
| | | * configuration would fail. |
| | | */ |
| | | private static String updateCryptoCipher() throws Exception |
| | | { |
| | | final ByteArrayOutputStream err = new ByteArrayOutputStream(); |
| | | final Constructor<ConfigureDS> constructor = |
| | | ConfigureDS.class.getDeclaredConstructor(String[].class, OutputStream.class, OutputStream.class); |
| | | constructor.setAccessible(true); |
| | | final ConfigureDS tool = constructor.newInstance(new String[0], new ByteArrayOutputStream(), err); |
| | | final Method updateCryptoCipher = ConfigureDS.class.getDeclaredMethod("updateCryptoCipher"); |
| | | updateCryptoCipher.setAccessible(true); |
| | | updateCryptoCipher.invoke(tool); |
| | | return new String(err.toByteArray(), StandardCharsets.UTF_8); |
| | | } |
| | | } |