Skip to content

Commit 23bdc90

Browse files
committed
JAVA-2514: Enable configuration of a custom SSLContext
1 parent 3770623 commit 23bdc90

File tree

7 files changed

+166
-18
lines changed

7 files changed

+166
-18
lines changed

build.gradle

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,15 @@ configure(subprojects.findAll { it.name != 'util' && it.name != 'mongo-java-driv
194194
maxParallelForks = 1
195195

196196
systemProperties(
197-
'org.mongodb.test.uri': System.getProperty('org.mongodb.test.uri', null),
197+
'org.mongodb.test.uri': System.getProperty('org.mongodb.test.uri'),
198198
'org.mongodb.useSocket': System.getProperty('org.mongodb.useSocket', 'false'),
199199
'org.mongodb.disableAsync': System.getProperty('org.mongodb.disableAsync', 'false'),
200200
'org.mongodb.async.type': System.getProperty('org.mongodb.async.type', 'nio2'),
201201

202-
'javax.net.ssl.trustStore': System.getProperty('javax.net.ssl.trustStore', "${System.getProperty('user.home')}/.keystore"),
203-
'javax.net.ssl.keyStore': System.getProperty('javax.net.ssl.keyStore', "${System.getProperty('user.home')}/.keystore"),
204-
'javax.net.ssl.keyStorePassword': System.getProperty('javax.net.ssl.keyStorePassword', 'changeit'),
205-
'javax.net.ssl.trustStorePassword': System.getProperty('javax.net.ssl.trustStorePassword', 'changeit')
202+
'javax.net.ssl.trustStore': System.getProperty('javax.net.ssl.trustStore'),
203+
'javax.net.ssl.keyStore': System.getProperty('javax.net.ssl.keyStore'),
204+
'javax.net.ssl.keyStorePassword': System.getProperty('javax.net.ssl.keyStorePassword'),
205+
'javax.net.ssl.trustStorePassword': System.getProperty('javax.net.ssl.trustStorePassword')
206206
)
207207

208208
if (project.buildingWith('ssl.enabled')) {

driver-core/src/main/com/mongodb/connection/SocketStreamFactory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package com.mongodb.connection;
1818

19+
import com.mongodb.MongoClientException;
1920
import com.mongodb.ServerAddress;
2021
import com.mongodb.internal.connection.PowerOfTwoBufferPool;
2122

2223
import javax.net.SocketFactory;
23-
import javax.net.ssl.SSLSocketFactory;
24+
import javax.net.ssl.SSLContext;
25+
import java.security.NoSuchAlgorithmException;
2426

2527
import static com.mongodb.assertions.Assertions.notNull;
2628

@@ -64,7 +66,7 @@ public Stream create(final ServerAddress serverAddress) {
6466
if (socketFactory != null) {
6567
stream = new SocketStream(serverAddress, settings, sslSettings, socketFactory, bufferProvider);
6668
} else if (sslSettings.isEnabled()) {
67-
stream = new SocketStream(serverAddress, settings, sslSettings, SSLSocketFactory.getDefault(), bufferProvider);
69+
stream = new SocketStream(serverAddress, settings, sslSettings, getSslContext().getSocketFactory(), bufferProvider);
6870
} else if (System.getProperty("org.mongodb.useSocket", "false").equals("true")) {
6971
stream = new SocketStream(serverAddress, settings, sslSettings, SocketFactory.getDefault(), bufferProvider);
7072
} else {
@@ -74,4 +76,11 @@ public Stream create(final ServerAddress serverAddress) {
7476
return stream;
7577
}
7678

79+
private SSLContext getSslContext() {
80+
try {
81+
return (sslSettings.getContext() == null) ? SSLContext.getDefault() : sslSettings.getContext();
82+
} catch (NoSuchAlgorithmException e) {
83+
throw new MongoClientException("Unable to create default SSLContext", e);
84+
}
85+
}
7786
}

driver-core/src/main/com/mongodb/connection/SslSettings.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.mongodb.annotations.Immutable;
2222
import com.mongodb.annotations.NotThreadSafe;
2323

24+
import javax.net.ssl.SSLContext;
25+
2426
/**
2527
* Settings for connecting to MongoDB via SSL.
2628
*
@@ -30,6 +32,7 @@
3032
public class SslSettings {
3133
private final boolean enabled;
3234
private final boolean invalidHostNameAllowed;
35+
private final SSLContext context;
3336

3437
/**
3538
* Gets a Builder for creating a new SSLSettings instance.
@@ -47,6 +50,7 @@ public static Builder builder() {
4750
public static class Builder {
4851
private boolean enabled;
4952
private boolean invalidHostNameAllowed;
53+
private SSLContext context;
5054

5155
/**
5256
* Define whether SSL should be enabled.
@@ -71,6 +75,18 @@ public Builder invalidHostNameAllowed(final boolean invalidHostNameAllowed) {
7175
return this;
7276
}
7377

78+
/**
79+
* Sets the SSLContext for use when SSL is enabled.
80+
*
81+
* @param context the SSLContext to use for connections. Ignored if SSL is not enabled.
82+
* @return this
83+
* @since 3.5
84+
*/
85+
public Builder context(final SSLContext context) {
86+
this.context = context;
87+
return this;
88+
}
89+
7490
/**
7591
* Take the settings from the given ConnectionString and set them in this builder.
7692
*
@@ -118,6 +134,18 @@ public boolean isInvalidHostNameAllowed() {
118134
return invalidHostNameAllowed;
119135
}
120136

137+
/**
138+
* Gets the SSLContext configured for use with SSL connections.
139+
*
140+
* @return the SSLContext, which defaults to null if not configured. In that case {@code SSLContext.getDefault()} will be used if SSL
141+
* is enabled.
142+
* @since 3.5
143+
* @see SSLContext#getDefault()
144+
*/
145+
public SSLContext getContext() {
146+
return context;
147+
}
148+
121149
SslSettings(final Builder builder) {
122150
enabled = builder.enabled;
123151
invalidHostNameAllowed = builder.invalidHostNameAllowed;
@@ -126,6 +154,7 @@ public boolean isInvalidHostNameAllowed() {
126154
+ "must run on Java 6, you must set the SslSettings.invalidHostNameAllowed property to "
127155
+ "true");
128156
}
157+
context = builder.context;
129158
}
130159

131160
@Override
@@ -145,14 +174,14 @@ public boolean equals(final Object o) {
145174
if (invalidHostNameAllowed != that.invalidHostNameAllowed) {
146175
return false;
147176
}
148-
149-
return true;
177+
return context != null ? context.equals(that.context) : that.context == null;
150178
}
151179

152180
@Override
153181
public int hashCode() {
154182
int result = (enabled ? 1 : 0);
155183
result = 31 * result + (invalidHostNameAllowed ? 1 : 0);
184+
result = 31 * result + (context != null ? context.hashCode() : 0);
156185
return result;
157186
}
158187

@@ -161,6 +190,7 @@ public String toString() {
161190
return "SslSettings{"
162191
+ "enabled=" + enabled
163192
+ ", invalidHostNameAllowed=" + invalidHostNameAllowed
193+
+ ", context=" + context
164194
+ '}';
165195
}
166196
}

driver-core/src/main/com/mongodb/connection/netty/NettyStream.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.mongodb.connection.netty;
1818

19+
import com.mongodb.MongoClientException;
1920
import com.mongodb.MongoException;
2021
import com.mongodb.MongoInternalException;
2122
import com.mongodb.MongoInterruptedException;
@@ -49,6 +50,7 @@
4950
import javax.net.ssl.SSLEngine;
5051
import javax.net.ssl.SSLParameters;
5152
import java.io.IOException;
53+
import java.security.NoSuchAlgorithmException;
5254
import java.util.Iterator;
5355
import java.util.LinkedList;
5456
import java.util.List;
@@ -121,7 +123,7 @@ public void openAsync(final AsyncCompletionHandler<Void> handler) {
121123
@Override
122124
public void initChannel(final SocketChannel ch) throws Exception {
123125
if (sslSettings.isEnabled()) {
124-
SSLEngine engine = SSLContext.getDefault().createSSLEngine(address.getHost(), address.getPort());
126+
SSLEngine engine = getSslContext().createSSLEngine(address.getHost(), address.getPort());
125127
engine.setUseClientMode(true);
126128
SSLParameters sslParameters = engine.getSSLParameters();
127129
enableSni(address, sslParameters);
@@ -308,6 +310,14 @@ public ByteBufAllocator getAllocator() {
308310
return allocator;
309311
}
310312

313+
private SSLContext getSslContext() {
314+
try {
315+
return (sslSettings.getContext() == null) ? SSLContext.getDefault() : sslSettings.getContext();
316+
} catch (NoSuchAlgorithmException e) {
317+
throw new MongoClientException("Unable to create default SSLContext", e);
318+
}
319+
}
320+
311321
private class InboundBufferHandler extends SimpleChannelInboundHandler<io.netty.buffer.ByteBuf> {
312322
@Override
313323
protected void channelRead0(final ChannelHandlerContext ctx, final io.netty.buffer.ByteBuf buffer) throws Exception {

driver-core/src/test/unit/com/mongodb/connection/SslSettingsSpecification.groovy

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import com.mongodb.MongoInternalException
2121
import spock.lang.IgnoreIf
2222
import spock.lang.Specification
2323

24+
import javax.net.ssl.SSLContext
25+
2426
import static com.mongodb.ClusterFixture.isNotAtLeastJava7
2527
import static com.mongodb.connection.SslSettings.builder
2628

@@ -47,6 +49,16 @@ class SslSettingsSpecification extends Specification {
4749
builder().invalidHostNameAllowed(true).build().invalidHostNameAllowed
4850
}
4951

52+
def 'should default to null SSLContext'() {
53+
expect:
54+
builder().build().getContext() == null
55+
}
56+
57+
def 'should set SSLContext'() {
58+
expect:
59+
builder().context(SSLContext.getDefault()).build().getContext() == SSLContext.getDefault()
60+
}
61+
5062
def 'should not allow invalid host name on Java 6'() {
5163
given:
5264
String javaVersion = System.getProperty('java.version')
@@ -96,15 +108,25 @@ class SslSettingsSpecification extends Specification {
96108
expect:
97109
builder().build() == builder().build()
98110
builder().build().hashCode() == builder().build().hashCode()
99-
builder().enabled(true).invalidHostNameAllowed(true).build() == builder().enabled(true).invalidHostNameAllowed(true).build()
111+
builder().enabled(true).invalidHostNameAllowed(true).build() ==
112+
builder().enabled(true).invalidHostNameAllowed(true).build()
100113
builder().enabled(true).invalidHostNameAllowed(true).build().hashCode() ==
101-
builder().enabled(true).invalidHostNameAllowed(true).build().hashCode()
114+
builder().enabled(true).invalidHostNameAllowed(true).build().hashCode()
115+
builder().enabled(true).invalidHostNameAllowed(true).context(SSLContext.getDefault()).build() ==
116+
builder().enabled(true).invalidHostNameAllowed(true)
117+
.context(SSLContext.getDefault()).build()
118+
builder().enabled(true).invalidHostNameAllowed(true)
119+
.context(SSLContext.getDefault()).build().hashCode() ==
120+
builder().enabled(true).invalidHostNameAllowed(true)
121+
.context(SSLContext.getDefault()).build().hashCode()
122+
102123
}
103124

104125
@IgnoreIf({ isNotAtLeastJava7() })
105126
def 'unequivalent settings should not be equal or have the same hash code'() {
106127
expect:
107128
builder().build() != builder().enabled(true).build()
108129
builder().build() != builder().invalidHostNameAllowed(true).build()
130+
builder().build() != builder().context(SSLContext.getDefault()).build()
109131
}
110132
}

driver/src/main/com/mongodb/MongoClientOptions.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.bson.codecs.configuration.CodecRegistry;
3030

3131
import javax.net.SocketFactory;
32+
import javax.net.ssl.SSLContext;
3233
import javax.net.ssl.SSLSocketFactory;
3334
import java.nio.charset.Charset;
3435
import java.util.ArrayList;
@@ -74,6 +75,7 @@ public class MongoClientOptions {
7475
private final boolean socketKeepAlive;
7576
private final boolean sslEnabled;
7677
private final boolean sslInvalidHostNameAllowed;
78+
private final SSLContext sslContext;
7779
private final boolean alwaysUseMBeans;
7880
private final int heartbeatFrequency;
7981
private final int minHeartbeatFrequency;
@@ -115,6 +117,7 @@ private MongoClientOptions(final Builder builder) {
115117
codecRegistry = builder.codecRegistry;
116118
sslEnabled = builder.sslEnabled;
117119
sslInvalidHostNameAllowed = builder.sslInvalidHostNameAllowed;
120+
sslContext = builder.sslContext;
118121
alwaysUseMBeans = builder.alwaysUseMBeans;
119122
heartbeatFrequency = builder.heartbeatFrequency;
120123
minHeartbeatFrequency = builder.minHeartbeatFrequency;
@@ -169,6 +172,7 @@ private MongoClientOptions(final Builder builder) {
169172
sslSettings = SslSettings.builder()
170173
.enabled(sslEnabled)
171174
.invalidHostNameAllowed(sslInvalidHostNameAllowed)
175+
.context(sslContext)
172176
.build();
173177
} catch (MongoInternalException e) {
174178
// The error message from SslSettings needs to be translated to make sense for users of MongoClientOptions
@@ -460,6 +464,16 @@ public boolean isSslInvalidHostNameAllowed() {
460464
return sslInvalidHostNameAllowed;
461465
}
462466

467+
/**
468+
* Returns the SSLContext. This property is ignored when either sslEnabled is false or socketFactory is non-null.
469+
*
470+
* @return the configured SSLContext, which may be null. In that case {@code SSLContext.getDefault()} will be used when SSL is enabled.
471+
* @since 3.5
472+
*/
473+
public SSLContext getSslContext() {
474+
return sslContext;
475+
}
476+
463477
/**
464478
* <p>The read preference to use for queries, map-reduce, aggregation, and count.</p>
465479
*
@@ -598,7 +612,7 @@ public SocketFactory getSocketFactory() {
598612
if (socketFactory != null) {
599613
return socketFactory;
600614
} else if (getSslSettings().isEnabled()) {
601-
return DEFAULT_SSL_SOCKET_FACTORY;
615+
return sslContext == null ? DEFAULT_SSL_SOCKET_FACTORY : sslContext.getSocketFactory();
602616
} else {
603617
return DEFAULT_SOCKET_FACTORY;
604618
}
@@ -703,6 +717,9 @@ public boolean equals(final Object o) {
703717
if (sslInvalidHostNameAllowed != that.sslInvalidHostNameAllowed) {
704718
return false;
705719
}
720+
if (sslContext != null ? !sslContext.equals(that.sslContext) : that.sslContext != null) {
721+
return false;
722+
}
706723
if (threadsAllowedToBlockForConnectionMultiplier != that.threadsAllowedToBlockForConnectionMultiplier) {
707724
return false;
708725
}
@@ -777,6 +794,7 @@ public int hashCode() {
777794
result = 31 * result + (socketKeepAlive ? 1 : 0);
778795
result = 31 * result + (sslEnabled ? 1 : 0);
779796
result = 31 * result + (sslInvalidHostNameAllowed ? 1 : 0);
797+
result = 31 * result + (sslContext != null ? sslContext.hashCode() : 0);
780798
result = 31 * result + (alwaysUseMBeans ? 1 : 0);
781799
result = 31 * result + heartbeatFrequency;
782800
result = 31 * result + minHeartbeatFrequency;
@@ -816,6 +834,7 @@ public String toString() {
816834
+ ", socketKeepAlive=" + socketKeepAlive
817835
+ ", sslEnabled=" + sslEnabled
818836
+ ", sslInvalidHostNamesAllowed=" + sslInvalidHostNameAllowed
837+
+ ", sslContext=" + sslContext
819838
+ ", alwaysUseMBeans=" + alwaysUseMBeans
820839
+ ", heartbeatFrequency=" + heartbeatFrequency
821840
+ ", minHeartbeatFrequency=" + minHeartbeatFrequency
@@ -864,6 +883,7 @@ public static class Builder {
864883
private boolean socketKeepAlive = false;
865884
private boolean sslEnabled = false;
866885
private boolean sslInvalidHostNameAllowed = false;
886+
private SSLContext sslContext;
867887
private boolean alwaysUseMBeans = false;
868888

869889
private int heartbeatFrequency = 10000;
@@ -913,6 +933,7 @@ public Builder(final MongoClientOptions options) {
913933
codecRegistry = options.getCodecRegistry();
914934
sslEnabled = options.isSslEnabled();
915935
sslInvalidHostNameAllowed = options.isSslInvalidHostNameAllowed();
936+
sslContext = options.getSslContext();
916937
alwaysUseMBeans = options.isAlwaysUseMBeans();
917938
heartbeatFrequency = options.getHeartbeatFrequency();
918939
minHeartbeatFrequency = options.getMinHeartbeatFrequency();
@@ -1135,6 +1156,18 @@ public Builder sslInvalidHostNameAllowed(final boolean sslInvalidHostNameAllowed
11351156
return this;
11361157
}
11371158

1159+
/**
1160+
* Sets the SSLContext to be used with SSL is enabled. This property is ignored when either sslEnabled is false or socketFactory is
1161+
* non-null.
1162+
*
1163+
* @param sslContext the SSLContext to be used for SSL connections
1164+
* @return {@code this}
1165+
* @since 3.5
1166+
*/
1167+
public Builder sslContext(final SSLContext sslContext) {
1168+
this.sslContext = sslContext;
1169+
return this;
1170+
}
11381171

11391172
/**
11401173
* Sets the read preference.

0 commit comments

Comments
 (0)