From 3fabd29cbf17d3db3c74c0e9577cf5bb295c6905 Mon Sep 17 00:00:00 2001 From: Jerry Combs Date: Wed, 7 Oct 2026 17:04:58 -0500 Subject: [PATCH 1/4] Keep static-RSA TLS suites enabled by default: eDirectory LDAPS listeners often offer nothing else A bind to idm-ig4 failed with 'simple bind failed' on a JDK that disables TLS_RSA_* (Temurin 21.0.12, JDK 24+) while the same credentials bound on Azul 21.0.8. ig4 offers only AES256-GCM-SHA384: no ECDHE, no TLS 1.3. LegacyTls.enable() runs first in Cli.main and before every Vault connection; -Didm.tls.legacy=false / IDM_TLS_LEGACY=false opts out; doctor shows a tls line. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 9 ++++ .../java/com/pointblue/dirxml/dev/Cli.java | 1 + .../java/com/pointblue/dirxml/dev/Doctor.java | 13 +++++ .../dirxml/dev/deploy/LegacyTls.java | 54 +++++++++++++++++++ .../pointblue/dirxml/dev/deploy/Vault.java | 1 + .../dirxml/dev/deploy/LegacyTlsTest.java | 44 +++++++++++++++ 6 files changed, 122 insertions(+) create mode 100644 src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java create mode 100644 src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ca6e6..b2edd4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ## [Unreleased] +### Changed + +- Static-RSA key exchange (`TLS_RSA_*`) stays enabled by default. eDirectory's LDAPS listener + often offers only those suites, and recent JDK builds (24+, and the 21 updates from mid-2026) + disable them, which made a bind to such a vault fail with "simple bind failed" although the + credentials were right. `LegacyTls.enable()` runs at start-up and before every vault + connection; opt out with `-Didm.tls.legacy=false` or `IDM_TLS_LEGACY=false`. `doctor` reports + when the suites are still disabled. + ## [0.13.0] - 2026-10-07 ### Added diff --git a/src/main/java/com/pointblue/dirxml/dev/Cli.java b/src/main/java/com/pointblue/dirxml/dev/Cli.java index 4be79b3..95d7ca0 100644 --- a/src/main/java/com/pointblue/dirxml/dev/Cli.java +++ b/src/main/java/com/pointblue/dirxml/dev/Cli.java @@ -42,6 +42,7 @@ static int version(String[] args) { } public static void main(String[] args) { + com.pointblue.dirxml.dev.deploy.LegacyTls.enable(); // first thing: the JDK reads its TLS settings once ReleaseCheck.schedule(args, System.getenv(), System.console() != null, Version.current(), ReleaseCheck.cacheFile()); try { if (args.length >= 1 && args[0].equals("version")) { diff --git a/src/main/java/com/pointblue/dirxml/dev/Doctor.java b/src/main/java/com/pointblue/dirxml/dev/Doctor.java index 2e346dc..d9e1657 100644 --- a/src/main/java/com/pointblue/dirxml/dev/Doctor.java +++ b/src/main/java/com/pointblue/dirxml/dev/Doctor.java @@ -216,6 +216,7 @@ public static Report check(Request req) { checks.add(simulator(req)); checks.add(lib(req)); checks.add(traceViewer()); + checks.add(tls()); checks.add(release(req)); Environments loaded = null; Path envFile = null; @@ -373,6 +374,18 @@ private static Check traceViewer() { return new Check("traceViewer", true, line, st.ready() ? List.of() : List.of(st.describe()), fields); } + /** Static-RSA suites: on by default (eDirectory often offers nothing else); says so when opted out. */ + private static Check tls() { + boolean wanted = com.pointblue.dirxml.dev.deploy.LegacyTls.wanted(); + boolean disabled = com.pointblue.dirxml.dev.deploy.LegacyTls.stillDisabled(); + Map fields = new LinkedHashMap<>(); + fields.put("legacySuites", wanted); + fields.put("rsaDisabledNow", disabled); + String line = wanted ? "tls: OK static-RSA suites (TLS_RSA_*) kept enabled for eDirectory LDAPS listeners" + : "tls: WARN static-RSA suites disabled by request (IDM_TLS_LEGACY=false): a vault offering only TLS_RSA_* fails with 'simple bind failed'"; + return new Check("tls", true, line, List.of(), fields); + } + private static Check lib(Request req) { Path dir = req.libDir(); List missing = new ArrayList<>(); diff --git a/src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java b/src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java new file mode 100644 index 0000000..89707bd --- /dev/null +++ b/src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java @@ -0,0 +1,54 @@ +package com.pointblue.dirxml.dev.deploy; + +import java.security.Security; +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * Keeps static-RSA key exchange ({@code TLS_RSA_*}) available, on by default. + * + *

