MLE-30239 Use secure trust store#1945
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the MarkLogic Java Client SSL examples/tests to avoid “trust-all” trust managers and instead rely on certificate validation via a configured truststore (or the JVM default trust managers where applicable).
Changes:
- Replaces a trust-all
TrustManagerin the SSL cookbook example with a truststore-backedTrustManagerFactoryapproach and switches to STRICT hostname verification. - Updates functional test SSLContext creation to use a truststore (classpath resource) when configured, falling back to JVM default trust managers otherwise.
- Adds/updates properties and a new test asserting handshake failure when the server CA is not trusted.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| marklogic-client-api/src/test/java/com/marklogic/client/test/ssl/OneWaySSLTest.java | Adds a test validating TLS handshake fails when using default JVM trust managers that don’t trust the test CA. |
| marklogic-client-api-functionaltests/src/test/resources/test.properties | Documents optional truststore properties for SSL-enabled functional test runs. |
| marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/functionaltest/ConnectedRESTQA.java | Replaces trust-all trust manager with truststore/JVM-default trust managers for SSLContext initialization. |
| examples/src/main/resources/Example.properties | Documents truststore configuration for the SSL cookbook example. |
| examples/src/main/java/com/marklogic/client/example/cookbook/Util.java | Adds truststore fields to example configuration parsing. |
| examples/src/main/java/com/marklogic/client/example/cookbook/SSLClientCreator.java | Loads truststore and uses a real trust manager + STRICT hostname verification for the SSL example. |
| */ | ||
| public class SSLClientCreator { | ||
| public static void main(String[] args) throws IOException, KeyManagementException, NoSuchAlgorithmException { | ||
| public static void main(String[] args) throws IOException, KeyManagementException, NoSuchAlgorithmException, |
There was a problem hiding this comment.
I think this should instead use the support in DatabaseClientFactory to configure trusted SSL via properties so that the user doesn't have to deal with all this boilerplate. Check out the public static DatabaseClient newClient(Function<String, Object> propertySource) { method. It handles all of this plumbing.
| // When ml_truststore_file is configured, a TrustManagerFactory is initialised | ||
| // from that truststore (which must contain the MarkLogic server's CA certificate). | ||
| // Otherwise the JVM's default trust managers are used as a safe fallback. | ||
| final TrustManager[] trustManagers; |
There was a problem hiding this comment.
Same thing here - try reworking this to use the support in DatabaseClientFactory for doing all of this for the user.
b9b5310 to
c0d2511
Compare
| # Path to a JKS truststore (as a classpath resource) containing the MarkLogic | ||
| # server's CA certificate. Required when restSSLset=true; leave commented out | ||
| # for non-SSL test runs. | ||
| # ml_truststore_file=/truststore.jks | ||
| # ml_truststore_password= |
| trustStorePath = props.getProperty("example.truststore.path"); | ||
| trustStorePassword = props.getProperty("example.truststore.password"); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| * A JKS or PKCS12 truststore containing the server's CA certificate must be | ||
| * configured via | ||
| * {@code example.truststore.path} and {@code example.truststore.password} in | ||
| * {@code Example.properties} | ||
| * (or the equivalent system properties) before running this example. |
| // Optional: configure example.truststore.path (and example.truststore.password) in Example.properties. | ||
| // If omitted, the JVM default trust store will be used. | ||
| if (props.trustStorePath != null && !props.trustStorePath.isEmpty() && props.trustStorePassword == null) { | ||
| throw new IllegalStateException( | ||
| "example.truststore.password is not configured. Set it in Example.properties."); | ||
| } |
| if (props.trustStorePath == null || props.trustStorePath.isEmpty()) { | ||
| yield null; | ||
| } | ||
| String lowerPath = props.trustStorePath.toLowerCase(); |
| final TrustManager[] trustManagers; | ||
| if (ml_truststore_file != null && !ml_truststore_file.trim().isEmpty()) { | ||
| KeyStore trustStore = KeyStore.getInstance("JKS"); | ||
| char[] tsPassword = ml_truststore_password != null ? ml_truststore_password.toCharArray() : new char[0]; |
| // server certificate | ||
| // CN/SANs are checked against the connected host. | ||
| try (DatabaseClient client = DatabaseClientFactory.newClient(propertyName -> switch (propertyName) { | ||
| case "marklogic.client.host" -> props.host; |
There was a problem hiding this comment.
This works, but I would just update the example properties file to use these "marklogic.client." properties instead so that no translation is needed.
Replace trust-all TrustManager in SSL cookbook example with a trust-store-based approach