From 73dcc78170d7e501acc76f41231950653c37bdd1 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 24 Sep 2025 14:31:17 +0300 Subject: [PATCH 01/36] Switch from sun.security.x509 to Bouncy Castle API --- opendj-core/pom.xml | 10 +- .../org/forgerock/opendj/ldap/LDAPServer.java | 56 +++- opendj-server-legacy/pom.xml | 4 + .../resource/bin/_script-util.bat | 4 +- .../resource/bin/_script-util.sh | 2 +- .../java/org/opends/server/util/Platform.java | 275 +++++------------- .../org/opends/messages/utility.properties | 10 +- pom.xml | 21 +- 8 files changed, 154 insertions(+), 228 deletions(-) diff --git a/opendj-core/pom.xml b/opendj-core/pom.xml index 6d318422f1..4130a3a014 100644 --- a/opendj-core/pom.xml +++ b/opendj-core/pom.xml @@ -96,14 +96,15 @@ org.bouncycastle bc-fips - ${bc.fips.version} - org.bouncycastle bctls-fips - ${bctls.fips.version} + + org.bouncycastle + bcpkix-fips + com.fasterxml.jackson.core @@ -113,8 +114,7 @@ - 2.1.1 - 2.1.20 + com.sun.security.auth*;resolution:=optional diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java index f4bcccbbc5..f7582263b3 100644 --- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java +++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java @@ -13,6 +13,7 @@ * * Copyright 2010 Sun Microsystems, Inc. * Portions Copyright 2011-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.forgerock.opendj.ldap; @@ -22,10 +23,18 @@ import static org.forgerock.opendj.ldap.TestCaseUtils.loopbackWithDynamicPort; import java.io.IOException; +import java.math.BigInteger; import java.net.InetSocketAddress; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.Security; import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -41,12 +50,20 @@ import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.x500.X500Principal; import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.RealmCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.util.BigIntegers; import org.forgerock.opendj.io.ASN1; import org.forgerock.opendj.io.ASN1Reader; import org.forgerock.opendj.io.LDAP; @@ -77,9 +94,6 @@ import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl; import com.forgerock.reactive.ServerConnectionFactoryAdapter; -import sun.security.tools.keytool.CertAndKeyGen; -import sun.security.x509.X500Name; - /** * A simple ldap server that manages 1000 entries and used for running * testcases. @@ -520,14 +534,40 @@ public boolean isRunning() { static { final String password="keypassword"; try { - CertAndKeyGen keyGen=new CertAndKeyGen("RSA","SHA256WithRSA",null); - keyGen.generate(2048); - X509Certificate[] chain=new X509Certificate[1]; - chain[0]=keyGen.getSelfCertificate(new X500Name("CN=localhost"), (long)1*3600); + if (Security.getProvider(BouncyCastleFipsProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + String keyType = "RSA"; + String signatureAlgorithm = "SHA256WithRSA"; + int keySize = 2048; + KeyPairGenerator generator = KeyPairGenerator.getInstance(keyType, BouncyCastleFipsProvider.PROVIDER_NAME); + generator.initialize(keySize); + KeyPair keyPair = generator.generateKeyPair(); + + X509Certificate[] chain = new X509Certificate[1]; + + BigInteger serial = BigIntegers.createRandomBigInteger(64, new SecureRandom()); + Instant now = Instant.now(); + Date notBeforeDate = Date.from(now); + Date notAfterDate = Date.from(now.plus(1, ChronoUnit.DAYS)); + + X500Principal subject = new X500Principal("CN=localhost"); + JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + subject, serial, notBeforeDate, notAfterDate, subject, keyPair.getPublic() + ); + ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm) + .setProvider(BouncyCastleFipsProvider.PROVIDER_NAME) + .build(keyPair.getPrivate()); + X509CertificateHolder holder = builder.build(signer); + JcaX509CertificateConverter converter = new JcaX509CertificateConverter() + .setProvider(BouncyCastleFipsProvider.PROVIDER_NAME); + + chain[0] = converter.getCertificate(holder); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); - ks.setKeyEntry("localhost", keyGen.getPrivateKey(),password.toCharArray(), chain); + ks.setKeyEntry("localhost", keyPair.getPrivate(), password.toCharArray(), chain); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, password.toCharArray()); sslContext = new SSLContextBuilder().setKeyManager(kmf.getKeyManagers()[0]).getSSLContext(); diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index d1c6d3309a..b62f3067d4 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -321,6 +321,10 @@ mssql-jdbc 12.10.0.jre8 + + org.bouncycastle + bcpkix-fips + ${project.groupId}.${project.artifactId} diff --git a/opendj-server-legacy/resource/bin/_script-util.bat b/opendj-server-legacy/resource/bin/_script-util.bat index 8971ccdaa7..785a4bd345 100644 --- a/opendj-server-legacy/resource/bin/_script-util.bat +++ b/opendj-server-legacy/resource/bin/_script-util.bat @@ -168,9 +168,9 @@ if %SET_ENVIRONMENT_VARS_DONE% == "true" goto end set PATH=%SystemRoot%;%PATH% set SCRIPT_NAME_ARG=-Dorg.opends.server.scriptName=%SCRIPT_NAME% set SET_ENVIRONMENT_VARS_DONE=true -"%OPENDJ_JAVA_BIN%" --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --version > NUL 2>&1 +"%OPENDJ_JAVA_BIN%" --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --version > NUL 2>&1 set RESULT_CODE=%errorlevel% -if %RESULT_CODE% == 0 set OPENDJ_JAVA_ARGS=%OPENDJ_JAVA_ARGS% --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED +if %RESULT_CODE% == 0 set OPENDJ_JAVA_ARGS=%OPENDJ_JAVA_ARGS% --add-opens java.base/jdk.internal.loader=ALL-UNNAMED goto scriptBegin :testJava diff --git a/opendj-server-legacy/resource/bin/_script-util.sh b/opendj-server-legacy/resource/bin/_script-util.sh index 09a8808f45..43b67cc0fe 100644 --- a/opendj-server-legacy/resource/bin/_script-util.sh +++ b/opendj-server-legacy/resource/bin/_script-util.sh @@ -184,7 +184,7 @@ set_environment_vars() { SCRIPT_NAME_ARG=-Dorg.opends.server.scriptName=${SCRIPT_NAME} export SCRIPT_NAME_ARG - "${OPENDJ_JAVA_BIN}" --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --version > /dev/null 2>&1 + "${OPENDJ_JAVA_BIN}" --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --version > /dev/null 2>&1 RESULT_CODE=${?} if test ${RESULT_CODE} -eq 0 then diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java index 543f5f93e9..05b5f769eb 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java @@ -13,30 +13,48 @@ * * Copyright 2009-2010 Sun Microsystems, Inc. * Portions Copyright 2013-2016 ForgeRock AS. + * Portions Copyright 2025 Wren Security. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.util; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.Security; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.util.BigIntegers; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.util.Reject; -import static org.opends.messages.UtilityMessages.*; -import static org.opends.server.util.ServerConstants.CERTANDKEYGEN_PROVIDER; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_ADD_CERT; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_ALIAS_ALREADY_EXISTS; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_ALIAS_INVALID; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_CERT_REPLIES_INVALID; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_DELETE_ALIAS; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_GEN_SELF_SIGNED_CERT; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_KEYSTORE_NONEXISTANT; +import static org.opends.messages.UtilityMessages.ERR_CERTMGR_TRUSTED_CERT; /** * Provides a wrapper class that collects all of the JVM vendor and JDK version @@ -45,38 +63,18 @@ public final class Platform { - /** Prefix that determines which security package to use. */ - private static final String pkgPrefix; - - /** The two security package prefixes (IBM and SUN). */ - private static final String IBM_SEC = "com.ibm.security"; - private static final String SUN_SEC = "sun.security"; - - /** The CertAndKeyGen class is located in different packages depending on JVM environment. */ - private static final String[] CERTANDKEYGEN_PATHS = new String[] { - "sun.security.x509.CertAndKeyGen", // Oracle/Sun/OpenJDK 6,7 - "sun.security.tools.keytool.CertAndKeyGen", // Oracle/Sun/OpenJDK 8 - "com.ibm.security.x509.CertAndKeyGen", // IBM SDK 7 - "com.ibm.security.tools.CertAndKeyGen" // IBM SDK 8 - }; - private static final PlatformIMPL IMPL; /** The minimum java supported version. */ - public static final String JAVA_MINIMUM_VERSION_NUMBER = "11"; + public static final String JAVA_MINIMUM_VERSION_NUMBER = "8"; static { - String vendor = System.getProperty("java.vendor"); - - if (vendor.startsWith("IBM")) - { - pkgPrefix = IBM_SEC; - } - else + if (Security.getProvider(BouncyCastleFipsProvider.PROVIDER_NAME) == null) { - pkgPrefix = SUN_SEC; + Security.addProvider(new BouncyCastleFipsProvider()); } + IMPL = new DefaultPlatformIMPL(); } @@ -84,10 +82,10 @@ public final class Platform public static enum KeyType { /** RSA key algorithm with 2048 bits size and SHA256withRSA signing algorithm. */ - RSA("rsa", 2048, "SHA256WithRSA"), + RSA("RSA", 2048, "SHA256withRSA"), - /** Elliptic Curve key algorithm with 233 bits size and SHA256withECDSA signing algorithm. */ - EC("ec", 256, "SHA256withECDSA"); + /** Elliptic Curve key algorithm with 256 bits size and SHA256withECDSA signing algorithm. */ + EC("EC", 256, "SHA256withECDSA"); /** Default key type used when none can be determined. */ public final static KeyType DEFAULT = RSA; @@ -103,22 +101,6 @@ private KeyType(String keyAlgorithm, int keySize, String signatureAlgorithm) this.signatureAlgorithm = signatureAlgorithm; } - /** - * Check whether this key type is supported by the current JVM. - * @return true if this key type is supported, false otherwise. - */ - public boolean isSupported() - { - try - { - return KeyPairGenerator.getInstance(keyAlgorithm.toUpperCase()) != null; - } - catch (NoSuchAlgorithmException e) - { - return false; - } - } - /** * Get a KeyType based on the alias name. * @@ -144,106 +126,13 @@ public static KeyType getTypeOrDefault(String alias) */ private static abstract class PlatformIMPL { - /** Time values used in validity calculations. */ - private static final int SEC_IN_DAY = 24 * 60 * 60; - - /** Methods pulled from the classes. */ - private static final String GENERATE_METHOD = "generate"; - private static final String GET_PRIVATE_KEY_METHOD = "getPrivateKey"; - private static final String GET_SELFSIGNED_CERT_METHOD = - "getSelfCertificate"; - - /** Classes needed to manage certificates. */ - private static final Class certKeyGenClass, X500NameClass; - - /** Constructors for each of the above classes. */ - private static Constructor certKeyGenCons, X500NameCons; - - /** Filesystem APIs */ - - static - { - - String certAndKeyGen = getCertAndKeyGenClassName(); - if(certAndKeyGen == null) - { - LocalizableMessage msg = ERR_CERTMGR_CERTGEN_NOT_FOUND.get(CERTANDKEYGEN_PROVIDER); - throw new ExceptionInInitializerError(msg.toString()); - } - - String X500Name = pkgPrefix + ".x509.X500Name"; - try - { - certKeyGenClass = Class.forName(certAndKeyGen); - X500NameClass = Class.forName(X500Name); - certKeyGenCons = certKeyGenClass.getConstructor(String.class, - String.class); - X500NameCons = X500NameClass.getConstructor(String.class); - } - catch (ClassNotFoundException e) - { - LocalizableMessage msg = ERR_CERTMGR_CLASS_NOT_FOUND.get(e.getMessage()); - throw new ExceptionInInitializerError(msg.toString()); - } - catch (SecurityException e) - { - LocalizableMessage msg = ERR_CERTMGR_SECURITY.get(e.getMessage()); - throw new ExceptionInInitializerError(msg.toString()); - } - catch (NoSuchMethodException e) - { - LocalizableMessage msg = ERR_CERTMGR_NO_METHOD.get(e.getMessage()); - throw new ExceptionInInitializerError(msg.toString()); - } - } - - /** - * Try to decide which CertAndKeyGen class to use. - * - * @return a fully qualified class name or null - */ - private static String getCertAndKeyGenClassName() { - String certAndKeyGen = System.getProperty(CERTANDKEYGEN_PROVIDER); - if (certAndKeyGen != null) - { - return certAndKeyGen; - } - - for (String className : CERTANDKEYGEN_PATHS) - { - if (classExists(className)) - { - return className; - } - } - return null; - } - - /** - * A quick check to see if a class can be loaded. Doesn't check if - * it can be instantiated. - * - * @param className full class name to check - * @return true if the class is found - */ - private static boolean classExists(final String className) - { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException | ClassCastException e) { - return false; - } - } protected PlatformIMPL() { } - - private final void deleteAlias(KeyStore ks, String ksPath, String alias, - char[] pwd) throws KeyStoreException + char[] pwd) throws KeyStoreException { try { @@ -264,10 +153,8 @@ private final void deleteAlias(KeyStore ks, String ksPath, String alias, } } - - private final void addCertificate(KeyStore ks, String ksType, String ksPath, - String alias, char[] pwd, String certPath) throws KeyStoreException + String alias, char[] pwd, String certPath) throws KeyStoreException { try { @@ -284,7 +171,7 @@ private final void addCertificate(KeyStore ks, String ksType, String ksPath, throw new KeyStoreException(msg.toString()); } else if (!ks.containsAlias(alias) - || ks.entryInstanceOf(alias, KeyStore.TrustedCertificateEntry.class)) + || ks.entryInstanceOf(alias, KeyStore.TrustedCertificateEntry.class)) { try (InputStream inStream = new FileInputStream(certPath)) { trustedCert(alias, cf, ks, inStream); @@ -305,11 +192,9 @@ else if (!ks.containsAlias(alias) } } - - private static final KeyStore generateSelfSignedCertificate(KeyStore ks, - String ksType, String ksPath, KeyType keyType, String alias, char[] pwd, String dn, - int validity) throws KeyStoreException + String ksType, String ksPath, KeyType keyType, String alias, char[] pwd, String dn, + int validity) throws KeyStoreException { try { @@ -324,12 +209,11 @@ else if (ks.containsAlias(alias)) throw new KeyStoreException(msg.toString()); } - final Object keypair = newKeyPair(keyType); - final Object subject = newX500Name(dn); - generate(keypair, keyType.keySize); - final PrivateKey privateKey = getPrivateKey(keypair); - final Certificate[] certificateChain = new Certificate[] { - getSelfCertificate(keypair, subject, validity * SEC_IN_DAY) + KeyPair keyPair = newKeyPair(keyType); + PrivateKey privateKey = keyPair.getPrivate(); + X500Name subject = new X500Name(dn); + Certificate[] certificateChain = new Certificate[] { + generateSelfCertificate(keyPair, keyType, subject, validity) }; ks.setKeyEntry(alias, privateKey, pwd, certificateChain); try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) { @@ -343,32 +227,31 @@ else if (ks.containsAlias(alias)) } } - private static Object newKeyPair(KeyType keyType) throws Exception - { - return certKeyGenCons.newInstance(keyType.keyAlgorithm, keyType.signatureAlgorithm); - } - - private static Object newX500Name(String dn) throws Exception - { - return X500NameCons.newInstance(dn); - } - - private static void generate(Object keypair, int keySize) throws Exception - { - Method certAndKeyGenGenerate = certKeyGenClass.getMethod(GENERATE_METHOD, int.class); - certAndKeyGenGenerate.invoke(keypair, keySize); - } - - private static PrivateKey getPrivateKey(Object keypair) throws Exception + private static KeyPair newKeyPair(KeyType keyType) throws Exception { - Method certAndKeyGetPrivateKey = certKeyGenClass.getMethod(GET_PRIVATE_KEY_METHOD); - return (PrivateKey) certAndKeyGetPrivateKey.invoke(keypair); + KeyPairGenerator generator = KeyPairGenerator.getInstance(keyType.keyAlgorithm, BouncyCastleFipsProvider.PROVIDER_NAME); + generator.initialize(keyType.keySize); + return generator.generateKeyPair(); } - private static Certificate getSelfCertificate(Object keypair, Object subject, int days) throws Exception + private static Certificate generateSelfCertificate(KeyPair keyPair, KeyType keyType, X500Name subject, int days) throws Exception { - Method getSelfCertificate = certKeyGenClass.getMethod(GET_SELFSIGNED_CERT_METHOD, X500NameClass, long.class); - return (Certificate) getSelfCertificate.invoke(keypair, subject, days); + BigInteger serial = BigIntegers.createRandomBigInteger(64, new SecureRandom()); + Instant now = Instant.now(); + Date notBeforeDate = Date.from(now); + Date notAfterDate = Date.from(now.plus(days, ChronoUnit.DAYS)); + + JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( + subject, serial, notBeforeDate, notAfterDate, subject, keyPair.getPublic() + ); + ContentSigner signer = new JcaContentSignerBuilder(keyType.signatureAlgorithm) + .setProvider(BouncyCastleFipsProvider.PROVIDER_NAME) + .build(keyPair.getPrivate()); + X509CertificateHolder holder = builder.build(signer); + JcaX509CertificateConverter converter = new JcaX509CertificateConverter() + .setProvider(BouncyCastleFipsProvider.PROVIDER_NAME); + + return converter.getCertificate(holder); } /** @@ -376,7 +259,7 @@ private static Certificate getSelfCertificate(Object keypair, Object subject, in * only if it is self-signed. */ private void trustedCert(String alias, CertificateFactory cf, KeyStore ks, - InputStream in) throws KeyStoreException + InputStream in) throws KeyStoreException { try { @@ -398,8 +281,6 @@ private void trustedCert(String alias, CertificateFactory cf, KeyStore ks, } } - - /** * Check that the issuer and subject DNs match. */ @@ -409,15 +290,11 @@ private boolean isSelfSigned(X509Certificate cert) } } - - /** Prevent instantiation. */ private Platform() { } - - /** * Add the certificate in the specified path to the provided keystore; * creating the keystore with the provided type and path if it doesn't exist. @@ -439,13 +316,11 @@ private Platform() * If an error occurred adding the certificate to the keystore. */ public static void addCertificate(KeyStore ks, String ksType, String ksPath, - String alias, char[] pwd, String certPath) throws KeyStoreException + String alias, char[] pwd, String certPath) throws KeyStoreException { IMPL.addCertificate(ks, ksType, ksPath, alias, pwd, certPath); } - - /** * Delete the specified alias from the provided keystore. * @@ -461,13 +336,11 @@ public static void addCertificate(KeyStore ks, String ksType, String ksPath, * If an error occurred deleting the alias. */ public static void deleteAlias(KeyStore ks, String ksPath, String alias, - char[] pwd) throws KeyStoreException + char[] pwd) throws KeyStoreException { IMPL.deleteAlias(ks, ksPath, alias, pwd); } - - /** * Generate a self-signed certificate using the specified alias, dn string and * validity period. If the keystore does not exist, it will be created using @@ -494,8 +367,8 @@ public static void deleteAlias(KeyStore ks, String ksPath, String alias, * If the self-signed certificate cannot be generated. */ public static void generateSelfSignedCertificate(KeyStore ks, String ksType, - String ksPath, KeyType keyType, String alias, char[] pwd, String dn, int validity) - throws KeyStoreException + String ksPath, KeyType keyType, String alias, char[] pwd, String dn, int validity) + throws KeyStoreException { PlatformIMPL.generateSelfSignedCertificate(ks, ksType, ksPath, keyType, alias, pwd, dn, validity); } @@ -507,8 +380,6 @@ private static class DefaultPlatformIMPL extends PlatformIMPL { } - - /** * Test if a platform java vendor property starts with the specified vendor * string. @@ -539,4 +410,4 @@ public static int computeNumberOfThreads(int minimumValue, float cpuMultiplier) Reject.ifTrue(cpuMultiplier < 0, "Multiplier must be a positive number"); return Math.max(minimumValue, (int)(Runtime.getRuntime().availableProcessors() * cpuMultiplier)); } -} +} \ No newline at end of file diff --git a/opendj-server-legacy/src/messages/org/opends/messages/utility.properties b/opendj-server-legacy/src/messages/org/opends/messages/utility.properties index fcb641f63a..428e26b202 100644 --- a/opendj-server-legacy/src/messages/org/opends/messages/utility.properties +++ b/opendj-server-legacy/src/messages/org/opends/messages/utility.properties @@ -352,14 +352,6 @@ ERR_CERTMGR_FILE_NAME_INVALID_293=The %s is invalid because it is \ null ERR_CERTMGR_VALUE_INVALID_294=The argument %s is invalid because it \ is either null, or has zero length -ERR_CERTMGR_CLASS_NOT_FOUND_295=A security class cannot be found \ -in this JVM because of the following reason: %s -ERR_CERTMGR_SECURITY_296=The security classes could not be \ -initialized because of the following reason: %s -ERR_CERTMGR_NO_METHOD_297=A method needed in the security classes \ -could not be located because of the following reason: %s -ERR_CERTMGR_CERTGEN_NOT_FOUND_298=The CertAndKeyGen security class cannot be \ -found, consider setting -D%s= WARN_EXPORT_LDIF_SET_PERMISSION_FAILED_300=An error occurred while \ setting file permissions for the LDIF file %s: %s ERR_LDIF_READ_ATTR_SKIP_301=Skipping entry %s because the following error \ @@ -440,4 +432,4 @@ ERR_EMBEDDED_SERVER_REBUILD_INDEX_SERVER_IS_RUNNING_341=The rebuild index operat ERR_EMBEDDED_SERVER_LDIF_MANAGEMENT_CONTEXT_342=An error occurred while attempting to \ read the configuration file '%s' ERR_EMBEDDED_SERVER_BUILD_VERSION_343=An error occurred while attempting to \ - retrieve the build version of the directory server: '%s' \ No newline at end of file + retrieve the build version of the directory server: '%s' diff --git a/pom.xml b/pom.xml index efc4a92a2e..9d9d0fdc46 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,9 @@ 3.0.0-SNAPSHOT 2.3.34 4.2.30 + 2.1.1 + 2.1.20 + 2.1.9 11 11 @@ -219,6 +222,22 @@ opendj-server-legacy ${project.version} + + org.bouncycastle + bc-fips + ${bc.fips.version} + + + + org.bouncycastle + bctls-fips + ${bctls.fips.version} + + + org.bouncycastle + bcpkix-fips + ${bcpkix.fips.version} + @@ -687,7 +706,7 @@ [17,) - -Xmx512m --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util.regex=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED + -Xmx512m --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util.regex=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED tomcat11x From 1c8120f1f84528e953c4b7b91c127d588a042277 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 24 Sep 2025 20:40:04 +0300 Subject: [PATCH 02/36] Fix load keystore from file error --- .../forgerock/opendj/cli/ConnectionFactoryProvider.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java index 248fc6d7db..107dd43640 100644 --- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java +++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java @@ -724,12 +724,8 @@ public X509KeyManager getKeyManager(String keyStoreFile) throws KeyStoreExceptio boolean isFips = StaticUtils.isFips(); final String keyStoreType = KeyStore.getDefaultType(); final KeyStore keystore = KeyStore.getInstance(keyStoreType); - if (isFips) { - keystore.load(null, keyStorePIN); - } else { - try (final FileInputStream fos = new FileInputStream(keyStoreFile)) { - keystore.load(fos, keyStorePIN); - } + try (final FileInputStream fos = new FileInputStream(keyStoreFile)) { + keystore.load(fos, keyStorePIN); } if (isFips) { From c28382191f0a3693b4d9ed092f1159a96b56c01f Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 25 Sep 2025 07:03:39 +0300 Subject: [PATCH 03/36] Enable FIPS by setting `org.openidentityplatform.opendj.fips.enable` system property --- .../src/main/java/com/forgerock/opendj/util/StaticUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java b/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java index 16ecb551eb..dd3ce33163 100644 --- a/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java +++ b/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java @@ -13,6 +13,8 @@ * * Copyright 2009-2010 Sun Microsystems, Inc. * Portions copyright 2011-2015 ForgeRock AS. + * Portions copyright 2017-2025 3A Systems, LLC. + * */ package com.forgerock.opendj.util; @@ -781,6 +783,9 @@ public static

P getProvider(final Class

providerClass, f } public static boolean isFips() { + if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.enabled"))) { + return false; + } java.security.Provider[] providers = java.security.Security.getProviders(); for (int i = 0; i < providers.length; i++) { if (providers[i].getName().toLowerCase().contains("fips")) From 61bbed6c6e425887d9acf9b0affabe79b8d80516 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 25 Sep 2025 07:31:43 +0300 Subject: [PATCH 04/36] change org.bouncycastle:bcpkix-fips scope to test for opendj-core --- opendj-core/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/opendj-core/pom.xml b/opendj-core/pom.xml index 4130a3a014..d1d0a09aa1 100644 --- a/opendj-core/pom.xml +++ b/opendj-core/pom.xml @@ -104,6 +104,7 @@ org.bouncycastle bcpkix-fips + test From b6a627663d98905f09664f643e04844eb449b53b Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 25 Sep 2025 08:43:05 +0300 Subject: [PATCH 05/36] revert: change org.bouncycastle:bcpkix-fips scope to test for opendj-core --- opendj-core/pom.xml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/opendj-core/pom.xml b/opendj-core/pom.xml index d1d0a09aa1..b976231bcd 100644 --- a/opendj-core/pom.xml +++ b/opendj-core/pom.xml @@ -92,19 +92,18 @@ jaxb-impl - - - org.bouncycastle - bc-fips - - - org.bouncycastle - bctls-fips - + + + org.bouncycastle + bc-fips + + + org.bouncycastle + bctls-fips + org.bouncycastle bcpkix-fips - test From cc7e4811a5b776509c541a7c31645224fe2e21fc Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 25 Sep 2025 09:24:34 +0300 Subject: [PATCH 06/36] temporary register BC security provider if not using FIPS --- .../opendj/cli/ConnectionFactoryProvider.java | 8 ++++++-- .../com/forgerock/opendj/util/StaticUtils.java | 3 --- .../java/org/opends/server/util/Platform.java | 17 ++++++++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java index 107dd43640..800ed27b3e 100644 --- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java +++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java @@ -724,8 +724,12 @@ public X509KeyManager getKeyManager(String keyStoreFile) throws KeyStoreExceptio boolean isFips = StaticUtils.isFips(); final String keyStoreType = KeyStore.getDefaultType(); final KeyStore keystore = KeyStore.getInstance(keyStoreType); - try (final FileInputStream fos = new FileInputStream(keyStoreFile)) { - keystore.load(fos, keyStorePIN); + if (isFips) { + keystore.load(null, keyStorePIN); + } else { + try (final FileInputStream fos = new FileInputStream(keyStoreFile)) { + keystore.load(fos, keyStorePIN); + } } if (isFips) { diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java b/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java index dd3ce33163..64dbbcc0b4 100644 --- a/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java +++ b/opendj-core/src/main/java/com/forgerock/opendj/util/StaticUtils.java @@ -783,9 +783,6 @@ public static

P getProvider(final Class

providerClass, f } public static boolean isFips() { - if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.enabled"))) { - return false; - } java.security.Provider[] providers = java.security.Security.getProviders(); for (int i = 0; i < providers.length; i++) { if (providers[i].getName().toLowerCase().contains("fips")) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java index 05b5f769eb..7a5c08903b 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java @@ -36,6 +36,8 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Date; + +import com.forgerock.opendj.util.StaticUtils; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -72,7 +74,7 @@ public final class Platform { if (Security.getProvider(BouncyCastleFipsProvider.PROVIDER_NAME) == null) { - Security.addProvider(new BouncyCastleFipsProvider()); + } IMPL = new DefaultPlatformIMPL(); @@ -196,8 +198,13 @@ private static final KeyStore generateSelfSignedCertificate(KeyStore ks, String ksType, String ksPath, KeyType keyType, String alias, char[] pwd, String dn, int validity) throws KeyStoreException { + boolean isFips = StaticUtils.isFips(); try { + if(isFips) + { + Security.addProvider(new BouncyCastleFipsProvider()); + } if (ks == null) { ks = KeyStore.getInstance(ksType); @@ -225,6 +232,13 @@ else if (ks.containsAlias(alias)) { throw new KeyStoreException(ERR_CERTMGR_GEN_SELF_SIGNED_CERT.get(alias, e.getMessage()).toString(), e); } + finally + { + if(!isFips) + { + Security.removeProvider(BouncyCastleFipsProvider.PROVIDER_NAME); + } + } } private static KeyPair newKeyPair(KeyType keyType) throws Exception @@ -251,6 +265,7 @@ private static Certificate generateSelfCertificate(KeyPair keyPair, KeyType keyT JcaX509CertificateConverter converter = new JcaX509CertificateConverter() .setProvider(BouncyCastleFipsProvider.PROVIDER_NAME); + return converter.getCertificate(holder); } From 78e89ea8d1da216c90c94ac937ca6245515462a5 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 25 Sep 2025 09:38:18 +0300 Subject: [PATCH 07/36] temporary register BC security provider if not using FIPS --- .../src/main/java/org/opends/server/util/Platform.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java index 7a5c08903b..36f01cfe85 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java @@ -201,7 +201,7 @@ private static final KeyStore generateSelfSignedCertificate(KeyStore ks, boolean isFips = StaticUtils.isFips(); try { - if(isFips) + if(!isFips) { Security.addProvider(new BouncyCastleFipsProvider()); } From 28b4cf0eda7c21c319a50b55c6d4a9d8ddf4c495 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 25 Sep 2025 19:47:09 +0300 Subject: [PATCH 08/36] restore minimum java supported version 11 --- .../src/main/java/org/opends/server/util/Platform.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java index 36f01cfe85..9289f99dc0 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/util/Platform.java @@ -68,15 +68,10 @@ public final class Platform private static final PlatformIMPL IMPL; /** The minimum java supported version. */ - public static final String JAVA_MINIMUM_VERSION_NUMBER = "8"; + public static final String JAVA_MINIMUM_VERSION_NUMBER = "11"; static { - if (Security.getProvider(BouncyCastleFipsProvider.PROVIDER_NAME) == null) - { - - } - IMPL = new DefaultPlatformIMPL(); } From 0724a10bbce43e955998b5b1488771fd31995c82 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 6 Oct 2025 06:46:20 +0300 Subject: [PATCH 09/36] Improve ReplicationDomainTest stability --- .../server/replication/service/ReplicationDomainTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index 50a6728e88..3aa3ffed85 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -302,8 +302,7 @@ private ReplicationServer createReplicationServer(int serverId, new ReplServerFakeConfiguration(replicationPort, dirName, 0, serverId, 0, windowSize, replServers)); } - private void disable(ReplicationDomain... domains) - { + private void disable(ReplicationDomain... domains) throws InterruptedException { for (ReplicationDomain domain : domains) { if (domain != null) @@ -311,6 +310,7 @@ private void disable(ReplicationDomain... domains) domain.disableService(); } } + Thread.sleep(2000); //wait for replication domain to shut down } @DataProvider(name = "exportAndImportData") From 3eabdb578660befee7dbef29554dc5db4b830132 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 6 Oct 2025 11:57:00 +0300 Subject: [PATCH 10/36] Add FIPS test to GitHub build workflow --- .github/workflows/build.yml | 56 +++++++++++++++++++ .../opendj/util/FipsStaticUtils.java | 3 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8570f0630..ed53e27920 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,62 @@ jobs: opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000 opendj-server-legacy/target/package/opendj/bin/stop-ds rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs} + + - name: Test on Unix FIPS + if: runner.os != 'Windows' + run: | + export OPENDJ_JAVA_ARGS="-server -Xmx512m -Dorg.openidentityplatform.opendj.fips.enabled=true" + echo password > /tmp/opendj.keystore.pin + + keytool -genkey -alias server-cert -keyalg rsa \ + -dname "CN=example.com,O=OpenDJ RSA Self-Signed Certificate" \ + -keystore /tmp/opendj.bcfks -storetype BCFKS -validity 3650 -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \ + -keypass:file /tmp/opendj.keystore.pin -storepass:file /tmp/opendj.keystore.pin -keysize 2048 -sigalg SHA256WITHRSA + + keytool -selfcert -alias server-cert -keystore /tmp/opendj.bcfks \ + -storetype BCFKS -validity 3650 -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \ + -storepass:file /tmp/opendj.keystore.pin + + keytool -genkey -alias admin-cert -keyalg rsa \ + -dname "CN=example.com,O=Administration Connector RSA Self-Signed Certificate" \ + -keystore /tmp/opendj.bcfks -storetype BCFKS -validity 3650 -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \ + -keypass:file /tmp/opendj.keystore.pin -storepass:file /tmp/opendj.keystore.pin -keysize 2048 -sigalg SHA256WITHRSA + + keytool -selfcert -alias admin-cert -keystore /tmp/opendj.bcfks \ + -storetype BCFKS -validity 3650 -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath ./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bc-fips.jar:./opendj-server-legacy/target/package/opendj/lib/org.bouncycastle.bcpkix-fips.jar \ + -storepass:file /tmp/opendj.keystore.pin + + echo "useBcfksKeystore=/tmp/opendj.bcfks + keyStorePasswordFile=/tmp/opendj.keystore.pin" > /tmp/opendj-setup.properties.bcfks + + opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 \ + --enableStartTLS --certNickname admin-cert --rootUserDN "cn=Directory Manager" --rootUserPassword password \ + --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt \ + --propertiesFilePath /tmp/opendj-setup.properties.bcfks --doNotStart + + opendj-server-legacy/target/package/opendj/bin/start-ds + + opendj-server-legacy/target/package/opendj/bin/status --bindDN "cn=Directory Manager" --bindPassword password --trustAll + opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 + opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000 + opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=example2 --type je --set=base-dn:dc=example2,dc=com --set=enabled:true --no-prompt --trustAll + opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example2,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template + opendj-server-legacy/target/package/opendj/bin/stop-ds + opendj-server-legacy/target/package/opendj/bin/import-ldif --offline --ldifFile /tmp/test.ldif --backendID=example2 + opendj-server-legacy/target/package/opendj/bin/rebuild-index --offline --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll + opendj-server-legacy/target/package/opendj/bin/start-ds + opendj-server-legacy/target/package/opendj/bin/rebuild-index --bindDN "cn=Directory Manager" --bindPassword password --baseDN "dc=example2,dc=com" --rebuildAll --trustAll + opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000 + opendj-server-legacy/target/package/opendj/bin/stop-ds + rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs} - name: Test LDAP in Cassandra if: runner.os == 'Linux' run: | diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java b/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java index e7daede306..024a9854bf 100644 --- a/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java +++ b/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java @@ -21,7 +21,8 @@ public class FipsStaticUtils { private static final String BC_FIPS_PROVIDER_CLASS_NAME = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider"; public static void registerBcProvider() { - if (!StaticUtils.isFips()) { + + if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.enabled"))) { return; } From da48ba455e762158bf68f4ef5a0aaf5e7bb5f9d3 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 6 Oct 2025 18:35:42 +0300 Subject: [PATCH 11/36] Improve ReplicationBroker concurrency stability --- .../service/ReplicationBroker.java | 24 +++++++++---------- .../service/ReplicationDomainTest.java | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java index 9ba8932df0..0ac2c40e89 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java @@ -165,7 +165,7 @@ public void toString(StringBuilder sb) } private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); - private volatile boolean shutdown; + private AtomicBoolean shutdown = new AtomicBoolean(); private final Object startStopLock = new Object(); private volatile ReplicationDomainCfg config; /** String reported under CSN=monitor when there is no connected RS. */ @@ -264,7 +264,7 @@ public ReplicationBroker(ReplicationDomain replicationDomain, this.replSessionSecurity = replSessionSecurity; this.rcvWindow = getMaxRcvWindow(); this.halfRcvWindow = rcvWindow / 2; - this.shutdown = true; + this.shutdown.set(true); /* * Only create a monitor if there is a replication domain (this is not the @@ -280,11 +280,11 @@ public void start() { synchronized (startStopLock) { - if (!shutdown) + if (!shutdown.get()) { return; } - shutdown = false; + shutdown.set(false); this.rcvWindow = getMaxRcvWindow(); connectAsDataServer(); } @@ -2159,7 +2159,7 @@ private void reStart(Session failingSession, boolean infiniteTry) // Synchronize inside the loop in order to allow shutdown. synchronized (startStopLock) { - if (rs.isConnected() || shutdown) + if (rs.isConnected() || shutdown.get()) { break; } @@ -2238,7 +2238,7 @@ private boolean publish(ReplicationMsg msg, boolean recoveryMsg, { boolean done = false; - while (!done && !shutdown) + while (!done && !shutdown.get()) { if (connectionError) { @@ -2420,7 +2420,7 @@ ReplicationMsg receive(boolean reconnectToTheBestRS, boolean reconnectOnFailure, boolean returnOnTopoChange) throws SocketTimeoutException { - while (!shutdown) + while (!shutdown.get()) { ConnectedRS rs = connectedRS.get(); if (!rs.isConnected()) @@ -2576,7 +2576,7 @@ else if (msg instanceof MonitorMsg) { logger.traceException(e); - if (!shutdown) + if (!shutdown.get()) { if (rs.session == null || !rs.session.closeInitiated()) { @@ -2660,19 +2660,19 @@ public synchronized void updateWindowAfterReplay() /** Stop the server. */ public void stop() { - if (logger.isTraceEnabled() && !shutdown) + if (logger.isTraceEnabled() && !shutdown.get()) { debugInfo("is stopping and will close the connection to RS(" + getRsServerId() + ")"); } synchronized (startStopLock) { - if (shutdown) + if (shutdown.get()) { return; } domain.publishReplicaOfflineMsg(); - shutdown = true; + shutdown.set(true); setConnectedRS(ConnectedRS.stopped()); stopRSHeartBeatMonitoring(); stopChangeTimeHeartBeatPublishing(); @@ -3225,7 +3225,7 @@ public void setRecoveryRequired(boolean b) */ boolean shuttingDown() { - return shutdown; + return shutdown.get(); } /** diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index 3aa3ffed85..abe9992cc0 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -310,7 +310,6 @@ private void disable(ReplicationDomain... domains) throws InterruptedException { domain.disableService(); } } - Thread.sleep(2000); //wait for replication domain to shut down } @DataProvider(name = "exportAndImportData") From 6920577cf9c9b6b2cd8a40588b500d9456c443ea Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 9 Oct 2025 10:16:22 +0300 Subject: [PATCH 12/36] Use PKCS12 as successor to PKCS11 in FIPS --- .../com/forgerock/opendj/cli/ConnectionFactoryProvider.java | 2 +- .../main/java/org/forgerock/opendj/ldap/TrustManagers.java | 4 ++-- .../java/org/opends/server/tools/SSLConnectionFactory.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java index 800ed27b3e..056ea7c2ef 100644 --- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java +++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java @@ -852,7 +852,7 @@ public TrustManager getTrustManager() throws IOException, GeneralSecurityExcepti } if (isFips) { - return TrustManagers.checkUsingPkcs11TrustStore(); + return TrustManagers.checkUsingPkcs12TrustStore(); } return tm; diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java index 803c2a0f54..d8624bc64e 100644 --- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java +++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java @@ -522,8 +522,8 @@ public static X509TrustManager checkUsingTrustStore(final String file, final cha throw new NoSuchAlgorithmException(); } - public static X509TrustManager checkUsingPkcs11TrustStore() throws GeneralSecurityException, IOException { - final KeyStore keyStore = KeyStore.getInstance("PKCS11"); + public static X509TrustManager checkUsingPkcs12TrustStore() throws GeneralSecurityException, IOException { + final KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(null, null); final TrustManagerFactory tmf = diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/SSLConnectionFactory.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/SSLConnectionFactory.java index 9be9cf47ac..b8f72a931c 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/tools/SSLConnectionFactory.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/SSLConnectionFactory.java @@ -122,7 +122,7 @@ public void init(boolean trustAll, String keyStorePath, trustManagers = blindTrustProvider.getTrustManagers(); } else if (trustStorePath == null) { if (isFips()) { - TrustManager tm = TrustManagers.checkUsingPkcs11TrustStore(); + TrustManager tm = TrustManagers.checkUsingPkcs12TrustStore(); trustManagers = new TrustManager[] { tm }; } else { trustManagers = PromptTrustManager.getTrustManagers(); From bea7b8842a4ea4feb5223167f0b9c1d445bcd2e7 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 9 Oct 2025 12:45:26 +0300 Subject: [PATCH 13/36] Set `org.openidentityplatform.opendj.fips.register` property to enabel FIPS in the non-strict mde --- .github/workflows/build.yml | 2 +- .../main/java/com/forgerock/opendj/util/FipsStaticUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed53e27920..18c943a18d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,7 +74,7 @@ jobs: - name: Test on Unix FIPS if: runner.os != 'Windows' run: | - export OPENDJ_JAVA_ARGS="-server -Xmx512m -Dorg.openidentityplatform.opendj.fips.enabled=true" + export OPENDJ_JAVA_ARGS="-server -Xmx512m -Dorg.openidentityplatform.opendj.fips.register=true" echo password > /tmp/opendj.keystore.pin keytool -genkey -alias server-cert -keyalg rsa \ diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java b/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java index 024a9854bf..81cb1af031 100644 --- a/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java +++ b/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java @@ -22,7 +22,7 @@ public class FipsStaticUtils { public static void registerBcProvider() { - if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.enabled"))) { + if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.register"))) { return; } From b86736b29a2eeac5df9c32c0c9b2af2f0f131b0e Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 9 Oct 2025 13:39:16 +0300 Subject: [PATCH 14/36] Print thread dump on a test failure --- opendj-server-legacy/pom.xml | 1 + .../java/org/opends/server/TestListener.java | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index d1c6d3309a..e360fca772 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -1274,6 +1274,7 @@ true false false + 600000 @{argLine} false diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java index 461d6158d2..cf4fcc6001 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java @@ -13,7 +13,7 @@ * * Copyright 2008 Sun Microsystems, Inc. * Portions Copyright 2013-2016 ForgeRock AS. - * Portions Copyright 2023 3A Systems, LLC. + * Portions Copyright 2023-2025 3A Systems, LLC. */ package org.opends.server; @@ -23,9 +23,13 @@ import java.io.IOException; import java.io.PrintStream; import java.lang.annotation.Annotation; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -155,10 +159,18 @@ public TestListener() throws Exception { @Override public void onStart(ITestContext testContext) { super.onStart(testContext); - + if (testContext.getAllTestMethods().length>0) { TestCaseUtils.setTestName(testContext.getAllTestMethods()[0].getInstance().getClass().getName()); } + long testTimeout = 0; + try { + testTimeout = Long.parseLong(System.getProperty("org.opends.test.timeout", "0")); + } catch (NumberFormatException ignored) {} + + for (int i = 0; i < testContext.getAllTestMethods().length; i++) { + testContext.getAllTestMethods()[i].setTimeOut(testTimeout); + } // Delete the previous report if it's there. new File(testContext.getOutputDirectory(), REPORT_FILE_NAME).delete(); @@ -335,6 +347,7 @@ private void writeReportToScreen(File reportFile) { @Override public void onTestStart(ITestResult tr) { super.onTestStart(tr); + originalSystemOut.println("-- Executing test: " + tr.getMethod()); enforceTestClassTypeAndAnnotations(tr); checkForInterleavedBetweenClasses(tr); @@ -358,6 +371,21 @@ public void onTestSuccess(ITestResult tr) { public void onTestFailure(ITestResult tr) { super.onTestFailure(tr); reportTestFailed(tr); + printThreadDump(); + } + + private void printThreadDump() { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); + + originalSystemErr.println("--- Java Thread Dump ---"); + originalSystemErr.println("Timestamp: " + LocalDateTime.now()); + originalSystemErr.println("------------------------"); + + for (ThreadInfo threadInfo : threadInfos) { + originalSystemErr.println(threadInfo.toString()); + } + originalSystemErr.println("------------------------"); } private void reportTestFailed(ITestResult tr) From 75c2eab3ba15c61b80912ca52eb7a3369d9aabcb Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 9 Oct 2025 17:06:18 +0300 Subject: [PATCH 15/36] print thread dump in test timer --- .../java/org/opends/server/TestListener.java | 16 +--------------- .../java/org/opends/server/util/TestTimer.java | 5 +++++ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java index cf4fcc6001..8d013bf001 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java @@ -23,13 +23,9 @@ import java.io.IOException; import java.io.PrintStream; import java.lang.annotation.Annotation; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; -import java.lang.management.ThreadMXBean; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -375,17 +371,7 @@ public void onTestFailure(ITestResult tr) { } private void printThreadDump() { - ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); - ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); - - originalSystemErr.println("--- Java Thread Dump ---"); - originalSystemErr.println("Timestamp: " + LocalDateTime.now()); - originalSystemErr.println("------------------------"); - - for (ThreadInfo threadInfo : threadInfos) { - originalSystemErr.println(threadInfo.toString()); - } - originalSystemErr.println("------------------------"); + originalSystemErr.println(TestCaseUtils.generateThreadDump()); } private void reportTestFailed(ITestResult tr) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java b/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java index ceeaa39a98..9095b5b770 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java @@ -18,7 +18,10 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import org.forgerock.i18n.LocalizableMessage; +import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.util.Reject; +import org.opends.server.TestCaseUtils; /** * Timer useful for testing: it helps to write loops that repeatedly runs code until some condition @@ -153,6 +156,7 @@ public TestTimer toTimer() /** A {@link TestTimer} that sleeps in steps and sleeps at maximum {@code nbSteps * sleepTimes}. */ public static class SteppingTimer implements TestTimer { + private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private final long sleepTime; private final long totalNbSteps; private long nbStepsRemaining; @@ -204,6 +208,7 @@ public R repeatUntilSuccess(Callable callable) throws Exception, Interrup { if (hasTimedOutNoSleep()) { + logger.info(LocalizableMessage.raw("failed to wait for" + callable + "\n" + TestCaseUtils.generateThreadDump())); throw e; } } From 9f111a26614181090b01da529ae7ca822110aa8a Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 10 Oct 2025 10:56:09 +0300 Subject: [PATCH 16/36] Extend thread dump for testing --- .../java/org/opends/server/TestCaseUtils.java | 78 +++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) 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 1e4763c2e1..4a2677a648 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 @@ -45,11 +45,12 @@ import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; +import java.lang.management.LockInfo; import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.net.BindException; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; @@ -1927,20 +1928,71 @@ public static void generateThreadDump(String id) public static String generateThreadDump() { final StringBuilder dump = new StringBuilder(); final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); - final ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); for (ThreadInfo threadInfo : threadInfos) { - dump.append('"'); - dump.append(threadInfo.getThreadName()); - dump.append("\" "); - final Thread.State state = threadInfo.getThreadState(); - dump.append("\n java.lang.Thread.State: "); - dump.append(state); - final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace(); - for (final StackTraceElement stackTraceElement : stackTraceElements) { - dump.append("\n at "); - dump.append(stackTraceElement); + dump.append("\"" + threadInfo.getThreadName() + "\"" + + (threadInfo.isDaemon() ? " daemon" : "") + + " prio=" + threadInfo.getPriority() + + " Id=" + threadInfo.getThreadId() + " " + + threadInfo.getThreadState()); + if (threadInfo.getLockName() != null) { + dump.append(" on " + threadInfo.getLockName()); + } + if (threadInfo.getLockOwnerName() != null) { + dump.append(" owned by \"" + threadInfo.getLockOwnerName() + + "\" Id=" + threadInfo.getLockOwnerId()); + } + if (threadInfo.isSuspended()) { + dump.append(" (suspended)"); + } + if (threadInfo.isInNative()) { + dump.append(" (in native)"); + } + dump.append('\n'); + StackTraceElement[] stackTrace = threadInfo.getStackTrace(); + int i = 0; + for (; i < stackTrace.length; i++) { + StackTraceElement ste = stackTrace[i]; + dump.append("\tat " + ste.toString()); + dump.append('\n'); + if (i == 0 && threadInfo.getLockInfo() != null) { + Thread.State ts = threadInfo.getThreadState(); + switch (ts) { + case BLOCKED: + dump.append("\t- blocked on " + threadInfo.getLockInfo()); + dump.append('\n'); + break; + case WAITING: + case TIMED_WAITING: + dump.append("\t- waiting on " + threadInfo.getLockInfo()); + dump.append('\n'); + break; + default: + } } - dump.append("\n\n"); + + for (MonitorInfo mi : threadInfo.getLockedMonitors()) { + if (mi.getLockedStackDepth() == i) { + dump.append("\t- locked " + mi); + dump.append('\n'); + } + } + } + if (i < stackTrace.length) { + dump.append("\t..."); + dump.append('\n'); + } + + LockInfo[] locks = threadInfo.getLockedSynchronizers(); + if (locks.length > 0) { + dump.append("\n\tNumber of locked synchronizers = " + locks.length); + dump.append('\n'); + for (LockInfo li : locks) { + dump.append("\t- " + li); + dump.append('\n'); + } + } + dump.append('\n'); } return dump.toString(); } From 3f659aabc0d1fb6fdde9204ff0ba2155f870367e Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 10 Oct 2025 12:06:10 +0300 Subject: [PATCH 17/36] Register the FIPS provider if using BCFKS keystore or truststore --- .../main/java/com/forgerock/opendj/util/FipsStaticUtils.java | 5 ++++- .../server/extensions/FileBasedKeyManagerProvider.java | 4 ++++ .../server/extensions/FileBasedTrustManagerProvider.java | 4 ++++ .../src/main/java/org/opends/server/tools/InstallDS.java | 5 +++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java b/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java index 81cb1af031..7f2758e761 100644 --- a/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java +++ b/opendj-core/src/main/java/com/forgerock/opendj/util/FipsStaticUtils.java @@ -21,8 +21,11 @@ public class FipsStaticUtils { private static final String BC_FIPS_PROVIDER_CLASS_NAME = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider"; public static void registerBcProvider() { + registerBcProvider(false); + } + public static void registerBcProvider(boolean force) { - if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.register"))) { + if(!"true".equals(System.getProperty("org.openidentityplatform.opendj.fips.register")) && !force) { return; } diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedKeyManagerProvider.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedKeyManagerProvider.java index 749b43f410..0f62462fe8 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedKeyManagerProvider.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedKeyManagerProvider.java @@ -32,6 +32,7 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; +import com.forgerock.opendj.util.FipsStaticUtils; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2; import org.forgerock.i18n.LocalizableMessageDescriptor.Arg3; @@ -259,6 +260,9 @@ private String getKeyStoreType(FileBasedKeyManagerProviderCfg cfg, ConfigChangeR { try { + if(cfg.getKeyStoreType().equals("BCFKS")) { + FipsStaticUtils.registerBcProvider(true); + } KeyStore.getInstance(cfg.getKeyStoreType()); return cfg.getKeyStoreType(); } diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedTrustManagerProvider.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedTrustManagerProvider.java index f82d64c7ef..32b4ad7295 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedTrustManagerProvider.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/FileBasedTrustManagerProvider.java @@ -16,6 +16,7 @@ */ package org.opends.server.extensions; +import com.forgerock.opendj.util.FipsStaticUtils; import org.forgerock.i18n.LocalizableMessage; import java.io.File; import java.io.FileInputStream; @@ -204,6 +205,9 @@ private String getTrustStoreType(FileBasedTrustManagerProviderCfg cfg, ConfigCha final String trustStoreType = cfg.getTrustStoreType(); if (trustStoreType != null) { + if(trustStoreType.equals("BCFKS")) { + FipsStaticUtils.registerBcProvider(true); + } try { KeyStore.getInstance(trustStoreType); diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java index f5bf1a4f8b..2594512906 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java @@ -40,6 +40,7 @@ import java.util.LinkedList; import java.util.List; +import com.forgerock.opendj.util.FipsStaticUtils; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0; import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; @@ -326,6 +327,10 @@ private int execute(String[] args) return InstallReturnCode.ERROR_LICENSE_NOT_ACCEPTED.getReturnCode(); } + if(argParser.useBcfksArg.isPresent()) { + FipsStaticUtils.registerBcProvider(true); + } + final UserData uData = new UserData(); InstallReturnCode fillUserDataRC; try From 1c01649e56512733af24384af445aab2dccc1e57 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 10 Oct 2025 12:10:15 +0300 Subject: [PATCH 18/36] revert the shutdown flag to volatile in ReplicationBroker due to tests failures --- .../service/ReplicationBroker.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java index 0ac2c40e89..9ba8932df0 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java @@ -165,7 +165,7 @@ public void toString(StringBuilder sb) } private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); - private AtomicBoolean shutdown = new AtomicBoolean(); + private volatile boolean shutdown; private final Object startStopLock = new Object(); private volatile ReplicationDomainCfg config; /** String reported under CSN=monitor when there is no connected RS. */ @@ -264,7 +264,7 @@ public ReplicationBroker(ReplicationDomain replicationDomain, this.replSessionSecurity = replSessionSecurity; this.rcvWindow = getMaxRcvWindow(); this.halfRcvWindow = rcvWindow / 2; - this.shutdown.set(true); + this.shutdown = true; /* * Only create a monitor if there is a replication domain (this is not the @@ -280,11 +280,11 @@ public void start() { synchronized (startStopLock) { - if (!shutdown.get()) + if (!shutdown) { return; } - shutdown.set(false); + shutdown = false; this.rcvWindow = getMaxRcvWindow(); connectAsDataServer(); } @@ -2159,7 +2159,7 @@ private void reStart(Session failingSession, boolean infiniteTry) // Synchronize inside the loop in order to allow shutdown. synchronized (startStopLock) { - if (rs.isConnected() || shutdown.get()) + if (rs.isConnected() || shutdown) { break; } @@ -2238,7 +2238,7 @@ private boolean publish(ReplicationMsg msg, boolean recoveryMsg, { boolean done = false; - while (!done && !shutdown.get()) + while (!done && !shutdown) { if (connectionError) { @@ -2420,7 +2420,7 @@ ReplicationMsg receive(boolean reconnectToTheBestRS, boolean reconnectOnFailure, boolean returnOnTopoChange) throws SocketTimeoutException { - while (!shutdown.get()) + while (!shutdown) { ConnectedRS rs = connectedRS.get(); if (!rs.isConnected()) @@ -2576,7 +2576,7 @@ else if (msg instanceof MonitorMsg) { logger.traceException(e); - if (!shutdown.get()) + if (!shutdown) { if (rs.session == null || !rs.session.closeInitiated()) { @@ -2660,19 +2660,19 @@ public synchronized void updateWindowAfterReplay() /** Stop the server. */ public void stop() { - if (logger.isTraceEnabled() && !shutdown.get()) + if (logger.isTraceEnabled() && !shutdown) { debugInfo("is stopping and will close the connection to RS(" + getRsServerId() + ")"); } synchronized (startStopLock) { - if (shutdown.get()) + if (shutdown) { return; } domain.publishReplicaOfflineMsg(); - shutdown.set(true); + shutdown = true; setConnectedRS(ConnectedRS.stopped()); stopRSHeartBeatMonitoring(); stopChangeTimeHeartBeatPublishing(); @@ -3225,7 +3225,7 @@ public void setRecoveryRequired(boolean b) */ boolean shuttingDown() { - return shutdown.get(); + return shutdown; } /** From ec601358677ce73460562f26d5053a1c0b813987 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 10 Oct 2025 12:19:17 +0300 Subject: [PATCH 19/36] remove `org.openidentityplatform.opendj.fips.register` property from FIPS test --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 18c943a18d..8e0f3df70c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,7 +74,7 @@ jobs: - name: Test on Unix FIPS if: runner.os != 'Windows' run: | - export OPENDJ_JAVA_ARGS="-server -Xmx512m -Dorg.openidentityplatform.opendj.fips.register=true" + export OPENDJ_JAVA_ARGS="-server -Xmx512m" echo password > /tmp/opendj.keystore.pin keytool -genkey -alias server-cert -keyalg rsa \ From 284144632408927969b1b51e04b920dc02bdb22c Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 13 Oct 2025 09:38:49 +0300 Subject: [PATCH 20/36] Fix SchemaReplicationTest.pushSchemaFilesChange: wait for schema file to exist --- .../org/opends/server/replication/SchemaReplicationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java index b778de7368..1278ea154f 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java @@ -234,6 +234,7 @@ public void pushSchemaFilesChange() throws Exception @Override public void call() throws Exception { + assertTrue(schemaFile.exists()); String fileStr = readAsString(schemaFile); assertTrue(fileStr.contains(stateStr), "The Schema persistentState (CSN:" + stateStr + ") has not been saved to " + schemaFile + " : " + fileStr); From 76f0a1768b64dd33efda09196b3cc94ace09d619 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 13 Oct 2025 13:38:38 +0300 Subject: [PATCH 21/36] Additional test logging to diagnose StringBuilder to StringBuffer for the FakeReplicationDomain because of thread safety --- .../replication/service/FakeReplicationDomain.java | 6 +++--- .../replication/service/ReplicationDomainTest.java | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java index 276b039ff6..dc11a9b7a6 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java @@ -42,8 +42,8 @@ public class FakeReplicationDomain extends ReplicationDomain private BlockingQueue queue; /** A string that will be exported should exportBackend be called. */ private String exportString; - /** A StringBuilder that will be used to build a new String should the import be called. */ - private StringBuilder importString; + /** A StringBuffer that will be used to build a new String should the import be called. */ + private StringBuffer importString; private int exportedEntryCount; private FakeReplicationDomain(DN baseDN, int serverID, @@ -82,7 +82,7 @@ public FakeReplicationDomain(DN baseDN, int serverID, FakeReplicationDomain(DN baseDN, int serverID, SortedSet replicationServers, long heartbeatInterval, - String exportString, StringBuilder importString, int exportedEntryCount) + String exportString, StringBuffer importString, int exportedEntryCount) throws ConfigException { this(baseDN, serverID, replicationServers, 100, heartbeatInterval, 1); diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index abe9992cc0..77ef76fd95 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -18,6 +18,7 @@ package org.opends.server.replication.service; import static org.opends.messages.ReplicationMessages.*; +import static org.opends.server.TestCaseUtils.generateThreadDump; import static org.opends.server.util.CollectionUtils.*; import static org.testng.Assert.*; @@ -28,6 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.server.TestCaseUtils; import org.opends.server.backends.task.Task; @@ -347,7 +349,7 @@ public void exportAndImport(int serverId1, int serverId2) throws Exception domain1 = new FakeReplicationDomain( testService, serverId1, servers, 0, exportedData, null, ENTRYCOUNT); - StringBuilder importedData = new StringBuilder(); + StringBuffer importedData = new StringBuffer(); domain2 = new FakeReplicationDomain( testService, serverId2, servers, 0, null, importedData, 0); @@ -418,7 +420,7 @@ public void exportAndImportAcross2ReplServers() throws Exception domain1 = new FakeReplicationDomain( testService, 1, servers1, 0, exportedData, null, ENTRYCOUNT); - StringBuilder importedData = new StringBuilder(); + StringBuffer importedData = new StringBuffer(); domain2 = new FakeReplicationDomain( testService, 2, servers2, 0, null, importedData, 0); @@ -444,18 +446,22 @@ private String buildExportedData(final int ENTRYCOUNT) return sb.toString(); } - private void waitEndExport(String exportedData, StringBuilder importedData) throws Exception + private void waitEndExport(String exportedData, StringBuffer importedData) throws Exception { int count = 0; while (importedData.length() < exportedData.length() && count < 500*5) { + if(count % 50 == 0) { //capture thread dump on start and every 5 seconds + logger.info(LocalizableMessage.raw("waitEndExport: thread dump on count=" + count)); + logger.info(LocalizableMessage.raw(generateThreadDump())); + } count ++; Thread.sleep(100); } } private void assertExportSucessful(ReplicationDomain domain1, - ReplicationDomain domain2, String exportedData, StringBuilder importedData) + ReplicationDomain domain2, String exportedData, StringBuffer importedData) { assertEquals(getLeftEntryCount(domain2), 0, "Wrong LeftEntryCount for export"); assertEquals(getLeftEntryCount(domain1), 0, "Wrong LeftEntryCount for import"); From deda956145bb4fbcb7dd58d27b8f208e258a1afd Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Tue, 14 Oct 2025 10:54:48 +0300 Subject: [PATCH 22/36] Close ServerSocket on BindException --- .../test/java/org/opends/server/TestCaseUtils.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 4a2677a648..bd09efaa33 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 @@ -743,11 +743,20 @@ public synchronized static ServerSocket bindFreePort() throws IOException { for (; port > 1024;) { + ServerSocket res=null; try { - return bindPort(port--); + res=bindPort(port--); + return res; } - catch (BindException e){} + catch (BindException e){ + if (res!=null) { + try { + res.close(); + } catch (IOException ex) {} + } + res=null; + } } throw new BindException("Unable to bind to a free port"); } From e50fc0faba6d3eedfa2c253edfb85ce3c14970e8 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 14 Oct 2025 19:23:17 +0300 Subject: [PATCH 23/36] enable trace logging for a failed test --- opendj-server-legacy/pom.xml | 1 + .../server/replication/service/ReplicationDomainTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index e360fca772..eafd81ca43 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -1275,6 +1275,7 @@ false false 600000 + _global:enabled @{argLine} false diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index 77ef76fd95..e457081301 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -451,7 +451,7 @@ private void waitEndExport(String exportedData, StringBuffer importedData) throw int count = 0; while (importedData.length() < exportedData.length() && count < 500*5) { - if(count % 50 == 0) { //capture thread dump on start and every 5 seconds + if(count % 100 == 0) { //capture thread dump on start and every 10 seconds logger.info(LocalizableMessage.raw("waitEndExport: thread dump on count=" + count)); logger.info(LocalizableMessage.raw(generateThreadDump())); } From f7ac4008a0075b8bb35a88d6fc1c211598911aea Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 15 Oct 2025 10:27:32 +0300 Subject: [PATCH 24/36] trace on replication tests failure --- opendj-server-legacy/pom.xml | 2 +- .../src/test/java/org/opends/server/TestListener.java | 9 ++++++++- .../replication/service/ReplicationDomainTest.java | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index eafd81ca43..be473df156 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -1275,7 +1275,7 @@ false false 600000 - _global:enabled + org\.opends\.server\.replication\..* @{argLine} false diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java index 8d013bf001..51190929d0 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java @@ -167,7 +167,13 @@ public void onStart(ITestContext testContext) { for (int i = 0; i < testContext.getAllTestMethods().length; i++) { testContext.getAllTestMethods()[i].setTimeOut(testTimeout); } - + if(System.getProperty("org.opends.test.trace.pattern") != null) { + String tracePattern = System.getProperty("org.opends.test.trace.pattern"); + if(testContext.getAllTestMethods()[0].getInstance().getClass().getName().matches(tracePattern)) { + System.setProperty("org.opends.server.debug.target.1", "_global:enabled"); + } + } + // Delete the previous report if it's there. new File(testContext.getOutputDirectory(), REPORT_FILE_NAME).delete(); } @@ -187,6 +193,7 @@ public void onFinish(ITestContext testContext) { originalSystemErr.println("check state: "+paths.unitRoot); } } + System.clearProperty("org.opends.server.debug.target.1"); } @Override diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index e457081301..33decab742 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -452,8 +452,8 @@ private void waitEndExport(String exportedData, StringBuffer importedData) throw while (importedData.length() < exportedData.length() && count < 500*5) { if(count % 100 == 0) { //capture thread dump on start and every 10 seconds - logger.info(LocalizableMessage.raw("waitEndExport: thread dump on count=" + count)); - logger.info(LocalizableMessage.raw(generateThreadDump())); + logger.debug(LocalizableMessage.raw("waitEndExport: thread dump on count=" + count)); + logger.debug(LocalizableMessage.raw(generateThreadDump())); } count ++; Thread.sleep(100); From 9f0d385f2fab3fb00b69c839571109fc141e631d Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 15 Oct 2025 11:57:22 +0300 Subject: [PATCH 25/36] fix ArrayIndexOutOfBoundsException in TestListener --- .../src/test/java/org/opends/server/TestListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java index 51190929d0..5e2a03e030 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java @@ -167,7 +167,7 @@ public void onStart(ITestContext testContext) { for (int i = 0; i < testContext.getAllTestMethods().length; i++) { testContext.getAllTestMethods()[i].setTimeOut(testTimeout); } - if(System.getProperty("org.opends.test.trace.pattern") != null) { + if(System.getProperty("org.opends.test.trace.pattern") != null && testContext.getAllTestMethods().length > 0) { String tracePattern = System.getProperty("org.opends.test.trace.pattern"); if(testContext.getAllTestMethods()[0].getInstance().getClass().getName().matches(tracePattern)) { System.setProperty("org.opends.server.debug.target.1", "_global:enabled"); From 7c78d030a2eba4e2c1379e0a0cfbe4a865afd029 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 15 Oct 2025 14:06:37 +0300 Subject: [PATCH 26/36] trace only for org.opends.server.replication.service package --- opendj-server-legacy/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index be473df156..0c91ff5f40 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -1275,7 +1275,7 @@ false false 600000 - org\.opends\.server\.replication\..* + org\.opends\.server\.replication\.service\..* @{argLine} false From a680b36ddcbc6d57944aad6a5214b5dcb0e51568 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 16 Oct 2025 10:23:55 +0300 Subject: [PATCH 27/36] shutdown server even if a test was skipped or failed --- .../test/java/org/opends/server/DirectoryServerTestCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java index b456deef22..c96c16648c 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java @@ -48,7 +48,7 @@ public final void suppressOutput() { TestCaseUtils.suppressOutput(); } - @AfterSuite + @AfterSuite(alwaysRun = true) public final void shutdownServer() { TestCaseUtils.shutdownServer(LocalizableMessage.raw("The current test suite has finished.")); TestCaseUtils.unsupressOutput(); From ecf521d1cba7d02a6d5810ea1bde8b7c8f21a181 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 20 Oct 2025 10:40:35 +0300 Subject: [PATCH 28/36] Move Export threads to a Thread Pool Executor to shut down gracefully --- .../service/ReplicationDomain.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java index 0ce56d14fc..8a9e4fa7e3 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java @@ -39,6 +39,9 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -372,6 +375,11 @@ public ECLIncludes addIncludedAttributes(int serverId, */ protected volatile long generationId; + /** + * Thread pool for export thread tasks + */ + private final ExecutorService exportThreadPool = Executors.newCachedThreadPool(); + /** * Returns the {@link CSNGenerator} that will be used to * generate {@link CSN} for this domain. @@ -879,7 +887,10 @@ else if (msg instanceof InitializeRcvAckMsg) // Do this work in a thread to allow replay thread continue working ExportThread exportThread = new ExportThread( initReqMsg.getSenderID(), initReqMsg.getInitWindow()); - exportThread.start(); + exportThreadPool.execute(() -> { + Thread.currentThread().setName(exportThread.getName()); + exportThread.run(); + }); } } @@ -1075,6 +1086,9 @@ public void run() This server is not the initiator of the export so there is nothing more to do locally. */ + if (logger.isTraceEnabled()) { + logger.trace(LocalizableMessage.raw("[IE] got exception" + getName()), de); + } } if (logger.isTraceEnabled()) @@ -3016,6 +3030,13 @@ public void disableService() { broker.stop(); } + try { + exportThreadPool.shutdown(); + boolean timedOut = exportThreadPool.awaitTermination(100, TimeUnit.SECONDS); + logger.info(LocalizableMessage.raw("export pool termination timed out: " + timedOut)); + } catch (InterruptedException e) { + // Give up waiting. + } // Stop the listener thread if (listenerThread != null) From 80cdb0efa1200b0a2c98ae966604b4125ea7d0f4 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 21 Oct 2025 12:28:20 +0300 Subject: [PATCH 29/36] Fix deadlock in the ReplicationBroker.stop() function --- .../replication/service/ReplicationBroker.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java index 9ba8932df0..fa1d7beb55 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java @@ -2664,14 +2664,16 @@ public void stop() { debugInfo("is stopping and will close the connection to RS(" + getRsServerId() + ")"); } + synchronized (startStopLock) { + if (shutdown) { + return; + } + } + + domain.publishReplicaOfflineMsg(); synchronized (startStopLock) { - if (shutdown) - { - return; - } - domain.publishReplicaOfflineMsg(); shutdown = true; setConnectedRS(ConnectedRS.stopped()); stopRSHeartBeatMonitoring(); From 616a52623064d2fe59341426922cb24dd8ef31ec Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 22 Oct 2025 14:25:52 +0300 Subject: [PATCH 30/36] Set trace for GenerationIdTest --- opendj-server-legacy/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index 0c91ff5f40..6e5b6b850a 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -1275,7 +1275,7 @@ false false 600000 - org\.opends\.server\.replication\.service\..* + (org\.opends\.server\.replication\.service\..*)|(org\.opends\.server\.replication\.GenerationIdTest) @{argLine} false From 25f10e570d5165f4fbeea6a96413190e59889ab0 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Thu, 23 Oct 2025 14:58:55 +0300 Subject: [PATCH 31/36] fix ReplicationDomainTest.exportAndImportAcross2ReplServers: wait for domain2 topology initialization --- .../replication/service/ReplicationDomainTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index 33decab742..8ba065e95a 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -17,6 +17,8 @@ */ package org.opends.server.replication.service; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.opends.messages.ReplicationMessages.*; import static org.opends.server.TestCaseUtils.generateThreadDump; import static org.opends.server.util.CollectionUtils.*; @@ -44,6 +46,7 @@ import org.opends.server.replication.service.ReplicationDomain.ImportExportContext; import org.forgerock.opendj.ldap.DN; import org.opends.server.types.DirectoryException; +import org.opends.server.util.TestTimer; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -424,6 +427,17 @@ public void exportAndImportAcross2ReplServers() throws Exception domain2 = new FakeReplicationDomain( testService, 2, servers2, 0, null, importedData, 0); + + //wait for domain2 topology initialization + { + TestTimer timer = new TestTimer.Builder() + .maxSleep(30, SECONDS) + .sleepTimes(100, MILLISECONDS) + .toTimer(); + final FakeReplicationDomain finalDomain = domain2; + timer.repeatUntilSuccess(() -> assertFalse(finalDomain.getReplicaInfos().isEmpty())); + } + domain2.initializeFromRemote(1, NO_INIT_TASK); waitEndExport(exportedData, importedData); From 1a51db727e6432cdb9d7000e636eaf9592407d76 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 29 Oct 2025 13:43:54 +0300 Subject: [PATCH 32/36] Fix GenerationIdTest.testMultiRS: wait for all RS handshakes to complete --- .../java/org/opends/server/replication/GenerationIdTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/GenerationIdTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/GenerationIdTest.java index 62426bae27..b452ce0bb7 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/GenerationIdTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/GenerationIdTest.java @@ -921,6 +921,8 @@ public void testMultiRS() throws Exception replServer3 = createReplicationServer(replServerId3, true, testCase); connectServer1ToReplServer(replServer1); + Thread.sleep(2000); //wait for all RS handshakes to complete + debugInfo("Expect genId are set in all replServers."); waitForStableGenerationId(EMPTY_DN_GENID); disconnectFromReplServer(replServer1); From 044f88780bc37513dcfc3add0619ffe8ed64eed5 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 31 Oct 2025 12:53:46 +0300 Subject: [PATCH 33/36] Update copyright --- .../org/opends/server/replication/service/ReplicationBroker.java | 1 + .../org/opends/server/replication/service/ReplicationDomain.java | 1 + .../src/test/java/org/opends/server/DirectoryServerTestCase.java | 1 + .../org/opends/server/replication/SchemaReplicationTest.java | 1 + .../opends/server/replication/service/FakeReplicationDomain.java | 1 + .../src/test/java/org/opends/server/util/TestTimer.java | 1 + 6 files changed, 6 insertions(+) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java index fa1d7beb55..0199b2833a 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java @@ -13,6 +13,7 @@ * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.replication.service; diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java index 8a9e4fa7e3..9c5bed70e5 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java @@ -13,6 +13,7 @@ * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.replication.service; diff --git a/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java index c96c16648c..a1ae527853 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/DirectoryServerTestCase.java @@ -13,6 +13,7 @@ * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server; diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java index 1278ea154f..fea63eba5a 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/SchemaReplicationTest.java @@ -13,6 +13,7 @@ * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2012-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.replication; diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java index dc11a9b7a6..90589a2a07 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/FakeReplicationDomain.java @@ -13,6 +13,7 @@ * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2013-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.replication.service; diff --git a/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java b/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java index 9095b5b770..ab81ab298a 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/util/TestTimer.java @@ -12,6 +12,7 @@ * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2015-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.util; From ed3872352f52831c863b13d41e1e1f30ea04ffd1 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Sat, 1 Nov 2025 09:43:21 +0300 Subject: [PATCH 34/36] Fix ReplicationDomainTest.exportAndImportAcross2ReplServers wait for domains topology initialization --- .../replication/service/ReplicationDomainTest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java index 8ba065e95a..27e11d85c2 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/service/ReplicationDomainTest.java @@ -428,14 +428,20 @@ public void exportAndImportAcross2ReplServers() throws Exception testService, 2, servers2, 0, null, importedData, 0); - //wait for domain2 topology initialization + //wait for domains topology initialization { TestTimer timer = new TestTimer.Builder() .maxSleep(30, SECONDS) .sleepTimes(100, MILLISECONDS) .toTimer(); - final FakeReplicationDomain finalDomain = domain2; - timer.repeatUntilSuccess(() -> assertFalse(finalDomain.getReplicaInfos().isEmpty())); + final FakeReplicationDomain finalDomain1 = domain1; + final FakeReplicationDomain finalDomain2 = domain2; + timer.repeatUntilSuccess(() -> { + assertFalse(finalDomain1.getReplicaInfos().isEmpty()); + assertEquals(2, finalDomain1.getRsInfos().size()); + assertFalse(finalDomain2.getReplicaInfos().isEmpty()); + assertEquals(2, finalDomain2.getRsInfos().size()); + }); } domain2.initializeFromRemote(1, NO_INIT_TASK); From a4bd0a8d620de4d49f868c5f8601dc519a6605dc Mon Sep 17 00:00:00 2001 From: maximthomas Date: Sat, 1 Nov 2025 21:15:17 +0300 Subject: [PATCH 35/36] Add tracing to non-server tests --- .../src/test/java/org/opends/server/TestCaseUtils.java | 3 +++ .../src/test/java/org/opends/server/TestListener.java | 1 + 2 files changed, 4 insertions(+) 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 bd09efaa33..c7a835bf32 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 @@ -543,6 +543,9 @@ private static void setupLoggers() ErrorLogger.getInstance().addLogPublisher( (ErrorLogPublisher) getServerStartupTextErrorPublisher(ERROR_TEXT_WRITER)); + } + + public static void setupTrace() { DebugLogger.getInstance().addPublisherIfRequired(DEBUG_TEXT_WRITER); } diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java index 5e2a03e030..869017ba98 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java @@ -171,6 +171,7 @@ public void onStart(ITestContext testContext) { String tracePattern = System.getProperty("org.opends.test.trace.pattern"); if(testContext.getAllTestMethods()[0].getInstance().getClass().getName().matches(tracePattern)) { System.setProperty("org.opends.server.debug.target.1", "_global:enabled"); + TestCaseUtils.setupTrace(); } } From 41ec1ee7ceb991cadd975024970a4e8d4b3ce3e0 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Sat, 1 Nov 2025 21:16:10 +0300 Subject: [PATCH 36/36] Enabled HostPort comparison tracing --- opendj-server-legacy/pom.xml | 2 +- .../java/org/opends/server/types/HostPort.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml index 6e5b6b850a..2a5b9c8fd4 100644 --- a/opendj-server-legacy/pom.xml +++ b/opendj-server-legacy/pom.xml @@ -1275,7 +1275,7 @@ false false 600000 - (org\.opends\.server\.replication\.service\..*)|(org\.opends\.server\.replication\.GenerationIdTest) + (org\.opends\.server\.replication\.service\..*)|(org\.opends\.server\.replication\.GenerationIdTest)|(org\.opends\.server\.types.\HostPortTest) @{argLine} false diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/HostPort.java b/opendj-server-legacy/src/main/java/org/opends/server/types/HostPort.java index 322023d907..fabb3df0a0 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/types/HostPort.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/types/HostPort.java @@ -13,6 +13,7 @@ * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2013-2016 ForgeRock AS. + * Portions Copyright 2025 3A Systems LLC. */ package org.opends.server.types; @@ -459,6 +460,9 @@ public boolean isEquivalentTo(final HostPort other) // Get and compare ports of RS1 and RS2 if (getPort() != other.getPort()) { + if(logger.isTraceEnabled()) { + logger.trace("port and host does not match " + this + "; " + other); + } return false; } @@ -475,6 +479,9 @@ public boolean isEquivalentTo(final HostPort other) } else if (thisAddresses == null || otherAddresses == null) { + if(logger.isTraceEnabled()) { + logger.trace("port and host does not match: " + this + "=" + thisAddresses + "; " + other + "=" + otherAddresses); + } // One local address and one non-local. return false; } @@ -490,10 +497,17 @@ else if (thisAddresses == null || otherAddresses == null) } } } + if(logger.isTraceEnabled()) { + logger.trace("port and host does not match: " + this + "=" + thisAddresses + "; " + other + "=" + otherAddresses); + } + return false; } catch (UnknownHostException ex) { + if(logger.isTraceEnabled()) { + logger.traceException(ex, "got exception when resolving hosts: " + this + " and " + other ); + } // Unknown RS: should not happen return false; }