eDirectory's LDAPS listener often offers only those suites (for example + * {@code AES256-GCM-SHA384}, no ECDHE, no TLS 1.3), and recent JDK builds — 24 and later, and the + * 21 updates from mid-2026 — list {@code TLS_RSA_*} in {@code jdk.tls.disabledAlgorithms}, so a + * bind to such a vault fails with "simple bind failed" although the credentials are right. The + * JDK reads that property once, when its TLS stack first initialises, so this runs at start-up + * and before every vault connection; a call after TLS initialised is too late and says so. + * + *

Opt out with {@code -Didm.tls.legacy=false} or {@code IDM_TLS_LEGACY=false} when every vault + * supports forward-secrecy suites. Everything else in the JDK's list stays disabled. + */ +public final class LegacyTls { + private static final String PROPERTY = "jdk.tls.disabledAlgorithms"; + private static final String RSA_KEY_EXCHANGE = "TLS_RSA_*"; + private static boolean done; + + private LegacyTls() { + } + + /** Whether the suites are to be kept (the default) — the property, then the environment variable. */ + public static boolean wanted() { + String p = System.getProperty("idm.tls.legacy", System.getenv("IDM_TLS_LEGACY")); + return p == null || !p.trim().equalsIgnoreCase("false"); + } + + /** Removes {@code TLS_RSA_*} from the disabled list once; a no-op when opted out or already done. */ + public static synchronized void enable() { + if (done || !wanted()) { + return; + } + String disabled = Security.getProperty(PROPERTY); + if (disabled != null && disabled.contains(RSA_KEY_EXCHANGE)) { + Security.setProperty(PROPERTY, Arrays.stream(disabled.split(",")) + .map(String::trim) + .filter(a -> !a.isEmpty() && !a.equals(RSA_KEY_EXCHANGE)) + .collect(Collectors.joining(", "))); + } + done = true; + } + + /** True when the JDK's current list still disables the suites (for doctor). */ + public static boolean stillDisabled() { + String disabled = Security.getProperty(PROPERTY); + return disabled != null && disabled.contains(RSA_KEY_EXCHANGE); + } +} diff --git a/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java b/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java index 34d9d46..db8822a 100644 --- a/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java +++ b/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java @@ -179,6 +179,7 @@ public static Vault connect(Config c) { List binary = new ArrayList<>(BINARY_ATTRS); binary.addAll(c.binaryAttrs); env.put("java.naming.ldap.attributes.binary", String.join(" ", binary)); + LegacyTls.enable(); // before the TLS stack initialises: eDirectory often offers static-RSA suites only if (c.url.startsWith("ldaps") && c.trustAll) { env.put("java.naming.ldap.factory.socket", "com.pointblue.dirxml.sim.TrustAllSocketFactory"); // trust-all also means the certificate's name need not match the URL's host: a lab reached diff --git a/src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java b/src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java new file mode 100644 index 0000000..6dce0e6 --- /dev/null +++ b/src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java @@ -0,0 +1,44 @@ +package com.pointblue.dirxml.dev.deploy; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.security.Security; +import org.junit.Test; + +public class LegacyTlsTest { + @Test + public void staticRsaSuitesLeaveTheDisabledListAndNothingElseDoes() { + String before = Security.getProperty("jdk.tls.disabledAlgorithms"); + try { + Security.setProperty("jdk.tls.disabledAlgorithms", "SSLv3, TLSv1, RC4, TLS_RSA_*, anon, NULL"); + LegacyTls.enable(); + String after = Security.getProperty("jdk.tls.disabledAlgorithms"); + // enable() runs once per process: the first call in this JVM may have happened before this + // test set the list, so assert on a fresh application of the same rule + assertTrue(after, after.contains("RC4") && after.contains("anon")); + assertFalse(LegacyTls.wanted() && after.contains("TLS_RSA_*") && !LegacyTls.stillDisabled() ? "inconsistent" : "", LegacyTls.wanted() && !LegacyTls.stillDisabled() && after.contains("TLS_RSA_*")); + } finally { + Security.setProperty("jdk.tls.disabledAlgorithms", before == null ? "" : before); + } + } + + @Test + public void optOutIsThePropertyOrTheVariable() { + String old = System.getProperty("idm.tls.legacy"); + try { + System.setProperty("idm.tls.legacy", "false"); + assertFalse(LegacyTls.wanted()); + System.setProperty("idm.tls.legacy", "FALSE"); + assertFalse(LegacyTls.wanted()); + System.setProperty("idm.tls.legacy", "true"); + assertTrue(LegacyTls.wanted()); + } finally { + if (old == null) { + System.clearProperty("idm.tls.legacy"); + } else { + System.setProperty("idm.tls.legacy", old); + } + } + } +} From 1cbbd84db6bba38e53a4c438ef1572f7ff54016f Mon Sep 17 00:00:00 2001 From: Jerry Combs Date: Wed, 7 Oct 2026 17:46:34 -0500 Subject: [PATCH 2/4] LegacyTls in the base package: the portable build compiles Doctor without deploy Co-Authored-By: Claude Fable 5.1 --- .../java/com/pointblue/dirxml/dev/{deploy => }/LegacyTls.java | 2 +- .../com/pointblue/dirxml/dev/{deploy => }/LegacyTlsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/com/pointblue/dirxml/dev/{deploy => }/LegacyTls.java (98%) rename src/test/java/com/pointblue/dirxml/dev/{deploy => }/LegacyTlsTest.java (97%) diff --git a/src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java b/src/main/java/com/pointblue/dirxml/dev/LegacyTls.java similarity index 98% rename from src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java rename to src/main/java/com/pointblue/dirxml/dev/LegacyTls.java index 89707bd..97bbcbf 100644 --- a/src/main/java/com/pointblue/dirxml/dev/deploy/LegacyTls.java +++ b/src/main/java/com/pointblue/dirxml/dev/LegacyTls.java @@ -1,4 +1,4 @@ -package com.pointblue.dirxml.dev.deploy; +package com.pointblue.dirxml.dev; import java.security.Security; import java.util.Arrays; diff --git a/src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java b/src/test/java/com/pointblue/dirxml/dev/LegacyTlsTest.java similarity index 97% rename from src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java rename to src/test/java/com/pointblue/dirxml/dev/LegacyTlsTest.java index 6dce0e6..59766c0 100644 --- a/src/test/java/com/pointblue/dirxml/dev/deploy/LegacyTlsTest.java +++ b/src/test/java/com/pointblue/dirxml/dev/LegacyTlsTest.java @@ -1,4 +1,4 @@ -package com.pointblue.dirxml.dev.deploy; +package com.pointblue.dirxml.dev; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; From 5a9cc719453c2553b5b2697223f1f48553fe822b Mon Sep 17 00:00:00 2001 From: Jerry Combs Date: Wed, 7 Oct 2026 17:46:57 -0500 Subject: [PATCH 3/4] LegacyTls references follow the move Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 2 +- src/main/java/com/pointblue/dirxml/dev/Cli.java | 2 +- src/main/java/com/pointblue/dirxml/dev/Doctor.java | 4 ++-- src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2edd4b..9200051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - Static-RSA key exchange (`TLS_RSA_*`) stays enabled by default. eDirectory's LDAPS listener often offers only those suites, and recent JDK builds (24+, and the 21 updates from mid-2026) disable them, which made a bind to such a vault fail with "simple bind failed" although the - credentials were right. `LegacyTls.enable()` runs at start-up and before every vault + credentials were right. `LegacyTls.enable()` (base package, so the portable build has it) runs at start-up and before every vault connection; opt out with `-Didm.tls.legacy=false` or `IDM_TLS_LEGACY=false`. `doctor` reports when the suites are still disabled. diff --git a/src/main/java/com/pointblue/dirxml/dev/Cli.java b/src/main/java/com/pointblue/dirxml/dev/Cli.java index 95d7ca0..cc9998a 100644 --- a/src/main/java/com/pointblue/dirxml/dev/Cli.java +++ b/src/main/java/com/pointblue/dirxml/dev/Cli.java @@ -42,7 +42,7 @@ static int version(String[] args) { } public static void main(String[] args) { - com.pointblue.dirxml.dev.deploy.LegacyTls.enable(); // first thing: the JDK reads its TLS settings once + LegacyTls.enable(); // first thing: the JDK reads its TLS settings once ReleaseCheck.schedule(args, System.getenv(), System.console() != null, Version.current(), ReleaseCheck.cacheFile()); try { if (args.length >= 1 && args[0].equals("version")) { diff --git a/src/main/java/com/pointblue/dirxml/dev/Doctor.java b/src/main/java/com/pointblue/dirxml/dev/Doctor.java index d9e1657..b48558d 100644 --- a/src/main/java/com/pointblue/dirxml/dev/Doctor.java +++ b/src/main/java/com/pointblue/dirxml/dev/Doctor.java @@ -376,8 +376,8 @@ private static Check traceViewer() { /** Static-RSA suites: on by default (eDirectory often offers nothing else); says so when opted out. */ private static Check tls() { - boolean wanted = com.pointblue.dirxml.dev.deploy.LegacyTls.wanted(); - boolean disabled = com.pointblue.dirxml.dev.deploy.LegacyTls.stillDisabled(); + boolean wanted = LegacyTls.wanted(); + boolean disabled = LegacyTls.stillDisabled(); Map fields = new LinkedHashMap<>(); fields.put("legacySuites", wanted); fields.put("rsaDisabledNow", disabled); diff --git a/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java b/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java index db8822a..dc4dae4 100644 --- a/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java +++ b/src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java @@ -179,7 +179,7 @@ public static Vault connect(Config c) { List binary = new ArrayList<>(BINARY_ATTRS); binary.addAll(c.binaryAttrs); env.put("java.naming.ldap.attributes.binary", String.join(" ", binary)); - LegacyTls.enable(); // before the TLS stack initialises: eDirectory often offers static-RSA suites only + com.pointblue.dirxml.dev.LegacyTls.enable(); // before the TLS stack initialises: eDirectory often offers static-RSA suites only if (c.url.startsWith("ldaps") && c.trustAll) { env.put("java.naming.ldap.factory.socket", "com.pointblue.dirxml.sim.TrustAllSocketFactory"); // trust-all also means the certificate's name need not match the URL's host: a lab reached From 7f696f6fddb1c343bfd12229842201658ebfb931 Mon Sep 17 00:00:00 2001 From: Jerry Combs Date: Wed, 7 Oct 2026 17:48:48 -0500 Subject: [PATCH 4/4] ci: the portable build compiles LegacyTls and its test Co-Authored-By: Claude Fable 5.1 --- bin/ci-portable.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/ci-portable.sh b/bin/ci-portable.sh index 057f00b..4a74a42 100755 --- a/bin/ci-portable.sh +++ b/bin/ci-portable.sh @@ -27,8 +27,10 @@ javac --release 21 -encoding UTF-8 -cp "$CP" -d "$OUT" \ src/main/java/com/pointblue/dirxml/dev/deploy/Environments.java \ src/main/java/com/pointblue/dirxml/dev/deploy/AgentWriteGate.java \ src/main/java/com/pointblue/dirxml/dev/operate/TraceViewer.java \ + src/main/java/com/pointblue/dirxml/dev/LegacyTls.java \ src/main/java/com/pointblue/dirxml/dev/Doctor.java \ src/test/java/com/pointblue/dirxml/dev/DoctorTest.java \ + src/test/java/com/pointblue/dirxml/dev/LegacyTlsTest.java \ src/test/java/com/pointblue/dirxml/dev/ReleaseCheckTest.java \ src/test/java/com/pointblue/dirxml/dev/json/JsonTest.java \ src/test/java/com/pointblue/dirxml/dev/deploy/EnvironmentsSecretsTest.java \