Skip to content

Commit 0c245f9

Browse files
committed
DEVEXP-585 Can now configure 2-way SSL
Bumped the version to 6.4-SNAPSHOT as well so we can start using it in the Mule connector. The main value here is in TwoWaySSLTest, which can use these new properties instead of having to construct an SSLContext itself.
1 parent 421350d commit 0c245f9

File tree

6 files changed

+284
-62
lines changed

6 files changed

+284
-62
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group=com.marklogic
2-
version=6.3-SNAPSHOT
2+
version=6.4-SNAPSHOT
33
describedName=MarkLogic Java Client API
44
publishUrl=file:../marklogic-java/releases
55

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,48 @@ public DatabaseClientBuilder withGzippedResponsesDisabled() {
259259
props.put(PREFIX + "disableGzippedResponses", true);
260260
return this;
261261
}
262+
263+
/**
264+
* Enables 2-way SSL by creating an SSL context based on the given key store path.
265+
*
266+
* @param path
267+
* @return
268+
* @since 6.4.0
269+
*/
270+
public DatabaseClientBuilder withKeyStorePath(String path) {
271+
props.put(PREFIX + "ssl.keystore.path", path);
272+
return this;
273+
}
274+
275+
/**
276+
* @param password optional password for a key store
277+
* @return
278+
* @since 6.4.0
279+
*/
280+
public DatabaseClientBuilder withKeyStorePassword(String password) {
281+
props.put(PREFIX + "ssl.keystore.password", password);
282+
return this;
283+
}
284+
285+
/**
286+
* @param type e.g. "JKS"
287+
* @return
288+
* @since 6.4.0
289+
*/
290+
public DatabaseClientBuilder withKeyStoreType(String type) {
291+
props.put(PREFIX + "ssl.keystore.type", type);
292+
return this;
293+
}
294+
295+
/**
296+
* @param algorithm e.g. "SunX509"
297+
* @return
298+
* @since 6.4.0
299+
*/
300+
public DatabaseClientBuilder withKeyStoreAlgorithm(String algorithm) {
301+
props.put(PREFIX + "ssl.keystore.algorithm", algorithm);
302+
return this;
303+
}
262304
}
263305

