-
Notifications
You must be signed in to change notification settings - Fork 74
MLE-30239 Use secure trust store #1945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,107 +1,86 @@ | ||
| /* | ||
| * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. | ||
| * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. | ||
| */ | ||
| package com.marklogic.client.example.cookbook; | ||
|
|
||
| import java.io.IOException; | ||
| import java.security.KeyManagementException; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.security.cert.X509Certificate; | ||
|
|
||
| import javax.net.ssl.SSLContext; | ||
| import javax.net.ssl.TrustManager; | ||
| import javax.net.ssl.X509TrustManager; | ||
|
|
||
| import com.marklogic.client.DatabaseClient; | ||
| import com.marklogic.client.DatabaseClientFactory; | ||
| import com.marklogic.client.DatabaseClientFactory.DigestAuthContext; | ||
| import com.marklogic.client.DatabaseClientFactory.SSLHostnameVerifier; | ||
| import com.marklogic.client.document.TextDocumentManager; | ||
| import com.marklogic.client.example.cookbook.Util.ExampleProperties; | ||
| import com.marklogic.client.io.StringHandle; | ||
|
|
||
| /** | ||
| * SSLClientCreator illustrates the basic approach for creating a client using SSL for database access. | ||
| * SSLClientCreator illustrates the basic approach for creating a client using | ||
| * SSL for database access. | ||
| * | ||
| * <p> | ||
| * 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. | ||
| * </p> | ||
| * | ||
| * Note: to run this example, you must modify the REST server by specifying a SSL certificate template. | ||
| * <p> | ||
| * Note: to run this example, you must also modify the REST server by specifying | ||
| * an SSL certificate template. | ||
| * </p> | ||
| */ | ||
| public class SSLClientCreator { | ||
| public static void main(String[] args) throws IOException, KeyManagementException, NoSuchAlgorithmException { | ||
| public static void main(String[] args) throws IOException { | ||
| run(Util.loadProperties()); | ||
| } | ||
|
|
||
| public static void run(ExampleProperties props) throws NoSuchAlgorithmException, KeyManagementException { | ||
| System.out.println("example: "+SSLClientCreator.class.getName()); | ||
|
|
||
| // create a trust manager | ||
| // (note: a real application should verify certificates. This | ||
| // naive trust manager which accepts all the certificates should be replaced | ||
| // by a valid trust manager or get a system default trust manager | ||
| // which would validate whether the remote authentication credentials | ||
| // should be trusted or not.) | ||
| TrustManager naiveTrustMgr[] = new X509TrustManager[] { | ||
| new X509TrustManager() { | ||
| @Override | ||
| public void checkClientTrusted(X509Certificate[] chain, String authType) { | ||
| } | ||
|
|
||
| @Override | ||
| public void checkServerTrusted(X509Certificate[] chain, String authType) { | ||
| } | ||
|
|
||
| @Override | ||
| public X509Certificate[] getAcceptedIssuers() { | ||
| return new X509Certificate[0]; | ||
| } | ||
| } | ||
| }; | ||
| public static void run(ExampleProperties props) { | ||
| System.out.println("example: " + SSLClientCreator.class.getName()); | ||
|
|
||
| // create an SSL context | ||
| 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); | ||
| // Configure example.truststore.path and example.truststore.password in | ||
| // Example.properties. | ||
| if (props.trustStorePath == null || props.trustStorePath.isEmpty()) { | ||
| throw new IllegalStateException( | ||
| "example.truststore.path is not configured. Set it in Example.properties to the path of a JKS or " | ||
| + "PKCS12 truststore containing the server's CA certificate."); | ||
| } | ||
| if (props.trustStorePassword == null) { | ||
| throw new IllegalStateException( | ||
| "example.truststore.password is not configured. Set it in Example.properties."); | ||
| } | ||
|
|
||
| // create the client | ||
| // (note: a real application should use a COMMON, STRICT, or implemented hostname verifier) | ||
| DatabaseClient client = DatabaseClientFactory.newClient( | ||
| props.host, props.port, | ||
| new DigestAuthContext(props.writerUser, props.writerPassword) | ||
| .withSSLContext(sslContext, (X509TrustManager) naiveTrustMgr[0]) | ||
| .withSSLHostnameVerifier(SSLHostnameVerifier.ANY)); | ||
| // Create the client using the property-source API. SSL is configured | ||
| // declaratively via the | ||
| // truststore path and password so that the client validates the server | ||
| // certificate against | ||
| // the trusted CAs in that store. STRICT hostname verification ensures the | ||
| // server certificate | ||
| // CN/SANs are checked against the connected host. | ||
| try (DatabaseClient client = DatabaseClientFactory.newClient(propertyName -> switch (propertyName) { | ||
| case "marklogic.client.host" -> props.host; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works, but I would just update the example properties file to use these "marklogic.client." properties instead so that no translation is needed. |
||
| case "marklogic.client.port" -> props.port; | ||
| case "marklogic.client.authType" -> "digest"; | ||
| case "marklogic.client.username" -> props.writerUser; | ||
| case "marklogic.client.password" -> props.writerPassword; | ||
| case "marklogic.client.sslProtocol" -> "TLSv1.3"; | ||
| case "marklogic.client.ssl.truststore.path" -> props.trustStorePath; | ||
| case "marklogic.client.ssl.truststore.password" -> props.trustStorePassword; | ||
| case "marklogic.client.sslHostnameVerifier" -> DatabaseClientFactory.SSLHostnameVerifier.STRICT; | ||
|
rjdew-progress marked this conversation as resolved.
|
||
| default -> null; | ||
| })) { | ||
|
|
||
| // make use of the client connection | ||
| TextDocumentManager docMgr = client.newTextDocumentManager(); | ||
| String docId = "/example/text.txt"; | ||
| StringHandle handle = new StringHandle(); | ||
| handle.set("A simple text document"); | ||
| docMgr.write(docId, handle); | ||
| // make use of the client connection | ||
| TextDocumentManager docMgr = client.newTextDocumentManager(); | ||
| String docId = "/example/text.txt"; | ||
| StringHandle handle = new StringHandle(); | ||
| handle.set("A simple text document"); | ||
| docMgr.write(docId, handle); | ||
|
|
||
| System.out.println( | ||
| "Connected by SSL to "+props.host+":"+props.port+" as "+props.writerUser); | ||
| System.out.println( | ||
| "Connected by SSL to " + props.host + ":" + props.port + " as " + props.writerUser); | ||
|
|
||
| // clean up the written document | ||
| docMgr.delete(docId); | ||
| // clean up the written document | ||
| docMgr.delete(docId); | ||
|
|
||
| // release the client | ||
| client.release(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. | ||
| * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. | ||
| */ | ||
|
|
||
| package com.marklogic.client.functionaltest; | ||
|
|
@@ -40,7 +40,6 @@ | |
| import java.io.InputStream; | ||
| import java.security.*; | ||
| import java.security.cert.CertificateException; | ||
| import java.security.cert.X509Certificate; | ||
| import java.util.*; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.fail; | ||
|
|
@@ -71,6 +70,8 @@ public abstract class ConnectedRESTQA { | |
| private static String admin_password = null; | ||
| private static String ml_certificate_password = null; | ||
| private static String ml_certificate_file = null; | ||
| private static String ml_truststore_file = null; | ||
| private static String ml_truststore_password = null; | ||
| private static String mlDataConfigDirPath = null; | ||
| private static Boolean isLBHost = false; | ||
|
|
||
|
|
@@ -782,23 +783,6 @@ public static void setPathRangeIndexInDatabase(String dbName, JsonNode jnode) { | |
| */ | ||
| public static SSLContext getSslContext() throws IOException, NoSuchAlgorithmException, KeyManagementException, | ||
| KeyStoreException, CertificateException, UnrecoverableKeyException { | ||
| // create a trust manager | ||
| // (note: a real application should verify certificates) | ||
|
|
||
| TrustManager tm = new X509TrustManager() { | ||
| public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { | ||
| // nothing to do | ||
| } | ||
|
|
||
| public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { | ||
| // nothing to do | ||
| } | ||
|
|
||
| public X509Certificate[] getAcceptedIssuers() { | ||
| return new X509Certificate[0]; | ||
| } | ||
| }; | ||
|
|
||
| // get the client certificate. In case we need to modify path. | ||
| String mlCertFile = new String(ml_certificate_file); | ||
|
|
||
|
|
@@ -816,9 +800,32 @@ public X509Certificate[] getAcceptedIssuers() { | |
| keyManagerFactory.init(keyStore, ml_certificate_password.toCharArray()); | ||
| KeyManager[] keyMgr = keyManagerFactory.getKeyManagers(); | ||
|
|
||
| // Build a trust manager that validates the server's certificate. | ||
| // 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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing here - try reworking this to use the support in DatabaseClientFactory for doing all of this for the user. |
||
| 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 = ConnectedRESTQA.class.getResourceAsStream(ml_truststore_file); | ||
| if (tsInput == null) { | ||
| throw new IOException("Truststore resource not found on classpath: " + ml_truststore_file | ||
| + ". Ensure the file exists under the test resources directory."); | ||
| } | ||
| try { | ||
| trustStore.load(tsInput, tsPassword); | ||
| } finally { | ||
| tsInput.close(); | ||
| } | ||
| trustManagers = SSLUtil.getTrustManagers(TrustManagerFactory.getDefaultAlgorithm(), trustStore); | ||
| } else { | ||
| trustManagers = SSLUtil.getDefaultTrustManagers(); | ||
| } | ||
|
|
||
| // create an SSL context | ||
| SSLContext mlsslContext = SSLContext.getInstance(SSLUtil.DEFAULT_PROTOCOL); | ||
| mlsslContext.init(keyMgr, new TrustManager[] { tm }, null); | ||
| mlsslContext.init(keyMgr, trustManagers, null); | ||
|
|
||
| return mlsslContext; | ||
| } | ||
|
|
@@ -991,6 +998,8 @@ public static void loadGradleProperties() { | |
| ssl_enabled = properties.getProperty("restSSLset"); | ||
| ml_certificate_password = properties.getProperty("ml_certificate_password"); | ||
| ml_certificate_file = properties.getProperty("ml_certificate_file"); | ||
| ml_truststore_file = properties.getProperty("ml_truststore_file"); | ||
| ml_truststore_password = properties.getProperty("ml_truststore_password"); | ||
| mlDataConfigDirPath = properties.getProperty("mlDataConfigDirPath"); | ||
| isLBHost = "gateway".equalsIgnoreCase(properties.getProperty("marklogic.client.connectionType")); | ||
| PROPERTY_WAIT = Integer.parseInt(isLBHost ? "15000" : "0"); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.