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. |
| jdbcUrl = props.getProperty("example.jdbc.url"); | ||
| jdbcUser = props.getProperty("example.jdbc.user"); | ||
| jdbcPassword = props.getProperty("example.jdbc.password"); | ||
| trustStorePath = props.getProperty("example.truststore.path"); | ||
| trustStorePassword = props.getProperty("example.truststore.password"); |
| if (ml_truststore_file != null && !ml_truststore_file.trim().isEmpty()) { | ||
| KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); | ||
| char[] tsPassword = ml_truststore_password != null ? ml_truststore_password.toCharArray() : new char[0]; | ||
| InputStream tsInput = property.getClass().getResourceAsStream(ml_truststore_file); |
| KeyStore trustStore = KeyStore.getInstance("JKS"); | ||
| try (FileInputStream fis = new FileInputStream(props.trustStorePath)) { | ||
| trustStore.load(fis, props.trustStorePassword.toCharArray()); | ||
| } | ||
|
|
||
| @Override | ||
| public X509Certificate[] getAcceptedIssuers() { | ||
| return new X509Certificate[0]; | ||
| } | ||
| } | ||
| }; | ||
| // Initialise a TrustManagerFactory from the truststore so that the SSLContext | ||
| // will validate the server's certificate against the trusted CAs it contains. | ||
| TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | ||
| tmf.init(trustStore); | ||
| TrustManager[] trustManagers = tmf.getTrustManagers(); | ||
| X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; | ||
|
|
||
| // create an SSL context | ||
| // Create an SSL context backed by the real trust manager. | ||
| SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); | ||
| /* | ||
| * Here, we use a naive TrustManager which would accept any certificate | ||
| * which the server produces. But in a real application, there should be a | ||
| * TrustManager which is initialized with a Keystore which would determine | ||
| * whether the remote authentication credentials should be trusted or not. | ||
| * | ||
| * If we init the sslContext with null TrustManager, it would use the | ||
| * <java-home>/lib/security/cacerts file for trusted root certificates, if | ||
| * javax.net.ssl.trustStore system property is not set and | ||
| * <java-home>/lib/security/jssecacerts is not present. See this link for | ||
| * more information on TrustManagers - | ||
| * http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ | ||
| * JSSERefGuide.html | ||
| * | ||
| * If self signed certificates, signed by CAs created internally are used, | ||
| * then the internal CA's root certificate should be added to the keystore. | ||
| * See this link - | ||
| * https://docs.oracle.com/cd/E19226-01/821-0027/geygn/index.html for adding | ||
| * a root certificate in the keystore. | ||
| */ | ||
| sslContext.init(null, naiveTrustMgr, null); | ||
| sslContext.init(null, trustManagers, null); |
| */ | ||
| 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.
Replace trust-all TrustManager in SSL cookbook example with a trust-store-based approach