264306

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
@@ -1299,6 +1299,10 @@ public String getCertificatePassword() {
12991299
* a String with a value of either "any", "common", or "strict"</li>
13001300
* <li>marklogic.client.trustManager = must be an instance of {@code javax.net.ssl.X509TrustManager};
13011301
* if not specified and an SSL context is configured, an attempt will be made to use the JVM's default trust manager</li>
1302+
* <li>marklogic.client.ssl.keystore.path = must be a String; enables 2-way SSL if set; since 6.4.0.</li>
1303+
* <li>marklogic.client.ssl.keystore.password = must be a String; optional password for a key store; since 6.4.0.</li>
1304+
* <li>marklogic.client.ssl.keystore.type = must be a String; optional type for a key store, defaults to "JKS"; since 6.4.0.</li>
1305+
* <li>marklogic.client.ssl.keystore.algorithm = must be a String; optional algorithm for a key store, defaults to "SunX509"; since 6.4.0.</li>
13021306
* </ol>
13031307
*
13041308
* @param propertySource

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

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ private DatabaseClientFactory.SecurityContext newSecurityContext() {
151151
}
152152
final String authType = (String) typeValue;
153153

154-
final SSLInputs sslInputs = buildSSLInputs(authType);
154+
final SSLUtil.SSLInputs sslInputs = buildSSLInputs(authType);
155155
DatabaseClientFactory.SecurityContext securityContext = newSecurityContext(authType, sslInputs);
156156
if (sslInputs.getSslContext() != null) {
157157
securityContext.withSSLContext(sslInputs.getSslContext(), sslInputs.getTrustManager());
@@ -160,7 +160,7 @@ private DatabaseClientFactory.SecurityContext newSecurityContext() {
160160
return securityContext;
161161
}
162162

163-
private DatabaseClientFactory.SecurityContext newSecurityContext(String type, SSLInputs sslInputs) {
163+
private DatabaseClientFactory.SecurityContext newSecurityContext(String type, SSLUtil.SSLInputs sslInputs) {
164164
switch (type.toLowerCase()) {
165165
case DatabaseClientBuilder.AUTH_TYPE_BASIC:
166166
return newBasicAuthContext();
@@ -188,11 +188,15 @@ private String getRequiredStringValue(String propertyName) {
188188
}
189189

190190
private String getNullableStringValue(String propertyName) {
191+
return getNullableStringValue(propertyName, null);
192+
}
193+
194+
private String getNullableStringValue(String propertyName, String defaultValue) {
191195
Object value = propertySource.apply(PREFIX + propertyName);
192196
if (value != null && !(value instanceof String)) {
193197
throw new IllegalArgumentException(propertyName + " must be of type String");
194198
}
195-
return (String) value;
199+
return value != null ? (String) value : defaultValue;
196200
}
197201

198202
private DatabaseClientFactory.SecurityContext newBasicAuthContext() {
@@ -221,7 +225,7 @@ private DatabaseClientFactory.SecurityContext newCloudAuthContext() {
221225
return new DatabaseClientFactory.MarkLogicCloudAuthContext(apiKey, duration);
222226
}
223227

224-
private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInputs sslInputs) {
228+
private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLUtil.SSLInputs sslInputs) {
225229
String file = getNullableStringValue("certificate.file");
226230
String password = getNullableStringValue("certificate.password");
227231
if (file != null && file.trim().length() > 0) {
@@ -234,6 +238,9 @@ private DatabaseClientFactory.SecurityContext newCertificateAuthContext(SSLInput
234238
throw new RuntimeException("Unable to create CertificateAuthContext; cause " + e.getMessage(), e);
235239
}
236240
}
241+
if (sslInputs.getSslContext() == null) {
242+
throw new RuntimeException("An SSLContext is required for certificate authentication.");
243+
}
237244
return new DatabaseClientFactory.CertificateAuthContext(sslInputs.getSslContext(), sslInputs.getTrustManager());
238245
}
239246

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

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

283-
// Approaches 2 and 3 - user defines an SSL protocol.
284-
// Approach 2 - "default" is a convenience for using the JVM's default SSLContext.
285-
// Approach 3 - create a new SSLContext, and initialize it if the user-provided TrustManager is not null.
290+
// Approach 2 - user wants two-way SSL via a keystore.
291+
final String keyStorePath = getNullableStringValue("ssl.keystore.path");
292+
if (keyStorePath != null && keyStorePath.trim().length() > 0) {
293+
return useKeyStoreForTwoWaySSL(keyStorePath, userTrustManager);
294+
}
295+
296+
// Approaches 3 and 4 - user defines an SSL protocol.
297+
// Approach 3 - "default" is a convenience for using the JVM's default SSLContext.
298+
// Approach 4 - create a new SSLContext, and initialize it if the user-provided TrustManager is not null.
286299
final String sslProtocol = getSSLProtocol(authType);
287300
if (sslProtocol != null) {
288301
return "default".equalsIgnoreCase(sslProtocol) ?
289302
useDefaultSSLContext(userTrustManager) :
290303
useNewSSLContext(sslProtocol, userTrustManager);
291304
}
292305

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

298311
private X509TrustManager getTrustManager() {
@@ -332,27 +345,36 @@ private String getSSLProtocol(String authType) {
332345
return sslProtocol;
333346
}
334347

348+
private SSLUtil.SSLInputs useKeyStoreForTwoWaySSL(String keyStorePath, X509TrustManager userTrustManager) {
349+
final String password = getNullableStringValue("ssl.keystore.password");
350+
final String keyStoreType = getNullableStringValue("ssl.keystore.type", "JKS");
351+
final String algorithm = getNullableStringValue("ssl.keystore.algorithm", "SunX509");
352+
final char[] charPassword = password != null ? password.toCharArray() : null;
353+
final String sslProtocol = getNullableStringValue("sslProtocol", "TLSv1.2");
354+
return SSLUtil.createSSLContextFromKeyStore(keyStorePath, charPassword, keyStoreType, algorithm, sslProtocol, userTrustManager);
355+
}
356+
335357
/**
336358
* Uses the JVM's default SSLContext. Because OkHttp requires a separate TrustManager, this approach will either
337359
* user the user-provided TrustManager or it will assume that the JVM's default TrustManager should be used.
338360
*/
339-
private SSLInputs useDefaultSSLContext(X509TrustManager userTrustManager) {
361+
private SSLUtil.SSLInputs useDefaultSSLContext(X509TrustManager userTrustManager) {
340362
SSLContext sslContext;
341363
try {
342364
sslContext = SSLContext.getDefault();
343365
} catch (NoSuchAlgorithmException e) {
344366
throw new RuntimeException("Unable to obtain default SSLContext; cause: " + e.getMessage(), e);
345367
}
346368
X509TrustManager trustManager = userTrustManager != null ? userTrustManager : SSLUtil.getDefaultTrustManager();
347-
return new SSLInputs(sslContext, trustManager);
369+
return new SSLUtil.SSLInputs(sslContext, trustManager);
348370
}
349371

350372
/**
351373
* Constructs a new SSLContext based on the given protocol (e.g. TLSv1.2). The SSLContext will be initialized if
352374
* the user's TrustManager is not null. Otherwise, OkHttpUtil will eventually initialize the SSLContext using the
353375
* JVM's default TrustManager.
354376
*/
355-
private SSLInputs useNewSSLContext(String sslProtocol, X509TrustManager userTrustManager) {
377+
private SSLUtil.SSLInputs useNewSSLContext(String sslProtocol, X509TrustManager userTrustManager) {
356378
SSLContext sslContext;
357379
try {
358380
sslContext = SSLContext.getInstance(sslProtocol);
@@ -368,27 +390,6 @@ private SSLInputs useNewSSLContext(String sslProtocol, X509TrustManager userTrus
368390
sslProtocol, e.getMessage()), e);
369391
}
370392
}
371-
return new SSLInputs(sslContext, userTrustManager);
372-
}
373-
374-
/**
375-
* Captures the inputs provided by the caller that pertain to constructing an SSLContext.
376-
*/
377-
private static class SSLInputs {
378-
private final SSLContext sslContext;
379-
private final X509TrustManager trustManager;
380-
381-
public SSLInputs(SSLContext sslContext, X509TrustManager trustManager) {
382-
this.sslContext = sslContext;
383-
this.trustManager = trustManager;
384-
}
385-
386-
public SSLContext getSslContext() {
387-
return sslContext;
388-
}
389-
390-
public X509TrustManager getTrustManager() {
391-
return trustManager;
392-
}
393+
return new SSLUtil.SSLInputs(sslContext, userTrustManager);
393394
}
394395
}

0 commit comments

Comments
 (0)