Skip to content

Commit e324d60

Browse files
authored
Merge pull request #1629 from marklogic/feature/4116-truststore
MLE-4116: Can now configure trust store
2 parents e4a7eaf + 4ddf618 commit e324d60

File tree

5 files changed

+112
-11
lines changed

5 files changed

+112
-11
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientBuilder.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,49 @@ public DatabaseClientBuilder withKeyStoreAlgorithm(String algorithm) {
301301
props.put(PREFIX + "ssl.keystore.algorithm", algorithm);
302302
return this;
303303
}
304+
305+
/**
306+
* Supports constructing an {@code X509TrustManager} based on the given file path, which should point to a Java
307+
* key store or trust store.
308+
*
309+
* @param path
310+
* @return
311+
* @since 6.5.0
312+
*/
313+
public DatabaseClientBuilder withTrustStorePath(String path) {
314+
props.put(PREFIX + "ssl.truststore.path", path);
315+
return this;
316+
}
317+
318+
/**
319+
* @param password optional password for a trust store
320+
* @return
321+
* @since 6.5.0
322+
*/
323+
public DatabaseClientBuilder withTrustStorePassword(String password) {
324+
props.put(PREFIX + "ssl.truststore.password", password);
325+
return this;
326+
}
327+
328+
/**
329+
* @param type e.g. "JKS"
330+
* @return
331+
* @since 6.5.0
332+
*/
333+
public DatabaseClientBuilder withTrustStoreType(String type) {
334+
props.put(PREFIX + "ssl.truststore.type", type);
335+
return this;
336+
}
337+
338+
/**
339+
* @param algorithm e.g. "SunX509"
340+
* @return
341+
* @since 6.5.0
342+
*/
343+
public DatabaseClientBuilder withTrustStoreAlgorithm(String algorithm) {
344+
props.put(PREFIX + "ssl.truststore.algorithm", algorithm);
345+
return this;
346+
}
304347
}
305348

306349

marklogic-client-api/src/main/java/com/marklogic/client/DatabaseClientFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,10 @@ public String getCertificatePassword() {
13031303
* <li>marklogic.client.ssl.keystore.password = must be a String; optional password for a key store; since 6.4.0.</li>
13041304
* <li>marklogic.client.ssl.keystore.type = must be a String; optional type for a key store, defaults to "JKS"; since 6.4.0.</li>
13051305
* <li>marklogic.client.ssl.keystore.algorithm = must be a String; optional algorithm for a key store, defaults to "SunX509"; since 6.4.0.</li>
1306+
* <li>marklogic.client.ssl.truststore.path = must be a String; specifies a file path for a trust store for SSL and/or certificate authentication; since 6.5.0.</li>
1307+
* <li>marklogic.client.ssl.truststore.password = must be a String; optional password for a trust store; since 6.5.0.</li>
1308+
* <li>marklogic.client.ssl.truststore.type = must be a String; optional type for a trust store, defaults to "JKS"; since 6.5.0.</li>
1309+
* <li>marklogic.client.ssl.truststore.algorithm = must be a String; optional algorithm for a trust store, defaults to "SunX509"; since 6.5.0.</li>
13061310
* </ol>
13071311
*
13081312
* @param propertySource

marklogic-client-api/src/main/java/com/marklogic/client/impl/DatabaseClientPropertySource.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.net.ssl.SSLContext;
2424
import javax.net.ssl.X509TrustManager;
2525
import java.security.KeyManagementException;
26+
import java.security.KeyStore;
2627
import java.security.NoSuchAlgorithmException;
2728
import java.util.LinkedHashMap;
2829
import java.util.Map;
@@ -317,9 +318,29 @@ private X509TrustManager getTrustManager() {
317318
throw new IllegalArgumentException("Trust manager must be an instanceof " + X509TrustManager.class.getName());
318319
}
319320
}
321+
322+
String path = getNullableStringValue("ssl.truststore.path");
323+
if (path != null && path.trim().length() > 0) {
324+
return buildTrustManagerFromTrustStorePath(path);
325+
}
326+
320327
return null;
321328
}
322329

