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
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=com.marklogic
version=6.3-SNAPSHOT
version=6.4-SNAPSHOT
describedName=MarkLogic Java Client API
publishUrl=file:../marklogic-java/releases

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,48 @@ public DatabaseClientBuilder withGzippedResponsesDisabled() {
props.put(PREFIX + "disableGzippedResponses", true);
return this;
}

/**
* Enables 2-way SSL by creating an SSL context based on the given key store path.
*
* @param path
* @return
* @since 6.4.0
*/
public DatabaseClientBuilder withKeyStorePath(String path) {
props.put(PREFIX + "ssl.keystore.path", path);
return this;
}

/**
* @param password optional password for a key store
* @return
* @since 6.4.0
*/
public DatabaseClientBuilder withKeyStorePassword(String password) {
props.put(PREFIX + "ssl.keystore.password", password);
return this;
}

/**
* @param type e.g. "JKS"
* @return
* @since 6.4.0
*/
public DatabaseClientBuilder withKeyStoreType(String type) {
props.put(PREFIX + "ssl.keystore.type", type);
return this;
}

/**
* @param algorithm e.g. "SunX509"
* @return
* @since 6.4.0
*/
public DatabaseClientBuilder withKeyStoreAlgorithm(String algorithm) {
props.put(PREFIX + "ssl.keystore.algorithm", algorithm);
return this;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,10 @@ public String getCertificatePassword() {
* a String with a value of either "any", "common", or "strict"</li>
* <li>marklogic.client.trustManager = must be an instance of {@code javax.net.ssl.X509TrustManager};
* if not specified and an SSL context is configured, an attempt will be made to use the JVM's default trust manager</li>
* <li>marklogic.client.ssl.keystore.path = must be a String; enables 2-way SSL if set; since 6.4.0.</li>
* <li>marklogic.client.ssl.keystore.password = must be a String; optional password for a key store; since 6.4.0.</li>
* <li>marklogic.client.ssl.keystore.type = must be a String; optional type for a key store, defaults to "JKS"; since 6.4.0.</li>
* <li>marklogic.client.ssl.keystore.algorithm = must be a String; optional algorithm for a key store, defaults to "SunX509"; since 6.4.0.</li>
* </ol>
*
* @param propertySource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private DatabaseClientFactory.SecurityContext newSecurityContext() {
}
final String authType = (String) typeValue;

final SSLInputs sslInputs = buildSSLInputs(authType);
final SSLUtil.SSLInputs sslInputs = buildSSLInputs(authType);
DatabaseClientFactory.SecurityContext securityContext = newSecurityContext(authType, sslInputs);
if (sslInputs.getSslContext() != null) {
securityContext.withSSLContext(sslInputs.getSslContext(), sslInputs.getTrustManager());
Expand All @@ -160,7 +160,7 @@ private DatabaseClientFactory.SecurityContext newSecurityContext() {
return securityContext;
}

private DatabaseClientFactory.SecurityContext newSecurityContext(String type, SSLInputs sslInputs) {
private DatabaseClientFactory.SecurityContext newSecurityContext(String type, SSLUtil.SSLInputs sslInputs) {
switch (type.toLowerCase()) {
case DatabaseClientBuilder.AUTH_TYPE_BASIC:
return newBasicAuthContext();
Expand Down Expand Up @@ -188,11 +188,15 @@ private String getRequiredStringValue(String propertyName) {
}

private String getNullableStringValue(String propertyName) {
return getNullableStringValue(propertyName, null);
}

private String getNullableStringValue(String propertyName, String defaultValue) {
Object value = propertySource.apply(PREFIX + propertyName);
if (value != null && !(value instanceof String)) {
throw new IllegalArgumentException(propertyName + " must be of type String");
}
return (String) value;
return value != null ? (String) value : defaultValue;
}

private DatabaseClientFactory.SecurityContext newBasicAuthContext() {
Expand Down Expand Up @@ -221,7 +225,7 @@ private DatabaseClientFactory.SecurityContext newCloudAuthContext() {
return new DatabaseClientFactory.MarkLogicCloudAuthContext(apiKey, duration);
}

private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInputs sslInputs) {
private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLUtil.SSLInputs sslInputs) {
String file = getNullableStringValue("certificate.file");
String password = getNullableStringValue("certificate.password");
if (file != null && file.trim().length() > 0) {
Expand All @@ -234,6 +238,9 @@ private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInput
throw new RuntimeException("Unable to create CertificateAuthContext; cause " + e.getMessage(), e);
}
}
if (sslInputs.getSslContext() == null) {
throw new RuntimeException("An SSLContext is required for certificate authentication.");
}
return new DatabaseClientFactory.CertificateAuthContext(sslInputs.getSslContext(), sslInputs.getTrustManager());
}

Expand Down Expand Up @@ -271,28 +278,34 @@ private DatabaseClientFactory.SSLHostnameVerifier determineHostnameVerifier() {
* case the user does not define their own SSLContext or SSL protocol
* @return
*/
private SSLInputs buildSSLInputs(String authType) {
private SSLUtil.SSLInputs buildSSLInputs(String authType) {
X509TrustManager userTrustManager = getTrustManager();

// Approach 1 - user provides an SSLContext object, in which case there's nothing further to check.
SSLContext sslContext = getSSLContext();
if (sslContext != null) {
return new SSLInputs(sslContext, userTrustManager);
return new SSLUtil.SSLInputs(sslContext, userTrustManager);
}

// Approaches 2 and 3 - user defines an SSL protocol.
// Approach 2 - "default" is a convenience for using the JVM's default SSLContext.
// Approach 3 - create a new SSLContext, and initialize it if the user-provided TrustManager is not null.
// Approach 2 - user wants two-way SSL via a keystore.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had 4 approaches before, we now have 5 - the new approach number 2 is for two-way SSL via a keystore.

final String keyStorePath = getNullableStringValue("ssl.keystore.path");
if (keyStorePath != null && keyStorePath.trim().length() > 0) {
return useKeyStoreForTwoWaySSL(keyStorePath, userTrustManager);
}

// Approaches 3 and 4 - user defines an SSL protocol.
// Approach 3 - "default" is a convenience for using the JVM's default SSLContext.
// Approach 4 - create a new SSLContext, and initialize it if the user-provided TrustManager is not null.
final String sslProtocol = getSSLProtocol(authType);
if (sslProtocol != null) {
return "default".equalsIgnoreCase(sslProtocol) ?
useDefaultSSLContext(userTrustManager) :
useNewSSLContext(sslProtocol, userTrustManager);
}

// Approach 4 - still return the user-defined TrustManager as that may be needed for certificate authentication,
// Approach 5 - still return the user-defined TrustManager as that may be needed for certificate authentication,
// which has its own way of constructing an SSLContext from a PKCS12 file.
return new SSLInputs(null, userTrustManager);
return new SSLUtil.SSLInputs(null, userTrustManager);
}

private X509TrustManager getTrustManager() {
Expand Down Expand Up @@ -332,27 +345,36 @@ private String getSSLProtocol(String authType) {
return sslProtocol;
}

private SSLUtil.SSLInputs useKeyStoreForTwoWaySSL(String keyStorePath, X509TrustManager userTrustManager) {
final String password = getNullableStringValue("ssl.keystore.password");
final String keyStoreType = getNullableStringValue("ssl.keystore.type", "JKS");
final String algorithm = getNullableStringValue("ssl.keystore.algorithm", "SunX509");
final char[] charPassword = password != null ? password.toCharArray() : null;
final String sslProtocol = getNullableStringValue("sslProtocol", "TLSv1.2");
return SSLUtil.createSSLContextFromKeyStore(keyStorePath, charPassword, keyStoreType, algorithm, sslProtocol, userTrustManager);
}

/**
* Uses the JVM's default SSLContext. Because OkHttp requires a separate TrustManager, this approach will either
* user the user-provided TrustManager or it will assume that the JVM's default TrustManager should be used.
*/
private SSLInputs useDefaultSSLContext(X509TrustManager userTrustManager) {
private SSLUtil.SSLInputs useDefaultSSLContext(X509TrustManager userTrustManager) {
SSLContext sslContext;
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to obtain default SSLContext; cause: " + e.getMessage(), e);
}
X509TrustManager trustManager = userTrustManager != null ? userTrustManager : SSLUtil.getDefaultTrustManager();
return new SSLInputs(sslContext, trustManager);
return new SSLUtil.SSLInputs(sslContext, trustManager);
}

/**
* Constructs a new SSLContext based on the given protocol (e.g. TLSv1.2). The SSLContext will be initialized if
* the user's TrustManager is not null. Otherwise, OkHttpUtil will eventually initialize the SSLContext using the
* JVM's default TrustManager.
*/
private SSLInputs useNewSSLContext(String sslProtocol, X509TrustManager userTrustManager) {
private SSLUtil.SSLInputs useNewSSLContext(String sslProtocol, X509TrustManager userTrustManager) {
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance(sslProtocol);
Expand All @@ -368,27 +390,6 @@ private SSLInputs useNewSSLContext(String sslProtocol, X509TrustManager userTrus
sslProtocol, e.getMessage()), e);
}
}
return new SSLInputs(sslContext, userTrustManager);
}

/**
* Captures the inputs provided by the caller that pertain to constructing an SSLContext.
*/
private static class SSLInputs {
private final SSLContext sslContext;
private final X509TrustManager trustManager;

public SSLInputs(SSLContext sslContext, X509TrustManager trustManager) {
this.sslContext = sslContext;
this.trustManager = trustManager;
}

public SSLContext getSslContext() {
return sslContext;
}

public X509TrustManager getTrustManager() {
return trustManager;
}
return new SSLUtil.SSLInputs(sslContext, userTrustManager);
}
}
Loading