Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()` (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.

## [0.13.0] - 2026-10-07

### Added
Expand Down
2 changes: 2 additions & 0 deletions bin/ci-portable.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/pointblue/dirxml/dev/Cli.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ static int version(String[] args) {
}

public static void main(String[] args) {
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")) {
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/pointblue/dirxml/dev/Doctor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = LegacyTls.wanted();
boolean disabled = LegacyTls.stillDisabled();
Map<String, Object> 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<String> missing = new ArrayList<>();
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/com/pointblue/dirxml/dev/LegacyTls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.pointblue.dirxml.dev;

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.
*
* <p>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.
*
* <p>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);
}
}
1 change: 1 addition & 0 deletions src/main/java/com/pointblue/dirxml/dev/deploy/Vault.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ public static Vault connect(Config c) {
List<String> binary = new ArrayList<>(BINARY_ATTRS);
binary.addAll(c.binaryAttrs);
env.put("java.naming.ldap.attributes.binary", String.join(" ", binary));
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
Expand Down
44 changes: 44 additions & 0 deletions src/test/java/com/pointblue/dirxml/dev/LegacyTlsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.pointblue.dirxml.dev;

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);
}
}
}
}
Loading