330+
/**
331+
* Added in 6.5.0 to support configuring a trust manager via properties.
332+
*
333+
* @param path
334+
* @return
335+
*/
336+
private X509TrustManager buildTrustManagerFromTrustStorePath(String path) {
337+
final String password = getNullableStringValue("ssl.truststore.password");
338+
final String type = getNullableStringValue("ssl.truststore.type", "JKS");
339+
final String algorithm = getNullableStringValue("ssl.truststore.algorithm", "SunX509");
340+
KeyStore trustStore = SSLUtil.getKeyStore(path, password != null ? password.toCharArray() : null, type);
341+
return (X509TrustManager) SSLUtil.getTrustManagers(algorithm, trustStore)[0];
342+
}
343+
323344
private SSLContext getSSLContext() {
324345
Object val = propertySource.apply(PREFIX + "sslContext");
325346
if (val != null) {

marklogic-client-api/src/main/java/com/marklogic/client/impl/SSLUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static TrustManager[] getDefaultTrustManagers() {
6060
* @param trustManagerAlgorithm e.g. "SunX509".
6161
* @param optionalKeyStore if not null, used to initialize the TrustManagerFactory constructed based on the
6262
* given algorithm.
63-
* @return
63+
* @return an array of at least length 1 where the first instance is an {@code X509TrustManager}
6464
*/
6565
public static TrustManager[] getTrustManagers(String trustManagerAlgorithm, KeyStore optionalKeyStore) {
6666
TrustManagerFactory trustManagerFactory;

marklogic-client-api/src/test/java/com/marklogic/client/test/ssl/TwoWaySSLTest.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.marklogic.client.test.junit5.RequireSSLExtension;
1313
import com.marklogic.mgmt.ManageClient;
1414
import com.marklogic.mgmt.resource.appservers.ServerManager;
15+
import com.marklogic.mgmt.resource.security.CertificateTemplateManager;
1516
import com.marklogic.rest.util.Fragment;
1617
import org.junit.jupiter.api.AfterAll;
1718
import org.junit.jupiter.api.BeforeAll;
@@ -74,6 +75,7 @@ public static void setup() throws Exception {
7475
createKeystoreFile(tempDir);
7576
keyStoreFile = new File(tempDir.toFile(), "client.jks");
7677
p12File = new File(tempDir.toFile(), "client.p12");
78+
addServerCertificateToKeyStore(tempDir);
7779
}
7880

7981
@AfterAll
@@ -99,11 +101,15 @@ void digestAuthentication() {
99101
DatabaseClient clientWithCert = Common.newClientBuilder()
100102
.withKeyStorePath(keyStoreFile.getAbsolutePath())
101103
.withKeyStorePassword(KEYSTORE_PASSWORD)
104+
102105
// Still need this as "common"/"strict" don't work for our temporary server certificate.
103106
.withSSLHostnameVerifier(DatabaseClientFactory.SSLHostnameVerifier.ANY)
104-
// This is a reasonable trust manager since it references the temporary server certificate as something
105-
// that it accepts instead of accepting everything.
106-
.withTrustManager(RequireSSLExtension.newSecureTrustManager())
107+
108+
// Starting in 6.5.0, we can use a real trust manager as the server certificate is in the keystore.
109+
.withTrustStorePath(keyStoreFile.getAbsolutePath())
110+
.withTrustStorePassword(KEYSTORE_PASSWORD)
111+
.withTrustStoreType("JKS")
112+
.withTrustStoreAlgorithm("SunX509")
107113
.build();
108114

109115
verifyTestDocumentCanBeRead(clientWithCert);
@@ -416,11 +422,7 @@ private static void createPkcs12File(Path tempDir) throws Exception {
416422
"-name", "my-client",
417423
"-passout", "pass:" + KEYSTORE_PASSWORD);
418424

419-
ExecutorService executorService = Executors.newSingleThreadExecutor();
420-
Process process = builder.start();
421-
executorService.submit(new StreamGobbler(process.getInputStream(), System.out::println));
422-
executorService.submit(new StreamGobbler(process.getErrorStream(), System.err::println));
423-
int exitCode = process.waitFor();
425+
int exitCode = runProcess(builder);
424426
assertEquals(0, exitCode, "Unable to create pkcs12 file using openssl");
425427
}
426428

@@ -436,12 +438,43 @@ private static void createKeystoreFile(Path tempDir) throws Exception {
436438
"-srcstorepass", KEYSTORE_PASSWORD,
437439
"-alias", "my-client");
438440

441+
int exitCode = runProcess(builder);
442+
assertEquals(0, exitCode, "Unable to create keystore using keytool");
443+
}
444+
445+
/**
446+
* Retrieves the server certificate associated with the certificate template for this test and stores it in the
447+
* key store so that the key store can also act as a trust store.
448+
*
449+
* @param tempDir
450+
* @throws Exception
451+
*/
452+
private static void addServerCertificateToKeyStore(Path tempDir) throws Exception {
453+
Fragment xml = new CertificateTemplateManager(Common.newManageClient()).getCertificatesForTemplate("java-unittest-template");
454+
String serverCertificate = xml.getElementValue("/msec:certificate-list/msec:certificate/msec:pem");
455+
456+
File certificateFile = new File(tempDir.toFile(), "server.cert");
457+
FileCopyUtils.copy(serverCertificate.getBytes(), certificateFile);
458+
459+
ProcessBuilder builder = new ProcessBuilder();
460+
builder.directory(tempDir.toFile());
461+
builder.command("keytool", "-importcert",
462+
"-keystore", keyStoreFile.getAbsolutePath(),
463+
"-storepass", KEYSTORE_PASSWORD,
464+
"-file", certificateFile.getAbsolutePath(),
465+
"-noprompt",
466+
"-alias", "java-unittest-template-certificate");
467+
468+
int exitCode = runProcess(builder);
469+
assertEquals(0, exitCode, "Unable to add server public certificate to keystore.");
470+
}
471+
472+
private static int runProcess(ProcessBuilder builder) throws Exception {
439473
Process process = builder.start();
440474
ExecutorService executorService = Executors.newSingleThreadExecutor();
441475
executorService.submit(new StreamGobbler(process.getInputStream(), System.out::println));
442476
executorService.submit(new StreamGobbler(process.getErrorStream(), System.err::println));
443-
int exitCode = process.waitFor();
444-
assertEquals(0, exitCode, "Unable to create keystore using keytool");
477+
return process.waitFor();
445478
}
446479

447480
/**

0 commit comments

Comments
 (0)